Powershell 'x or y' assignment - powershell

There are several languages that provide either a defaulting or logical or mechanism for assignment:
a = b || c;
a = b or c
a="${b:-$c}"
a = b ? b : c;
So far the only equivalent I've found in Powershell Core is the exceedingly verbose:
$a = if ($b) { $b } else { $c }
which in some cases has to become
$a = if ($b -ne $null) { $b } else { $c }
Is there a better alternative [edit:] which doesn't sacrifice readability?

There's no || short-circuit operator in PowerShell assignments, and nothing equivalent to Perl's // "defined-or" operator - but you can construct a simple null coalesce imitation like so:
function ?? {
param(
[Parameter(Mandatory=$true,ValueFromRemainingArguments=$true,Position=0)]
[psobject[]]$InputObject,
[switch]$Truthy
)
foreach($object in $InputObject){
if($Truthy -and $object){
return $object
}
elseif($object -ne $null){
return $object
}
}
}
Then use like:
$a = ?? $b $c
or, if you want to just have return anything that would have evaluated to $true like in your first example:
$a = ?? $b $c -Truthy

So a simple method for reducing this:
$a = if ($b -ne $null) { $b } else { $c }
Would be to use the fact that true and false are just one or zero and could be used as an array index like so:
$a = #($c, $b)[$b -ne $null]

Related

Evaluating a condition with AND and OR statements combined

I'm parsing files which all have following structure:
A=B #A==test
This means that variable A will be set to B, only if A equals 'test'
Conditions can get more complex as well
C=D #C==test1,D==test2
This means that C will be set to D if C equals 'test1' AND D equals 'test2'
So far so good, I parse the individual conditions one by one, and stop as soon as one evaluates to false.
Conditions can also have OR statements:
E=F #E==test3,(F==test4|F==test5),username!=abc
This means that E will be set to F if E equals 'test3' AND (F equals 'test4' OR F equals 'test5') AND username does not equal 'abc'
I'm stuck on evaluating this last condition. What is a good approach to evaluate such an expression?
Currently I'm parsing each condition into 3 arrays, being 'lefthands', 'operators, 'righthands'.
The condition #C==test1,D==test2 gets parsed into:
$lefthands=(C, D)
$righthands=(test1,test2)
$operands=(==, ==)
Afterwards I loop over the arrays and do the evaluation based on the operand that is currently being used.
if $operands[$i] -eq "==" ( return ($lefthands[$i] -eq $righthands[$i] }
As soon as one of the conditions returns false, I stop evaluating the complete condition.
This works fine if there are only AND statements, but with an OR statement present this does no longer work.
Looks like you've got a fairly straightforward infix expression grammar that goes something like this:
variable = [ a-z | A-Z ]
string = [ a-z | A-Z | 0-9 ]
atom = variable [ "==" | "!=" ] string
expr = [ atom | "(" expr ")" | expr [ "," | "|" ] expr ]
Assuming you can nest expressions to an arbitrary depth and you want to handle operator precedence properly (e.g. does w,x|y,z mean ((w,x)|y),z) or (w,x)|(y,z) or even w,(x|y),z, all of which give different results) you're going to struggle to evaluate the expression with basic string manipulation and you might need use a more complicated approach:
Lex the string into tokens
Change the order of the tokens (infix -> postfix) to make it easier to calculate results
Evaluate the expression given some values for variables
Lexing
This basically breaks the expression string down into a list of logical chunks - for example A==test might become identifier "A", operator "==", identifier "test".
It's called "infix" notation because the operator sits in-between the operands - e.g. A==test.
function ConvertTo-InfixTokens
{
param( [string] $Source )
$chars = $Source.ToCharArray();
$index = 0;
while( $index -lt $chars.Length )
{
$token = [pscustomobject] [ordered] #{
"Type" = $null
"Value" = [string]::Empty
};
$char = $chars[$index];
switch -regex ( $char )
{
# var or str
"[a-zA-z0-9]" {
$token.Type = "identifier";
$token.Value += $char;
$index += 1;
while( ($index -lt $chars.Length) -and ($chars[$index] -match "[a-zA-z0-9]") )
{
$token.Value += $chars[$index];
$index += 1;
}
}
# eq
"=" {
if( $chars[$index+1] -eq "=" )
{
$token.Type = "eq";
$token.Value = $chars[$index] + $chars[$index+1];
$index += 2;
}
else
{
throw "LEX01 - unhandled char '$($chars[$index+1])'";
}
}
# ne
"!" {
if( $chars[$index+1] -eq "=" )
{
$token.Type = "ne";
$token.Value = $chars[$index] + $chars[$index+1];
$index += 2;
}
else
{
throw "LEX02 - unhandled char '$($chars[$index+1])'";
}
}
"," {
$token.Type = "and";
$token.Value = $char;
$index += 1;
}
"\|" {
$token.Type = "or";
$token.Value = $char;
$index += 1;
}
"\(" {
$token.Type = "lb";
$token.Value = $char;
$index += 1;
}
"\)" {
$token.Type = "rb";
$token.Value = $char;
$index += 1;
}
default {
throw "LEX03 - unhandled char '$char'";
}
}
write-output $token;
}
}
Examples:
PS> ConvertTo-InfixTokens -Source "A==test"
Type Value
---- -----
identifier A
eq ==
identifier test
PS> ConvertTo-InfixTokens -Source "E==test3,(F==test4|F==test5),username!=abc"
Type Value
---- -----
identifier E
eq ==
identifier test3
and ,
lb (
identifier F
eq ==
identifier test4
or |
identifier F
eq ==
identifier test5
rb )
and ,
identifier username
ne !=
identifier abc
Convert to Postfix
Infix notation looks more natural because it's similar to normal maths, but it relies on you handling the precedence rules for operations when you evaluate expressions - e.g. is 1 + 1 * 2 + 2 equal to (((1 + 1) * 2) + 2) => 6 or 1 + (1 * 2) + 2 => 5 or (1 + 1) * (2 + 2) => 8? - which means you might need to look ahead to see what other operators are coming in case they need to be processed first.
It's easier to evaluate the expression if we convert it to postfix notation (or Reverse Polish Notation) - see this answer for some additional advantages. This basically puts the operands first and then follows with the operator - e.g. 1 + 1 * 2 + 2 becomes 1 1 2 * + 2 +, which will be logically processed as: ((1 (1 2 *) +) 2 +) => ((1 2 +) 2 +) => (3 2 +) => 5
The following code will apply this conversion:
# based on https://www.tutorialspoint.com/Convert-Infix-to-Postfix-Expression#:~:text=To%20convert%20infix%20expression%20to,maintaining%20the%20precedence%20of%20them.
function Convert-InfixToPostfix
{
param( [pscustomobject[]] $Tokens )
$precedence = #{
"or" = 1
"and" = 2
"eq" = 9
"ne" = 9
};
$stack = new-object System.Collections.Generic.Stack[pscustomobject];
for( $i = 0; $i -lt $Tokens.Length; $i++ )
{
$token = $Tokens[$i];
switch( $token.Type )
{
"identifier" {
write-output $token;
}
"lb" {
$stack.Push($token);
}
"rb" {
while( $stack.Peek().Type -ne "lb" )
{
write-output $stack.Pop();
}
$null = $stack.Pop();
}
default {
# must be a known operator
if( -not $precedence.ContainsKey($token.Type) )
{
throw "PF01 - unknown operator '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i";
}
while( ($stack.Count -gt 0) -and ($precedence[$token.Type] -le $precedence[$stack.Peek().Type]) )
{
write-output $stack.Pop();
}
$stack.Push($token);
}
}
}
while( $stack.Count -gt 0 )
{
write-output $stack.Pop();
}
}
Examples:
PS> $infix = ConvertTo-InfixTokens -Source "A==test"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $postfix
Type Value
---- -----
identifier A
identifier test
eq ==
PS> $infix = ConvertTo-InfixTokens -Source "E==test3,(F==test4|F==test5),username!=abc"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $postfix
Type Value
---- -----
identifier E
identifier test3
eq ==
identifier F
identifier test4
eq ==
identifier F
identifier test5
eq ==
or |
and ,
identifier username
identifier abc
ne !=
and ,
Evaluation
The last step is to take the postfix tokens and evaluate them. "All" we need to do is read the tokens from left to right - when we get an operand we put it on a stack, and when we get an operator we read some operands off the stack, apply the operator and push the result back onto the stack...
function Invoke-PostfixEval
{
param( [pscustomobject[]] $Tokens, [hashtable] $Variables )
$stack = new-object System.Collections.Generic.Stack[pscustomobject];
for( $i = 0; $i -lt $Tokens.Length; $i++ )
{
$token = $Tokens[$i];
if( $token.Type -eq "identifier" )
{
$stack.Push($token);
}
elseif( $token.Type -in #( "eq", "ne" ) )
{
$str = $stack.Pop().Value;
$var = $stack.Pop();
if( -not $Variables.ContainsKey($var.Value) )
{
throw "undefined variable '$($var.Value)' in token '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i";
}
$val = $Variables[$var.Value];
$result = switch( $token.Type ) {
"eq" { $val -eq $str }
"ne" { $val -ne $str }
default { throw "unhandled operator '$($token.Type)' in token '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i"; }
}
#write-host "'$val' $($token.Type) '$str' => $result";
$stack.Push([pscustomobject] [ordered] #{ "Type" = "bool"; Value = $result });
}
elseif( $token.Type -in #( "and", "or" ) )
{
$left = $stack.Pop().Value;
$right = $stack.Pop().Value;
$result = switch( $token.Type ) {
"and" { $left -and $right }
"or" { $left -or $right }
default { throw "unhandled operator '$($token.Type)' in token '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i"; }
}
#write-host "'$left' $($token.Type) '$right' => $result";
$stack.Push([pscustomobject] [ordered] #{ "Type" = "bool"; Value = $result });
}
else
{
throw "EVAL01 - unhandled token '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i";
}
}
return $stack.Peek().Value;
}
Examples:
PS> $variables = [ordered] #{
"A" = "test";
"C" = "test1";
"D" = "test2";
"E" = "test3";
"F" = "test4";
"username" = "abc"
}
PS> $infix = ConvertTo-InfixTokens -Source "A==test"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $value = Invoke-PostfixEval -Tokens $postfix -Variables $variables
PS> $value
True
PS> $infix = ConvertTo-InfixTokens -Source "E==test3,(F==test4|F==test5),username!=abc"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $value = Invoke-PostfixEval -Tokens $postfix -Variables $variables
PS> $value
False # fails because "username!=abc" is false
PS> $infix = ConvertTo-InfixTokens -Source "E==test3,F==test4|F==test5,username!=abc"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $value = Invoke-PostfixEval -Tokens $postfix -Variables $variables
PS> $value
True # operator precedence means it's evaluated as "(E==test3,F==test4)|(F==test5,username!=abc)" and "(E==test3,F==test4)" is true
You'll probably want to add more error handling for production code, and write some Pester tests to make sure it works for a larger variety of inputs, but it worked for the limited tests I did here. If you find any examples that don't work feel free to post them in comments...

How to use powershell to reorder a string to obfuscate a hidden message?

Just for fun a friend and I are trying to find a creative way to send coded messages to eachother using steganography.I stumbled upon doing something like whats shown below and I have been struggling trying to write a function to automate the process.
this is a secret message
can be turned into:
("{2}{1}{0}{3}"-f'ecret m','is a s','this ','essage')
splitting the string and using reordering seems to be the way to go.
So the string needs to be split in random splits between 5-10 characters
.
The index of the original positions need to be saved
the splits need to be swapped around
and the new indexes sorted as to reorder the message properly
i've just really been struggling
help is appreciated
Just for fun .... 😉🤡
$InputMessage = 'this is a secret message'
$SplittedString = $InputMessage -split '' | Select-Object -Skip 1 | Select-Object -SkipLast 1
[array]::Reverse($SplittedString)
foreach ($Character in $SplittedString) {
if ($Character -notin $CharacterList) {
[array]$CharacterList += $Character
}
}
foreach ($Character in ($InputMessage -split '' | Select-Object -Skip 1 | Select-Object -SkipLast 1)) {
$Index = [array]::indexof($CharacterList, $Character)
$Output += "{$Index}"
}
$Result = "'$Output' -f $(($CharacterList | ForEach-Object {"'$_'"}) -join ',')"
$Result
And the output of this would be:
'{6}{10}{9}{3}{5}{9}{3}{5}{2}{5}{3}{0}{8}{7}{0}{6}{5}{4}{0}{3}{3}{2}{1}{0}' -f 'e','g','a','s','m',' ','t','r','c','i','h'
And the output of this would be:
this is a secret message
And now if you want to go fancy with it you remove the curly braces and the quotes and the commas and the -f and add only the numbers and characters to the data. ;-)
Not exactly what you're looking for but this might give you something to start with:
class Encode {
[string] $EncodedMessage
[int[]] $Map
[int] $EncodingComplexity = 3
Encode ([string] $Value) {
$this.Shuffle($Value)
}
Encode ([string] $Value, [int] $Complexity) {
$this.EncodingComplexity = $Complexity
$this.Shuffle($Value)
}
[void] Shuffle([string] $Value) {
$set = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!##$%^&*()_-+=[{]};:<>|./?'
$ref = [Collections.Generic.HashSet[int]]::new()
$ran = [random]::new()
$enc = [char[]]::new($Value.Length * $this.EncodingComplexity)
for($i = 0; $i -lt $enc.Length; $i++) {
$enc[$i] = $set[$ran.Next($set.Length)]
}
for($i = 0; $i -lt $Value.Length; $i++) {
do {
$x = $ran.Next($enc.Length)
} until($ref.Add($x))
$enc[$x] = $Value[$i]
}
$this.EncodedMessage = [string]::new($enc)
$this.Map = $ref
}
}
class Decode {
static [string] DecodeMessage ([Encode] $Object) {
return [Decode]::DecodeMessage($Object.EncodedMessage, $Object.Map, $Object.EncodingComplexity)
}
static [string] DecodeMessage ([string] $EncodedMessage, [int[]] $Map) {
return [Decode]::DecodeMessage($EncodedMessage, $Map, 3)
}
static [string] DecodeMessage ([string] $EncodedMessage, [int[]] $Map, [int] $Complexity) {
$decoded = [char[]]::new($EncodedMessage.Length / $Complexity)
for($i = 0; $i -lt $decoded.Length; $i++) {
$decoded[$i] = $EncodedMessage[$Map[$i]]
}
return [string]::new($decoded)
}
}
Encoding a message:
PS /> $message = 'this is a secret message'
PS /> $encoded = [Encode] $message
PS /> $encoded
EncodingComplexity EncodedMessage Map
------------------ -------------- ---
3 B$h^elu2w#CeeHH^qa siQJ)t}es:.a3 ema=eN(GiIcsO;tst1 .fsg}eSUk7ms4 N>rfe# {49, 2, 41, 27…}
For decoding the message you can either use the object of the type Encode or you can give your friend the Encoded Message and the Map to decode it ;)
PS /> [Decode]::DecodeMessage($encoded)
this is a secret message
PS /> [Decode]::DecodeMessage('B$h^elu2w#CeeHH^qa siQJ)t}es:.a3 ema=eN(GiIcsO;tst1 .fsg}eSUk7ms4 N>rfe#', $encoded.Map)
this is a secret message

Sorting not working in a Powershell function [duplicate]

This question already has answers here:
How do I pass multiple parameters into a function in PowerShell?
(15 answers)
Powershell function call changing passed string into int
(1 answer)
Closed 1 year ago.
I have the following function in Powershell. It always returns False and also the sorting of the arrays doesn't work within the function while it works in the console. The function is
# $a = [121, 144, 19, 161, 19, 144, 19, 11]
# $b = [121, 14641, 20736, 361, 25921, 361, 20736, 361]
function comp($a, $b) {
if( -Not ($a.length -Eq $b.length) || -Not $a || -Not $b) {
return $false
}
$a = $a | sort
$b = $b | sort
# Adding echo statements here to check if $a and $b are sorted tells that both are NOT sorted despite assigning them to the same variable
# Assigning the sorted arrays to other variables such as $x and $y again doesn't solve the problem. $x and $y also have the unsorted values
for($i=0;$i -lt $a.length;$i++) {
if( -Not ($b[$i] -Eq $a[$i]*$a[$i])) {
return $false
}
}
return $true
}
Note: $a and $b in the top are initialized without [ and ] and it is just provided to give emphasis that they are arrays.
The above function returns False while it must be True. I then tried this
function comp($a, $b) {
if( -Not ($a.length -Eq $b.length) || -Not $a || -Not $b) {
return $false
}
for($i=0;$i -lt $a.length;$i++) {
$flag = $false
for($j=0;$j -lt $b.length; $j++) {
if($b[$j] -Eq $a[$i]*$a[$i]) {
$flag = $true
# Never gets into this i.e. never executed
break;
}
}
if( -Not $flag) {
return $flag
}
}
return $true
}
But this works on the console when run without a function. Please see the image below
It didn't return False. And hence, the output is True which is correct
Now see the output for the above functions
Now for the second one
What is wrong here?
You're passing the arguments to comp incorrectly.
PowerShell's command invocation syntax expects you to pass arguments separated by whitespace, not a comma-separated list.
Change:
comp($a, $b)
to:
comp $a $b
# or
comp -a $a -b $b
See the about_Command_Syntax help topic for more information on how to invoke commands in PowerShell

How to set PowerShell variables to True or False

PowerShell logic question:
$a = 1
$b = 2
if ($a = $b) {
$ans = $true
}
else {
$ans = $false
}
Write-Host $ans
Output:
True
Can someone explain to me why this evaluates as true? Is it because I assign $ans to true first? Also, could someone show me how to get this to evaluate the way I think it should?
You're doing an assignment $a = $b, the assignment succeeds and that returns true because b was true, so the first case will always evaluate to true at the moment, instead use a comparison: $a -eq $b.
More detailed information on comparisons in powershell.

Converting strings to timespans, $PSItem in 'switch'?

I have a bunch of strings, in the form of:
'3m 36s', '24m 38s', '59s'
, to be converted to timespans. My current "solution" is:
'3m 36s', '24m 38s', '59s' |ForEach-Object {
$s = 0
$m = 0
$h = 0
$PSItem.Split(' ') |ForEach-Object {
$item = $PSItem
switch ($PSItem[-1])
{
's'
{
$s = $item.TrimEnd('s')
}
'm'
{
$m = $item.TrimEnd('m')
}
'h'
{
$h = $item.TrimEnd('h')
}
Default
{
Write-Error 'Ooops...' -ErrorAction Stop
}
}
}
$timespan = New-TimeSpan -Hours $h -Minutes $m -Seconds $s
# ToString() is used just to get some easy to read output
$timespan.ToString()
}
While it seems to work for me, I have two issues with the above:
Is the general approach
ForEach -> Split(' ') -> ForEach -> switch
OK-ish? Are there any alternative/better ways of doing the conversion?
I tried using $PSItem in the switch
It seems that the switch construct has it's "own pipeline"
# $item = $PSItem
switch ($PSItem[-1])
{
's'
{
$PSItem
}
}
-- in the above $PSItem evaluates to 's'(, 'm', the value matched). What is actually going on? (internaly?)
I would take one ForEach loop out of things by performing that loop with the Switch command. Here's what I'd end up with:
'3m 36s', '59s', '24m 38s' |%{
$TSParams = #{}
Switch($_.Split()){
{$_[-1] -eq 's'}{$TSParams.Add('Seconds', ([int]$_.trim('s')))}
{$_[-1] -eq 'm'}{$TSParams.Add('Minutes', ([int]$_.trim('m')))}
{$_[-1] -eq 'h'}{$TSParams.Add('Hours', ([int]$_.trim('h')))}
}
New-TimeSpan #TSParams
}
For each string it creates an empty hashtable, then loops through each item of the Split() method, adding the appropriate time to the hashtable. Then it splats that to the New-TimeSpan command, and moves to the next item in the ForEach loop. I tried it locally and had some issues initially when the numbers did not cast as an int, and it tried to convert them to a DateTime, which is why I type cast them in the above code.