Recently, one of my build scripts failed with an odd error, claiming that the application wasn’t recognized. Given that the script had been working fine the previous day, this error was rather confusing.

Exception: The term 
'C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.10\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.11\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.2\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.6\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.8\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.9\tools\ReportGenerator.exe' 
is not recognized as the name of a cmdlet, function, script file, or operable 
program. Check the spelling of the name, or if a path was included, verify that 
the path is correct and try again.

Investigating the problem, I soon discovered the cause.

NuGet used to always store downloaded packages into a project specific .\packages\ folder. This folder only ever contained a single version of any one package, and my build scripts had previously only ever found one match when resolving wildcards.

This has all changed.

To avoid downloading the same package over and over again, and perhaps to save disk space too, NuGet now uses a cache of packages stored under the user profile. This means that there may now legitimately be multiple versions of a single package, catering for different projects needing different versions.

My build script failed because resolve-path call returned multiple results instead of just one.

PS> $packagesFolder = $env:userprofile\.nuget\packages
PS> resolve-path $packagesFolder\reportgenerator\*\tools\ReportGenerator.exe

Path
----
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.10\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.11\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.2\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.6\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.8\tools\ReportGenerator.exe
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.9\tools\ReportGenerator.exe

How do we select just one version to use?

My initial solution was something like this:

PS> $packagesFolder = $env:userprofile\.nuget\packages
PS> resolve-path $packagesFolder\reportgenerator\*\tools\ReportGenerator.exe | 
        sort-object | select-object -last 1
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.9\tools\ReportGenerator.exe

Unfortunately, this didn’t return the version I expected - the result was version 2.5.9, not 2.5.11.

What we need is a natural sort, one that respects numbers, not an alphabetical sort. Fortunately, a natural sort is fairly easy to do in PowerShell, by using a regular expression to expand all the numbers to a fixed width:

PS> $packagesFolder = $env:userprofile\.nuget\packages
PS> $toNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }
PS> resolve-path $packagesFolder\reportgenerator\*\tools\ReportGenerator.exe | 
        sort-object $toNatural | select-object -last 1
C:\Users\Bevan\.nuget\packages\reportgenerator\2.5.11\tools\ReportGenerator.exe

The variable $toNatural contains a PowerShell block, used obtain the sort key for each item as they are sorted.

The revised task to find ReportGenerator:

Task Requires.ReportGenerator {

    $packagesFolder = env:userprofile\.nuget\packages
    $toNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }
    $script:reportGeneratorExe =
        resolve-path $packagesFolder\reportgenerator\*\tools\ReportGenerator.exe | 
            sort-object $toNatural | select-object -last 1

    if ($reportGeneratorExe -eq $null)
    {
        throw "Failed to find ReportGenerator.exe"
    }

    Write-Host "Found Report Generator here: $reportGeneratorExe"
}

Now that this particular problem has been solved, I’m applying the same fix to all my other Require.* tasks so that I don’t get caught out in future.

Comments

blog comments powered by Disqus
Next Post
Sharpen The Saw #18  06 Nov 2017
Prior Post
Sharpen The Saw #17  30 Oct 2017
Related Posts
Coverage History with Psake  18 Nov 2017
Tracking time with Psake  11 Nov 2017
NuGet, .NET Core and Psake  28 Oct 2017
.NET Core with Psake  21 Oct 2017
Test Coverage Reporting with Psake  07 Oct 2017
Test Coverage with Psake  30 Sep 2017
NuGet packaging with Psake  23 Sep 2017
Semantic versioning with Psake  16 Sep 2017
Versioning with Psake  09 Sep 2017
Launch Scripts for Psake  02 Sep 2017
More powershell posts »
Related Pages
November 2017 archive