Home > Blockchain >  How to stop powershell script when error occurs?
How to stop powershell script when error occurs?

Time:11-10

I have a "main" powershell script that executes multiple scripts that install apps on a VM. I'm trying to implement the error control on the main script, meaning:

If one of the scripts that installs the apps fails, the rest of the scripts aren't executed.

Here is my main script:

try{
    powershell.exe -ExecutionPolicy Unrestricted -File 'C:\\TEST\\Scripts\\App1.ps1'    
    powershell.exe -ExecutionPolicy Unrestricted -File 'C:\\TEST\\Scripts\\App2.ps1'    
    powershell.exe -ExecutionPolicy Unrestricted -File 'C:\\TEST\\Scripts\\App3.ps1'    
 
}catch
{
  Write-Host "Error"

}

Here is an example of one of the scripts (App2.ps1) that installs the apps (all the scripts follow the same logic as this one)

#Set logging 
$logFile = "C:\TEST\Logs\"   (get-date -format 'yyyyMMdd')   '_softwareinstall.log'
function Write-Log {
    Param($message)
    Write-Output "$(get-date -format 'yyyyMMdd HH:mm:ss') $message" | Out-File -Encoding utf8 $logFile -Append
}


#Install APP2 
$file = Test-Path "C:\TEST\Apps\APP2\APP2 x64 7.2.1.msi"
if($file)
{
   try{
        Write-Log "Installing App2"
        Start-Process msiexec.exe -Wait -ArgumentList '/i "C:\TEST\Apps\APP2\App2 x64 7.2.1.msi" ALLUSERS=1 AddLocal=MiniDriver,PKCS,UserConsole,Troubleshooting,Help /qn /norestart' 
        if(Test-Path -Path "C:\Program Files\HID Global\APP2\ac.app2.exe")
        {
            Write-Log "App2 installed"
        }
        else
        {
            Write-Log "There was a problem while installing App2"
        throw "There was a problem while installing App2"
        }
    }catch
    {
        Write-Log "[ERROR] There was a problem while starting the installation for App2"
        throw "[ERROR] There was a problem while starting the installation for App2"
    }
}
else
{
     Write-Log "Installation file for App2 not found"
     throw "Installation file for App2 not found"
}

Here is the output: enter image description here

(I blured the names of the apps for confidential purposes)

Why did the main script continue to execute when the script to install the APP2 through an exception? Shouldn't have stopped and shown the message written on the catch section in the main script?

Thank you in advance

CodePudding user response:

As recommended I added $ErrorActionPreference = 'Stop'to the beginning of my script

$ErrorActionPreference = 'Stop'

try{
    powershell.exe -ExecutionPolicy Unrestricted -File 'C:\\TEST\\Scripts\\APP1.ps1'    
    powershell.exe -ExecutionPolicy Unrestricted -File 'C:\\TEST\\Scripts\\APP2.ps1'    
    powershell.exe -ExecutionPolicy Unrestricted -File 'C:\\TEST\\Scripts\\APP3.ps1'    
 
}catch
{
  Write-Host "Error"

}

And the output it's just the message "Error", indicated in the catch section.

Thank you to @Doug Maurer

CodePudding user response:

As of this writing, your own answer, which relies on the presence of stderr output, is not effective - except in the following contexts:

  • In the obsolescent, Windows PowerShell-only ISE, which is best avoided these days:

  • In Windows PowerShell only, in the context of remoting calls as well as locally when a 2> redirection is used.

That it works in these specific cases relies on undesirable behavior, which has been fixed in PowerShell (Core) 7 :

  • In the cases above, stderr output from external programs is inappropriately routed through PowerShell's error output stream, even though stderr output cannot be assumed to represent errors (it is often used for status or progress messages - anything other than data; the only reliable indicator of whether an external program failed is its process exit code).

  • It is this inappropriate routing through PowerShell's error stream that makes stderr output susceptible to $ErrorActionPreference = 'Stop'


Taking a step back:

  • From PowerShell, there's rarely a need to call its own CLI, powershell.exe (in the case of Windows PowerShell), which requires creating another PowerShell instance as a child process, which is not only expensive, but also means that any output is received as text only, forgoing PowerShell's rich support for .NET data types (except, with limitations, if you use a script block - see below).

    • Usually, you'd simply invoke .ps1 files directly, for efficient in-process execution; e.g., , as a stand-alone statement, C:\TEST\Scripts\App1.ps1

    • If you do need to call via PowerShell's CLI (which situationally may be the respective other PowerShell edition's CLI), you'll get the best integration with using a script block ({ ... }), which uses behind-the-scenes serialization and deserialization, with the child session's PowerShell-specific output streams mapped to the calling one's, so that $ErrorActionPreference = 'Stop' acts the same way it would if you called the script in-process; e.g., powershell.exe { C:\TEST\Scripts\App1.ps1 }; additionally, the serialization/deserialization tries to preserve data types as much as possible.


Considerations for external programs in general:

  • As stated, success vs. failure should not be inferred from the presence of stderr output in an external-program call.

  • Instead, by (widely observed) convention, external programs signal success vs. failure via their process exit code: 0 for success, any nonzero value for failure.

  • PowerShell reflects the exit code of the most recently executed external program in its automatic $LASTEXITCODE variable.

Up to at least PowerShell (Core) 7.3, you'll therefore need to check $LASTEXITCODE -ne 0 after every external-program call in order to detect failure (in PowerShell (Core) 7 , you may also use -not $? immediately after the call, but in Windows PowerShell that doesn't work reliably, due to the 2> redirection bug mentioned above).

This is cumbersome, and a future PowerShell (Core) version may make this easier - see next section.


Future considerations, as of PowerShell (Core) 7.3:

As of v7.3, there is an experimental feature named PSNativeCommandErrorActionPreference, which may become an official feature in a future version:

  • If this feature is enabled (Enable-ExperimentalFeature PSNativeCommandErrorActionPreference) and the associated $PSNativeCommandErrorActionPreference preference variable is set to $true, any external-program call that reports a nonzero exit code automatically triggers a PowerShell error in response (obviating the need for explicit $LASTEXITCODE -ne 0 checks), which then integrates with PowerShell's own error handling.

Unfortunately, as of v7.3.0, the automatically triggered PowerShell error is a non-terminating error rather than a statement-terminating one; the latter would make more sense, as it would allow it to be selectively caught with a try statement - see GitHub issue #18368.

  • Related