Public/Move-HDTBootImageStartCommand.ps1

function Move-HDTBootImageStartCommand {
    <#
        .SYNOPSIS
            Moves a start command one place up or down the list.
 
        .DESCRIPTION
            ORDER IS SEMANTICS IN THIS LIST. startnet.cmd runs these lines in
            the order they are written and cmd.exe runs them synchronously, so
            a tool that has to be up before the next line runs has to be ABOVE
            it. Until this existed the whole vocabulary was Add - which appends
            or takes -First - and reordering meant remove and re-add: three
            presses to move one row, and the row's place lost entirely if the
            second half failed.
 
            IT MOVES ONE PLACE, NOT TO A POSITION. That is what an up arrow
            beside a list means, and it composes: pressing it three times moves
            a row three places. A -Position parameter would need the caller to
            know the current index, which is the thing the list is already
            showing them.
 
            AT THE END IT DOES NOTHING, AND THAT IS NOT AN ERROR. The button is
            pressed by somebody holding it to walk a row up several places, and
            refusing at the top would put a message on screen for a press that
            simply had nowhere to go. A command the document does not run IS an
            error, because that is a caller working from a stale list.
 
            THE WHOLE ENTRY MOVES, comment and all. Get-HDTWorkspaceItem reports
            the first and last line each entry owns, so a note an administrator
            wrote under a command travels with the command rather than being
            left behind pointing at whatever moved into its place.
 
        .PARAMETER Line
            The workspace.yaml lines to edit. Returned spliced, with every line
            this command was not asked to change byte-identical.
 
        .PARAMETER Command
            The start command to move, exactly as the document holds it.
 
        .PARAMETER Direction
            Up or Down, one place.
 
        .INPUTS
            None. This command does not accept pipeline input.
 
        .OUTPUTS
            System.String[] - the workspace.yaml lines, spliced.
 
        .EXAMPLE
            $line = Move-HDTBootImageStartCommand -Line $line -Command 'X:\Tools\run.cmd' -Direction Up
 
        .EXAMPLE
            $line = Move-HDTBootImageStartCommand -Line $line -Command 'wpeutil disablefirewall' -Direction Up
            $line = Move-HDTBootImageStartCommand -Line $line -Command 'wpeutil disablefirewall' -Direction Up
 
            Two places up. Pressing an arrow twice is what this composes to.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    [OutputType([string[]])]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]] $Line,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [string] $Command,

        [Parameter(Mandatory = $true, Position = 2)]
        [ValidateSet('Up', 'Down')]
        [string] $Direction
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'

    $workspace = ConvertFrom-HDTWorkspaceLine -Line $Line
    $declared = @($workspace.BootImage.StartCommand)

    $at = [array]::IndexOf([string[]] @($declared), [string] $Command)

    if ($at -lt 0) {
        $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Command -Category ObjectNotFound `
                    -Message ("this boot image does not run '{0}', so there is nothing to move. It runs: {1}" -f
                        $Command, (@($declared) -join ' | '))))
    }

    $other = $at - 1
    if ($Direction -eq 'Down') { $other = $at + 1 }

    # Already where it was asked to go. See the header: a press with nowhere to
    # go is a press, not a mistake.
    if ($other -lt 0 -or $other -ge @($declared).Count) { return [string[]] @($Line) }

    if (-not $PSCmdlet.ShouldProcess($Command, ('Run this one place {0} the list' -f $Direction.ToLowerInvariant()))) {
        return [string[]] @($Line)
    }

    $block = Get-HDTWorkspaceKey -Line $Line -Path @('bootImage', 'startCommand')
    $entry = @(Get-HDTWorkspaceItem -Line $Line -Block $block)

    # In document order, whichever way the move was asked for: the splice below
    # walks the file forwards and has to meet the earlier one first.
    $first = $entry[$at]
    $second = $entry[$other]

    if ($first.Index -gt $second.Index) {
        $first = $entry[$other]
        $second = $entry[$at]
    }

    $result = New-Object -TypeName System.Collections.ArrayList
    $index = 0

    while ($index -lt @($Line).Count) {

        if ($index -ne $first.Index) {
            [void] $result.Add([string] $Line[$index])
            $index++
            continue
        }

        # The later entry, then whatever sits between the two, then the earlier
        # one. Anything between them - a blank line, a comment about neither -
        # is still between them afterwards.
        for ($take = $second.Index; $take -le $second.End; $take++) {
            [void] $result.Add([string] $Line[$take])
        }

        for ($take = $first.End + 1; $take -lt $second.Index; $take++) {
            [void] $result.Add([string] $Line[$take])
        }

        for ($take = $first.Index; $take -le $first.End; $take++) {
            [void] $result.Add([string] $Line[$take])
        }

        $index = $second.End + 1
    }

    try {
        [void] (ConvertFrom-HDTWorkspaceLine -Line ([string[]] @($result)))
    } catch {
        $PSCmdlet.ThrowTerminatingError($_)
    }

    return [string[]] @($result)
}