@@ -32,6 +32,7 @@ public interface INodeHandlerHelper
3232 string [ ] GetFilteredPossibleNodeFolders ( string nodeFolderName , string [ ] possibleNodeFolders ) ;
3333 string GetNodeFolderPath ( string nodeFolderName , IHostContext hostContext ) ;
3434 bool IsNodeFolderExist ( string nodeFolderName , IHostContext hostContext ) ;
35+ bool IsNodeExecutable ( string nodeFolder , IHostContext HostContext , IExecutionContext ExecutionContext ) ;
3536 }
3637
3738 public class NodeHandlerHelper : INodeHandlerHelper
@@ -52,6 +53,36 @@ public string[] GetFilteredPossibleNodeFolders(string nodeFolderName, string[] p
5253 possibleNodeFolders . Skip ( nodeFolderIndex + 1 ) . ToArray ( )
5354 : Array . Empty < string > ( ) ;
5455 }
56+
57+ public bool IsNodeExecutable ( string nodeFolder , IHostContext HostContext , IExecutionContext ExecutionContext )
58+ {
59+ if ( ! this . IsNodeFolderExist ( nodeFolder , HostContext ) )
60+ {
61+ ExecutionContext . Debug ( $ "Node folder does not exist: { nodeFolder } ") ;
62+ return false ;
63+ }
64+ var nodePath = Path . Combine ( HostContext . GetDirectory ( WellKnownDirectory . Externals ) , nodeFolder , "bin" , $ "node{ IOUtil . ExeExtension } ") ;
65+ const int NodeNotExecutableExitCode = 216 ;
66+ try
67+ {
68+ var processInvoker = HostContext . CreateService < IProcessInvoker > ( ) ;
69+ var exitCodeTask = processInvoker . ExecuteAsync (
70+ workingDirectory : HostContext . GetDirectory ( WellKnownDirectory . Work ) ,
71+ fileName : nodePath ,
72+ arguments : "-v" ,
73+ environment : null ,
74+ requireExitCodeZero : false ,
75+ outputEncoding : null ,
76+ cancellationToken : CancellationToken . None ) ;
77+ int exitCode = exitCodeTask . GetAwaiter ( ) . GetResult ( ) ;
78+ return exitCode != NodeNotExecutableExitCode ;
79+ }
80+ catch ( Exception ex )
81+ {
82+ ExecutionContext . Debug ( $ "Node executable test threw exception: { ex . Message } ") ;
83+ return false ;
84+ }
85+ }
5586 }
5687
5788 public sealed class NodeHandler : Handler , INodeHandler
@@ -215,7 +246,7 @@ public async Task RunAsync()
215246 supportsNode20 = ! node20ResultsInGlibCErrorHost ;
216247 }
217248 }
218-
249+
219250 if ( ! useNode24InUnsupportedSystem )
220251 {
221252 if ( supportsNode24 . HasValue )
@@ -340,24 +371,24 @@ private string GetNodeFolderWithFallback(string preferredNodeFolder, bool node20
340371 switch ( preferredNodeFolder )
341372 {
342373 case var folder when folder == NodeHandler . Node24Folder :
343- // Fallback if Node24 has glibc error OR doesn't exist (e.g., win-x86)
344- bool node24NotAvailable = ! nodeHandlerHelper . IsNodeFolderExist ( NodeHandler . Node24Folder , HostContext ) ;
345- if ( node24ResultsInGlibCError || node24NotAvailable )
374+ // Fallback if Node24 has glibc error OR doesn't exist (e.g., win-x86) or not executable (e.g, windows 2012 R2)
375+ bool node24NotExecutable = ! nodeHandlerHelper . IsNodeExecutable ( NodeHandler . Node24Folder , this . HostContext , this . ExecutionContext ) ;
376+ if ( node24ResultsInGlibCError || node24NotExecutable )
346377 {
347378 // Fallback to Node20, then Node16 if Node20 also fails or doesn't exist
348379 bool node20NotAvailableForNode24Fallback = ! nodeHandlerHelper . IsNodeFolderExist ( NodeHandler . Node20_1Folder , HostContext ) ;
349380 if ( node20ResultsInGlibCError || node20NotAvailableForNode24Fallback )
350381 {
351- fallbackReason = node24NotAvailable ? "NodeNotAvailable " : "GlibCError" ;
382+ fallbackReason = node24NotExecutable ? "NodeNotExecutable " : "GlibCError" ;
352383 fallbackOccurred = true ;
353- NodeFallbackWarning ( "24" , "16" , inContainer , node24NotAvailable ) ;
384+ NodeFallbackWarning ( "24" , "16" , inContainer , node24NotExecutable ) ;
354385 return NodeHandler . Node16Folder ;
355386 }
356387 else
357388 {
358- fallbackReason = node24NotAvailable ? "NodeNotAvailable " : "GlibCError" ;
389+ fallbackReason = node24NotExecutable ? "NodeNotExecutable " : "GlibCError" ;
359390 fallbackOccurred = true ;
360- NodeFallbackWarning ( "24" , "20" , inContainer , node24NotAvailable ) ;
391+ NodeFallbackWarning ( "24" , "20" , inContainer , node24NotExecutable ) ;
361392 return NodeHandler . Node20_1Folder ;
362393 }
363394 }
@@ -383,15 +414,15 @@ private string GetNodeFolderWithFallback(string preferredNodeFolder, bool node20
383414 public string GetNodeLocation ( bool node20ResultsInGlibCError , bool node24ResultsInGlibCError , bool inContainer )
384415 {
385416 bool useStrategyPattern = AgentKnobs . UseNodeVersionStrategy . GetValue ( ExecutionContext ) . AsBoolean ( ) ;
386-
417+
387418 if ( useStrategyPattern )
388419 {
389420 return GetNodeLocationUsingStrategy ( inContainer ) . GetAwaiter ( ) . GetResult ( ) ;
390421 }
391-
422+
392423 return GetNodeLocationLegacy ( node20ResultsInGlibCError , node24ResultsInGlibCError , inContainer ) ;
393424 }
394-
425+
395426 private async Task < string > GetNodeLocationUsingStrategy ( bool inContainer )
396427 {
397428 try
@@ -413,7 +444,7 @@ private async Task<string> GetNodeLocationUsingStrategy(bool inContainer)
413444 throw ;
414445 }
415446 }
416-
447+
417448 private string GetNodeLocationLegacy ( bool node20ResultsInGlibCError , bool node24ResultsInGlibCError , bool inContainer )
418449 {
419450 if ( ! string . IsNullOrEmpty ( ExecutionContext . StepTarget ( ) ? . CustomNodePath ) )
@@ -539,11 +570,11 @@ private string GetNodeLocationLegacy(bool node20ResultsInGlibCError, bool node24
539570 return nodeHandlerHelper . GetNodeFolderPath ( nodeFolder , HostContext ) ;
540571 }
541572
542- private void NodeFallbackWarning ( string fromVersion , string toVersion , bool inContainer , bool notAvailable = false )
573+ private void NodeFallbackWarning ( string fromVersion , string toVersion , bool inContainer , bool notExecutable = false )
543574 {
544575 string systemType = inContainer ? "container" : "agent" ;
545- string reason = notAvailable
546- ? $ "Node{ fromVersion } is not available on this platform"
576+ string reason = notExecutable
577+ ? $ "Node{ fromVersion } is not executable on this platform(e.g.,node binary missing or incompatible) "
547578 : $ "The { systemType } operating system doesn't support Node{ fromVersion } ";
548579
549580 ExecutionContext . Warning ( $ "{ reason } . Using Node{ toVersion } instead. " +
0 commit comments