3

When I run the following code:

'Windows Embedded Standard',
'Windows 7 Enterprise',
'Windows XP Professional',
'Windows Server 2003',
'Windows 7 Entreprise',
'',
'Windows 7 Professionnel',
'Windows 7 Professional',
'Windows 10 Enterprise',
'Windows Server 2008 R2 Standard',
'Windows Server 2012 Standard',
'Windows Server 2012 R2 Standard',
'unknown',
'Windows Server 2008 R2 Enterprise' | Sort-Object

It sorts as following:

unknown
Windows 10 Enterprise
Windows 7 Enterprise
Windows 7 Professional
Windows 7 Professionnel
Windows Embedded Standard
Windows Server 2003
Windows Server 2008 R2 Enterprise
Windows Server 2008 R2 Standard
Windows Server 2012 R2 Standard
Windows Server 2012 Standard
Windows XP Professional
Windows 7 Entreprise

I can't figure out why the last entry Windows 7 Entreprise is not correctly sorted. It should come in the 4th position. What am I missing here?

UPDATE

Thanks to the comments it became clear that the source data seems to be the problem:

$Computers = Get-ADComputer -SearchBase 'OU=EU,DC=domain,DC=net' -Filter * -Properties OperatingSystem
$Computers | Group-Object OperatingSystem | Sort-Object Name | Select-Object Count, Name | Format-Table -AutoSize

ANSI Values:

$Computers | Group-Object OperatingSystem | ForEach-Object{$_.Name;[int[]][char[]]$_.Name -join "|"}
Windows Server 2008 R2 Standard
87|105|110|100|111|119|115|32|83|101|114|118|101|114|32|50|48|48|56|32|82|50|32|83|116|97|110|100|97|114|100
Windows Server 2003
87|105|110|100|111|119|115|32|83|101|114|118|101|114|32|50|48|48|51
Windows Server 2012 Standard
87|105|110|100|111|119|115|32|83|101|114|118|101|114|32|50|48|49|50|32|83|116|97|110|100|97|114|100


Windows Server 2012 R2 Standard
87|105|110|100|111|119|115|32|83|101|114|118|101|114|32|50|48|49|50|32|82|50|32|83|116|97|110|100|97|114|100
unknown
117|110|107|110|111|119|110
Windows Server 2008 R2 Enterprise
87|105|110|100|111|119|115|32|83|101|114|118|101|114|32|50|48|48|56|32|82|50|32|69|110|116|101|114|112|114|105|115|101
Windows 7 Enterprise
87|105|110|100|111|119|115|32|55|32|69|110|116|101|114|112|114|105|115|101
Windows 7 Entreprise
87|105|110|100|111|119|115|160|55|32|69|110|116|114|101|112|114|105|115|101
Windows 7 Professionnel
87|105|110|100|111|119|115|32|55|32|80|114|111|102|101|115|115|105|111|110|110|101|108
Windows XP Professional
87|105|110|100|111|119|115|32|88|80|32|80|114|111|102|101|115|115|105|111|110|97|108
Windows 7 Professional
87|105|110|100|111|119|115|32|55|32|80|114|111|102|101|115|115|105|111|110|97|108
Windows 10 Enterprise
87|105|110|100|111|119|115|32|49|48|32|69|110|116|101|114|112|114|105|115|101
Windows Embedded Standard
87|105|110|100|111|119|115|32|69|109|98|101|100|100|101|100|32|83|116|97|110|100|97|114|100

Is there a way to encode it differently so PowerShell can sort this correctly?

9
  • I've just ran that like for like locally and it sorts correctly. What version of powershell are you using? Commented Mar 15, 2016 at 9:31
  • PSVersion 4.0 on a Windows Server 2012. Commented Mar 15, 2016 at 9:33
  • Sorting correctly on my Server 2012 with PS 4 too. Commented Mar 15, 2016 at 9:34
  • 1
    Maybe you've got a problem with your source data? Some invisible unicode character perhaps? Can you check the script in a hex editor? Commented Mar 15, 2016 at 9:35
  • Luaan, you're on to something. When I copy the code from the website here it does run correctly. But not when I copy it from the source data, which is Get-ADComputer -SearchBase 'OU=EU,DC=domain,DC=net' -Filter * -Properties OperatingSystem Commented Mar 15, 2016 at 9:38

4 Answers 4

3
+50

Use the String.Normalize() function with Normalization Form KC or KD (documented here):

$Computers = Get-ADComputer -SearchBase 'OU=EU,DC=domain,DC=net' -Filter * -Properties OperatingSystem
$OSList = $Computers | Select-Object -ExpandProperty OperatingSystem | ForEach-Object {
    $_.ToString().Normalize([Text.NormalizationForm]::FormKC)
}

Note that the Normalize function will remote diacritics such as accents and graves. For example, á will beome a. In order to convert character 160 to character 32, I had to use one of the normalization forms that used "full compatibility decomposition," which is either the KC or KD form.

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

2 Comments

Learned something new. Wonderful job!
Thank you very much @JamesQMurphy, this was exactly what I was looking for. Didn't know this method yet. What I used in the end was $Computers | Group-Object OperatingSystem | Select-Object @{N='OS';E={$_.Name.ToString().Normalize([Text.NormalizationForm]::FormKC)}},Count | Sort-Object OS.
1

So I have a solution to this issue but it is more hacking then I would like. You have French OSes in your environment. This is obvious based on the spelling of some of your systems. Depending on what you are doing with this data you are going to have to massage this anyway if you need to group by OS since the spelling is different between languages.

Looked at the Ansi codes the space you are seeing on the offending item is 160 which is higher than the natural space which is 32.

That being said you could just Sort on a custom property to get the result you are looking for.

| Sort-Object {$_ -replace [char]160," "}

Even if the character displayed correctly on the screen it would not be sorted how you want. I think this solution is required given the data presented. I don't have any experience with multiple language environment to provide and other ideas just yet.

1 Comment

Thanks Matt for the workaround. In the end I think @JamesQMurphy his answer is better in this case so I used that one.
1

Interesting question, sorting strings with numbers is always tricky because the ASCII/ordinal values will have "1" less then "7", so it will compare "Windows 10" as less than "Windows 7".

Searching for "PowerShell custom sorting" led me to an answer on this page: custom sorting in powershell.

I used that approach as a basis to write the following solution.

Code

$items = @(
'Windows Embedded Standard',
'Windows 7 Enterprise',
'Windows XP Professional',
'Windows Server 2003',
'Windows 7 Entreprise',
'',
'Windows 7 Professionnel',
'Windows 7 Professional',
'Windows 10 Enterprise',
'Windows Server 2008 R2 Standard',
'Windows Server 2012 Standard',
'Windows Server 2012 R2 Standard',
'unknown',
'Windows Server 2008 R2 Enterprise')

# Define our search criteria
# match non-digits
$part1 = { if ($_ -match '(\D+)') { $matches[1] } }
# after part 1, match digits and cast to int so sorting works by number/value
$part2 = { if ($_ -match '\D+(\d+)') { [int]$matches[1] } }
# rest of string after part 2
$part3 = { if ($_ -match '\D+\d+(.*)') { $matches[1] } }

Write-Output "`nTest of our parts and how they parse a string."
# write values out surrounded by single quotes so we see exactly what is returned
'Windows 10 Enterprise' | % $part1 | % {Write-Output $("'" + $_ + "'")}
'Windows 10 Enterprise' | % $part2 | % {Write-Output $("'" + $_ + "'")}
'Windows 10 Enterprise' | % $part3 | % {Write-Output $("'" + $_ + "'")}

Write-Output "`nSorted by string (where 1 is less than 7, even if number is 10"
$items | Sort-Object | % {Write-Output $("'" + $_ + "'")}

Write-Output "`nSorted using our custom parsing rules!!!"
# write values out surrounded by single quotes so we see exactly what is returned
$items | Sort-Object $part1, $part2, $part3 | % {Write-Output $("'" + $_ + "'")}

Output:

Test of our parts and how they parse a string.
'Windows '
'10'
' Enterprise'

Sorted by string (where 1 is less than 7, even if number is 10
''
'unknown'
'Windows 10 Enterprise'
'Windows 7 Enterprise'
'Windows 7 Entreprise'
'Windows 7 Professional'
'Windows 7 Professionnel'
'Windows Embedded Standard'
'Windows Server 2003'
'Windows Server 2008 R2 Enterprise'
'Windows Server 2008 R2 Standard'
'Windows Server 2012 R2 Standard'
'Windows Server 2012 Standard'
'Windows XP Professional'

Sorted using our custom parsing rules!!!
''
'unknown'
'Windows 7 Enterprise'
'Windows 7 Entreprise'
'Windows 7 Professional'
'Windows 7 Professionnel'
'Windows 10 Enterprise'
'Windows Embedded Standard'
'Windows Server 2003'
'Windows Server 2008 R2 Enterprise'
'Windows Server 2008 R2 Standard'
'Windows Server 2012 R2 Standard'
'Windows Server 2012 Standard'
'Windows XP Professional'

1 Comment

Thanks for the sorting tips Kory, really appreciated.
0

-Properties parameter doesn't show only specified properties, but rather adds them to the standard set. So running Get-ADComputer -Filter "name -like '*'" -Properties OperatingSystem I don't get only OperatingSystem property as a result, but all of these: DistinguishedName, DNSHostName, Enabled, Name, ObjectClass, ObjectGUID, OperatingSystem, SamAccountName, SID, UserPrincipalName.

Now when you run something like this: Get-ADComputer -Filter "name -like '*'" -Properties OperatingSystem | sort | select OperatingSystem It means you sorted by DistinguishedName and then selected OperatingSystem.

Use | Sort-Object OperatingSystem and you'll be fine.

1 Comment

Thank you Adam, I know this. I've updated the OP to reflect the full code so you can see it better. I'm sorry for the confusion but it doesn't work. There should be some change of the string before Sort-Object can handle it properly..

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.