1

I am collecting some user info from AD and trying to sort it, then remove duplicates, but I can't seem to get sort -unique to work:

foreach ($pute in $puters){
    $user = get-loggedinuser -computer $pute.name | select -expandproperty username
    $fullname = get-aduser $user | select -expandproperty name
    $fullnames += $fullname
}
$fullnames = $fullnames | sort -unique

What this gives me is something like:
Adam Warlock
Adam Warlock
Bruce Biggalow
Frank Coutel
Frank Coutel
Tony Shaloub

Where the output is sorted, but the duplicates remain.

4
  • What is get-loggedinuser? Commented Nov 24, 2021 at 20:42
  • 1
    what type is $fullnames? it might be viewed as a normal string. Try defining it as [string[]] or a collection Commented Nov 24, 2021 at 21:35
  • Get-Loggedinuser is a function written by Jiten (community.spiceworks.com/people/jitensh). I'll try redefining $fullnames as a string and comment back. Commented Nov 29, 2021 at 13:32
  • edited code to resemble [string[]] fullnames += $fullname, but the results were the same. Commented Nov 29, 2021 at 15:55

1 Answer 1

1

Update: The problem turned out to be the lack of initialization of $fullnames as an array, with $fullnames = @() (in the absence of this initialization, += perform simple string concatenation instead of appending an element to an array, conceptually speaking).

The answer below may still be of interest for diagnosing how identical-looking strings may differ.


Since your Sort-Object inputs are strings (and the name-per-line output implies that $fullnames is indeed an array, even though you don't show its initialization as such), the implication is that the ostensible duplicates must differ by whitespace (type) or invisible control characters.

You can try the following technique to discover such invisible discrepancies:

'Adam Warlock',
'Adam Warlock ',  # trailing space
'Frank Coutel',
('Frank{0}Coutel' -f [char] 0xa0) | # no-break space instead of regular space
  ForEach-Object { $_ + "`t" + [int[]] [char[]] $_ } |
    Sort-Object -Unique

Output:

Adam Warlock    65 100 97 109 32 87 97 114 108 111 99 107
Adam Warlock    65 100 97 109 32 87 97 114 108 111 99 107 32 # Note the extra 32
Frank Coutel    70 114 97 110 107 160 67 111 117 116 101 108 # Note the 160
Frank Coutel    70 114 97 110 107 32 67 111 117 116 101 108

The numbers are the Unicode code points of the characters that make up each string, in decimal format.


Alternatively, for a better visualization of the differences, use helper function Debug-String from this Gist:

  • Note: The following snippet downloads and defines the function automatically. I can personally assure you that doing so is safe, but you you should always check the source code yourself first.
# Download and define helper function Debug-String
#  * See comments above.
#  * To see instructions on how to make the function available in 
#    *future* sessions, remove >3 $null and re-run this command directly
#    from the command line.
irm https://gist.github.com/mklement0/7f2f1e13ac9c2afaf0a0906d08b392d1/raw/Debug-String.ps1 | iex 3>$null

'Adam Warlock',
'Adam Warlock ',  # trailing space
'Frank Coutel',
('Frank{0}Coutel' -f [char] 0xa0) | # no-break space instead of regular space
  Debug-String |
    Sort-Object -Unique

Output:

Adam·Warlock
Adam·Warlock·      # Note the trailing · 
Frank·Coutel
Frank`u{a0}Coutel  # Note the `u{a0} escape sequence

Debug-String visualizes regular spaces as ·, and formatting/control characters outside the ASCII range as Unicode escape sequences, e.g. `u{a0}, where a0 is the hex form of the character's Unicode code point, namely of the no-break space ( U+00A0) character, in this case.

Sign up to request clarification or add additional context in comments.

4 Comments

Implemented debug-string and compared the output. the portions of the list that match, like Adam Warlock in the example, still match with the hidden formatting/control characters exposed. E.g. Adam Warlock now shows up as Adam·ÂWarlock, and all copies match.
@SpaceCoyote300, A much simpler way to collect all names is to use the foreach loop as an expression: $fullnames = foreach ($pute in $puters) { $user = get-loggedinuser -computer $pute.name | select -expandproperty username; get-aduser $user | select -expandproperty name }, which also ensures that $fullnames receives an array of names (assuming two or more output objects).
Also, the  character you quote as Debug-String output would indicate that UTF-8 encoded names are being misinterpreted as ANSI strings, and I wouldn't know how that could happen with Get-ADUser output - unless the names are truly stored in this misinterpreted form in AD.
You mentioned that I wasn't declaring the arrays ahead of initialization, which was the problem. I added declarations for my arrays and they started working properly. Thank you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.