-
Notifications
You must be signed in to change notification settings - Fork 53
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 5 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.
Show resolved
Hide resolved
|
||
Environment.SetEnvironmentVariable("PSModulePath", $"{additionalPath}{Path.PathSeparator}{modulePath}"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @daxian-dbw I could add one for the user as well:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, adding |
||
} | ||
|
||
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.
Show resolved
Hide resolved
|
||
.AddStatement() | ||
.AddCommand("Get-Command", useLocalScope: true).AddParameter("Name", entryPoint) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When running c# cmdlet, there is no need to run it in a local scope, as c# cmdlet doesn't create scope anyway. |
||
.InvokeAndClearCommands<FunctionInfo>()[0].Parameters; | ||
|
||
_pwsh.AddCommand(entryPoint, useLocalScope: true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to use |
||
|
||
} | ||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you are not dot-sourcing the script, it will run in its script scope, which is a local scope, so no need to specify |
||
} | ||
} | ||
|
||
|
@@ -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()}"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about |
||
} | ||
|
||
returnObject = pipelineItems[pipelineItems.Count - 1]; | ||
} | ||
|
||
var result = _pwsh.AddCommand("Azure.Functions.PowerShell.Worker.Module\\Get-OutputBinding", useLocalScope: true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cmdlet will run in local scope by default when not dot-sourcing, so |
||
.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.