Sorting not working in a Powershell function [duplicate] - powershell

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

Related

How to navigate nested loops within Powershell

I am trying to do something of the following nature
if(...){
}
elseif(...){
if(...){
}
else{
...
}
}
else{
...
}
However, it seems that powershell does not like having both an if and and else statement within the elseif loop. Any workarounds for this? Thanks for your help! I am really new to Powershell
I have tried switch statements but they do not make sense for what I am trying to do
Not an answer per se, but I tested the structure exactly as provided and it works just fine:
function iftest($a, $b, $c) {
if ($a) {
'$a true. $b and $c not tested.'
}
elseif ($b) {
if ($c) {
'$a false. $b true. $c true.'
}
else {
'$a false. $b true. $c false.'
}
}
else {
'$a false. $b false. $c not tested.'
}
}
# command # output
iftest $true $false $false # $a true. $b and $c not tested.
iftest $false $false $false # $a false. $b false. $c not tested.
iftest $false $true $false # $a false. $b true. $c true.
iftest $false $true $true # $a false. $b true. $c false.
As noted by mclayton in the comments, formatting goes a long way toward making the structure clear.

Using multiple commands in the <init> part of a Powershell for loop

I get an error when trying to use multiple commands in the <Init> part of a for loop in Powershell. For example,
function Example {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)] [int] $Base,
[Parameter(Mandatory=$True)] [int] $Count
)
Process {
for ( $item = 1, $id = $Base; $item -le $Count; $id++, $item++ ) {
}
}
}
Example -Base 1 -Count 2
The Microsoft documentation says that <Init> "represents one or more commands" and that <Repeat> "represents one or more commands, separated by commas". The wording is different, so I realize that the syntax may be different.
The error I get is "The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property." with the underscore beneath the 1 in "$item = 1".
You have to wrap the <init> component of your for loop either with Subexpression operator $(..):
for($($item1 = 0; $item2 = 10); $item1 -lt 10 -or $item2 -gt 0; $item1++, $item2--) {
[pscustomobject]#{
item1 = $item1
item2 = $item2
}
}
Or, as Abraham points out in his comment, each variable assignment wrapped with parentheses: ($item1 = 0), ($item2 = 10).
Following your example function, this should work:
function Example {
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[int] $Base,
[Parameter(Mandatory)]
[int] $Count
)
Process {
for (($item = 1), ($id = $Base); $item -le $Count; $id++, $item++) {
[pscustomobject]#{
item = $item
id = $id
}
}
}
}
Example -Base 4 -Count 10

How to check if a row exist in an object array?

I have an array
$a = (Invoke-RestMethod -Uri "...." -Method Get) | select X,Y,Z # Object[] Z has type of datetime
$a has X, Y, Z.
Now I need to check if a row is in $a
$x,$y,$z = ....
if ($a -contains $x,$y, $x) { ... } # doesn't work
How to do it?
It sounds like you want to test array $a for containing an object that has a given set of property values ($x, $y, $z) for a given set of property names (.X, .Y, .Z):
$hasObjectWithValues = [bool] $(foreach ($o in $a) {
if ($o.X -eq $x -and $o.Y -eq $y -and $o.Z -eq $z) {
$true
break
}
})
Note: The cleaner form [bool] $hasObjectWithValues = foreach ... should work, but, as of PowerShell Core 7.0.0-preview.4, doesn't, due to this bug
As for what you tried:
$a -contains $x,$y, $z
The RHS of PowerShell's -contains operator only supports a scalar (single value), to be tested for equality with the elements in the the array-valued LHS.
However, even if you wrapped your RHS into a single object - [pscustomobject] #{ X = $x, Y = $y, Z = $z }, that approach wouldn't work, because [pscustomobject]s, as also returned by Invoke-RestMethod, are reference types, which - in the absence of custom equality comparison behavior - are compared by reference equality, meaning that they're only considered equal if they refer to the very same object in memory.

Powershell 'x or y' assignment

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]

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.