How to handle some function with if statement using Powershell? - function

I want to handle my function with if statement. I tried this code, but it always return me the value of $End_F which is "BB" even my file contains of "#AB#CD" . Anyone can help, please.
The file that I look for "#AB#CD" is like this.
Config
; Date="2019/06/12" Time="10:25:02" UTC="0"
;
Number
123456#AB#CD
$Get_SKU = Get-Content '.\Number.txt' | Where-Object {$_.Contains("#AB#CD")}
$Get_SKU
if($Get_SKU)
{$ML = "1"
AUTO_SELECT
}
else
{
END_Proc
}
Function AUTO_SELECT
{
$AT = "AA"
$AT
}
Function END_Proc
{
$End_F = "BB"
$End_F
}
$FE_UB = "4"
if($ML = "1" -and $FE_UB -eq $true)
{
G_BEGIN
}
if($ML = "1" -and $FE_UB -eq $false)
{
G_END
}
else
{
END_Proc
}
Function G_BEGIN
{
$begin = "Ready"
$begin
}
Function G_END
{
$ending = "Stop"
$ending
}

Some things need to be corrected to make your code work as expected.
Function AUTO_SELECT
{
$AT = "AA"
$AT
}
Function END_Proc
{
$End_F = "BB"
$End_F
}
Function G_BEGIN
{
$begin = "Ready"
$begin
}
Function G_END
{
$ending = "Stop"
$ending
}
$Get_SKU = Get-Content '.\Number.txt' | Where-Object {$_.Contains("#AB#CD")}
$Get_SKU
if($Get_SKU)
{
$ML = "1"
AUTO_SELECT
}
else
{
END_Proc
}
$FE_UB = "4"
if($ML -eq "1" -and $FE_UB)
{
G_BEGIN
}
if($ML -eq "1" -and !$FE_UB)
{
G_END
}
else
{
END_Proc
}
Explanation of Changes:
$Get_SKU will store either $null or a string depending on whether the Where-Object condition finds a match. As a result, I swapped out if ($Get_SKU -eq $true) in favor of if ($Get_SKU). This change will result in a $true evaluation if $Get_SKU is not $null.
I moved the functions to the top of the script because PowerShell executes the code starting from top to bottom. It is not compiled first. So you can't make a function call BEFORE the function has been read into memory and defined.
if ($ML = "1" -and $FE_UB -eq $true) has been updated to if ($ML -eq "1" -and $FE_UB) because variable assignment variable = value should not happen in an if statement condition. If you are comparing values, the proper operator here is -eq. Regarding $FE_UB, the same explanation applies as in the $Get_SKU changes.
$FE_UB -eq $false was changed to !$FE_UB. The removal of the -eq $false operator is based on the explanation given for $Get_SKU. The ! character is used to effectively -not the result. This will turn the value into a boolean value and then output the opposite boolean response. For example, !"string data" will output $False. !$null will output $True. I hope this part is clear.
Further Insight:
$True and $False evaluations
You can make just about anything return a boolean value. Three such ways include using casting, the -as operator, and !. There are many other ways and hacks to do this.
Casting:
$get_sku = "data"
[boolean]$get_sku
True
-as Operator:
$get_sku = $null
$get_sku -as [boolean]
False
Fun with !:
$get_sku = 4
!$get_sku
False
!!$get_sku
True

Related

PowerShell function return boolean doesn't work

Good day,
in our Active Directory we have two groups for user sync for a third party system.
First group contains all users for that system. Second group contains all members from first group that are disabled or expired. This is necessary for licensing.
Now I want to automate that procedure with a PowerShell script.
$d3_UserGrp = Get-ADGroup -Identity "d3_users"
$d3_DisabledUserGrp = Get-ADGroup -Identity "d3_Users disabled v2"
Function IsUserActive
{
param(
$AD_User
)
#$return = $false
$AD_User.SamAccountName
$AD_User.AccountExpirationDate
$now = get-date
if ( $AD_User.AccountExpirationDate -ne $null ) {
#"Account has expiration date"
if ( $AD_User.AccountExpirationDate -lt $now ) {
"Account expired"
} else {
#Write-Host "account not expired"
}
} elseif ( $AD_User.Enabled -ne $true ) {
"Account not active"
} else {
#"everything fine with that account"
#return $true
}
#write-host "return is $return"
#return $false
}
$grp = Get-ADGroupMember $d3_UserGrp
foreach ( $username in $grp )
{
$ad_user = Get-ADUser -Identity $username -Properties AccountExpirationDate, DisplayName
if ( IsUserActive -AD_User $ad_user )
{
write-host "add $ad_user.SamAccountName"
#Add-ADGroupMember -Identity $d3_DisabledUserGrp -Members $ad_user -Confirm:$false
}
else
{
write-host "remove $ad_user.SamAccountName"
#Remove-ADGroupMember -Identity $d3_DisabledUserGrp -Members $ad_user -Confirm:$false
}
""
}
This won't work. The script always add's the ad users in the second group.
Any idea why?
Best regards
Your function outputs multiple values. For example, it's always returning:
$AD_User.SamAccountName
$AD_User.AccountExpirationDate
That means IsUserActive -AD_User $ad_user is always going to evaluate to true.
Consider:
$test = #(1, $null, $false)
if ($test) { 'It is true' } else { 'It is false' }
Even when the multiple values should all be false individually, it will not be false:
$test = #($null, $false)
if ($test) { 'It is true' } else { 'It is false' }
You need to make sure that the output of your function is only the return value. You can do this by making sure that you use Write-Host when you want to write to the screen and not return output.
Try:
Function IsUserActive
{
param(
$AD_User
)
#$return = $false
Write-Host $AD_User.SamAccountName
Write-Host $AD_User.AccountExpirationDate
$now = get-date
if ( $null -eq $AD_User.AccountExpirationDate ) {
Write-Host "Account has expiration date"
if ( $AD_User.AccountExpirationDate -lt $now ) {
Write-Host "Account expired"
$false
} else {
Write-Host "account not expired"
$true
}
} elseif ( $AD_User.Enabled -ne $true ) {
Write-Host "Account not active"
$false
} else {
Write-Host "everything fine with that account"
$true
}
}
Thanks a lot, that solved my problem.
I wonder if this works in different way as in other script languages. There I have to give an explicit return value.
Never the less: thanks again!

Powershell - Retain the text of all Enum properties with ConvertTo-Json

For "Get-Msoldomain" powershell command-let I get the below output (lets call it Output#1) where Name, Status and Authentication are the property names and below are their respective values.
Name Status Authentication
myemail.onmicrosoft.com Verified Managed
When I use the command with "ConvertTo-Json" like below
GetMsolDomain |ConvertTo-Json
I get the below output (lets call it Output#2) in Json Format.
{
"ExtensionData": {
},
"Authentication": 0,
"Capabilities": 5,
"IsDefault": true,
"IsInitial": true,
"Name": "myemail.onmicrosoft.com",
"RootDomain": null,
"Status": 1,
"VerificationMethod": 1
}
However, the problem is, that if you notice the Status property in both the outputs, it's different. Same happens for VerificationMethod property. Without using the ConvertTo-JSon Powershell gives the Text, and with using ConvertTo-Json it gives the integer.
When I give the below command
get-msoldomain |Select-object #{Name='Status';Expression={"$($_.Status)"}}|ConvertTo-json
I get the output as
{
"Status": "Verified"
}
However, I want something so that I don't have to specify any specific property name for it to be converted , the way I am specifying above as
Select-object #{Name='Status';Expression={"$($_.Status)"}}
This line is transforming only the Status Property and not the VerificationMethod property because that is what I am providing as input .
Question: Is there something generic that I can give to the "ConvertTo-Json" commandlet, so that It returns ALL the Enum properties as Texts and not Integers, without explicitly naming them, so that I get something like below as the output:
{
"ExtensionData": {
},
"Authentication": 0,
"Capabilities": 5,
"IsDefault": true,
"IsInitial": true,
"Name": "myemail.onmicrosoft.com",
"RootDomain": null,
"Status": "Verified",
"VerificationMethod": "DnsRecord"
}
Well, if you don't mind to take a little trip :) you can convert it to CSV which will force the string output, then re-convert it back from CSV to PS Object, then finally back to Json.
Like this:
Get-MsolDomain | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json
If you need to keep the original Types instead of converting it all to string see mklement0 helpful answer...
PowerShell Core (PowerShell versions 6 and above) offers a simple solution via ConvertTo-Json's -EnumsAsStrings switch.
GetMsolDomain | ConvertTo-Json -EnumsAsStrings # PS *Core* (v6+) only
Unfortunately, this switch isn't supported in Windows PowerShell.
Avshalom's answer provides a quick workaround that comes with a big caveat, however: All property values are invariably converted to strings in the process, which is generally undesirable (e.g., the Authentication property's numeric value of 0 would turn into string '0').
Here's a more generic workaround based on a filter function that recursively introspects the input objects and outputs ordered hashtables that reflect the input properties with enumeration values converted to strings and all other values passed through, which you can then pass to ConvertTo-Json:
Filter ConvertTo-EnumsAsStrings ([int] $Depth = 2, [int] $CurrDepth = 0) {
if ($_ -is [enum]) { # enum value -> convert to symbolic name as string
$_.ToString()
} elseif ($null -eq $_ -or $_.GetType().IsPrimitive -or $_ -is [string] -or $_ -is [decimal] -or $_ -is [datetime] -or $_ -is [datetimeoffset]) {
$_
} elseif ($_ -is [Collections.IEnumerable] -and $_ -isnot [Collections.IDictionary]) { # enumerable (other than a dictionary)
, ($_ | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1))
} else { # non-primitive type or dictionary (hashtable) -> recurse on properties / entries
if ($CurrDepth -gt $Depth) { # depth exceeded -> return .ToString() representation
Write-Warning "Recursion depth $Depth exceeded - reverting to .ToString() representations."
"$_"
} else {
$oht = [ordered] #{}
foreach ($prop in $(if ($_ -is [Collections.IDictionary]) { $_.GetEnumerator() } else { $_.psobject.properties })) {
if ($prop.Value -is [Collections.IEnumerable] -and $prop.Value -isnot [Collections.IDictionary] -and $prop.Value -isnot [string]) {
$oht[$prop.Name] = #($prop.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1))
} else {
$oht[$prop.Name] = $prop.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1)
}
}
$oht
}
}
}
Caveat: As with ConvertTo-Json, the recursion depth (-Depth) is limited to 2 by default, to prevent infinite recursion / excessively large output (as you would get with types such as [System.IO.FileInfo] via Get-ChildItem, for instance). Similarly, values that exceed the implied or specified depth are represented by their .ToString() value. Use -Depth explicitly to control the recursion depth.
Example call:
PS> [pscustomobject] #{ p1 = [platformId]::Unix; p2 = 'hi'; p3 = 1; p4 = $true } |
ConvertTo-EnumsAsStrings -Depth 2 |
ConvertTo-Json
{
"p1": "Unix", # Enum value [platformId]::Unix represented as string.
"p2": "hi", # Other types of values were left as-is.
"p3": 1,
"p4": true
}
Note: -Depth 2 isn't necessary here, given that 2 is the default value (and given that the input has depth 0), but it is shown here as a reminder that you may want to control it explicitly.
If you want to implement custom representations for additional types, such as [datetime], [datetimoffset] (using the ISO 8601-compatible .NET round-trip date-time string format, o, as PowerShell (Core) v6+ automatically does), as well as [timespan], [version], [guid] and [ipaddress], see Brett's helpful variation of this answer.
I needed to serialize pwsh objects to JSON, and was not able to use the -EnumsAsStrings parameter of ConvertTo-Json, as my code is running on psv5. As I encountered infinite loops while using #mklement0's code Editor's note: since fixed., I rewrote it. My revised code also deals with the serialization of some other types such as dates, serializing them into the ISO 8601 format, which is generally the accepted way to represent dates in JSON. Feel free to use this, and let me know if you encounter any issues.
Filter ConvertTo-EnumsAsStrings ([int] $Depth = 10, [int] $CurrDepth = 0) {
if ($CurrDepth -gt $Depth) {
Write-Error "Recursion exceeded depth limit of $Depth"
return $null
}
Switch ($_) {
{ $_ -is [enum] -or $_ -is [version] -or $_ -is [IPAddress] -or $_ -is [Guid] } {
$_.ToString()
}
{ $_ -is [datetimeoffset] } {
$_.UtcDateTime.ToString('o')
}
{ $_ -is [datetime] } {
$_.ToUniversalTime().ToString('o')
}
{ $_ -is [timespan] } {
$_.TotalSeconds
}
{ $null -eq $_ -or $_.GetType().IsPrimitive -or $_ -is [string] -or $_ -is [decimal] } {
$_
}
{ $_ -is [hashtable] } {
$ht = [ordered]#{}
$_.GetEnumerator() | ForEach-Object {
$ht[$_.Key] = ($_.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth + 1))
}
if ($ht.Keys.Count) {
$ht
}
}
{ $_ -is [pscustomobject] } {
$ht = [ordered]#{}
$_.PSObject.Properties | ForEach-Object {
if ($_.MemberType -eq 'NoteProperty') {
Switch ($_) {
{ $_.Value -is [array] -and $_.Value.Count -eq 0 } {
$ht[$_.Name] = #()
}
{ $_.Value -is [hashtable] -and $_.Value.Keys.Count -eq 0 } {
$ht[$_.Name] = #{}
}
Default {
$ht[$_.Name] = ($_.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth + 1))
}
}
}
}
if ($ht.Keys.Count) {
$ht
}
}
Default {
Write-Error "Type not supported: $($_.GetType().ToString())"
}
}
}

Exit a PowerShell function but continue the script

This might seem like a very very stupid question, but I can't really figure it out. I'm trying to have the function stop when it finds its first hit (match) and then continue with the rest of the script.
Code:
Function Get-Foo {
[CmdLetBinding()]
Param ()
1..6 | ForEach-Object {
Write-Verbose $_
if ($_ -eq 3) {
Write-Output 'We found it'
# break : Stops the execution of the function but doesn't execute the rest of the script
# exit : Same as break
# continue : Same as break
# return : Executes the complete loop and the rest of the script
}
elseif ($_ -eq 5) {
Write-Output 'We found it'
}
}
}
Get-Foo -Verbose
Write-Output 'The script continues here'
Desired result:
VERBOSE: 1
VERBOSE: 2
VERBOSE: 3
We found it
The script continues here
I've tried using break, exit, continue and return but none of these get me the desired result. Thank you for your help.
As was mentioned, Foreach-object is a function of its own. Use regular foreach
Function Get-Foo {
[CmdLetBinding()]
Param ()
$a = 1..6
foreach($b in $a)
{
Write-Verbose $b
if ($b -eq 3) {
Write-Output 'We found it'
break
}
elseif ($b -eq 5) {
Write-Output 'We found it'
}
}
}
Get-Foo -Verbose
Write-Output 'The script continues here'
The scriptblock you are passing to ForEach-Object is a function in its own right. A return in that script block just returns from the current iteration of the scriptblock.
You'll need a flag to tell future iterations to return immediately. Something like:
$done = $false;
1..6 | ForEach-Object {
if ($done) { return; }
if (condition) {
# We're done!
$done = $true;
}
}
Rather than this, you may be better using a Where-Object to filter the pipeline objects to only those that you need to process.

Conditionally change boolean variable in an imported CSV

I am working on a script and I want to compare an array to another array, and change a boolean value (from $false to $true) based on a result. This works fine on strings using the Replace method, but that doesn't exist for boolean values. Can anyone tell me how to do this?
$bv is an array of objects, as follows.
ServerName,Domain,Environment,Tier0
ServerA,usa,dev,$false
ServerB,usa,sit,$false
I am trying to compare that list to another list of Tier0 computers ($t0List) that looks like this.
ServerB
ServerC
ServerD
So if there is a match between the ServerName in column 1 of $bv and an entry in $t0List, then I want to change the Tier0 column in $bv to $true.
foreach ($b in $bv) {
if ($t0List -contains $b.ServerName) {
$b.Tier0.Replace($b.Tier0,$true)
}
}
The error I get with the above code is...
Method invocation failed because [System.Boolean] does not contain a method named 'Replace'.
There's no need to use something like replace, just assign the value with =:
foreach ($b in $bv) {
if ($t0List -contains $b.ServerName) {
$b.Tier0 = $true
}
}
Want to simplify it even more? Just assign it the result of your -contains:
foreach ($b in $bv) {
$b.Tier0 = $t0List -contains $b.ServerName
}
Maybe this helps you:
You can flip between $true and $false just by telling it -not to be
-not $true brings back $false
-not $false
brings back
$true
So
foreach ($b in $bv) {
if ($t0List -contains $b.ServerName) {
$b.Tier0 = -not ($b.Tier0)
}
}
or
foreach ($b in $bv) {
if ($t0List -contains $b.ServerName) {
$b.Tier0 = $true
}
}
should work

Empty parameter is not Null in function

Given this basic function:
Function TestFunction {
Param ( [int]$Par1, [string]$Par2, [string]$Par3 )
If ($Par1 -ne $Null) { Write-Output "Par1 = $Par1" }
If ($Par2 -ne $Null -or $Par2 -ne '') { Write-Output "Par2 = $Par2" }
If ($Par3 -ne $Null) { Write-Output "Par3 = $Par3" }
}
TestFunction -Par1 1 -Par3 'par3'
...the output is:
Par1 = 1
Par2 =
Par3 = par3
Even though I didn't pass anything into the $Par2 variable, it still isn't Null or empty. What happened, and how can I rewrite the statement so that the second If-statement evaluates as False and the script-block does not get executed?
(I added the -or $Par2 -ne '' just to test, it behaves the same with and without it.)
You have a logic error in your program: $Par2 will always be not equal to $null or not equal to ''.
To fix the logic, you should use -and instead of -or here:
If ($Par2 -ne $Null -and $Par2 -ne '') { Write-Output "Par2 = $Par2" }
However, because you casted the $Par2 argument to a string in the function's argument list:
Param ( [int]$Par1, [string]$Par2, [string]$Par3 )
^^^^^^^^
the check for $Par2 -ne $Null is unnecessary since $Par2 will always be of type string (if you do not give it a value, it will be assigned to ''). So, you should actually write:
If ($Par2 -ne '') { Write-Output "Par2 = $Par2" }
Or, because '' evaluates to false, you might just do:
If ($Par2) { Write-Output "Par2 = $Par2" }
You can check that (check if $variablename has $null as value):
if (!$variablename) { Write-Host "variable is null" }
And if you wanna check if $variablename has any value except $null:
if ($variablename) { Write-Host "variable is NOT null" }