Home > database >  PowerShell - ShouldProcess not working when called from inside Invoke-Command
PowerShell - ShouldProcess not working when called from inside Invoke-Command

Time:10-29

I am trying to add ShouldProcess logic to a script that deletes files on a remote server to I can use the -WhatIf parameter, but it is returning an error. Here is the function:

function testshouldprocess {
    [CmdletBinding(SupportsShouldProcess = $true]
    param(
        $server
    )
        invoke-command $server {
        Get-ChildItem c:\temp\ | ForEach-Object {
            if($pscmdlet.ShouldProcess($Server)) {
                remove-item $_.fullname
            }
        }
    }
}
testshouldprocess 'Server1' -WhatIf

When the script is run, it returns error

InvalidOperation: You cannot call a method on a null-valued expression.

as each file passes through the pipeline. If I change the code to

  if ($pscmdlet.ShouldProcess($server)) {
        invoke-command $server {
            Get-ChildItem c:\temp\ | ForEach-Object {
                remove-item $_.fullname
            }
        }
    }

it works, but the WhatIf only executes one time for the entire directory listing. If I change the code to

Get-ChildItem \\$server\c$\temp\ | ForEach-Object {
        if ($pscmdlet.ShouldProcess($server)) {
            remove-item $_.fullname
        }
    }

it works, but I would would much prefer to use Invoke-Command.

Is ShouldProcess not compatible with Invoke-Command?

Any insight is appreciated.

CodePudding user response:

The remote server knows only the command you execute. Not the values from the remote caller. Try with remove-item $_.fullname -Whatif:$($using:pscmdlet.ShouldProcess($server)). See Remote variables Another option is to specify $WhatIfPreference on the remote server and use that in next statements $WhatIfPreference = $using:pscmdlet.ShouldProcess($server); Then remove-item $_.fullname -WhatIf:$WhatIfPreference

CodePudding user response:

Hazrelle's answer provides the crucial pointer regarding the need to use the $using: scope in order for the remotely executing script block to have access to values from the caller's scope.

To fully support your scenario - both for -WhatIf and for -Confirm functionality, both of which are implied by turning SupportShouldProces on - you must:

  • Make your remotely executing script block an advanced one too, with its own [CmdletBinding(SupportsShouldProcess)] attribute above the param() block, and therefore its own $PSCmdlet instance.

  • Refer to the what-if/confirm-relevant values from the caller's scope via $using:WhatIfPreference and $using:ConfirmPreference

    • Note that for advanced functions and scripts PowerShell translates the -WhatIf and -Confirm switches into the equivalent preference-variable values, using function-local variables; that is, passing -WhatIf creates a function-local $WhatIfPreference variable with value $true, and passing -Confirm creates a function-local $ConfirmPreference with value High.
function testshouldprocess {
  [CmdletBinding(SupportsShouldProcess)]
  param(
    $server
  )

  Invoke-Command $server {
    [CmdletBinding(SupportsShouldProcess)]
    param()

    # Use the caller's WhatIf / Confirm preferences.
    $WhatIfPreference = $using:WhatIfPreference
    $ConfirmPreference = $using:ConfirmPreference

    Get-ChildItem c:\temp\ | ForEach-Object {
      if ($pscmdlet.ShouldProcess($using:server, "delete file: $($_.FullName)")) {
        Remove-Item $_.FullName
      }
    }
  }

}

testshouldprocess 'Server1' -WhatIf
  • Related