17
17
18
18
namespace Microsoft . Azure . Functions . PowerShellWorker . PowerShell
19
19
{
20
+ using System . Linq ;
20
21
using System . Management . Automation ;
22
+ using System . Text ;
21
23
22
24
internal class PowerShellManager
23
25
{
24
26
private readonly ILogger _logger ;
25
27
private readonly PowerShell _pwsh ;
28
+ private const string PROFILE_FILENAME = "Profile.ps1" ;
29
+
30
+ // The path to the FunctionApp root. This is set at the first FunctionLoad message
31
+ //and used for determining the path to the 'Profile.ps1' and 'Modules' folder.
32
+ internal string FunctionAppRootLocation { get ; set ; }
26
33
27
34
internal PowerShellManager ( ILogger logger )
28
35
{
@@ -48,69 +55,54 @@ internal PowerShellManager(ILogger logger)
48
55
_pwsh . Streams . Warning . DataAdding += streamHandler . WarningDataAdding ;
49
56
}
50
57
51
- internal void AuthenticateToAzure ( )
58
+ internal void InvokeProfile ( )
52
59
{
53
- // Check if Az.Profile is available
54
- Collection < PSModuleInfo > azProfile = _pwsh . AddCommand ( "Get-Module" )
55
- . AddParameter ( "ListAvailable" )
56
- . AddParameter ( "Name" , "Az.Profile" )
57
- . InvokeAndClearCommands < PSModuleInfo > ( ) ;
58
-
59
- if ( azProfile . Count == 0 )
60
+ IEnumerable < string > profiles = Directory . EnumerateFiles ( FunctionAppRootLocation , PROFILE_FILENAME ) ;
61
+ if ( profiles . Count ( ) == 0 )
60
62
{
61
- _logger . Log ( LogLevel . Trace , "Required module to automatically authenticate with Azure `Az. Profile` was not found in the PSModulePath. ") ;
63
+ _logger . Log ( LogLevel . Trace , $ "No ' Profile.ps1' found at: { FunctionAppRootLocation } ") ;
62
64
return ;
63
65
}
64
66
65
- // Try to authenticate to Azure using MSI
66
- string msiSecret = Environment . GetEnvironmentVariable ( "MSI_SECRET" ) ;
67
- string msiEndpoint = Environment . GetEnvironmentVariable ( "MSI_ENDPOINT" ) ;
68
- string accountId = Environment . GetEnvironmentVariable ( "WEBSITE_SITE_NAME" ) ;
69
-
70
- if ( ! string . IsNullOrEmpty ( msiSecret ) &&
71
- ! string . IsNullOrEmpty ( msiEndpoint ) &&
72
- ! string . IsNullOrEmpty ( accountId ) )
67
+ var dotSourced = new StringBuilder ( ". " ) . Append ( QuoteEscapeString ( profiles . First ( ) ) ) ;
68
+ _pwsh . AddScript ( dotSourced . ToString ( ) ) . InvokeAndClearCommands ( ) ;
69
+
70
+ if ( _pwsh . HadErrors )
73
71
{
74
- // NOTE: There is a limitation in Azure PowerShell that prevents us from using the parameter set:
75
- // Connect-AzAccount -MSI or Connect-AzAccount -Identity
76
- // see this GitHub issue https://github.com/Azure/azure-powershell/issues/7876
77
- // As a workaround, we can all an API endpoint on the MSI_ENDPOINT to get an AccessToken and use that to authenticate
78
- Collection < PSObject > response = _pwsh . AddCommand ( "Microsoft.PowerShell.Utility\\ Invoke-RestMethod" )
79
- . AddParameter ( "Method" , "Get" )
80
- . AddParameter ( "Headers" , new Hashtable { { "Secret" , msiSecret } } )
81
- . AddParameter ( "Uri" , $ "{ msiEndpoint } ?resource=https://management.azure.com&api-version=2017-09-01")
82
- . InvokeAndClearCommands < PSObject > ( ) ;
72
+ var logMessage = $ "Invoking the Profile had errors. See logs for details. Profile location: { FunctionAppRootLocation } ";
73
+ _logger . Log ( LogLevel . Error , logMessage , isUserLog : true ) ;
74
+ throw new InvalidOperationException ( logMessage ) ;
75
+ }
76
+ }
83
77
84
- if ( _pwsh . HadErrors )
85
- {
86
- _logger . Log ( LogLevel . Warning , "Failed to Authenticate to Azure via MSI. Check the logs for the errors generated." ) ;
87
- }
88
- else
78
+ /// <summary>
79
+ /// Wrap a string in quotes to make it safe to use in scripts.
80
+ /// </summary>
81
+ /// <param name="path">The path to wrap in quotes.</param>
82
+ /// <returns>The given path wrapped in quotes appropriately.</returns>
83
+ private static StringBuilder QuoteEscapeString ( string path )
84
+ {
85
+ var sb = new StringBuilder ( path . Length + 2 ) ; // Length of string plus two quotes
86
+ sb . Append ( '\' ' ) ;
87
+ if ( ! path . Contains ( '\' ' ) )
88
+ {
89
+ sb . Append ( path ) ;
90
+ }
91
+ else
92
+ {
93
+ foreach ( char c in path )
89
94
{
90
- // We have successfully authenticated to Azure so we can return out.
91
- using ( ExecutionTimer . Start ( _logger , "Authentication to Azure" ) )
95
+ if ( c == '\' ' )
92
96
{
93
- _pwsh . AddCommand ( "Az.Profile\\ Connect-AzAccount" )
94
- . AddParameter ( "AccessToken" , response [ 0 ] . Properties [ "access_token" ] . Value )
95
- . AddParameter ( "AccountId" , accountId )
96
- . InvokeAndClearCommands ( ) ;
97
-
98
- if ( _pwsh . HadErrors )
99
- {
100
- _logger . Log ( LogLevel . Warning , "Failed to Authenticate to Azure. Check the logs for the errors generated." ) ;
101
- }
102
- else
103
- {
104
- // We've successfully authenticated to Azure so we can return
105
- return ;
106
- }
97
+ sb . Append ( "''" ) ;
98
+ continue ;
107
99
}
100
+
101
+ sb . Append ( c ) ;
108
102
}
109
103
}
110
- else
111
- {
112
- _logger . Log ( LogLevel . Trace , "Skip authentication to Azure via MSI. Environment variables for authenticating to Azure are not present." ) ;
113
- }
104
+ sb . Append ( '\' ' ) ;
105
+ return sb ;
114
106
}
115
107
116
108
internal void InitializeRunspace ( )
0 commit comments