Reputation: 11
First post to stackoverflow so apologies in advance if I miss a social norm.
I have an array in PowerShell that holds thousands of objects in it. Standard stuff with each object having properties such as username, login times, computer they logged into, etc. I then split off a small number of objects from the first array with users whose name starts with A:
$Array2 = $Array1 | where-object Username -like '*\a*'
This works as expected.
I want to build a third array that only contains the objects from $Array1 that are not in $Array2. Or in other words an array with only users whose usernames do not start with A. This seems like the way to go:
$Array3 = compare-object $Array1 $Array2 | select-object -expandproperty InputObject
Expectation:
$Array3 | where-object Username -like '*\a*'
results in a nothing returned
What I'm getting though are plenty of results. Strangely if I look at the .count of each array the math works out. Array1 minus Array2 equals Array3. So it is removing the correct number of objects, just not the expected objects. What am I doing wrong here? Since I'm doing a pull from the exact array I'm comparing against I can't think of any other criteria it would be searching for that would return an unexpected match. I also tried sorting each array by the same property before comparing because I was grasping but as expected that didn't fix it.
Thanks in advance!
Upvotes: 1
Views: 717
Reputation: 11
My conclusion is that some commandlets build arrays in such a way that it breaks compare-object. I'm still unsure as to what this mysterious "way" is, but the problem presents itself when using Citrix's Get-BrokerSession and does not when using Get-AdUser.
Long answer For those really wanting to use compare-object here is the way I got it to work:
compare-object $Array1 $Array2 -Property Username -PassThru
With the -PassThru being the eureka moment for me.
My question was specifically about how/why compare-object wasn't working the way I expected. All answers, to my interpretation, only provided different ways to work around the problem and didn't actually answer the question. Special thanks to @js2010 for continuing to work with me through it.
Upvotes: 0
Reputation: 27606
Compare object works in a funny way. With objects, you usually need to specify the properties. I'm surprised the docs don't have an example of this. Unfortunately, -property doesn't take wildcards. I think without "-property", unless there's a special compareto() or equals() method, it will compare the string versions of the objects. I think with get-aduser output, it will compare strings (distinguishedname's).
$a = @([pscustomobject]@{name='joe'})
$b = @([pscustomobject]@{name='joey'})
compare-object $a $b # no output, they're equal!
compare-object $a $b -property name
name SideIndicator
---- -------------
joey =>
joe <=
Trying out datetime's. It uses something besides strings to compare.
$a = get-date; sleep -Milli 500; $b = get-date
$a.tostring(); $b.tostring()
4/23/2020 6:33:56 PM
4/23/2020 6:33:56 PM
$a -eq $b
False
compare-object $a $b
InputObject SideIndicator
----------- -------------
4/23/2020 6:33:56 PM =>
4/23/2020 6:33:56 PM <=
Upvotes: 2
Reputation: 174920
You can solve this by negating the Where-Object
operand (-notlike
instead of -like
):
$Array2 = $Array1 |Where-Object Username -like *\a*
$Array3 = $Array1 |Where-Object Username -notlike *\a*
... or, you can take advantage of the .Where()
extension method's "Split" mode, and do it in a single assignment:
$Array2,$Array3 = $Array1.Where({$_.Username -like '*\a*'}, 'Split')
Upvotes: 2
Reputation: 1244
I think Compare-Object
internally uses the ToString()
method to compare objects.
class foo {
[string]$str
foo($str) {
$this.str = $str
}
}
class bar {
[string]$str
bar($str) {
$this.str = $str
}
[string] ToString() { return $this.str }
}
"== Compare foo object =="
$fooA = [foo]::new("a")
$fooB = [foo]::new("b")
compare $fooA $fooB -IncludeEqual | ft
"== Compare bar object =="
$barA = [bar]::new("a")
$barB = [bar]::new("b")
compare $barA $barB -IncludeEqual | ft
output:
== Compare foo object ==
InputObject SideIndicator
----------- -------------
foo ==
== Compare bar object ==
InputObject SideIndicator
----------- -------------
b =>
a <=
Upvotes: 0
Reputation: 13513
The simple way is to do the same -like
assignment but with the opposite operator -notlike
e.g.:
$Array2 = $Array1 | where-object Username -like 'a*'
$Array3 = $Array1 | where-object Username -notlike 'a*'
Note:
(not sure if it was a typo in the question for the match: '*\a*'
), The match should be a*
with only a single asterix after the "a
" character. You want to match everything starting with "a
" and "*
" for anything after (e.g. only match "Alex", and not "Brad").
Upvotes: 2