2

There is a script that has CmdletBinding attribute, which effectively makes it an “advanced” script. Inside the script I'm processing data in a pipeline in parallel, and I want the -WhatIf parameter to be passed down to the processing script block when I pass it to the script invocation.

Simplified code:

#Requires -Version 7.2
[CmdletBinding(SupportsShouldProcess = $true)]
param()

Get-ChildItem | ForEach-Object -Parallel {
    if ($PSCmdlet.ShouldProcess("target", "operation")) {
        Write-Host "Processing"
    }
}
PS C:\> script.ps1 -WhatIf
InvalidOperation: 
Line |
   2 |      if ($PSCmdlet.ShouldProcess("target", "operation")) {
     |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | You cannot call a method on a null-valued expression.

This does not work because $PSCmdlet is not defined in the script block.

When I replace $PSCmdlet with ($using:PSCmdlet), I get another error (only when -WhatIf is provided):

MethodInvocationException: 
Line |
   2 |      if (($using:PSCmdlet).ShouldProcess("target", "operation")) {
     |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Exception calling "ShouldProcess" with "2" argument(s): "The WriteObject and WriteError methods cannot be called from outside the overrides of the BeginProcessing, ProcessRecord, and EndProcessing methods, and they can only be called from within the same thread. Validate that the cmdlet makes these calls correctly, 
or contact Microsoft Customer Support Services."

Obviously, this happens because script blocks are executed in seaprate threads (“they can only be called from within the same thread”).

How to properly handle -WhatIf inside the script blocks of Foreach-Object -Parallel?

I've read this official article and seen this comment to the PowerShell issue #13816. Maybe another related issue: #14984.

As a side-note: specifying -WhatIf to the ForEach-Object itself doesn't make any difference in this case. This is also noticed here: https://thedavecarroll.com/powershell/foreach-object-whatif/#script-blocks-and--whatif

AKd
  • 501
  • 4
  • 17
  • 1
    Well `ForEach-Object -Parallel` doesnt seem to support risk management parameters, you can give a try the function in [this answer](https://stackoverflow.com/questions/74257556/is-there-an-easier-way-to-run-commands-in-parallel-while-keeping-it-efficient-in) instead of using `ForEach-Object -Parallel`. `-WhatIf` and `-Confirm` seem to work for the most part except for "Suspend" – Santiago Squarzon Jan 01 '23 at 15:28
  • 1
    Leaving that aside, multithreading risk management (confirmation) doesn't make much sense which is most likely why SupportsShouldProcess doesnt work in `-Parallel` or `ThreadJob` – Santiago Squarzon Jan 01 '23 at 15:40
  • Thanks for suggestion, I'll look into that function and see if I can use in my particular task. I agree that confirmation doesn't make sense within other threads, but `-WhatIf` makes sense, I think. Since `ShouldProcess` takes care of both aspects of risk mitigation, it obviously doesn't work here. So is using `$WhatIfPreference` manually our only resort? – AKd Jan 02 '23 at 04:01

1 Answers1

0

The closest I could get to something working, as you can see it is very cumbersome. First, as stated, Risk Management parameters do not seem to work in ForEach-Object -Parallel nor Start-ThreadJob. Using the function from this answer, -WhatIf seem to work properly, however the workaround does require an inner script block also supporting SupportsShouldProcess.

This is how the code would look:

[CmdletBinding(SupportsShouldProcess)]
param()

Get-ChildItem | Invoke-Parallel {
    & {
        [CmdletBinding(SupportsShouldProcess)]
        param()
        if ($PSCmdlet.ShouldProcess($_, "doing something")) {
            Write-Host "Processing"
        }
    } -WhatIf:$WhatIf
} -Variables @{ WhatIf = [bool] $PSBoundParameters['WhatIf'] }
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • Thanks for this alternative to `ForEach-Object -Parallel`. Though in the context of this question I'd like to figure out how to deal with `-WhatIf` specifically in `ForEach-Object -Parallel`. – AKd Jan 03 '23 at 18:02
  • @AKd I understand what you mean what you mean but as you may have noted in https://github.com/PowerShell/PowerShell/issues/13816 it's not supported, output from WhatIf is not redirected to the console – Santiago Squarzon Jan 03 '23 at 18:09
  • Yes, `ShouldProcess` is not supported there. But I guess it's not the only way of adding WhatIf support, though being the recommended one in usual scenario. I envision that we can build a solution with `$using:WhatIfPreference` and conditional logic and `Write-Host` as a poor-man implementation. Still, I hope to hear from experts, whether there is a more elegant/built-in way. – AKd Jan 04 '23 at 06:30