├── testfiles ├── 1.txt ├── 10.txt └── 2.txt ├── NaturalSort.ps1 └── README.md /testfiles/1.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testfiles/10.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testfiles/2.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /NaturalSort.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Sort-Naturally 3 | { 4 | PARAM( 5 | [Parameter(ValueFromPipeline=$true)] 6 | [System.Collections.ArrayList]$Array, 7 | [String]$Property, 8 | [switch]$Descending 9 | ) 10 | 11 | Add-Type -TypeDefinition @' 12 | using System; 13 | using System.Collections; 14 | using System.Collections.Generic; 15 | using System.Runtime.InteropServices; 16 | 17 | namespace NaturalSort { 18 | public static class NaturalSort 19 | { 20 | [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] 21 | public static extern int StrCmpLogicalW(string psz1, string psz2); 22 | 23 | public static System.Collections.ArrayList Sort(System.Collections.ArrayList foo) 24 | { 25 | foo.Sort(new NaturalStringComparer()); 26 | return foo; 27 | } 28 | } 29 | 30 | public class NaturalStringComparer : IComparer 31 | { 32 | public int Compare(object x, object y) 33 | { 34 | return NaturalSort.StrCmpLogicalW(x.ToString(), y.ToString()); 35 | } 36 | } 37 | } 38 | '@ 39 | if($Property) 40 | { 41 | $ArrayTmp = @{} 42 | foreach($obj in $Array) 43 | { 44 | $ArrayTmp.Add(("{0}_{1}" -f $obj.$Property, $obj.GetHashCode()), $obj) 45 | } 46 | $Keys = New-Object System.Collections.ArrayList 47 | $Keys.AddRange(@($ArrayTmp.Keys)) 48 | $Keys.Sort((New-Object NaturalSort.NaturalStringComparer)) 49 | $Array.Clear() 50 | foreach($k in $Keys) 51 | { 52 | $Array.Add($ArrayTmp[$k]) | Out-Null 53 | } 54 | } 55 | else 56 | { 57 | $Array.Sort((New-Object NaturalSort.NaturalStringComparer)) 58 | } 59 | if($Descending) 60 | { 61 | $Array.Reverse() 62 | } 63 | return $Array 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Sort? 3 | 4 | You may notice "sort" in Windows explorer is different from other programs. 5 | 6 | Windows explorer is using `natural sort`, which in many cases more like a `human`. 7 | 8 | ## Natural sort? 9 | 10 | Windows explorer uses a legacy API in `shlwapi.dll` named `StrCmpLogicalW` on sorting strings. 11 | 12 | ## I want use `Natural sort` in my powershell script. 13 | 14 | ### Sort string array 15 | ```powershell 16 | PS> # Load the function 17 | PS> . .\NaturalSort.ps1 18 | PS> 19 | PS> # Typical use 20 | PS> Sort-Naturally -Array @('2', '1', '11') 21 | 1 22 | 2 23 | 11 24 | PS> # Typical use, via pipeline 25 | PS> ,@('2', '1', '11') | Sort-Naturally 26 | 1 27 | 2 28 | 11 29 | PS> # For compare, if regular sort is used 30 | PS> @('2', '1', '11') | Sort-Object 31 | 1 32 | 11 33 | 2 34 | ``` 35 | 36 | ### Sort object array 37 | ```powershell 38 | PS> 39 | PS> # Not good 40 | PS> $t = ls .\testfiles\*.txt 41 | PS> $t | Sort-Object 42 | 1.txt 43 | 10.txt 44 | 2.txt 45 | PS> 46 | PS> # Good result 47 | PS> Sort-Naturally -Array $t 48 | 1.txt 49 | 2.txt 50 | 10.txt 51 | PS> ,$t | Sort-Naturally 52 | 1.txt 53 | 2.txt 54 | 10.txt 55 | ``` 56 | 57 | ### Sort object array by member property 58 | ```powershell 59 | PS> 60 | PS> # Sort by a selected property 61 | PS> $t = @() 62 | PS> $t += New-Object PSObject -Property @{Name = "1"; ID = "2"} 63 | PS> $t += New-Object PSObject -Property @{Name = "10"; ID = "1"} 64 | PS> $t += New-Object PSObject -Property @{Name = "2"; ID = "10"} 65 | PS> $t 66 | ID Name 67 | -- ---- 68 | 2 1 69 | 1 10 70 | 10 2 71 | PS> ,$t | Sort-Naturally -Property ID 72 | ID Name 73 | -- ---- 74 | 1 10 75 | 2 1 76 | 10 2 77 | PS> ,$t | Sort-Naturally -Property Name 78 | ID Name 79 | -- ---- 80 | 2 1 81 | 10 2 82 | 1 10 83 | PS> $tt = ls .\testfiles 84 | PS> ,$tt | Sort-Naturally -Property Name -Descending 85 | Name 86 | ---- 87 | 10.txt 88 | 2.txt 89 | 1.txt 90 | PS> 91 | PS> 92 | PS> 93 | ``` 94 | --------------------------------------------------------------------------------