Call Function on all errors in Powershell Script - powershell

$player = New-Object System.Media.SoundPlayer "C:\Error-Sound.wav"
$ErrorActionPreference = "$player.Play()"
my code is not working
i want this when it face any error in the whole Powershell Script to call function
any help ?

You're probably looking to play a sound whenever the script fails, if that's the case then:
$ErrorActionPreference = 'Stop'
$player = New-Object System.Media.SoundPlayer "C:\Error-Sound.wav"
try
{
# Script do something here
}
catch
{
$player.PlaySync() # Play a sound if Error
Write-Warning $_.Exception.Message # Display exception in the console
}

For script-wide error handling, use the trap statement, as described in the conceptual about_Trap help topic.
Note that a trap only traps terminating errors by default, so that a non-terminating error such as Get-ChildItem NoSuchir would not be caught.
To also trap non-terminating errors, set $ErrorActionPreference = 'Stop' (the $ErrorActionPreference preference variable only accepts predefined values denoting an abstract action to perform, such as Stop). This promotes non-terminating to (script-)terminating errors, which trap then traps.
For more fine-grained handling of terminating errors, consider use of try / catch/ finally statements, discussed in about_Try_Catch_Finally.
See this answer for examples of both.
A simple example for your use case:
trap {
(New-Object System.Media.SoundPlayer "C:\Error-Sound.wav").PlaySync()
# `break` causes the script to abort.
# `continue` does too, but without issuing the error
# Using neither continues execution.
break
}
$ErrorActionPreference = 'Stop'
# Provoke an error
Get-ChildItem NoSuchDir
Santiago's helpful answer shows you an equivalent try / catch solution.

The $ErrorActionPreference variable takes one of the ActionPreference enumeration values: SilentlyContinue, Stop, Continue, Inquire, Ignore, Suspend, or Break.
Post some of your code but unless your $player.play is one of the above actions then you're not using this correctly.
# Change the ErrorActionPreference to 'Continue'
$ErrorActionPreference = 'Continue'
# Generate a non-terminating error and continue processing the script.
Write-Error -Message 'Test Error' ; Write-Host 'Hello World'

Related

How can I change the default PowerShell error for when a command needs elevation?

I have a lot of code in a PowerShell script that are mix of commands that need elevation to run and commands that don't, those that need elevation show errors in PowerShell console like:
"You don't have enough permissions to perform the requested operation"
and
"Requested registry access is not allowed."
is there a way to globally suppress only the kinds of errors that PowerShell shows due to lack of necessary privileges?
I thought about a function that checks for elevation and performs actions based on the result, like this:
https://devblogs.microsoft.com/scripting/use-function-to-determine-elevation-of-powershell-console/
Function Test-IsAdmin
{
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal $identity
$principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
and
if(-NOT (Test-IsAdmin))
{ write-host "Skipping Admin command" }
else { $code }
but I don't know how to apply it globally to the whole script, so that commands that don't need elevation run, and those that need elevation show a custom message or skip that part silently.
another thing that can help my situation would be to find out if a PowerShell command needs elevation before actually running it and causing it to show errors in console due to lack of privileges.
It seems that errors stemming from a lack of privileges typically - but not necessarily - involve a System.UnauthorizedAccessException or System.Security.SecurityException .NET exception behind the scenes, whose name is then reflected as part of the .FullyQualifiedErrorId property of the resulting PowerShell error record, which is of type System.Management.Automation.ErrorRecord.
Assuming that this applies to all errors you care about, you can use a (rarely used anymore) trap statement as follows:
trap {
if ($_.FullyQualifiedErrorId -match 'UnauthorizedAccessException|SecurityException') {
Write-Warning "Skipping admin command ($($_.InvocationInfo.Line.Trim()))"
continue # Suppress the original error and continue.
}
# If the error was created with `throw`, emit the error and abort processing.
# SEE CAVEAT BELOW.
elseif ($_.Exception.WasThrownFromThrowStatement) { break }
# Otherwise: emit the error and continue.
}
# ... your script
Caveat:
If your script implicitly raises script-terminating errors - via -ErrorAction Stop or $ErrorActionPreference = 'Stop' - the above solution in effect turns them into statement-terminating errors and continues execution (only explicit script-terminating errors created with a throw statement are recognized as such in the code above, and result in the script getting aborted).
Unfortunately, as of PowerShell 7.2.x, there is no way to generally discover whether a given error is (a) non-terminating, (b) statement-terminating or (c) script-terminating (fatal).
See GitHub issue #4781 for a proposal to add properties to [System.Management.Automation.ErrorRecord] to allow such discovery in the future.

Catching script-terminating errors only in PowerShell

PowerShell 5.1
I've written a wrapper script that launches another script, logs its output (via Start-Transcript), sends an email if it exited with a non-zero code, and finally rotates the log file based on file size.
I only want to catch errors that would cause the called script to exit when executed on its own.
(Script-terminating term based on the discussion here: https://github.com/MicrosoftDocs/PowerShell-Docs/issues/1583).
try {
Invoke-Expression "& '$Script' $ArgList"
$err = $LastExitCode
} catch {
Write-Error -ErrorRecord $_
$err = 1
}
Catch this:
$ErrorActionPreference = 'Stop'
Remove-Item -Path DoesntExist
or this:
throw "Error"
But not this (script should continue executing as it would if not called via the logging script):
Test-Path -Foo bar
Is it possible? Or is there a better approach?
A try { ... } catch { ... } statement makes no distinction between a statement-terminating (Test-Path -Foo bar) and a script-terminating error (throw "Error"): both sub-types of terminating errors trigger the catch block.
In other words: Any intrinsically statement-terminating error, such as 1 / 0 or Test-Path -Foo bar (an invocation-syntax error, because there is no such parameter) will invariably also trigger your catch block.
So will any non-terminating error that is promoted to a script-terminating one, either with $ErrorActionPreference = 'Stop' or -ErrorAction Stop (which is what you want).
As an aside: $ErrorActionPreference = 'Stop' - unlike -ErrorAction Stop - will also promote statement-terminating errors to script-terminating ones, which is just one of the problematic aspects of PowerShell's surprisingly complex error handling, discussed in detail in the GitHub docs issue linked to in your question.
Related information (which won't solve your problem):
With respect to post-mortem error analysis, the best you can do to analyze what kind of error occurred is to check if it was a script-terminating one triggered by a throw statement, by examining $_.Exception.WasThrownFromThrowStatement, but you won't generally be able to distinguish between a statement-terminating error, a non-terminating error, and a non-terminating error promoted to a script-terminating one.
GitHub issue #4781 discusses a potential future improvement for detecting the intrinsic vs. effective "fatality" of an error.

Powershell: cannot hide 'Access is denied' error on 'Remove-Item'

I would like to keep a Remove-Item instruction quiet, exception or not. I'm running below command in a script to delete a certificate:
Remove-Item $store\$thumbprint
If I run the script as local Admin, fine... it keeps quiet and the file is deleted.
If however I run it as unpriviledged user, I get an 'Access in denied' error as expected, but I would like to keep this quiet in any case.
I've tried the following:
$output = (Remove-Item $store\$thumbprint)
# or...
try{Remove-Item $store\$thumbprint} catch{}
# or...
Remove-Item $store\$thumbprint -ErrorAction SilentlyContinue
But I always get the error/exception
displayed on the console.
By default, a non-terminating error is generated by Remove-Item and it adds an error to the $Error variable without throwing an exception. To see what Windows PowerShell will do when a non-terminating error arises, look at the value of the $ErrorActionPreference variable (its default value is Continue).
The Access to the path '…' is denied is an example of such a non-terminating error so you can use ErrorAction parameter which overrides the value of the $ErrorActionPreference variable for the current command:
Remove-Item $store\$thumbprint -ErrorAction SilentlyContinue
On the other side, $ErrorActionPreference and the ErrorAction parameter don't affect how PowerShell responds to terminating errors that stop cmdlet processing. So if we are not sure whether an error is terminating or not then it's safe to handle any error the Try-Catch-Finally blocks using -ErrorAction Stop as follows:
try {
Remove-Item $store\$thumbprint -ErrorAction Stop
} catch {
### A Catch block can include commands for tracking the error
### or for recovering the expected flow of the script
}

What's the right way to emit errors in powershell module functions?

I've read quite a bit on powershell error handling and now I'm quite confused about what I should be doing on any given situation (error handling). I'm working with powershell 5.1 (not core).
With that said:
Suppose I have a module with a function that would look like this mock:
function Set-ComputerTestConfig {
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $Name)
begin { ... }
process {
# task 1
# task 2 => results in a failure that prevents further tasks
# task 3
# task 4
}
end { ... }
Let's say that for each computer name that I pass to this function, I have 4 tasks to complete, but if any of the tasks fail, I can't continue with the remaining tasks.
How should I be producing an error (best practice) such that it halts "process" for this particular computer name but effectively continues to process the pipeline?
If you want to continue processing inputs from the pipeline, you must emit a non-terminating error:
Write-Error writes non-terminating errors; it writes to PowerShell's error stream without generating an exception behind the scenes; execution continues normally.
If a .NET method call is the error source, as in your case, wrap it in try / catch, and call Write-Error -ErrorRecord $_ in the catch block:
try { <#task 1 #>; ... } catch { Write-Error -ErrorRecord $_ }
Unfortunately, still as of PowerShell Core 7.0.0-preview.4, Write-Error doesn't fully behave as expected, in that it doesn't set the automatic success-status variable, $?, to $false in the caller's context, as it should. The only workaround at present is to make sure that your function/script is an advanced one and to use $PSCmdlet.WriteError(); from a catch block you can simply use $PSCmdlet.WriteError($_), but crafting your own error from scratch is cumbersome - see GitHub issue #3629.
If you want processing to stop right away, use a terminating error:
throw creates terminating errors.
Unfortunately, throw creates a more fundamental kind of terminating error than binary cmdlets emit: unlike the statement-terminating errors emitted by (compiled) cmdlets, throw creates a script-terminating (fatal) error.
That is, by default a binary cmdlet's statement-terminating error only terminates the statement (pipeline) at hand and continues execution of the enclosing script, whereas throw by default aborts the entire script (and its callers).
GitHub issue #14819 discusses this asymmetry.
Again, the workaround requires that your script/function is an advanced one, which enables you to call $PSCmdlet.ThrowTerminatingError() instead of throw, which properly generates a statement-terminating error; as with $PSCmdlet.WriteError(), you can simply use $PSCmdlet.ThrowTerminatingError($_) from a catch block, but crafting your own statement-terminating error from scratch is cumbersome.
As for $ErrorActionPreference = 'Stop'
This turns all error types into script-terminating errors, and at least advanced functions / scripts - those expected to act like cmdlets - should not set it.
Instead, make your script / function emit the appropriate types of errors and let the caller control the response to them, either via the common -ErrorAction parameter or via the $ErrorActionPreference variable.
Caveat: Functions in modules do not see the caller's preference variables, if the caller is outside a module or in a different module - this fundamental problem is discussed in GitHub issue #4568.
As for passing errors through / repackaging them from inside your function script:
Non-terminating errors are automatically passed through.
If needed, you can suppress them with -ErrorAction Ignore or 2>$null and optionally also collect them for later processing with the -ErrorVariable common parameter (combine with -ErrorAction SilentlyContinue).
Script-terminating errors are passed through in the sense that the entire call stack is terminated by default, along with your code.
Statement-terminating errors are written to the error stream, but by default your script / function continues to run.
Use try { ... } catch { throw } to instead turn them into script-terminating errors, or ...
... use $PSCmdlet.ThrowTerminatingError($_) instead of throw to relay the error as a statement-terminating one.
Further reading:
Guidance on when to emit a terminating vs. a non-terminating error is in this answer.
A comprehensive overview of PowerShell's error handling is in GitHub docs issue #1583.
Use Try...Catch - make sure all commands use -ErrorAction Stop switch or set the environment to stop on error e.g. $ErrorActionPreference = 'Stop'
function Set-ComputerTestConfig {
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $Name)
begin { ... }
process {
Try {
# task 1
# task 2 => results in a failure that prevents further tasks
# task 3
# task 4
}
Catch {
Write-Host "One of the tasks has failed`nError Message:"
Write-Host $_
}
}
end { ... }

Script stops execution when faces an error

I have a master script master.ps1 which calls two scripts One.ps1 and Two.ps1 like:
&".\One.ps1"
&".\Two.ps1"
When the One.ps1 script has an error, the execution gets stopped without continuing the execution of Two.ps1
How to continue execution of Two.ps1 even if there is an error in One.ps1?
You have to set the $ErrorActionPreference to continue:
Determines how Windows PowerShell responds to a non-terminating
error (an error that does not stop the cmdlet processing) at the
command line or in a script, cmdlet, or provider, such as the
generated by the Write-Error cmdlet.
You can also use the ErrorAction common parameter of a cmdlet to
override the preference for a specific command.
Source.
$ErrorActionPreference = 'continue'
Note: As a best practice I would recommend to first determine the current error action preference, store it in a variable and reset it after your script:
$currentEAP = $ErrorActionPreference
$ErrorActionPreference = 'continue'
&".\One.ps1"
&".\Two.ps1"
$ErrorActionPreference = $currentEAP
#Martin is correct assuming that the success or failure of .\One.ps1 does not impact .\Two.ps1 and if you don't care about logging or otherwise dealing with the error. but if you would prefer to handle the error rather than just continue past it you could also use a Try{}Catch{} block as below to log the error (or take any other action you would like in the Catch{})
Try{
&".\One.ps1"
} Catch {
$error | Out-File "OneError.txt"
}
Try{
&".\Two.ps1"
} Catch {
$error | Out-File "TwoError.txt"
}
Other ways to format this but you get the idea.