Skip to content

Commit dfb3866

Browse files
Bruce Payettelzybkr
authored andcommitted
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
1 parent f0b03bc commit dfb3866

7 files changed

Lines changed: 303 additions & 109 deletions

File tree

src/System.Management.Automation/engine/parser/Compiler.cs

Lines changed: 98 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ internal static class CachedReflectionInfo
318318
typeof(PipelineOps).GetMethod(nameof(PipelineOps.FlushPipe), staticFlags);
319319
internal static readonly MethodInfo PipelineOps_InvokePipeline =
320320
typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipeline), staticFlags);
321+
internal static readonly MethodInfo PipelineOps_InvokePipelineInBackground =
322+
typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipelineInBackground), staticFlags);
321323
internal static readonly MethodInfo PipelineOps_Nop =
322324
typeof(PipelineOps).GetMethod(nameof(PipelineOps.Nop), staticFlags);
323325
internal static readonly MethodInfo PipelineOps_PipelineResult =
@@ -1942,7 +1944,8 @@ private Expression CaptureStatementResultsHelper(
19421944
}
19431945

19441946
var pipelineAst = stmt as PipelineAst;
1945-
if (pipelineAst != null)
1947+
// If it's a pipeline that isn't being backgrounded, try to optimize expression
1948+
if (pipelineAst != null && ! pipelineAst.Background)
19461949
{
19471950
var expr = pipelineAst.GetPureExpression();
19481951
if (expr != null) { return Compile(expr); }
@@ -2983,104 +2986,115 @@ public object VisitPipeline(PipelineAst pipelineAst)
29832986
exprs.Add(UpdatePosition(pipelineAst));
29842987
}
29852988

2986-
var pipeElements = pipelineAst.PipelineElements;
2987-
var firstCommandExpr = (pipeElements[0] as CommandExpressionAst);
2988-
if (firstCommandExpr != null && pipeElements.Count == 1)
2989+
if (pipelineAst.Background)
29892990
{
2990-
if (firstCommandExpr.Redirections.Count > 0)
2991-
{
2992-
exprs.Add(GetRedirectedExpression(firstCommandExpr, captureForInput: false));
2993-
}
2994-
else
2995-
{
2996-
exprs.Add(Compile(firstCommandExpr));
2997-
}
2991+
Expression invokeBackgroundPipe = Expression.Call(
2992+
CachedReflectionInfo.PipelineOps_InvokePipelineInBackground,
2993+
Expression.Constant(pipelineAst),
2994+
_functionContext);
2995+
exprs.Add(invokeBackgroundPipe);
29982996
}
29992997
else
30002998
{
3001-
Expression input;
3002-
int i, commandsInPipe;
3003-
3004-
if (firstCommandExpr != null)
2999+
var pipeElements = pipelineAst.PipelineElements;
3000+
var firstCommandExpr = (pipeElements[0] as CommandExpressionAst);
3001+
3002+
if (firstCommandExpr != null && pipeElements.Count == 1)
30053003
{
30063004
if (firstCommandExpr.Redirections.Count > 0)
30073005
{
3008-
input = GetRedirectedExpression(firstCommandExpr, captureForInput: true);
3006+
exprs.Add(GetRedirectedExpression(firstCommandExpr, captureForInput: false));
30093007
}
30103008
else
30113009
{
3012-
input = GetRangeEnumerator(firstCommandExpr.Expression) ??
3013-
Compile(firstCommandExpr.Expression);
3010+
exprs.Add(Compile(firstCommandExpr));
30143011
}
3015-
i = 1;
3016-
commandsInPipe = pipeElements.Count - 1;
30173012
}
30183013
else
30193014
{
3020-
// Compiled code normally never sees AutomationNull. We use that value
3021-
// here so that we can tell the difference b/w $null and no input when
3022-
// starting the pipeline, in other words, PipelineOps.InvokePipe will
3023-
// not pass this value to the pipe.
3024-
3025-
input = ExpressionCache.AutomationNullConstant;
3026-
i = 0;
3027-
commandsInPipe = pipeElements.Count;
3028-
}
3029-
Expression[] pipelineExprs = new Expression[commandsInPipe];
3030-
CommandBaseAst[] pipeElementAsts = new CommandBaseAst[commandsInPipe];
3031-
var commandRedirections = new object[commandsInPipe];
3032-
3033-
for (int j = 0; i < pipeElements.Count; ++i, ++j)
3034-
{
3035-
var pipeElement = pipeElements[i];
3036-
pipelineExprs[j] = Compile(pipeElement);
3037-
3038-
commandRedirections[j] = GetCommandRedirections(pipeElement);
3039-
pipeElementAsts[j] = pipeElement;
3040-
}
3041-
3042-
// The redirections are passed as a CommandRedirection[][] - one dimension for each command in the pipe,
3043-
// one dimension because each command may have multiple redirections. Here we create the array for
3044-
// each command in the pipe, either a compile time constant or created at runtime if necessary.
3045-
Expression redirectionExpr;
3046-
if (commandRedirections.Any(r => r is Expression))
3047-
{
3048-
// If any command redirections are non-constant, commandRedirections will have a Linq.Expression in it,
3049-
// in which case we must create the array at runtime
3050-
redirectionExpr =
3051-
Expression.NewArrayInit(typeof(CommandRedirection[]),
3052-
commandRedirections.Select(r => (r as Expression) ?? Expression.Constant(r, typeof(CommandRedirection[]))));
3053-
}
3054-
else if (commandRedirections.Any(r => r != null))
3055-
{
3056-
// There were redirections, but all were compile time constant, so build the array at compile time.
3057-
redirectionExpr =
3058-
Expression.Constant(commandRedirections.Map(r => r as CommandRedirection[]));
3059-
}
3060-
else
3061-
{
3062-
// No redirections.
3063-
redirectionExpr = ExpressionCache.NullCommandRedirections;
3064-
}
3065-
3066-
if (firstCommandExpr != null)
3067-
{
3068-
var inputTemp = Expression.Variable(input.Type);
3069-
temps.Add(inputTemp);
3070-
exprs.Add(Expression.Assign(inputTemp, input));
3071-
input = inputTemp;
3015+
Expression input;
3016+
int i, commandsInPipe;
3017+
3018+
if (firstCommandExpr != null)
3019+
{
3020+
if (firstCommandExpr.Redirections.Count > 0)
3021+
{
3022+
input = GetRedirectedExpression(firstCommandExpr, captureForInput: true);
3023+
}
3024+
else
3025+
{
3026+
input = GetRangeEnumerator(firstCommandExpr.Expression) ??
3027+
Compile(firstCommandExpr.Expression);
3028+
}
3029+
i = 1;
3030+
commandsInPipe = pipeElements.Count - 1;
3031+
}
3032+
else
3033+
{
3034+
// Compiled code normally never sees AutomationNull. We use that value
3035+
// here so that we can tell the difference b/w $null and no input when
3036+
// starting the pipeline, in other words, PipelineOps.InvokePipe will
3037+
// not pass this value to the pipe.
3038+
3039+
input = ExpressionCache.AutomationNullConstant;
3040+
i = 0;
3041+
commandsInPipe = pipeElements.Count;
3042+
}
3043+
Expression[] pipelineExprs = new Expression[commandsInPipe];
3044+
CommandBaseAst[] pipeElementAsts = new CommandBaseAst[commandsInPipe];
3045+
var commandRedirections = new object[commandsInPipe];
3046+
3047+
for (int j = 0; i < pipeElements.Count; ++i, ++j)
3048+
{
3049+
var pipeElement = pipeElements[i];
3050+
pipelineExprs[j] = Compile(pipeElement);
3051+
3052+
commandRedirections[j] = GetCommandRedirections(pipeElement);
3053+
pipeElementAsts[j] = pipeElement;
3054+
}
3055+
3056+
// The redirections are passed as a CommandRedirection[][] - one dimension for each command in the pipe,
3057+
// one dimension because each command may have multiple redirections. Here we create the array for
3058+
// each command in the pipe, either a compile time constant or created at runtime if necessary.
3059+
Expression redirectionExpr;
3060+
if (commandRedirections.Any(r => r is Expression))
3061+
{
3062+
// If any command redirections are non-constant, commandRedirections will have a Linq.Expression in it,
3063+
// in which case we must create the array at runtime
3064+
redirectionExpr =
3065+
Expression.NewArrayInit(typeof(CommandRedirection[]),
3066+
commandRedirections.Select(r => (r as Expression) ?? Expression.Constant(r, typeof(CommandRedirection[]))));
3067+
}
3068+
else if (commandRedirections.Any(r => r != null))
3069+
{
3070+
// There were redirections, but all were compile time constant, so build the array at compile time.
3071+
redirectionExpr =
3072+
Expression.Constant(commandRedirections.Map(r => r as CommandRedirection[]));
3073+
}
3074+
else
3075+
{
3076+
// No redirections.
3077+
redirectionExpr = ExpressionCache.NullCommandRedirections;
3078+
}
3079+
3080+
if (firstCommandExpr != null)
3081+
{
3082+
var inputTemp = Expression.Variable(input.Type);
3083+
temps.Add(inputTemp);
3084+
exprs.Add(Expression.Assign(inputTemp, input));
3085+
input = inputTemp;
3086+
}
3087+
3088+
Expression invokePipe = Expression.Call(
3089+
CachedReflectionInfo.PipelineOps_InvokePipeline,
3090+
input.Cast(typeof(object)),
3091+
firstCommandExpr != null ? ExpressionCache.FalseConstant : ExpressionCache.TrueConstant,
3092+
Expression.NewArrayInit(typeof(CommandParameterInternal[]), pipelineExprs),
3093+
Expression.Constant(pipeElementAsts),
3094+
redirectionExpr,
3095+
_functionContext);
3096+
exprs.Add(invokePipe);
30723097
}
3073-
3074-
Expression invokePipe = Expression.Call(
3075-
CachedReflectionInfo.PipelineOps_InvokePipeline,
3076-
input.Cast(typeof(object)),
3077-
firstCommandExpr != null ? ExpressionCache.FalseConstant : ExpressionCache.TrueConstant,
3078-
Expression.NewArrayInit(typeof(CommandParameterInternal[]), pipelineExprs),
3079-
Expression.Constant(pipeElementAsts),
3080-
redirectionExpr,
3081-
_functionContext);
3082-
3083-
exprs.Add(invokePipe);
30843098
}
30853099

30863100
return Expression.Block(temps, exprs);

src/System.Management.Automation/engine/parser/Parser.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,7 +2451,7 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke
24512451
{
24522452
endErrorStatement = fileNameExpr.Extent;
24532453
condition = new PipelineAst(fileNameExpr.Extent,
2454-
new CommandExpressionAst(fileNameExpr.Extent, fileNameExpr, null));
2454+
new CommandExpressionAst(fileNameExpr.Extent, fileNameExpr, null), background: false);
24552455

24562456
if (!specifiedFlags.ContainsKey("file"))
24572457
{
@@ -5182,6 +5182,7 @@ private PipelineBaseAst PipelineRule()
51825182

51835183
Token pipeToken = null;
51845184
bool scanning = true;
5185+
bool background = false;
51855186
while (scanning)
51865187
{
51875188
CommandBaseAst commandAst;
@@ -5293,6 +5294,11 @@ private PipelineBaseAst PipelineRule()
52935294
case TokenKind.EndOfInput:
52945295
scanning = false;
52955296
continue;
5297+
case TokenKind.Ampersand:
5298+
SkipToken();
5299+
scanning = false;
5300+
background = true;
5301+
break;
52965302
case TokenKind.Pipe:
52975303
SkipToken();
52985304
SkipNewlines();
@@ -5328,7 +5334,7 @@ private PipelineBaseAst PipelineRule()
53285334
return null;
53295335
}
53305336

5331-
return new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements);
5337+
return new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements, background);
53325338
}
53335339

53345340
private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, RedirectionAst[] redirections, ref IScriptExtent extent)
@@ -5672,16 +5678,11 @@ internal Ast CommandRule(bool forDynamicKeyword)
56725678
case TokenKind.Semi:
56735679
case TokenKind.AndAnd:
56745680
case TokenKind.OrOr:
5681+
case TokenKind.Ampersand:
56755682
UngetToken(token);
56765683
scanning = false;
56775684
continue;
56785685

5679-
case TokenKind.Ampersand:
5680-
// ErrorRecovery: just ignore the token.
5681-
endExtent = token.Extent;
5682-
ReportError(token.Extent, () => ParserStrings.AmpersandNotAllowed);
5683-
break;
5684-
56855686
case TokenKind.MinusMinus:
56865687
endExtent = token.Extent;
56875688
// Add the first -- as a parameter, which is then ignored when constructing the command processor unless it's a native

0 commit comments

Comments
 (0)