Private/Get-HDTPowerCommand.ps1

function Get-HDTPowerCommand {
    <#
        .SYNOPSIS
            Decides which executable ends the machine, and with what arguments.

        .DESCRIPTION
            One question was deferred to phase 05: "does WinPE need
            wpeutil reboot rather than shutdown.exe". A read-only mount of the
            boot image Update-HDTBootImage builds answers it, and the answer is
            not a preference:

                Windows\System32\shutdown.exe ABSENT
                Windows\System32\wpeutil.exe PRESENT, 32768 bytes

            shutdown.exe is not in WinPE. So a Restart step running in WinPE
            through the old adapter called a command that does not exist. It was
            never noticed because DEMO-M3 and DEMO-M4 both deliberately have no
            Restart step and the IPowerService contract's real row is skipped -
            a contract test may not reboot the machine running it.

            tests/integration/WinPeContent.Integration.Tests.ps1 holds that fact
            against a real mounted image, so the day Microsoft changes it,
            somebody is told.

            THIS FUNCTION IS THE ONLY PLACE THAT BRANCHES ON IT. The adapter
            New-HDTPowerService is a shell-out with no `if` in it at all, which
            is what earns it the exemption from TDD; every
            decision it would otherwise have made is here, pure, and asserted as
            an exact argument array.

              FullOS shutdown.exe /r|/s /t <delay> /f the command delays
              WinPE wpeutil.exe reboot|shutdown the caller sleeps

            The delay is the one real difference. `wpeutil reboot` and
            `wpeutil shutdown` take no arguments, so there is nowhere to put a
            delay - and dropping it silently would make a sequence's
            `delaySecond:` a lie in WinPE. SleepSecond carries it instead, and
            the adapter sleeps that long before invoking. Saying so in the
            returned object is deliberate: the adapter must not be the thing
            that knows.

        .PARAMETER Environment
            WinPE or FullOS. There is no default and no detection: the two
            payloads that build a power service already know which world they
            are in - Start-HDTDeployment.ps1 is the WinPE entry point and
            Start-HDTResume.ps1 runs in the deployed OS.

        .PARAMETER Operation
            Restart or Stop.

        .PARAMETER DelaySecond
            How long to wait before the machine goes. Refused if negative:
            shutdown.exe /t -5 is an error, and a negative sleep is nonsense.

        .OUTPUTS
            System.Management.Automation.PSCustomObject with Environment,
            Operation, Command, Argument (string[]), SleepSecond and Reason.

        .EXAMPLE
            Get-HDTPowerCommand -Environment WinPE -Operation Restart -DelaySecond 0

            Command wpeutil.exe, Argument @('reboot').

        .EXAMPLE
            Get-HDTPowerCommand -Environment FullOS -Operation Stop -DelaySecond 30

            Command shutdown.exe, Argument @('/s', '/t', '30', '/f').
    #>

    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('WinPE', 'FullOS')]
        [string] $Environment,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Restart', 'Stop')]
        [string] $Operation,

        [Parameter()]
        [int] $DelaySecond = 0
    )

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

    if ($DelaySecond -lt 0) {
        $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord `
                    -Message ("A power delay cannot be negative, and {0} was asked for. Use 0 to go immediately." -f $DelaySecond) `
                    -TargetObject $DelaySecond))
    }

    # The verb each world spells the same operation with.
    $winPeVerb = @{ Restart = 'reboot'; Stop = 'shutdown' }
    $fullOsSwitch = @{ Restart = '/r'; Stop = '/s' }

    if ($Environment -eq 'WinPE') {
        # Bare, not "$env:SystemRoot\System32\wpeutil.exe": this function reads
        # no environment, and System32 is on the PATH in WinPE - SPIKES S11.3
        # records startnet.cmd resolving `wpeinit` and `powershell.exe` exactly
        # that way inside an image this repository built.
        return [pscustomobject] @{
            Environment = $Environment
            Operation   = $Operation
            Command     = 'wpeutil.exe'
            Argument    = [string[]] @($winPeVerb[$Operation])
            SleepSecond = $DelaySecond
            Reason      = ('WinPE has no shutdown.exe; wpeutil {0} is the only way to end a machine there, and it takes no delay, so the {1}s delay is a sleep.' -f $winPeVerb[$Operation], $DelaySecond)
        }
    }

    return [pscustomobject] @{
        Environment = $Environment
        Operation   = $Operation
        Command     = 'shutdown.exe'
        Argument    = [string[]] @($fullOsSwitch[$Operation], '/t', ([string] $DelaySecond), '/f')
        SleepSecond = 0
        Reason      = ('The deployed OS has shutdown.exe, which owns the {0}s delay itself, so nothing sleeps.' -f $DelaySecond)
    }
}