diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs
index d24520080d9..bb7fc7dc087 100644
--- a/src/System.Management.Automation/engine/parser/Compiler.cs
+++ b/src/System.Management.Automation/engine/parser/Compiler.cs
@@ -318,6 +318,8 @@ internal static class CachedReflectionInfo
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 @@ private Expression CaptureStatementResultsHelper(
}
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 @@ public object VisitPipeline(PipelineAst pipelineAst)
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[] 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 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 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);
diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs
index 72c49aee994..98d6533d201 100644
--- a/src/System.Management.Automation/engine/parser/Parser.cs
+++ b/src/System.Management.Automation/engine/parser/Parser.cs
@@ -2451,7 +2451,7 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke
{
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 @@ private PipelineBaseAst PipelineRule()
Token pipeToken = null;
bool scanning = true;
+ bool background = false;
while (scanning)
{
CommandBaseAst commandAst;
@@ -5293,6 +5294,11 @@ private PipelineBaseAst PipelineRule()
case TokenKind.EndOfInput:
scanning = false;
continue;
+ case TokenKind.Ampersand:
+ SkipToken();
+ scanning = false;
+ background = true;
+ break;
case TokenKind.Pipe:
SkipToken();
SkipNewlines();
@@ -5328,7 +5334,7 @@ private PipelineBaseAst PipelineRule()
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 @@ internal Ast CommandRule(bool forDynamicKeyword)
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
diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs
index f6a187bbb61..3e418fe5256 100644
--- a/src/System.Management.Automation/engine/parser/ast.cs
+++ b/src/System.Management.Automation/engine/parser/ast.cs
@@ -5472,13 +5472,14 @@ public class PipelineAst : PipelineBaseAst
///
/// The extent of the pipeline.
/// The collection of commands representing the pipeline.
+ /// Indicates that this pipeline should be run in the background
///
/// If is null.
///
///
/// If is null or is an empty collection.
///
- public PipelineAst(IScriptExtent extent, IEnumerable pipelineElements)
+ public PipelineAst(IScriptExtent extent, IEnumerable pipelineElements, bool background)
: base(extent)
{
if (pipelineElements == null || !pipelineElements.Any())
@@ -5486,19 +5487,37 @@ public PipelineAst(IScriptExtent extent, IEnumerable pipelineEle
throw PSTraceSource.NewArgumentException("pipelineElements");
}
+ this.Background = background;
this.PipelineElements = new ReadOnlyCollection(pipelineElements.ToArray());
SetParents(PipelineElements);
}
+ ///
+ /// Construct a pipeline from a collection of commands.
+ ///
+ /// The extent of the pipeline.
+ /// The collection of commands representing the pipeline.
+ ///
+ /// If is null.
+ ///
+ ///
+ /// If is null or is an empty collection.
+ ///
+ public PipelineAst(IScriptExtent extent, IEnumerable pipelineElements) :this (extent, pipelineElements, background: false)
+ {
+
+ }
+
///
/// Construct a pipeline from a single command.
///
/// The extent of the pipeline (which should be the extent of the command).
/// The command for the pipeline.
+ /// Indicates that this pipeline should be run in the background
///
/// If or is null.
///
- public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst)
+ public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst, bool background)
: base(extent)
{
if (commandAst == null)
@@ -5506,15 +5525,34 @@ public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst)
throw PSTraceSource.NewArgumentNullException("commandAst");
}
+ this.Background = background;
this.PipelineElements = new ReadOnlyCollection(new CommandBaseAst[] { commandAst });
SetParent(commandAst);
}
+ ///
+ /// Construct a pipeline from a single command.
+ ///
+ /// The extent of the pipeline (which should be the extent of the command).
+ /// The command for the pipeline.
+ ///
+ /// If or is null.
+ ///
+ public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst) :this (extent, commandAst, background: false)
+ {
+
+ }
+
///
/// A non-null, non-empty collection of commands that represent the pipeline.
///
public ReadOnlyCollection PipelineElements { get; private set; }
+ ///
+ /// Indicates that this pipeline should be run in the background.
+ ///
+ public bool Background { get; private set; }
+
///
/// If the pipeline represents a pure expression, the expression is returned, otherwise null is returned.
///
@@ -5540,7 +5578,7 @@ public override ExpressionAst GetPureExpression()
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 GetInferredType(CompletionContext context)
@@ -6314,9 +6352,10 @@ public AssignmentStatementAst(IScriptExtent extent, ExpressionAst left, TokenKin
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 @@ internal PipelineAst GenerateSetItemPipelineAst()
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 @@ internal PipelineAst GenerateSetItemPipelineAst()
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 @@ internal PipelineAst GenerateCommandCallPipelineAst()
//
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;
}
diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
index 02e7519174b..9ee81b6e390 100644
--- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
+++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
@@ -403,13 +403,14 @@ internal static void InvokePipeline(object input,
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 @@ internal static void InvokePipeline(object input,
// 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 @@ internal static void InvokePipeline(object input,
}
}
+ 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