What does the special character "!" mean in PowerShell? - powershell

What does the special character ! mean in PowerShell?
Or a site which lists all special characters and meaning.
Example:
$string = blah
!$String
(Returns $false)

PowerShell uses the ! character as an alias for the logical -not operator:
$true
!$true
$false
!$false
True
False
False
True

PowerShell interprets everything that is empty, $Null, or 0 to the Boolean $False. Bool can only have $True or $False.
By casting the value to a Boolean you can see what PowerShell interprets for each value:
[bool]0 # False
[bool]1 # True
[bool]"" # False
[bool]"test" # True
[bool]$null # False
The locical NOT operation turns each Boolean into its opposite:
!$True # Is $False
!$False # Is $True
![bool]0 # True
![bool]1 # False
![bool]"" # True
![bool]"test" # False
![bool]$null # True
You were assigning a string to a variable and then checking whether it is empty or not.
$string = blah
!$String # $String is not $Null or Empty so it is $True
# But the !(NOT) operation turns it to $False
Conditionals and loops in programming languages only work with Boolean values.
So when getting user input you can use this to check whether the user has input text, or not, and react on it:
$UserName = Read-Host -Prompt "Whats your Name Sir?"
If ($UserName) {
Write-Output "Happy Birthday $UserName"
}
Else {
Write-Output "I can't congratulate you as I don't know your name :("
}

The ! (exclamation mark) character in PowerShell is a shortcut to the -not operator ('not equal').
For example:
$a = $null;
if(!$a) {
Write-Host '$a is null'
}
Output.
$a is null

The symbol ! is an alias for -Not, which is a unary (one-argument) operator that casts its argument to a Boolean value and then returns the logical opposite of that value. (Most of the time, spelling out -Not is a better choice for readability.)
Boolean is a data type with only two possible values: true and false. In PowerShell the type is known as Bool and the two values are written $True and $False. The -Not/! operator just flips the value to its opposite: -Not $True is $False and ! $False is $True.
But in PowerShell, as in many other programming languages, you can apply Boolean operators (which also include -And and -Or) to non-Boolean values; those values just get automatically cast to Bool before being operated on, as if you had put a [Bool] in front of them.
Values that become $True when so cast are called "truthy"; values that become $False are called "falsy" (sometimes spelled "falsey"). Different programming languages have different conventions for what goes in each category; for instance, an empty array is falsy in Perl and Python, but truthy in Ruby.
In PowerShell, both the null value $Null and the empty string are falsy. So in your code example, !$String would be true if the string is either null or empty (since it would boolify to false and then get flipped to true by the !). Such an expression would likely show up in, for example, an If statement that sets a variable to a default value if it has not already been set to a different one by earlier code.
Besides the empty string and $Null, other falsy values in PowerShell include the number 0 and the empty array. However, an empty HashTable is truthy. You can see these correspondences by casting the values to Boolean, either with [Bool] or the shortcut !!. (! casts the value to Boolean but then flips it; the second ! flips it back. I recommend using !! at the prompt for quicker typing, but spelling out [Bool] for better self-documentation in production code.) Here are some examples:
PS /> [Bool]$Null
False
PS /> !!''
False
PS /> !!0
False
PS /> !!#()
False
PS /> !!#{}
True

Related

Powershell Understanding "continue"

So in the spirit of this short but sweet tutorial I'm trying to filter out disabled user and only work on the "Enabled" users with this code. (FYI Search-ADAccount needs elevated)
$EXPusers = (Search-ADAccount -AccountExpired -UsersOnly)
foreach($user in $EXPusers){
$UENB = $user.Enabled
$UENB # Sanity Check
if($UENB -eq "False"){
continue
}
# All of this is functioning
# disable user
# Logoff user
# Send email
}
In my lab $EXPusers just resolves to one user that is disabled or Enabled=False. So what happens is no matter what I set $UENB equal to it keeps sending mail. Seems to me that if it's "False" it should skip that iteration and not process the rest of the foreach statement and move to the next user, in this case do nothing.
What am I missing?
The reason why it's failing is because you're comparing a boolean (the Enabled Property of an ADAccount instance is a bool) with a string. It's important to note that, in PowerShell a string that is not empty will always be $true and, since in your comparison the string is in the right hand side (RHS) of the comparison, PowerShell attempts type coercion as the same type of the left hand side (LHS), so the string 'false' is converted to a boolean during the comparison which results being $true.
In about Comparison Operators documentation on the Equality operators section we can read the following:
The equality operator can compare objects of different types. It is important to understand that the value is on the right-hand side of the comparison can be converted to the type of the left-hand side value for comparison.
A simple demo:
[bool] 'false' # => $true
$false -eq 'false' # => $false
'false' -eq $false # => $true
The last comparison results in $true because the boolean $false in the RHS is converted to string and, in PowerShell, [string] $false results in the literal string false.
In conclusion, by simply changing your if condition your code would work properly:
if($false -eq $UENB) {
continue
}
The other alternative would be to use the logical -not operator:
if(-not $UENB) {
continue
}

float variable that should be empty or null is being set to 0

I'm working on a function that accepts [Single]$DurationMS as an optional parameter. This is supposed to be float value. So in my function I have the following code to check if its been provided to the function. if it is provided I want to add the value to an object nested in another object.
if ($DurationMS -ne $null) {
$MyObject.attributes | Add-Member -MemberType NoteProperty -Name 'duration.ms' -Value $DurationMS
}
All looks fine except when I test it I get 0 in the duration and I can't figure out why.
duration.ms
-----------
0
so my condition is evaluating to true but I don't understand why.
[single] is a .NET value type, and instances of such types can never be $null.
[single].IsValueType returning $true tells you that it is a value type.
$null only applies to .NET reference types and tells you that is a reference to no object.
It is therefore pointless to test your [single]-typed $DurationMS parameter variable for being $null:
A [single] instance's default value is 0, so that your $DurationMS -ne $null conditional is effectively 0 -ne $null by default, which is $true.
The robust way to check if an argument was passed to a given (non-mandatory) parameter in a given invocation is to consult the automatic $PSBoundParameters variable, as Santiago Squarzon suggests.
This variable contains a dictionary that has entries for all explicitly passed arguments, keyed by their parameter names (sans prefix -); e.g., if your function is invoked with -DurationMS 1.2, $PSBoundParameters['DurationMS'] returns 1.2, and $PSBoundParameters.ContainsKey('DurationMS') indicates $true
Therefore:
# Was an argument passed to -DurationMS?
if ($PSBoundParameters.ContainsKey('DurationMS')) {
$MyObject.attributes |
Add-Member -MemberType NoteProperty -Name 'duration.ms' -Value $DurationMS
}
The following aspects are incidental:
if ($DurationMs) would only work if you also wanted to consider an explicit argument of 0 to signal "no value was provided", because with a [single]-typed $DurationMs, if ($DurationMs) is the same as if ($DurationMs -ne 0)
PowerShell allows you to use an expression of any type in a Boolean context; with numeric types, 0 maps to $false, and any nonzero value to $true.
While this implicit to-Boolean conversion behavior is generally convenient, it has its pitfalls - see the bottom section of this answer for a summary of the rules.
Given that many PowerShell operators can implicitly operate on arrays (collections) as the LHS - in which case they act as filters, returning the subarray of matching items - it is generally better to place a scalar comparison operand on the LHS (in the case at we know that the non-literal operand is by definition also a scalar - a [single] instance - so that doesn't matter).
Placing the scalar on the LHS avoids false positives / negatives, such as in the following example:
$arr = 0, $null
# !! -> 'null', because (0, $null) -ne $null filters the
# !! array to #(0), and [bool] #() - perhaps surprisingly - is $false
if ($arr -ne $null) { 'not null' } else { 'null' }
# OK, with $null on the LHS
# -> 'not null'
if ($null -ne $arr) { 'not null' } else { 'null' }
However, even on the LHS $null can exhibit unexpected behavior, namely with the -lt, -le, -gt, and -ge operators, as discussed in this answer; e.g.:
$null -lt 0 # !! -> $true - even though [int] $null yields 0
If PowerShell offered a dedicated test for $null, these pitfalls could be avoided; implementing such a test - in the form $var -is $null or $var -isnull - was the subject of GitHub PR #10704; unfortunately, that PR was abandoned by its creator, and no one has picked up the work since, which is why no such test exists as of PowerShell 7.2.2.
As Lee Dailey points out, a property name such as duration.ms can be problematic, given that it contains ., which normally suggests a nested property access, given that an (unquoted) . serves as the member-access operator.

Jenkins (Pipeline) Boolean Parameter in Powershell confusing

I have a parametrized Jenkins Pipeline Script, where I pass a Boolean Parameter "isModuleUpdate".
When I use this parameter in my Pipeline Script I get confusing results.
My Script:
Write-Host ">>> isModuleUpdate as String: $Env:isModuleUpdate"
Write-Host ">>> isModuleUpdate as Variable: " $Env:isModuleUpdate
if ($Env:isModuleUpdate) {
Write-Host ">>> ModuleUpdate is checked!"
}
When I run my Script, the Result is:
>>> isModuleUpdate as String: false
>>> isModuleUpdate as Variable: false
>>> ModuleUpdate is checked!
What is the sexiest way to check this variable corectly?
From about_Environment_Variables:
Environment variables, unlike other types of variables in PowerShell, are always stored as a string and can't be empty.
Your if statement evaluates to true, because your string variable is not empty. In other words, it contains the string false and not a boolean value. Do a proper string comparison instead:
if ($Env:isModuleUpdate -like 'true') {...
I recall having issues with checking booleans in PowerShell as well. Ultimately, -eq $true worked:
if ($Env:isModuleUpdate -eq $true) {

Why do PowerShell comparison operators not enumerate collections of size 1?

When checking variables and collections of variables for nullity, comparison operators seem to enumerate collections of size 2 or more:
> if ( #( $null, $null ) -eq $null ) { $True } else { $False }
True
But they do not for collections of size 1:
> if ( #( $null ) -eq $null ) { $True } else { $False }
False
I'm aware that it's best practice to null-compare using the left-hand side ($null -eq #( $null )), but can someone explain what's happening here? I suspect there's something more subtle happening that impacts other code that I write.
Why are these two results different?
tl;dr
In PowerShell conditionals / implicit Boolean contexts:
Single-element arrays are treated like scalars: that is, their one and only element itself is interpreted as a Boolean.[1]
2+-element arrays are always $true, irrespective of their content.
With an array as the LHS, array-aware operators such as -eq invariably also output an array.
Since your array elements are all $null and you compare to $null, your comparison is an effective no-op - e.g., #( $null ) -eq $null results in #( $null ) - and your conditionals are equivalent to:
[bool] #( $null, $null ) # -> $true - array with 2+ elements is always $True
[bool] #( $null ) # -> $false(!) - treated like: [bool] $null
Perhaps surprisingly, the implicit Boolean logic applies pipeline logic to an array:
That is, a single-element array is (conceptually) unwrapped and its element is interpreted as a Boolean.
Therefore, [bool] #( $null ) is treated the same as [bool] $null, which is $false.
Generally, #( <one-and-only-element> ) (or , <one-and-only-element>) is treated the same as <one-and-only-element> in a Boolean context.
By contrast, if an array has 2 or more elements, it is always $true in a Boolean context, even if all its elements would individually be considered $false.
Workaround for testing whether an arbitrary array is empty:
Base your conditional on the .Count property:
if ( (<array>).Count ) { $true } else { $false }
You could append -gt 0, but that's not strictly necessary, because any nonzero value is implicitly $true.
Applied to your example:
PS> if ( ( #($null) -eq $null ).Count ) { $true } else { $false }
True
Testing an arbitrary value for being a (scalar) $null:
if ($null -eq <value>) { $true } else { $false }
Note how $null must be used as the LHS in order to prevent the array-filtering logic from taking effect, should <value> be an array.
That's also the reason why Visual Studio Code with the PowerShell extension advises "$null should be on the left side of comparisons" if you write something like $var -eq $null.
[1] To-Boolean conversion summary:
Among scalars:
The following are implicitly $false:
''/"" (empty string)
0 (of any numeric type).
$null
Pitfall: Comparing $null to a Boolean explicitly with -eq is always $false, even with $null as the RHS (despite the RHS normally getting coerced to the type of the LHS):
$false -eq $null # !! $false - unlike `$false -eq [bool] $null`
Pitfall: Any non-empty string evaluates to $true
e.g., [bool] 'False' is $true
Note that this differs from explicit string parsing: [bool]::Parse('false') does return$false (and $true for 'true', but recognizes nothing else).
Instances of any other (non-collection) type are implicitly $true, including of type [pscustomobject] and [hashtable] (which PowerShell treats as a single object, not as a collection of entries).
Unfortunately, this includes types that define explicit [bool] .NET conversion operators, meaning that these operators are - mostly - not honored; see this answer.
Among collections such as arrays (more accurately, collection-like types that implement the IList interface - see the source code):
Empty collections are always $false, as is the special "null collection" value indicating the absence of output from a command, [System.Management.Automation.Internal.AutomationNull]::Value.
Pitfall: Single-element collections evaluate to:
If the one and only element is a scalar: its Boolean value
If that element is itself a collection: $true if it has at least 1 element (irrespective of what that element is).
2+-element collections are always $true.
The following items evaluate to $false:
#()
0
$null
$false
''
In your first example:
#($null, $null) -eq $null
This evaluates to $null, $null which is a non-zero collection, so it is $true. You can observe this with the following:
[bool]($null, $null)
In your second example, what you're observing is filtering of an array like the first case, but returning a scalar (instead of an array) since only one item of the array matched the filter:
#($null) -eq $null
This evaluates to #($null) but powershell is evaluating it as a scalar in a boolean context, so it returns $false, observed by:
[bool]#($null)
Footnote: in powershell v2, there was a bug with $null filtering which spawned the left-hand $null comparison. This bug caused if/else blocks to be skipped entirely.

Conditional logic no longer working as expected?

Its been a long day and I think I'm going mad. I wanted to test for a file and generate an email if none existed. Here it is pared down to its most minimal:
> IF('False' -eq (Test-Path D:\Scripts\SFTP\Import\*)){ECHO "SEND EMAIL"}
> SEND EMAIL
__________________________________________________________________________
> IF((Test-Path D:\Scripts\SFTP\Import\*) -eq 'False'){ECHO "SEND EMAIL"}
>
Why doesn't the second command work?
I've tried running the Test-Path outside of the 'IF' statement into a variable and then testing against that, again it doesn't work.
If I simply run the 'Test-Path' command I get a boolean 'False' as expected. I've used conditional logic in this way before and its worked.
What am I doing wrong?
The reason is this. In the first one you have a string as the first operand of the comparison. This forces PS to coerce the second operand to a string if possible. In this case that means calling the .ToString() method of the boolean which would return the 'False' string (if the boolean is actually false of course). In the second case though, you are presenting a boolean as the first operand, so the string is being coerced to a boolean. Obviously it is not working. To avoid these issues, use the builtin $false (or $true) variable. Personally I would suggest just negating the Test-Path. Here are a couple of ways that should work:
if( -NOT (Test-Path D:\Scripts\SFTP\Import\*)){
if( (Test-Path D:\Scripts\SFTP\Import\*) -eq $false){
For the coercing rules of powershell
'False' -eq (Test-Path D:\Scripts\SFTP\Import\*)
the second value of comparision is evaluated as [string]
here
(Test-Path D:\Scripts\SFTP\Import\*) -eq 'False'
the second value of comparison can't be evaluated as [Bool] then it fails.
For bool comparin is optima use the automatic variable $false and $true