How to stop a child process with Ctrl+C without breaking the main script and stream child output to the main script in PowerShell?

Viewed 294

I am writing a powershell script that is running on Linux. The purpose of this script is to run the MS SQL Server and show its output, but when the user presses Ctrl+C or some error happens, the script takes a copy of the data folder and then after that exits.

[CmdletBinding()]
PARAM (
    [Parameter(ValueFromPipelineByPropertyName)]
    [string] $Command,

    [Parameter(ValueFromPipelineByPropertyName)]
    [string] $Args
)
BEGIN {
    Write-Output "started $PSScriptRoot"
    $currentErrorLevel = $ErrorActionPreference
    # [console]::TreatControlCAsInput = $true
}
PROCESS {

    try {
        $ErrorActionPreference = 'SilentlyContinue';
        $IsSqlRunning = Get-Process -name "sqlservr";

        if ($null -eq $IsSqlRunning) {
            start-process "/opt/mssql/bin/sqlservr" -wait -NoNewWindow
        }
    }
    catch {
        $ErrorActionPreference = $currentErrorLevel;
        Write-Error $_.Exception
    }

}
End {
    $ErrorActionPreference = $currentErrorLevel;
    #do backup
    Create-Backup "/opt/mssql/bin/data"
    Write-Output "finishd!"
}

I got a couple of problems with this script:

  • When I press Ctrl+C it breaks the main script and it never reaches to the Create-Backup section at the bottom of the script.
  • If I remove -Wait then the script won't show the sql log output

So my prefered solution is to run the sql with -Wait parameter and prevent the powershell to exit the code after I press Ctrl+C, but instead Ctrl+C close the sql instance

So I'm looking for a way to achieve both.

1 Answers

For simplicity I'll assume that your function only needs to support a single input object, so I'm using a simple function body without begin, process and end blocks, which is equivalent to having just an end block:

[CmdletBinding()]
PARAM (
    [Parameter(ValueFromPipelineByPropertyName)]
    [string] $Command,

    [Parameter(ValueFromPipelineByPropertyName)]
    [string] $Args
)
    Write-Output "started $PSScriptRoot"

    # No need to save the current value, because the modified
    # value is local to the function and goes out of scope on 
    # exiting the function.
    $ErrorActionPreference = 'SilentlyContinue';

    try {
        $IsSqlRunning = Get-Process -name "sqlservr";

        if ($null -eq $IsSqlRunning) {
            start-process "/opt/mssql/bin/sqlservr" -wait -NoNewWindow
        }
    }
    catch {
        Write-Error $_.Exception
    }
    finally {

      # This block is *always* called - even if Ctrl-C was used.

      Create-Backup "/opt/mssql/bin/data"
      # CAVEAT: If Ctrl-C was used to terminate the run, 
      #         you can no longer produce *pipeline* input at this point
      #         (it will be quietly ignored).
      #         However, you can still output to the *host*.
      Write-Host "finished!"

    }

If you really need to support multiple input objects, it gets more complicated.

Related