-
Notifications
You must be signed in to change notification settings - Fork 55
Switch to module approach #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
ec76e8e
b099d09
e34c687
6c0c016
221e088
0097057
311a9d9
304278c
71a0e0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
| // | ||
|
|
||
| using System.Collections.ObjectModel; | ||
|
|
||
| namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell | ||
| { | ||
| using System.Management.Automation; | ||
|
|
||
| internal static class PowerShellExtensions | ||
| { | ||
| public static void InvokeAndClearCommands(this PowerShell pwsh) | ||
| { | ||
| pwsh.Invoke(); | ||
| pwsh.Commands.Clear(); | ||
| } | ||
|
|
||
| public static Collection<T> InvokeAndClearCommands<T>(this PowerShell pwsh) | ||
| { | ||
| var result = pwsh.Invoke<T>(); | ||
| pwsh.Commands.Clear(); | ||
| return result; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ | |
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Collections.ObjectModel; | ||
| using System.Text; | ||
| using System.IO; | ||
|
|
||
| using Microsoft.Azure.Functions.PowerShellWorker.Utility; | ||
| using Microsoft.Azure.WebJobs.Script.Grpc.Messages; | ||
|
|
@@ -20,33 +20,16 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell | |
|
|
||
| internal class PowerShellManager | ||
| { | ||
| // This script handles when the user adds something to the pipeline. | ||
| // It logs the item that comes and stores it as the $return out binding. | ||
| // The last item stored as $return will be returned to the function host. | ||
|
|
||
| readonly static string s_LogAndSetReturnValueScript = @" | ||
| param([Parameter(ValueFromPipeline=$true)]$return) | ||
|
|
||
| Write-Information $return | ||
|
|
||
| Set-Variable -Name '$return' -Value $return -Scope global | ||
| "; | ||
|
|
||
| readonly static string s_SetExecutionPolicyOnWindowsScript = @" | ||
| if ($IsWindows) | ||
| { | ||
| Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process | ||
| } | ||
| "; | ||
|
|
||
| readonly static string s_TriggerMetadataParameterName = "TriggerMetadata"; | ||
|
|
||
| RpcLogger _logger; | ||
| PowerShell _pwsh; | ||
|
|
||
| PowerShellManager(RpcLogger logger) | ||
| internal PowerShellManager(RpcLogger logger) | ||
| { | ||
| _pwsh = PowerShell.Create(InitialSessionState.CreateDefault()); | ||
| var initialSessionState = InitialSessionState.CreateDefault(); | ||
| initialSessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted; | ||
| _pwsh = PowerShell.Create(initialSessionState); | ||
| _logger = logger; | ||
|
|
||
| // Setup Stream event listeners | ||
|
|
@@ -59,64 +42,19 @@ internal class PowerShellManager | |
| _pwsh.Streams.Warning.DataAdding += streamHandler.WarningDataAdding; | ||
| } | ||
|
|
||
| public static PowerShellManager Create(RpcLogger logger) | ||
| { | ||
| var manager = new PowerShellManager(logger); | ||
|
|
||
| internal void InitializeRunspace() | ||
| { | ||
| // Add HttpResponseContext namespace so users can reference | ||
| // HttpResponseContext without needing to specify the full namespace | ||
| manager.ExecuteScriptAndClearCommands($"using namespace {typeof(HttpResponseContext).Namespace}"); | ||
| manager.ExecuteScriptAndClearCommands(s_SetExecutionPolicyOnWindowsScript); | ||
| return manager; | ||
| } | ||
|
|
||
| static string BuildBindingHashtableScript(IDictionary<string, BindingInfo> outBindings) | ||
| { | ||
| // Since all of the out bindings are stored in variables at this point, | ||
| // we must construct a script that will return those output bindings in a hashtable | ||
| StringBuilder script = new StringBuilder(); | ||
| script.AppendLine("@{"); | ||
| foreach (KeyValuePair<string, BindingInfo> binding in outBindings) | ||
| { | ||
| script.Append("'"); | ||
| script.Append(binding.Key); | ||
|
|
||
| // since $return has a dollar sign, we have to treat it differently | ||
| if (binding.Key == "$return") | ||
| { | ||
| script.Append("' = "); | ||
| } | ||
| else | ||
| { | ||
| script.Append("' = $"); | ||
| } | ||
| script.AppendLine(binding.Key); | ||
| } | ||
| script.AppendLine("}"); | ||
|
|
||
| return script.ToString(); | ||
| } | ||
|
|
||
| void ResetRunspace() | ||
| { | ||
| // Reset the runspace to the Initial Session State | ||
| _pwsh.Runspace.ResetRunspaceState(); | ||
| _pwsh.AddScript($"using namespace {typeof(HttpResponseContext).Namespace}").InvokeAndClearCommands(); | ||
|
|
||
| // Prepend the path to the internal Modules folder to the PSModulePath | ||
| var modulePath = Environment.GetEnvironmentVariable("PSModulePath"); | ||
| var additionalPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Modules"); | ||
daxian-dbw marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Environment.SetEnvironmentVariable("PSModulePath", $"{additionalPath}{Path.PathSeparator}{modulePath}"); | ||
|
||
| } | ||
|
|
||
| void ExecuteScriptAndClearCommands(string script) | ||
| { | ||
| _pwsh.AddScript(script).Invoke(); | ||
| _pwsh.Commands.Clear(); | ||
| } | ||
|
|
||
| public Collection<T> ExecuteScriptAndClearCommands<T>(string script) | ||
| { | ||
| var result = _pwsh.AddScript(script).Invoke<T>(); | ||
| _pwsh.Commands.Clear(); | ||
| return result; | ||
| } | ||
|
|
||
| public PowerShellManager InvokeFunctionAndSetGlobalReturn( | ||
| internal Hashtable InvokeFunction( | ||
| string scriptPath, | ||
| string entryPoint, | ||
| Hashtable triggerMetadata, | ||
|
|
@@ -130,20 +68,24 @@ public PowerShellManager InvokeFunctionAndSetGlobalReturn( | |
| // If it does, we invoke the command of that name. We also need to fetch | ||
| // the ParameterMetadata so that we can tell whether or not the user is asking | ||
| // for the $TriggerMetadata | ||
|
|
||
| using (ExecutionTimer.Start(_logger, "Parameter metadata retrieved.")) | ||
| { | ||
| if (entryPoint != "") | ||
| { | ||
| ExecuteScriptAndClearCommands($@". {scriptPath}"); | ||
| parameterMetadata = ExecuteScriptAndClearCommands<FunctionInfo>($@"Get-Command {entryPoint}")[0].Parameters; | ||
| _pwsh.AddScript($@". {entryPoint} @args"); | ||
| parameterMetadata = _pwsh | ||
| .AddScript($@". {scriptPath}") | ||
daxian-dbw marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .AddStatement() | ||
| .AddCommand("Get-Command", useLocalScope: true).AddParameter("Name", entryPoint) | ||
|
||
| .InvokeAndClearCommands<FunctionInfo>()[0].Parameters; | ||
|
|
||
| _pwsh.AddCommand(entryPoint, useLocalScope: true); | ||
|
||
|
|
||
| } | ||
| else | ||
| { | ||
| parameterMetadata = ExecuteScriptAndClearCommands<ExternalScriptInfo>($@"Get-Command {scriptPath}")[0].Parameters; | ||
| _pwsh.AddScript($@". {scriptPath} @args"); | ||
| parameterMetadata = _pwsh.AddCommand("Get-Command", useLocalScope: true).AddParameter("Name", scriptPath) | ||
| .InvokeAndClearCommands<ExternalScriptInfo>()[0].Parameters; | ||
| _pwsh.AddCommand(scriptPath, useLocalScope: true); | ||
|
||
| } | ||
| } | ||
|
|
||
|
|
@@ -160,35 +102,39 @@ public PowerShellManager InvokeFunctionAndSetGlobalReturn( | |
| _logger.LogDebug($"TriggerMetadata found. Value:{Environment.NewLine}{triggerMetadata.ToString()}"); | ||
| } | ||
|
|
||
| // This script handles when the user adds something to the pipeline. | ||
| PSObject returnObject = null; | ||
| using (ExecutionTimer.Start(_logger, "Execution of the user's function completed.")) | ||
| { | ||
| ExecuteScriptAndClearCommands(s_LogAndSetReturnValueScript); | ||
| // Log everything we received from the pipeline and set the last one to be the ReturnObject | ||
| Collection<PSObject> pipelineItems = _pwsh.InvokeAndClearCommands<PSObject>(); | ||
| foreach (var psobject in pipelineItems) | ||
| { | ||
| _logger.LogInformation($"FROM FUNCTION: {psobject.ToString()}"); | ||
|
||
| } | ||
|
|
||
| returnObject = pipelineItems[pipelineItems.Count - 1]; | ||
| } | ||
|
|
||
| var result = _pwsh.AddCommand("Azure.Functions.PowerShell.Worker.Module\\Get-OutputBinding", useLocalScope: true) | ||
|
||
| .AddParameter("Purge") | ||
| .InvokeAndClearCommands<Hashtable>()[0]; | ||
|
|
||
| if(returnObject != null) | ||
| { | ||
| result.Add("$return", returnObject); | ||
| } | ||
| return this; | ||
| return result; | ||
| } | ||
| catch(Exception e) | ||
| finally | ||
| { | ||
| ResetRunspace(); | ||
| throw e; | ||
| } | ||
| } | ||
|
|
||
| public Hashtable ReturnBindingHashtable(IDictionary<string, BindingInfo> outBindings) | ||
| private void ResetRunspace() | ||
| { | ||
| try | ||
| { | ||
| // This script returns a hashtable that contains the | ||
| // output bindings that we will return to the function host. | ||
| var result = ExecuteScriptAndClearCommands<Hashtable>(BuildBindingHashtableScript(outBindings))[0]; | ||
| ResetRunspace(); | ||
| return result; | ||
| } | ||
| catch(Exception e) | ||
| { | ||
| ResetRunspace(); | ||
| throw e; | ||
| } | ||
| // Reset the runspace to the Initial Session State | ||
| _pwsh.Runspace.ResetRunspaceState(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be const.