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