Experimental feature: Implicit remoting batching perf improvement (#8038)
This commit is contained in:
parent
1aa5bb3576
commit
20f3a6a337
|
@ -1985,6 +1985,7 @@ namespace Microsoft.PowerShell.Commands
|
|||
|
||||
PrivateData = @{{
|
||||
ImplicitRemoting = $true
|
||||
ImplicitSessionId = '{4}'
|
||||
}}
|
||||
}}
|
||||
";
|
||||
|
@ -2003,7 +2004,8 @@ namespace Microsoft.PowerShell.Commands
|
|||
CodeGeneration.EscapeSingleQuotedStringContent(_moduleGuid.ToString()),
|
||||
CodeGeneration.EscapeSingleQuotedStringContent(StringUtil.Format(ImplicitRemotingStrings.ProxyModuleDescription, this.GetConnectionString())),
|
||||
CodeGeneration.EscapeSingleQuotedStringContent(Path.GetFileName(psm1fileName)),
|
||||
CodeGeneration.EscapeSingleQuotedStringContent(Path.GetFileName(formatPs1xmlFileName)));
|
||||
CodeGeneration.EscapeSingleQuotedStringContent(Path.GetFileName(formatPs1xmlFileName)),
|
||||
this._remoteRunspaceInfo.InstanceId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Runspaces;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Management.Automation.Language;
|
||||
|
||||
using Dbg = System.Management.Automation.Diagnostics;
|
||||
|
||||
|
@ -320,6 +322,22 @@ namespace Microsoft.PowerShell
|
|||
{
|
||||
Dbg.Assert(!String.IsNullOrEmpty(command), "command should have a value");
|
||||
|
||||
// Experimental:
|
||||
// Check for implicit remoting commands that can be batched, and execute as batched if able.
|
||||
if (ExperimentalFeature.IsEnabled("PSImplicitRemotingBatching"))
|
||||
{
|
||||
var addOutputter = ((options & ExecutionOptions.AddOutputter) > 0);
|
||||
if (addOutputter &&
|
||||
!_parent.RunspaceRef.IsRunspaceOverridden &&
|
||||
_parent.RunspaceRef.Runspace.ExecutionContext.Modules != null &&
|
||||
_parent.RunspaceRef.Runspace.ExecutionContext.Modules.IsImplicitRemotingModuleLoaded &&
|
||||
Utils.TryRunAsImplicitBatch(command, _parent.RunspaceRef.Runspace))
|
||||
{
|
||||
exceptionThrown = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline tempPipeline = CreatePipeline(command, (options & ExecutionOptions.AddToHistory) > 0);
|
||||
|
||||
return ExecuteCommandHelper(tempPipeline, out exceptionThrown, options);
|
||||
|
|
|
@ -88,6 +88,10 @@ namespace System.Management.Automation
|
|||
source: EngineSource,
|
||||
isEnabled: false),
|
||||
*/
|
||||
new ExperimentalFeature(name: "PSImplicitRemotingBatching",
|
||||
description: "Batch implicit remoting proxy commands to improve performance",
|
||||
source: EngineSource,
|
||||
isEnabled: false)
|
||||
};
|
||||
EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);
|
||||
|
||||
|
|
|
@ -5034,6 +5034,21 @@ namespace Microsoft.PowerShell.Commands
|
|||
|
||||
// And the appdomain level module path cache.
|
||||
PSModuleInfo.RemoveFromAppDomainLevelCache(module.Name);
|
||||
|
||||
// Update implicit module loaded property
|
||||
if (Context.Modules.IsImplicitRemotingModuleLoaded)
|
||||
{
|
||||
Context.Modules.IsImplicitRemotingModuleLoaded = false;
|
||||
foreach (var modInfo in Context.Modules.ModuleTable.Values)
|
||||
{
|
||||
var privateData = modInfo.PrivateData as Hashtable;
|
||||
if ((privateData != null) && privateData.ContainsKey("ImplicitRemoting"))
|
||||
{
|
||||
Context.Modules.IsImplicitRemotingModuleLoaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6883,6 +6898,13 @@ namespace Microsoft.PowerShell.Commands
|
|||
{
|
||||
targetSessionState.Module.AddNestedModule(module);
|
||||
}
|
||||
|
||||
var privateDataHashTable = module.PrivateData as Hashtable;
|
||||
if (context.Modules.IsImplicitRemotingModuleLoaded == false &&
|
||||
privateDataHashTable != null && privateDataHashTable.ContainsKey("ImplicitRemoting"))
|
||||
{
|
||||
context.Modules.IsImplicitRemotingModuleLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -50,6 +50,15 @@ namespace System.Management.Automation
|
|||
|
||||
private const int MaxModuleNestingDepth = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets boolean that indicates when an implicit remoting module is loaded.
|
||||
/// </summary>
|
||||
internal bool IsImplicitRemotingModuleLoaded
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal void IncrementModuleNestingDepth(PSCmdlet cmdlet, string path)
|
||||
{
|
||||
if (++ModuleNestingDepth > MaxModuleNestingDepth)
|
||||
|
|
|
@ -6,6 +6,8 @@ using System.Runtime.InteropServices;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Management.Automation.Configuration;
|
||||
using System.Management.Automation.Internal;
|
||||
using System.Management.Automation.Language;
|
||||
using System.Management.Automation.Runspaces;
|
||||
using System.Management.Automation.Security;
|
||||
using System.Reflection;
|
||||
using Microsoft.PowerShell.Commands;
|
||||
|
@ -1359,7 +1361,377 @@ namespace System.Management.Automation
|
|||
return obj != null && Marshal.IsComObject(obj);
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Implicit Remoting Batching
|
||||
|
||||
// Commands allowed to run on target remote session along with implicit remote commands
|
||||
private static readonly HashSet<string> AllowedCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"ForEach-Object",
|
||||
"Measure-Command",
|
||||
"Measure-Object",
|
||||
"Sort-Object",
|
||||
"Where-Object"
|
||||
};
|
||||
|
||||
// Determines if the typed command invokes implicit remoting module proxy functions in such
|
||||
// a way as to allow simple batching, to reduce round trips between client and server sessions.
|
||||
// Requirements:
|
||||
// a. All commands must be implicit remoting module proxy commands targeted to the same remote session
|
||||
// b. Except for *allowed* commands that can be safely run on remote session rather than client session
|
||||
// c. Commands must be in a simple pipeline
|
||||
internal static bool TryRunAsImplicitBatch(string command, Runspace runspace)
|
||||
{
|
||||
try
|
||||
{
|
||||
var scriptBlock = ScriptBlock.Create(command);
|
||||
var scriptBlockAst = scriptBlock.Ast as ScriptBlockAst;
|
||||
if (scriptBlockAst == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure that this is a simple pipeline
|
||||
string errorId;
|
||||
string errorMsg;
|
||||
scriptBlockAst.GetSimplePipeline(true, out errorId, out errorMsg);
|
||||
if (errorId != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run checker
|
||||
var checker = new PipelineForBatchingChecker { ScriptBeingConverted = scriptBlockAst };
|
||||
scriptBlockAst.InternalVisit(checker);
|
||||
|
||||
// If this is just a single command, there is no point in batching it
|
||||
if (checker.Commands.Count < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We have a valid batching candidate
|
||||
using (var ps = System.Management.Automation.PowerShell.Create())
|
||||
{
|
||||
ps.Runspace = runspace;
|
||||
|
||||
// Check commands
|
||||
if (!TryGetCommandInfoList(ps, checker.Commands, out Collection<CommandInfo> cmdInfoList))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// All command modules must be implicit remoting modules from the same PSSession
|
||||
var success = true;
|
||||
var psSessionId = Guid.Empty;
|
||||
foreach (var cmdInfo in cmdInfoList)
|
||||
{
|
||||
// Check for allowed command
|
||||
string cmdName = (cmdInfo is AliasInfo aliasInfo) ? aliasInfo.ReferencedCommand.Name : cmdInfo.Name;
|
||||
if (AllowedCommands.Contains(cmdName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Commands must be from implicit remoting module
|
||||
if (cmdInfo.Module == null || string.IsNullOrEmpty(cmdInfo.ModuleName))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Commands must be from modules imported into the same remote session
|
||||
if (cmdInfo.Module.PrivateData is System.Collections.Hashtable privateData)
|
||||
{
|
||||
var sessionIdString = privateData["ImplicitSessionId"] as string;
|
||||
if (string.IsNullOrEmpty(sessionIdString))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
var sessionId = new Guid(sessionIdString);
|
||||
if (psSessionId == Guid.Empty)
|
||||
{
|
||||
psSessionId = sessionId;
|
||||
}
|
||||
else if (psSessionId != sessionId)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
//
|
||||
// Invoke command pipeline as entire pipeline on remote session
|
||||
//
|
||||
|
||||
// Update script to declare variables via Using keyword
|
||||
if (checker.ValidVariables.Count > 0)
|
||||
{
|
||||
foreach (var variableName in checker.ValidVariables)
|
||||
{
|
||||
command = command.Replace(variableName, ("Using:" + variableName), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
scriptBlock = ScriptBlock.Create(command);
|
||||
}
|
||||
|
||||
// Retrieve the PSSession runspace in which to run the batch script on
|
||||
ps.Commands.Clear();
|
||||
ps.Commands.AddCommand("Get-PSSession").AddParameter("InstanceId", psSessionId);
|
||||
var psSession = ps.Invoke<System.Management.Automation.Runspaces.PSSession>().FirstOrDefault();
|
||||
if (psSession == null || (ps.Streams.Error.Count > 0) || (psSession.Availability != RunspaceAvailability.Available))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create and invoke implicit remoting command pipeline
|
||||
ps.Commands.Clear();
|
||||
ps.AddCommand("Invoke-Command").AddParameter("Session", psSession).AddParameter("ScriptBlock", scriptBlock).AddParameter("HideComputerName", true)
|
||||
.AddCommand("Out-Default");
|
||||
|
||||
try
|
||||
{
|
||||
ps.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorRecord = new ErrorRecord(ex, "ImplicitRemotingBatchExecutionTerminatingError", ErrorCategory.InvalidOperation, null);
|
||||
|
||||
ps.Commands.Clear();
|
||||
ps.AddCommand("Write-Error").AddParameter("InputObject", errorRecord).Invoke();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private const string WhereObjectCommandAlias = "?";
|
||||
private static bool TryGetCommandInfoList(PowerShell ps, HashSet<string> commandNames, out Collection<CommandInfo> cmdInfoList)
|
||||
{
|
||||
if (commandNames.Count == 0)
|
||||
{
|
||||
cmdInfoList = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool specialCaseWhereCommandAlias = commandNames.Contains(WhereObjectCommandAlias);
|
||||
if (specialCaseWhereCommandAlias)
|
||||
{
|
||||
commandNames.Remove(WhereObjectCommandAlias);
|
||||
}
|
||||
|
||||
// Use Get-Command to collect CommandInfo from candidate commands, with correct precedence so
|
||||
// that implicit remoting proxy commands will appear when available.
|
||||
ps.Commands.Clear();
|
||||
ps.Commands.AddCommand("Get-Command").AddParameter("Name", commandNames.ToArray());
|
||||
cmdInfoList = ps.Invoke<CommandInfo>();
|
||||
if (ps.Streams.Error.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// For special case '?' alias don't use Get-Command to retrieve command info, and instead
|
||||
// use the GetCommand API.
|
||||
if (specialCaseWhereCommandAlias)
|
||||
{
|
||||
var cmdInfo = ps.Runspace.ExecutionContext.SessionState.InvokeCommand.GetCommand(WhereObjectCommandAlias, CommandTypes.Alias);
|
||||
if (cmdInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cmdInfoList.Add(cmdInfo);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region ImplicitRemotingBatching
|
||||
|
||||
// A visitor to walk an AST and validate that it is a candidate for implicit remoting batching.
|
||||
// Based on ScriptBlockToPowerShellChecker.
|
||||
internal class PipelineForBatchingChecker : AstVisitor
|
||||
{
|
||||
internal readonly HashSet<string> ValidVariables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
internal readonly HashSet<string> Commands = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
internal ScriptBlockAst ScriptBeingConverted { get; set; }
|
||||
|
||||
public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
|
||||
{
|
||||
if (!variableExpressionAst.VariablePath.IsAnyLocal())
|
||||
{
|
||||
ThrowError(
|
||||
new ImplicitRemotingBatchingNotSupportedException(
|
||||
"VariableTypeNotSupported"),
|
||||
variableExpressionAst);
|
||||
}
|
||||
|
||||
if (variableExpressionAst.VariablePath.UnqualifiedPath != "_")
|
||||
{
|
||||
ValidVariables.Add(variableExpressionAst.VariablePath.UnqualifiedPath);
|
||||
}
|
||||
|
||||
return AstVisitAction.Continue;
|
||||
}
|
||||
|
||||
public override AstVisitAction VisitPipeline(PipelineAst pipelineAst)
|
||||
{
|
||||
if (pipelineAst.PipelineElements[0] is CommandExpressionAst)
|
||||
{
|
||||
// If the first element is a CommandExpression, this pipeline should be the value
|
||||
// of a parameter. We want to avoid a scriptblock that contains only a pure expression.
|
||||
// The check "pipelineAst.Parent.Parent == ScriptBeingConverted" guarantees we throw
|
||||
// error on that kind of scriptblock.
|
||||
|
||||
// Disallow pure expressions at the "top" level, but allow them otherwise.
|
||||
// We want to catch:
|
||||
// 1 | echo
|
||||
// But we don't want to error out on:
|
||||
// echo $(1)
|
||||
// See the comment in VisitCommand on why it's safe to check Parent.Parent, we
|
||||
// know that we have at least:
|
||||
// * a NamedBlockAst (the end block)
|
||||
// * a ScriptBlockAst (the ast we're comparing to)
|
||||
if (pipelineAst.GetPureExpression() == null || pipelineAst.Parent.Parent == ScriptBeingConverted)
|
||||
{
|
||||
ThrowError(
|
||||
new ImplicitRemotingBatchingNotSupportedException(
|
||||
"PipelineStartingWithExpressionNotSupported"),
|
||||
pipelineAst);
|
||||
}
|
||||
}
|
||||
|
||||
return AstVisitAction.Continue;
|
||||
}
|
||||
|
||||
public override AstVisitAction VisitCommand(CommandAst commandAst)
|
||||
{
|
||||
if (commandAst.InvocationOperator == TokenKind.Dot)
|
||||
{
|
||||
ThrowError(
|
||||
new ImplicitRemotingBatchingNotSupportedException(
|
||||
"DotSourcingNotSupported"),
|
||||
commandAst);
|
||||
}
|
||||
|
||||
/*
|
||||
// Up front checking ensures that we have a simple script block,
|
||||
// so we can safely assume that the parents are:
|
||||
// * a PipelineAst
|
||||
// * a NamedBlockAst (the end block)
|
||||
// * a ScriptBlockAst (the ast we're comparing to)
|
||||
// If that isn't the case, the conversion isn't allowed. It
|
||||
// is also safe to assume that we have at least 3 parents, a script block can't be simpler.
|
||||
if (commandAst.Parent.Parent.Parent != ScriptBeingConverted)
|
||||
{
|
||||
ThrowError(
|
||||
new ImplicitRemotingBatchingNotSupportedException(
|
||||
"CantConvertWithCommandInvocations not supported"),
|
||||
commandAst);
|
||||
}
|
||||
*/
|
||||
|
||||
if (commandAst.CommandElements[0] is ScriptBlockExpressionAst)
|
||||
{
|
||||
ThrowError(
|
||||
new ImplicitRemotingBatchingNotSupportedException(
|
||||
"ScriptBlockInvocationNotSupported"),
|
||||
commandAst);
|
||||
}
|
||||
|
||||
var commandName = commandAst.GetCommandName();
|
||||
if (commandName != null)
|
||||
{
|
||||
Commands.Add(commandName);
|
||||
}
|
||||
|
||||
return AstVisitAction.Continue;
|
||||
}
|
||||
|
||||
public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst)
|
||||
{
|
||||
if (redirectionAst.ToStream != RedirectionStream.Output)
|
||||
{
|
||||
ThrowError(
|
||||
new ImplicitRemotingBatchingNotSupportedException(
|
||||
"MergeRedirectionNotSupported"),
|
||||
redirectionAst);
|
||||
}
|
||||
|
||||
return AstVisitAction.Continue;
|
||||
}
|
||||
|
||||
public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst)
|
||||
{
|
||||
ThrowError(
|
||||
new ImplicitRemotingBatchingNotSupportedException(
|
||||
"FileRedirectionNotSupported"),
|
||||
redirectionAst);
|
||||
|
||||
return AstVisitAction.Continue;
|
||||
}
|
||||
|
||||
/*
|
||||
public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
|
||||
{
|
||||
ThrowError(new ImplicitRemotingBatchingNotSupportedException(
|
||||
"ScriptBlocks not supported"),
|
||||
scriptBlockExpressionAst);
|
||||
|
||||
return AstVisitAction.SkipChildren;
|
||||
}
|
||||
*/
|
||||
|
||||
public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst)
|
||||
{
|
||||
// Using expressions are not expected in Implicit remoting commands.
|
||||
ThrowError(new ImplicitRemotingBatchingNotSupportedException(
|
||||
"UsingExpressionNotSupported"),
|
||||
usingExpressionAst);
|
||||
|
||||
return AstVisitAction.SkipChildren;
|
||||
}
|
||||
|
||||
internal static void ThrowError(ImplicitRemotingBatchingNotSupportedException ex, Ast ast)
|
||||
{
|
||||
InterpreterError.UpdateExceptionErrorRecordPosition(ex, ast.Extent);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ImplicitRemotingBatchingNotSupportedException : Exception
|
||||
{
|
||||
internal string ErrorId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
internal ImplicitRemotingBatchingNotSupportedException(string errorId) : base(
|
||||
ParserStrings.ImplicitRemotingPipelineBatchingNotSupported)
|
||||
{
|
||||
ErrorId = errorId;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
namespace System.Management.Automation.Internal
|
||||
|
@ -1404,6 +1776,19 @@ namespace System.Management.Automation.Internal
|
|||
fieldInfo.SetValue(null, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test hook used to test implicit remoting batching. A local runspace must be provided that has imported a
|
||||
/// remote session, i.e., has run the Import-PSSession cmdlet. This hook will return true if the provided commandPipeline
|
||||
/// is successfully batched and run in the remote session, and false if it is rejected for batching.
|
||||
/// </summary>
|
||||
/// <param name="commandPipeline">Command pipeline to test</param>
|
||||
/// <param name="runspace">Runspace with imported remote session</param>
|
||||
/// <returns>True if commandPipeline is batched successfully</returns>
|
||||
public static bool TestImplicitRemotingBatching(string commandPipeline, System.Management.Automation.Runspaces.Runspace runspace)
|
||||
{
|
||||
return Utils.TryRunAsImplicitBatch(commandPipeline, runspace);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1467,4 +1467,7 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent
|
|||
<data name="ParserError" xml:space="preserve">
|
||||
<value>{0}</value>
|
||||
</data>
|
||||
<data name="ImplicitRemotingPipelineBatchingNotSupported" xml:space="preserve">
|
||||
<value>Command pipeline not supported for implicit remoting batching.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
Describe "TestImplicitRemotingBatching hook should correctly batch simple remote command pipelines" -Tags 'Feature','RequireAdminOnWindows' {
|
||||
|
||||
BeforeAll {
|
||||
|
||||
if (! $isWindows) { return }
|
||||
|
||||
[powershell] $powerShell = [powershell]::Create([System.Management.Automation.RunspaceMode]::NewRunspace)
|
||||
|
||||
# Create remote session in new PowerShell session
|
||||
$powerShell.AddScript('Import-Module -Name HelpersRemoting; $remoteSession = New-RemoteSession').Invoke()
|
||||
if ($powerShell.Streams.Error.Count -gt 0) { throw "Unable to create remote session for test" }
|
||||
|
||||
# Import implicit commands from remote session
|
||||
$powerShell.Commands.Clear()
|
||||
$powerShell.AddScript('Import-PSSession -Session $remoteSession -CommandName Get-Process,Write-Output -AllowClobber').Invoke()
|
||||
if ($powerShell.Streams.Error.Count -gt 0) { throw "Unable to import pssession for test" }
|
||||
|
||||
# Define $filter variable in local session
|
||||
$powerShell.Commands.Clear()
|
||||
$powerShell.AddScript('$filter = "pwsh","powershell"').Invoke()
|
||||
$localRunspace = $powerShell.Runspace
|
||||
|
||||
[powershell] $psInvoke = [powershell]::Create([System.Management.Automation.RunspaceMode]::NewRunspace)
|
||||
|
||||
$testCases = @(
|
||||
@{
|
||||
Name = 'Two implicit commands should be successfully batched'
|
||||
CommandLine = 'Get-Process -Name "pwsh" | Write-Output'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with Where-Object should be successfully batched'
|
||||
CommandLine = 'Get-Process | Write-Output | Where-Object { $_.Name -like "*pwsh*" }'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with Where-Object alias (?) should be successfully batched'
|
||||
CommandLine = 'Get-Process | Write-Output | ? { $_.Name -like "*pwsh*" }'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with Where-Object alias (where) should be successfully batched'
|
||||
CommandLine = 'Get-Process | Write-Output | where { $_.Name -like "*pwsh*" }'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with Sort-Object should be successfully batched'
|
||||
CommandLine = 'Get-Process -Name "pwsh" | Sort-Object -Property Name | Write-Output'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with Sort-Object alias (sort) should be successfully batched'
|
||||
CommandLine = 'Get-Process -Name "pwsh" | sort -Property Name | Write-Output'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with ForEach-Object should be successfully batched'
|
||||
CommandLine = 'Get-Process -Name "pwsh" | Write-Output | ForEach-Object { $_ }'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with ForEach-Object alias (%) should be successfully batched'
|
||||
CommandLine = 'Get-Process -Name "pwsh" | Write-Output | % { $_ }'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with ForEach-Object alias (foreach) should be successfully batched'
|
||||
CommandLine = 'Get-Process -Name "pwsh" | Write-Output | foreach { $_ }'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with Measure-Command should be successfully batched'
|
||||
CommandLine = 'Measure-Command { Get-Process | Write-Output }'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with Measure-Object should be successfully batched'
|
||||
CommandLine = 'Get-Process | Write-Output | Measure-Object'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Two implicit commands with Measure-Object alias (measure) should be successfully batched'
|
||||
CommandLine = 'Get-Process | Write-Output | measure'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Implicit commands with variable arguments should be successfully batched'
|
||||
CommandLine = 'Get-Process -Name $filter | Write-Output'
|
||||
ExpectedOutput = $true
|
||||
},
|
||||
@{
|
||||
Name = 'Pipeline with non-implicit command should not be batched'
|
||||
CommandLine = 'Get-Process | Write-Output | Select-Object -Property Name'
|
||||
ExpectedOutput = $false
|
||||
},
|
||||
@{
|
||||
Name = 'Non-simple pipeline should not be batched'
|
||||
CommandLine = '1..2 | % { Get-Process pwsh | Write-Output }'
|
||||
ExpectedOutput = $false
|
||||
}
|
||||
@{
|
||||
Name = 'Pipeline with single command should not be batched'
|
||||
CommandLine = 'Get-Process pwsh'
|
||||
ExpectedOutput = $false
|
||||
},
|
||||
@{
|
||||
Name = 'Pipeline without any implicit commands should not be batched'
|
||||
CommandLine = 'Get-PSSession | Out-Default'
|
||||
ExpectedOutput = $false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
|
||||
if (! $isWindows) { return }
|
||||
|
||||
if ($remoteSession -ne $null) { Remove-PSSession $remoteSession -ErrorAction Ignore }
|
||||
if ($powershell -ne $null) { $powershell.Dispose() }
|
||||
if ($psInvoke -ne $null) { $psInvoke.Dispose() }
|
||||
}
|
||||
|
||||
It "<Name>" -TestCases $testCases -Skip:(! $IsWindows) {
|
||||
param ($CommandLine, $ExpectedOutput)
|
||||
|
||||
$psInvoke.Commands.Clear()
|
||||
$psInvoke.Commands.AddScript('param ($cmdLine, $runspace) [System.Management.Automation.Internal.InternalTestHooks]::TestImplicitRemotingBatching($cmdLine, $runspace)').AddArgument($CommandLine).AddArgument($localRunspace)
|
||||
|
||||
$result = $psInvoke.Invoke()
|
||||
$result | Should Be $ExpectedOutput
|
||||
}
|
||||
}
|
|
@ -88,6 +88,7 @@ function CreateParameters
|
|||
|
||||
return $parameters
|
||||
}
|
||||
|
||||
function New-RemoteSession
|
||||
{
|
||||
param (
|
||||
|
|
Loading…
Reference in a new issue