Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dbadf8b
Add &! ThreadJob background operator support
Copilot Jan 1, 2026
ba401e6
Add tests for ThreadJob background operator
Copilot Jan 1, 2026
b4fd925
Add exception handling for Start-ThreadJob cmdlet lookup
Copilot Jan 1, 2026
16e6070
Use specific exception types in catch blocks for Start-ThreadJob lookup
Copilot Jan 1, 2026
023709a
Fix compilation errors: move BackgroundThreadJob to ChainableAst and …
Copilot Jan 2, 2026
332e423
Move BackgroundThreadJob property to PipelineBaseAst base class
Copilot Jan 2, 2026
523b7cf
Address PR review feedback: improve tests and simplify exception hand…
Copilot Mar 11, 2026
3c8b2d6
Fix binary-breaking change: move AmpersandExclaim token to end of enum
Copilot Mar 11, 2026
729cf1b
Remove accidentally committed backup file
Copilot Mar 11, 2026
2dedf53
Fix parser error with &! operator - set flags correctly
Copilot Mar 11, 2026
88eb01e
Fix &! operator recognition in PipelineRule switch statement
Copilot Mar 11, 2026
8c20d35
Fix missing backgroundThreadJob variable declaration in PipelineRule
Copilot Mar 11, 2026
24ae465
Fix ThreadJob detection by using GetCommand instead of GetCmdlet
Copilot Mar 11, 2026
b19bf52
Fix compilation error: properly handle CommandInfo type checking
Copilot Mar 11, 2026
d63f1c7
Fix WorkingDirectory parameter error with Start-ThreadJob
Copilot Mar 11, 2026
22b3538
Fix script block literal execution with &! operator
Copilot Mar 11, 2026
c51022a
Fix compilation error: cast PipelineBaseAst to PipelineAst before acc…
Copilot Mar 11, 2026
4a7f57f
Fix BackgroundThreadJob property not set on PipelineChainAst
Copilot Mar 11, 2026
dddf371
Apply review feedback: fix TypeNames checks, add try/finally cleanup,…
Copilot Mar 11, 2026
f6c19c4
Initial plan
Copilot Mar 11, 2026
9b8894b
Fix parser CommandRule, scriptblock body extraction, and ThreadJob cm…
Copilot Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ public static PSTokenType GetPSTokenType(Token token)
/* Hidden */ PSTokenType.Keyword,
/* Base */ PSTokenType.Keyword,
/* Default */ PSTokenType.Keyword,
/* Clean */ PSTokenType.Keyword,
/* AmpersandExclaim */ PSTokenType.Operator,

#endregion Flags for keywords

Expand Down
49 changes: 47 additions & 2 deletions src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5346,6 +5346,7 @@ private StatementAst FunctionDeclarationRule(Token functionToken)
case TokenKind.AndAnd:
case TokenKind.OrOr:
case TokenKind.Ampersand:
case TokenKind.AmpersandExclaim:
case TokenKind.Variable:
case TokenKind.SplattedVariable:
case TokenKind.HereStringExpandable:
Expand Down Expand Up @@ -5849,6 +5850,7 @@ private PipelineBaseAst PipelineChainRule()
Token currentChainOperatorToken = null;
Token nextToken = null;
bool background = false;
bool backgroundThreadJob = false;
while (true)
{
// Look for the next pipeline in the chain,
Expand Down Expand Up @@ -5938,6 +5940,24 @@ private PipelineBaseAst PipelineChainRule()
background = true;
goto default;

// ThreadJob background operator
case TokenKind.AmpersandExclaim:
SkipToken();
nextToken = PeekToken();

switch (nextToken.Kind)
{
case TokenKind.AndAnd:
case TokenKind.OrOr:
SkipToken();
ReportError(nextToken.Extent, nameof(ParserStrings.BackgroundOperatorInPipelineChain), ParserStrings.BackgroundOperatorInPipelineChain);
return new ErrorStatementAst(ExtentOf(currentPipelineChain ?? nextPipeline, nextToken.Extent));
}

background = true;
backgroundThreadJob = true;
goto default;

// No more chain operators -- return
default:
// If we haven't seen a chain yet, pass through the pipeline
Expand All @@ -5951,15 +5971,18 @@ private PipelineBaseAst PipelineChainRule()

// Set background on the pipeline AST
nextPipeline.Background = true;
nextPipeline.BackgroundThreadJob = backgroundThreadJob;
return nextPipeline;
}

return new PipelineChainAst(
var chainAst = new PipelineChainAst(
ExtentOf(currentPipelineChain.Extent, nextPipeline.Extent),
currentPipelineChain,
nextPipeline,
currentChainOperatorToken.Kind,
background);
chainAst.BackgroundThreadJob = backgroundThreadJob;
return chainAst;
}

// Assemble the new chain statement AST
Expand Down Expand Up @@ -6008,6 +6031,7 @@ private PipelineBaseAst PipelineRule(
Token nextToken = null;
bool scanning = true;
bool background = false;
bool backgroundThreadJob = false;
ExpressionAst expr = startExpression;
while (scanning)
{
Expand Down Expand Up @@ -6125,6 +6149,20 @@ private PipelineBaseAst PipelineRule(
background = true;
break;

case TokenKind.AmpersandExclaim:
if (!allowBackground)
{
// Handled by invoking rule
scanning = false;
continue;
}

SkipToken();
scanning = false;
background = true;
backgroundThreadJob = true;
break;

case TokenKind.Pipe:
SkipToken();
SkipNewlines();
Expand Down Expand Up @@ -6156,7 +6194,12 @@ private PipelineBaseAst PipelineRule(
return null;
}

return new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements, background);
var pipeline = new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements, background);
if (backgroundThreadJob)
{
pipeline.BackgroundThreadJob = true;
}
return pipeline;
}

private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, RedirectionAst[] redirections, ref IScriptExtent extent)
Expand Down Expand Up @@ -6316,6 +6359,7 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t
case TokenKind.AndAnd:
case TokenKind.OrOr:
case TokenKind.Ampersand:
case TokenKind.AmpersandExclaim:
case TokenKind.MinusMinus:
case TokenKind.Comma:
UngetToken(token);
Expand Down Expand Up @@ -6520,6 +6564,7 @@ internal Ast CommandRule(bool forDynamicKeyword)
case TokenKind.AndAnd:
case TokenKind.OrOr:
case TokenKind.Ampersand:
case TokenKind.AmpersandExclaim:
UngetToken(token);
scanning = false;
continue;
Expand Down
13 changes: 11 additions & 2 deletions src/System.Management.Automation/engine/parser/ast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5610,7 +5610,9 @@ public PipelineChainAst(
/// </returns>
public override Ast Copy()
{
return new PipelineChainAst(Extent, CopyElement(LhsPipelineChain), CopyElement(RhsPipeline), Operator, Background);
var copy = new PipelineChainAst(Extent, CopyElement(LhsPipelineChain), CopyElement(RhsPipeline), Operator, Background);
copy.BackgroundThreadJob = this.BackgroundThreadJob;
return copy;
}

internal override object Accept(ICustomAstVisitor visitor)
Expand Down Expand Up @@ -5675,6 +5677,11 @@ public virtual ExpressionAst GetPureExpression()
{
return null;
}

/// <summary>
/// Indicates that this pipeline should be run in the background as a ThreadJob.
/// </summary>
public bool BackgroundThreadJob { get; internal set; }
}

/// <summary>
Expand Down Expand Up @@ -5793,7 +5800,9 @@ public override ExpressionAst GetPureExpression()
public override Ast Copy()
{
var newPipelineElements = CopyElements(this.PipelineElements);
return new PipelineAst(this.Extent, newPipelineElements, this.Background);
var copy = new PipelineAst(this.Extent, newPipelineElements, this.Background);
copy.BackgroundThreadJob = this.BackgroundThreadJob;
return copy;
}

#region Visitors
Expand Down
9 changes: 7 additions & 2 deletions src/System.Management.Automation/engine/parser/token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,9 @@ public enum TokenKind
/// <summary>The 'clean' keyword.</summary>
Clean = 170,

/// <summary>The ThreadJob background operator '&!'.</summary>
AmpersandExclaim = 171,

#endregion Keywords
}

Expand Down Expand Up @@ -952,6 +955,7 @@ public static class TokenTraits
/* Base */ TokenFlags.Keyword,
/* Default */ TokenFlags.Keyword,
/* Clean */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName,
/* AmpersandExclaim */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant,

#endregion Flags for keywords
};
Expand Down Expand Up @@ -1152,6 +1156,7 @@ public static class TokenTraits
/* Base */ "base",
/* Default */ "default",
/* Clean */ "clean",
/* AmpersandExclaim */ "&!",

#endregion Text for keywords
};
Expand All @@ -1160,10 +1165,10 @@ public static class TokenTraits
static TokenTraits()
{
Diagnostics.Assert(
s_staticTokenFlags.Length == ((int)TokenKind.Clean + 1),
s_staticTokenFlags.Length == ((int)TokenKind.AmpersandExclaim + 1),
"Table size out of sync with enum - _staticTokenFlags");
Diagnostics.Assert(
s_tokenText.Length == ((int)TokenKind.Clean + 1),
s_tokenText.Length == ((int)TokenKind.AmpersandExclaim + 1),
"Table size out of sync with enum - _tokenText");
// Some random assertions to make sure the enum and the traits are in sync
Diagnostics.Assert(GetTraits(TokenKind.Begin) == (TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName),
Expand Down
9 changes: 8 additions & 1 deletion src/System.Management.Automation/engine/parser/tokenizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4975,12 +4975,19 @@ internal Token NextToken()
return ScanNumber(c);

case '&':
if (PeekChar() == '&')
c1 = PeekChar();
if (c1 == '&')
{
SkipChar();
return NewToken(TokenKind.AndAnd);
}

if (c1 == '!')
{
SkipChar();
return NewToken(TokenKind.AmpersandExclaim);
}

return NewToken(TokenKind.Ampersand);

case '|':
Expand Down
Loading