Home > Mobile >  Find a file name that holds more than one version
Find a file name that holds more than one version

Time:12-27

I wrote something that works well for most cases. I encountered with an edge case that two files with the same name (the different is the upper and lower case) and the sane version printed, actually the version is the same so I am not suppose to print them. I did a change and now it looks ok (The $MatcheddllFiles is empty) but I am afraid I'm missing something and might not cover all the cases. I ran it on files with an issue (some files with the name contains more than one version) and it worked. Am I missing something? I think that I am missing a case when the names are equal (with upper and lower case) but contains different versions (checked and its not working with the -CaseSensitive)

Assume I got the $dlls first

foreach ($dll in $dlls) {
        $MatcheddllFiles  = [PsCustomObject]@{
            DllName = $dll.BaseName 
            Version = ($dll.VersionInfo.FileVersion).Replace(',', '.')
          }
       }

In case $dlls contains two dlls with the same version with names:

dataprivilege.configuration 
DataPrivilege.Configuration

This will be the result:

$differentDllVersions = $MatcheddllFiles  |
  Select-Object -Unique -Property DllName, Version |
    Group-Object  DllName  |
      Where-Object { $_.Count -gt 1 } |
        ForEach-Object { $_.Group }
$differentDllVersions
DllName                     Version   
-------                     -------   
dataprivilege.configuration 8.6.24.491
DataPrivilege.Configuration 8.6.24.491

My change is to add -CaseSensitive

$differentDllVersions = $MatcheddllFiles  |
  Select-Object -Unique -Property DllName, Version |
    Group-Object  DllName -CaseSensitive  |
      Where-Object { $_.Count -gt 1 } |
        ForEach-Object { $_.Group }
$differentDllVersions

Update: It looks like this was solve it. I replaced the Select-Object with Sort-Object, make sense?

$differentDllVersions = $MatcheddllFiles  |
  Sort-Object -Unique -Property DllName, Version |
   Group-Object  DllName  |
    Where-Object { $_.Count -gt 1 } |
     ForEach-Object { $_.Group }

CodePudding user response:

As you have discovered yourself, you ran into a long-standing Select-Object bug, where
-Unique is unexpectedly and invariably case-sensitive, as of PowerShell 7.3.1.

  • See GitHub issue #12059; a simple repro:

    # !! Outputs BOTH inputs, even though 'foo'
    # !! is just a case variation of 'FOO'.
    'foo', 'FOO' | Select-Object -Unique
    
  • The expected behavior that would be consistent with other cmdlets, such as Sort-Object, Group-Object and Select-Object is:

    • Case-insensitivity by default.
    • Case-sensitivity as an opt-in, via a -CaseSensitive switch.

Workarounds:

  • If you don't need to maintain the input order, instead using Sort-Object -Unique, which does behave as expected, is a viable solution, as you've discovered.

    • Oddly, Sort-Object -Unique even appears to be faster than Select-Object -Unique, even though it shouldn't be - see GitHub issue #7707.
  • If you do want to maintain the input order, use Select-Object -Unique with a calculated property, where you can case-normalize the input values.

Here's a simplified example:

@(
  [pscustomobject] @{ DllName = 'FOO'; Version = '1.2.3.4' }
  [pscustomobject] @{ DllName = 'foo'; Version = '1.2.3.4' }
) |
  Select-Object -Unique -Property @{ 
      Name='DllName'; Expression={ $_.DllName.ToLower() }
    },
    Version

Output:

DllName Version
------- -------
foo     1.2.3.4

Note how the two input objects resulted in only one output object, because the lowercased version of the .DllName property values (as well as the .Version values) matched.

However, the output objects' .DllName property will invariably contain the lowercased value.


By contrast, Sort-Object -Unique preserves the original case (and by definition sorts by the specified properties); in PowerShell (Core) 7 only, you can add -Stable to predictably use the first among the duplicates:

@(
  [pscustomobject] @{ DllName = 'FOO'; Version = '1.2.3.4' }
  [pscustomobject] @{ DllName = 'foo'; Version = '1.2.3.4' }
) | 
  Sort-Object -Unique DllName, Version # PSv7 : add -Stable, if needed.

Output:

DllName Version
------- -------
FOO     1.2.3.4
  • Related