Variables seem the same but don't match - arrays

I am trying to compare 2 values in variables to see if they're the same, in the PowerShell output I can see that some combinations should be true!
First, without the making of $vergelijking1 and $vergelijking2 it showed as if $nummersPOs[$counter] and $object.'col1' were the same but the if statement was never true.
The only thing I could think of as to why it would fail is that 1 of the variables comes from an array. When I changed both types to String I could indeed see that there was some hidden text but I don't understand why my if statment is never true now. It writes "test2" but should write "inside the loop".
[System.Collections.ArrayList]$data = Import-Csv "C:\Users\UserName\Documents\test.csv"
[System.Collections.ArrayList]$NummersPOs = Import-Csv "C:\Users\UserName\Documents\test.csv" | select "col1" -Unique
$counter = 0
foreach ($object in $NummersPOs) {
$newCSV = New-Object System.Collections.ArrayList
foreach ($object in $data) {
if ($object."col2") {
$index = $newCSV.Add($object)
[string]$vergelijking1 = $NummersPOs[$counter]
#$vergelijking1 = $vergelijking1 -replace '\D+(\d+)','$1'
$vergelijking1
[string]$vergelijking2 = $object.'col1'
$vergelijking2
if ($vergelijking1 -contains $vergelijking2) {
Write-Host "inside the loop"
} else {
Write-Host "test2"
}
}
}
$counter++
}
$newCSV | Export-Csv "C:\Users\UserName\Documents\test2.csv"
Sample output:
#{col1=632424}
632424
test2
#{col1=632424}
632446
test2
As you can see, the first one should have been true already. -contain or -like both give false BTW.

Take an actual look at your operands:
"#{col1=632424}" ← $vergelijking1
"632424" ← $vergelijking2
These two strings are obviously not equal, so why would you expect them to be?
You're assigning an object to $verglijking1 and cast it to a string, so you actually get a string representation of that object (#{col1=632424}). To $verglijking2 you assign the value of an object property ($object.'col1'). Even though you still cast the value to a string you only get the string representation of the value, not of the object.
Also, you can't use the -contains operator, as #Mathias R. Jesson pointed out, since that operator is for checking the presence of elements in an array. The -like operator would've worked, but you'd have to put wildcards before and after the value ("*$verglijking2*"). Without wildcards the operator behaves exactly like the -eq operator.
With that said, all the casting you do in your script is completely unnecessary. Handle both variables the same way and use the correct comparison operator, and the problem will disappear.
if ($object.col2) {
$vergelijking1 = $NummersPOs[$counter].col1
$vergelijking2 = $object.col1
if ($vergelijking1 -eq $vergelijking2) {
Write-Host "inside the loop"
} else {
Write-Host "test2"
}
}

Related

String comparison in PowerShell doesn't seem to work

I am trying to compare strings from one array to every string from the other. The current method works, as I have tested it with simpler variables and it retrieves them correctly. However, the strings that I need it to compare don't seem to be affected by the comparison.
The method is the following:
$counter = 0
for ($num1 = 0; $num1 -le $serverid_oc_arr.Length; $num1++) {
for ($num2 = 0; $num2 -le $moss_serverid_arr.Length; $num2++) {
if ($serverid_oc_arr[$num1] -eq $moss_serverid_arr[$num2]) {
break
}
else {
$counter += 1
}
if ($counter -eq $moss_serverid_arr.Length) {
$unmatching_serverids += $serverid_oc_arr[$num1]
$counter = 0
break
}
}
}
For each string in the first array it is iterating between all strings in the second and comparing them. If it locates equality, it breaks and goes to the next string in the first array. If it doesn't, for each inequality it is adding to the counter and whenever the counter hits the length of the second array (meaning no equality has been located in the second array) it adds the corresponding string to a third array that is supposed to retrieve all strings that don't match to anything in the second array in the end. Then the counter is set to 0 again and it breaks so that it can go to the next string from the first array.
This is all okay in theory and also works in practice with simpler strings, however the strings that I need it to work with are server IDs and look like this:
289101b4-3e6c-4495-9c67-f317589ba92c
Hence, the script seems to completely ignore the comparison and just puts all the strings from the first array into the third one and retrieves them at the end (sometimes also some random strings from both first and second array).
Another method I tried with similar results was:
$unmatching_serverids = $serverid_oc_arr | Where {$moss_serverid_arr -NotContains $_}
Can anyone spot any mistake that I may be making anywhere?
The issue with your code is mainly the use of -le instead of -lt, collection index starts at 0 and the collection's Length or Count property starts at 1. This was causing $null values being added to your result collection.
In addition to the above, $counter was never getting reset if the following condition was not met:
if ($counter -eq $moss_serverid_arr.Length) { ... }
You would need to a add a $counter = 0 outside inner for loop to prevent this.
Below you can find the same, a little bit improved, algorithm in addition to a test case that proves it's working.
$ran = [random]::new()
$ref = [System.Collections.Generic.HashSet[int]]::new()
# generate a 100 GUID collection
$arr1 = 1..100 | ForEach-Object { [guid]::NewGuid() }
# pick 90 unique GUIDs from arr1
$arr2 = 1..90 | ForEach-Object {
do {
$i = $ran.Next($arr1.Count)
} until($ref.Add($i))
$arr1[$i]
}
$result = foreach($i in $arr1) {
$found = foreach($z in $arr2) {
if ($i -eq $z) {
$true; break
}
}
if(-not $found) { $i }
}
$result.Count -eq 10 # => Must be `$true`
As side, the above could be reduced to this using the .ExceptWith(..) method from HashSet<T> Class:
$hash = [System.Collections.Generic.HashSet[guid]]::new([guid[]]$arr1)
$hash.ExceptWith([guid[]]$arr2)
$hash.Count -eq 10 # => `$true`
The working answer that I found for this is the below:
$unmatching_serverids = #()
foreach ($item in $serverid_oc_arr)
{
if ($moss_serverid_arr -NotContains $item)
{
$unmatching_serverids += $item
}
}
No obvious differences can be seen between it and the other methods (especially for the for-loop, this is just a simplified variant), but somehow this works correctly.

Powershell: adding an element from an array to another array [duplicate]

This question already has an answer here:
Powershell: Piping output of pracl command to array
(1 answer)
Closed 1 year ago.
I am trying to add elements to array for filtering. after it goes through the loop the first time
I receive "Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'."
I have tried several methods to try and figure this out.
$JsonDB = Get-Content 'Q:\Technology\1AA\HardwareCollection.json' | Out-String | ConvertFrom-Json
foreach($client in $JsonDB)
{
if($client.HRSeparation -eq "No")
{
$ClientNotHRSeparated += $client
}
else
{
$ClientHRSeparated += $client
}
}
$JsonDB
Any help would be greatly appreciated, Thanks!!
ConvertFrom-Json parses a JSON string into PSObject(s).
Since you did not define $ClientNotHRSeparated and $ClientHRSeparated anywhere, but immediately start adding ($client) objects to it, in the first iteration your variable $ClientNotHRSeparated will become that client object.
The next time you do +=, you're trying to add an object to another object which does not work.
Define the variables on top of the script, preferably as List object that has a .Add() method.
$ClientNotHRSeparated = [System.Collections.Generic.List[object]]::new()
$ClientHRSeparated = [System.Collections.Generic.List[object]]::new()
Then in your loop use that as
$ClientNotHRSeparated.Add($client)
# same for $ClientHRSeparated
P.S. Using a List is much faster/better that adding to a simple array (#()), because when you add items to an array (which has a fixed length) with +=, the entire array needs to be rebuilt in memory, consuming memory and processing time
Although this works, you don't need a loop at all. Just do:
$ClientNotHRSeparated = $JsonDB | Where-Object { $_.HRSeparation -eq "No" }
$ClientHRSeparated = $JsonDB | Where-Object { $_.HRSeparation -ne "No" }
The first line can be rewritten as $JsonDB = Get-Content -Path 'Q:\Technology\1AA\HardwareCollection.json' -Raw | ConvertFrom-Json.
Switch -Raw makes the cmdlet read the content of the file as one single multilined string
The behavior of += is entirely dependent on the left-hand side operand. On the first assignment, the value of $ClientNotHRSeparated is $null, so the resulting operation is:
$ClientNotHRSeparated = $null + $someCustomPSObject
Which PowerShell evaluates as just:
$ClientNotHRSeparated = $someObject
On the second assigment, $ClientNotHRSeparated is no longer $null, and PowerShell instead of tries to identify an overload for + that works on two operands of type [PSObject], which is where it fails.
If you want += to perform array addition, define the two array variables ahead of time with an assignment of a resizable array (use the #() array subexpression operator):
$ClientNotHRSeparated = #()
$ClientHRSeparated = #()
$JsonDB = Get-Content 'Q:\Technology\1AA\HardwareCollection.json' | Out-String | ConvertFrom-Json
foreach ($client in $JsonDB) {
if ($client.HRSeparation -eq "No") {
$ClientNotHRSeparated += $client
}
else {
$ClientHRSeparated += $client
}
}
$JsonDB
Now += is unambiguous both the first time and subsequently - the left-hand side operand is an array in either case.
As an alternative to looping through the whole collection manually, consider using the .Where() extension method in Split mode:
$JsonDB = Get-Content 'Q:\Technology\1AA\HardwareCollection.json' | Out-String | ConvertFrom-Json
$ClientNotHRSeparated, $ClientHRSeparated = #($JsonDB).Where({$_.HRSeparation -eq 'No'}, 'Split')
Much faster and more concise :-)

Adding element to array in powershell scriptblock converts array to string

I noticed odd behaviour using arrays in scriptblocks. The following code shows the problem:
$array = #("x", "y")
Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"
$bad = {
$array += "z"
Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"
$array
}
$good = {
$array = $array.Clone()
$array += "z"
Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"
$array
}
& $good
& $bad
Executing the script will produce the following output:
Object[]
array
Object[]
array
x
y
z
String
System.Object
z
The scriptblock $bad does not work as I would expect. It converts the array to string, but it should simply add the element z to the array. If there is no element added, the array can be used as expected.
I noticed this behaviour in powershell 5.0 and 5.1 but not in the ISE. Is it a bug or can anyone explain this?
It's a scope issue. The variable on the left side of the assignment operation in the scriptblocks is defined in the local scope.
This statement
$array = $array.Clone()
clones the value of the global variable $array and assigns it to the local variable $array (same name, but different variable due to different scope). The local variable $array then contains a copy of the original array, so the next statement
$array += "z"
appends a new element to that array.
In your other scriptblock you immediately append a string to the (local) variable $array. In that context the local variable is empty, so $array += "z" has the same effect as $array = "z", leaving you with a variable containing just the string "z".
Specify the correct scope and you'll get the behavior you expect:
$array = #("x", "y")
$not_bad = {
$script:array += "z"
Write-Host "$($script:array.GetType().Name)"
Write-Host "$($script:array.GetType().BaseType)"
$script:array
}
& $not_bad
Beware, however, that this will actually modify the original array in the global/script scope (your $good example leaves the original array unchanged).
I'm not sure if I would consider this behavior a bug, but it's definitely a gotcha.
I would like to post my preferred solution which bases on Ansgars explanation:
$array = #("x", "y")
$not_bad = {
$array = $array + "z"
Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"
$array
}
& $not_bad
Important is the assignment to the local variable (or better to create a local variable) before adding further elements. A simple
$array = $array
would do, but this line may be confusing.

Array.Find and IndexOf for multiple elements that are exactly the same object

I have trouble of getting index of the current element for multiple elements that are exactly the same object:
$b = "A","D","B","D","C","E","D","F"
$b | ? { $_ -contains "D" }
Alternative version:
$b = "A","D","B","D","C","E","D","F"
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" })
This will return:
D
D
D
But this code:
$b | % { $b.IndexOf("D") }
Alternative version:
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" }) | % { $b.IndexOf($_) }
Returns:
1
1
1
so it's pointing at the index of the first element. How to get indexes of the other elements?
You can do this:
$b = "A","D","B","D","C","E","D","F"
(0..($b.Count-1)) | where {$b[$_] -eq 'D'}
1
3
6
mjolinor's answer is conceptually elegant, but slow with large arrays, presumably due to having to build a parallel array of indices first (which is also memory-inefficient).
It is conceptually similar to the following LINQ-based solution (PSv3+), which is more memory-efficient and about twice as fast, but still slow:
$arr = 'A','D','B','D','C','E','D','F'
[Linq.Enumerable]::Where(
[Linq.Enumerable]::Range(0, $arr.Length),
[Func[int, bool]] { param($i) $arr[$i] -eq 'D' }
)
While any PowerShell looping solution is ultimately slow compared to a compiled language, the following alternative, while more verbose, is still much faster with large arrays:
PS C:\> & { param($arr, $val)
$i = 0
foreach ($el in $arr) { if ($el -eq $val) { $i } ++$i }
} ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Note:
Perhaps surprisingly, this solution is even faster than Matt's solution, which calls [array]::IndexOf() in a loop instead of enumerating all elements.
Use of a script block (invoked with call operator & and arguments), while not strictly necessary, is used to prevent polluting the enclosing scope with helper variable $i.
The foreach statement is faster than the Foreach-Object cmdlet (whose built-in aliases are % and, confusingly, also foreach).
Simply (implicitly) outputting $i for each match makes PowerShell collect multiple results in an array.
If only one index is found, you'll get a scalar [int] instance instead; wrap the whole command in #(...) to ensure that you always get an array.
While $i by itself outputs the value of $i, ++$i by design does NOT (though you could use (++$i) to achieve that, if needed).
Unlike Array.IndexOf(), PowerShell's -eq operator is case-insensitive by default; for case-sensitivity, use -ceq instead.
It's easy to turn the above into a (simple) function (note that the parameters are purposely untyped, for flexibility):
function get-IndicesOf($Array, $Value) {
$i = 0
foreach ($el in $Array) {
if ($el -eq $Value) { $i }
++$i
}
}
# Sample call
PS C:\> get-IndicesOf ('A','D','B','D','C','E','D','F') 'D'
1
3
6
You would still need to loop with the static methods from [array] but if you are still curious something like this would work.
$b = "A","D","B","D","C","E","D","F"
$results = #()
$singleIndex = -1
Do{
$singleIndex = [array]::IndexOf($b,"D",$singleIndex + 1)
If($singleIndex -ge 0){$results += $singleIndex}
}While($singleIndex -ge 0)
$results
1
3
6
Loop until a match is not found. Assume the match at first by assigning the $singleIndex to -1 ( Which is what a non match would return). When a match is found add the index to a results array.

powershell, function calls fomr an array

So I am curious how to call a function from an array?
Function example:
function test($a, $b)
{
write-host $a
write-host $b
}
An array
$testArray = #{"test";"testA";"testB"}
And the whole bit I want to work is
$testArray[0] $testArray[1] $testArray[2]
Essentially mimic this
test "testA" "testB"
Reason for this is I have a few arrays like this and a loop that would go through each one using the custom function call on the data in each array.
Basically trying a different programing style.
Ok, it sounds like you have an array of arrays to be honest, so we'll go with that. Then let's reference this SO question which very closely resembles your question, and from that take away the whole [scriptblock]::create() thing, and splatting arrays. From that we can come up with this script:
function test($a, $b)
{
Write-Host "Function 'test'"
write-host $a
write-host $b
}
function test2($a, $b)
{
Write-Host "Function 'test2'"
write-host $b
write-host $a
}
$TestArray = #() #the correct way to create an array, instead of a broken HashTable
$testArray = #("test","testA","testB"),#("test2","testC","testD")
ForEach($Test in $TestArray){
$script = [scriptblock]::Create($test[0]+" #Args")
$script.Invoke($test[1..$test.count])
}
If all you have is one array, and not an array of arrays then I guess this is pretty simple. You could do:
$testArray = #("test","testA","testB")
$script = [scriptblock]::Create($testArray[0]+" #Args")
$script.Invoke($testArray[1..$testArray.count])
Edit (Capturing): Ok, to capture the results of a function you should be able to prefix it with $Variable = and be good to go, such as:
$MyResults = $script.Invoke($testArray[1..$testArray.count])
That will capture any output given by the function. Now, since the functions we have been working with only perform Write-Host they don't actually output anything at all, they just print text to the screen. For this I would modify the function a bit to get real output that's usable. In this example the function takes 2 parameters as input, and creates a new object with those 2 parameters assigned to it as properties. That object is the output.
function test($a, $b)
{
New-Object PSObject -Property #{Value1=$a;Value2=$b}
}
$testArray = #("test","testA","testB")
$script = [scriptblock]::Create($testArray[0]+" #Args")
$MyResults = $script.Invoke($testArray[1..$testArray.count])
Now if you ran that you would end up with the variable $MyResults being a PSCustomObject that has 2 properties named Value1 and Value2. Value1 would contain the string "testA" and Value2 would contain the string "testB". Any pair of strings passed to that function would output 1 object with 2 properties, Value1 and Value2. So after that you could call $MyResults.Value1 and it would return testA.

Resources