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.
Related
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
I am having trouble thinking of the correct logic on how to go about this. I have a function that accepts two types of switches:
-Add
-Remove
Function Test-Bool {
Param (
# Input Parameters
[Parameter(Mandatory = $false,
HelpMessage='Enter. Workflow. Name.')]
[Alias('OMB','MailBox')]
[string]$Workflow,
[Parameter(Mandatory = $false)]
[Alias('EDIPI','DisplayName')]
[string[]]$UserName
)
DynamicParam {
if ($Workflow -ne $null -and $UserName -ne $null) {
$parameterAttribute = [System.Management.Automation.ParameterAttribute]#{
ParameterSetName = "AddingMembers"
Mandatory = $false
}
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add($parameterAttribute)
$dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'Add', [switch], $attributeCollection
)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('Add', $dynParam1)
$parameterAttribute1 = [System.Management.Automation.ParameterAttribute]#{
ParameterSetName = "RemovingMembers"
Mandatory = $false
}
$attributeCollection1 = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection1.Add($parameterAttribute1)
$dynParam11 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'Remove', [switch], $attributeCollection1
)
$paramDictionary.Add('Remove', $dynParam11)
return $paramDictionary
}
}
Begin {
$ABool = {
'Add Block'
$Bool = Read-Host -Prompt 'Adding or Removing Member(s)'
if ($Bool.ToLower() -like 'a*') {
$true
}
else {
Break;
}
}
$RBool = {
'Remove Block'
$Bool = Read-Host -Prompt 'Adding or Removing Member(s)'
if ($Bool.ToLower() -like 'r*') {
$true
}
else {
Break;
}
}
if ($PSBoundParameters['Add'].IsPresent) { [bool]$Add = $true }
elseif ($PSBoundParameters['Remove'].IsPresent) { [bool]$Remove = $true }
elseif (-not$PSBoundParameters['Add'].IsPresent) { $Add = & $ABool }
elseif (-not$PSBoundParameters['Remove'].IsPresent) { $Remove = & $RBool }
}
Process {
if ($Add) {
"Add was selected"
}
if ($Remove) {
"Remove was selected"
}
}
}
I can run it several ways:
Test-Bool -Workflow spg -UserName a -Add - works
Test-Bool -Workflow spg -UserName a -Remove - works
Test-Bool -Workflow spg
Should ask to remove or add: Typing Add, works.
Typing Remove, does not work.
Which makes sense, because elseif (-not$PSBoundParameters['Add'].IsPresent) { $Add = & $ABool } is evaluated first and when asked, Remove is typed in which throws it into the else block making it Break/Exit.
QUESTION:
What can I change to make $Remove be evaluated to true and vise-versa when Add is typed (to make $Add True)?
I honestly confused myself so much that I just decided to post here instead of attempting to figure this out on my own.
Use Parameter Sets instead:
Function Test-Bool {
[CmdletBinding(DefaultParameterSetName = 'Undecided')]
Param (
# Input Parameters
[Parameter(Mandatory = $false,
HelpMessage='Enter. Workflow. Name.')]
[Alias('OMB','MailBox')]
[string]$Workflow,
[Parameter(Mandatory = $false)]
[Alias('EDIPI','DisplayName')]
[string[]]$UserName,
[Parameter(Mandatory = $true, ParameterSetName = 'Add')]
[switch]$Add,
[Parameter(Mandatory = $true, ParameterSetName = 'Remove')]
[switch]$Remove
)
begin {
$action = if($PSCmdlet.ParameterSetName -eq 'Undecided'){
$answer = Read-Host -Prompt 'Adding or Removing Member(s)?'
if($answer -like 'a*'){
'Add'
}
elseif($answer -like 'r*') {
'Remove'
}
else {
throw 'Invalid option provided'
}
}
else {
$PSCmdlet.ParameterSetName
}
# $action now contains either 'Add' or 'Remove'
$actionBlock = #{
'Add' = { <# code to add user to workflow #> }
'Remove' = { <# code to remove user from workflow #> }
}[$action]
}
process {
# if/else statements no longer needed, $actionBlock contains the correct scriptblock
. $actionBlock
}
}
If a user doesn't specify either switch, the parameter set name will be Undecided, and the user will be prompted in the begin block - otherwise we simply use the parameter set name associated with the specified switch.
Since the two switch parameters belong to separate parameter sets, the user can no longer pick both.
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 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"
}
Last week I ran into a strange issue.
I modified code that should test if a file is locked and which I once found on the Internet (essentially similar to https://superuser.com/questions/876288/how-do-i-detect-and-skip-locked-files-in-a-powershell-script) to
1) deal with timeout and
2) to handle non existing files (and directories) correctly.
So here is my code:
function Test-IsFileLocked {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, Position = 0)]
[ValidateNotNullOrEmpty()]
[System.IO.FileInfo] $File
, [ref] $MSecsPassed
)
try {
$Stream = $File.Open([System.IO.FileMode]"Open", [System.IO.FileAccess]"Read", [System.IO.FileShare]"None")
}
# The follwing catch block seems to cause the problem that the 'IO.FileNotFoundException' gets caught after some time (although it is still a simple 'IO.IOException.)
# Outcomment it by removing the '#' in front of '<# ...' to see how all works nicely...
#<#
catch [System.IO.DirectoryNotFoundException] {
Write-Host "System.IO.DirectoryNotFoundException: '$($_.Exception)'`nType = '$($_.Exception.GetType().FullName)'`nType = '$($_.GetType().FullName)'" -ForegroundColor:Yellow
return $False
}
#>
catch [System.IO.FileNotFoundException] {
Write-Host "Caught a 'FileNotFoundException' exception, although the exception still is of type of a simple 'System.IO.IOException'! MSecs passed: $($MSecsPassed.Value)" -ForegroundColor:Red
Write-Host "FileNotFoundException: '$($_.Exception)'`nType = '$($_.Exception.GetType().FullName)'`nType = '$($_.GetType().FullName)'" -ForegroundColor:Yellow
#if ($_.Exception.GetType().FullName -eq 'System.IO.IOException') {
# return $True
#} else {
return $False
#}
} catch [System.IO.IOException] {
#Write-Host "System.IO.IOException: '$($_.Exception)'`nType = '$($_.Exception.GetType().FullName)'`nType = '$($_.GetType().FullName)'"
if ($_.Exception.GetType().FullName -eq 'System.IO.IOException') {
return $True
} else {
return $False
}
} catch {
Write-Host "Any Exception: '$($_.Exception)'`nType = '$($_.Exception.GetType().FullName)'`nType = '$($_.GetType().FullName)'" -ForegroundColor:Yellow
if ($_.Exception.GetType().FullName -eq 'System.IO.IOException') {
return $True
} else {
return $False
}
} finally {
if ($Stream) {
$Stream.Dispose()
}
}
return $False
}
function Wait-UntilFileIsAccessible {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, Position = 0)]
[ValidateNotNullOrEmpty()]
[System.IO.FileInfo] $File
, [Parameter(Position = 1)]
[uint32] $CheckIntervalInMSecs
, [Parameter(Position = 2)]
[uint32] $Timeout
)
if (!$CheckIntervalInMSecs) {
$CheckIntervalInMSecs = 500
}
[double] $SecondsPassed = 0.0
[double] $SecondsAdder = [double]$CheckIntervalInMSecs / 1000.0
[uint32] $MSecsPassed = 0
#Write-Verbose "Waiting for '$($File.Fullname)' to get unlocked!"
while (Test-IsFileLocked -File:$File -MSecsPassed:([ref]$MSecsPassed)) {
Start-Sleep -MilliSeconds:$CheckIntervalInMSecs
$MSecsPassed += $CheckIntervalInMSecs
if ($Timeout) {
$SecondsPassed += $SecondsAdder
if (([uint32]$SecondsPassed) -ge $Timeout) {
return $False
}
}
}
#Write-Vebose "'$($File.Fullname)' now isn't locked anymore"
return $True
}
If I call this code with
Wait-UntilFileIsAccessible -File:'C:\LockedByMSWord.txt' -Timeout:30
where 'C:\LockedByMSWord.txt' is an existing file actually locked (e.g. opened with MS Word) then after some time (16500 ms in most cases for me) the 'IO.FileNotFoundException' gets caught although the exception thrown seems to be of type 'IO.IOException'.
If I then repeat the call to 'Wait-UntilFileIsAccessible' then usually Powershell immediately catches this 'wrong' exception.
It drove me nuts and I tried different things until I removed the code block that catches the 'IO.DirectoryNotFoundException' (for testing purposes initially) just to find out that then everything works as expected.
Am I doing something wrong here (I mean with the code that catches this 'IO.DirectoryNotFoundException' exception), do I misunderstand something or could this be a Powershell bug?
This is with Powershell 4 and ErrorActionPreference set to 'Continue'.
Also some code obviously is just in there for testing purposes (I wouldn't need ref parameter MSecsPassed in Test-IsFileLocked and output to host normally)
P.S.: I know how to workaround it, but I would like feedback to my question if I am doing something wrong in my code or if this possibly could be a bug in Powershell.
Thanks,
Patrik