mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Support backgrounding pipelines with ampersand (#3360)
Implements support for backgrounding pipelines with &. Putting & at the end of a pipeline will cause the pipeline
to be run as a PowerShell job. When a pipeline is backgrounded a job object is returned. Once the pipeline is
running as a job, all of the normal job cmdlets can be used to manage the job. Variables (ignoring process-specific
variables) used in the pipeline are automatically copied to the job so
copy $foo $bar &
just works. The job is also run in the current directory instead of the user's home directory as is the case with Start-Job.Implement
This commit is contained in:
committed by
Jason Shirk
parent
f0b03bc57d
commit
dfb3866c5d
@@ -318,6 +318,8 @@ namespace System.Management.Automation.Language
|
||||
typeof(PipelineOps).GetMethod(nameof(PipelineOps.FlushPipe), staticFlags);
|
||||
internal static readonly MethodInfo PipelineOps_InvokePipeline =
|
||||
typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipeline), staticFlags);
|
||||
internal static readonly MethodInfo PipelineOps_InvokePipelineInBackground =
|
||||
typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipelineInBackground), staticFlags);
|
||||
internal static readonly MethodInfo PipelineOps_Nop =
|
||||
typeof(PipelineOps).GetMethod(nameof(PipelineOps.Nop), staticFlags);
|
||||
internal static readonly MethodInfo PipelineOps_PipelineResult =
|
||||
@@ -1942,7 +1944,8 @@ namespace System.Management.Automation.Language
|
||||
}
|
||||
|
||||
var pipelineAst = stmt as PipelineAst;
|
||||
if (pipelineAst != null)
|
||||
// If it's a pipeline that isn't being backgrounded, try to optimize expression
|
||||
if (pipelineAst != null && ! pipelineAst.Background)
|
||||
{
|
||||
var expr = pipelineAst.GetPureExpression();
|
||||
if (expr != null) { return Compile(expr); }
|
||||
@@ -2983,104 +2986,115 @@ namespace System.Management.Automation.Language
|
||||
exprs.Add(UpdatePosition(pipelineAst));
|
||||
}
|
||||
|
||||
var pipeElements = pipelineAst.PipelineElements;
|
||||
var firstCommandExpr = (pipeElements[0] as CommandExpressionAst);
|
||||
if (firstCommandExpr != null && pipeElements.Count == 1)
|
||||
if (pipelineAst.Background)
|
||||
{
|
||||
if (firstCommandExpr.Redirections.Count > 0)
|
||||
{
|
||||
exprs.Add(GetRedirectedExpression(firstCommandExpr, captureForInput: false));
|
||||
}
|
||||
else
|
||||
{
|
||||
exprs.Add(Compile(firstCommandExpr));
|
||||
}
|
||||
Expression invokeBackgroundPipe = Expression.Call(
|
||||
CachedReflectionInfo.PipelineOps_InvokePipelineInBackground,
|
||||
Expression.Constant(pipelineAst),
|
||||
_functionContext);
|
||||
exprs.Add(invokeBackgroundPipe);
|
||||
}
|
||||
else
|
||||
{
|
||||
Expression input;
|
||||
int i, commandsInPipe;
|
||||
|
||||
if (firstCommandExpr != null)
|
||||
var pipeElements = pipelineAst.PipelineElements;
|
||||
var firstCommandExpr = (pipeElements[0] as CommandExpressionAst);
|
||||
|
||||
if (firstCommandExpr != null && pipeElements.Count == 1)
|
||||
{
|
||||
if (firstCommandExpr.Redirections.Count > 0)
|
||||
{
|
||||
input = GetRedirectedExpression(firstCommandExpr, captureForInput: true);
|
||||
exprs.Add(GetRedirectedExpression(firstCommandExpr, captureForInput: false));
|
||||
}
|
||||
else
|
||||
{
|
||||
input = GetRangeEnumerator(firstCommandExpr.Expression) ??
|
||||
Compile(firstCommandExpr.Expression);
|
||||
exprs.Add(Compile(firstCommandExpr));
|
||||
}
|
||||
i = 1;
|
||||
commandsInPipe = pipeElements.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compiled code normally never sees AutomationNull. We use that value
|
||||
// here so that we can tell the difference b/w $null and no input when
|
||||
// starting the pipeline, in other words, PipelineOps.InvokePipe will
|
||||
// not pass this value to the pipe.
|
||||
|
||||
input = ExpressionCache.AutomationNullConstant;
|
||||
i = 0;
|
||||
commandsInPipe = pipeElements.Count;
|
||||
Expression input;
|
||||
int i, commandsInPipe;
|
||||
|
||||
if (firstCommandExpr != null)
|
||||
{
|
||||
if (firstCommandExpr.Redirections.Count > 0)
|
||||
{
|
||||
input = GetRedirectedExpression(firstCommandExpr, captureForInput: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
input = GetRangeEnumerator(firstCommandExpr.Expression) ??
|
||||
Compile(firstCommandExpr.Expression);
|
||||
}
|
||||
i = 1;
|
||||
commandsInPipe = pipeElements.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compiled code normally never sees AutomationNull. We use that value
|
||||
// here so that we can tell the difference b/w $null and no input when
|
||||
// starting the pipeline, in other words, PipelineOps.InvokePipe will
|
||||
// not pass this value to the pipe.
|
||||
|
||||
input = ExpressionCache.AutomationNullConstant;
|
||||
i = 0;
|
||||
commandsInPipe = pipeElements.Count;
|
||||
}
|
||||
Expression[] pipelineExprs = new Expression[commandsInPipe];
|
||||
CommandBaseAst[] pipeElementAsts = new CommandBaseAst[commandsInPipe];
|
||||
var commandRedirections = new object[commandsInPipe];
|
||||
|
||||
for (int j = 0; i < pipeElements.Count; ++i, ++j)
|
||||
{
|
||||
var pipeElement = pipeElements[i];
|
||||
pipelineExprs[j] = Compile(pipeElement);
|
||||
|
||||
commandRedirections[j] = GetCommandRedirections(pipeElement);
|
||||
pipeElementAsts[j] = pipeElement;
|
||||
}
|
||||
|
||||
// The redirections are passed as a CommandRedirection[][] - one dimension for each command in the pipe,
|
||||
// one dimension because each command may have multiple redirections. Here we create the array for
|
||||
// each command in the pipe, either a compile time constant or created at runtime if necessary.
|
||||
Expression redirectionExpr;
|
||||
if (commandRedirections.Any(r => r is Expression))
|
||||
{
|
||||
// If any command redirections are non-constant, commandRedirections will have a Linq.Expression in it,
|
||||
// in which case we must create the array at runtime
|
||||
redirectionExpr =
|
||||
Expression.NewArrayInit(typeof(CommandRedirection[]),
|
||||
commandRedirections.Select(r => (r as Expression) ?? Expression.Constant(r, typeof(CommandRedirection[]))));
|
||||
}
|
||||
else if (commandRedirections.Any(r => r != null))
|
||||
{
|
||||
// There were redirections, but all were compile time constant, so build the array at compile time.
|
||||
redirectionExpr =
|
||||
Expression.Constant(commandRedirections.Map(r => r as CommandRedirection[]));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No redirections.
|
||||
redirectionExpr = ExpressionCache.NullCommandRedirections;
|
||||
}
|
||||
|
||||
if (firstCommandExpr != null)
|
||||
{
|
||||
var inputTemp = Expression.Variable(input.Type);
|
||||
temps.Add(inputTemp);
|
||||
exprs.Add(Expression.Assign(inputTemp, input));
|
||||
input = inputTemp;
|
||||
}
|
||||
|
||||
Expression invokePipe = Expression.Call(
|
||||
CachedReflectionInfo.PipelineOps_InvokePipeline,
|
||||
input.Cast(typeof(object)),
|
||||
firstCommandExpr != null ? ExpressionCache.FalseConstant : ExpressionCache.TrueConstant,
|
||||
Expression.NewArrayInit(typeof(CommandParameterInternal[]), pipelineExprs),
|
||||
Expression.Constant(pipeElementAsts),
|
||||
redirectionExpr,
|
||||
_functionContext);
|
||||
exprs.Add(invokePipe);
|
||||
}
|
||||
Expression[] pipelineExprs = new Expression[commandsInPipe];
|
||||
CommandBaseAst[] pipeElementAsts = new CommandBaseAst[commandsInPipe];
|
||||
var commandRedirections = new object[commandsInPipe];
|
||||
|
||||
for (int j = 0; i < pipeElements.Count; ++i, ++j)
|
||||
{
|
||||
var pipeElement = pipeElements[i];
|
||||
pipelineExprs[j] = Compile(pipeElement);
|
||||
|
||||
commandRedirections[j] = GetCommandRedirections(pipeElement);
|
||||
pipeElementAsts[j] = pipeElement;
|
||||
}
|
||||
|
||||
// The redirections are passed as a CommandRedirection[][] - one dimension for each command in the pipe,
|
||||
// one dimension because each command may have multiple redirections. Here we create the array for
|
||||
// each command in the pipe, either a compile time constant or created at runtime if necessary.
|
||||
Expression redirectionExpr;
|
||||
if (commandRedirections.Any(r => r is Expression))
|
||||
{
|
||||
// If any command redirections are non-constant, commandRedirections will have a Linq.Expression in it,
|
||||
// in which case we must create the array at runtime
|
||||
redirectionExpr =
|
||||
Expression.NewArrayInit(typeof(CommandRedirection[]),
|
||||
commandRedirections.Select(r => (r as Expression) ?? Expression.Constant(r, typeof(CommandRedirection[]))));
|
||||
}
|
||||
else if (commandRedirections.Any(r => r != null))
|
||||
{
|
||||
// There were redirections, but all were compile time constant, so build the array at compile time.
|
||||
redirectionExpr =
|
||||
Expression.Constant(commandRedirections.Map(r => r as CommandRedirection[]));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No redirections.
|
||||
redirectionExpr = ExpressionCache.NullCommandRedirections;
|
||||
}
|
||||
|
||||
if (firstCommandExpr != null)
|
||||
{
|
||||
var inputTemp = Expression.Variable(input.Type);
|
||||
temps.Add(inputTemp);
|
||||
exprs.Add(Expression.Assign(inputTemp, input));
|
||||
input = inputTemp;
|
||||
}
|
||||
|
||||
Expression invokePipe = Expression.Call(
|
||||
CachedReflectionInfo.PipelineOps_InvokePipeline,
|
||||
input.Cast(typeof(object)),
|
||||
firstCommandExpr != null ? ExpressionCache.FalseConstant : ExpressionCache.TrueConstant,
|
||||
Expression.NewArrayInit(typeof(CommandParameterInternal[]), pipelineExprs),
|
||||
Expression.Constant(pipeElementAsts),
|
||||
redirectionExpr,
|
||||
_functionContext);
|
||||
|
||||
exprs.Add(invokePipe);
|
||||
}
|
||||
|
||||
return Expression.Block(temps, exprs);
|
||||
|
||||
@@ -2451,7 +2451,7 @@ namespace System.Management.Automation.Language
|
||||
{
|
||||
endErrorStatement = fileNameExpr.Extent;
|
||||
condition = new PipelineAst(fileNameExpr.Extent,
|
||||
new CommandExpressionAst(fileNameExpr.Extent, fileNameExpr, null));
|
||||
new CommandExpressionAst(fileNameExpr.Extent, fileNameExpr, null), background: false);
|
||||
|
||||
if (!specifiedFlags.ContainsKey("file"))
|
||||
{
|
||||
@@ -5182,6 +5182,7 @@ namespace System.Management.Automation.Language
|
||||
|
||||
Token pipeToken = null;
|
||||
bool scanning = true;
|
||||
bool background = false;
|
||||
while (scanning)
|
||||
{
|
||||
CommandBaseAst commandAst;
|
||||
@@ -5293,6 +5294,11 @@ namespace System.Management.Automation.Language
|
||||
case TokenKind.EndOfInput:
|
||||
scanning = false;
|
||||
continue;
|
||||
case TokenKind.Ampersand:
|
||||
SkipToken();
|
||||
scanning = false;
|
||||
background = true;
|
||||
break;
|
||||
case TokenKind.Pipe:
|
||||
SkipToken();
|
||||
SkipNewlines();
|
||||
@@ -5328,7 +5334,7 @@ namespace System.Management.Automation.Language
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements);
|
||||
return new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements, background);
|
||||
}
|
||||
|
||||
private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, RedirectionAst[] redirections, ref IScriptExtent extent)
|
||||
@@ -5672,16 +5678,11 @@ namespace System.Management.Automation.Language
|
||||
case TokenKind.Semi:
|
||||
case TokenKind.AndAnd:
|
||||
case TokenKind.OrOr:
|
||||
case TokenKind.Ampersand:
|
||||
UngetToken(token);
|
||||
scanning = false;
|
||||
continue;
|
||||
|
||||
case TokenKind.Ampersand:
|
||||
// ErrorRecovery: just ignore the token.
|
||||
endExtent = token.Extent;
|
||||
ReportError(token.Extent, () => ParserStrings.AmpersandNotAllowed);
|
||||
break;
|
||||
|
||||
case TokenKind.MinusMinus:
|
||||
endExtent = token.Extent;
|
||||
// Add the first -- as a parameter, which is then ignored when constructing the command processor unless it's a native
|
||||
|
||||
@@ -5472,13 +5472,14 @@ namespace System.Management.Automation.Language
|
||||
/// </summary>
|
||||
/// <param name="extent">The extent of the pipeline.</param>
|
||||
/// <param name="pipelineElements">The collection of commands representing the pipeline.</param>
|
||||
/// <param name="background">Indicates that this pipeline should be run in the background</param>
|
||||
/// <exception cref="PSArgumentNullException">
|
||||
/// If <paramref name="extent"/> is null.
|
||||
/// </exception>
|
||||
/// <exception cref="PSArgumentException">
|
||||
/// If <paramref name="pipelineElements"/> is null or is an empty collection.
|
||||
/// </exception>
|
||||
public PipelineAst(IScriptExtent extent, IEnumerable<CommandBaseAst> pipelineElements)
|
||||
public PipelineAst(IScriptExtent extent, IEnumerable<CommandBaseAst> pipelineElements, bool background)
|
||||
: base(extent)
|
||||
{
|
||||
if (pipelineElements == null || !pipelineElements.Any())
|
||||
@@ -5486,10 +5487,49 @@ namespace System.Management.Automation.Language
|
||||
throw PSTraceSource.NewArgumentException("pipelineElements");
|
||||
}
|
||||
|
||||
this.Background = background;
|
||||
this.PipelineElements = new ReadOnlyCollection<CommandBaseAst>(pipelineElements.ToArray());
|
||||
SetParents(PipelineElements);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a pipeline from a collection of commands.
|
||||
/// </summary>
|
||||
/// <param name="extent">The extent of the pipeline.</param>
|
||||
/// <param name="pipelineElements">The collection of commands representing the pipeline.</param>
|
||||
/// <exception cref="PSArgumentNullException">
|
||||
/// If <paramref name="extent"/> is null.
|
||||
/// </exception>
|
||||
/// <exception cref="PSArgumentException">
|
||||
/// If <paramref name="pipelineElements"/> is null or is an empty collection.
|
||||
/// </exception>
|
||||
public PipelineAst(IScriptExtent extent, IEnumerable<CommandBaseAst> pipelineElements) :this (extent, pipelineElements, background: false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a pipeline from a single command.
|
||||
/// </summary>
|
||||
/// <param name="extent">The extent of the pipeline (which should be the extent of the command).</param>
|
||||
/// <param name="commandAst">The command for the pipeline.</param>
|
||||
/// <param name="background">Indicates that this pipeline should be run in the background</param>
|
||||
/// <exception cref="PSArgumentNullException">
|
||||
/// If <paramref name="extent"/> or <paramref name="commandAst"/> is null.
|
||||
/// </exception>
|
||||
public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst, bool background)
|
||||
: base(extent)
|
||||
{
|
||||
if (commandAst == null)
|
||||
{
|
||||
throw PSTraceSource.NewArgumentNullException("commandAst");
|
||||
}
|
||||
|
||||
this.Background = background;
|
||||
this.PipelineElements = new ReadOnlyCollection<CommandBaseAst>(new CommandBaseAst[] { commandAst });
|
||||
SetParent(commandAst);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a pipeline from a single command.
|
||||
/// </summary>
|
||||
@@ -5498,16 +5538,9 @@ namespace System.Management.Automation.Language
|
||||
/// <exception cref="PSArgumentNullException">
|
||||
/// If <paramref name="extent"/> or <paramref name="commandAst"/> is null.
|
||||
/// </exception>
|
||||
public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst)
|
||||
: base(extent)
|
||||
public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst) :this (extent, commandAst, background: false)
|
||||
{
|
||||
if (commandAst == null)
|
||||
{
|
||||
throw PSTraceSource.NewArgumentNullException("commandAst");
|
||||
}
|
||||
|
||||
this.PipelineElements = new ReadOnlyCollection<CommandBaseAst>(new CommandBaseAst[] { commandAst });
|
||||
SetParent(commandAst);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -5515,6 +5548,11 @@ namespace System.Management.Automation.Language
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<CommandBaseAst> PipelineElements { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this pipeline should be run in the background.
|
||||
/// </summary>
|
||||
public bool Background { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the pipeline represents a pure expression, the expression is returned, otherwise null is returned.
|
||||
/// </summary>
|
||||
@@ -5540,7 +5578,7 @@ namespace System.Management.Automation.Language
|
||||
public override Ast Copy()
|
||||
{
|
||||
var newPipelineElements = CopyElements(this.PipelineElements);
|
||||
return new PipelineAst(this.Extent, newPipelineElements);
|
||||
return new PipelineAst(this.Extent, newPipelineElements, this.Background);
|
||||
}
|
||||
|
||||
internal override IEnumerable<PSTypeName> GetInferredType(CompletionContext context)
|
||||
@@ -6314,9 +6352,10 @@ namespace System.Management.Automation.Language
|
||||
throw PSTraceSource.NewArgumentException("operator");
|
||||
}
|
||||
|
||||
// If the assignment is just an expression - remove the pipeline wrapping the expression.
|
||||
// If the assignment is just an expression and the expression is not backgrounded then
|
||||
// remove the pipeline wrapping the expression.
|
||||
var pipelineAst = right as PipelineAst;
|
||||
if (pipelineAst != null)
|
||||
if (pipelineAst != null && ! pipelineAst.Background)
|
||||
{
|
||||
if (pipelineAst.PipelineElements.Count == 1)
|
||||
{
|
||||
@@ -6662,7 +6701,7 @@ namespace System.Management.Automation.Language
|
||||
|
||||
var cmdAst = new CommandAst(this.Extent, cea, TokenKind.Unknown, null);
|
||||
|
||||
var pipeLineAst = new PipelineAst(this.Extent, cmdAst);
|
||||
var pipeLineAst = new PipelineAst(this.Extent, cmdAst, background: false);
|
||||
var funcStatements = ConfigurationExtraParameterStatements.Select(statement => (StatementAst)statement.Copy()).ToList();
|
||||
funcStatements.Add(pipeLineAst);
|
||||
var statmentBlockAst = new StatementBlockAst(this.Extent, funcStatements, null);
|
||||
@@ -6702,7 +6741,7 @@ namespace System.Management.Automation.Language
|
||||
var setItemCmdlet = new CommandAst(this.Extent, setItemCmdElements, TokenKind.Unknown, null);
|
||||
#endregion
|
||||
|
||||
var returnPipelineAst = new PipelineAst(this.Extent, setItemCmdlet);
|
||||
var returnPipelineAst = new PipelineAst(this.Extent, setItemCmdlet, background: false);
|
||||
|
||||
SetParent(returnPipelineAst);
|
||||
|
||||
@@ -7247,7 +7286,7 @@ namespace System.Management.Automation.Language
|
||||
//
|
||||
var cmdAst = new CommandAst(FunctionName.Extent, cea, TokenKind.Unknown, null);
|
||||
cmdAst.DefiningKeyword = Keyword;
|
||||
_commandCallPipelineAst = new PipelineAst(FunctionName.Extent, cmdAst);
|
||||
_commandCallPipelineAst = new PipelineAst(FunctionName.Extent, cmdAst, background: false);
|
||||
return _commandCallPipelineAst;
|
||||
}
|
||||
|
||||
|
||||
@@ -403,13 +403,14 @@ namespace System.Management.Automation
|
||||
|
||||
CommandProcessorBase commandProcessor = null;
|
||||
CommandRedirection[] commandRedirection = null;
|
||||
|
||||
for (int i = 0; i < pipeElements.Length; i++)
|
||||
{
|
||||
commandRedirection = commandRedirections != null ? commandRedirections[i] : null;
|
||||
commandProcessor = AddCommand(pipelineProcessor, pipeElements[i], pipeElementAsts[i],
|
||||
commandRedirection, context);
|
||||
}
|
||||
|
||||
|
||||
var cmdletInfo = commandProcessor?.CommandInfo as CmdletInfo;
|
||||
if (cmdletInfo?.ImplementingType == typeof(OutNullCommand))
|
||||
{
|
||||
@@ -419,7 +420,7 @@ namespace System.Management.Automation
|
||||
// Out-Null is the only command, bail without running anything
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Out-Null is the last command, rewrite command before Out-Null to a null pipe, but
|
||||
// only if it didn't redirect anything, e.g. `Get-Stuff > o.txt | Out-Null`
|
||||
var nextToLastCommand = pipelineProcessor.Commands[commandsCount - 2];
|
||||
@@ -465,6 +466,83 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
internal static void InvokePipelineInBackground(
|
||||
PipelineAst pipelineAst,
|
||||
FunctionContext funcContext)
|
||||
{
|
||||
PipelineProcessor pipelineProcessor = new PipelineProcessor();
|
||||
ExecutionContext context = funcContext._executionContext;
|
||||
Pipe outputPipe = funcContext._outputPipe;
|
||||
|
||||
try
|
||||
{
|
||||
if (context.Events != null)
|
||||
{
|
||||
context.Events.ProcessPendingActions();
|
||||
}
|
||||
|
||||
CommandProcessorBase commandProcessor = null;
|
||||
|
||||
// For background jobs rewrite the pipeline as a Start-Job command
|
||||
var scriptblockBodyString = pipelineAst.Extent.Text;
|
||||
var pipelineOffset = pipelineAst.Extent.StartOffset;
|
||||
var variables = pipelineAst.FindAll(x => x is VariableExpressionAst, true);
|
||||
// Used to make sure that the job runs in the current directory
|
||||
const string cmdPrefix = @"Microsoft.PowerShell.Management\Set-Location -LiteralPath $using:pwd ; ";
|
||||
// Minimize allocations by initializing the stringbuilder to the size of the source string + prefix + space for ${using:} * 2
|
||||
System.Text.StringBuilder updatedScriptblock = new System.Text.StringBuilder(cmdPrefix.Length + scriptblockBodyString.Length + 18);
|
||||
updatedScriptblock.Append(cmdPrefix);
|
||||
int position = 0;
|
||||
// Prefix variables in the scriptblock with $using:
|
||||
foreach (var v in variables)
|
||||
{
|
||||
var vName = ((VariableExpressionAst) v).VariablePath.UserPath;
|
||||
// Skip variables that don't exist
|
||||
if (funcContext._executionContext.EngineSessionState.GetVariable(vName) == null)
|
||||
continue;
|
||||
// Skip PowerShell magic variables
|
||||
if (Regex.Match(vName,
|
||||
"^(global:){0,1}(PID|PSVersionTable|PSEdition|PSHOME|HOST|TRUE|FALSE|NULL)$",
|
||||
RegexOptions.IgnoreCase|RegexOptions.CultureInvariant).Success == false
|
||||
)
|
||||
{
|
||||
updatedScriptblock.Append(scriptblockBodyString.Substring(position, v.Extent.StartOffset - pipelineOffset - position));
|
||||
updatedScriptblock.Append("${using:");
|
||||
updatedScriptblock.Append(CodeGeneration.EscapeVariableName(vName));
|
||||
updatedScriptblock.Append('}');
|
||||
position = v.Extent.EndOffset - pipelineOffset;
|
||||
}
|
||||
}
|
||||
updatedScriptblock.Append(scriptblockBodyString.Substring(position));
|
||||
var sb = ScriptBlock.Create(updatedScriptblock.ToString());
|
||||
var commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand));
|
||||
commandProcessor = context.CommandDiscovery.LookupCommandProcessor(
|
||||
commandInfo, CommandOrigin.Internal, false, context.EngineSessionState);
|
||||
var parameter = CommandParameterInternal.CreateParameterWithArgument(
|
||||
pipelineAst.Extent, "ScriptBlock", null,
|
||||
pipelineAst.Extent, sb,
|
||||
false);
|
||||
commandProcessor.AddParameter(parameter);
|
||||
pipelineProcessor.Add(commandProcessor);
|
||||
pipelineProcessor.LinkPipelineSuccessOutput(outputPipe ?? new Pipe(new List<object>()));
|
||||
|
||||
context.PushPipelineProcessor(pipelineProcessor);
|
||||
try
|
||||
{
|
||||
pipelineProcessor.SynchronousExecuteEnumerate(AutomationNull.Value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.PopPipelineProcessor(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.QuestionMarkVariableValue = !pipelineProcessor.ExecutionFailed;
|
||||
pipelineProcessor.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddNoopCommandProcessor(PipelineProcessor pipelineProcessor, ExecutionContext context)
|
||||
{
|
||||
var commandInfo = new CmdletInfo("Out-Null", typeof(OutNullCommand));
|
||||
@@ -716,7 +794,7 @@ namespace System.Management.Automation
|
||||
internal static void Nop() { }
|
||||
}
|
||||
|
||||
#region Redirections
|
||||
#region Redirections
|
||||
|
||||
internal abstract class CommandRedirection
|
||||
{
|
||||
@@ -1105,7 +1183,7 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Redirections
|
||||
#endregion Redirections
|
||||
|
||||
internal static class FunctionOps
|
||||
{
|
||||
|
||||
@@ -446,9 +446,6 @@ Possible matches are</value>
|
||||
<data name="InvalidEndOfLine" xml:space="preserve">
|
||||
<value>The token '{0}' is not a valid statement separator in this version.</value>
|
||||
</data>
|
||||
<data name="AmpersandNotAllowed" xml:space="preserve">
|
||||
<value>The ampersand (&) character is not allowed. The & operator is reserved for future use; wrap an ampersand in double quotation marks ("&") to pass it as part of a string.</value>
|
||||
</data>
|
||||
<data name="FromKeywordNotAllowed" xml:space="preserve">
|
||||
<value>The 'from' keyword is not supported in this version of the language.</value>
|
||||
</data>
|
||||
@@ -1447,4 +1444,4 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent
|
||||
<data name="PsDscRunAsCredentialMergeErrorForCompositeResources" xml:space="preserve">
|
||||
<value>Conflict in using PsDscRunAsCredential for Resource {0} because it already specifies PsDscRunAsCredential value. We can only use one PsDscRunAsCredential for the composite resource. </value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
||||
@@ -277,7 +277,6 @@ Describe 'Pipes parsing' -Tags "CI" {
|
||||
ShouldBeParseError 'gps|' EmptyPipeElement 4
|
||||
ShouldBeParseError '1|1' ExpressionsMustBeFirstInPipeline 2
|
||||
ShouldBeParseError '$a=' ExpectedValueExpression 3
|
||||
ShouldBeParseError '1 &' UnexpectedToken,MissingExpression 2,2
|
||||
}
|
||||
|
||||
Describe 'commands parsing' -Tags "CI" {
|
||||
|
||||
@@ -165,3 +165,69 @@ Describe "Debug-job test" -tag "Feature" {
|
||||
$result.Command | Should be "<ScriptBlock>"
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Ampersand background test" -tag "CI","Slow" {
|
||||
Context "Simple background job" {
|
||||
AfterEach {
|
||||
Get-Job | Remove-Job -Force
|
||||
}
|
||||
It "Background with & produces a job object" {
|
||||
$j = Write-Output Hi &
|
||||
$j | Should BeOfType System.Management.Automation.Job
|
||||
}
|
||||
}
|
||||
Context "Variable tests" {
|
||||
AfterEach {
|
||||
Get-Job | Remove-Job -Force
|
||||
}
|
||||
It "doesn't cause error when variable is missing" {
|
||||
Remove-Item variable:name -ErrorAction Ignore
|
||||
$j = write-output "Hi $name" &
|
||||
Receive-Job $j -Wait | Should BeExactly "Hi "
|
||||
}
|
||||
It "Copies variables to the child process" {
|
||||
$n1 = "Bob"
|
||||
$n2 = "Mary"
|
||||
${n 3} = "Bill"
|
||||
$j = Write-Output "Hi $n1! Hi ${n2}! Hi ${n 3}!" &
|
||||
Receive-Job $j -Wait | Should BeExactly "Hi Bob! Hi Mary! Hi Bill!"
|
||||
}
|
||||
It 'Make sure that $PID from the parent process does not overwrite $PID in the child process' {
|
||||
$j = Write-Output $pid &
|
||||
$cpid = Receive-Job $j -Wait
|
||||
$pid | Should Not BeExactly $cpid
|
||||
}
|
||||
It 'Make sure that $global:PID from the parent process does not overwrite $global:PID in the child process' {
|
||||
$j = Write-Output $global:pid &
|
||||
$cpid = Receive-Job -Wait $j
|
||||
$pid | Should Not BeExactly $cpid
|
||||
}
|
||||
It "starts in the current directory" {
|
||||
$j = Get-Location | Foreach-Object -MemberName Path &
|
||||
Receive-Job -Wait $j | Should Be ($pwd.Path)
|
||||
}
|
||||
It "Test that output redirection is done in the background job" {
|
||||
$j = Write-Output hello > $TESTDRIVE/hello.txt &
|
||||
Receive-Job -Wait $j | Should Be $null
|
||||
Get-Content $TESTDRIVE/hello.txt | Should BeExactly "hello"
|
||||
}
|
||||
It "Test that error redirection is done in the background job" {
|
||||
$j = Write-Error MyError 2> $TESTDRIVE/myerror.txt &
|
||||
Receive-Job -Wait $j | Should Be $null
|
||||
Get-Content -Raw $TESTDRIVE/myerror.txt | Should Match "MyError"
|
||||
}
|
||||
}
|
||||
Context "Backgrounding expressions" {
|
||||
AfterEach {
|
||||
Get-Job | Remove-Job -Force
|
||||
}
|
||||
It "handles backgrounding expressions" {
|
||||
$j = 2+3 &
|
||||
Receive-Job $j -Wait | Should Be 5
|
||||
}
|
||||
It "handles backgrounding mixed expressions" {
|
||||
$j = 1..10 | ForEach-Object -Begin {$s=0} -Process {$s += $_} -End {$s} &
|
||||
Receive-Job -Wait $j | Should Be 55
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user