Powershell universal error logging with &{} 2>> and trap {} - powershell

I have been trying to come up with a better solution to error management than what I do now. Current practice is to test as much as possible to avoid errors, then try/catch and log exceptions. The problem is that that only works when I KNOW there's a possibility of an exception. What I have been trying to find is a way to have some sort of generic exception trap, that logs those exceptions with enough detail to actually troubleshoot.
What I have come up with is this
$errorLog = "$env:temp\errors.log"
# main
&{
# productive code here
} 2>> $errorLog
trap {
Out-File -InputObject $psItem.Exception.Message -FilePath $errorLog -Append
Out-File -InputObject $psItem.ScriptStackTrace -FilePath $errorLog -Append
Out-File -InputObject '' -FilePath $errorLog -Append
}
if (([System.IO.FileInfo]::new($errorLog)).Length -eq 0) {
Remove-Item $pxToolsErrorLog
} else {
Write-Host 'Errors'
}
This
&{
}
defines the main code block, and 2>> $pxToolErrorLog redirects standard errors in that code block to the log file.
The Trap outputs unhandled exceptions to the same log.
And the final conditional looks to see if I logged any errors, deletes the log if not, and let's me know if I did.
I have only played with this in a test scenario, but I am looking to implement it with a utility I am working on now, and I wonder if there is something I am missing that argues against this approach? Mostly because I am self taught, and regularly do silly things that I later discover are a bad idea, and I have never seen this approach or anything like it mentioned in the MANY blog posts and SO threads and such that I have read on the topic. It seems unlikely to me that I really did blunder in to something both new and better. So, does anyone see anything in this simple example that screams "No!!!!" ? Or something that makes the Spidey sense tingle? Or even just a bit of a code smell I am missing? Other than "You really SHOULD know what might cause an exception!" because I know that, and I am trying to learn that, but I want something a bit more robust to help in the meantime. I should mention that the ultimate goal is to use it in a program that will have MANY thousands of lines of code, and also use classes over functions to the extent possible, in no small part because I am sick and tired of chasing pipeline pollution issues and would prefer the rigor of types. So, any issues related to classes and scaling are especially pertinent.
Also, to clarify, I would STILL do things like test to see if a file exists before doing anything with that file, and also try/catch wherever I know there's potential for exceptions. I am just looking to get a log, with line numbers and other details, for anything I miss. Then I can address those issues with tests or try/catch. But that does raise a question in my mind, could this be a viable option for development, but SHOULD be removed for production code? Or is it safe/performant enough to keep in production code, with something more elegant than just dumping Errors to the console if errors occurred?

Related

powershell: suppress command output during assignment [duplicate]

When my PowerShell script tries, for example, to create a SQL Server object for a server that doesn't exist ("bla" in my case), PowerShell displays lots of PowerShell errors in red.
Since my script checks the value of $? after such calls, and displays and logs errors, I'd rather not have the several lines of PowerShell errors displayed as well.
How can I deactivate those being displayed for my script?
You have a couple of options. The easiest involve using the ErrorAction settings.
-Erroraction is a universal parameter for all cmdlets. If there are special commands you want to ignore you can use -erroraction 'silentlycontinue' which will basically ignore all error messages generated by that command. You can also use the Ignore value (in PowerShell 3+):
Unlike SilentlyContinue, Ignore does not add the error message to the $Error automatic variable.
If you want to ignore all errors in a script, you can use the system variable $ErrorActionPreference and do the same thing: $ErrorActionPreference= 'silentlycontinue'
See about_CommonParameters for more info about -ErrorAction.
See about_preference_variables for more info about $ErrorActionPreference.
Windows PowerShell provides two mechanisms for reporting errors: one mechanism for terminating errors and another mechanism for non-terminating errors.
Internal CmdLets code can call a ThrowTerminatingError method when an error occurs that does not or should not allow the cmdlet to continue to process its input objects. The script writter can them use exception to catch these error.
EX :
try
{
Your database code
}
catch
{
Error reporting/logging
}
Internal CmdLets code can call a WriteError method to report non-terminating errors when the cmdlet can continue processing the input objects. The script writer can then use -ErrorAction option to hide the messages, or use the $ErrorActionPreference to setup the entire script behaviour.
You can also append 2>$null to your command.
Example:
$rec = Resolve-DnsName $fqdn -Server $dns 2>$null
You're way off track here. Silencing errors is almost never a good idea, and manually checking $? explicitly after every single command is enormously cumbersome and easy to forget to do (error prone). Don't set yourself up to easily make a mistake. If you're getting lots and lots of red, that means your script kept going when it should have stopped instead. It can no longer do useful work if most of its commands are failing. Continuing a program when it and the system are in an unknown state will have unknown consequences; you could easily leave the system in a corrupt state.
The correct solution is to stop the algorithm on the first error. This principle is called "fail fast," and PowerShell has a built in mechanism to enable that behavior. It is a setting called the error preference, and setting it to the highest level will make your script (and the child scopes if they don't override it) behave this way:
$ErrorActionPreference = 'Stop'
This will produce a nice, big error message for your consumption and prevent the following commands from executing the first time something goes wrong, without having to check $? every single time you run a command. This makes the code vastly simpler and more reliable. I put it at the top of every single script I ever write, and you almost certainly should as well.
In the rare cases where you can be absolutely certain that allowing the script to continue makes sense, you can use one of two mechanisms:
catch: This is the better and more flexible mechanism. You can wrap a try/catch block around multiple commands, allowing the first error to stop the sequence and jump into the handler where you can log it and then otherwise recover from it or rethrow it to bubble the error up even further. You can also limit the catch to specific errors, meaning that it will only be invoked in specific situations you anticipated rather than any error. (For example, failing to create a file because it already exists warrants a different response than a security failure.)
The common -ErrorAction parameter: This parameter changes the error handling for one single function call, but you cannot limit it to specific types of errors. You should only use this if you can be certain that the script can continue on any error, not just the ones you can anticipate.
In your case, you probably want one big try/catch block around your entire program. Then your process will stop on the first error and the catch block can log it before exiting. This will remove a lot of duplicate code from your program in addition to cleaning up your log file and terminal output and making your program less likely to cause problems.
Do note that this doesn't handle the case when external executables fail (exit code nonzero, conventionally), so you do still need to check $LASTEXITCODE if you invoke any. Despite this limitation, the setting still saves a lot of code and effort.
Additional reliability
You might also want to consider using strict mode:
Set-StrictMode -Version Latest
This prevents PowerShell from silently proceeding when you use a non-existent variable and in other weird situations. (See the -Version parameter for details about what it restricts.)
Combining these two settings makes PowerShell much more of fail-fast language, which makes programming in it vastly easier.
I had a similar problem when trying to resolve host names using [system.net.dns]. If the IP wasn't resolved .Net threw a terminating error.
To prevent the terminating error and still retain control of the output, I created a function using TRAP.
E.G.
Function Get-IP
{PARAM ([string]$HostName="")
PROCESS {TRAP
{"" ;continue}
[system.net.dns]::gethostaddresses($HostName)
}
}
Add -ErrorAction SilentlyContinue to your script and you'll be good to go.
In some cases you can pipe after the command a Out-Null
command | Out-Null
To extend on Mikkel's answer.
If you still want to capture the error, you can use "-ErrorAction stop" combined with a try - catch.
"-ErrorAction silentlycontinue" will ignore the error.
For instance:
try
{
New-Item -Path "/somepath" -Name "somename" -ErrorAction Stop | Out-Null
}
catch
{
echo "You must run this command in an elevated mode."
}
NOTE: There is no "silentlyStop" action, and I believe Mickel's answer refers to the "stop" action. It is likely a typo.
The idea of using a try-catch combined with the "stop" action is to be able to not just dismiss eventual errors but to show something in case of errors.
If you want the powershell errormessage for a cmdlet suppressed, but still want to catch the error, use "-erroraction 'silentlyStop'"

add-content, stream not readable, root cause?

I have a script that is using Add-Content to log progress. The log files are written to a network share, and on some networks I have gotten stream not readable errors. Rare, but often enough to want to address the issue.
I found [this thread][1] that seems to offer an answer. And I initially implemented this loop
$isWritten = $false
do {
try {
Add-Content -Path $csv_file -Value $newline -ErrorAction Stop
$isWritten = $true
}
catch {
}
} until ( $isWritten )
but I added a 1 second wait between tries, and limited myself to 20 tries. I figured no network could be such crap that it would timeout for longer than that. But on one network I still have problems, so I bumped the count to 60, and STILL have failures to write to the log. I tried [System.IO.File]::AppendAllText($file, $line) and that seems to solve all the timeouts, at least in 20 some odd tries it hasn't failed, where before I would get 1 or two failures in 10 tries. But the formatting is off, likely I need to set the encoding.
But more importantly, I wonder what is actually the SOURCE of the issue in Add-Content, and why does [System.IO.File]::AppendAllText() not have the issue, and is this a sign of potentially other problems with the network, or with the machines at this one location? Or just a bug in PowerShell that I need to work around. FWIW, it's PS 5.1 on Windows 10 21H2.
Also, FWIW, the logs can get to a few hundred lines long, but I often see the error in the first 10 lines.
[1]: add-content produces stream not readable

How to ensure that a logging Cmdlet is called for every exception in a PowerShell script module?

In a PowerShell (5.1 and later) Script Module, I want to ensure that every Script and System exception which is thrown calls a logging Cmdlet (e.g. Write-Log). I know that I can wrap all code within the module Cmdlets into try/catch blocks, but my preferred solution would be to use trap for all exceptions thrown while executing Cmdlets of the Module
trap { Write-Log -Level Critical -ErrorRecord $_ }
Using the above statement works as intended if I add it to each Cmdlet inside the module, but I would like to only have one trap statement which catches all exceptions thrown by Cmdlets of the Module to not replicate code and also ensure that I do not miss the statement in any Cmdlet. Is this possible?
What I would do is this.
Set multiple Try/Catch block as needed.
Group multiple cmdlet calls under the same block when you can. As you mentionned, we don't want to group everything under 1 giant try/catch block but still, related calls can go together.
Design your in-module functions as Advanced functions, so you can make use of the common parameters, such as... -ErrorAction
Set $PSDefaultParameterValues = #{'*:ErrorAction'='Stop'} so all cmdlets that support -ErrorAction don't fall through the try/catch block.
(You could also manually set -ErrorAction Stop everywhere but since you want this as default, it make sense to do it that way. In any cases You don't want to touch $ErrorActionPreference as this has a global scope amd your users won't like you if you change defaults outside of the module scope.)
You can also redirect the error stream to a file so instead of showing up in the output, it is written to a file.
Here's a self contained example of this:
& {
Write-Warning "hello"
Write-Error "hello"
Write-Output "hi"
} 2>> 'C:\Temp\redirection.log'
See : About_Redirection for more on this.
(Now I am wondering if you can redirect the stream to something else than a file
Additional note
External modules can help with logging too and might provide a more streamlined approach.
I am not familiar with any of them though.
I know PSFramework have some interesting stuff regarding logging.
You can take a look and experiment to see if fit your needs.
Otherwise, you can do a research on PSGallery for logging modules
(this research is far from perfect but some candidates might be interesting)
find-module *logging* | Select Name, Description, PublishedDate,Projecturi | Sort PublishedDate -Descending

Powershell: errorhandling in script

I have a script which are going to run on several local computers, but I haven't done any errorhandling in it. I have not done any errorhandling in powershell at all, so i'm a total noob here. I read something about it, but honestly I am just looking for an quick and easy answer..
Q: Is there something like a try/catch, as little code as possible to not make the script heavy weight?
Yes, try/catch/finally is available in Powershell v2:
Try{
<main code here>
}
Catch{
write-host $_ #Using the reserved $_ variable which should contain the error string
}
Finally{
<clean up code here - will execute regardless>
}
If you're constrained to use Powershell v1 then you will have to use the trap construct.
Powershell 2.0 has try, catch and finally. See http://technet.microsoft.com/en-us/library/dd315350.aspx You can also do a more command-line oriented variation of error handling with error trapping like here: http://www.informit.com/articles/article.aspx?p=729515&seqNum=5

Powershell: How can I stop errors from being displayed in a script?

When my PowerShell script tries, for example, to create a SQL Server object for a server that doesn't exist ("bla" in my case), PowerShell displays lots of PowerShell errors in red.
Since my script checks the value of $? after such calls, and displays and logs errors, I'd rather not have the several lines of PowerShell errors displayed as well.
How can I deactivate those being displayed for my script?
You have a couple of options. The easiest involve using the ErrorAction settings.
-Erroraction is a universal parameter for all cmdlets. If there are special commands you want to ignore you can use -erroraction 'silentlycontinue' which will basically ignore all error messages generated by that command. You can also use the Ignore value (in PowerShell 3+):
Unlike SilentlyContinue, Ignore does not add the error message to the $Error automatic variable.
If you want to ignore all errors in a script, you can use the system variable $ErrorActionPreference and do the same thing: $ErrorActionPreference= 'silentlycontinue'
See about_CommonParameters for more info about -ErrorAction.
See about_preference_variables for more info about $ErrorActionPreference.
Windows PowerShell provides two mechanisms for reporting errors: one mechanism for terminating errors and another mechanism for non-terminating errors.
Internal CmdLets code can call a ThrowTerminatingError method when an error occurs that does not or should not allow the cmdlet to continue to process its input objects. The script writter can them use exception to catch these error.
EX :
try
{
Your database code
}
catch
{
Error reporting/logging
}
Internal CmdLets code can call a WriteError method to report non-terminating errors when the cmdlet can continue processing the input objects. The script writer can then use -ErrorAction option to hide the messages, or use the $ErrorActionPreference to setup the entire script behaviour.
You can also append 2>$null to your command.
Example:
$rec = Resolve-DnsName $fqdn -Server $dns 2>$null
You're way off track here. Silencing errors is almost never a good idea, and manually checking $? explicitly after every single command is enormously cumbersome and easy to forget to do (error prone). Don't set yourself up to easily make a mistake. If you're getting lots and lots of red, that means your script kept going when it should have stopped instead. It can no longer do useful work if most of its commands are failing. Continuing a program when it and the system are in an unknown state will have unknown consequences; you could easily leave the system in a corrupt state.
The correct solution is to stop the algorithm on the first error. This principle is called "fail fast," and PowerShell has a built in mechanism to enable that behavior. It is a setting called the error preference, and setting it to the highest level will make your script (and the child scopes if they don't override it) behave this way:
$ErrorActionPreference = 'Stop'
This will produce a nice, big error message for your consumption and prevent the following commands from executing the first time something goes wrong, without having to check $? every single time you run a command. This makes the code vastly simpler and more reliable. I put it at the top of every single script I ever write, and you almost certainly should as well.
In the rare cases where you can be absolutely certain that allowing the script to continue makes sense, you can use one of two mechanisms:
catch: This is the better and more flexible mechanism. You can wrap a try/catch block around multiple commands, allowing the first error to stop the sequence and jump into the handler where you can log it and then otherwise recover from it or rethrow it to bubble the error up even further. You can also limit the catch to specific errors, meaning that it will only be invoked in specific situations you anticipated rather than any error. (For example, failing to create a file because it already exists warrants a different response than a security failure.)
The common -ErrorAction parameter: This parameter changes the error handling for one single function call, but you cannot limit it to specific types of errors. You should only use this if you can be certain that the script can continue on any error, not just the ones you can anticipate.
In your case, you probably want one big try/catch block around your entire program. Then your process will stop on the first error and the catch block can log it before exiting. This will remove a lot of duplicate code from your program in addition to cleaning up your log file and terminal output and making your program less likely to cause problems.
Do note that this doesn't handle the case when external executables fail (exit code nonzero, conventionally), so you do still need to check $LASTEXITCODE if you invoke any. Despite this limitation, the setting still saves a lot of code and effort.
Additional reliability
You might also want to consider using strict mode:
Set-StrictMode -Version Latest
This prevents PowerShell from silently proceeding when you use a non-existent variable and in other weird situations. (See the -Version parameter for details about what it restricts.)
Combining these two settings makes PowerShell much more of fail-fast language, which makes programming in it vastly easier.
I had a similar problem when trying to resolve host names using [system.net.dns]. If the IP wasn't resolved .Net threw a terminating error.
To prevent the terminating error and still retain control of the output, I created a function using TRAP.
E.G.
Function Get-IP
{PARAM ([string]$HostName="")
PROCESS {TRAP
{"" ;continue}
[system.net.dns]::gethostaddresses($HostName)
}
}
Add -ErrorAction SilentlyContinue to your script and you'll be good to go.
In some cases you can pipe after the command a Out-Null
command | Out-Null
To extend on Mikkel's answer.
If you still want to capture the error, you can use "-ErrorAction stop" combined with a try - catch.
"-ErrorAction silentlycontinue" will ignore the error.
For instance:
try
{
New-Item -Path "/somepath" -Name "somename" -ErrorAction Stop | Out-Null
}
catch
{
echo "You must run this command in an elevated mode."
}
NOTE: There is no "silentlyStop" action, and I believe Mickel's answer refers to the "stop" action. It is likely a typo.
The idea of using a try-catch combined with the "stop" action is to be able to not just dismiss eventual errors but to show something in case of errors.
If you want the powershell errormessage for a cmdlet suppressed, but still want to catch the error, use "-erroraction 'silentlyStop'"