2

We're using the following statement to enable TeamCity to recognize errors in our PowerShell deployment scripts:

trap { $host.SetShouldExit(1) }

This works fine, however, since Param(...) needs to be the very first statement we have to use this order:

Param(
    ...
)

Set-StrictMode -Version 2.0
$ErrorActionPreference = "Stop"
trap { $host.SetShouldExit(1) }

Is there any way to also trap errors during the Param() evaluation? E.g. if we omit a mandatory parameter, TeamCity is not able to detect this at the moment.

D.R.
  • 20,268
  • 21
  • 102
  • 205
  • How your are making the parameter mandatory? If you are using `[Parameter(Mandatory=$true)]` then technically it doesn't error but prompts the user for the value. If you can pass the parameter `-NonInteractive` to the Powershell.exe that TeamCity fires up, that missing parameter will cause an error. – Keith Hill Dec 05 '13 at 17:25
  • Although it causes an error the original powershell.exe process executed by TeamCity does succeed (because only the script executed by powershell.exe failed). This is why we added the $host.SetShouldExit(1)-trap! However, this is not possible for the Param()-statement. – D.R. Dec 06 '13 at 10:09
  • Trap statements may be defined anywhere within a given scope, but always apply to all statements in that scope. At runtime, traps in a block are defined before any other statements are executed. In JavaScript, this is known as hoisting. This means that traps apply to all statements in that block even if execution has not advanced past the point at which they are defined. For example, defining a trap at the end of a script and throwing an error in the first statement still triggers that trap. https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_trap – WSas Sep 15 '19 at 13:44

4 Answers4

4

This is yet another annoying oversight of Powershell. I find the best way to workaround this Powershell bug is to roll your own mandatory verification. It's more code, but clear, simple and fail-safe.

First remove the mandatory flag from the param declaration and check for null or empty at the beginning of the script.

param (
    [string] $required_param
)
# Check required params
if ([string]::IsNullOrEmpty($required_param)) {
    Write-Host "The required_param is required."
    exit 1;
}

Plus you can call your script with the regular File argument:

powershell -File foo.ps1

Hope this helps.

Phil C
  • 431
  • 4
  • 7
1

Try putting the trap in the TeamCity string to execute. For instance, say I have a script with a param block at the top of the script (foo.ps1) with a mandatory parameter that I don't supply e.g.:

param([Parameter(Mandatory)][int]$num, [bool]$bool)

Then I can execute it like so from CMD and get an error exit code:

C:\> cmd /c Powershell.exe -NonInteractive -Command "& {trap {exit 1}; c:\foo.ps1}"
C:\> %ERRORLEVEL%
1

BTW you might find this blog post an interesting read.

Keith Hill
  • 194,368
  • 42
  • 353
  • 369
1

Actually I had the same question and ended up on combining both approaches:

  1. Use -Command calling to powershell.exe to allow to trap the Parameter error
  2. Use trap within the Powershell script to handle errors within - and use $LASTEXITCODE to counter balance Powershell's behavior

Calling side in BAT/CMD:

@echo off
powershell -NoLogo -NonInteractive -Command "& {trap {Write-Error $_; exit 2}; scipt.ps1 %1 %2 %3 %4 %5 %6 %7 %8 %9 ; exit $LASTEXITCODE}"
IF %ERRORLEVEL% NEQ 0 Exit /B %ERRORLEVEL%
Goto :EOF

:EOF

The PowerShell script

Param(
    ...
)

Set-StrictMode -Version 2.0
$ErrorActionPreference = "Stop"
trap { Exit(1) }

...

That way one can decide between Param errors (exit code 2) and errors in the script itself (exit code 1)

Sebastian J.
  • 762
  • 9
  • 29
0

If I understand you correctly, you want to execute a PowerShell script, and have TeamCity see the error returned. I think that this is very similar to how I catch and return errors in my scripts that need to be run with scheduled tasks.

How I do it is first make functions/cmdlts for everything you want to do. Then execute those functions in a try..catch block. e.g.

#My test function
Function test{
Param( [Parameter(Mandatory=$true)] [int]$a )
Process
    {
        Write-Host "Hi $a"
    }
}

#Execute Function
Try{
    test 1234 -ErrorAction "Stop"
}
Catch
{
    #Error encountered
    Write-Host "Error Encountered: exiting"
    #Return Bad error code
    Exit 1
}

This should catch any parameters that are incorrect.

This may not be applicable, but other people's reference, for scheduled tasks, I run my scheduled tasks with the following command:

powershell.exe -Command ". C:\Scripts\Run_PS1.ps1 ; exit $LASTEXITCODE"

This will run powershell, execute the script, and then correctly return the last exit code back to the scheduled task.

Hope this helps you out.

HAL9256
  • 12,384
  • 1
  • 34
  • 46
  • This is mostly the same than my approach in the question. The Param() statement is not within the Try/Catch and any errors during Param()-evaluation are not correctly reported. – D.R. Dec 06 '13 at 10:07