0

I'm doing an invoke-expression of an old console command written in C++ and I don't have control over the C++ source code... But, I think it's trapping the Ctrl-C to prevent the command line from interrupting it... Thus, when I invoke-expression from my powershell script there's no way to break the execution using ctrl-C and it locks my terminal and I need to keep killing and restarting my terminal window... which is super annoying...

Is there a way to make sure that powershell gets the ctrl-C instead of the C++ program when starting the C++ program using invoke-expression? Like maybe, I can refuse to give stdin to the C++ program, and let powershell have it instead? or maybe there's some solution where stdin goes to a different powershell thread that waits for a ctrl-c and then kills the invoke-expression thread..

Example:

PS> $cmd = <path_to_misbehaving_cpp_program_that_doesnt_Allow_ctrl_c_To_break_it>

PS> invoke-expression $cmd
# now here if I ctrl-C I can't break out of the invoke-expression....
# But I need this capability to ctrl-C
#  to break the script and terminate the
#  invoke expression of the C++ program.
pico
  • 1,660
  • 4
  • 22
  • 52
  • Does it work in Powershell ISE with the stop button (red square)? – T-Me Dec 28 '21 at 15:04
  • interesting idea, but it needs to run from standard powershell window.... unless this somehow helps you understand the problem? – pico Dec 28 '21 at 15:06
  • 1
    `Start-Process` to do it another thread and kill the process if needed? – T-Me Dec 28 '21 at 15:08
  • Do you need to be able to consume the output from the application as it comes out, or does it just need to run quietly until you press Ctrl+C? – Mathias R. Jessen Dec 28 '21 at 15:13
  • Need to see the output... input to the process is not really required.. normally if you hit control-C it goes to a special command prompt ... but this doesn't work well enough... i would prefer ctrl-c to kill the process... – pico Dec 28 '21 at 15:29
  • 1
    As an aside: [`Invoke-Expression` (`iex`) should generally be avoided](https://stackoverflow.com/a/51252636/45375); definitely [don't use it to invoke an external program or PowerShell script](https://stackoverflow.com/a/57966347/45375). If your executable path (only) is stored in `$cmd`, simply invoke it with `& $cmd` – mklement0 Dec 28 '21 at 15:34

2 Answers2

2

Starting and stopping (killing) a process from within powershell:

Use the Start-Process cmdlet to start the other program. If you use the -PassThru switch you get back the information witch process was started.

$Proc = Start-Process powershell.exe -ArgumentList '-command "sleep 60" '  -PassThru

This process can easily be killed with Stop-Process even if it is supposed to run another 60 seconds:

$Proc | Stop-Process

Edit:

Now with exit code (Thx @mklement0 for the Wait)

$Proc.WaitForExit()
$Proc.ExitCode
T-Me
  • 1,814
  • 1
  • 9
  • 22
  • How do you get the exit code from the process? – pico Dec 28 '21 at 15:30
  • 1
    @pico: `$Proc.WaitForExit(); $Proc.ExitCode` – mklement0 Dec 28 '21 at 15:38
  • It might be a good idea to pass `-NoNewWindow` to `Start-Process` so the output of the process appears in the current console window instead of creating a new window. – zett42 Dec 28 '21 at 15:49
  • @zett42 can be done but might be very distracting. Its hard to kill the process when stuff keeps on apearing in the window you are tipping in:`$Proc = start-process powershell.exe -ArgumentList '-command "1..60 | % {sleep 1;$_}" ' -PassThru -NoNewWindow` – T-Me Dec 28 '21 at 15:55
  • 1
    You probably want `try {$proc = ...; while($true){Write-Host "Still running..."; Start-Sleep 2}}finally{$proc|Stop-Process -Force; $proc.ExitCode}` for a "self-cleaning" solution on Ctrl+C – Mathias R. Jessen Dec 28 '21 at 16:06
0

The trick to get it to respond to ctrl-c was to pipe $null into the command run via start-process powershell... see below... Also, note at the end the finally "kill -force" kills the remaining child processes from the initial powershell.exe process-start... my program created childprocesses that keep running after the ctrl-c kill powershell.exe so i need to kill again...to kill the entire process tree.

$script:Proc             = $null

# Run Command as a separate process
function x1_process {
    
    write-host "`nx1_process: $args"
                
    $posh = (get-command powershell.exe).Source

    $iproc = Start-Process $posh -ArgumentList "`$null `| $args" -PassThru -NoNewWindow 
    $script:Proc = $iproc

    if ($iproc -eq $null) {
        throw "null proc"       
    }
    
    Wait-Process -InputObject $iproc

    $code = $iproc.ExitCode
    if ($code -ne 0) {
        throw "return-code:$code"
    }   
    
    write-host "ok`n"
    return
}

try {
    x1_process mycommand.exe arg1 arg2 ...
}
finally {
    if ($script:Proc) {
        if (-not $script:Proc.HasExited) {
            write-host -ForegroundColor Red "=> Killing X1_PROCESS <="
            Stop-Process -InputObject $script:Proc -Force -ErrorAction SilentlyContinue
        }
    }
}
pico
  • 1,660
  • 4
  • 22
  • 52
  • 1
    `Stop-Process` does _not_ kill a process _tree_ - see https://github.com/PowerShell/PowerShell/blob/6c22065d77f0d150484ac0234938b0afb7ee11f1/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs#L1243 – mklement0 Dec 28 '21 at 23:00
  • 1
    While piping `$null` to _external programs_ in order to provide _empty stdin input_ does work, for conceptual clarity I recommend `@()`, because it works with _PowerShell commands_ too. – mklement0 Dec 28 '21 at 23:02
  • Using `Start-Process` with `powershell.exe` and `-ArgumentList "\`$null \`| $args"` is _brittle_ - it'll fail with arguments containing spaces and other metacharacters. – mklement0 Dec 28 '21 at 23:04
  • 2
    If the key to making Ctrl-C work with your external program is to provide _no stdin input_, you could try _direct invocation_ with `@() | mycommand.exe arg1 arg2` – mklement0 Dec 28 '21 at 23:06