Replacing multiple if statements - powershell

I am using PS version 5.0 and I have quite a few if statements which might grow over time.
if ($hostname -like "**12*") {
Write-Output "DC1"
} elseif ($Hostname -like "**23*") {
Write-Output "DC2"
} elseif ($Hostname -like "**34*") {
Write-Output "DC3"
} elseif ($Hostname -like "**45*") {
Write-Output "DC4"
}
Can you suggest some better way of writing the same code?

You could use a switch statement. Here is an example using the -Regex flag since it looks like you are doing just a simple match and then could cut out the * wildcards.
$hostname = 'asdf12asdf'
switch -Regex ($hostname) {
"12" {Write-Output "DC1"}
"23" {Write-Output "DC2"}
"34" {Write-Output "DC3"}
"45" {Write-Output "DC4"}
Default {Write-Error "No Match Found"}
}
If you didn't want multiple matches add a ; Break after each case. For example if you had a host name such as asdf12asdf34 a statement "12" {Write-Output "DC1"; Break} would prevent the output both of 12 and 34

Related

Powershell: Switch and If on $null valued variable behaving diffrently

I do not know why my switch-statemant won't evaluate the default branch:
function Test-Function {
[CmdletBinding()]param()
$stage = Get-ItemProperty -Path $REGISTRY_KEY -Name $FRUIT_VALUE -ErrorAction Ignore
if ($null -eq $stage) {
"`$stage is `$null."
#$stage = $null
}
switch ($stage) {
"apple" {"We found an apple"; break}
"pear" {"We found a pear"; break}
"orange" {"We found an orange"; break}
"peach" {"We found a peach"; break}
"banana" {"We found a banana"; break}
default {"Something else happened"; break}
}
}
Get-ItemProperty returns $null because the registry value is missing. As one would expect I get $stage is $null. as an output. But strangely the output won't display Something else happened. If I do set $stage = $null (line 7) although it's already $null I do get the desired output:
$stage is $null.
Something else happened
What am I missing?
Further minification
function Test-Function {
[CmdletBinding()]param()
$txt = (Get-ItemProperty -Path "HKLM:\SOFTWARE\does\not" -Name "exist" -ErrorAction Ignore)
if ($null -eq $txt) {
"if: `$null"
# $txt = $null
}
switch ($txt) {
"test" {"switch: TEST"}
$null {"switch: `$null"}
default {"switch: DEFAULT"}
}
}
Neither switch: $null nor switch: DEFAULT appears as output but if: $null does. How come?
Also, be careful with empty returns from cmdlets. Cmdlets or pipelines that have no output are treated as an empty array that does not match anything, including the default case.
$file = Get-ChildItem NonExistantFile*
switch ( $file )
{
$null { '$file is $null' }
default { "`$file is type $($file.GetType().Name)" }
}
# No matches
Taken from here

Switch handling with global arrays

Say I have 2 global arrays:
$Global:Values = #(1..100)
$Global:Test = #(75, 50, 25, 101)
Then I create a Switch by piping $Global:Test into a ForEach loop by using different conditions which I tried troubleshooting to get any kind of response:
$Global:Test | ForEach-Object ($_) {
$_
Switch ($_) {
($_ -contains $Global:Values) {Write-Host "Excellent"}
($_ -in $Global:Values) {Write-Host "Satisfactory"}
($_ -eq "25") {Write-Host "Unsatisfactory"}
Default {Write-Host "Invalid Grade"}
}
}
Output:
75
Invalid Grade
50
Invalid Grade
25
Invalid Grade
101
Invalid Grade
All of these switch statements do not work except the default. I don't know what I'm missing, but I'm sure it's a simple mistake I'm overlooking. Can anyone help me spot this mistake?
The foreach() statement is not the same as the ForEach-Object cmdlet, and you seem to be combining the syntax of both.
Additionally, you're using the switch statement incorrectly.
The part you're trying to match against needs to be a value:
switch ($something)
{
5 { "5 thing" }
(10 + 10) { "20 thing" }
}
or a scriptblock expression that returns a truthy or falsey value:
switch ($something)
{
{ $_ -gt 5 } { "do > 5 thing" }
{ Test-SomeCondition -Value $_ } { "do other thing" }
}
So you want the scriptblock form, but you're using parentheses.
So here's the ForEach-Object form:
$Global:Test | ForEach-Object {
Switch ($_) {
{$_ -contains $Global:Values} {Write-Host "Excellent"}
{$_ -in $Global:Values} {Write-Host "Satisfactory"}
"25" {Write-Host "Unsatisfactory"}
Default {Write-Host "Invalid Grade"}
}
}
or the foreach form:
foreach($value in $Global:Test) {
Switch ($value) {
{$_ -contains $Global:Values} {Write-Host "Excellent"}
{$_ -in $Global:Values} {Write-Host "Satisfactory"}
"25" {Write-Host "Unsatisfactory"}
Default {Write-Host "Invalid Grade"}
}
}
Also of note, the switch statement can actually work directly with arrays, so you don't have to iterate first. Basic example:
switch (1..10)
{
{ $_ -gt 5 } { "Second Half"}
default: { "First Half" }
}
So with yours, maybe:
Switch ($Global:Test) {
{$_ -contains $Global:Values} {Write-Host "Excellent"}
{$_ -in $Global:Values} {Write-Host "Satisfactory"}
"25" {Write-Host "Unsatisfactory"}
Default {Write-Host "Invalid Grade"}
}
Small addendum: stop using Global variables!

Pipe a string through multiple if-statements with an else for each

Essentially I have an If statement that has two conditions using -and, which a string is then run through. However, what I really want is the string to be run through the first condition, then if that is true, it checks for the second, and if that is true it does one thing, and if it is false do something else.
I currently have:
if(($_ -match "/cls") -and ($env:UserName -eq $Name)){cls}
I know I can do what I want with:
if(($_ -match "/cls") -and ($env:UserName -eq $Name)){cls}
elseif(($_ -match "/cls") -and ($env:UserName -ne $Name)){OTHER COMMAND}
But I would like to know if there was a more simple way.
(The result is already being piped in from somewhere else, hence the $_)
Move the first match to the outer scope.
if($_ -match "/cls")
{
if ($env:UserName -eq $Name))
{
cls
}
else
{
other command
}
}
I would write two if statements to improve readabilty. This way, you also need the pipeline value only once::
if($_ -match "/cls")
{
if ($env:UserName -eq $Name)
{
cls
}
else
{
#OTHER COMMAND
}
}

Break out of inner loop only in nested loop

I'm trying to implement a break so I don't have to continue to loop when I got the result xxxxx times.
$baseFileCsvContents | ForEach-Object {
# Do stuff
$fileToBeMergedCsvContents | ForEach-Object {
If ($_.SamAccountName -eq $baseSameAccountName) {
# Do something
break
}
# Stop doing stuff in this For Loop
}
# Continue doing stuff in this For Loop
}
The problem is, that break is exiting both ForEach-Object loops, and I just want it to exit the inner loop. I have tried reading and setting flags like :outer, however all I get is syntax errors.
Anyone know how to do this?
You won't be able to use a named loop with ForEach-Object, but can do it using the ForEach keyword instead like so:
$OuterLoop = 1..10
$InnerLoop = 25..50
$OuterLoop | ForEach-Object {
Write-Verbose "[OuterLoop] $($_)" -Verbose
:inner
ForEach ($Item in $InnerLoop) {
Write-Verbose "[InnerLoop] $($Item)" -Verbose
If ($Item -eq 30) {
Write-Warning 'BREAKING INNER LOOP!'
BREAK inner
}
}
}
Now whenever it gets to 30 on each innerloop, it will break out to the outer loop and continue on.
Using the return keyword works, as "ForEach-Object" takes a scriptblock as it's parameter and than invokes that scriptblock for every element in the pipe.
1..10 | ForEach-Object {
$a = $_
1..2 | ForEach-Object {
if($a -eq 5 -and $_ -eq 1) {return}
"$a $_"
}
"---"
}
Will skip "5 1"

Powershell Issues with .contains function for strings

OK, i am trying to get this script to work and not continuously hit the same computers over and over and am having trouble getting it working. I don't think this is the best way to do it and if you have any suggestions please and thank you. Anyways there seems to be an issue with line 6 "IF (!$Succssful.Contains($Computer)) {" it doesn't shoot an error at me but instead ends the script per-maturealy. I have tried removing the "!" but no luck as I expected.
$Computers = "TrinityTechCorp"
$HotFixes = Get-Content HotFixes.csv
While ($Successful -AND $Successful.count -ne $Computers.count) {
ForEach ($Computer in $Computers) {
IF (!$Succssful.Contains($Computer)) {
If (Test-Connection $Computer) {
$Comparison = get-hotfix -ComputerName $Computer | Select -expand HotFixID
ForEach ($HotFix in $HotFixes) {
IF ($Comparison -NotLike "*$HotFix*") {
Write-Host "$Computer missing $HotFix"
}
$Successful += $Computer
}
}
}
}
}
This line
While ($Successful -AND $Successful.count -ne $Computers.count) {
can never evaluate to $true. The .count-property of a non-existing variable ($Successful) is $null, and the same goes for a string (because you specify only 1 computer, it's not an array).
To force Powershell to return the "true" number of items, use #($Successful).count and #($Computers).count instead. This will give you 1, even if $Computers wasn't defined as an array. So, with variables substituted, your line is interpreted as
While ($null -AND $null.count -ne $null) {
In Powershell 3, as you can read here, this is no longer the case.