How to return an exit code from a Powershell script only when run non-interactively - powershell

I have a lot of scripts that are running as scheduled tasks. So they do a $host.setshouldexit(1) on any failure, which shows up in the task scheduler as the return code.
I also want to be able to run these scripts interactively while debugging and testing. So the $host.setshouldexit() kills my powershell or ISE session.
My question is: how can I detect if a script is running non-interactively? If it is, then I'll use setshouldexit, otherwise it will print the error code or something nondestructive. (Note that I don't want to use [environment]::userinteractive because these scripts are not always running in what the OS thinks is a non-interactive session.)
There is a -noninteractive switch that I'm using for the scheduled tasks. Is there some way I can query that from powershell?

The $Host.SetShouldExit method should not be necessary, and is actually inconsistent, depending on how you are calling your scripts. Using the keyword exit should get you your exit status.
Using powershell -F script.ps1:
exit - works
SetShouldExit - ignored
Using powershell -c '.\script.ps1':
exit - status reduced to 0 or 1, for success or failure of the script, respectively.
SetShouldExit - exits with correct status, but remaining lines in script are still run.
Using powershell -c '.\script.ps1; exit $LASTEXITCODE' [1]:
exit - works
SetShouldExit - exits with status == 0, and remaining lines in script are still run.
Calling directly from powershell (> .\script.ps1):
exit - works
SetShouldExit - terminates calling powershell host with given exit status

Why not just have it take a parameter "testing" which sets the right behavior during your tests? You have a history buffer so it will be hardly any more typing to run.

I had the same issue. The following works for me:
# Exit with Return Code when NOT using PowerShell ISE
if ($psise -eq $Null)
{
$host.SetShouldExit(1)
}

Upon finding your question I have taken the issue a bit further and found $MyInvocation.MyCommand.Name. This is False in both the command interpreter and the ISE command interpreter. And when a script (mine is jest.ps1) containing just the line: Write-Host $MyInvocation.MyCommand.Name is run from cmd.exe call to powershell.exe as:
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy RemoteSigned -NoExit -NonInteractive -NoProfile -File "M:\WindowsPowerShell\Jest.ps1"
Output is simply:
Jest.ps1

Check $Host.Name. If your script is running outside of an IDE, it will return a value of ConsoleHost. Otherwise it will return a reference to the IDE such as Windows PowerShell ISE Host or PowerGUIScriptEditorHost.
I have to use SetShouldExit because the scheduler that runs my scripts usually ignores other methods of indicating a failure. I add a function to allow SetShouldExit when $Host.Name is ConsoleHost. It saves a lot of IDE crashes during testing.

This directly answers the question poster had about whether or not you can query if the powershell console was launched with -NonInteractive switch.
Function Test-IsPowerShellConsoleNonInteractive { [Boolean]([Environment]::GetCommandLineArgs() -Match '-NonInteractive') }
$IsPSConsoleNonInteractive = Test-IsPowerShellConsoleNonInteractive
If ($IsPSConsoleNonInteractive) { $Host.SetShouldExit(2) }

Related

How can I propagate the failure to the caller from an invoked script in powershell

I am using jenkins to drive a powershell script. The script is build.ps1. In the Jenkins pipeline I specify
powershell './build.ps1'
I also tried
powershell './build.ps1; exit $LastExitCode'
I have a typo in my build.ps1, or I also get build failures, but Jenkins marks it as success anyway. The reason is that errors are apparently not propagated from the build.ps1 execution to the powershell spawned by jenkins. $LastExitCode is always zero. I verified this in a regular PS prompt with a broken script
./broken.ps1; echo $LastExitCode
gives zero even if test.ps1 throws an error.
I also tried to invoke with &. Same effect.
Try adding the following to your Build.ps1
$ErrorActionPreference = 'Stop'
#
# YOUR CODE
#
trap{
echo "Error while building, error: $($Error[0] | select *)"
exit 1
}
That should trap all errors, and exit with an error code 1.
Also if you can add a screenshot of your Jenkins config when you call the script, just to be sure I think it is what it is.
Hope it helps, good luck.

Is it possible to get different results on `$?` on the same command?

I have a powershell script where I'm executing a node command which is meant to be executed by a TFS 2013 Build:
node "$Env:TF_BUILD_SOURCESDIRECTORY\app\Proj\App_Build\r.js" -o "$Env:TF_BUILD_SOURCESDIRECTORY\app\Proj\App_Build\build-styles.js"
$success = $?
if (!$success){
exit 1
}
When I run this script manually and the command fails $success is false and the script exits 1, but when the build executes the script and the node command fails, $success (and $?) is true.
What can change the behavior of powershell? I have no idea what else to try. So far I eliminated the following:
Changed the Build Service user to the same Admin user that executes the script manually
Tried executing the command with cmd /c node ...
Tried executing the command with Start-Process node...
Ran the Build Service interactively
Ran the build with both VSO Build Controller and an on premise Build Controller
Executed the script manually with the same command used by TFS (per the Build Log)
Thoughts?
Can we restructure this a bit so we have a better feel for what is happening? I tend to avoid $? because it is harder to debug and test with.
try
{
Write-Host "TF_BUILD_SOURCESDIRECTORY = $Env:TF_BUILD_SOURCESDIRECTORY"
$result = node "$Env:TF_BUILD_SOURCESDIRECTORY\app\Proj\App_Build\r.js" -o "$Env:TF_BUILD_SOURCESDIRECTORY\app\Proj\App_Build\build-styles.js"
Write-Host "Result = $result"
}
catch
{
Write-Error "Command failed"
Exit 1
}
Sometimes I wrap my command in a Start-Process -NoNewWindow -Wait just to see if that generates a different error message.
In your case, I would also try Enter-PSSession to get a non-interactive prompt on the TFS server. I have seen cases where powershell acts diferently when the shell is not interactive.

Powershell InitializeDefaultDrives Error always stops Team Build from performing a successful build

Whenever we start up Powershell on any machine on our network we instantly receive the following error in the console 'Attempting to perform the InitializeDefaultDrives operation on the 'FileSystem' provider failed.'
The reason for this is that upon start-up Powershell attempts to map all the drives it can find to Powershell objects, however, for some reason our Ops guys have configured all our network drives to appear as 'disconnected' even though they are fully accessible. Powershell sees these drives as 'disconnected' and throws an error. I've tried to get the Ops guys to change this but the behaviour seems to be set quite deeply in our infrastructure.
Under normal usage it isn't too much of an issue, however, we are trying to run a Powershell script (with Psake) as part of an automated build process via Team Build, and the error on start-up is picked up by the build process and causes our build process to only partially succeed, it's impossible for us to achieve a nice, green, successful build.
Our Psake-based Powershell scripts is kicked off from a simple batch file that looks like this -
cls
powershell -ExecutionPolicy Unrestricted -command "& \"%~dp0psake.ps1\"" %*
echo EXIT CODE - %ERRORLEVEL%
exit /b %ERRORLEVEL%
This batch file is called from an Invoke-Process workflow object in TeamBuild with the standard output and error output mapped to stdout and stderr respectively.
I can see a few potential areas we might be able to solve this
Find a way to stop Powershell from performing the InitializeDefaultDrives operation
Filter out that specific error in the batch wrapper somehow but still pass genuine errors back up to the build process
Parse errors in the Invoke Process workflow object so that particular error doesn't cause a failure, but all other errors still each the build process.
Any help GREATLY appreciated! : )
Eventually I found I was missing some error checking after my call to Powershell so my batch file should have looked like this...
cls
powershell -ExecutionPolicy Unrestricted -command "& \"%~dp0psake.ps1\"" %*; if ($psake.build_success -eq $false) { exit 1 } else { exit 0 }
echo EXIT CODE - %ERRORLEVEL%
exit /b %ERRORLEVEL%

Using read-host cmdlet when execute powershell script file in MSBuild

All , I am trying to execute a external power shell script file in the MSBuild. But every time when PS run the cmdlet Read-Host. The MSBuild seems stop. and doesn't prompt me to input . I don't know what happen to it . Seems the console is in deadlock..thanks.
The testloop.ps1 code is shown below.
$ErrorActionPreference = 'Stop'
$error.clear()
function GetAzureSubScription()
{
read-host "please input something :"
}
write-host "Get into script"
GetAzureSubScription
The MSBuild code is below (wrapped for clarity):
<Exec WorkingDirectory="$(MSBuildProjectDirectory)"
Command="$(windir)\system32\WindowsPowerShell\v1.0\powershell.exe -f
E:\Work\AutoDeploy\testloop.ps1" />
So yes, the console (just a minor point - powershell.exe does not run under cmd.exe - they are separate processes, but they both use a console window) window is hidden so it will appear to freeze when prompting for input. The simplest option here is to override the read-host function with a version that will prompt using a graphical window. Add the start of your script, add the following function:
# override the built in prompting, just for this script
function read-host($prompt) {
$x = 0; $y = 0;
add-type -assemblyname microsoft.visualbasic
[Microsoft.VisualBasic.Interaction]::InputBox($prompt,
"Interactive", "(default value)", $x, $y)
}
Now your script will be able to prompt for values. Also, you should run powershell.exe with the -noninteractive argument to catch any other places where you are accidentally calling interactive host functions. It will not stop the above function from working though.
The MSBuild Exec tasks starts cmd.exe and let that execute the command. MSbuild has to channel the writes to the console through since the cmd.exe window itself is invisible. It seems the writes do get through, but the reads do not. You can see the same effect if instead of calling powershell you exec a command like "del c:\temp\somefile.txt /p" which asks for confirmation. Although that way it doesn't block, but there is also no way of giving an answer.
That it does not handle reads properly is not that strange. It is a build script, so it should just build and not ask questions. My advice is to have the MSBuild script run without asking questions. If you really need to ask questions, then ask them before calling MSBuild.

Powershell window disappears before I can read the error message

When I call a Powershell script, how can I keep the called script from closing its command window. I'm getting an error and I'm sure I can fix it if I could just read the error.
I have a Powershell script that sends an email with attachment using the .NET classes. If I call the script directly by executing it from the command line or calling it from the Windows Scheduler then it works fine. If I call it from within another script (IronPython, if that matters) then it fails. All scenarios work fine on my development machine. (I really do have to get that "Works on My Machine" logo!) I've got the call to Powershell happening in a way that displays a command window and I can see a flicker of red just before it closes.
Sorry: Powershell 1.0, IronPython 1.1
Solution: powershell -noexit d:\script\foo.ps1
The -noexit switch worked fine. I just added it to the arguments I pass from IronPython. As I suspected, it's something that I can probably fix myself (execution policy, although I did temporarily set as unrestricted with no effect, so I guess I need to look deeper). I'll ask another question if I run into trouble with that.
Thanks to all for the help. I learned that I need to investigate powershell switches a little more closely, and I can see quite a few things that will prove useful in the future.
Try with the -noexit switch:
powershell -noexit d:\script\foo.ps1
You basically have 3 options to prevent the PowerShell Console window from closing, that I describe in more detail on my blog post.
One-time Fix: Run your script from the PowerShell Console, or launch the PowerShell process using the -NoExit switch. e.g. PowerShell -NoExit "C:\SomeFolder\SomeScript.ps1"
Per-script Fix: Add a prompt for input to the end of your script file. e.g. Read-Host -Prompt "Press Enter to exit"
Global Fix: Change your registry key to always leave the PowerShell Console window open after the script finishes running.
Here are the registry keys to modify for option #3:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"& \\\"%1\\\"\""
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\0\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"-Command\" \"if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & \\\"%1\\\"\""
See my blog for more information and a .reg file that will apply these registry changes automatically.
I've needed this before and usually I didn't want to modify the script (typically for scripts fired off from the Task Scheduler). I just wanted to see what was spit out to console.
All you need to do is just append a Read-Host command after the script invocation e.g.:
PowerShell.exe -command { .\foo.ps1; read-host "Press enter key to continue" }
BTW the problem with using Start-Transcript is that it doesn't capture EXE output. And any form of attempted logging in V1 and even V2 with the standard host will not capture the verbose, debug, progress or warning streams. You can only see these by viewing the associated host window.
One cheesy but effective way to capture all script output (stdout, stderr, verbose, warning, debug) is to use another host like cmd.exe e.g.:
cmd.exe /c powershell.exe "$pwd\foo.ps1" > foo.log
I am generaly fine with scripts autoclosing except when an error occurs, where I need to see the error. Assuming you have not changed $ErrorActionPreference away from the default 'Continue', then for the behaviour I described do this at the end of you script
if ($Error)
{
Pause
}
There is no ordinary Try...Catch construction in Powershell; however you can trap exceptions instead and react properly.
I.E:
Function Example() {
trap [Exception] {
write-host "We have an error!";
write-error $("ERROR: " + $_.Exception.Message);
sleep 30;
break;
}
write-host "Hello world!";
throw "Something very bad has happened!";
}
You can also simulate Try...Catch construction:
Function Example2() {
${
write-host "Our try clause...";
throw "...caused an exception! It hurts!";
}
trap [Exception] {
write-error $_.Exception.Message;
sleep 30;
continue;
}
Of course as soon as you will trap an exception, you can log it, sleep, or whatever you want with the error message. My examples just sleep, allowing you to read what happened, but it's much better to log all the errors. (The simplest way is to redirect them with >>).
Look also at:
http://huddledmasses.org/trap-exception-in-powershell/
A quick and dirty solution is to use CTRL+S to halt the scrolling of the display and CTRL+Q to resume it.
You have three options:
Do a catch in the script (if using
Powershell V2)
Write a dummy
script which catches and redirects
stdout which you can then access as a
variable from your IronPython script.
VBS/Wscript Intro An addition to
this is just liberally drop
Read-Host commands everywhere,
and hit return to page through.
Rather than outputting anything to the shell, wrap your powershell script in a second script that redirects all output to a log file.
PS C:> myscript.ps1 |Out-File myscript.log
Create run_ps_script.bat file containing
#PowerShell.exe -command "try { %1 } finally { read-host 'Press ENTER...' }"
and make it default program to open PowerShell scrips.
My solution was to execute the script with a command line from the console window instead of right-clicking the file -> execute with powershell.
The console keeps displaying the error messages,
even though the execution of the script ended.
Have you thought about redirecting stdout and stderr to a file ex:
./ascript.ps1 >logs 2>&1
Note: You can create wrapper script in powershell that calls your powershell script with all necessary redirections.
My .PS1 script ran fine from the Powershell console but when "double-clicking" or "right-click open with powershell" it would exhibit the 'open/close' problem.
The Fix for me was to rename the script folder to a Name Without Spaces.
Then it all worked - Windows couldn't deal with
"C:\This is my folder\myscript.ps1" but
"C:\This_is_my_folder\myscript.ps1" worked just fine
A couple more ideas...You could use the start-sleep cmdlet at the end of you script to give you enough time to review the error.
You might also be able to use start-transcript to record the session to a text file.