Jenkins powershell plugin always builds successfully - powershell

I'm using Jenkins PowerShell plugin to build a project.
However, I found that Jenkins always considers my build successful no matter what I type inside Windows PowerShell command.
Here's an example:
As you can see, asdf isn't a legal command. Jenkins should give me FAILURE after the build.
But the console output gives me:
Started by user admin
Building in workspace C:\Users\Administrator\.jenkins\jobs\Test\workspace
[workspace] $ powershell.exe -NonInteractive -ExecutionPolicy ByPass "& 'C:\Users\ADMINI~1\AppData\Local\Temp\hudson2092642221832331776.ps1'"
The term 'asdf' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\Users\ADMINI~1\AppData\Local\Temp\hudson2092642221832331776.ps1:1 char:5
+ asdf <<<<
+ CategoryInfo : ObjectNotFound: (asdf:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Finished: SUCCESS
I think the execution result of PowerShell should depend on $lastexitcode.
Is this a bug of PowerShell plugin?

As of 1.3, the plugin will not handle exceptions such as those from missing commands. You can do this yourself with try/catch:
try
{
asdf
}
catch
{
write-host "Caught an exception"
exit 1
}
See MSDN for more.

For me, I wanted the script to stop and fail in Jenkins soon as it hit an error. This was accomplished by adding this to the start of the script:
$ErrorActionPreference = "Stop"
This is discussed here: [How to stop a PowerShell script on the first error?][1]
[1]: How to stop a PowerShell script on the first error?. ..................

Per the latest version of the plugin (Version 1.3 Sept 18 2015), you must use $LastExitCode to fail a build.
Version 1.3 (Sept 18 2015)
PowerShell now runs in Non-Interactive mode to prevent interactive prompts from hanging the build
PowerShell now runs with ExcecutionPolicy set to "Bypass" to avoid execution policy issues
Scripts now exit with $LastExitCode, causing non-zero exit codes to mark a build as failed
Added help and list of available environment variables (including English and French translations)

I want to add here that I just ran into a quirk: you must have the powershell script end with exit and not return.
My jenkins pipe looked like:
script {
result = powershell(returnStatus: true, script: '''...if(error condition) { return 1 }''')
if(result) { error }
}
I was using if(error condition) { return 1 } and while the 1 was showing up as the return value in the jenkins console, it was not failing the build. When i used if(error condition) { exit 1 }, the build failed as expected.
I think this is a helpful addition to this thread - the need to use exit and not return. But I don't understand this part: the pipe is checking for result to be non-zero. What is the difference between exit and return in a powershell directive that makes if(result) { error } not work as expected when using return?
Update Feb-16-2021: Coming back to this to add some more notes from my experience: I think what I was doing and what a lot of people do that confuses things, is to use returnStdout or returnStatus and not check them and/or not fully understand what's coming back.
If you use either of those params, Jenkins will not do anything for you based on their value. You have to check them yourself and act accordingly. On the other hand, if you don't use them, Jenkins will recognize a failure code and fail the pipe if one comes back.
Think about it: If you set returnStatus, you're getting the exit code back from the step as a return value and not as something for Jenkins itself to worry about. If you set returnStdout, you're getting the stdout stream - and not any error codes or anything from stderr. So you must check the return for what you want or you will not get the behavior you are expecting.
What I've been doing for a while is actually not setting either of those params and making sure to set $ErrorActionPreference = 'Stop' at the start of any and all PowerShell scripts running in my pipes. That way any powershell failures automatically fail the pipe as expected, without having to check it.

This is how I implemented RRIROWER's solution. Hope it helps.
<yourscript>.ps1; exit $lastexitcode
Make sure your powershell scripts does exit with the desired value.
Run "exit <value>" as the last line.

Ultimately, I had to resort to the following configuration in Jenkins as none of the solutions here worked for me. Chris Nelson's answer got me on the right track. We're invoking chef-client remotely so we had to do a little magic to get the remote PS session to talk the local and then pass status on to Jenkins.
$res gives the output of chef-client.
$lastsuccess is true or false according to PS rules of engagment.
Of course, you'll have to supply your own evironment variables! :)
Write-host "Deploying $env:Computer with $env:Databag data bag... "
$secstr = ConvertTo-SecureString $env:Password -AsPlainText -Force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $env:User, $secstr
$s = New-PSSession -ComputerName $env:Computer -Credential $cred
$res = Invoke-Command -Session $s -ScriptBlock { try {chef-client} catch {exit 1}}
$lastsuccess = Invoke-Command -Session $s -ScriptBlock {$?}
Remove-PSSession $s
write-host " --- "
write-host $res
write-host " --- "
if($lastsuccess)
{
write-host "chef deployment completed"
exit 0
}
write-host "chef deployment had errors"
exit 1

Related

azure cli not stopping on error in PS script

Boiled down to the minimum I have a Powershell script that looks like this:
$ErrorActionPreference='Stop'
az group deployment create -g ....
# Error in az group
# More az cli commands
Even though there is an error in the az group deployment create, it continues to execute beyond the error. How do I stop the script from executing on error?
Normally, the first thing to try is to wrap everything in a try...catch block.
try {
$ErrorActionPreference='Stop'
az group deployment create -g ....
# Error in az group
# More az cli commands
}
catch {
Write-Host "ERROR: $Error"
}
Aaaaand it doesn't work.
This is when you scratch your head and realize that we are dealing with Azure CLI commands and not Azure PowerShell. They are not native PowerShell commands which would honor $ErrorActionPreference, instead, (as bad as it sounds), we have to treat each Azure CLI command independently as if we were running individual programs (in the back end, the Azure CLI is basically aliases which run python commands. Ironically most of Azure PowerShell commands are just PowerShell wrappers around Azure CLI commands ;-)).
Knowing that the Azure CLI will not throw a terminating error, instead, we have to treat it like a program, and look at the return code (stored in the variable $LASTEXITCODE) to see if it was successful or not. Once we evaluate that, we can then throw an error:
az group deployment create -g ....
if($LASTEXITCODE){
Write-Host "ERROR: in Az Group"
Throw "ERROR: in Az Group"
}
This then can be implemented into a try...catch block to stop the subsequent commands from running:
try {
az group deployment create -g ....
if($LASTEXITCODE){
Write-Host "ERROR: in Az Group"
Throw "ERROR: in Az Group"
}
# Error in az group
# More az cli commands
}
catch {
Write-Host "ERROR: $Error"
}
Unfortunately this means you have to evaluate $LASTEXITCODE every single time you execute an Azure CLI command.
You may use the automatic variable $?. This contains the result of the last execution, i.e. True if succeded or False if it failed: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-5.1#section-1
Your code would look something like this:
az group deployment create -g ....
if(!$?){
Write-Error "Your error message"
# Handle your error
}
Unfortunately, and same to #HAL9256 answer, you will need to add this code every single time you execute azure-cli
Update - PS Version 7 error trapping for Az CLI (or kubectl)
This behavior changed significantly in PowerShell v7:
https://github.com/PowerShell/PowerShell/issues/4002
https://github.com/PowerShell/PowerShell/pull/13361
I ended up using the following solution instead which is consistent with PowerShell v5/7. It has the advantage of accepting pipeline input for commands like kubectl where stdio can be used for applying configuration etc.
I used scriptblocks since this integrates nicely into existing scripts without breaking syntax:
# no error
# Invoke-Cli {cmd /c echo hi}
# throws terminating error
# (with psv7 you can now call Get-Error to see details)
# Invoke-Cli {az bad}
# outputs a json object
# Invoke-Cli {az account show} -AsJson
# applies input from pipeline to kubernetes cluster
# Read-Host "Enter some json" | Invoke-Cli {kubectl apply -f -}
function Invoke-Cli([scriptblock]$script, [switch]$AsJson)
{
$ErrorActionPreference = "Continue"
$jsonOutputArg = if ($AsJson)
{
"--output json"
}
$scriptBlock = [scriptblock]::Create("$script $jsonOutputArg 2>&1")
if ($MyInvocation.ExpectingInput)
{
Write-Verbose "Invoking with input: $script"
$output = $input | Invoke-Command $scriptBlock 2>&1
}
else
{
Write-Verbose "Invoking: $script"
$output = Invoke-Command $scriptBlock
}
if ($LASTEXITCODE)
{
Write-Error "$Output" -ErrorAction Stop
}
else
{
if ($AsJson)
{
return $output | ConvertFrom-Json
}
else
{
return $output
}
}
}
Handling command shell errors in PowerShell <= v5
Use $ErrorActionPreference = 'Stop' and append 2>&1 to the end of the statement.
# this displays regular output:
az account show
# this also works as normal:
az account show 2>&1
# this demonstrates that regular output is unaffected / still works:
az account show -o json 2>&1 | ConvertFrom-Json
# this displays an error as normal console output (but unfortunately ignores $ErrorActionPreference):
az gibberish
# this throws a terminating error like the OP is asking:
$ErrorActionPreference = 'Stop'
az gibberish 2>&1
Background
PowerShell native and non-native streams, while similar, do not function identically. PowerShell offers extended functionality with streams and concepts that are not present in the Windows command shell (such as Write-Warning or Write-Progress).
Due to the way PowerShell handles Windows command shell output, the error stream from a non-native PowerShell process is unable (by itself) to throw a terminating error in PowerShell. It will appear in the PowerShell runspace as regular output, even though in the context of Windows command shell, it is indeed writing to the error stream.
This can be demonstrated:
# error is displayed but appears as normal text
cmd /c "asdf"
# nothing is displayed since the stdio error stream is redirected to nul
cmd /c "asdf 2>nul"
# error is displayed on the PowerShell error stream as red text
(cmd /c asdf) 2>&1
# error is displayed as red text, and the script will terminate at this line
$ErrorActionPreference = 'Stop'
(cmd /c asdf) 2>&1
Explanation of 2>&1 workaround
Unless otherwise specified, PowerShell will redirect Windows command shell stdio errors to the console output stream by default. This happens outside the scope of PowerShell. The redirection applies before any errors reach the PowerShell error stream, making $ErrorActionPreference irrelevant.
The behavior changes when explicitly specified to redirect the Windows command shell error stream to any other location in PowerShell context. As a result, PowerShell is forced to remove the stdio error redirection, and the output becomes visible to the PowerShell error stream.
Once the output is on the PowerShell error stream, the $ErrorActionPreference setting will determine the outcome of how error messages are handled.
Further info on redirection and PowerShell streams
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7.2
The sample from HAL9256 didn't work for me either, but I did find a workaround:
az group deployment create -g ....
if($error){
Write-Host "ERROR: in Az Group"
#throw error or take corrective action
$error.clear() # optional - clear the error
}
# More az cli commands
I used this to try to deploy a keyvault, but in my environment, soft delete is enabled, so the old key vault is kept for a number of days. If the deployment failed, I'd run a key vault purge and then try the deployment again.
Building from HAL9256's and sam's answers, add this one-liner after each az cli or Powershell az command in a Powershell script to ensure that the catch block is hit when an error occurs:
if($error){ throw $error }
Then for the catch block, clear the error, e.g.
catch {
$ErrorMessage = $_.Exception.Message
$error.Clear()
Write-Warning "-- Operation Failed: $ErrorMessage"
}

How to determine if Write-Host will work for the current host

Is there any sane, reliable contract that dictates whether Write-Host is supported in a given PowerShell host implementation, in a script that could be run against any reasonable host implementation?
(Assume that I understand the difference between Write-Host and Write-Output/Write-Verbose and that I definitely do want Write-Host semantics, if supported, for this specific human-readable text.)
I thought about trying to interrogate the $Host variable, or $Host.UI/$Host.UI.RawUI but the only pertinent differences I am spotting are:
in $Host.Name:
The Windows powershell.exe commandline has $Host.Name = 'ConsoleHost'
ISE has $Host.Name = 'Windows PowerShell ISE Host'
SQL Server Agent job steps have $Host.Name = 'Default Host'
I have none of the non-Windows versions installed, but I expect they are different
in $Host.UI.RawUI:
The Windows powershell.exe commandline returns values for all properties of $Host.UI.RawUI
ISE returns no value (or $null) for some properties of $Host.UI.RawUI, e.g. $Host.UI.RawUI.CursorSize
SQL Server Agent job steps return no values for all of $Host.UI.RawUI
Again, I can't check in any of the other platforms
Maintaining a list of $Host.Name values that support Write-Host seems like it would be bit of a burden, especially with PowerShell being cross-platform now. I would reasonably want the script to be able to be called from any host and just do the right thing.
Background
I have written a script that can be reasonably run from within the PowerShell command prompt, from within the ISE or from within a SQL Server Agent job. The output of this script is entirely textual, for human reading. When run from the command prompt or ISE, the output is colorized using Write-Host.
SQL Server jobs can be set up in two different ways, and both support capturing the output into the SQL Server Agent log viewer:
via a CmdExec step, which is simple command-line execution, where the Job Step command text is an executable and its arguments, so you invoke the powershell.exe executable. Captured output is the stdout/sterr of the process:
powershell.exe -Command x:\pathto\script.ps1 -Arg1 -Arg2 -Etc
via a PowerShell step, where the Job Step command text is raw PS script interpreted by its own embedded PowerShell host implementation. Captured output is whatever is written via Write-Output or Write-Error:
#whatever
Do-WhateverPowershellCommandYouWant
x:\pathto\script.ps1 -Arg1 -Arg2 -Etc
Due to some other foibles of the SQL Server host implementation, I find that you can emit output using either Write-Output or Write-Error, but not both. If the job step fails (i.e. if you throw or Write-Error 'foo' -EA 'Stop'), you only get the error stream in the log and, if it succeeds, you only get the output stream in the log.
Additionally, the embedded PS implementation does not support Write-Host. Up to at least SQL Server 2016, Write-Host throws a System.Management.Automation.Host.HostException with the message A command that prompts the user failed because the host program or the command type does not support user interaction.
To support all of my use-cases, so far, I took to using a custom function Write-Message which was essentially set up like (simplified):
$script:can_write_host = $true
$script:has_errors = $false
$script:message_stream = New-Object Text.StringBuilder
function Write-Message {
Param($message, [Switch]$iserror)
if ($script:can_write_host) {
$private:color = if ($iserror) { 'Red' } else { 'White' }
try { Write-Host $message -ForegroundColor $private:color }
catch [Management.Automation.Host.HostException] { $script:can_write_host = $false }
}
if (-not $script:can_write_host) {
$script:message_stream.AppendLine($message) | Out-Null
}
if ($iserror) { $script:has_errors = $true }
}
try {
<# MAIN SCRIPT BODY RUNS HERE #>
}
catch {
Write-Message -Message ("Unhandled error: " + ($_ | Format-List | Out-String)) -IsError
}
finally {
if (-not $script:can_write_host) {
if ($script:has_errors) { Write-Error ($script:message_stream.ToString()) -EA 'Stop' }
else { Write-Output ($script:message_stream.ToString()) }
}
}
As of SQL Server 2019 (perhaps earlier), it appears Write-Host no longer throws an exception in the embedded SQL Server Agent PS host, but is instead a no-op that emits nothing to either output or error streams. Since there is no exception, my script's Write-Message function can no longer reliably detect whether it should use Write-Host or StringBuilder.AppendLine.
The basic workaround for SQL Server Agent jobs is to use the more-mature CmdExec step type (where Write-Output and Write-Host both get captured as stdout), but I do prefer the PowerShell step type for (among other reasons) its ability to split the command reliably across multiple lines, so I am keen to see if there is a more-holistic, PowerShell-based approach to solve the problem of whether Write-Host does anything useful for the host I am in.
Just check if your host is UserInteractive or an service type environment.
$script:can_write_host = [Environment]::UserInteractive
Another way to track the output of a script in real time is to push that output to a log file and then monitor it in real time using trace32. This is just a workaround, but it might work out for you.
Add-Content -Path "C:\Users\username\Documents\PS_log.log" -Value $variablewithvalue

PowerShell error Method invocation failed

I have a strange problem where I have existing code that works on Windows 7x86 PowerShell 2.0 but will not work on Windows 10x86 using PowerShell 5.0.
The error states:
Method invocation failed because
[System.Management.Automation.PSRemotingJob] does not contain a method named 'op_Addition'.
At
C:\Build\AmazingCharts\Working\Building\Installer\WiseInstaller\Obfusated
Projects\ObfuscateFiles.ps1:211
char:13 + $Jarray +=
Start-Job -name ObfuscateFiles $ScriptBlock - ...
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation:
(op_Addition:String) [], Runti
meException +
FullyQualifiedErrorId : MethodNotFound
This is stating that the overloaded operator += doesn't seem to be defined but I find it very difficult to believe that the += overloaded addition operator would be deprecated in PowerShell 5.0 for arrays.
Further, the code seems to still execute and produce a correct output.
In my case I use a jobs array to execute Red Gate Smart Assembly to obfuscate build files. Below is a code snippet
$Jarray = #() #initialize jobs array
foreach ($file in $farray_) {
$params = $file,$cwd,$bldLog #parameters that are passed to script block
$nCount = (get-job -state "Running").Count #determines num of running jobs
if (-not $nCount) {$ncount=0} #initialize variable if null
if ($nCount -le $MAX_INSTANCES) {
#The line below is the one that generates the error
$Jarray += Start-Job -name ObfuscateFiles $ScriptBlock -ArgumentList $params
Start-Sleep -s 1 #gives the system time for process to start
} else {
$nCount = (get-job -state "Running").Count
if (-not $nCount) {$nCount=0}
while ($nCount -ge $MAX_INSTANCES) {
Start-Sleep -s 1 #gives time to recognize the job has started
$nCount = (get-job -state "Running").Count
if (-not $nCount) {$nCount=0}
}
$Jarray += Start-Job -name ObfuscateFiles $ScriptBlock -ArgumentList $params
}
}
When this execute it correctly generates all of the obfuscated files and in a timely manner so I know it is building in parallel up to the MAX_INSTANCES value. I even have code (not included above) that verifies that the number of expected obfuscated files is correct. The 'Method invocation failed' error reports back to CruiseControl.NET and my build fails.
Another frustration is that executing the same .ps1 file in PowerGUI completes successfully without any errors.
I have made sure that my array gets initialized. I have tried suggestions to specify that the array be of type [PSObject] but I get the same results.
Does anyone have any ideas what I can check for this?
Thank you.
EDIT:
I tested this in the PowerShell IDE
-- When tested running from a command prompt, I get the errors
-- When tested running the debugger, I do not get errors
I checked the $Jarray type and it is a System.Object[], which is what I would expect for an array.
I checked the $Jarray value during debug and the jobs are being created but the error still persists as if it doesn't understand the += overloaded Addition operator. My only guess is that this is a bogus error masking something else.
I added a try/catch block around the call to $Jarray. I had tried ErrorAction Ignore but that did not work because the call does not know what to do with the error. The try/catch block successfully masks the error; fortunately I have another method to determine if my call is successful. Needless to say this does not 'solve' the issue, it only masks the issue because right now it is not critical.
I will continue to update this area with things I have tested.
Just an idea, but might want to make sure your new machine has a relaxed policy for remoting AND enabling scripts:
Set-ExecutionPolicy Unrestricted &
Enable-PSRemoting

Powershell ISE wrongly interprets openssl.exe normal output as error

I am trying to debug a script in the PowerShell ISE, but I am running in an issue where the normal output of a line is interpreted as an error by the ISE
I have been able to simplify the reproduction of this issue:
I get whichever version of openssl at https://www.openssl.org/related/binaries.html (I tested this with 1.0.2d x86 from the linked repository http://slproweb.com/products/Win32OpenSSL.html)
I open Powershell ISE, navigate to where the exe is and run the following:
$ErrorActionPreference = "Stop"
$env:OPENSSL_CONF = ((Resolve-Path "openssl.cfg").Path)
&openssl.exe genrsa
The output is red and starts like this:
openssl.exe : Loading 'screen' into random state - done
At line:1 char:1
+ &C:\Trayport\OpenSsl\openssl.exe genrsa
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (Loading 'screen...om state - done:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Generating RSA private key, 2048 bit long modulus
If I run this in the normal PowerShell command window, the output is the same but is white and is not considered as an error
Loading 'screen' into random state - done
Generating RSA private key, 2048 bit long modulus
I have tried using 2>&1 or encaspsulating the call with an ErrorPreference parameter (as suggested in PowerShell ISE throws an error on git checkout) but it still fails
try/catching all exception does work, but I'd rather not if I can avoid it.
I have tried using PowerShell version 3.0 and 4.0
Edit: if you use $ErrorActionPreference = "SilentlyContinue", it does silence the error, but then you lose access to the output, plus legitimate issues also disappear
Powershell interprets any dump into error channel as an error that's happened on the application side, so it stops the script past that point. You should add 2>&1 in the line which calls openssl.exe, so that standard error of the application won't be captured and interpreted by Powershell as an actual error.
$ErrorActionPreference = "Stop"
$env:OPENSSL_CONF = ((Resolve-Path "openssl.cfg").Path)
& openssl.exe genrsa 2>&1
UPDATE: It's incurable as is, Powershell ISE does intercept standard error channel into Write-Error and triggers stop. There is a workaround here that includes wrapping the external app that generates error channel output into a script block with local override of $ErrorActionPreference, like this:
& {
$ErrorActionPreference='silentlycontinue'
openssl.exe genrsa 2>&1
}
I found a working solution.
As specified in the edit to the question, setting $ErrorActionPreference = "SilentlyContinue" is a bit of a wrong solution, because it is in this case the PowerShell equivalent of swallowing all errors, and it also removes access to the actual output of the process.
But, while testing with parameters for the command that would actually return a legitimate error, I noticed that in this case I would get a non-0 $LASTEXITCODE (note to anyone having a similar issue: this is actually specific to openssl.exe, it might not be the case for whichever application you are calling).
So I decided to rely on the process exit code to decide if I should treat the stderr as an actual error.
Capturing standard out and error with Start-Process gives a solution to keep the output of the process alive, so:
$processStartInfo = New-Object System.Diagnostics.ProcessStartInfo
$processStartInfo.FileName = "openssl.exe"
$processStartInfo.RedirectStandardError = $true
$processStartInfo.RedirectStandardOutput = $true
$processStartInfo.UseShellExecute = $false
$processStartInfo.Arguments = "genrsa"
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processStartInfo
$process.Start() | Out-Null
$process.WaitForExit()
$standardError = $process.StandardError.ReadToEnd()
if ($process.ExitCode) {
Write-Error $standardError
} else {
Write-Host $standardError
}
Invoke-Expression ".\openssl.exe req -new -nodes -out c:\test\certs\rui.csr -keyout c:\test\certs\rui.key -config c:\test\certs\esxi.cfg" 2>&1
From here

Return code and status from PowerShell command

I'm running the following command from within a Microsoft System Centre Orchestrator PowerShell activity:
Install-WindowsFeature -ConfigurationFilePath C:\DeploymentConfigTemplate.xml -ComputerName ServerXYZ
the command isn't doing what it's supposed to do, and I want to be able to return if the command was successful or not, and any error message if possible. Ignore the fact it's running in Orchestrator, as I'm more concerned about the PowerShell question. When I run the command from ISE it does what it's supposed to do, that's why I want to see what is returned from PowerShell.
Thanks.
It's hard to know what may be happening without more context. The following will record any errors encountered in an xml file that you can import later with import-clixml:
Install-WindowsFeature -ConfigurationFilePath C:\DeploymentConfigTemplate.xml -ComputerName ServerXYZ
IF (!($?)) {
$error[0] | export-clixml C:\myerror.xml
}
This solves my problem:
$Result = Install-WindowsFeature -Name SNMP-Service -IncludeAllSubFeature -IncludeManagementTools
Write-Host $Result.ExitCode