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 theparam()
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 valueHigh
.
- Note that for advanced functions and scripts PowerShell translates the
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