Skip to content

Commit be35db0

Browse files
Switch to module approach
1 parent 58924c8 commit be35db0

File tree

4 files changed

+106
-105
lines changed

4 files changed

+106
-105
lines changed

examples/PSCoreApp/MyHttpTrigger/run.ps1

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
param($req, $TriggerMetadata)
2-
3-
# Write-Host $TriggerMetadata["Name"]
4-
51
# Invoked with Invoke-RestMethod:
62
# irm http://localhost:7071/api/MyHttpTrigger?Name=Tyler
7-
# Input bindings are added to the scope of the script: ex. `$req`
3+
# Input bindings are added via param block
4+
5+
param($req, $TriggerMetadata)
86

97
# If no name was passed by query parameter
108
$name = 'World'
@@ -22,7 +20,7 @@ Write-Warning "Warning $name"
2220
$name
2321

2422
# You set the value of your output bindings by assignment `$nameOfOutputBinding = 'foo'`
25-
$res = [HttpResponseContext]@{
23+
Push-OutputBinding -Name res -Value ([HttpResponseContext]@{
2624
Body = @{ Hello = $name }
2725
ContentType = 'application/json'
28-
}
26+
})
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System.Collections.ObjectModel;
7+
8+
namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell
9+
{
10+
using System.Management.Automation;
11+
12+
internal static class PowerShellExtensions
13+
{
14+
public static void InvokeAndClearCommands(this PowerShell pwsh)
15+
{
16+
pwsh.Invoke();
17+
pwsh.Commands.Clear();
18+
}
19+
20+
public static Collection<T> InvokeAndClearCommands<T>(this PowerShell pwsh)
21+
{
22+
var result = pwsh.Invoke<T>();
23+
pwsh.Commands.Clear();
24+
return result;
25+
}
26+
}
27+
}

src/Azure.Functions.PowerShell.Worker/PowerShell/PowerShellManager.cs

Lines changed: 68 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,19 @@
1717
namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell
1818
{
1919
using System.Management.Automation;
20+
using System.Reflection;
2021

2122
internal class PowerShellManager
2223
{
23-
// This script handles when the user adds something to the pipeline.
24-
// It logs the item that comes and stores it as the $return out binding.
25-
// The last item stored as $return will be returned to the function host.
26-
27-
readonly static string s_LogAndSetReturnValueScript = @"
28-
param([Parameter(ValueFromPipeline=$true)]$return)
29-
30-
Write-Information $return
31-
32-
Set-Variable -Name '$return' -Value $return -Scope global
33-
";
34-
35-
readonly static string s_SetExecutionPolicyOnWindowsScript = @"
36-
if ($IsWindows)
37-
{
38-
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process
39-
}
40-
";
41-
4224
readonly static string s_TriggerMetadataParameterName = "TriggerMetadata";
25+
readonly static bool s_UseLocalScope = true;
4326

4427
RpcLogger _logger;
4528
PowerShell _pwsh;
4629

47-
PowerShellManager(RpcLogger logger)
30+
PowerShellManager(PowerShell pwsh, RpcLogger logger)
4831
{
49-
_pwsh = System.Management.Automation.PowerShell.Create(InitialSessionState.CreateDefault());
32+
_pwsh = pwsh;
5033
_logger = logger;
5134

5235
// Setup Stream event listeners
@@ -60,63 +43,33 @@ internal class PowerShellManager
6043
}
6144

6245
public static PowerShellManager Create(RpcLogger logger)
63-
{
64-
var manager = new PowerShellManager(logger);
65-
66-
// Add HttpResponseContext namespace so users can reference
67-
// HttpResponseContext without needing to specify the full namespace
68-
manager.ExecuteScriptAndClearCommands($"using namespace {typeof(HttpResponseContext).Namespace}");
69-
manager.ExecuteScriptAndClearCommands(s_SetExecutionPolicyOnWindowsScript);
70-
return manager;
71-
}
72-
73-
static string BuildBindingHashtableScript(IDictionary<string, BindingInfo> outBindings)
7446
{
75-
// Since all of the out bindings are stored in variables at this point,
76-
// we must construct a script that will return those output bindings in a hashtable
77-
StringBuilder script = new StringBuilder();
78-
script.AppendLine("@{");
79-
foreach (KeyValuePair<string, BindingInfo> binding in outBindings)
47+
// Set up initial session state: set execution policy, import helper module, and using namespace
48+
var initialSessionState = InitialSessionState.CreateDefault();
49+
if(Platform.IsWindows)
8050
{
81-
script.Append("'");
82-
script.Append(binding.Key);
83-
84-
// since $return has a dollar sign, we have to treat it differently
85-
if (binding.Key == "$return")
86-
{
87-
script.Append("' = ");
88-
}
89-
else
90-
{
91-
script.Append("' = $");
92-
}
93-
script.AppendLine(binding.Key);
51+
initialSessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
9452
}
95-
script.AppendLine("}");
96-
97-
return script.ToString();
98-
}
99-
100-
void ResetRunspace()
101-
{
102-
// Reset the runspace to the Initial Session State
103-
_pwsh.Runspace.ResetRunspaceState();
104-
}
105-
106-
void ExecuteScriptAndClearCommands(string script)
107-
{
108-
_pwsh.AddScript(script).Invoke();
109-
_pwsh.Commands.Clear();
110-
}
53+
var pwsh = PowerShell.Create(initialSessionState);
11154

112-
public Collection<T> ExecuteScriptAndClearCommands<T>(string script)
113-
{
114-
var result = _pwsh.AddScript(script).Invoke<T>();
115-
_pwsh.Commands.Clear();
116-
return result;
55+
// Add HttpResponseContext namespace so users can reference
56+
// HttpResponseContext without needing to specify the full namespace
57+
// and also import the Azure Functions binding helper module
58+
string modulePath = System.IO.Path.Join(
59+
AppDomain.CurrentDomain.BaseDirectory,
60+
"Azure.Functions.PowerShell.Worker.Module",
61+
"Azure.Functions.PowerShell.Worker.Module.psd1");
62+
pwsh.AddScript($"using namespace {typeof(HttpResponseContext).Namespace}")
63+
.AddStatement()
64+
.AddCommand("Import-Module")
65+
.AddParameter("Name", modulePath)
66+
.AddParameter("Scope", "Global")
67+
.InvokeAndClearCommands();
68+
69+
return new PowerShellManager(pwsh, logger);
11770
}
11871

119-
public PowerShellManager InvokeFunctionAndSetGlobalReturn(
72+
public Hashtable InvokeFunction(
12073
string scriptPath,
12174
string entryPoint,
12275
Hashtable triggerMetadata,
@@ -135,15 +88,23 @@ public PowerShellManager InvokeFunctionAndSetGlobalReturn(
13588
{
13689
if (entryPoint != "")
13790
{
138-
ExecuteScriptAndClearCommands($@". {scriptPath}");
139-
parameterMetadata = ExecuteScriptAndClearCommands<FunctionInfo>($@"Get-Command {entryPoint}")[0].Parameters;
140-
_pwsh.AddScript($@". {entryPoint} @args");
91+
parameterMetadata = _pwsh
92+
.AddScript($@". {scriptPath}", s_UseLocalScope)
93+
.AddStatement()
94+
.AddCommand("Get-Command", s_UseLocalScope).AddParameter("Name", entryPoint)
95+
.InvokeAndClearCommands<FunctionInfo>()[0].Parameters;
96+
97+
_pwsh
98+
.AddScript($@". {scriptPath}", s_UseLocalScope)
99+
.AddStatement()
100+
.AddCommand(entryPoint, s_UseLocalScope);
141101

142102
}
143103
else
144104
{
145-
parameterMetadata = ExecuteScriptAndClearCommands<ExternalScriptInfo>($@"Get-Command {scriptPath}")[0].Parameters;
146-
_pwsh.AddScript($@". {scriptPath} @args");
105+
parameterMetadata = _pwsh.AddCommand("Get-Command", s_UseLocalScope).AddParameter("Name", scriptPath)
106+
.InvokeAndClearCommands<ExternalScriptInfo>()[0].Parameters;
107+
_pwsh.AddCommand(scriptPath, s_UseLocalScope);
147108
}
148109
}
149110

@@ -160,35 +121,47 @@ public PowerShellManager InvokeFunctionAndSetGlobalReturn(
160121
_logger.LogDebug($"TriggerMetadata found. Value:{Environment.NewLine}{triggerMetadata.ToString()}");
161122
}
162123

163-
// This script handles when the user adds something to the pipeline.
124+
PSObject returnObject = null;
164125
using (ExecutionTimer.Start(_logger, "Execution of the user's function completed."))
165126
{
166-
ExecuteScriptAndClearCommands(s_LogAndSetReturnValueScript);
127+
// Log everything we received from the pipeline and set the last one to be the ReturnObject
128+
Collection<PSObject> pipelineItems = _pwsh.InvokeAndClearCommands<PSObject>();
129+
foreach (var psobject in pipelineItems)
130+
{
131+
_logger.LogInformation(psobject.ToString());
132+
}
133+
134+
returnObject = pipelineItems[pipelineItems.Count - 1];
167135
}
168-
return this;
169-
}
170-
catch(Exception e)
171-
{
172-
ResetRunspace();
173-
throw e;
174-
}
175-
}
136+
137+
var result = _pwsh.AddCommand("Get-OutputBinding", s_UseLocalScope).InvokeAndClearCommands<Hashtable>()[0];
176138

177-
public Hashtable ReturnBindingHashtable(IDictionary<string, BindingInfo> outBindings)
178-
{
179-
try
180-
{
181-
// This script returns a hashtable that contains the
182-
// output bindings that we will return to the function host.
183-
var result = ExecuteScriptAndClearCommands<Hashtable>(BuildBindingHashtableScript(outBindings))[0];
139+
if(returnObject != null)
140+
{
141+
result.Add("$return", returnObject);
142+
}
184143
ResetRunspace();
185144
return result;
186145
}
187146
catch(Exception e)
188147
{
189148
ResetRunspace();
190-
throw e;
149+
throw;
191150
}
192151
}
152+
153+
void ResetRunspace()
154+
{
155+
// Reset the runspace to the Initial Session State
156+
_pwsh.Runspace.ResetRunspaceState();
157+
158+
// TODO: Change this to clearing the variable by running in the module
159+
string modulePath = System.IO.Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Azure.Functions.PowerShell.Worker.Module", "Azure.Functions.PowerShell.Worker.Module.psd1");
160+
_pwsh.AddCommand("Import-Module")
161+
.AddParameter("Name", modulePath)
162+
.AddParameter("Scope", "Global")
163+
.AddParameter("Force")
164+
.InvokeAndClearCommands();
165+
}
193166
}
194167
}

src/Azure.Functions.PowerShell.Worker/Requests/HandleInvocationRequest.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ public static StreamingMessage Invoke(
5353
Hashtable result = null;
5454
try
5555
{
56-
result = powerShellManager
57-
.InvokeFunctionAndSetGlobalReturn(scriptPath, entryPoint, triggerMetadata, invocationRequest.InputData)
58-
.ReturnBindingHashtable(functionInfo.OutputBindings);
56+
result = powerShellManager.InvokeFunction(
57+
scriptPath,
58+
entryPoint,
59+
triggerMetadata,
60+
invocationRequest.InputData);
5961
}
6062
catch (Exception e)
6163
{
@@ -67,6 +69,7 @@ public static StreamingMessage Invoke(
6769
// Set out binding data and return response to be sent back to host
6870
foreach (KeyValuePair<string, BindingInfo> binding in functionInfo.OutputBindings)
6971
{
72+
// TODO: How do we want to handle when a binding is not set?
7073
ParameterBinding paramBinding = new ParameterBinding()
7174
{
7275
Name = binding.Key,

0 commit comments

Comments
 (0)