Supporting -Whatif, -Confirm, -Verbose – In SCRIPTS!

<This is a super-important issue so you should definitely start using this in your scripts that you share with others (that have side effects on the system).
Please try it out and blog about it to others so that it becomes a community norm.
Thanks-jps>


One of the greatest things about PowerShell is that when you use a Cmdlet which is going to have a side effect on the system, you can always type –Whatif, -Confirm or –Verbose. Sadly, you lose this option when it comes to scripts. Over time we want to makes scripts be the semantic equivalent of Cmdlets – that is to say that a Cmdlet is a Cmdlet independent of whether you implement it in C#, VB.NET or PowerShell. That said, it takes us a while to get releases out and it really torques me that most scripts don’t support –Whatif, -Confirm, and –Verbose so I decided to do something about it. I wrote a PowerShell fuction, Should-Process, which helps you implement these in your scripts (this is very similar to how we do it in Cmdlets). First let me show you a script using it and a transcript of using that script. This is a script which stops all the running calc programs:


function Stop-Calc ([Switch]$Verbose, [Switch]$Confirm, [Switch]$Whatif)
{
$AllAnswer = $null
foreach ($p in Get-Process calc)
{ if (Should-Process Stop-Calc $p.Id ([REF]$AllAnswer) “`n***Are you crazy?” -Verbose:$Verbose -Confirm:$Confirm -Whatif:$Whatif)
{ Stop-Process $p.Id
}
}
}


Here is a transcript of using that function:


PS> calc;calc;calc
PS> Stop-Calc -Whatif
What if: Performing operation “Stop-Calc” on Target “436”
What if: Performing operation “Stop-Calc” on Target “3776”
What if: Performing operation “Stop-Calc” on Target “4104”
PS>
PS>
PS>
PS> Stop-Calc -Verbose
VERBOSE: Performing “Stop-Calc” on Target “436”.
VERBOSE: Performing “Stop-Calc” on Target “3776”.
VERBOSE: Performing “Stop-Calc” on Target “4104”.
PS> calc;calc;calc
PS>
PS>
PS>
PS> Stop-Calc -Confirm
Confirm
Are you sure you want to perform this action?
Performing operation “Stop-Calc” on Target “4596”.
***Are you crazy?
[Y] Yes [A] Yes to All [N] No [L] No to all [S] Suspend [?] Help (defau
lt is “Y”): n
Confirm
Are you sure you want to perform this action?
Performing operation “Stop-Calc” on Target “5492”.
***Are you crazy?
[Y] Yes [A] Yes to All [N] No [L] No to all [S] Suspend [?] Help (defau
lt is “Y”): s
PS> gps calc

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
55 3 1252 5148 49 0.02 4596 calc
55 3 1248 4936 49 0.06 5492 calc
55 3 1248 5168 49 0.06 5588 calc


PS> exit
Confirm
Are you sure you want to perform this action?
Performing operation “Stop-Calc” on Target “5492”.
***Are you crazy?
[Y] Yes [A] Yes to All [N] No [L] No to all [S] Suspend [?] Help (defau
lt is “Y”): l
PS>
PS>
PS>
PS> Stop-Calc -Confirm
Confirm
Are you sure you want to perform this action?
Performing operation “Stop-Calc” on Target “4596”.
***Are you crazy?
[Y] Yes [A] Yes to All [N] No [L] No to all [S] Suspend [?] Help (defau
lt is “Y”): ?
Y – Continue with only the next step of the operation.
A – Continue with all the steps of the operation.
N – Skip this operation and proceed with the next operation.
L – Skip this operation and all subsequent operations.
S – Pause the current pipeline and return to the command prompt. Type “exit”
to resume the pipeline.
[Y] Yes [A] Yes to All [N] No [L] No to all [S] Suspend [?] Help (defau
lt is “Y”): a
PS>


Here is the function that makes this all possible:


Function Should-Process ($Operation, $Target, [REF]$AllAnswer, $Warning = “”, [Switch]$Verbose, [Switch]$Confirm, [Switch]$Whatif)
{
# Check to see if “YES to All” or “NO to all” has previously been selected
# Note that this technique requires the [REF] attribute on the variable.
# Here is an example of how to use this:
# function Stop-Calc ([Switch]$Verbose, [Switch]$Confirm, [Switch]$Whatif)
# {
# $AllAnswer = $null
# foreach ($p in Get-Process calc)
# { if (Should-Process Stop-Calc $p.Id ([REF]$AllAnswer) “`n***Are you crazy?” -Verbose:$Verbose -Confirm:$Confirm -Whatif:$Whatif)
# { Stop-Process $p.Id
# }
# }
# }
if ($AllAnswer.Value -eq $false)
{ return $false
}elseif ($AllAnswer.Value -eq $true)
{ return $true
}



if ($Whatif)
{ Write-Host “What if: Performing operation `”$Operation`” on Target `”$Target`””
return $false
}
if ($Confirm)
{
$ConfirmText = @”
Confirm
Are you sure you want to perform this action?
Performing operation “$Operation” on Target “$Target”. $Warning
“@
Write-Host $ConfirmText
while ($True)
{
$answer = Read-Host @”
[Y] Yes [A] Yes to All [N] No [L] No to all [S] Suspend [?] Help (default is “Y”)
“@
switch ($Answer)
{
“Y” { return $true}
“” { return $true}
“A” { $AllAnswer.Value = $true; return $true }
“N” { return $false }
“L” { $AllAnswer.Value = $false; return $false }
“S” { $host.EnterNestedPrompt(); Write-Host $ConfirmText }
“?” { Write-Host @”
Y – Continue with only the next step of the operation.
A – Continue with all the steps of the operation.
N – Skip this operation and proceed with the next operation.
L – Skip this operation and all subsequent operations.
S – Pause the current pipeline and return to the command prompt. Type “exit” to resume the pipeline.
“@
}
}
}
}
if ($verbose)
{
Write-Verbose “Performing `”$Operation`” on Target `”$Target`”.”
}

return $true
}


I’m including this as an attachment to this entry just in case you have any copy/paste issues.


Use this often – it works great.


BTW – this all depends upon a technique that is not well documented – the [REF] attribute on parameters. Yes – PowerShell supports reference parameters! This is how we can set a variable in the Should-Process function in a way that that change is seen in the parent’s scope. We do that in order to handle the YES-TO-ALL/NO-TO-ALL feature. We need to change state and then see that change the next time we get called. Notice that this requires you to pass a reference variable using a very specific syntax ([REF]$AllAnswer). Then when we want to access this parameter, we have to access it’s VALUE – e.g. $AllAnswer.Value . This deserves an entire blog entry for itself but I needed to use it here so I thought a quick explanation was in order.


Jeffrey Snover [MSFT]
Windows PowerShell/MMC Architect
Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.msp

should-process.ps1