Parameters and Prompts

When you want the best (or worst) of both worlds…

Words: 929

Time to read: ~ 5 minutes

TL;DR: Create a wrapper function that calls your properly constructed one. 🙂

Update: 21-Sep-2018 – u/Ta11ow and u/Lee_Dailey brought up two succinct points about using Read-Host as default parameters and the more appropriate method would be to create a “wrapper” function over the parameter function.
Also created a TL;DR.

Recently, I have been working together on a script with an co-worker who isn’t technically minded.

The reason why is because automation isn’t just hitting the I.T. sector, people from all over are realising that there are more convenient ways of accomplishing tasks.

However, there was an inherent difference in how my co-worker saw automation and how I saw automation.

They saw it along the lines of:

Automation will bring me to exactly where I need to go so that I can do what I’m supposed to do.

Whereas I saw it more along the lines of:

Automation will do exactly what I ask it to do; removing me from the equation so I can work on other tasks.

To this very moment I’m still not exactly sure who is right and, if we are both right then, who is more right. (that’s important between us 🙂 ).

What they want: Prompts


function Test-Prompts {
do {
[string]$Path = Read-Host Prompt 'Enter the location of the folder.'
} until (Test-Path Path $Path)
do {
[int]$Number = Read-Host Prompt 'Enter a number from 1 to 10.'
} until ($Number -ge 1 -and $Number -le 10)
do {
[string]$Set = Read-Host Prompt 'Choose a set of Set1, Set2, or Set3.'
} until ($Set -in 'Set1', 'Set2', 'Set3')
[PSCustomObject]@{
Path = $Path
Number = $Number
Set = $Set
}
}

PromptsWorking
Prompts for automation

This is the automation that they are talking about; step by step guide where, rather than you use parameters, you get prompted for the input.

In case you are wondering what the deal is with all the do...until blocks, it’s quite difficult to introduce parameter validation so this is my attempt at doing so.

Case in point…

PromptsWithBadInfo.png
We’re going to keep doing this until you get it right…

What I want: Parameters


function Test-Parameters {
[cmdletbinding()]
param(
[parameter(Mandatory, Position = 0, HelpMessage = 'Enter the location of the folder.')]
[ValidateScript({ Test-Path Path $_ })]
[Alias('PSPath')]
[string]
$Path,
[parameter(Mandatory, Position = 1, HelpMessage = 'Enter a number from 1 to 10.')]
[ValidateRange(1, 10)]
[Alias('RandomNumber')]
[int]
$Number,
[parameter(Mandatory, Position = 2, HelpMessage = 'Choose a set of Set1, Set2, or Set3.')]
[ValidateSet('Set1', 'Set2', 'Set3')]
[Alias('Option', 'Choose')]
[string]
$Set
)
process {
[PSCustomObject]@{
Path = $Path
Number = $Number
Set = $Set
}
}
}

ParametersWorksNoHelp
It works!

Now, it may be just because I’m used to this way but I like it more. We can pass in the parameters at the very start which we couldn’t do with prompts…

ParametersAtTheStart
L’one-liner!

…and validation is built in for us when we use the ValidateSet, ValidateRange, and ValidateScript attributes…

ParameterValidation.png
1. Script 2. Range 3. Set

but what about the help? How are we going to know what we’re supposed to put in there? ‘Path’, ‘Number’, and ‘Set’ could mean anything!”

Not a problem, we can just run it again and, when we’re prompted for a value, we can just write “!?”…

ParametersWorking
“!?” for help

As you may have guessed…

…this did not go down well with my co-worker.

That?! How are we supposed to know to write “!?” there? How does that even mean help anyway? Can we not just get the help message to show up on it’s own!

To which I said…

We can but it’s going to create a massive bottleneck! If we try and put in the help messages then it’s going to slow things down eventually because we’re not going to be able to run this without someone babysitting it! Do you want to be the person that has to sit there watching this every single time it’s run?

Compromise is a wonderful thing though and we eventually managed to merge the best of both worlds…

What we got: Parameters and Prompts


function Test-ParametersAndPrompts {
[cmdletbinding()]
param(
[parameter(Position = 0, HelpMessage = 'Enter the location of the folder.')]
[ValidateScript({ Test-Path Path $_ })]
[Alias('PSPath')]
[string]
$Path,
[parameter(Position = 1, HelpMessage = 'Enter a number from 1 to 10.')]
[ValidateRange(1, 10)]
[Alias('RandomNumber')]
[int]
$Number,
[parameter(Position = 2, HelpMessage = 'Choose a set of Set1, Set2, or Set3.')]
[ValidateSet('Set1', 'Set2', 'Set3')]
[Alias('Option', 'Choose')]
[string]
$Set
)
process {
switch ($PSBoundParameters) {
{ $_.Keys -notcontains 'Path' } {
Try {
$Path = Read-Host Prompt 'Enter the location of the folder.' ErrorAction Stop
} catch {
Write-Error $error[0]
return
}
}
{ $_.Keys -notcontains 'Number' } {
try {
$Number = Read-Host Prompt 'Enter a number from 1 to 10.' ErrorAction Stop
} catch {
Write-Error $error[0]
return
}
}
{ $_.Keys -notcontains 'Set' } {
Try {
$Set = Read-Host Prompt 'Choose a set of Set1, Set2, or Set3.' ErrorAction Stop
} catch {
Write-Error $error[0]
return
}
}
}
[PSCustomObject]@{
Path = $Path
Number = $Number
Set = $Set
}
}
}

ParametersAndPrompts.png
Prompts, Parameter, and…party?

Thanks to the joy that is $PSBoundParameters (Get-Help about_Automatic_Variables | Select-String -Pattern 'PSBoundParameters' -Context 0,5)[0]) we can tell when a parameter is passed in, and prompt for it when it’s not.

$PSBoundParameters
Contains a dictionary of the parameters that are passed to a script
or function and their current values. This variable has a value only
in a scope where parameters are declared, such as a script or function.
You can use it to display or change the current values of parameters
or to pass parameter values to another script or function.

This way we can include full automation without the need for baby-sitting while allowing helpful prompts to be displayed for any users not trained in the function.

A great addition is that the prompts still respect the Validation that we did in the param() block!

ParametersAndPromptsValidation.png
All the Validation!

 

Update 21-Sep-2018

It was pointed out to me on the PowerShell Reddit forum (if you’re not on there already sign up, even if it’s just for notifications of trending posts. I cannot begin to explain how much I’ve improved from there) that a potentially easier method for this would be to make the default value of a parameter be Read-Host.


function Test-DefaultParameters {
[cmdletbinding()]
param(
[parameter(Position = 0)]
[ValidateScript({ Test-Path Path $_ })]
[Alias('PSPath')]
[string]
$Path = (Read-Host Prompt 'Enter the location of the folder'),
[parameter(Position = 1)]
[ValidateRange(1, 10)]
[Alias('RandomNumber')]
[int]
$Number = (Read-Host Prompt 'Enter a number from 1 to 10'),
[parameter(Position = 2)]
[ValidateSet('Set1', 'Set2', 'Set3')]
[Alias('Option', 'Choose')]
[string]
$Set = (Read-Host Prompt 'Choose a set of Set1, Set2, or Set3')
)
process {
[PSCustomObject]@{
Path = $Path
Number = $Number
Set = $Set
}
}
}

As you can see it works perfectly:

DefaultParameters.PNG
So much simpler!

An even better idea though, was when they said that recommended practices would be to create a wrapper function. That way you can have your properly constructed function, with validation, verbose comments, confirmation messages, etc nicely separated from their semi-manual process.

Plus it’s so much sneakier which, I’m not going to lie, appeals to me on some level 🙂


function ConvertTo-Parameters {
$Path = (Read-Host Prompt 'Enter the location of the folder')
$Number = (Read-Host Prompt 'Enter a number from 1 to 10')
$Set = (Read-Host Prompt 'Choose a set of Set1, Set2, or Set3')
Test-Parameters Path $Path Number $Number Set $Set
}

That way, my co-worker can call this function while I happily automate it away with Test-Parameters.

ConvertTo-Parameters.PNG
Like a snickers bar, it’s got a wrapper (bad joke intended)

I’m liking this!

Overall…

…if an aspect is in the way, and it’s an obvious and easy task to remove it that won’t cause issues down the line, then why not just remove it?

This way we have a way that keeps both parties happy; my co-worker with his prompts and me with my parameters.

I’ll take that.

P.S.

I do most of my coding in VSCode now and found a lovely little bug in the PowerShellEditorServices.

MiniBug.PNG
No help for you, you play the game on extreme difficulty.

See it? No “!?” option for help.

Added a bug report here so if smarter people than me want to work on that, it’d be much appreciated.

Thank you!

 

 

 

 

 

 

 

Author: Shane O'Neill

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

2 thoughts on “Parameters and Prompts”

Leave a Reply

%d bloggers like this: