PowerShell

A Tip for Writing Better Scripts with PowerShell's Read-Host cmdlet

In a previous article I guided you through some variations on using Read-Host to prompt for user input. It’s not too surprising that I couldn’t leave the topic alone. I just had to keep working with it, and I came up with something else that I hope you’ll fine equally helpful. I wanted to take Read-Host to the next level and create a tool that will let you write better scripts.

Let’s say you are using Read-Host in a script or function to prompt for additional information. You might start with something like this:

$name = Read-Host "Enter a user name"

You most likely need to validate it, perhaps to make sure something was entered.
if ($name) {
    #code to continue
    #...
}
else {
    Write-Warning "You didn't enter a user name"
} 

Another option is to use one of the parameter validation checks. They are really variable validation tests, and you can use them anywhere.
[ValidatePattern("\w+")]$name = Read-Host "Enter a user name"

Using a validation test (Image Credit: Jeff Hicks)
Using a validation test (Image Credit: Jeff Hicks)

I didn’t use ValidateNotNullorEmpty because even if you press Enter, Read-Host writes a 0 length string to the pipeline, which technically passes the NotNullorEmpty test. So I wanted a Read-Host prompt that offered built-in validation. I also wanted to take some of the features of the $host.ui.Prompt() method like a title. And finally, I liked the idea of adding color to the prompt to make it stand out. Here’s my function, where I call Read-HostSpecial, as well as an alias.

Function Read-HostSpecial {
[cmdletbinding(DefaultParameterSetName="_All")]
Param(
[Parameter(Position = 0,Mandatory,HelpMessage = "Enter prompt text.")]
[Alias("message")]
[ValidateNotNullorEmpty()]
[string]$Prompt,
[Alias("foregroundcolor","fg")]
[consolecolor]$PromptColor,
[string]$Title,
[Parameter(ParameterSetName = "SecureString")]
[switch]$AsSecureString,
[Parameter(ParameterSetName = "NotNull")]
[switch]$ValidateNotNull,
[Parameter(ParameterSetName = "Range")]
[ValidateNotNullorEmpty()]
[int[]]$ValidateRange,
[Parameter(ParameterSetName = "Pattern")]
[ValidateNotNullorEmpty()]
[regex]$ValidatePattern,
[Parameter(ParameterSetName = "Set")]
[ValidateNotNullorEmpty()]
[string[]]$ValidateSet
)

Write-Verbose "Starting: $($MyInvocation.Mycommand)"
Write-Verbose "Parameter set = $($PSCmdlet.ParameterSetName)"
Write-Verbose "Bound parameters $($PSBoundParameters | Out-String)"


#combine the Title (if specified) and prompt
$Text = @"
$(if ($Title) {
"$Title`n$("-" * $Title.Length)"
})
$Prompt : 
"@

#create a hashtable of parameters to splat to Write-Host
$paramHash = @{
NoNewLine = $True
Object = $Text
}

if ($PromptColor) {
    $paramHash.Add("Foregroundcolor",$PromptColor)
}

#display the prompt
Write-Host @paramhash
#get the value
if ($AsSecureString) {
    $r = $host.ui.ReadLineAsSecureString()
}
else {
  #read console input
  $r = $host.ui.ReadLine() 
}

#assume the input is valid unless proved otherwise
$Valid = $True

#run validation if necessary
if ($ValidateNotNull) {
    Write-Verbose "Validating for null or empty"
    if($r.length -eq 0 -OR $r -notmatch "\S" -OR $r -eq $Null) {
        $Valid = $False
        Write-Error "Validation test for not null or empty failed."
    }
}
elseif ($ValidatePattern) {
    Write-Verbose "Validating for pattern $($validatepattern.ToString())"
    If ($r -notmatch $ValidatePattern) {
        $Valid = $False
        Write-Error "Validation test for the specified pattern failed."
    }
}
elseif ($ValidateRange) {
    Write-Verbose "Validating for range $($ValidateRange[0])..$($ValidateRange[1]) "
    if ( -NOT ([int]$r -ge $ValidateRange[0] -AND [int]$r -le $ValidateRange[1])) {
        $Valid = $False
        Write-Error "Validation test for the specified range ($($ValidateRange[0])..$($ValidateRange[1])) failed."
    }
    else {
         #convert to an integer
        [int]$r = $r 
    }
}
elseif ($ValidateSet) {
    Write-Verbose "Validating for set $($validateset -join ",")"
    if ($ValidateSet -notcontains $r) {
        $Valid = $False
        Write-Error "Validation test for set $($validateset -join ",") failed."
    }
}
If ($Valid) {
    Write-Verbose "Writing result to the pipeline"
    #any necessary validation passed
    $r
}
Write-Verbose "Ending: $($MyInvocation.Mycommand)"

} #end function
#define an alias
Set-Alias -Name rhs -Value Read-HostSpecial

The function uses several parameter sets for the different validation tasks you might want. At a minimum, all you need to enter is a message prompt. Instead of using Read-Host, I use the Readline() method to get the input.
$r = $host.ui.ReadLine()

I do this because I’m using Write-Host to display the prompt text as well as an optional title.

$Text = @"
$(if ($Title) {
"$Title`n$("-" * $Title.Length)"
})
$Prompt :
"@

In the here string I have inserted some logic in the form of an If statement. If there is a title, then it is inserted along with a string of —- that match the length. By using Write-Host, I can take advantage of its ForegroundColor parameter.
$paramHash = @{
NoNewLine = $True
Object = $Text
}
if ($PromptColor) {
    $paramHash.Add("Foregroundcolor",$PromptColor)
}
#display the prompt
Write-Host @paramhash

Using Read-HostSpecial (Image Credit: Jeff Hicks)
Using Read-HostSpecial (Image Credit: Jeff Hicks)

Sponsored Content

What is “Inside Microsoft Teams”?

“Inside Microsoft Teams” is a webcast series, now in Season 4 for IT pros hosted by Microsoft Product Manager, Stephen Rose. Stephen & his guests comprised of customers, partners, and real-world experts share best practices of planning, deploying, adopting, managing, and securing Teams. You can watch any episode at your convenience, find resources, blogs, reviews of accessories certified for Teams, bonus clips, and information regarding upcoming live broadcasts. Our next episode, “Polaris Inc., and Microsoft Teams- Reinventing how we work and play” will be airing on Oct. 28th from 10-11am PST.

You can also enter text as a secure string.

Entering a secure string (Image Credit: Jeff Hicks)
Entering a secure string (Image Credit: Jeff Hicks)

But the real part of the function are the validation tests. You can test for null or empty values, and I count a 0 length string as empty as well as anything that is only whitespace.

Validating not null or empty (Image Credit: Jeff Hicks)
Validating not null or empty (Image Credit: Jeff Hicks)

You can validate on a regular expression pattern:

Validating on a pattern (Image Credit: Jeff Hicks)
Validating on a pattern (Image Credit: Jeff Hicks)

You can validate on a set:

Validating on a set (Image Credit: Jeff Hicks)
Validating on a set (Image Credit: Jeff Hicks)

And you can validate on a range. Enter a comma separated list of the low and high values.

Validating a range (Image Credit: Jeff Hicks)
Validating a range (Image Credit: Jeff Hicks)

In the function I wrote my own validation tests primarily so that I could write my own error messages should they fail. I suppose you might want to run multiple validation tests on the input, but this version won’t do that. But you are more than welcome to take the code and run with it. In fact, even if you don’t really have a need for the function, I’m hoping you picked up a PowerShell scripting trick or two.

If you find a use for this function, I hope you’ll share your experience in the comments. Enjoy!

Related Topics:

BECOME A PETRI MEMBER:

Don't have a login but want to join the conversation? Sign up for a Petri Account

Register
Comments (1)

One response to “A Tip for Writing Better Scripts with PowerShell’s Read-Host cmdlet”

  1. <p>Thank you for sharing this advice with us. Actually, I recently started programming, in particular <a href="https://en.wikipedia.org/wiki/PHP&quot; target="_blank">PHP</a> and <a href="https://en.wikipedia.org/wiki/JavaScript&quot; target="_blank">JavaScript</a>, to be honest, this is hard enough and I realized that I had to go all the way and learn <a href="https://en.wikipedia.org/wiki/C%2B%2B&quot; target="_blank">C++</a>, but it turned out to be even harder. The most interesting thing is that I couldn’t even imagine that it would be able to carry me away so much, because I’m studying for a lawyer, but over time I realized that it’s not mine, it takes a lot of free time which I would rather spend on learning programming, if you have the same situation, I advise to <a href="https://coolessay.net/&quot; target="_blank">look here</a>. I finally had time and I paid attention to <a href="https://en.wikipedia.org/wiki/PowerShell&quot; target="_blank">PowerShell</a>, but I was afraid to approach it until I found out how this thing could be useful, in particular, for automating processes. But not only local but also huge server arrays in different places, just for a couple of lines. This is what makes it unique, but at the same time it is a rather complicated programming language.</p>

Leave a Reply

External Sharing and Guest User Access in Microsoft 365 and Teams

This eBook will dive into policy considerations you need to make when creating and managing guest user access to your Teams network, as well as the different layers of guest access and the common challenges that accompany a more complicated Microsoft 365 infrastructure.

You will learn:

  • Who should be allowed to be invited as a guest?
  • What type of guests should be able to access files in SharePoint and OneDrive?
  • How should guests be offboarded?
  • How should you determine who has access to sensitive information in your environment?

Sponsored by:

 
Live Webinar: Active Directory Security: What Needs Immediate Priority!Live on Tuesday, October 12th at 1 PM ET

Attacks on Active Directory are at an all-time high. Companies that are not taking heed are being punished, both monetarily and with loss of production.

In this webinar, you will learn:

  • How to prioritize vulnerability management
  • What attackers are leveraging to breach organizations
  • Where Active Directory security needs immediate attention
  • Overall strategy to secure your environment and keep it secured

Sponsored by: