Reputation: 1712
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"
Upvotes: 1
Views: 61
Reputation: 439777
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 }
).
Upvotes: 3
Reputation: 30183
Well-documented in about_automatic_variables
:
$input
Contains an enumerator that enumerates all input that's passed to a function. The
$input
variable is available only to functions, script blocks (which are unnamed functions), and script files (which are saved script blocks).
In a function without a
begin
,process
, or endblock
, the$input
variable enumerates the collection of all input to the function.In the
begin
block, the$input
variable contains no data.In the
process
block, the$input
variable contains the current object in the pipeline.In the
end
block, the$input
variable enumerates the collection of all input to the function.
Follow sirtao's comment: just remove the process{}
block from Sort-Scripts
, or replace it with end{}
.
Upvotes: 0
Reputation: 2880
Powershell has a Type for Semantic Versioning: [semver]
(and [version]
for regular(?) versioning)
And you do not need a whole function to sort, you can just prepare a splat with the parameters for Sort-Object
# Evaluate a name with Approved Verbs
function ParseVersionDetails {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, Mandatory)]
[string]$InputString
)
process {
# Check if there was a match
if ($InputString -match '^(?<version>.*?)_(?<sequence>\d+)_?(?<scriptname>.*)') {
# will valorize $version with $false of the parsing fails.
# otherwise it will valorize it with the parsed [semver] version number.
# [ref] requires the variable being created in advance, no matter if with just $null
$version = $null
# suppressing the boolean output.
$null = [semver]::TryParse($matches['version'], [ref]$version)
[PSCustomObject]@{
Version = $version
Sequence = [int]$matches['sequence']
ScriptName = $matches['scriptname']
FullName = $InputString
}
}
}
}
# Splat ariable for Sort-Object .
$SortParameters = @{
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',
'Made.to.fail'
)
$list | ParseVersionDetails | Sort-Object @SortParameters
'Done'
Upvotes: 0