55using System . Threading . Tasks ;
66using Azure . Functions . Cli . Arm ;
77using Azure . Functions . Cli . Common ;
8+ using static Azure . Functions . Cli . Common . OutputTheme ;
89using Azure . Functions . Cli . Interfaces ;
10+ using Colors . Net ;
911using Fclp ;
1012using Newtonsoft . Json ;
1113using Newtonsoft . Json . Linq ;
@@ -14,6 +16,18 @@ namespace Azure.Functions.Cli.Actions.AzureActions
1416{
1517 abstract class BaseAzureAction : BaseAction , IInitializableAction
1618 {
19+ // Az is the Azure PowerShell module that works in both PowerShell Core and Windows PowerShell
20+ private const string _azProfileModuleName = "Az.Profile" ;
21+
22+ // AzureRm is the Azure PowerShell module that only works on Windows PowerShell
23+ private const string _azureRmProfileModuleName = "AzureRM.Profile" ;
24+
25+ // PowerShell Core is version 6.0 and higher that is cross-platform
26+ private const string _powerShellCoreExecutable = "pwsh" ;
27+
28+ // Windows PowerShell is PowerShell version 5.1 and lower that only works on Windows
29+ private const string _windowsPowerShellExecutable = "powershell" ;
30+
1731 public string AccessToken { get ; set ; }
1832 public bool ReadStdin { get ; set ; }
1933
@@ -66,10 +80,16 @@ public async Task Initialize()
6680
6781 private async Task < string > GetAccessToken ( )
6882 {
69- return await AzureCliGetToken ( ) ;
83+ ( bool cliSucceeded , string cliToken ) = await TryGetAzCliToken ( ) ;
84+ if ( cliSucceeded ) return cliToken ;
85+
86+ ( bool powershellSucceeded , string psToken ) = await TryGetAzPowerShellToken ( ) ;
87+ if ( powershellSucceeded ) return psToken ;
88+
89+ throw new CliException ( "Unable to connect to Azure. Make sure you have the `az` CLI or Azure PowerShell installed and logged in and try again" ) ;
7090 }
7191
72- private async Task < string > AzureCliGetToken ( )
92+ private async Task < ( bool succeeded , string token ) > TryGetAzCliToken ( )
7393 {
7494 if ( CommandChecker . CommandExists ( "az" ) )
7595 {
@@ -80,19 +100,115 @@ private async Task<string> AzureCliGetToken()
80100 var stdout = new StringBuilder ( ) ;
81101 var stderr = new StringBuilder ( ) ;
82102 var exitCode = await az . RunAsync ( o => stdout . AppendLine ( o ) , e => stderr . AppendLine ( e ) ) ;
83- if ( exitCode != 0 )
103+ if ( exitCode == 0 )
104+ {
105+ return ( true , stdout . ToString ( ) . Trim ( ' ' , '\n ' , '\r ' , '"' ) ) ;
106+ }
107+ else
108+ {
109+ if ( StaticSettings . IsDebug )
110+ {
111+ ColoredConsole . WriteLine ( VerboseColor ( $ "Unable to fetch access token from az cli. Error: { stderr . ToString ( ) . Trim ( ' ' , '\n ' , '\r ' ) } ") ) ;
112+ }
113+ }
114+ }
115+ return ( false , null ) ;
116+ }
117+
118+ private async Task < ( bool succeeded , string token ) > TryGetAzPowerShellToken ( )
119+ {
120+ // PowerShell Core can only use Az so we can check that it exists and that the Az module exists
121+ if ( CommandChecker . CommandExists ( _powerShellCoreExecutable ) &&
122+ await CommandChecker . PowerShellModuleExistsAsync ( _powerShellCoreExecutable , _azProfileModuleName ) )
123+ {
124+ var az = new Executable ( _powerShellCoreExecutable ,
125+ $ "-NonInteractive -o Text -NoProfile -c { GetPowerShellAccessTokenScript ( _azProfileModuleName ) } ") ;
126+
127+ var stdout = new StringBuilder ( ) ;
128+ var stderr = new StringBuilder ( ) ;
129+ var exitCode = await az . RunAsync ( o => stdout . AppendLine ( o ) , e => stderr . AppendLine ( e ) ) ;
130+ if ( exitCode == 0 )
131+ {
132+ return ( true , stdout . ToString ( ) . Trim ( ' ' , '\n ' , '\r ' , '"' ) ) ;
133+ }
134+ else
135+ {
136+ if ( StaticSettings . IsDebug )
137+ {
138+ ColoredConsole . WriteLine ( VerboseColor ( $ "Unable to fetch access token from Az.Profile in PowerShell Core. Error: { stderr . ToString ( ) . Trim ( ' ' , '\n ' , '\r ' ) } ") ) ;
139+ }
140+ }
141+ }
142+
143+ // Windows PowerShell can use Az or AzureRM so first we check if powershell.exe is available
144+ if ( CommandChecker . CommandExists ( _windowsPowerShellExecutable ) )
145+ {
146+ string moduleToUse ;
147+
148+ // depending on if Az.Profile or AzureRM.Profile is available, we need to change the prefix
149+ if ( await CommandChecker . PowerShellModuleExistsAsync ( _windowsPowerShellExecutable , _azProfileModuleName ) )
150+ {
151+ moduleToUse = _azProfileModuleName ;
152+ }
153+ else if ( await CommandChecker . PowerShellModuleExistsAsync ( _windowsPowerShellExecutable , _azureRmProfileModuleName ) )
154+ {
155+ moduleToUse = _azureRmProfileModuleName ;
156+ }
157+ else
158+ {
159+ // User doesn't have either Az.Profile or AzureRM.Profile
160+ if ( StaticSettings . IsDebug )
161+ {
162+ ColoredConsole . WriteLine ( VerboseColor ( "Unable to find Az.Profile or AzureRM.Profile." ) ) ;
163+ }
164+ return ( false , null ) ;
165+ }
166+
167+ var az = new Executable ( "powershell" , $ "-NonInteractive -o Text -NoProfile -c { GetPowerShellAccessTokenScript ( moduleToUse ) } ") ;
168+
169+ var stdout = new StringBuilder ( ) ;
170+ var stderr = new StringBuilder ( ) ;
171+ var exitCode = await az . RunAsync ( o => stdout . AppendLine ( o ) , e => stderr . AppendLine ( e ) ) ;
172+ if ( exitCode == 0 )
84173 {
85- throw new CliException ( stderr . ToString ( ) . Trim ( ' ' , '\n ' , '\r ' ) + $ " { Environment . NewLine } " + "Make sure to run \" az login \" to log in to Azure and retry this command." ) ;
174+ return ( true , stdout . ToString ( ) . Trim ( ' ' , '\n ' , '\r ' , '"' ) ) ;
86175 }
87176 else
88177 {
89- return stdout . ToString ( ) . Trim ( ' ' , '\n ' , '\r ' , '"' ) ;
178+ if ( StaticSettings . IsDebug )
179+ {
180+ ColoredConsole . WriteLine ( VerboseColor ( $ "Unable to fetch access token from '{ moduleToUse } '. Error: { stderr . ToString ( ) . Trim ( ' ' , '\n ' , '\r ' ) } ") ) ;
181+ }
90182 }
91183 }
184+ return ( false , null ) ;
185+ }
186+
187+ // Sets the prefix of the script in case they have Az.Profile or AzureRM.Profile
188+ private static string GetPowerShellAccessTokenScript ( string module )
189+ {
190+ string prefix ;
191+ if ( module == _azProfileModuleName )
192+ {
193+ prefix = "Az" ;
194+ }
195+ else if ( module == _azureRmProfileModuleName )
196+ {
197+ prefix = "AzureRM" ;
198+ }
92199 else
93200 {
94- throw new FileNotFoundException ( "Cannot find az cli. Please make sure to install az cli. ") ;
201+ throw new ArgumentException ( $ "Expected module to be ' { _azProfileModuleName } ' or ' { _azureRmProfileModuleName } ' ") ;
95202 }
203+
204+ // This PowerShell script first grabs the Azure context, fetches the profile client and requests an accesstoken.
205+ // This entirely done using the Az.Profile module or AzureRM.Profile
206+ return $@ "
207+ $currentAzureContext = Get-{ prefix } Context;
208+ $azureRmProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile;
209+ $profileClient = New-Object Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient $azureRmProfile;
210+ $profileClient.AcquireAccessToken($currentAzureContext.Subscription.TenantId).AccessToken;
211+ " ;
96212 }
97213 }
98214}
0 commit comments