How do I output an error without it being returned on the screen? - powershell

Okay, so I'm writing a very basic PowerShell script (I'm on the newbie side here) that does the following:
Checks for the version of a running process (output saved to variable)
If the process is not detected and there is an error, perform an install of program
If the process version is less than or equal to $version, run a command to upgrade the program
My problem is that if the process is not detected, the command errors out and returns to the prompt. What I really want is for this error (or other responses to the command) to -only- be recorded in the variable, not terminate the script. So if I do:
$DesiredVersion = Versionnumber
$ProductVersion = (Get-Process 'process').ProductVersion
if ($ProductVersion -like "*Cannot find*") {
Start-Process InstallProgram.exe
}
elseif ($ProductVersion -lt $DesiredVersion) {
Start-Process UpgradeProgram.exe
}
else {
"Product is up to date"
}
and so on, I want the first command to direct any output of the command into the variable without terminating the script (even in event of error). How would I go about doing this?
I tried doing a Write-Output command to write everything to a text file. This did not resolve things, the command still returns an error and exits.

Just starting out is the perfect time to get into good habits and use Try...Catch constructs:
$DesiredVersion = Versionnumber
Try {
$ProductVersion = (Get-Process 'process' -ErrorAction Stop).ProductVersion
}
Catch {
"Process not found or error:"
# $_ contains the error code
$_
}
# If $ProductVersion has a value then process accordingly
If ($ProductVersion) {
Try {
If ($ProductVersion -lt $DesiredVersion) {
Start-Process UpgradeProgram.exe -ErrorAction Stop
}
Else {
"Product is up to date"
}
}
Catch {
"Error upgrading:"
$_
}
}

Related

Powershell: Trap continue breaks entire loop

Trying to create a better way to handle errors for some automated stuff.
My understanding is that, when using "continue" in Trap, it should just keep looping, but skip the rest of the current iteration. However, in the following code, everything handle as I expect, except in the loop, code completely stops, and no matter what I try I can't get it to keep looping.
trap {
Clear-Host
Write-Host "Running script: '$PSCommandPath' as user: '$(whoami)' with process id (PID): '$PID'`n"
Write-Host "Exception error: " $_
Write-Host "`nVariables at run time:"
Get-Variable *
continue;
}
try {
throw "TryCatchError"
} catch { Write-Host "test 2 caught" $_ }
throw "TrapError"
#this loop doesnt work
While ($true) { Throw "error"; Start-sleep 1}
Can anybody explain why or have a solution that would let the loop continue, while still trapping (not try/catching) the error?
Based on the comment I did the way I would do it is handle this is using a try-catch statement since thats the correct way to do it. This will continue your while-statement gracefully and ignore the Start-sleep 1; row.
While ($true) {
try {
Throw "error";
}
catch {
Clear-Host
Write-Host "Running script: '$PSCommandPath' as user: '$(whoami)' with process id (PID): '$PID'`n"
Write-Host "Exception error: " $_
Write-Host "`nVariables at run time:"
Get-Variable *
continue;
}
Start-sleep 1;
}

How to handle errors for the commands to run in Start-Job?

I am writing an automation script. I had a function which takes either a command or an executable. I had to wait until the command or executable has completed running and return if failed or passed. I also want to write the output to file. I am trying with the Start-Job cmdlet.
My current code:
$job = Start-Job -scriptblock {
Param($incmd)
$ret = Invoke-Expression $incmd -ErrorVariable e
if ($e) {
throw $e
} else {
return $ret
}
} -ArumentList $outcmd
Wait-Job $job.id
"write the output to file using receive-job and return if passed or failed"
This works perfectly fine for commands but for executables irrespective of errorcode the value of $e is null. This falsely shows as passed even though the errorcode is 0.
I tried with errorcode using $LASTEXISTCODE and $?. But $? is true for executables and $LASTEXISTCODE is either null or garbage value for commands. I am out of ideas and struck here.
When in doubt, read the documentation:
$?
Contains the execution status of the last operation. It contains TRUE if the last operation succeeded and FALSE if it failed.
[…]
$LASTEXITCODE
Contains the exit code of the last Windows-based program that was run.
Basically, you need to check both. $? indicates whether the last PowerShell command/cmdlet was run successfully, whereas $LASTEXITCODE contains the exit code of the external program that was last executed.
if (-not $? -or $LASTEXITCODE -ne 0) {
throw '... whatever ...'
} else {
return $ret
}
However, Invoke-Expression is not a very good approach to executing commands. Depending on what you actually want to execute there are probably better ways to do it, with better methods for error handling.

How to redirect Access Denied and all other errors in Powershell to a variable (Loop until command doesnt fail)

I am trying to create a bootstrap script to setup servers and add them to the domain. The problem is some of the networking changes do not get implemented for a varying amount of time causing the command to join the domain to fail (always with the same error). TO get around this all I have to do is run the command again a minute later but this is not practical as part of a long script.
My solution is along the lines of:
$exit = 1
while ($exit -ne 0){
$exit = 0
join-domain-command
$exit = $?
sleep 20
}
However $exit is always 0/false even when I put in some non existent command that is bound to fail.
My other idea was to redirect all errors to a variable and search the variable for text containing the error I commonly come across and if that error is not present, stop the loop.
Trying to redirect the stderr in powershell doesn't work for me either
$exit = & (dummy-command -confirm:$false) 2>$exit
echo "exit = $exit"
SO here I deliberately set the ExecPol in an un-elevated prompt:
you can use -errorvariable
$exit = 1
while ($exit -ne 0){
$exit = 0
join-domain-command -errorvariable $error
$exit = $?
sleep 20
}
Or if you want to do something with the error:
try {
join-domain-command -errorvariable $error
}
catch{
$error | out-file C:\errors.txt -append
#or if $error -contains "something"
#...do something
}
then search the text file for your errors
EDIT
So a few things the actual correct use of errorVariable doesnt use the $ so it would be -errorvariable myError
If you want to search an error a better way to do it would be this:
while ($exit -ne 0)
{
try {
join-domain-command
}
catch{
if(!($error[0].tostring().contains("specific error text"))) #error text is not your specific error
{$exit = 1}
}
sleep 20
}
All errors can be found in $error and if you want to check that last error you use $error[0] which give you the last error that was received.
I usually do something like the following and I put it in a separate function to keep the main code path clean. A counter can be added to limit the number of retries.
$Succeeded = $false
while($Succeeded -eq $false){
try{
#commands that may fail
$Succeeded = $true
}
catch{
#write a message or log something
}
start-sleep -s 60
}

Powershell redirect std error to variable

I would like to invoke an arbitrary expression and redirect std error to a variable without redirection to a file.
For example, in Powershell it is possible to redirect standard error using 2> operator. Using a temporary file, I can easily get what I want:
#$expr = "1/0" # with std error
#$expr = "2+2" # without stderror
#$expr = "Get-Service invalidsvc" # with stderror
$expr = "try { throw '111' } catch { }" # without std error
$ans = Invoke-Expression $expr -ErrorVariable ev 2> C:\log\stderr.txt
if(cat C:\log\stderr){
Write-Host "Error $ev"
}
How can I do the same, but without creation of a temporal output file?
Wrong solutions:
Using -ErrorVariable switch. Counter example:
Invoke-Expression $expr -ErrorVariable ev 2> C:\aaa\file1.txt
$expr = "try { throw '111' } catch { }"
Write-Host "Error $ev" # will output error, but std error is empty
$LASTEXITCODE and $? check. Counter example:
Get-Service invalidservice
$lastexitcode is equal to 0, $? is equal to True, but std error is not empty
The idea is simple: save the "red" (std error) text in Powershell console in a variable. The command I receive is an arbitrary string.
Examples:
When I write "try { throw '111' } catch { }" in Powershell console there will be no red (error) text in PS console (despite the fact $error is not empty). So if I invoke that expression in my code I get no error saved in some variable.
When I write "Get-Service notexistingservice", or "1/0", or "Write-Error 111" there will red (error) text and non-null $error. So if I invoke that expression in my code I would like to get error saved in some variable.
Save standard output and standard error to separate variables. It won't work without the dollar sign (from Windows Powershell in Action).
$err = $( $output = get-childitem foo3 ) 2>&1
The way to do it is the -errorvariable common parameter. Your counter example is only valid (and I only hesitantly use that word) because you have explicitly coded for it to not output an error with the use of the Try/Catch and not including anything coding in your catch. You are basically complaining that you told PowerShell to send error cases to the Catch scriptblock, where you did not output anything, and then having an issue when nothing is output. An error still occurs, it is logged in the errorvariable as you stated it should be, and also stored in $Error, but since you did not output anything in your Catch block there's nothing for your StdErr redirect to do.
If you want $ev to not contain an error because you corrected the issue in your Catch block, then also clear the variable in the catch block.
$expr = 'try{ throw "1111"}catch{Remove-Variable ev}'
Or if you want StdErr to contain the error text, make sure you include that output in your Catch block:
$expr = 'try{ throw "1111"}catch{Write-Error $_.Exception.Message}'
I know this is a very old question but I had the same problem and wanted to share what I ended up with.
$error.clear()
$List = New-Object PSObject
Invoke-Command -ComputerName $server -ScriptBlock {
$smbv1 = (Get-SmbServerConfiguration | Select EnableSMB1Protocol)
$smbv1 | Select-Object EnableSMB1Protocol} -OutVariable List
foreach($item in $list.EnableSMB1Protocol){
if($error -ne $null){
$item = "unknown"
$msg = $error.Exception.Message
ac .\smb1errors.txt "$Server, $msg"
}
Since the $error variable is a .NET object in order to clear it I needed to pass it parameter (). I then executed my code, in this case checking for SMBv1 service, and testing if $error is still $null. If the $error variable has content I grabed it in a variable. $msg = $error.Exception.Message

Ouput redirection/capturing issue with Powershell and Try-Catch and external EXE

First off, either A) I'm not investigating into this hard enough or B) I've found a problem that requires some funky hack. By the way this is posh v1.0.
Here it goes:
A week or so ago I asked a question about redirecting the output from the exection of an EXE in powershell that was otherwise not being caught. I was swiftly presented with "2>&1" which solved the problem.
Now I've hit another snag and hope to see what some of you stackoverflowers can throw at it.
I'm using try-catch blocks throughout my code as a good programmer should. When I went to place a call to GPG (gnupg.org), passing it a few commands as follows:
try `
{
& $gpgExeLocation --import $keyFileName 2>&1 | out-file "theOutput.txt";
} `
-Catch `
{
write-host "$_";
}
I get a blank text file (theOutput.txt).
But if I do the same call outside of the try-catch block, the text file gets some text written to it as expected.
What I'm wondering is if there is an issue with output redirection to stdout and the way powershell traps exceptions - or if it is my try-catch code to begin with?
here is my try-catch implementation
function global:try
{
param
(
[ScriptBlock]$Command = $(Throw "The parameter -Command is required."),
[ScriptBlock]$Catch = { Throw $_ },
[ScriptBlock]$Finally = {}
)
& {
$local:ErrorActionPreference = "SilentlyContinue"
trap
{
trap
{
& {
trap { Throw $_ }
&$Finally
}
Throw $_
}
$_ | & { &$Catch }
}
&$Command
}
& {
trap { Throw $_ }
&$Finally
}
};
It appears you are using a custom Try function with a -Catch parameter. Mind sharing your implementation to see if that could be causing the problem?
BTW I doubt that your catch statement would ever be invoked unless you are converting the non-terminating error condition of $lastexitode -ne 0 to a terminating error. In this case, you may be better off with a function like this. I use it a lot (it's quite handy):
function Get-CallStack {
trap { continue }
1..100 | foreach {
$var = Get-Variable -scope $_ MyInvocation
$var.Value.PositionMessage -replace "`n"
}
}
#--------------------------------------------------------------------
# Helper function to deal with legacy exe exit codes
#--------------------------------------------------------------------
function CheckLastExitCode {
param ([int[]]$SuccessCodes = #(0), [scriptblock]$CleanupScript=$null)
if ($SuccessCodes -notcontains $LastExitCode) {
if ($CleanupScript) {
"Executing cleanup script: $CleanupScript"
&$CleanupScript
}
$OFS = $NL = [System.Environment]::NewLine
throw "EXE RETURNED EXIT CODE ${LastExitCode}${NL}$(Get-CallStack)"
}
}
Use it like so:
& $gpgExeLocation --import $keyFileName 2>&1 | out-file "theOutput.txt"
CheckLastExitCode