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 atry
/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 thecatch
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.
- Introduction to Error Action in PowerShell
- Use the
-ErrorAction
Parameter in PowerShell - Setting Error Action Preferences in PowerShell
Whether we want to ignore error messages or terminate a script’s execution when an error occurs, Windows PowerShell has plenty of options for dealing with errors. This article will discuss multiple techniques in handling and suppressing errors.
Introduction to Error Action in PowerShell
Even though it is effortless to suppress Windows PowerShell errors, doing so isn’t always the best option (although it can be). If we carelessly tell PowerShell to hide errors, it can cause our script to behave unpredictably.
Suppressing error messages also makes troubleshooting and information gathering a lot more complicated. So tread lightly and be careful about using the following snippets that you will see in this article.
Use the -ErrorAction
Parameter in PowerShell
The most common method for dealing with errors is to append the -ErrorAction
parameter switch to a cmdlet. The -ErrorAction
parameter switch lets PowerShell tell what to do if the cmdlet produces an error.
Command:
Get-Service 'svc_not_existing' -ErrorAction SilentlyContinue
In the command above, we are querying for a service that doesn’t exist. Usually, PowerShell will throw an error if the service doesn’t exist.
Since we use the -ErrorAction
parameter, the script will continue as expected, like it doesn’t have an error.
Setting Error Action Preferences in PowerShell
If we need a script to behave in a certain way (such as suppressing errors), we might consider setting up some preference variables. Preference variables act as configuration settings for PowerShell.
We might use a preference variable to control the number of history items that PowerShell retains or force PowerShell to ask the user before performing specific actions.
For example, here is how you can use a preference variable to set the -ErrorAction
parameter to SilentlyContinue
for the entire session.
Command:
$ErrorActionPreference = 'SilentlyContinue'
There are many other error actions that we can specify for the ErrorAction
switch parameter.
Continue
: PowerShell will display the error message, but the script will continue to run.Ignore
: PowerShell does not produce any error message, writes any error output on the host, and continues execution.Stop
: PowerShell will display the error message and stop running the script.Inquire
: PowerShell displays the error message but will ask for confirmation first if the user wants to continue.SilentlyContinue
: PowerShell silently continues with code execution if the code does not work or has non-terminating errors.Suspend
: PowerShell suspends the workflow of the script.
As previously mentioned, the -ErrorAction
switch has to be used in conjunction with a PowerShell cmdlet. For instance, we used the Get-Process
cmdlet to demonstrate how the ErrorAction
switch works.
Powershell ErrorAction это ключ, с помощью которого мы можем обходить часть ошибок. Это помогает остановить скрипт, если будет ошибка.
У меня есть две директории, одна которая существует, а другая нет. Если я выполню поиск файлов, то скрипт выведет ошибку:
$path = 'C:\NotExist\','C:\Exist\'
$path | Get-ChildItem
Get-ChildItem : Cannot find path ‘C:\NotExist\’ because it does not exist.
Что бы этого избежать у нас есть ключ ErrorAction со следующими значениями:
- Continue или 0 — значение по умолчанию. Ошибка выводится на экран, но работа скрипта продолжается.
- SilentlyContinue или 1 — ошибка не выводится на экран и скрипт продолжает работу.
- Stop или 2 — останавливает выполнение при ошибке.
- Ignore или 3 — игнорирует ошибки и при этом никакие логи об ошибке не сохраняются.
- Inquire — с этим ключом у нас будет запрос на дальнейшее действия.
- Suspend — работает при режиме Workflow (рабочих процессов). К обычным командлетам не имеет отношения.
Пример со значением по умолчанию:
$services = "NotExist","NetLogon"
$services | Get-Service -ErrorAction Continue
В случае с SilentContinue у нас ошибок не будет:
Get-Process -Name "NotExist","svchost" -ErrorAction SilentlyContinue
При этом, конечно, ключ мы должны указывать везде где ожидаем увидеть ошибку:
Если мы выполним такую команду, то все равно получим ошибку, которая остановит процесс:
Get-Variable -Name $null -ErrorAction SilentlyContinue
Это ошибка, которая прерывает процесс и для нее нужно использовать другие методы в виде try и catch.
…
Теги:
#powershell
Important to note is that some Powershell cmdlets do not behave correctly with these methods. Such an example is Get-ADUser
which will output the error in case of failure no matter what trick you use:
PS C:\Temp\2022-09-05T08-28-46_oexvijzd.tfq> Get-ADUser -Identity "CN=administrator,DC=acme,DC=internal" 2>&1 | Out-Null
Get-ADUser : Directory object not found
At line:1 char:1
+ Get-ADUser -Identity "CN=administrator,DC=acme,DC=internal" 2>&1 | Ou ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (CN=administrator,DC=acme,DC=internal:ADUser) [Get-ADUser], ADIdentityNotFoundException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException,Microsoft.ActiveDirectory.Management.Commands.GetADUser
PS C:\Temp\2022-09-05T08-28-46_oexvijzd.tfq> Get-ADUser -Identity "CN=administrator,DC=acme,DC=internal" *>$null
Get-ADUser : Directory object not found
At line:1 char:1
+ Get-ADUser -Identity "CN=administrator,DC=acme,DC=internal" *>$null
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (CN=administrator,DC=acme,DC=internal:ADUser) [Get-ADUser], ADIdentityNotFoundException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException,Microsoft.ActiveDirectory.Management.Commands.GetADUser
PS C:\Temp\2022-09-05T08-28-46_oexvijzd.tfq> Get-ADUser -Identity "CN=administrator,DC=acme,DC=internal" *>$null | Out-Null *>$null
Get-ADUser : Directory object not found
At line:1 char:1
+ Get-ADUser -Identity "CN=administrator,DC=acme,DC=internal" *>$null | ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (CN=administrator,DC=acme,DC=internal:ADUser) [Get-ADUser], ADIdentityNotFoundException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException,Microsoft.ActiveDirectory.Management.Commands.GetADUser
In these cases, you will have to make try
/catch
blocks, or find other alternatives (e.g. Checking for the existence of an AD object; how do I avoid an ugly error message?).
Hope this will save someone some unproductive head scratching.
I am trying to see if a process is running on multiple servers and then format it into a table.
get-process -ComputerName server1,server2,server3 -name explorer | Select-Object processname,machinename
Thats the easy part — When the process does not exist or if the server is unavailable, powershell outputs a big ugly error, messes up the the table and doesn’t continue. Example
Get-Process : Couldn't connect to remote machine.At line:1 char:12 + get-process <<<< -ComputerName server1,server2,server3 -name explorer | format-table processname,machinename
+ CategoryInfo : NotSpecified: (:) [Get-Process], InvalidOperatio nException + FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.Power Shell.Commands.GetProcessCommand
How do I get around this? If the I would still like to get notified if the process isn’t available or Running.