I'm looking for example of how I would solve the scenario below:
Imagine my printer has the following property for "Status"
0 -Offline
2 -Paper Tray Empty
4 -Toner Exhausted
8 -Paper Jam
When I query status it returns a value of 12. I can obviously see that this means the printer has the Toner Exhausted and a Paper Jam, but how would I work this out with Powershell?
Thanks
The boolean bitwise and operator in Powershell is -band.
Assume you define your values and descriptions in a hashtable, and have the value of 12 from the printer:
$status = #{1 = "Offline" ; 2 = "Paper Tray Empty" ; 4 = "Toner Exhausted" ; 8 = "Paper Jam" }
$value = 12
Then, this statement will give you the textual descriptions:
$status.Keys | where { $_ -band $value } | foreach { $status.Get_Item($_) }
You could define the enum in Powershell, but the above works just as well, and defining enums in Powershell seems like a lot of work.
Here is an article, that talks about how to use the bitwise operators in Powershell.
You can let PowerShell do more of the work for you. Here's an example using System.IO.FileOptions:
PS> [enum]::GetValues([io.fileoptions]) | ?{$_.value__ -band 0x90000000}
RandomAccess
WriteThrough
Related
Today (2017-05-29) I am using PowerShell 5.0.10586.117 on Windows 7 Enterprise and run the following (shortened):
$dateOfLicense = "2017-04-20"
$dateOfToday = '{0:yyyy-MM-dd}' -f (Get-Date)
$TimeDifference = [DateTime]$dateOfToday - [DateTime]$dateOfLicense
if (($TimeDifference) = 14)
{
Write-Host "test"
}
Even the difference between both days is 39, my code jumps in the if-clause and sends "test" to screen.
What am I doing wrong here?
You are assigning 14 to $TimeDifference. Instead you wan't to compare the Days property using -le:
if ($TimeDifference.Days -le 14)
{
Write-Host "test"
}
To complement Martin Brandl's helpful answer:
Like many other languages - but unlike VBScript, for instance - PowerShell uses distinct symbols for:
the assignment operator (=)
vs. the equality test operator (-eq).
This distinction enables using assignments as expressions, which is what you inadvertently did:
if ($TimeDifference = 14) ... # same as: if (($TimeDifference) = 14) ...
assigns 14 to variable $TimeDifference, as Martin explains, and, because the assignment is (of necessity, to serve as a conditional for if) enclosed in (...), returns the assigned value (the inner (...) around $TimeDifference make no difference here, however) and that value is used as the Boolean conditional for if.
That is, the (...) expression evaluated by if has value 14 - a nonzero number - and is therefore interpreted as $true in this Boolean context, irrespective of the original value of $TimeDifference.
Note:
To learn more about PowerShell's operators, run Get-Help about_Operators
To learn about how PowerShell interprets arbitrary values as Booleans in conditionals (to-Boolean coercion), see the bottom section of this answer.
To test variables or expressions that already are Booleans, just use them as-is or, if necessary, negate them with -not (!); e.g.:
if ($someBoolean) { # Better than: if ($someBoolean -eq $true)
if (-not $someBoolean) { # Better than: if ($someBoolean -eq $false)
Finally, here's a streamlined version of your code that doesn't require intermediate variables, uses a cast to convert the string to a [datetime] instance and uses [datetime]::now, the more efficient equivalent of Get-Date (though that will rarely matter).
if (([datetime]::now - [datetime] '2017-04-20').Days -eq 14) {
"test"
}
Note how "test" as a statement by itself implicitly sends output to PowerShell's (success) output stream, which prints to the console by default.
Write-Host bypasses this stream and should generally be avoided.
Not better solution of Martin, just an shorty code
$dateOfLicense = [DateTime]"2017-04-20"
$TimeDifferenceDays = ((Get-Date) - $dateOfLicense).Days
if ($TimeDifferenceDays -lt 14)
{
Write-Host "test"
}
If you google for PowerShell Eqv you currently will find the VBScript-to-Windows PowerShell Conversion Guide on top of the list. Yet the answer is much of technical help:
Eqv Operator
Definition: Performs a logical equivalence on two expressions.
No doubt Eqv has its uses; we’re just not sure if it has any practical uses. Although there might be a Windows PowerShell equivalent we have to be honest: we didn’t look real hard for one.
In fact, I used the Eqv operator (also written as: A↔B) a number of times in VBScript and likely would have used it in PowerShell if it existed.
Example
I have a list of groups ("HR", "SAP", "!IT", "..."). If a user is a member of all the listed groups and explicitly not a member of groups that are preceded with an escalation mark (like "!IT", which is unraveled to: $Negate = $True and $Group = "IT") a specific task should be done. The script needs to iterate through the groups and immediately break the iteration when a group condition is not met (to save time).
A command for this would have been something like:
If ($Negate -eqv (IsMember($Group))) {Break}
How can I build a logical equivalence operator with a minimum of code?
If you take definition quiet literally, you will probably already see a possible way to achieve a logical equivalence operation:
If ([Bool]$Expression1 -eq [Bool]$Expression2) {...}
But if you take a close look to the truth table you might notice that the results for Eqv are exactly the opposite of an Xor operation.
Meaning that you can also achieve logical equivalence operation with an inverted Xor:
If (!(Expression1 -xor $Expression2)) {...}
And as it doesn’t matter what you invert for an Xor (either the whole operation or one of the expressions), you can even simplify it to:
If (!Expression1 -xor $Expression2) {...}
Check
0..3 | ForEach {
$Expression1, $Expression2 = [Int]($_ / 2), [Int]($_ % 2)
New-Object PSObject -Property #{
Expression1 = [Bool]$Expression1
Expression2 = [Bool]$Expression2
Equivalence = !$Expression1 -xor $Expression2
}
} | Format-Table -AutoSize
Truth Table
Expression1 Expression2 Equivalence
----------- ----------- -----------
False False True
False True False
True False False
True True True
Note: In this solution $Null expressions are considered $False. This differs from the VBScript Eqv implementation but is consistent with other PowerShell operators that contain $Null expressions. e.g. The VBScript statement: If 1 And vbNull Then msgbox "True" Else msgbox "False", returns True where the PowerShell statement If (1 -and $Null) {"True"} Else {"False"}, returns False.
Bitwise
If you looking for a bitwise Eqv operator (which should probably be called -bEqv, if it existed), then it would be:
$Equivalence = -bNot Expression1 -bXOr Expression2 # e.g.: -bNot 3 -bXOr 5 = -7 (-bAnd 0xF = 9)
Today (2017-05-29) I am using PowerShell 5.0.10586.117 on Windows 7 Enterprise and run the following (shortened):
$dateOfLicense = "2017-04-20"
$dateOfToday = '{0:yyyy-MM-dd}' -f (Get-Date)
$TimeDifference = [DateTime]$dateOfToday - [DateTime]$dateOfLicense
if (($TimeDifference) = 14)
{
Write-Host "test"
}
Even the difference between both days is 39, my code jumps in the if-clause and sends "test" to screen.
What am I doing wrong here?
You are assigning 14 to $TimeDifference. Instead you wan't to compare the Days property using -le:
if ($TimeDifference.Days -le 14)
{
Write-Host "test"
}
To complement Martin Brandl's helpful answer:
Like many other languages - but unlike VBScript, for instance - PowerShell uses distinct symbols for:
the assignment operator (=)
vs. the equality test operator (-eq).
This distinction enables using assignments as expressions, which is what you inadvertently did:
if ($TimeDifference = 14) ... # same as: if (($TimeDifference) = 14) ...
assigns 14 to variable $TimeDifference, as Martin explains, and, because the assignment is (of necessity, to serve as a conditional for if) enclosed in (...), returns the assigned value (the inner (...) around $TimeDifference make no difference here, however) and that value is used as the Boolean conditional for if.
That is, the (...) expression evaluated by if has value 14 - a nonzero number - and is therefore interpreted as $true in this Boolean context, irrespective of the original value of $TimeDifference.
Note:
To learn more about PowerShell's operators, run Get-Help about_Operators
To learn about how PowerShell interprets arbitrary values as Booleans in conditionals (to-Boolean coercion), see the bottom section of this answer.
To test variables or expressions that already are Booleans, just use them as-is or, if necessary, negate them with -not (!); e.g.:
if ($someBoolean) { # Better than: if ($someBoolean -eq $true)
if (-not $someBoolean) { # Better than: if ($someBoolean -eq $false)
Finally, here's a streamlined version of your code that doesn't require intermediate variables, uses a cast to convert the string to a [datetime] instance and uses [datetime]::now, the more efficient equivalent of Get-Date (though that will rarely matter).
if (([datetime]::now - [datetime] '2017-04-20').Days -eq 14) {
"test"
}
Note how "test" as a statement by itself implicitly sends output to PowerShell's (success) output stream, which prints to the console by default.
Write-Host bypasses this stream and should generally be avoided.
Not better solution of Martin, just an shorty code
$dateOfLicense = [DateTime]"2017-04-20"
$TimeDifferenceDays = ((Get-Date) - $dateOfLicense).Days
if ($TimeDifferenceDays -lt 14)
{
Write-Host "test"
}
I have a script that checks the health of a pc by parsing through log files looking for indicators of compromise. If the script finds a certain event id it returns a normalized message. The end goal is to do math on these returns- this will generate a health score for that PC.
What I need to know is how set a variable (say X with a value of 1) if the event id is found, and set the same variable (say X with a value of 2) if the event id is not found. If I just set both variables in the script -in their respective if/else blocks, won't the last variable always overwrite the first regardless of the condition?
Unfortunately PowerShell doesn't have a conditional assignment statement like Perl ($var = (<condition>) ? 1 : 2;), but you can assign the output of an if statement to a variable:
$var = if (<condition>) { 1 } else { 2 }
Of course you could also do the "classic" approach and assign the variable directly in the respective branches:
if (<condition>) {
$var = 1
} else {
$var = 2
}
The second assignment doesn't supersede the first one, because only one of them is actually executed, depending on the result of the condition.
Another option (with a little more hack value) would be to calculate the values from the boolean result of the condition. Negate the boolean value, cast it to an int and add 1 to it:
$var = [int](-not (<condition>)) + 1
In Powershell 7 you can use the ternary operator:
$x = $true ? 1 : 2
echo $x
displays 1.
What you may want however is switch, e.g.,
$in = 'test2'
$x = switch ($in) {
'test1' {1}
'test2' {2}
'test3' {4}
}
echo $x
displays 2.
A little example that can help to understand.
PowerShell script:
$MyNbr = 10
$MyMessage = "Crucial information: " + $(
if ($MyNbr -gt 10) {
"My number is greater than 10"
} elseif ($MyNbr -lt 10) {
"My number is lower than 10"
} else {
"My number is 10"
}
)
Write-Host $MyMessage
Output:
Crucial information: My number is 10
If you change the MyNbr variable, you will have a different result depending on conditions in the if statements.
You actually can evaluate a condition and assign the result to a variable. It's the closest you get to a ternary operator in PowerShell. I find it makes it simpler to do complicated conditional statements. It makes it easier to read and change them. Here's an example:
$theEventIdIWant = 6000
$eventId = 5000
$correct = $eventID -eq $theEventIdIWant
if($correct) {$true} else {$false}
substring complains when I try to limit a string to 10 characters which is not 10 or more characters in length. I know I can test the length but I would like to know if there is a single cmdlet which will do what I need.
PS C:\> "12345".substring(0,5)
12345
PS C:\> "12345".substring(0,10)
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within the string.
Parameter name: length"
At line:1 char:18
+ "12345".substring( <<<< 0,10)
Do you need exactly a cmdlet? I wonder why you don't like getting length. If it's part of a script, then it looks fine.
$s = "12345"
$s.substring(0, [System.Math]::Min(10, $s.Length))
Using the substring function has it's limitations and requires you to first capture the length of the string. Granted this does work you can do it without that limitation.
The following will return the first 5 characters of the string
"1234567890"[0..4] -join "" # returns the string '12345'
And this will work on strings that are shorter than desired length
"1234567890"[0..1000] -join "" # returns the string '1234567890'
You can load and use other libraries and use their string functions, for example the visual
basic string functions work nicely for what you want to do
call once per session
>[void][reflection.assembly]::LoadWithPartialName("microsoft.visualbasic")
then use various vb string functions
>[microsoft.visualbasic.strings]::left("12345",10)
12345
or
>[microsoft.visualbasic.strings]::mid("12345",1,10)
12345
The previous answers didn't suit my purposes (no offence!) so I took Denomales suggestion above and rolled it into a function which I thought I'd share:
function Trim-Length {
param (
[parameter(Mandatory=$True,ValueFromPipeline=$True)] [string] $Str
, [parameter(Mandatory=$True,Position=1)] [int] $Length
)
$Str[0..($Length-1)] -join ""
}
Example usages:
"1234567" | Trim-Length 4 # returns: "1234"
"1234" | Trim-Length 99 # returns: "1234"
Thanks to Dmitry for the answer, I turned it into a function and made it so it is 1 based as opposed to 0 based.
function acme-substr ([string]$str, $start, $end) {
$str.substring($start-1, [System.Math]::Min($str.Length-1, $end))
}
> $foo="0"*20
> $foo
00000000000000000000
> acme-substr $foo 1 5
00000