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$PSCmdletinstance.Refer to the what-if/confirm-relevant values from the caller's scope via
$using:WhatIfPreferenceand$using:ConfirmPreference- Note that for advanced functions and scripts PowerShell translates the
-WhatIfand-Confirmswitches into the equivalent preference-variable values, using function-local variables; that is, passing-WhatIfcreates a function-local$WhatIfPreferencevariable with value$true, and passing-Confirmcreates a function-local$ConfirmPreferencewith 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
