Public/Start-HDTCommandPrompt.ps1

function Start-HDTCommandPrompt {
    <#
        .SYNOPSIS
            Opens a command prompt for the technician - MDT's "Exit to Command
            Prompt".
 
        .DESCRIPTION
            THE BUTTON HAD NO CALLER. The wizard has offered Open CMD since W2
            and returned 'CommandPrompt' by design, leaving the prompt to the
            caller - and no caller ever opened one. Pressing it closed the
            window and produced nothing at all, on every machine. This is what
            makes it do something.
 
            WHY IT DOES NOT GO THROUGH IProcessService.Start. That method
            redirects both pipes, sets CreateNoWindow and waits for exit -
            correct for a command line step and wrong in all three ways here.
            A prompt opened through it would be invisible AND would freeze the
            wizard that opened it. StartInteractive is the separate verb.
 
            A MISSING ComSpec IS NOT A REASON TO REFUSE. The technician asked
            for a prompt because something on this machine is already wrong;
            answering "I could not read an environment variable" is the least
            useful thing this could do. So ComSpec wins when it says something
            and 'cmd.exe' is used when it does not.
 
            NOR IS A PROMPT THAT WOULD NOT OPEN. The wizard window is already
            closing by the time this runs, so an exception here would take the
            deployment with it and leave a blank screen - strictly worse than
            the missing prompt it would be reporting. It comes back as Started
            false with the reason, and the caller decides.
 
            THE CONSOLE IS NOT THIS COMMAND'S BUSINESS. In WinPE the payload
            hid the console to put the wizard on screen and restores it with
            Hide-HDTShellWindow -Restore; that is a Win32 call on this process's
            own window and has nothing to do with starting another one. Two
            things, two commands, at one call site.
 
        .PARAMETER WorkingDirectory
            Where the prompt opens. Omitted, it inherits this process's.
 
        .PARAMETER Process
            An IProcessService. Defaults to the real adapter.
 
        .PARAMETER Environment
            An IEnvironmentProvider, for ComSpec. Defaults to the real adapter;
            null is not an error and means 'cmd.exe'.
 
        .INPUTS
            None. This command does not accept pipeline input.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject with Started, FilePath,
            ProcessId and Message.
 
        .EXAMPLE
            Start-HDTCommandPrompt
 
            Opens a prompt in a window of its own.
 
        .EXAMPLE
            if ($answer.Action -eq 'CommandPrompt') {
                [void] (Start-HDTCommandPrompt)
                [void] (Hide-HDTShellWindow -Restore)
                return
            }
 
            What every wizard caller does: open the prompt, put the console the
            wizard hid back, and stop - the technician asked to leave.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Opens an interactive window the technician explicitly asked for; it changes no system state and a confirmation prompt on a machine with no keyboard focus is worse than the action.')]
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter()]
        [AllowEmptyString()]
        [string] $WorkingDirectory = '',

        [Parameter()]
        [AllowNull()]
        [object] $Process,

        [Parameter()]
        [AllowNull()]
        [object] $Environment
    )

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

    if ($null -eq $Process) { $Process = New-HDTProcessService }
    if (-not $PSBoundParameters.ContainsKey('Environment')) { $Environment = New-HDTEnvironmentProvider }

    # THE RULE IS Get-HDTCommandPromptPath'S AND IT USED TO BE HERE. F8 in the
    # progress window needs the same answer and cannot call this command - that
    # window runs in a runspace with no module in it - so the rule moved to
    # where both can ask.
    $filePath = Get-HDTCommandPromptPath -Environment $Environment

    # NO ARGUMENTS. cmd.exe /c would run nothing and close again immediately,
    # which from the technician's side is the same dead button this replaces.
    try {
        $started = $Process.StartInteractive($filePath, '', $WorkingDirectory)

        return [pscustomobject] @{
            Started   = $true
            FilePath  = $filePath
            ProcessId = [int] $started.ProcessId
            Message   = ''
        }
    } catch {
        return [pscustomobject] @{
            Started   = $false
            FilePath  = $filePath
            ProcessId = 0
            Message   = ('the command prompt ''{0}'' could not be opened: {1}' -f $filePath, [string] $_.Exception.Message)
        }
    }
}