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
}
Related
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.
While comparing a string to an array in PowerShell today I mistakenly used -eq rather than -contains, but still got the expected result.
Example Code:
$arr = "Fred","Bob"
if($arr -eq "Bob")
{
Return $true
}
Else
{
Return $false
}
i.e. The output from the above will be $true, as would comparing the string "Fred", but comparing with "Steve" will result in $false. The same would happen if I used -contains.
It would appear that PowerShell has "helped me out", and turned my -eq into a -contains. Is this assumption correct?
Secondly, is there a reason why PowerShell does this where as other languages (to my knowledge) don't?
Cheers,
Pete
P.S. Similarly, if I use -contains to compare a string to another string, it will only return $true if the two string match completely.
Look at this code.
$arr = "Fred", "Bob"
$arr -eq "Bob" # Bob
The relevant part of the docs is
When the input to an operator is a scalar value, comparison operators return a Boolean value. When the input is a collection of values, the comparison operators return any matching values.
In this case, the input is a collection of values. If we turn that earlier expression around, though, so that the input (left-hand side) is the scalar value 'Bob', we get something completely different.
'Bob' -eq $arr # False
I just discovered that when you apply bool operators on a collection, it acts as a filter on that collection.
So the following will return all elements that are not null:
$objectArray = #('a','b','c','d')
$objectArray -ne $null
But how can I check if the collection's reference is null?
Trevor Sullivan's if () test forces the $objectArray to cast to a boolean.
[bool]$null #is $false
[bool]#(1,2,3) # is $true , so it looks good.
But empty arrays mislead it:
[bool]#() # is $false , so it's not an accurate test.
I suggest $null -eq $objectArray:
NB. It really opens the question of why you want to know if it's $null, specifically. Trevor's answer is typical and good enough for any common use.
NB. My answer includes an uncommon, but useful suggestion - when you have a literal value for one side of a comparison, put it on the left if you can.
0 -lt $counter
$null -eq $thing
"text" -eq $variable
4 -in $collection
It's less common, so looks less familiar, but it's more resilient against PowerShell implicit casting doing something you don't expect.
All you have to do is test the variable for $true or $false. If it's $false, then it's a null reference, otherwise the opposite is true.
if (!$objectArray) {
}
The following tells you if the reference is null:
[Object]::ReferenceEquals($objectArray, $null)
Testing if the variable is $true or $false does not always work because an empty collection will cast to false:
$objectArray = #()
if (!$objectArray) {
'The array is not actually null'
}
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
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