Public/Add-HDTBootImageStartCommand.ps1

function Add-HDTBootImageStartCommand {
    <#
        .SYNOPSIS
            Declares a command the booted image runs before the deployment,
            leaving every other line of workspace.yaml byte-identical.
 
        .DESCRIPTION
            The command an administrator types to make a tool RUN in WinPE, and
            the one anything with a Start This button has to run.
 
            THIS IS THE OTHER HALF OF Add-HDTBootImageContent. Copying BGInfo or
            a VNC server into the image starts nothing: WinPE runs startnet.cmd,
            which brings the network up and then launches the deployment. Content
            that is never launched looks exactly like an image that was never
            changed, and that is the failure this exists to prevent.
 
            WHERE IT RUNS IS THE POINT. Every command declared here is written
            into startnet.cmd AFTER wpeinit, so a tool that needs a network has
            one, and BEFORE the entry command, because the entry command is the
            deployment and it does not return. Anything queued after it would
            never start.
 
            THE ORDER IS AN INSTRUCTION, NOT LAYOUT, which is why -First exists
            rather than a plain append. cmd.exe runs these one after another and
            waits for each; a tool that has to stay up while the deployment runs
            is launched with `start`, and that decision belongs to the
            administrator who knows the tool rather than to this command.
 
            THE DEFAULT IS THE END, the one position that cannot change when
            anything already declared runs.
 
            IT SPLICES LINES AND NEVER PARSES AND RE-EMITS, and it builds the
            bootImage block when there is none - which is the usual case, because
            New-HDTWorkspace deliberately writes no boot image settings at all.
 
            IT RETURNS LINES AND WRITES NOTHING. Save-HDTWorkspaceDocument is what
            touches the share.
 
        .PARAMETER Line
            The document, already split into lines.
 
        .PARAMETER Command
            The command line, exactly as cmd.exe should see it. Paths inside the
            image are on X:, which is the WinPE RAM disk and the one drive letter
            WinPE guarantees.
 
        .PARAMETER First
            Run this before everything already declared.
 
        .INPUTS
            None. This command does not accept pipeline input.
 
        .OUTPUTS
            System.String[] - the document with the command added.
 
        .EXAMPLE
            Add-HDTBootImageStartCommand -Line $line -Command 'X:\HDT\Tools\BGInfo\bginfo.exe X:\HDT\Tools\BGInfo\hdt.bgi /timer:0 /nolicprompt'
 
            The machine's own details on the WinPE desktop, before the
            deployment starts.
 
        .EXAMPLE
            Add-HDTBootImageStartCommand -Line $line -Command 'start "" X:\HDT\Tools\VNC\winvnc.exe' -First
 
            A VNC server, started first and left running - `start` because
            cmd.exe waits for anything else.
 
        .LINK
            Add-HDTBootImageContent
 
        .LINK
            Remove-HDTBootImageStartCommand
    #>

    [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()]
        [switch] $First
    )

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

    if ([string]::IsNullOrWhiteSpace($Command)) {
        $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Command `
                    -Message 'a start command names something to run. Give the command line exactly as cmd.exe should see it.'))
    }

    if ($Command.IndexOfAny([char[]] @("`r", "`n")) -ge 0) {
        $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Command `
                    -Message 'a start command is one command on one line. A line break here becomes a second command inside startnet.cmd that nobody reading the workspace document would see.'))
    }

    $workspace = ConvertFrom-HDTWorkspaceLine -Line $Line

    foreach ($current in @($workspace.BootImage.StartCommand)) {
        if ([string] $current -eq $Command) {
            $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Command `
                        -Message ("this boot image already runs '{0}'. Running the same command twice is not a thing to ask for by accident." -f $Command)))
        }
    }

    $action = 'Run in WinPE, after the commands already declared'
    if ($First) { $action = 'Run in WinPE, before the commands already declared' }

    if (-not $PSCmdlet.ShouldProcess($Command, $action)) {
        return [string[]] @($Line)
    }

    $text = [string[]] @('- {0}' -f (ConvertTo-HDTRuleScalarText -Value $Command))

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

    if ($null -ne $block) {
        $result = [string[]] @(Add-HDTWorkspaceItem -Line $Line -Block $block -Text $text -First:$First)
    } else {
        $written = New-Object -TypeName System.Collections.ArrayList
        [void] $written.Add('startCommand:')
        foreach ($current in $text) { [void] $written.Add(' ' + $current) }

        $result = [string[]] @(Set-HDTWorkspaceKey -Line $Line -Path @('bootImage', 'startCommand') `
                -Text ([string[]] @($written)))
    }

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

    return [string[]] $result
}