I have a script which takes lots of input from user. I need to validate those inputs. The issue I am currently facing is that if one of the input fails validation, the user should be prompted to re-enter only that particular input and rest all valid inputs should remain as is
Sample Code :
Function Validate
{
Param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidateLength(3,5)]
[String[]]$Value
)
}
$Value = Read-Host "Please enter a value"
Validate $Value
Write-Host $value
$Test = Read-Host "Enter Another value"
Write-Host $Test
Here when validation fails for $Value it throws exception and moves to take second input.
You can add it directly into the parameter using ValidateScript
Function Validate
{
Param(
[Parameter(Mandatory=$true)]
[ValidateScript({
while ((Read-Host "Please enter a value") -ne "SomeValue") {
Write-Host "Incorrect value... Try again"
Read-Host "Please enter a value"
}})]
[string]
$Value
)
}
You might use this PowerShell Try/Catch and Retry technique to do this like:
function Get-ValidatedInput {
function Validate {
Param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidateLength(3,5)]
[String]$Value
)
$Value
}
$Value = $Null
do {
Try {
$Value = Validate (Read-Host 'Please enter a value')
}
Catch {
Write-Warning 'Incorrect value entered, please reenter a value'
}
} while ($Value -isnot [String])
$Value
}
$Value = Get-ValidatedInput
$Test = Get-ValidatedInput
Related
How to control the while loop using boolean in Powershell?
param
(
[Parameter(Mandatory = $true, HelpMessage = "Name. -1 for all names.")]
[string]$name
)
try
{
$selected = $false
while($selected -eq $false)
{
if($name -eq "-1")
{
write-host "Displaying all names"
Get-Name | select name
}
else
{
# do some code here
$selected = $true
}
}
}
catch
{
'Name {0} was not found. Provide a valid name.' -f $name
exit(0)
}
Expected behavior:
User is prompted to enter name
If the user doesn't know the name, they can type -1
When -1 typed, the user is presented with all names
Code doesn't quit while loop
Just set $selected to $true to exit the loop when user enter "-1" in $name.
I don't really understand why you loop ?
I'm just learning switch to make my logic a bit cleaner, and it seems to work except I'm having trouble determining if my Read-Host value is a number (for the access point number to select).
## Give option to reset all aps on site
$continueVal = Read-Host "`nSpecify AP # to see more details or type 'Reset' to reset all APs in Store $Store"
## Start switch
$event = switch ($continueVal) {
[int]{
$apNumber = $continueVal
Query-AP($apNumber)
}
'Reset' {
Manage-Prelim($e = 2)
}
default {
Repeat
}
}
When I was using If/Else/ElseIf I'd use if($continueVal -gt 0) which would work, but still dirty. With switch it seems that -gt 0 is improper syntax and fails. How would I effectively check if the value of $continueVal is a number to pass it to the next function as $apNumber?
I don't want to pre-validate as possible options can come through as an integer or a string.
Here is another approach that uses parameters and parameter sets:
# testscript.ps1
[CmdletBinding(DefaultParameterSetName = "APNumber")]
param(
[Parameter(Mandatory = $true,ParameterSetName = "APNumber")]
[Int] $APNumber,
[Parameter(Mandatory = $true,ParameterSetName = "Controller")]
[String] $Controller,
[Parameter(Mandatory = $true,ParameterSetName = "Reset")]
[Switch] $Reset
)
switch ( $PSCmdlet.ParameterSetName ) {
"APNumber" {
"You specified -APNumber with value '$APNumber'"
break
}
"Controller" {
"You specified -Controller with value '$Controller'"
break
}
"Reset" {
"You specified -Reset"
break
}
}
This script is simple to use. Example usage:
testscript -APNumber 3
testscript -Controller "foo"
testscript -Reset
If you omit any parameters, it will prompt for the -APNumber parameter (since it specifies that as the default parameter set).
Now that I understand your question more, this can be done with switch -regex and parsing. Here is a short example:
do {
$response = Read-Host "Enter a response"
$valid = $true
switch -regex ( $response ) {
'^AP Number \d+$' {
$arg = [Regex]::Match($_,'\d+$').Value -as [Int]
Write-Host "You entered 'AP Number $arg'"
break
}
'^Controller \S+$' {
$arg = [Regex]::Match($_,'\S+$').Value
Write-Host "You entered 'Controller $arg'"
break
}
'^Reset$' {
Write-Host "You entered 'Reset'"
break
}
default {
$valid = $false
Write-Host "Invalid entry"
}
}
}
until ( $valid )
Note that this is more code than the parameter version, more complex, and you can't automate it.
I'm trying to make a parameter mandatory, but only if another parameter uses certain ValidateSet values. It seems that using a code block on Mandatory doesn't work as expected.
function Test-Me {
[CmdletBinding()]
Param (
[Parameter()]
[ValidateSet("NameRequired", "AlsoRequired")]
[string]
$Type = "NoNameRequired",
[Parameter(Mandatory = {-not ($Type -eq "NoNameRequired")})]
[string]
$Name
)
Process {
Write-Host "I ran the process block."
Write-Host "Type = '$Type'"
Write-Host "Name = '$Name'"
Write-Host "Name Parameter Mandatory? = '$(-not ($Type -eq "NoNameRequired"))'"
}
}
Set-StrictMode -Version Latest
function Test-Me {
[CmdletBinding(DefaultParameterSetName = "Gorgonzola")]
Param (
[Parameter(Mandatory)]
[int]
$Number,
[Parameter(Mandatory, ParameterSetName = "NameNeeded")]
[ValidateSet("NameRequired", "AlsoRequired")]
[string]
$Type = "NoNameRequired",
[Parameter(Mandatory, ParameterSetName = "NameNeeded")]
[string]
$Name
)
Process {
Write-Host "I ran the process block."
Write-Host "Number = '$Number'"
Write-Host "Type = '$Type'"
Write-Host "Name = '$Name'"
Write-Host "Name Parameter Mandatory = '$(-not ($Type -eq "NoNameRequired"))'"
}
}
Parameter sets seem to help simulate conditional mandatory parameters.
I can make it to where if either the Type or Name parameter is given, then they are both required. This can happen regardless of other parameters in the function, such as the sibling Number parameter above.
I set the default parameter set name to something random; I usually specify "None". That parameter set name doesn't need to actually exist, again indicated by the Number parameter.
All of this works regardless of your strict mode setting.
I already have my credentials stored in an xml file.
$myCredential=Get-Credential -Message "Enter the credentials."
$myCredential | Out-File "C:\cred.xml"
Now, I have a script that prompts and gets new credential when it is run.
$newCredential= Get-Credential -Message "Enter your credential."
So, how do I check if the newly provided credential is matching with the old credential without decrypting the credentials to human understandable actual plain text?
Here is how to securely compare two SecureString objects without decrypting them:
# Safely compares two SecureString objects without decrypting them.
# Outputs $true if they are equal, or $false otherwise.
function Compare-SecureString {
param(
[Security.SecureString]
$secureString1,
[Security.SecureString]
$secureString2
)
try {
$bstr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString1)
$bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString2)
$length1 = [Runtime.InteropServices.Marshal]::ReadInt32($bstr1,-4)
$length2 = [Runtime.InteropServices.Marshal]::ReadInt32($bstr2,-4)
if ( $length1 -ne $length2 ) {
return $false
}
for ( $i = 0; $i -lt $length1; ++$i ) {
$b1 = [Runtime.InteropServices.Marshal]::ReadByte($bstr1,$i)
$b2 = [Runtime.InteropServices.Marshal]::ReadByte($bstr2,$i)
if ( $b1 -ne $b2 ) {
return $false
}
}
return $true
}
finally {
if ( $bstr1 -ne [IntPtr]::Zero ) {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1)
}
if ( $bstr2 -ne [IntPtr]::Zero ) {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2)
}
}
}
You can use the above function to compare the Password property of two PSCredential objects thus:
$theyMatch = Compare-SecureString $cred1.Password $cred2.Password
if ( $theyMatch ) {
...
}
You can use the GetNetworkCredential() Method to get the plain text without saving it anywhere, just validate it.
if ($newCredential.GetNetworkCredential().Password -eq $oldCredential.GetNetworkCredential().Password )
{
return "Password is match"
}
a simple Function:
function Just-Test
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[ValidateSet('yes','no')]
[string]$inputen,
[Parameter(Mandatory=$true)]
[ValidateScript(
{
if(!(Test-Connection -ComputerName $_ -count 1 -quiet))
{
throw "no conn to server!"
}
else
{
$true
}
})]
[ValidatePattern('^19')]
[string]$comp
)
}
Then I call the function:
Just-Test -inputen 'yes' -comp '172.168.0.1'
First: there is no computer '172.168.0.1', second: the pattern is wrong, so I get the error:
Just-Test : Cannot validate argument on parameter 'comp'. The argument "172.168.0.1" does not match the "^19" pattern. Supply an argument that matches "^19" and try the command again.
Then I change the code and put [ValidationPattern] directly after ...Mandatory..:
function Just-Test
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[ValidateSet('yes','no')]
[string]$inputen,
[Parameter(Mandatory=$true)]
[ValidatePattern('^19')]
[ValidateScript(
{
if(!(Test-Connection -ComputerName $_ -count 1 -quiet))
{
throw "no conn to server!"
}
else
{
$true
}
})]
[string]$comp
)
}
Of course I get an error:
Just-Test : Cannot validate argument on parameter 'comp'. no conn to server!
but the wrong pattern of the comp-name ('172...') would be totally ignored??
Why?
I believe it would just use the last Validate attribute it sees, which would be why changing the order they are defined in would change the behavior. Really there's no reason you can't put both checks in the ValidateScript code if you are using that anyway.