@@ -15,7 +15,7 @@ internal class DataRetriever : IDisposable
1515 private const string MetadataQueryTemplate = "{{\" command\" :\" {0}\" }}" ;
1616 private const string MetadataEndpoint = "https://cli-validation-tool-meta-qry.azurewebsites.net/api/command_metadata" ;
1717
18- private static string s_azPythonPath ;
18+ private static readonly string s_azCompleteCmd , s_azCompleteArg ;
1919 private static readonly Dictionary < string , NamingRule > s_azNamingRules ;
2020 private static readonly ConcurrentDictionary < string , AzCLICommand > s_azStaticDataCache ;
2121
@@ -306,26 +306,45 @@ static DataRetriever()
306306 s_azNamingRules . Add ( rule . AzPSCommand , rule ) ;
307307 }
308308
309- s_azPythonPath = GetAzCLIPythonPath ( ) ;
309+ ( s_azCompleteCmd , s_azCompleteArg ) = GetAzCLIPythonPath ( ) ;
310310 s_azStaticDataCache = new ( StringComparer . OrdinalIgnoreCase ) ;
311311 }
312312
313313 /// <summary>
314314 /// TODO: Need to support Linux and macOS.
315315 /// </summary>
316- private static string GetAzCLIPythonPath ( )
316+ private static ( string compCmd , string compArg ) GetAzCLIPythonPath ( )
317317 {
318318 if ( OperatingSystem . IsWindows ( ) )
319319 {
320- const string AzWindowsPath = @"Microsoft SDKs\Azure\CLI2\python.exe" ;
321- string x64Path = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFiles ) , AzWindowsPath ) ;
322- string x86Path = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFilesX86 ) , AzWindowsPath ) ;
320+ const string AzWinCmd = @"Microsoft SDKs\Azure\CLI2\python.exe" ;
321+ const string AzWinArg = "-Im azure.cli" ;
323322
324- if ( File . Exists ( x64Path ) ) { return x64Path ; }
325- if ( File . Exists ( x86Path ) ) { return x86Path ; }
323+ string x64Path = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFiles ) , AzWinCmd ) ;
324+ string x86Path = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFilesX86 ) , AzWinCmd ) ;
325+
326+ if ( File . Exists ( x64Path ) ) { return ( x64Path , AzWinArg ) ; }
327+ if ( File . Exists ( x86Path ) ) { return ( x86Path , AzWinArg ) ; }
328+ }
329+ else
330+ {
331+ // On Linux and macOS, simply run 'az' as starting a new process is cheap.
332+ string pathEnv = Environment . GetEnvironmentVariable ( "PATH" ) ;
333+ if ( ! string . IsNullOrEmpty ( pathEnv ) )
334+ {
335+ string [ ] paths = pathEnv . Split ( Path . PathSeparator , StringSplitOptions . RemoveEmptyEntries ) ;
336+ foreach ( string path in paths )
337+ {
338+ string fullPath = Path . Combine ( path , "az" ) ;
339+ if ( File . Exists ( fullPath ) )
340+ {
341+ return ( fullPath , string . Empty ) ;
342+ }
343+ }
344+ }
326345 }
327346
328- return null ;
347+ return ( null , null ) ;
329348 }
330349
331350 internal DataRetriever ( ResponseData data , HttpClient httpClient )
@@ -496,7 +515,7 @@ private List<string> GetArgValues(ArgumentPair pair)
496515 hasCompleter = param . HasCompleter ;
497516 }
498517
499- if ( _stop || ! hasCompleter || s_azPythonPath is null ) { return null ; }
518+ if ( _stop || ! hasCompleter || s_azCompleteCmd is null ) { return null ; }
500519
501520 // Then, try to get dynamic argument values using AzCLI tab completion.
502521 string commandLine = $ "{ pair . Command } { pair . Parameter } ";
@@ -510,11 +529,14 @@ private List<string> GetArgValues(ArgumentPair pair)
510529 {
511530 StartInfo = new ProcessStartInfo ( )
512531 {
513- FileName = s_azPythonPath ,
514- Arguments = "-Im azure.cli" ,
532+ FileName = s_azCompleteCmd ,
533+ Arguments = s_azCompleteArg ,
515534 UseShellExecute = false ,
516535 RedirectStandardOutput = true ,
517536 RedirectStandardError = true ,
537+ // Redirect stdin to force installing a missing extension, otherwise 'az'
538+ // may prompt interactively for user's approval to install.
539+ RedirectStandardInput = true ,
518540 }
519541 } ;
520542
0 commit comments