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