Troy
Troy

Reputation: 11

Compare-Object Returning Invalid Results

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

Answers (5)

Troy
Troy

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

js2010
js2010

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

Mathias R. Jessen
Mathias R. Jessen

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

rokumaru
rokumaru

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

HAL9256
HAL9256

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

Related Questions