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

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

Related

Powershell - Exchange JSON output without needing to write to a file

EDIT: Added Setupconfigfiles.ps1
I'm a bit new to detailed scripting so please bear with me.
I have two Powershell scripts
Setupconfigfiles.ps1 generates JSON output to be fed to an API.
Script2 uses that JSON data to execute API commands.
Script 2 can call setupconfigfiles.ps1 as indicated below and use the output data.
.\SetupConfigFiles.ps1 -type $Type -outfile .\Templist.json
$servers = Get-Content -Raw -Path .\templist.json | ConvertFrom-Json
setupconfigfiles.ps1:
param (
# If this parameter is set, format the output as csv.
# If this parameter is not set, just return the output so that the calling program can use the info
[string]$outfile,
# this parameter can be 'production', 'development' or 'all'
[string]$type
)
enum MachineTypes {
production = 1
development = 2
all = 3
}
$Servers = Get-ADObject -Filter 'ObjectClass -eq "computer"' -SearchBase 'Obfuscated DSN' | Select-Object Name
$output = #()
$count = 0
# Set this to [MachineTypes]::production or [MachineTypes]::development or [MachineTypes]::all
if ($type -eq "all") {
$server_types = [MachineTypes]::all
}
ElseIf ($type -eq "production") {
$server_types = [MachineTypes]::production
}
else {
$server_types = [MachineTypes]::development
}
ForEach ($Server in $Servers)
{
$count = $count + 1
$this_server = #{}
$this_server.hostname = $Server.Name
$this_server.id = $count
$this_server."site code" = $this_server.hostname.substring(1,3)
$this_server."location code" = $this_server.hostname.substring(4,2)
if ($this_server.hostname.substring(7,1) -eq "P") {
$this_server.environment = "Production"
}
ElseIf ($this_server.hostname.substring(7,1) -eq "D") {
$this_server.environment = "Development"
}
Else {
$this_server.environment = "Unknown"
}
if (($server_types -eq [MachineTypes]::production ) -and ($this_server.environment -eq "Production")) {
$output += $this_server
}
ElseIf (($server_types -eq [MachineTypes]::development ) -and ($this_server.environment -eq "Development")) {
$output += $this_server
}
Else {
if ($server_types -eq [MachineTypes]::all ) {
$output += $this_server
}
}
}
if ($outfile -eq "")
{
ConvertTo-Json $output
}
else {
ConvertTo-Json $output | Out-File $outfile
}
How can I do it without needing to write to the Templist.json file?
I've called this many different ways. The one I thought would work is .\SetupConfigFiles.ps1 $servers
Y'all are great. #Zett42 pointed me in a direction and #Mathias rounded it out.
The solution was to change:
"ConvertTo-Json $output" to "Write-Output $output"
Then it's handled in the calling script.
thanks!

How to compare an item property to a string value

I am relatively new to PowerShell and cannot understand why my original attempts failed. I am attempting to validate the bit version of MS Office and perform actions off that. For whatever reason the strings were not comparing properly until I found a solution in the actual question here. Help understanding the difference between the two examples below would be much appreciated.
First attempt:
$getMSBitVersion= Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" | Select-Object -Property Platform
if( $getMSBitVersion -eq "x64" ){
Write-Host "true"
} else {
Write-Host "false"
}
Working solution:
$getMSBitVersion= (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name Platform).Platform
if( $getMSBitVersion -eq "x64" ){
Write-Host "true"
} else {
Write-Host "false"
}
My assumption is the first is outputting an object instead of string and thus the comparison cannot be done. If this is the case, is the working solution the only way/best way to do this?
Thank you Mathias and Abraham.
From what I gather, the following are confirmed methods on how to make the desired comparison.
1: This will scope into the object property of "Platform" by using dot notation and return the string value instead of the whole object.
$getMSBitVersion= (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name Platform).Platform
if( $getMSBitVersion -eq "x64" ){
Write-Host "true"
} else {
Write-Host "false"
}
2: This will take all the properties of the Path and pass through to Select-Object. Select-Object will take and expand the "Property" object and return it as a string.
$getMSBitVersion= Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" | Select-Object -ExpandProperty Platform
if( $getMSBitVersion -eq "x64" ){
Write-Host "true"
} else {
Write-Host "false"
}
I was unable to get this solution to function correctly, but should, in theory, work.
3: This, in theory, should work, but the two objects are recognized differently and do not compare as intended.
$getMSBitVersion= Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" | Select-Object -Property Platform
$test= #{Platform="x64"}
if( Compare-Object $getMSBitVersion $test ){
Write-Host "true"
} else {
Write-Host "false"
}

Replacing multiple if statements

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

PowerShell - continue if string is not contained in file (detect when a cmdlet returns nothing)

I am searching for a string ($Loan_Id) in a file ($File_LoanData). I am able to find it, but should the string not be found, I need the script to continue. That does not seem to be working. Here is the problem line --
if ($Note_Line -eq $false) {Continue}
$Fie_LCExtract = "c:\temp\Fie_LCExtract.txt"
$File_LoanData = "c:\temp\File_LoanData.txt"
$File_LoanDataToday = "c:\temp\File_LoanDataToday.txt"
$r = [IO.File]::OpenText($Fie_LCExtract)
while ($r.Peek() -ge 0)
{
$Note = $r.ReadLine()
$Loan_Id = ($Note -split ';')[0].trim()
$Loan_Id = $Loan_Id -as [int]
if (($Loan_Id -is [int]) -eq $false) {Continue}
$Note_Line = Select-String -Path $File_LoanData -Pattern $Loan_Id
if ($Note_Line -eq $false) {Continue}
$Note_Line = ($Note_Line -split ':')[3].trim()
$Note_Line >> $File_LoanDataToday
}
Don't compare to $False, use the return value directly as if it were a Boolean:
if (-not $Note_Line) { Continue }
Alternatively, compare to $null:
if ($null -eq $Note_Line) { Continue }
Or use the .Count property:
if ($Note_Line.Count -eq 0) { Continue }
Select-String returns a "null collection" ("nothing") when it finds no matches and this special value ([System.Management.Automation.Internal.AutomationNull]::Value) is "falsy" in a Boolean context; also, it is considered equal to $null and its .Count property is always 0.
However, despite a null collection implicitly being considered $False, comparing it explicitly to $False does not yield $True:
The null collection is treated like $null in an expression context, and the only value that $null equals (-eq) is $null itself[1].
$null -eq $False # !! $False
$null -eq $null # $True
Using a null collection / $null implicitly as a Boolean is equivalent to casting it to [bool], so we can see that it is "falsy":
[bool] $null # $False
[1] This applies to using $null as the LHS of -eq. As the RHS,
if the LHS is array-valued, it act as a filter and returns the sub-array of elements containing $null.
For instance, $null, 1, $null -eq $null returns a 2-element array of $null values.

How can I get out of the loop once and for all?

I have this file:
function t {
"abcd" -split "" |%{ if ($_ -eq "b") { return; } write-host $_; }
}
$o = #{}
$o |add-member -name t -membertype scriptmethod -value {
"abcd" -split "" |%{ if ($_ -eq "b") { return; } write-host $_; }
}
write-host '-function-'
t;
write-host '-method-'
$o.t();
and if I run it I'll get:
-function-
a
c
d
-method-
a
c
d
as expected. but if what I wanted was 'a', I could replace the return with break. if I do so in the function what I get is:
-function-
a
so the method never even gets called. if I replace it in the method, it won't compile (and complains about calling it). what is going on here?
and what's the proper way to exit the foreach-object loop once and for all?
The simplest solution is to rewrite it as a foreach statement, instead of using the ForEach-Object command, which is particularly hard to stop:
$o | add-member -name t -membertype scriptmethod -value {
foreach($e in "abcd" -split "") {
if ($_ -eq "b") { break }
write-host $_;
}
}
As a side note, the foreach(){} keyword/construct runs a lot faster than piping through ForEach-Object.
For the sake of completeness ...
To stop a pipeline, throw PipelineStoppedException
"abcd" -split "" | % {
if($_ -eq "b") {
throw [System.Management.Automation.PipelineStoppedException]::new()
}
$_
}
You don't use break to stop the ForEach, you use return since it's executed as a lambda on each member of the object anyway.