Last active
June 10, 2025 12:01
-
-
Save powercode/dabd290a969fc524a73140ab3f3414a8 to your computer and use it in GitHub Desktop.
Proxy Command for Foreach-Object that runs in single runspace even with -Parallel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
Describe MyScriptTest { | |
BeforeAll { | |
. $PSScriptRoot\NoParallel.Foreach-Object.ps1 | |
} | |
It FakeRunsInParallel { | |
Mock Set-Content { } -Verifiable | |
# to demonstrate that $using: works in parallel | |
$Offset = 10 | |
1..10 | ForEach-Object -Parallel { | |
$offsetValue = $_ + $using:Offset | |
Set-Content -Path:"$_.txt" -Value:$offsetValue | |
} | |
Should -Invoke Set-Content -Times:10 -Exactly -ParameterFilter { [int]$Value[0] -gt 10 } | |
} | |
} | |
Describe MyModuleTest { | |
BeforeAll { | |
# create a module with a function that uses ForEach-Object -Parallel | |
$moduleUnderTest = New-Module -Name MyModule -Scriptblock { | |
function Set-ParallelContent { | |
# to demonstrate that $using: works in parallel | |
$Offset = 10 | |
1..10 | ForEach-Object -Parallel { | |
$offsetValue = $_ + $using:Offset | |
Set-Content -Path:"$_.txt" -Value:$offsetValue | |
} | |
} | |
} | Import-Module -PassThru | |
# replace Foreach-Object with the NoParallel version in the module under test | |
. $moduleUnderTest $PSScriptRoot\NoParallel.Foreach-Object.ps1 | |
} | |
It FakeRunsInParallel { | |
Mock -ModuleName:MyModule Set-Content { } -Verifiable | |
# call function in module that uses ForEach-Object -Parallel | |
Set-ParallelContent | |
Should -Invoke -ModuleName:MyModule -CommandName:Set-Content -Times:10 -Exactly -ParameterFilter: { [int]$Value[0] -gt 10 } | |
} | |
} | |
#> | |
using namespace System.Collections.Generic | |
using namespace System.Management.Automation | |
using namespace System.Management.Automation.Language | |
using namespace System.Reflection | |
#region AstRebuilder | |
# Cudos to @seeminglyscience for the AstRebuilder | |
class AstRebuilder : ICustomAstVisitor2 { | |
[object[]] VisitAll([Ast[]] $asts) { | |
return & { | |
foreach ($a in $asts) { | |
$a.Visit($this) | |
} | |
} | |
} | |
[object] Visit([Ast] $ast) { | |
return ($ast)?.Visit($this) | |
} | |
[Object] VisitTypeDefinition([TypeDefinitionAst] $typeDefinitionAst) { | |
return [TypeDefinitionAst]::new( | |
$typeDefinitionAst.Extent, | |
$typeDefinitionAst.Name, | |
[AttributeAst[]]$this.VisitAll($typeDefinitionAst.Attributes), | |
[MemberAst[]]$this.VisitAll($typeDefinitionAst.Members), | |
$typeDefinitionAst.TypeAttributes, | |
[TypeConstraintAst[]]$this.VisitAll($typeDefinitionAst.BaseTypes)) | |
} | |
[Object] VisitPropertyMember([PropertyMemberAst] $propertyMemberAst) { | |
return [PropertyMemberAst]::new( | |
$propertyMemberAst.Extent, | |
$propertyMemberAst.Name, | |
$this.Visit($propertyMemberAst.PropertyType), | |
[AttributeAst[]]$this.VisitAll($propertyMemberAst.Attributes), | |
$propertyMemberAst.PropertyAttributes, | |
$this.Visit($propertyMemberAst.InitialValue)) | |
} | |
[Object] VisitFunctionMember([FunctionMemberAst] $functionMemberAst) { | |
return [FunctionMemberAst]::new( | |
$functionMemberAst.Extent, | |
$this.Visit($functionMemberAst.Body.Parent), | |
$this.Visit($functionMemberAst.ReturnType), | |
[AttributeAst[]]$this.VisitAll($functionMemberAst.Attributes), | |
$functionMemberAst.MethodAttributes) | |
} | |
[Object] VisitBaseCtorInvokeMemberExpression([BaseCtorInvokeMemberExpressionAst] $baseCtorInvokeMemberExpressionAst) { | |
return [BaseCtorInvokeMemberExpressionAst]::new( | |
$baseCtorInvokeMemberExpressionAst.Expression.Extent, | |
$baseCtorInvokeMemberExpressionAst.Member.Extent, | |
[ExpressionAst[]]$this.VisitAll($baseCtorInvokeMemberExpressionAst.Arguments)) | |
} | |
[Object] VisitUsingStatement([UsingStatementAst] $usingStatement) { | |
if ($usingStatement.ModuleSpecification) { | |
if ($usingStatement.Alias) { | |
return [UsingStatementAst]::new( | |
$usingStatement.Extent, | |
$this.Visit($usingStatement.Alias), | |
$this.Visit($usingStatement.ModuleSpecification)) | |
} | |
return [UsingStatementAst]::new( | |
$usingStatement.Extent, | |
$this.Visit($usingStatement.ModuleSpecification)) | |
} | |
if ($usingStatement.Alias) { | |
return [UsingStatementAst]::new( | |
$usingStatement.Extent, | |
$usingStatement.UsingStatementKind, | |
$this.Visit($usingStatement.Name), | |
$this.Visit($usingStatement.Alias)) | |
} | |
return [UsingStatementAst]::new( | |
$usingStatement.Extent, | |
$usingStatement.UsingStatementKind, | |
$this.Visit($usingStatement.Name)) | |
} | |
[Object] VisitConfigurationDefinition([ConfigurationDefinitionAst] $configurationDefinitionAst) { | |
return [ConfigurationDefinitionAst]::new( | |
$configurationDefinitionAst.Extent, | |
$this.Visit($configurationDefinitionAst.Body), | |
$configurationDefinitionAst.ConfigurationType, | |
$this.Visit($configurationDefinitionAst.InstanceName)) | |
} | |
[Object] VisitDynamicKeywordStatement([DynamicKeywordStatementAst] $dynamicKeywordAst) { | |
return [DynamicKeywordStatementAst]::new( | |
$dynamicKeywordAst.Extent, | |
[CommandElementAst[]]$this.VisitAll($dynamicKeywordAst.CommandElements)) | |
} | |
[Object] VisitTernaryExpression([TernaryExpressionAst] $ternaryExpressionAst) { | |
return [TernaryExpressionAst]::new( | |
$ternaryExpressionAst.Extent, | |
$this.Visit($ternaryExpressionAst.Condition), | |
$this.Visit($ternaryExpressionAst.IfTrue), | |
$this.Visit($ternaryExpressionAst.IfFalse)) | |
} | |
[Object] VisitPipelineChain([PipelineChainAst] $statementChainAst) { | |
return [PipelineChainAst]::new( | |
$statementChainAst.Extent, | |
$this.Visit($statementChainAst.LhsPipelineChain), | |
$this.Visit($statementChainAst.RhsPipeline), | |
$statementChainAst.Operator, | |
$statementChainAst.Background) | |
} | |
[Object] DefaultVisit([Ast] $ast) { | |
return $ast.Copy() | |
} | |
[Object] VisitErrorStatement([ErrorStatementAst] $errorStatementAst) { | |
$errorFlags = [Dictionary[string, Tuple[Token, Ast]]]::new($errorStatementAst.Flags.Comparer) | |
foreach ($flag in $errorStatementAst.Flags.GetEnumerator()) { | |
$errorFlags.Add( | |
$flag.Key, | |
[Tuple[Token, Ast]]::new($flag.Value.Item1, $this.Visit($flag.Value.Item2))) | |
} | |
$flags = [BindingFlags]::Instance -bor 'NonPublic' | |
return [ErrorStatementAst]. | |
GetConstructor( | |
$flags, | |
([IScriptExtent], [Token], [IEnumerable[KeyValuePair[string, Tuple[Token, Ast]]]], [IEnumerable[Ast]], [IEnumerable[Ast]])). | |
Invoke(( | |
$errorStatementAst.Extent, | |
$errorStatementAst.Kind, | |
$errorFlags.GetEnumerator(), | |
[Ast[]]$this.VisitAll($errorStatementAst.Conditions), | |
[Ast[]]$this.VisitAll($errorStatementAst.Bodies))) | |
} | |
[Object] VisitErrorExpression([ErrorExpressionAst] $errorExpressionAst) { | |
$flags = [BindingFlags]::Instance -bor 'NonPublic' | |
return [ErrorExpressionAst]. | |
GetConstructor( | |
$flags, | |
([IScriptExtent], [IEnumerable[Ast]])). | |
Invoke(( | |
$errorExpressionAst.Extent, | |
[Ast[]]$this.VisitAll($errorExpressionAst.NestedAst))) | |
} | |
[Object] VisitScriptBlock([ScriptBlockAst] $scriptBlockAst) { | |
return [ScriptBlockAst]::new( | |
$scriptBlockAst.Extent, | |
[UsingStatementAst[]]$this.VisitAll($scriptBlockAst.UsingStatements), | |
[AttributeAst[]]$this.VisitAll($scriptBlockAst.Attributes), | |
$this.Visit($scriptBlockAst.ParamBlock), | |
$this.Visit($scriptBlockAst.BeginBlock), | |
$this.Visit($scriptBlockAst.ProcessBlock), | |
$this.Visit($scriptBlockAst.EndBlock), | |
$this.Visit($scriptBlockAst.CleanBlock), | |
$this.Visit($scriptBlockAst.DynamicParamBlock)) | |
} | |
[Object] VisitParamBlock([ParamBlockAst] $paramBlockAst) { | |
return [ParamBlockAst]::new( | |
$paramBlockAst.Extent, | |
[AttributeAst[]]$this.VisitAll($paramBlockAst.Attributes), | |
[ParameterAst[]]$this.VisitAll($paramBlockAst.Parameters)) | |
} | |
[Object] VisitNamedBlock([NamedBlockAst] $namedBlockAst) { | |
return [NamedBlockAst]::new( | |
$namedBlockAst.Extent, | |
$namedBlockAst.BlockKind, | |
[StatementBlockAst]::new( | |
$namedBlockAst.Extent, | |
[StatementAst[]]$this.VisitAll($namedBlockAst.Statements), | |
[TrapStatementAst[]]$this.Visitall($namedBlockAst.Traps)), | |
$namedBlockAst.Unnamed) | |
} | |
[Object] VisitTypeConstraint([TypeConstraintAst] $typeConstraintAst) { | |
return $typeConstraintAst.Copy() | |
} | |
[Object] VisitAttribute([AttributeAst] $attributeAst) { | |
return [AttributeAst]::new( | |
$attributeAst.Extent, | |
$attributeAst.TypeName, | |
[ExpressionAst[]]$this.VisitAll($attributeAst.PositionalArguments), | |
[NamedAttributeArgumentAst[]]$this.VisitAll($attributeAst.NamedArguments)) | |
} | |
[Object] VisitNamedAttributeArgument([NamedAttributeArgumentAst] $namedAttributeArgumentAst) { | |
return [NamedAttributeArgumentAst]::new( | |
$namedAttributeArgumentAst.Extent, | |
$namedAttributeArgumentAst.ArgumentName, | |
$this.Visit($namedAttributeArgumentAst.Argument), | |
$namedAttributeArgumentAst.ExpressionOmitted) | |
} | |
[Object] VisitParameter([ParameterAst] $parameterAst) { | |
return [ParameterAst]::new( | |
$parameterAst.Extent, | |
$this.Visit($parameterAst.Name), | |
[AttributeBaseAst[]]$this.VisitAll($parameterAst.Attributes), | |
$this.Visit($parameterAst.DefaultValue)) | |
} | |
[Object] VisitFunctionDefinition([FunctionDefinitionAst] $functionDefinitionAst) { | |
return [FunctionDefinitionAst]::new( | |
$functionDefinitionAst.Extent, | |
$functionDefinitionAst.IsFilter, | |
$functionDefinitionAst.IsWorkflow, | |
$functionDefinitionAst.Name, | |
[ParameterAst[]]$this.VisitAll($functionDefinitionAst.Parameters), | |
$this.Visit($functionDefinitionAst.Body)) | |
} | |
[Object] VisitStatementBlock([StatementBlockAst] $statementBlockAst) { | |
return [StatementBlockAst]::new( | |
$statementBlockAst.Extent, | |
[StatementAst[]]$this.VisitAll($statementBlockAst.Statements), | |
[TrapStatementAst[]]$this.Visitall($statementBlockAst.Traps)) | |
} | |
[Object] VisitIfStatement([IfStatementAst] $ifStmtAst) { | |
[Tuple[PipelineBaseAst, StatementBlockAst][]] $clauses = foreach ($clause in $ifStmtAst.Clauses) { | |
[Tuple[PipelineBaseAst, StatementBlockAst]]::new( | |
$this.Visit($clause.Item1), | |
$this.Visit($clause.Item2)) | |
} | |
return [IfStatementAst]::new( | |
$ifStmtAst.Extent, | |
$clauses, | |
$this.Visit($ifStmtAst.ElseClause)) | |
} | |
[Object] VisitTrap([TrapStatementAst] $trapStatementAst) { | |
return [TrapStatementAst]::new( | |
$trapStatementAst.Extent, | |
$this.Visit($trapStatementAst.TrapType), | |
$this.Visit($trapStatementAst.Body)) | |
} | |
[Object] VisitSwitchStatement([SwitchStatementAst] $switchStatementAst) { | |
[Tuple[ExpressionAst, StatementBlockAst][]] $clauses = foreach ($clause in $switchStatementAst.Clauses) { | |
[Tuple[ExpressionAst, StatementBlockAst]]::new( | |
$this.Visit($clause.Item1), | |
$this.Visit($clause.Item2)) | |
} | |
return [SwitchStatementAst]::new( | |
$switchStatementAst.Extent, | |
$switchStatementAst.Label, | |
$this.Visit($switchStatementAst.Condition), | |
$switchStatementAst.Flags, | |
$clauses, | |
$this.Visit($switchStatementAst.Default)) | |
} | |
[Object] VisitDataStatement([DataStatementAst] $dataStatementAst) { | |
return [DataStatementAst]::new( | |
$dataStatementAst.Extent, | |
$dataStatementAst.Variable, | |
[ExpressionAst[]]$this.VisitAll($dataStatementAst.Variable), | |
$this.Visit($dataStatementAst.Body)) | |
} | |
[Object] VisitForEachStatement([ForEachStatementAst] $forEachStatementAst) { | |
return [ForEachStatementAst]::new( | |
$forEachStatementAst.Extent, | |
$forEachStatementAst.Label, | |
$forEachStatementAst.Flags, | |
$this.Visit($forEachStatementAst.Variable), | |
$this.Visit($forEachStatementAst.Condition), | |
$this.Visit($forEachStatementAst.Body)) | |
} | |
[Object] VisitDoWhileStatement([DoWhileStatementAst] $doWhileStatementAst) { | |
return [DoWhileStatementAst]::new( | |
$doWhileStatementAst.Extent, | |
$doWhileStatementAst.Label, | |
$this.Visit($doWhileStatementAst.Condition), | |
$this.Visit($doWhileStatementAst.Body)) | |
} | |
[Object] VisitForStatement([ForStatementAst] $forStatementAst) { | |
return [ForStatementAst]::new( | |
$forStatementAst.Extent, | |
$forStatementAst.Label, | |
$this.Visit($forStatementAst.Initializer), | |
$this.Visit($forStatementAst.Condition), | |
$this.Visit($forStatementAst.Iterator), | |
$this.Visit($forStatementAst.Body)) | |
} | |
[Object] VisitWhileStatement([WhileStatementAst] $whileStatementAst) { | |
return [WhileStatementAst]::new( | |
$whileStatementAst.Extent, | |
$whileStatementAst.Label, | |
$this.Visit($whileStatementAst.Condition), | |
$this.Visit($whileStatementAst.Body)) | |
} | |
[Object] VisitCatchClause([CatchClauseAst] $catchClauseAst) { | |
return [CatchClauseAst]::new( | |
$catchClauseAst.Extent, | |
[TypeConstraintAst[]]$this.VisitAll($catchClauseAst.CatchTypes), | |
$this.Visit($catchClauseAst.Body)) | |
} | |
[Object] VisitTryStatement([TryStatementAst] $tryStatementAst) { | |
return [TryStatementAst]::new( | |
$tryStatementAst.Extent, | |
$this.Visit($tryStatementAst.Body), | |
[CatchClauseAst[]]$this.VisitAll($tryStatementAst.CatchClauses), | |
$this.Visit($tryStatementAst.Finally)) | |
} | |
[Object] VisitBreakStatement([BreakStatementAst] $breakStatementAst) { | |
return [BreakStatementAst]::new( | |
$breakStatementAst.Extent, | |
$this.Visit($breakStatementAst.Label)) | |
} | |
[Object] VisitContinueStatement([ContinueStatementAst] $continueStatementAst) { | |
return [ContinueStatementAst]::new( | |
$continueStatementAst.Extent, | |
$this.Visit($continueStatementAst.Label)) | |
} | |
[Object] VisitReturnStatement([ReturnStatementAst] $returnStatementAst) { | |
return [ReturnStatementAst]::new( | |
$returnStatementAst.Extent, | |
$this.Visit($returnStatementAst.Pipeline)) | |
} | |
[Object] VisitExitStatement([ExitStatementAst] $exitStatementAst) { | |
return [ExitStatementAst]::new( | |
$exitStatementAst.Extent, | |
$this.Visit($exitStatementAst.Pipeline)) | |
} | |
[Object] VisitThrowStatement([ThrowStatementAst] $throwStatementAst) { | |
return [ThrowStatementAst]::new( | |
$throwStatementAst.Extent, | |
$this.Visit($throwStatementAst.Pipeline)) | |
} | |
[Object] VisitDoUntilStatement([DoUntilStatementAst] $doUntilStatementAst) { | |
return [DoUntilStatementAst]::new( | |
$doUntilStatementAst.Extent, | |
$doUntilStatementAst.Label, | |
$this.Visit($doUntilStatementAst.Condition), | |
$this.Visit($doUntilStatementAst.Body)) | |
} | |
[Object] VisitAssignmentStatement([AssignmentStatementAst] $assignmentStatementAst) { | |
return [AssignmentStatementAst]::new( | |
$assignmentStatementAst.Extent, | |
$this.Visit($assignmentStatementAst.Left), | |
$assignmentStatementAst.Operator, | |
$this.Visit($assignmentStatementAst.Right), | |
$assignmentStatementAst.ErrorPosition) | |
} | |
[Object] VisitPipeline([PipelineAst] $pipelineAst) { | |
return [PipelineAst]::new( | |
$pipelineAst.Extent, | |
[CommandBaseAst[]]$this.VisitAll($pipelineAst.PipelineElements), | |
$pipelineAst.Background) | |
} | |
[Object] VisitCommand([CommandAst] $commandAst) { | |
return [CommandAst]::new( | |
$commandAst.Extent, | |
[CommandElementAst[]]$this.VisitAll($commandAst.CommandElements), | |
$commandAst.InvocationOperator, | |
[RedirectionAst[]]$this.VisitAll($commandAst.Redirections)) | |
} | |
[Object] VisitCommandExpression([CommandExpressionAst] $commandExpressionAst) { | |
return [CommandExpressionAst]::new( | |
$commandExpressionAst.Extent, | |
$this.Visit($commandExpressionAst.Expression), | |
[RedirectionAst[]]$this.VisitAll($commandExpressionAst.Redirections)) | |
} | |
[Object] VisitCommandParameter([CommandParameterAst] $commandParameterAst) { | |
return [CommandParameterAst]::new( | |
$commandParameterAst.Extent, | |
$commandParameterAst.ParameterName, | |
$this.Visit($commandParameterAst.Argument), | |
$commandParameterAst.Extent) | |
} | |
[Object] VisitFileRedirection([FileRedirectionAst] $fileRedirectionAst) { | |
return [FileRedirectionAst]::new( | |
$fileRedirectionAst.Extent, | |
$fileRedirectionAst.FromStream, | |
$this.Visit($fileRedirectionAst.Location), | |
$fileRedirectionAst.Append) | |
} | |
[Object] VisitMergingRedirection([MergingRedirectionAst] $mergingRedirectionAst) { | |
return [MergingRedirectionAst]::new( | |
$mergingRedirectionAst.Extent, | |
$mergingRedirectionAst.FromStream, | |
$mergingRedirectionAst.ToStream) | |
} | |
[Object] VisitBinaryExpression([BinaryExpressionAst] $binaryExpressionAst) { | |
return [BinaryExpressionAst]::new( | |
$binaryExpressionAst.Extent, | |
$this.Visit($binaryExpressionAst.Left), | |
$binaryExpressionAst.Operator, | |
$this.Visit($binaryExpressionAst.Right), | |
$binaryExpressionAst.ErrorPosition) | |
} | |
[Object] VisitUnaryExpression([UnaryExpressionAst] $unaryExpressionAst) { | |
return [UnaryExpressionAst]::new( | |
$unaryExpressionAst.Extent, | |
$unaryExpressionAst.TokenKind, | |
$this.Visit($unaryExpressionAst.Child)) | |
} | |
[Object] VisitConvertExpression([ConvertExpressionAst] $convertExpressionAst) { | |
return [ConvertExpressionAst]::new( | |
$convertExpressionAst.Extent, | |
$this.Visit($convertExpressionAst.Type), | |
$this.Visit($convertExpressionAst.Child)) | |
} | |
[Object] VisitConstantExpression([ConstantExpressionAst] $constantExpressionAst) { | |
return $constantExpressionAst.Copy() | |
} | |
[Object] VisitStringConstantExpression([StringConstantExpressionAst] $stringConstantExpressionAst) { | |
return $stringConstantExpressionAst.Copy() | |
} | |
[Object] VisitSubExpression([SubExpressionAst] $subExpressionAst) { | |
return [SubExpressionAst]::new( | |
$subExpressionAst.Extent, | |
$this.Visit($subExpressionAst.SubExpression)) | |
} | |
[Object] VisitUsingExpression([UsingExpressionAst] $usingExpressionAst) { | |
return [UsingExpressionAst]::new( | |
$usingExpressionAst.Extent, | |
$this.Visit($usingExpressionAst.SubExpression)) | |
} | |
[Object] VisitVariableExpression([VariableExpressionAst] $variableExpressionAst) { | |
return [VariableExpressionAst]::new( | |
$variableExpressionAst.Extent, | |
$variableExpressionAst.VariablePath, | |
$variableExpressionAst.Splatted) | |
} | |
[Object] VisitTypeExpression([TypeExpressionAst] $typeExpressionAst) { | |
return [TypeExpressionAst]::new( | |
$typeExpressionAst.Extent, | |
$typeExpressionAst.TypeName) | |
} | |
[Object] VisitMemberExpression([MemberExpressionAst] $memberExpressionAst) { | |
return [MemberExpressionAst]::new( | |
$memberExpressionAst.Extent, | |
$this.Visit($memberExpressionAst.Expression), | |
$this.Visit($memberExpressionAst.Member), | |
$memberExpressionAst.Static, | |
$memberExpressionAst.NullConditional) | |
} | |
[Object] VisitInvokeMemberExpression([InvokeMemberExpressionAst] $invokeMemberExpressionAst) { | |
return [InvokeMemberExpressionAst]::new( | |
$invokeMemberExpressionAst.Extent, | |
$this.Visit($invokeMemberExpressionAst.Expression), | |
$this.Visit($invokeMemberExpressionAst.Member), | |
[ExpressionAst[]]$this.VisitAll($invokeMemberExpressionAst.Arguments), | |
$invokeMemberExpressionAst.Static, | |
$invokeMemberExpressionAst.NullConditional) | |
} | |
[Object] VisitArrayExpression([ArrayExpressionAst] $arrayExpressionAst) { | |
return [ArrayExpressionAst]::new( | |
$arrayExpressionAst.Extent, | |
$this.Visit($arrayExpressionAst.SubExpression)) | |
} | |
[Object] VisitArrayLiteral([ArrayLiteralAst] $arrayLiteralAst) { | |
return [ArrayLiteralAst]::new( | |
$arrayLiteralAst.Extent, | |
[ExpressionAst[]]$this.VisitAll($arrayLiteralAst.Elements)) | |
} | |
[Object] VisitHashtable([HashtableAst] $hashtableAst) { | |
[Tuple[ExpressionAst, StatementAst][]] $clauses = foreach ($clause in $hashtableAst.KeyValuePairs) { | |
[Tuple[ExpressionAst, StatementAst]]::new( | |
$this.Visit($clause.Item1), | |
$this.Visit($clause.Item2)) | |
} | |
return [HashtableAst]::new( | |
$hashtableAst.Extent, | |
$clauses) | |
} | |
[Object] VisitScriptBlockExpression([ScriptBlockExpressionAst] $scriptBlockExpressionAst) { | |
return [ScriptBlockExpressionAst]::new( | |
$scriptBlockExpressionAst.Extent, | |
$this.Visit($scriptBlockExpressionAst.ScriptBlock)) | |
} | |
[Object] VisitParenExpression([ParenExpressionAst] $parenExpressionAst) { | |
return [ParenExpressionAst]::new( | |
$parenExpressionAst.Extent, | |
$this.Visit($parenExpressionAst.Pipeline)) | |
} | |
[Object] VisitExpandableStringExpression([ExpandableStringExpressionAst] $expandableStringExpressionAst) { | |
return $this.MakeExpandableString( | |
$expandableStringExpressionAst.Extent, | |
$expandableStringExpressionAst.Value, | |
$this.GetFormatString($expandableStringExpressionAst), | |
$expandableStringExpressionAst.StringConstantType, | |
[ExpressionAst[]]$this.VisitAll($expandableStringExpressionAst.NestedExpressions)) | |
} | |
[string] GetFormatString([ExpandableStringExpressionAst] $expandableStringExpressionAst) { | |
$flags = [BindingFlags]::Instance -bor 'NonPublic' | |
return [ExpandableStringExpressionAst]. | |
GetProperty('FormatExpression', $flags). | |
GetValue($expandableStringExpressionAst) | |
} | |
[ExpandableStringExpressionAst] MakeExpandableString( | |
[IScriptExtent] $extent, | |
[string] $value, | |
[string] $formatExpression, | |
[StringConstantType] $kind, | |
[ExpressionAst[]] $nestedExpressions) { | |
$flags = [BindingFlags]::Instance -bor 'NonPublic' | |
return [ExpandableStringExpressionAst]. | |
GetConstructor( | |
$flags, | |
([IScriptExtent], [string], [string], [StringConstantType], [IEnumerable[ExpressionAst]])). | |
Invoke(( | |
$extent, | |
$value, | |
$formatExpression, | |
$kind, | |
$nestedExpressions)) | |
} | |
[Object] VisitIndexExpression([IndexExpressionAst] $indexExpressionAst) { | |
return [IndexExpressionAst]::new( | |
$indexExpressionAst.Extent, | |
$this.Visit($indexExpressionAst.Target), | |
$this.Visit($indexExpressionAst.Index), | |
$indexExpressionAst.NullConditional) | |
} | |
[Object] VisitAttributedExpression([AttributedExpressionAst] $attributedExpressionAst) { | |
return [AttributedExpressionAst]::new( | |
$attributedExpressionAst.Extent, | |
$this.Visit($attributedExpressionAst.Attribute), | |
$this.Visit($attributedExpressionAst.Child)) | |
} | |
[Object] VisitBlockStatement([BlockStatementAst] $blockStatementAst) { | |
return [BlockStatementAst]::new( | |
$blockStatementAst.Extent, | |
$blockStatementAst.Kind, | |
$this.Visit($blockStatementAst.Body)) | |
} | |
} | |
class RemoveUsingExpressionRebuilder : AstRebuilder { | |
static [ScriptBlockAst] RemoveUsingExpression([ScriptBlockAst] $scriptBlockAst) { | |
$rebuilder = [RemoveUsingExpressionRebuilder]::new() | |
return $rebuilder.Visit($scriptBlockAst) | |
} | |
<# override #> | |
[object] VisitUsingExpression([UsingExpressionAst] $usingExpressionAst) { | |
return $this.Visit($usingExpressionAst.SubExpression) | |
} | |
} | |
function Remove-UsingExpression { | |
[OutputType([ScriptBlockAst])] | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory)] | |
[ScriptBlockAst] $ScriptBlockAst | |
) | |
return [RemoveUsingExpressionRebuilder]::RemoveUsingExpression($ScriptBlockAst) | |
} | |
#endregion #astrebuilder | |
#region ForEach-Object | |
function Foreach-Object { | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(<#Category#>'PSUseApprovedVerbs',<#CheckId#>'', Justification = 'Reason for suppressing')] | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(<#Category#>'PSShouldProcess',<#CheckId#>'', Justification = 'Reason for suppressing')] | |
[CmdletBinding(DefaultParameterSetName = 'ScriptBlockSet', SupportsShouldProcess, ConfirmImpact = 'Low', HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=2096867', RemotingCapability = 'None')] | |
param( | |
[Parameter(ParameterSetName = 'ScriptBlockSet', ValueFromPipeline = $true)] | |
[Parameter(ParameterSetName = 'PropertyAndMethodSet', ValueFromPipeline = $true)] | |
[Parameter(ParameterSetName = 'ParallelParameterSet', ValueFromPipeline = $true)] | |
[psobject] | |
${InputObject}, | |
[Parameter(ParameterSetName = 'ScriptBlockSet')] | |
[scriptblock] | |
${Begin}, | |
[Parameter(ParameterSetName = 'ScriptBlockSet', Mandatory = $true, Position = 0)] | |
[AllowNull()] | |
[AllowEmptyCollection()] | |
[scriptblock[]] | |
${Process}, | |
[Parameter(ParameterSetName = 'ScriptBlockSet')] | |
[scriptblock] | |
${End}, | |
[Parameter(ParameterSetName = 'ScriptBlockSet', ValueFromRemainingArguments = $true)] | |
[AllowNull()] | |
[AllowEmptyCollection()] | |
[scriptblock[]] | |
${RemainingScripts}, | |
[Parameter(ParameterSetName = 'PropertyAndMethodSet', Mandatory = $true, Position = 0)] | |
[ValidateNotNullOrEmpty()] | |
[string] | |
${MemberName}, | |
[Parameter(ParameterSetName = 'PropertyAndMethodSet', ValueFromRemainingArguments = $true)] | |
[Alias('Args')] | |
[System.Object[]] | |
${ArgumentList}, | |
[Parameter(ParameterSetName = 'ParallelParameterSet', Mandatory = $true)] | |
[scriptblock] | |
${Parallel}, | |
[Parameter(ParameterSetName = 'ParallelParameterSet')] | |
[ValidateRange(1, 2147483647)] | |
[int] | |
${ThrottleLimit}, | |
[Parameter(ParameterSetName = 'ParallelParameterSet')] | |
[ValidateRange(0, 2147483)] | |
[int] | |
${TimeoutSeconds}, | |
[Parameter(ParameterSetName = 'ParallelParameterSet')] | |
[switch] | |
${AsJob}, | |
[Parameter(ParameterSetName = 'ParallelParameterSet')] | |
[switch] | |
${UseNewRunspace}) | |
begin { | |
try { | |
$outBuffer = $null | |
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { | |
$PSBoundParameters['OutBuffer'] = 1 | |
} | |
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Core\ForEach-Object', [System.Management.Automation.CommandTypes]::Cmdlet) | |
# Modify the call if we are using the Parallel parameter set | |
if ($PSCmdlet.ParameterSetName -eq 'ParallelParameterSet') { | |
if ($AsJob) { | |
throw "The 'AsJob' parameter is not supported in this test wrapper for 'Parallel' parameter set of ForEach-Object." | |
} | |
if ($UseNewRunspace) { | |
# The assumption here is that variables are used in advanced ways that this test wrapper cannot handle. | |
throw "The 'UseNewRunspace' parameter is not supported in this test wrapper for 'Parallel' parameter set of ForEach-Object." | |
} | |
$null = $PSBoundParameters.Remove('ThrottleLimit') | |
$null = $PSBoundParameters.Remove('TimeoutSeconds') | |
$parallel = $PSBoundParameters['Parallel'] | |
if ($parallel) { | |
$noUsingScriptBlockAst = Remove-UsingExpression -ScriptBlockAst $parallel.Ast | |
$processScriptBlock = $noUsingScriptBlockAst.GetScriptBlock() | |
$null = $PSBoundParameters.Remove('Parallel') | |
$PSBoundParameters['Process'] = $processScriptBlock | |
} | |
} | |
# back to regular ForEach-Object | |
$scriptCmd = { & $wrappedCmd @PSBoundParameters } | |
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) | |
$steppablePipeline.Begin($PSCmdlet) | |
} | |
catch { | |
throw | |
} | |
} | |
process { | |
try { | |
$steppablePipeline.Process($_) | |
} | |
catch { | |
throw | |
} | |
} | |
end { | |
try { | |
$steppablePipeline.End() | |
} | |
catch { | |
throw | |
} | |
} | |
clean { | |
if ($null -ne $steppablePipeline) { | |
$steppablePipeline.Clean() | |
} | |
} | |
<# | |
.ForwardHelpTargetName Microsoft.PowerShell.Core\ForEach-Object | |
.ForwardHelpCategory Cmdlet | |
#> | |
} | |
#endregion #foreach-object |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment