I have a list of strings containing the names of scripts. I need to check the versions of these scripts in order to figure out whether they have a version lower than the latest executed version and to only execute those scripts with a version number higher than the latest version, in order of the script sequence for that version. I also have a list of strings for the already executed scripts, so in order to determine the latest script, that list needs to be sorted.
Thus, I wrote some code to parse the version details from these strings and wanted to sort both lists with the same "function" - but I can't get the sorting to work. The Sort-Object
part works when I call it directly, but not with the function Sort-Scripts
. I am new to PowerShell, so I must be overlooking something fundamental. But I can't figure out what.
function Get-SemVer($version) {
$version -match '^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?(\-(?<pre>[0-9A-Za-z\-\.]+))?(\+(?<build>[0-9A-Za-z\-\.]+))?$' | Out-Null
if ($matches) {
[PSCustomObject]@{
Major = [int]$matches['major']
Minor = [int]$matches['minor']
Patch = [int]$matches['patch']
PreReleaseLabel = [string]$matches['pre']
BuildLabel = [string]$matches['build']
}
}
}
function ParseVersionDetails {
param([string]$InputString)
if ($InputString -match '^(?<version>.*?)_(?<sequence>\d+)_?(?<scriptname>.*)') {
[PSCustomObject]@{
Version = Get-SemVer $matches['version']
Sequence = [int]$matches['sequence']
ScriptName = $matches['scriptname']
FullName = $InputString
}
}
}
function Sort-Scripts {
process {
@($Input) | Sort-Object -Property @{Expression={$_.Version.Major}}, @{Expression={$_.Version.Minor}}, @{Expression={$_.Version.Patch}}, @{Expression={$_.Version.PreReleaseLabel}}, @{Expression={$_.Version.BuildLabel}}, @{Expression={$_.Sequence}}
}
}
$list = @(
"7.9.0-dev-IRIS-Dyn_02_SomeScript",
"7.9.0-dev-IRIS-Dyn_01_SomeScript",
"7.9.1-prod-IRIS-Dyn+13_01_OtherScript",
"7.8.5_02_AnotherScript",
"7.8.5_01_AnotherScript"
)
"#### Doesn't work"
$list | ForEach-Object { ParseVersionDetails -InputString $_ } | Sort-Scripts
"#### Works"
$list | ForEach-Object { ParseVersionDetails -InputString $_ } | Sort-Object -Property @{Expression={$_.Version.Major}}, @{Expression={$_.Version.Minor}}, @{Expression={$_.Version.Patch}}, @{Expression={$_.Version.PreReleaseLabel}}, @{Expression={$_.Version.BuildLabel}}, @{Expression={$_.Sequence}}
"Done"
The
Sort-Object
part works when I call it directly, but not with the functionSort-Scripts
You must pass all pipeline input to a single Sort-Object
call, so do not use a process
block:
function Sort-Scripts {
# *No* process block; no need for @(...)
$input | Sort-Object -Property { $_.Version.Major }, { $_.Version.Minor }, { $_.Version.Patch }, { $_.Version.PreReleaseLabel }, { $_.Version.BuildLabel }, Sequence
}
Note:
Not using a process
block makes the function body run once, after all pipeline input has been received and collected in $input
(see below); that is, it runs implicitly as if it were enclosed in an end
block.
Note that collecting all pipeline input first can be problematic with large input sets or when true streaming processing is required, but that isn't a problem in your use case, given that Sort-Object
of technical necessity too must collect all its input first.
If streaming processing is required, you'll have to write a so-called proxy function that uses a steppable pipeline.
In cases where you do need a process
block, it is better to use the automatic $_
variable rather than the automatic $input
variable; the latter is an enumerator and meant to be used to represent the collected-up-front pipeline input.
Also, as Mathias notes, and as reflected above, you don't need full-fledged calculated properties (e.g. @{ Expression={ $_.Version.Major } }
) to pass dynamic sort criteria to Sort-Object
- a script block alone will do (e.g. { $_.Version.Major }
).