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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |

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…

What I want: Parameters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |

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…

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

“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 “!?”…

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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |

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!

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
.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:

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 🙂
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
.

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.

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!
2 thoughts on “Parameters and Prompts”