Skip to content

Commit e114f90

Browse files
committed
Refactor and clean up based on the dependency management changes (Azure#194)
Fix no runspace available Fix regression: create the first PowerShellManager in FunctionLoad, but delay the init Update comments Integrate all changes together Clear the outputbinding hashtable for the runspace after get the results Update tests and build scripts Create constant functions so it's more secure Alter the to-be-deployed PS function name to avoid conflicts with built-in functions Use PowerShell API instead of 'InvokeScript' as the former is way faster Bring back old code to clean global variables
1 parent 3ed9960 commit e114f90

31 files changed

+1207
-970
lines changed

build.ps1

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ if ($Bootstrap.IsPresent) {
3535
Write-Log -Warning "Module 'PSDepend' is missing. Installing 'PSDepend' ..."
3636
Install-Module -Name PSDepend -Scope CurrentUser -Force
3737
}
38-
if (-not (Get-Module -Name Pester -ListAvailable)) {
39-
Write-Log -Warning "Module 'Pester' is missing. Installing 'Pester' ..."
40-
Install-Module -Name Pester -Scope CurrentUser -Force
41-
}
4238
if (-not (Get-Module -Name platyPS -ListAvailable)) {
4339
Write-Log -Warning "Module 'platyPS' is missing. Installing 'platyPS' ..."
4440
Install-Module -Name platyPS -Scope CurrentUser -Force
@@ -93,36 +89,6 @@ if(!$NoBuild.IsPresent) {
9389

9490
# Test step
9591
if($Test.IsPresent) {
96-
if (-not (Get-Module -Name Pester -ListAvailable)) {
97-
throw "Cannot find the 'Pester' module. Please specify '-Bootstrap' to install build dependencies."
98-
}
99-
10092
dotnet test "$PSScriptRoot/test/Unit"
10193
if ($LASTEXITCODE -ne 0) { throw "xunit tests failed." }
102-
103-
Invoke-Tests -Path "$PSScriptRoot/test/Unit/Modules" -OutputFile UnitTestsResults.xml
104-
105-
if (-not (Get-Module -Name platyPS -ListAvailable)) {
106-
throw "Cannot find the 'platyPS' module. Please specify '-Bootstrap' to install build dependencies."
107-
}
108-
elseif (-not (Get-Command -Name git -CommandType Application)) {
109-
throw "Cannot find 'git'. Please make sure it's in the 'PATH'."
110-
}
111-
112-
# Cmdlet help docs should be up-to-date.
113-
# PlatyPS needs the module to be imported.
114-
Import-Module -Force (Join-Path $PSScriptRoot src Modules Microsoft.Azure.Functions.PowerShellWorker)
115-
try {
116-
# Update the help and diff the result.
117-
$docsPath = Join-Path $PSScriptRoot docs cmdlets
118-
$null = Update-MarkdownHelp -Path $docsPath
119-
$diff = git diff $docsPath
120-
if ($diff) {
121-
throw "Cmdlet help docs are not up-to-date, run Update-MarkdownHelp.`n$diff`n"
122-
}
123-
Write-Host "Help is up-to-date."
124-
} finally {
125-
# Clean up.
126-
Remove-Module Microsoft.Azure.Functions.PowerShellWorker -Force
127-
}
12894
}

docs/cmdlets/Get-OutputBinding.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
external help file: Microsoft.Azure.Functions.PowerShellWorker-help.xml
2+
external help file: Microsoft.Azure.Functions.PowerShellWorker.dll-Help.xml
33
Module Name: Microsoft.Azure.Functions.PowerShellWorker
44
online version:
55
schema: 2.0.0
@@ -13,7 +13,7 @@ Gets the hashtable of the output bindings set so far.
1313
## SYNTAX
1414

1515
```
16-
Get-OutputBinding [[-Name] <String[]>] [-Purge] [<CommonParameters>]
16+
Get-OutputBinding [-Name <String>] [-Purge] [<CommonParameters>]
1717
```
1818

1919
## DESCRIPTION
@@ -49,12 +49,12 @@ The name of the output binding you want to get.
4949
Supports wildcards.
5050

5151
```yaml
52-
Type: String[]
52+
Type: String
5353
Parameter Sets: (All)
5454
Aliases:
5555

5656
Required: False
57-
Position: 1
57+
Position: Named
5858
Default value: *
5959
Accept pipeline input: True (ByPropertyName, ByValue)
6060
Accept wildcard characters: True

docs/cmdlets/Push-OutputBinding.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
external help file: Microsoft.Azure.Functions.PowerShellWorker-help.xml
2+
external help file: Microsoft.Azure.Functions.PowerShellWorker.dll-Help.xml
33
Module Name: Microsoft.Azure.Functions.PowerShellWorker
44
online version:
55
schema: 2.0.0
@@ -92,6 +92,21 @@ The output binding of "outQueue" will now have a list with 4 items:
9292

9393
## PARAMETERS
9494

95+
### -Clobber
96+
(Optional) If specified, will force the value to be set for a specified output binding.
97+
98+
```yaml
99+
Type: SwitchParameter
100+
Parameter Sets: (All)
101+
Aliases:
102+
103+
Required: False
104+
Position: Named
105+
Default value: False
106+
Accept pipeline input: False
107+
Accept wildcard characters: False
108+
```
109+
95110
### -Name
96111
The name of the output binding you want to set.
97112
@@ -101,7 +116,7 @@ Parameter Sets: (All)
101116
Aliases:
102117

103118
Required: True
104-
Position: 1
119+
Position: 0
105120
Default value: None
106121
Accept pipeline input: False
107122
Accept wildcard characters: False
@@ -116,27 +131,12 @@ Parameter Sets: (All)
116131
Aliases:
117132

118133
Required: True
119-
Position: 2
134+
Position: 1
120135
Default value: None
121136
Accept pipeline input: True (ByValue)
122137
Accept wildcard characters: False
123138
```
124139
125-
### -Clobber
126-
(Optional) If specified, will force the value to be set for a specified output binding.
127-
128-
```yaml
129-
Type: SwitchParameter
130-
Parameter Sets: (All)
131-
Aliases:
132-
133-
Required: False
134-
Position: Named
135-
Default value: False
136-
Accept pipeline input: False
137-
Accept wildcard characters: False
138-
```
139-
140140
### CommonParameters
141141
This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
142142

docs/cmdlets/Trace-PipelineObject.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
external help file: Microsoft.Azure.Functions.PowerShellWorker-help.xml
2+
external help file: Microsoft.Azure.Functions.PowerShellWorker.dll-Help.xml
33
Module Name: Microsoft.Azure.Functions.PowerShellWorker
44
online version:
55
schema: 2.0.0
@@ -13,7 +13,7 @@ Writes the formatted output of the pipeline object to the information stream bef
1313
## SYNTAX
1414

1515
```
16-
Trace-PipelineObject [-InputObject] <Object> [<CommonParameters>]
16+
Trace-PipelineObject -InputObject <Object> [<CommonParameters>]
1717
```
1818

1919
## DESCRIPTION
@@ -40,7 +40,7 @@ Parameter Sets: (All)
4040
Aliases:
4141

4242
Required: True
43-
Position: 1
43+
Position: Named
4444
Default value: None
4545
Accept pipeline input: True (ByValue)
4646
Accept wildcard characters: False

src/DependencyManagement/DependencyManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ internal void InstallFunctionAppDependencies(PowerShell pwsh, ILogger logger)
215215
.AddParameter("Name", moduleName)
216216
.AddParameter("RequiredVersion", latestVersion)
217217
.AddParameter("Path", DependenciesPath)
218-
.AddParameter("Force", true)
218+
.AddParameter("Force", Utils.BoxedTrue)
219219
.AddParameter("ErrorAction", "Stop")
220220
.InvokeAndClearCommands();
221221

@@ -228,7 +228,7 @@ internal void InstallFunctionAppDependencies(PowerShell pwsh, ILogger logger)
228228
// Clean up
229229
pwsh.AddCommand(Utils.RemoveModuleCmdletInfo)
230230
.AddParameter("Name", "PackageManagement, PowerShellGet")
231-
.AddParameter("Force", true)
231+
.AddParameter("Force", Utils.BoxedTrue)
232232
.AddParameter("ErrorAction", "SilentlyContinue")
233233
.InvokeAndClearCommands();
234234
}

src/FunctionInfo.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Generic;
88
using System.Collections.ObjectModel;
99
using System.Linq;
10+
using System.Management.Automation;
1011
using System.Management.Automation.Language;
1112
using System.Text;
1213

@@ -30,7 +31,9 @@ internal class AzFunctionInfo
3031
internal readonly string FuncName;
3132
internal readonly string EntryPoint;
3233
internal readonly string ScriptPath;
34+
internal readonly string DeployedPSFuncName;
3335
internal readonly AzFunctionType Type;
36+
internal readonly ScriptBlock FuncScriptBlock;
3437
internal readonly ReadOnlyDictionary<string, PSScriptParamInfo> FuncParameters;
3538
internal readonly ReadOnlyDictionary<string, ReadOnlyBindingInfo> AllBindings;
3639
internal readonly ReadOnlyDictionary<string, ReadOnlyBindingInfo> InputBindings;
@@ -50,7 +53,8 @@ internal AzFunctionInfo(RpcFunctionMetadata metadata)
5053
// Support 'entryPoint' only if 'scriptFile' is a .psm1 file;
5154
// Support .psm1 'scriptFile' only if 'entryPoint' is specified.
5255
bool isScriptFilePsm1 = ScriptPath.EndsWith(".psm1", StringComparison.OrdinalIgnoreCase);
53-
if (string.IsNullOrEmpty(EntryPoint))
56+
bool entryPointNotDefined = string.IsNullOrEmpty(EntryPoint);
57+
if (entryPointNotDefined)
5458
{
5559
if (isScriptFilePsm1)
5660
{
@@ -63,7 +67,7 @@ internal AzFunctionInfo(RpcFunctionMetadata metadata)
6367
}
6468

6569
// Get the parameter names of the script or function.
66-
var psScriptParams = GetParameters(ScriptPath, EntryPoint);
70+
var psScriptParams = GetParameters(ScriptPath, EntryPoint, out ScriptBlockAst scriptAst);
6771
FuncParameters = new ReadOnlyDictionary<string, PSScriptParamInfo>(psScriptParams);
6872

6973
var parametersCopy = new Dictionary<string, PSScriptParamInfo>(psScriptParams, StringComparer.OrdinalIgnoreCase);
@@ -121,6 +125,15 @@ internal AzFunctionInfo(RpcFunctionMetadata metadata)
121125
throw new InvalidOperationException(errorMsg);
122126
}
123127

128+
if (entryPointNotDefined && scriptAst.ScriptRequirements == null)
129+
{
130+
// If the function script is a '.ps1' file that doesn't have '#requires' defined,
131+
// then we get the script block and will deploy it as a PowerShell function in the
132+
// global scope of each Runspace, so as to avoid hitting the disk every invocation.
133+
FuncScriptBlock = scriptAst.GetScriptBlock();
134+
DeployedPSFuncName = $"_{FuncName}_";
135+
}
136+
124137
AllBindings = new ReadOnlyDictionary<string, ReadOnlyBindingInfo>(allBindings);
125138
InputBindings = new ReadOnlyDictionary<string, ReadOnlyBindingInfo>(inputBindings);
126139
OutputBindings = new ReadOnlyDictionary<string, ReadOnlyBindingInfo>(outputBindings);
@@ -140,9 +153,9 @@ private AzFunctionType GetAzFunctionType(ReadOnlyBindingInfo bindingInfo)
140153
}
141154
}
142155

143-
private Dictionary<string, PSScriptParamInfo> GetParameters(string scriptFile, string entryPoint)
156+
private Dictionary<string, PSScriptParamInfo> GetParameters(string scriptFile, string entryPoint, out ScriptBlockAst scriptAst)
144157
{
145-
ScriptBlockAst sbAst = Parser.ParseFile(scriptFile, out _, out ParseError[] errors);
158+
scriptAst = Parser.ParseFile(scriptFile, out _, out ParseError[] errors);
146159
if (errors != null && errors.Length > 0)
147160
{
148161
var stringBuilder = new StringBuilder(15);
@@ -158,11 +171,11 @@ private Dictionary<string, PSScriptParamInfo> GetParameters(string scriptFile, s
158171
ReadOnlyCollection<ParameterAst> paramAsts = null;
159172
if (string.IsNullOrEmpty(entryPoint))
160173
{
161-
paramAsts = sbAst.ParamBlock?.Parameters;
174+
paramAsts = scriptAst.ParamBlock?.Parameters;
162175
}
163176
else
164177
{
165-
var asts = sbAst.FindAll(
178+
var asts = scriptAst.FindAll(
166179
ast => ast is FunctionDefinitionAst func && entryPoint.Equals(func.Name, StringComparison.OrdinalIgnoreCase),
167180
searchNestedScriptBlocks: false).ToList();
168181

src/FunctionLoader.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ namespace Microsoft.Azure.Functions.PowerShellWorker
1515
/// <summary>
1616
/// FunctionLoader holds metadata of functions.
1717
/// </summary>
18-
internal class FunctionLoader
18+
internal static class FunctionLoader
1919
{
20-
private readonly Dictionary<string, AzFunctionInfo> _loadedFunctions = new Dictionary<string, AzFunctionInfo>();
20+
private static readonly Dictionary<string, AzFunctionInfo> LoadedFunctions = new Dictionary<string, AzFunctionInfo>();
2121

2222
internal static string FunctionAppRootPath { get; private set; }
2323
internal static string FunctionAppProfilePath { get; private set; }
@@ -26,9 +26,9 @@ internal class FunctionLoader
2626
/// <summary>
2727
/// Query for function metadata can happen in parallel.
2828
/// </summary>
29-
internal AzFunctionInfo GetFunctionInfo(string functionId)
29+
internal static AzFunctionInfo GetFunctionInfo(string functionId)
3030
{
31-
if (_loadedFunctions.TryGetValue(functionId, out AzFunctionInfo funcInfo))
31+
if (LoadedFunctions.TryGetValue(functionId, out AzFunctionInfo funcInfo))
3232
{
3333
return funcInfo;
3434
}
@@ -40,9 +40,25 @@ internal AzFunctionInfo GetFunctionInfo(string functionId)
4040
/// This method runs once per 'FunctionLoadRequest' during the code start of the worker.
4141
/// It will always run synchronously because we process 'FunctionLoadRequest' synchronously.
4242
/// </summary>
43-
internal void LoadFunction(FunctionLoadRequest request)
43+
internal static void LoadFunction(FunctionLoadRequest request)
4444
{
45-
_loadedFunctions.Add(request.FunctionId, new AzFunctionInfo(request.Metadata));
45+
LoadedFunctions.Add(request.FunctionId, new AzFunctionInfo(request.Metadata));
46+
}
47+
48+
/// <summary>
49+
/// Get all loaded functions.
50+
/// </summary>
51+
internal static IEnumerable<AzFunctionInfo> GetLoadedFunctions()
52+
{
53+
return LoadedFunctions.Values;
54+
}
55+
56+
/// <summary>
57+
/// Clear all loaded functions.
58+
/// </summary>
59+
internal static void ClearLoadedFunctions()
60+
{
61+
LoadedFunctions.Clear();
4662
}
4763

4864
/// <summary>

src/Microsoft.Azure.Functions.PowerShellWorker.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ Licensed under the MIT license. See LICENSE file in the project root for full li
2222
<ItemGroup>
2323
<PackageReference Include="Grpc.Core" Version="1.20.1" />
2424
<PackageReference Include="Microsoft.PowerShell.SDK" Version="6.2.0" />
25-
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
2625
<PackageReference Include="CommandLineParser" Version="2.3.0" />
2726
<PackageReference Include="Google.Protobuf" Version="3.7.0" />
2827
</ItemGroup>

src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psd1

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
@{
77

88
# Script module or binary module file associated with this manifest.
9-
RootModule = 'Microsoft.Azure.Functions.PowerShellWorker.psm1'
9+
RootModule = 'Microsoft.Azure.Functions.PowerShellWorker.dll'
1010

1111
# Version number of this module.
1212
ModuleVersion = '0.1.0'
1313

1414
# Supported PSEditions
15-
CompatiblePSEditions = @('Desktop', 'Core')
15+
CompatiblePSEditions = @('Core')
1616

1717
# ID used to uniquely identify this module
1818
GUID = 'f0149ba6-bd6f-4dbd-afe5-2a95bd755d6c'
@@ -30,7 +30,7 @@ Copyright = '(c) Microsoft Corporation. All rights reserved.'
3030
Description = 'The module used in an Azure Functions environment for setting and retrieving Output Bindings.'
3131

3232
# Minimum version of the PowerShell engine required by this module
33-
PowerShellVersion = '5.1'
33+
PowerShellVersion = '6.2'
3434

3535
# Modules that must be imported into the global environment prior to importing this module
3636
RequiredModules = @()
@@ -51,10 +51,10 @@ FormatsToProcess = @()
5151
NestedModules = @()
5252

5353
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
54-
FunctionsToExport = @('Push-OutputBinding', 'Get-OutputBinding', 'Trace-PipelineObject')
54+
FunctionsToExport = @()
5555

5656
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
57-
CmdletsToExport = @()
57+
CmdletsToExport = @('Push-OutputBinding', 'Get-OutputBinding', 'Trace-PipelineObject')
5858

5959
# Variables to export from this module
6060
VariablesToExport = @()

0 commit comments

Comments
 (0)