Change Read-Host to Confirm!

Words: 884

Time to read: ~ 4 minutes

Update: 29-Aug-2018 – Thanks to the good folks in reddit (u/TheIncorrigible1 & u/Ta11ow) who pointed out that Pause is a function, not an alias & a really cool addition to the confirm attribute. Also added the second step I left out of the 1st draft (oops).

As more and more companies are doing and should be doing, we’re creating scripts and automating things away.

Also, like more and more companies, we’re not the best at it just yet. We’re trying; we make mistakes but we aim to learn from them.

So when a call came to add a “safety-net”, a pause in a script so that the user knows what they’re changing, we originally went with using pause.

Here’s why you should use the built in confirm instead.

Pause

We’re only going to be dealing with numbers from 1 to 10.

Let’s start off simple and only use the pause function:


#region Test-ManualPause
function Test-ManualPause {
[CmdletBinding()]
param(
[int[]]$Numbers
)
process {
foreach ($num in $Numbers) {
Pause
"Current number: [$num]"
}
}
}
Test-ManualPause Numbers $Numbers
#endregion

Test-ManualConfirm
enter…enter…enter…enter…

I mean, that’s fine and it does what it should BUT…

  • We have no idea what we’re working on next, just “Press Enter to continue…:
  • Unless you KNOW that Ctrl + C cancels commands, there is no way to exit this. You’ll try to exit and end up processing what you don’t want to!

So we go back and try to fix the first point, how can we see what we are about to process?

Read-Host

We’re just going to change pause to Read-Host so we can specify the message. It’s not that big a deal really since if you run Get-Content function:pause you can see that pause = Read-Host!

WhatIsPause.PNG
Uses $null and everything!


#region Test-ManualConfirm
function Test-ManualConfirm {
[CmdletBinding()]
param(
[int[]]$Numbers
)
process {
foreach ($num in $Numbers) {
Read-Host Prompt "Are you sure you want to process: [$num]"
"Current number: [$num]"
}
}
}
Test-ManualConfirm Numbers $Numbers
#endregion

Test-ManualConfirmReal
Are you sure you want to process: Maybe

So now we know what we are working on BUT…

  • Unless you KNOW that Ctrl + C cancels commands, there is no way to exit this. You’ll try to exit and end up processing what you don’t want to!

So we go back and try to give them a yes or no option.

Read-Host then some

This time we save what the users says to our Read-Host, if they say yes (“Y”) then we continue, otherwise we stop.


#region Test-ListeningConfirm
function Test-ListeningConfirm {
[CmdletBinding()]
param(
[int[]]$Numbers
)
process {
foreach ($num in $Numbers) {
$keepGoing = Read-Host "Are you sure you want to process: [$num] (Y/N)"
if ($keepGoing -eq 'Y') {
"Current number: [$num]"
} else {
break
}
}
}
}
Test-ListeningConfirm Numbers $Numbers
#endregion

Test-ListeningConfirm

So now we have given the option of Yes or No.

Then we get told that this is automation, right? They don’t want to say Yes every single time or No every single time. So now we have to add those options as well!

Yes, All, No, Exit

So now we have to check if the use says Yes, No, Exit, otherwise we default to All.

So we use Read-Host and switch (with the -regex switch because we’ve had to give them full word options and they’re probably only going to say a single letter for what they want).

We also have to add a flag ($doAll) and a check if it’s set or not for our “do all” option.


#region Test-YesAllConfirm
function Test-YesAllConfirm {
[CmdletBinding()]
param(
[int[]]$Numbers
)
process {
foreach ($num in $Numbers) {
if (-not $doAll) {
$keepGoing = Read-Host "Are you sure you want to process: [$num] (Yes, No, All (default), Exit)"
switch Regex ($keepGoing) {
'Yes' { "Current number: [$num]" }
'No' { break }
'Exit' { return }
Default {
$doAll = $true
"Current number: [$num]"
}
}
} else {
"Current number: [$num]"
}
}
}
}
Test-YesAllConfirm Numbers $Numbers
#endregion

Test-YesAllConfirm.PNG

Finally it works… BUT…

  • It’s 26 lines long for this piece of code. If we have multiple then this is going to blow up size wise, and
  • There’s definitely more but I think I’ve gotten the point across (or I hope I have).

So let’s try and use what’s built in to PowerShell.

Confirm

It breaks down to 3 lines of code.

  1. In the [CmdletBinding()] attribute, we add a SupportsShouldProcess & a ConfirmImpact and set it to a value of either Low, Medium, or High. I set it to High cause I want to see it. (Want to know why? Run this Get-Help about_Preference_variables | More and check out the “ConfirmPreference” section).
  2. We wrap what we want to do in an if statement and use $PSCmdlet.ShouldProcess() to specify our confirm “target”.


#region Test-Confirm
function Test-Confirm {
[CmdletBinding(SupportsShouldProcess,
ConfirmImpact = 'High')]
param(
[int[]]$Numbers
)
process {
foreach ($num in $Numbers) {
if ($PSCmdlet.ShouldProcess($num)) {
"Current numbers: [$num]"
}
}
}
}
Test-Confirm Numbers $Numbers
#endregion

Test-Confirm.PNG

Woah! Look at that!

3 lines; we get “Yes”, “Yes to all”, “No”, “No to all”, “Suspend”, and “Help”!

I don’t even know what suspend does so I checked it out. It actually pauses our script and we can check the current values of our variables. That’s super handy and we can just “exit” back to our script.

I haven’t even checked out the Help option yet!

It’s actually so much simpler to use confirm! So much so that I actually feel bad for all the times that I didn’t use it and used Read-Host instead!

UPDATE

I had actually been trying to think of a way to do this but couldn’t figure it out until u/Ta11ow pointed me to the online docs of the SupportProcess method. Thanks to that I now know how to create a custom message with Confirm!


#region Test-EditedConfirm
function Test-EditedConfirm {
[CmdletBinding(SupportsShouldProcess,
ConfirmImpact = 'High')]
param(
[int[]]$Numbers
)
process {
foreach ($num in $Numbers) {
if ($PSCmdlet.ShouldProcess("Guess what this does? [$num]",
"Are you sure you want to process: [$num]",
'STOP!')) {
"Current numbers: [$num]"
}
}
}
}
Test-EditedConfirm Numbers $Numbers
#endregion

TestEditedConfirm.PNG
The 3rd option becomes the first line!

So now even our confirm can be edited to our liking. I think that’s brilliant!

And in case you are wondering what the first option “Guess what this does?” do…

TestEditedConfirmWhatif.PNG
Say what!!

Congratulations! You’ve also just introduced -Whatif as well 🙂

Overall

However, the main reason that you should start to use Confirm instead of Pause is that Confirm can be turned off!

There’s no point automating away the manual tasks if you then go back in and create a human step again!

All we need to do is add a -confirm:$false to our script and we can turn off the command and have our tests run as they should.

Automated & independent.

ConfrimFalse.PNG

Luckily our scripts are in source control, please excuse me while I change them 🙂

Author: Shane O'Neill

DBA, T-SQL and PowerShell admirer, Food, Coffee, Whiskey (not necessarily in that order)...

6 thoughts on “Change Read-Host to Confirm!”

    1. I think this is pretty cool but, I can’t get “A” Yes to All to work if your script has multiple of these and if that doesn’t work, you script looks goofy, because it keeps asking even if they say “A”

Leave a Reply

%d bloggers like this: