Powershell: Trap continue breaks entire loop - powershell

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;
}

Related

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

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:"
$_
}
}

How Do I Exit While Loop From Within a Switch Statement

In PowerShell, how do I exit this while loop that is nested inside a switch statement, without executing the code immediately following the while block? I can't seem to figure it out. Everything I've tried so far results in that block of code being executed.
Here's what I'm trying to accomplish:
Check for the presence of a file and notify user if file is
detected.
Check again every 10 seconds and notify user
If the file is not detected, then exit the loop and switch, and continue
with Step #2
If the file is still detected after 30 seconds, timeout and exit
the script entirely.
Here's the code:
try {
#Step 1
$Prompt = <Some Notification Dialog with two buttons>
switch ($Prompt){
'YES' {
# Display the Windows Control Panel
#Wait for user to manually uninstall an application - which removes a file from the path we will check later.
$Timeout = New-Timespan -Seconds 30
$Stopwatch = [Dispatch.Stopwatch]::StartNew()
while ($Stopwatch.elapsed -lt $Timeout) {
if (Test-Path -Path "C:\SomeFile.exe" -PathType Leaf) {
Write-Host "The file is still there, remove it!"
return
}
Start-Sleep 10
}
#After timeout is reached, notify user and exit the script
Write-Host "Timeout reached, exiting script"
Exit-Script -ExitCode $mainExitCode #Variable is declared earlier in the script
}
'NO' {
# Do something and exit script
}
}
# Step 2
# Code that does something here
# Step 3
# Code that does something here
} catch {
# Error Handling Code Here
}
You can use break with a label, to exit a specific loop (a switch statements counts as a loop), see about_break.
$a = 0
$test = 1
:test switch ($test) {
1 {
Write-Output 'Start'
while ($a -lt 100)
{
Write-Output $a
$a++
if ($a -eq 5) {
break test
}
}
Write-Output 'End'
}
}
Write-Output "EoS"
Is that what you see without the try/catch? I get an exception: Unable to find type [Dispatch.Stopwatch]. Otherwise the return works ok for me.
I think what you want is break with a label going outside the switch? Then steps 2 & 3 will run. I altered the code to make a manageable example. This is more ideal when asking a question. I don't know what exit-script is.
echo hi > file
#try {
#Step 1
$Prompt = '<Some Notification Dialog with two buttons>'
$Prompt = 'yes'
:atswitch switch ($Prompt){
'YES' {
'Display the Windows Control Panel'
#Wait for user to manually uninstall an application - which removes a file from the path we will check later.
$Timeout = New-Timespan -Seconds 30
#$Stopwatch = [Dispatch.Stopwatch]::StartNew()
while (1) {
if (Test-Path -Path "file" -PathType Leaf) {
Write-Host "The file is still there, remove it!"
break atswitch
}
Start-Sleep 10
}
#After timeout is reached, notify user and exit the script
Write-Host "Timeout reached, exiting script"
'Exit-Script -ExitCode $mainExitCode #Variable is declared earlier in the script'
}
'NO' {
'Do something and exit script'
}
}
# Step 2
'Code that does something here'
# Step 3
'Code that does something here2'
#} catch {
# 'Error Handling Code Here'
#}

Why aren't error thrown when I run this function as domain administrator?

This script is intended to recurse through a series of directories and when an error of type DirUnauthorizedAccessError,Microsoft or PowerShell.Commands.GetChildItemCommand is thrown it's supposed to call another function Take-Ownership which takes ownership of the directory and adds full permissions for the localAdmin and domain admin to the folder. (It's really a script used for easing the deletion of old user profiles):
function Test-Folder($FolderToTest, $localAdminName) {
# Remeber the old error preference...
$old_ErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
$error.Clear()
# Go through the directories...and capture errors in $error
Get-ChildItem $FolderToTest -Recurse -ErrorAction SilentlyContinue -ErrorVariable errz | Select FullName
Write-Host $errz.count
if ($errz.Count -eq 0) {
Write-Host "blah no errors"
foreach ($err in $errz) {
Write-Host "Error: $err"
if ($err.FullyQualifiedErrorId -eq "DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand") {
Write-Host Unable to access $err.TargetObject -Fore Red
Write-Host Attempting to take ownership of $err.TargetObject -Fore Yellow
Take-Ownership -Folder $err.TargetObject, -LocalAdminName $localAdminName
Test-Folder -FolderToTest $err.TargetObject -localAdminName $localAdminName
}
}
}
$ErrorActionPreference = $old_ErrorActionPreference
}
Unfortunately, it doesn't throw any errors when I run it as domain administrator. I've found a list of ErrorActionPreferences here, but the errors just seem to get ignored, and it outputs blah no errors What can I do to make sure I receive errors and that my Take-Ownership function is actually called?
Your code only enters the if block if $errz.Count is 0. With a count of 0 there are no elements in $errz, so there's nothing to do for the foreach loop.
Add an else branch to the conditional, move the foreach loop there, and the code should do what you want.
if ($errz.Count -eq 0) {
Write-Host "blah no errors"
} else {
foreach ($err in $errz) {
Write-Host "Error: $err"
...
}
}

Powershell - Error handling (Dont invoke finally block if Catch block has been invoked)

In Powershell, it seem like it will always execute Finally block. Is there a way to tell Powershell not execute statement in Finally block if Catch block
has been executed. Example my below code, if email has been trigger then I don't want powershell generate log file.
Catch
{
$ErrorMessage = $_.Exception.Message
$FailedItem = $_.Exception.ItemName
Send-MailMessage -From xxxxxxxxxx
Break
}
Finally
{
$Time=(Get-Date).ToString('MM/dd/yyyy hh:mm:ss tt')
"This script made a read attempt at" + "$d1" | out-file $logfile -append
}
I do not endorse using this code, but to answer your question, yes, it is possible doing something like this.
$badIdea = $true
try
{
Write-Output "About to do something dumb."
$ugh = 1 / 0;
Write-Output "Did I do something dumb?"
}
catch
{
Write-Output "Caught something dumb."
$badIdea = $false
}
finally
{
if ($badIdea -eq $true)
{
Write-Output "Performing finally..."
}
else
{
Write-Output "Skipping finally..."
}
}
Code does what you tell it...and you can use conditional statements anywhere you want.
This produces:
About to do something dumb.
Caught something dumb.
Skipping finally...
The purpose of the Finally clause is to always execute, whether an exception
occurred or not.
Thus, as Jason Boyd points out in a comment on the question, you would typically place code you only want executed if no exception occurred after the try statement.
break in your Catch handler ensures that any code below the try statement is only reached in case no exception occurred - but note that break simply breaks out of any enclosing loop - even across function and script boundaries(!); only if there is no enclosing loop is break equivalent to exiting the enclosing script.

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
}