Powershell on error resume next

Avoiding «ON ERROR RESUME NEXT» when Using Powershell In Powershell and the Principle of Most Astonishment, I covered some of the surprises that new Powershell users encounter when trying to reliably return results from functions. The next area where Powershell suprises new users is in its approach to error handling. Fortunately, there are some […]

Содержание

  1. Avoiding «ON ERROR RESUME NEXT» when Using Powershell
  2. Hey, Scripting Guy! How Can I Use $ErrorActionPreference to Control Cmdlet Handling of Errors?
  3. Обработка ошибок в PowerShell (часть 1)
  4. Обработка непрерывающих ошибок
  5. Переменные для обработки ошибок

Avoiding «ON ERROR RESUME NEXT» when Using Powershell

In Powershell and the Principle of Most Astonishment, I covered some of the surprises that new Powershell users encounter when trying to reliably return results from functions. The next area where Powershell suprises new users is in its approach to error handling. Fortunately, there are some useful workarounds for making the surprising default behavior work more like you would expect.

While learning Powershell, I was trying to create a deployment script. The script needed to perform several tasks including, copying the deployment package to the target machine, setting up services, and the like. While testing it out locally, I would deliberately cause certain steps to fail in order to ensure that the user of the script would be clearly alerted to failures. To my surprise, when I caused exceptions to be thrown, the script would happily continue on to the next step, ultimately printing a success message to the user. Consider this example:

What the heck just happened? If you can’t rely on uncaught exceptions to stop execution, how can you reliably deal with failures? What if we throw an exception and then blindly move along to a subsequent step that depends on the success of previous steps?

Powershell reintroduces VB’s “ON ERROR RESUME NEXT”, but goes one step further by making it the default! Abandon all hope, ye who etc, etc.

To make Powershell error handling work more like error handling in other .NET languages, we can set $global:ErrorActionPreference = “Stop” at the start of our script. With this variable set, uncaught exceptions thrown by Powershell code will cause the whole script to stop. Altering our example with this line, we get the output that we originally expected:

This solves most of our problem: the behavior of Powershell code that throws errors. Unfortunately, it doesn’t help us when we invoke an external executable that fails in the middle of our script. In my next post, I’ll show you how to address the failure of external executables.

Источник

Hey, Scripting Guy! How Can I Use $ErrorActionPreference to Control Cmdlet Handling of Errors?

Hey, Scripting Guy! One thing I liked about using VBScript is that I could add On Error Resume Next to the top of a script, and all my problems with the script went away. It was like magic, and I used it to fix many of my scripts. I really miss this capability in Windows PowerShell.

Hello MW,

Microsoft Scripting Guy Ed Wilson here. It is Monday morning. Generally, I love Mondays. Although if the truth were told, they are not that much different from Saturdays or Thursdays. A day of scripting is after all a day of scripting, and that means they are all good! Those of you who follow the Scripting Guys on Twitter or Facebook know that you are quite likely to find either Craig or me hanging out over the weekend. This weekend was a bit different. I have been in contact with a professional woodworker who studied with James Krenov at the College of the Redwoods . We sent e-mail back and forth, and he agreed to give me private lessons in his studio. It made for an excellent weekend, but because his studio is several hundred miles away, I am a bit tired this morning. In fact, this morning is an unplanned coffee morning . I have my French press sitting beside my Zune HD, and I am sipping a cup of Peaberry Kona coffee from Kauai, which has a rich, nutty taste with hints of cherry and nutmeg. Whereas I normally put a cinnamon stick in a cup of coffee (a habit I picked up while I was teaching VBScript in Lisbon, Portugal), I feel to do so with this excellent coffee would almost be an insult. The same is true of loading it down with sugar and milk. Therefore, I drink this coffee unadorned. Speaking of Kauai, in addition to having excellent coffee, there is great scuba diving there. The following photograph is one I took during a trip there.

MW, you can achieve something similar to On Error Resume Next by assigning the string SilentlyContinue to the $ErrorActionPreference automatic variable. The reason I say it is similar is that it does not hide all errors. For example, if I try to create an object that does not exist, such as the infamous foo, Windows PowerShell generates an error (in spite of all the examples of this in various programming books). The error seen here tells you it cannot find the type for foo, and that the assembly containing this type is not loaded:

PS C:> New-Object foo
New-Object : Cannot find type [foo]: make sure the assembly containing this type is loaded.
At line:1 char:11
+ New-Object foo
+ CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

To hide this error, set the value of the automatic variable $ErrorActionPreference to SilentlyContinue, as seen here:

PS C:> $ErrorActionPreference = “silentlycontinue”
PS C:> New-Object foo
PS C:>

This command does not suppress all errors. For example, if I generate a divide-by-zero error, an error appears. Windows PowerShell generates the error regardless of the setting for the $ErrorActionPreference automatic variable. The following code illustrates this:

PS C:> 1/0
Attempted to divide by zero.
At line:1 char:3
+ 1/ + CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : RuntimeException

PS C:> $ErrorActionPreference = silentlycontinue
The term ‘silentlycontinue’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:42
+ $ErrorActionPreference = silentlycontinue + CategoryInfo : ObjectNotFound: (silentlycontinue:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

The difference in behavior is because the $ErrorActionPreference automatic variable controls reporting from cmdlets and not system errors. In addition, it is i mportant to remember that the $ErrorActionPreference automatic variable is only valid for –not terminating errors. So what is the difference between a terminating error and a nonterminating error? A terminating error is raised by a cmdlet (by calling the ThrowTerminatingError method) when the error is so bad that the cmdlet would not be able to continue processing the pipeline or in some cases the entire script. Generally, these will be syntax errors.

A nonterminating error is one that is not so bad that it would halt the execution of the cmdlet or the script. For example, attempting to read a text file that does not exist does not mean that the Get-Content cmdlet will stop working. If you give it the path to a file that does exist, it will work just fine as shown here:

PS C:> Get-Content foo

Get-Content : Cannot find path ‘C:foo’ because it does not exist.

At line:1 char:12

+ CategoryInfo : ObjectNotFound: (C:foo:String) [Get-Content], ItemNotFoundException

Источник

Обработка ошибок в PowerShell (часть 1)

Для начала определимся, что такое обработка ошибок вообще. В общем случае ошибка — это поведение программы или скрипта, отличное от запланированного. Совсем избежать ошибок не очень возможно, поэтому надо предвидеть, где они могут возникнуть и писать код так, чтобы при возникновении ошибки можно было перехватить ее, проанализировать и определить дальнейшее поведение скрипта. Именно это обычно и подразумевается под обработкой ошибок.

В PowerShell ошибки делятся на два типа: прерывающие (Terminating) и непрерывающие (Non-Terminating). Как следует из названия, непрерывающие ошибки позволяют продолжить выполнение команды, тогда как при возникновении прерывающей ошибки дальнейшее продолжение выполнения команды невозможно. К примеру, у нас есть файл со списком служб, которые необходимо перезапустить следующей командой:

Get-Content -Path C:Filesservices.txt | Restart-Service

Предположим, что перезапуск одной из перечисленных служб по какой либо причине невозможен. Тем не менее можно продолжать выполнение задачи, поскольку остальные службы доступны и их можно перезапустить. Это пример непрерывающей ошибки.

А теперь представьте, что у нас нет прав на открытие этого файла, и соответственно прочитать список служб мы не можем. В этой ситуации продолжение работы невозможно, т.е. это прерывающая ошибка.

PowerShell позволяет обрабатывать оба эти типа ошибок. Большинство ошибок в PowerShell непрерывающие, и сегодня речь пойдет о том, как их обрабатывать.

Обработка непрерывающих ошибок

Для получения ошибки возьмем службу с ″оригинальным″ названием Service. Поскольку службы этой на сервере нет, то обращение к ней стабильно будет генерировать ошибку. Запросим данные о нескольких службах командой:

Как видно из примера, PowerShell не нашел службу Service, о чем выдал ошибку и затем продолжил выполнение команды. Давайте разберемся, почему команда повела себя именно так и как это поведение изменить.

За поведение команды при возникновении ошибки отвечает параметр ErrorAction, который может принимать одно из пяти значений:

• Continue;
• SilentlyContinue;
• Stop;
• Ignore;
• Inquire.

Примечание. Еще у ErrorAction может быть значение Suspend. Но это значение может применяться только к рабочим процессам (workflows), поэтому в рамках данной статьи речь о нем не пойдет.

Значение Continue означает, что при возникновении ошибки информация об этом будет выведена на экран (отправлена в поток вывода Error) и добавлена в автоматическую переменную $Error, после чего выполнение команды будет продолжено. Надо сказать, что Continue — это действие, определенное в сеансе по умолчанию, поэтому его можно не указывать явно.

При значении SilentlyContinue информация об ошибке добавляется в переменную $Error, но не выводится на экран. При этом команда продолжает выполняться дальше, также как и в предыдущем случае.

Значение Stop останавливает дальнейшее выполнение команды при возникновении ошибки. И наоборот, значение Ignore полностью игнорирует возникновение ошибки, при этом не выводится сообщение на экран и не производится запись в $Error. Это значение появилось в PowerShell 3.0.

Inquire — наиболее интересное значение ErrorAction. Если задать это значение, то при возникновении ошибки предлагается на выбор несколько действий: продолжить (Yes), продолжить не смотря на эту и все последующие ошибки (Yes to All), остановить (Halt) или приостановить (Suspend) выполнение команды.

Самый необычный эффект дает Suspend, при выборе которого открывается параллельный сеанс (Nested Namespace). Определить его можно по значку >>. Nested Namespace представляет из себя дочерний процесс, в котором можно полноценно работать — выполнять команды, запускать скрипты и т.п. Этот режим удобно использовать для отладки скриптов, например можно по быстрому исправить причину ошибки и продолжить выполнение. Для выхода из Nested Namespace достаточно набрать exit и выбрать необходимое действие.

Примечание. У параметра ErrorAction есть алиас — EA. Кроме того, вместо названия параметра можно указывать числовые значения: 0 (SilentlyContinue), 1 (Stop), 2 (Continue), 3 (Inquire). Так например, вместо:

Get-Service service,spooler -ErrorAction SilentlyContinue

можно написать так:

Get-Service service,spooler -EA 0

Переменные для обработки ошибок

Как я уже говорил, если не указывать параметр ErrorAction, то для команды действует режим обработки ошибок, определенный в сеансе. Этот режим задается переменной $ErrorActionPreference, которая по умолчанию имеет значение Continue. При желании можно переопределить режим для всего сеанса, задав переменной $ErrorActionPreference нужное значение.

Все ошибки PowerShell сохраняет в автоматическую переменную $Error. Это глобальная переменная, которая представляет из себя массив строк, содержащий записи обо всех ошибках в текущем сеансе. Каждая новая ошибка добавляется в начало массива, соответственно для просмотра последней ошибки надо обратиться к самому первому элементу массива $Error[0].

$Error имеет свои свойства и методы, которые можно использовать. Например, посмотреть общее количество ошибок в текущем сеансе можно командой $Error.Count, а очистить список — командой $Error.Clear().

Переменная $Error не безразмерна, по умолчанию она хранит не более 256 ошибок. При превышении этого количества наиболее старые ошибки будут затираться. При необходимости количество записей в переменной $Error можно увеличить, изменив значение другой переменной $MaximumErrorCount.

Кроме $Error для хранения ошибок допускается задавать собственные переменные. Сделать это можно с помощью параметра ErrorVariable, например так:

Get-Service service,spooler -ErrorAction SilentlyContinue -ErrorVariable var

Обратите внимание, что имя переменной в команде задается без знака $, хотя в дальнейшем к ней обращаемся как к обычной переменной $var.

В отличие от глобальной переменной $Error заданные вручную переменные хранят только ошибки той команды, в которой они определены. Кроме того, по умолчанию эти переменные каждый раз перезаписываются и поэтому хранят только последнюю ошибку. Если вы хотите, чтобы новые ошибки добавлялись в переменную, не перезаписывая ее содержимое, то перед именем переменной надо поставить знак +, например +var.

Пока все, а в следующей части пойдет о способах обработки прерывающих ошибок.

Источник

May 28th, 2010

Bookmark and Share

(Note: These solutions were written for Beginner Event 10.)

Beginner Event 10 (Windows PowerShell)

Photo of Jonathan Medd

Jonathan Medd is a Windows PowerShell MVP and co-host of the Get-Scripting podcast. He regularly posts about Windows PowerShell on his blog, http://jonathanmedd.net, and you can tune in to the monthly Windows PowerShell podcast at http://get-scripting.blogspot.com. You can follow his Windows PowerShell and other system administrator antics at https://twitter.com/jonathanmedd.

The first step in troubleshooting this script is to comment out the opening line:

#$errorActionPreference = “silentlycontinue”


The default value of $errorActionPreference in Windows PowerShell is continue. This means that upon hitting an error, Windows PowerShell will flag the error, but attempt to carry on processing the script. By commenting out the first line, which has changed the value of $errorActionPreference to silentlycontinue, we will be able to troubleshoot what is wrong with the script.

If we now run the script again we get the following error: “You cannot call a method on a null-valued expression as seen below.”

Image of error message

From the second line of the error, we can see that the issue is with line 4:

$Shell.popup($w.UserName)


The method is the part of the line after the dot so we know that for some reason the variable $Shell is empty. This is because in line 3, the variable used to store the wscript.shell ComObject has been named $wshShell. So to correct that, line 4 should look like this:


$wshShell.popup($w.UserName)

If we run the updated script again, we receive the below message box. No error this time, but it is empty. So we know that we can create a message box, but there must be something wrong with populating the text.

Image of empty message box

This is again partly to do with the naming of variables. In line 1, we used $wmi to store the results of the WMI query to retrieve the currently logged-on user. This is the variable we need to use in the popup method to display the correct text. So line 3 should be further corrected:


$wshShell.popup($wmi.UserName)

However, running the script again still returns an empty message box. Going back to the original WMI query in line 1, if we run it interactively we get the below result:

Image of results of running WMI query interactively

The value of the Name property is the name of the computer, not the logged on user. Also this property is not called UserName, which we are trying to use in line 4. By changing the WMI query to instead use the UserName property, we get the correct logged-on user:


Get-WmiObject
-Class Win32_computerSystem -Property UserName

Image of running WMI query with UserName property

The corrected script now looks like this:

Beginner10_Fixed.ps1

#$errorActionPreference = “silentlycontinue”

$wmi = Get-WmiObject -Class Win32_computerSystem -Property UserName

$wshShell = New-Object -ComObject wscript.shell

$wshShell.popup($wmi.UserName)


When we run this corrected script, the following message box is displayed.

Image of message box displayed

A slightly more advanced way to create a message box would be to use the message box class within the System.Windows.Forms .NET Framework namespace. While not providing much extra in this particular example, more complex forms could be created using these tools.

You first need to load the system.windows.forms assembly (only a subset of the .NET Framework is available by default in Windows PowerShell):

[reflection.assembly]::loadwithpartialname(‘system.windows.forms’) | Out-Null


Run the same WMI query as before:

$wmi = Get-WmiObject -Class Win32_computerSystem -Property UserName


Then use the show method of the system.windows.forms.messagebox class to display the message box:

[system.Windows.Forms.MessageBox]::show($wmi.username)


Image of message box

Beginner Event 10 (VBScript)

Photo of Georges Maheu

Georges Maheu is the Premier Field Engineer (PFE) Security Technology Lead for Microsoft Canada. As a Senior PFE, he focuses on delegation of authority (how many domain administrators do you really have?), server hardening, and doing security assessments using his own scripts. Georges has a passion for VBScript, Windows PowerShell, and WMI and uses them on a regular basis to gather information from Active Directory and computers. Georges also delivers popular scripting workshops to Microsoft Premier Customers worldwide.

————

VBScript solution

On Error Resume Next
Set wmi = GetObject(“winmgmts:”)
colitems = wmi.Execquery(“Select user from Win32_computersystem”)
For Each item In colitem
WScript.Echo item.username
Next


This is a classic: a script written by someone else without comments. In this situation, the first thing I do after examining the source code is to double-click the script in a test environment. Personally, I use Virtual PC for all my script development and testing. Never run a script in production until you fully and totally understand what it does.

Well, running this script did not help. Nothing seems to happen. My next reflex is to comment out the first line and to add the Option explicit statement to force variable declarations:


‘ On Error Resume Next ‘GLM: commented out
Option explicit ’GLM: added

The apostrophe character is used to add comments or descriptive text in a VBScript. In this case, it will prevent the error-handling statement On error resume next from executing. On error resume next makes your scripts ignore errors (no error messages either) and move on to the next line. I could have deleted the line but I like to keep the original code around while I debug. I also add comments preceded by my initials, GLM.

Now, when I run the script, I get this dialog box:

Image of dialog box shown when script is run

As a best practice, all variables should be declared, and adding the Option explicit statement makes this mandatory. Variables are declared with the Dim statement. I add the following:

Dim WMI


I then repeat the process until all the variables are declared:

Dim colItems
Dim item


During this process, I notice there is a typo in colItem. An s is missing at the end!

colItems = wmi.Execquery(“Select user from Win32_computersystem”)
‘ For Each item In colItem ‘original code
For Each item In colItems ‘GLM: modified


At this point, the code looks like this:

‘On Error Resume Next ‘GLM: commented out
Option explicit ‘GLM: added
Dim WMI ‘GLM: added
Dim colItems ‘GLM: added
Dim item ‘GLM: added
Set WMI = GetObject(“winmgmts:”)
colItems = WMI.Execquery(“Select user from Win32_computersystem”)
‘ For Each item In colItem ‘original code
For Each item In colItems ‘GLM: modified
WScript.Echo item.username
Next


And I get the following error message:

Image of error message

A search (using Bing) on “vbscript error 800A01C2” may give you an indication of what the problem could be. Fortunately, I found a webpage with a similar problem and remembered that the WMI ExecQuery requires a Set to work:


‘ colItems = wmi.Execquery(“Select user from Win32_computersystem”) ‘original code
Set colItems = wmi.Execquery(“Select user from Win32_computersystem”) ‘GLM: modified

Let’s run the code and see what happens.

Image of another error

OK, another error. And I thought this was going to be easy.

However, line #10 (For Each item In colItems) seems to be fine. One thing you learn quickly when you write or debug scripts is that errors sometimes occur in cascades. If you can’t find anything wrong with a particular line, it could be the error occurred in the code before the line in which the error was reported. The question is, where?

WMI, which stands for Windows Management Instrumentation, is in some respect Microsoft’s implementation of WBEM, a standard created by DMTF. WMI uses WQL, a query language similar to SQL. Knowing this, I decided to replace the WQL query with the following:


‘ colItems = WMI.Execquery(“Select user from Win32_computersystem”) ‘original code
Set colItems = WMI.Execquery(“Select * from Win32_computersystem”) ‘GLM: modified

I did this because the “*” is like a wild card character and will return all the properties associated with the Win32_computerSytem class. Unlike PowerShell, there is no simple way to list properties in VBScript. You could write some code (review Scriptomatic source code for an example), but this would probably be more work than debugging this code! Another option is to use CIM Studio from the free WMI tools.

Image of CIM Studio

CIM Studio will give you a complete list of all the properties and methods for a given class. With this information, I could also have rewritten the line to:


Set colItems = WMI.Execquery(“Select userName from Win32_computersystem”)

Let’s run the script one more time.

Image of running script one more time

Yes! It works.

Here is the working script after our debugging session:


‘On Error Resume Next ‘GLM: commented out
Option explicit ‘GLM: added
Dim WMI ‘GLM: added
Dim colItems ‘GLM: added
Dim item ‘GLM: added
Set WMI = GetObject(“winmgmts:”)
‘ colitems = WMI.Execquery(“Select user from Win32_computersystem”) ‘original code
Set colItems = WMI.Execquery(“Select * from Win32_computersystem”) ‘GLM: modified
‘ For Each item In colItem ‘original code
For Each item In colItems ‘GLM: modified
WScript.Echo item.UserName
Next

I save this version as a reference and then clean the code. You will notice I removed the On Error Resume Next statement. Because I’m not doing any error handling, there is no point using this statement. Here is the final script with added comments:

ShowLoggedUserAccount.vbs

‘* File: ShowLoggedUserAccount.vbs
‘* Version: 1.06
‘* Date: 2010/04/06
‘* Author: Georges Maheu, Microsoft PFE
‘*
‘* Based on the original file
‘* File: VBScriptDoesNotWOrk.vbs
‘* Version: 1.0
‘* Date: 2010/03/21
‘* Author: John Doe
‘* Modifications: documented in file Debug-VBScriptDoesNotWOrk.vbs

‘ This script will display the name of the current logged-on user
‘ on a local or remote computer
‘ ================================================================
Option Explicit ‘make variable declaration mandatory
Dim WMI ‘WMI service object
Dim colItems ‘collection of items returned by WMI query
Dim item ‘individual item from the WMI collection variable colItems
Dim computerName ‘name of computer to query
‘LocalHost can be replaced by a remote computer name or IP address
‘must be run from an account with sufficient permissions to see the data
computerName = “localhost”
‘create WMI service object and connect to CIMv2 namespace on computerName
Set WMI = GetObject(“winmgmts:\”+computerName+”rootCIMv2”)
‘retrieve userName property from Win32_computerSystem WMI class
set colItems = WMI.Execquery(“Select userName from Win32_computersystem”)
‘WMI ExecQueries returns a SWbemObjectSet collection data structure
‘even when there is only one item in the collection. For this reason
‘we use the For Each construct to list the userName property
For Each item In colItems ‘iterate through collection
WScript.Echo item.userName ‘display user name on screen
Next


Just for fun, I also created a very short version of this script:

WScript.Echo GetObject(“winmgmts:”).Execquery(“Select * from Win32_computersystem”).itemIndex(0).userName

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson and Craig Liebendorfer, Scripting Guys








Home
Login
Join



  1. Learn
  2. Windows
  3. PowerShell
Author Nick (Automox)
Nick (Automox)

This person is a Verified Professional

This person is a verified professional.
Verify your account
to enable IT peers to see that you are a professional.

Nov 01, 2018
3 Minute Read

  • Spice
  • Reply (8)
  • Subscribe

  • Share
    Opens a new window

    • Facebook
      Opens a new window

    • Twitter
      Opens a new window

    • Reddit
      Opens a new window

    • LinkedIn
      Opens a new window

Author Nick (Automox)

Nick Almiron

This person is a Verified Professional

This person is a verified professional.
Verify your account
to enable IT peers to see that you are a professional.

Nick (Automox)
Badge Verified Professional
Badge Voice of IT
Badge Brain Buster Level 1
Badge Brain Buster Level 2
Badge Brain Buster Level 3


38
Contributions
6
Best Answers
2
Projects

Main Areas of Contribution:
  • PowerShell |
  • Windows Server |
  • Imaging, Deployment, & Patching |
  • Windows 10 |
  • General Databases
Register. Track Progress. Earn Credits.
Learning has never been so easy!

Sign Up

In Powershell and the Principle of Most Astonishment, I covered some of the surprises that new Powershell users encounter when trying to reliably return results from functions. The next area where Powershell suprises new users is in its approach to error handling. Fortunately, there are some useful workarounds for making the surprising default behavior work more like you would expect.

While learning Powershell, I was trying to create a deployment script. The script needed to perform several tasks including, copying the deployment package to the target machine, setting up services, and the like. While testing it out locally, I would deliberately cause certain steps to fail in order to ensure that the user of the script would be clearly alerted to failures. To my surprise, when I caused exceptions to be thrown, the script would happily continue on to the next step, ultimately printing a success message to the user. Consider this example:

function divide-by-zero() {
    $x = 0
    $y = 1/$x
}

write-host "A"
divide-by-zero
write-host "B"
divide-by-zero
write-host "Success!"

Output:
    A
    Attempted to divide by zero.
    At C:devblog_postdefault.ps1:5 char:12
    +     $y = 1/ <<<< $x
        + CategoryInfo          : NotSpecified: (:) [], RuntimeException
        + FullyQualifiedErrorId : RuntimeException
         
    B
    Attempted to divide by zero.
    At C:devblog_postdefault.ps1:5 char:12
    +     $y = 1/ <<<< $x
        + CategoryInfo          : NotSpecified: (:) [], RuntimeException
        + FullyQualifiedErrorId : RuntimeException
       
    Success!

What the heck just happened? If you can’t rely on uncaught exceptions to stop execution, how can you reliably deal with failures? What if we throw an exception and then blindly move along to a subsequent step that depends on the success of previous steps?

Powershell reintroduces VB’s “ON ERROR RESUME NEXT”, but goes one step further by making it the default! Abandon all hope, ye who etc, etc.

To make Powershell error handling work more like error handling in other .NET languages, we can set $global:ErrorActionPreference = “Stop” at the start of our script. With this variable set, uncaught exceptions thrown by Powershell code will cause the whole script to stop. Altering our example with this line, we get the output that we originally expected:

$global:ErrorActionPreference = "Stop"

function divide-by-zero() {
    $x = 0
    $y = 1/$x
}

write-host "A"
divide-by-zero
write-host "B"
divide-by-zero
write-host "Success!"

Output:
    A
    Attempted to divide by zero.
    At C:devblog_postdefault.ps1:5 char:12
    +     $y = 1/ <<<< $x
        + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
        + FullyQualifiedErrorId : RuntimeException

This solves most of our problem: the behavior of Powershell code that throws errors. Unfortunately, it doesn’t help us when we invoke an external executable that fails in the middle of our script. In my next post, I’ll show you how to address the failure of external executables.

Для начала определимся, что такое обработка ошибок вообще. В общем случае ошибка — это поведение программы или скрипта, отличное от запланированного. Совсем избежать ошибок не очень возможно, поэтому надо предвидеть, где они могут возникнуть и писать код так, чтобы при возникновении ошибки можно было перехватить ее, проанализировать и определить дальнейшее поведение скрипта.  Именно это обычно и подразумевается под обработкой ошибок.

В PowerShell ошибки делятся на два типа: прерывающие (Terminating) и непрерывающие (Non-Terminating). Как следует из названия, непрерывающие ошибки позволяют продолжить выполнение команды, тогда как при возникновении прерывающей ошибки дальнейшее продолжение выполнения команды невозможно. К примеру, у нас есть файл со списком служб, которые необходимо перезапустить следующей командой:

Get-Content -Path C:Filesservices.txt | Restart-Service

Предположим, что перезапуск одной из перечисленных служб по какой либо причине невозможен. Тем не менее можно продолжать выполнение задачи, поскольку остальные службы доступны и их можно перезапустить. Это пример непрерывающей ошибки.

А теперь представьте, что у нас нет прав на открытие этого файла, и соответственно прочитать список служб мы не можем. В этой ситуации продолжение работы невозможно, т.е. это прерывающая ошибка.

PowerShell позволяет обрабатывать оба эти типа ошибок. Большинство ошибок в PowerShell непрерывающие, и сегодня речь пойдет о том, как их обрабатывать.

Обработка непрерывающих ошибок

Для получения ошибки возьмем службу с ″оригинальным″ названием Service. Поскольку службы этой на сервере нет, то обращение к ней стабильно будет генерировать ошибку. Запросим данные о нескольких службах командой:

Get-Service service,spooler

Как видно из примера, PowerShell не нашел службу Service, о чем выдал ошибку и затем продолжил выполнение команды. Давайте разберемся, почему команда повела себя именно так и как это поведение изменить.

обработка ошибки по умолчанию

За поведение команды при возникновении ошибки отвечает параметр ErrorAction, который может принимать одно из пяти значений:

• Continue;
• SilentlyContinue;
• Stop;
• Ignore;
• Inquire.

Примечание. Еще у ErrorAction может быть значение Suspend. Но это значение может применяться только к рабочим процессам (workflows), поэтому в рамках данной статьи речь о нем не пойдет.

Значение Continue означает, что при возникновении ошибки информация об этом будет выведена на экран (отправлена в поток вывода Error) и добавлена в автоматическую переменную $Error, после чего выполнение команды будет продолжено. Надо сказать, что Continue — это действие, определенное в сеансе по умолчанию, поэтому его можно не указывать явно.

обработка ошибки, режим Continue

При значении SilentlyContinue информация об ошибке добавляется в переменную $Error, но не выводится на экран. При этом команда продолжает выполняться дальше, также как и в предыдущем случае.

обработка ошибки, режим SilentlyContinue

Значение Stop останавливает дальнейшее выполнение команды при возникновении ошибки. И наоборот, значение Ignore полностью игнорирует возникновение ошибки, при этом не выводится сообщение на экран и не производится запись в $Error. Это значение появилось в PowerShell 3.0.

Обработка ошибок в режимах Stop и Ignore

Inquire — наиболее интересное значение ErrorAction. Если задать это значение, то при возникновении ошибки предлагается на выбор несколько действий: продолжить (Yes), продолжить не смотря на эту и все последующие ошибки (Yes to All), остановить (Halt) или приостановить (Suspend) выполнение команды.

Самый необычный эффект дает Suspend, при выборе которого открывается параллельный сеанс (Nested Namespace). Определить его можно по значку >>. Nested Namespace представляет из себя дочерний процесс, в котором можно полноценно работать — выполнять команды, запускать скрипты и т.п. Этот режим удобно использовать для отладки скриптов, например можно по быстрому исправить причину ошибки и продолжить выполнение. Для выхода из Nested Namespace достаточно набрать exit и выбрать необходимое действие.

Обработка ошибки в режиме Inquire

Примечание. У параметра ErrorAction есть алиас — EA. Кроме того, вместо названия параметра можно указывать числовые значения: 0 (SilentlyContinue), 1 (Stop), 2 (Continue), 3 (Inquire). Так например, вместо:

Get-Service service,spooler -ErrorAction SilentlyContinue

можно написать так:

Get-Service service,spooler -EA 0

Переменные для обработки ошибок

Как я уже говорил, если не указывать параметр ErrorAction, то для команды действует режим обработки ошибок, определенный в сеансе. Этот режим задается переменной $ErrorActionPreference, которая по умолчанию имеет значение Continue. При желании можно переопределить режим для всего сеанса, задав переменной $ErrorActionPreference нужное значение.

Политика обработки ошибок по умолчанию

Все ошибки PowerShell сохраняет в автоматическую переменную $Error. Это глобальная переменная, которая представляет из себя массив строк, содержащий записи обо всех ошибках в текущем сеансе. Каждая новая ошибка добавляется в начало массива, соответственно для просмотра последней ошибки надо обратиться к самому первому элементу массива $Error[0].

$Error имеет свои свойства и методы, которые можно использовать. Например, посмотреть общее количество ошибок в текущем сеансе можно командой $Error.Count, а очистить список — командой $Error.Clear().

переменная $Error

Переменная $Error не безразмерна, по умолчанию она хранит не более 256 ошибок. При превышении этого количества наиболее старые ошибки будут затираться. При необходимости количество записей в переменной $Error можно увеличить, изменив значение другой переменной $MaximumErrorCount.

Кроме $Error для хранения ошибок допускается задавать собственные переменные. Сделать это можно с помощью параметра ErrorVariable, например так:

Get-Service service,spooler -ErrorAction SilentlyContinue -ErrorVariable var

Обратите внимание, что имя переменной в команде задается без знака $, хотя в дальнейшем к ней обращаемся как к обычной переменной $var.

назначение собственной переменной для хранения ошибок

В отличие от глобальной переменной $Error заданные вручную переменные хранят только ошибки той команды, в которой они определены. Кроме того, по умолчанию эти переменные каждый раз перезаписываются и поэтому хранят только последнюю ошибку. Если вы хотите, чтобы новые ошибки добавлялись в переменную, не перезаписывая ее содержимое, то перед именем переменной надо поставить знак +, например +var.

Пока все, а в следующей части пойдет о способах обработки прерывающих ошибок.

Время прочтения
5 мин

Просмотры 240K

Только автоматизация. Только PowerShell.

Предисловие

В качестве хобби и при наличии времени преподаю студентам в УКИТ (бывший Московский государственный колледж информационных технологий). На данный момент у меня мало времени, чтобы уделить его группе студентов, зато вполне достаточно, чтобы подготовить пост здесь, на Хабре.

Я работаю системным администратором в крупной не ИТ-компании с большой завязкой на ИТ ресурсы. По роду деятельности представляется решать большое количество однотипных задач по обслуживанию пользователей.

С языком PowerShell познакомился около двух лет назад, но вплотную занялся им лишь спустя год, не осознав поначалу его огромных возможностей. В статье, прежде всего, я буду ориентироваться на тех, кто хочет начать работать с PowerShell, но пока не доверяет ему или не знает, с какой стороны подступиться к этому чуду.

Внимание: PowerShell вызывает привыкание.

Введение

Википедия говорит нам:

Windows PowerShell — расширяемое средство автоматизации от Microsoft, состоящее из оболочки с интерфейсом командной строки и сопутствующего языка сценариев.

Выглядеть среда PowerShell может так, как командная строка:


powershell.exe

Или в виде приложения:


powershell_ise.exe

Powershell_ise.exe называется интегрированной средой сценариев — Windows PowerShell ISE. Позволяет работать с языком в удобной среде с подсветкой синтаксиса, конструктором команд, автозаполнением команд по нажатию TAB и прочими прелестями. Идеальна для создания и тестирования сценариев.

Для запуска среды powershell.exe или powershell_ise.exe достаточно набрать аналогичное название в строке выполнить.

Файл сценария PowerShell имеет расширение .ps1.

Сценарий не получится запустить двойным ЛКМ. Это сделано специально для того, чтобы не нанести вред системе случайно запущенным скриптом.

Для запуска, по клику ПКМ следует выбрать «Выполнить с помощью PowerShell»:

Помимо того, что существует ограничение по запуску сценариев ЛКМ, по умолчанию выполнение сценариев в системе запрещено, опять же, по описанной выше причине — не нанести вред системе. Для проверки текущей политики выполнения выполним команду:

Get-ExecutionPolicy

Мы получим одно из следующих ниже значений. С большой вероятностью, если это был первый запуск, мы получим Restricted.

  • Restricted — Сценарии не могут быть запущены;
  • AllSigned — Могут быть запущены только сценарии, подписанные доверенным издателем. Перед выполнением сценария доверенного издателя будет запрашиваться подтверждение;
  • RemoteSigned — Разрешено выполнять созданные нами сценарии и скачанные сценарии, подписанные доверенным издателем;
  • Unrestricted — Никаких ограничений, все скрипты могут быть запущены.

Для выполнения и тестирования понизим политику до RemoteSigned выполнив команду:

Set-ExecutionPolicy RemoteSigned

Приступаем к работе

Командлет

  • Командлетами называются команды PowerShell, в которых заложена различная функциональность;
  • Командлеты могут быть как системными, так и пользовательскими, созданные кем-либо;
  • Командлеты именуются по правилу Глагол-Существительное, что упрощает их запоминание;
  • Командлеты выводят результаты в виде объектов или их коллекций;
  • Командлеты могут как получать данные для обработки, так и передавать данные по конвейеру (про конвейеры позже);
  • Командлеты не чувствительны к регистру (можно написать и get-process, и Get-Process, и GeT-pRoCeSs);
  • После командлетов не обязательно ставить «;«, за исключением, когда мы выполняем несколько командлетов в одну строку (Get-Process; Get-Services).

Например, для получения текущих процессов, мы выполним команду:

Get-Process 

И получим результат:

Попробуйте самостоятельно выполнить:

Get-Service #для получения статуса служб, запущенных на компьютерах
Get-Content C:WindowsSystem32driversetchosts #для получения содержимого файла. В данном случае, файл hosts

Не обязательно знать наизусть все командлеты. Get-Help спасёт ситуацию.
Информацию о всех доступных командлетах можно получить, введя следующую команду:

Get-Help -Category cmdlet

Если мы используем PowerShell ISE, мы облегчаем процесс разработки.
Достаточно ввести знак тире «» после того, как ввели командлет, и мы получим все возможные варианты параметров и их типы:

Попробуйте выполнить:

Get-Service -Name p*

Если, всё же, мы забудем какие свойства есть у того или иного командлета, прогоним его через Get-Member:

Get-Process | Get-Member

#Знак "|" называется конвейером. О нём ниже.

Недостаточно информации? Обратимся к справке с параметром -Examples:

Get-Help Get-Process -Examples

Получаем описание Get-Process, да ещё и с примерами использования:

  • Командлеты могут иметь сокращённые названия — алиасы. Например, вместо Get-Help можно использовать просто Help. Для получения всех сокращений выполните Get-Alias.

Попробуйте выполнить:

Start-Process notepad

Что аналогично записи:

start notepad

А теперь остановим процесс:

Stop-Process -Name notepad

Или так:

spps -Name notepad

Немногим ранее мы сказали, что командлеты именуются по правилу Глагол-Существительное. Уточню, что глагол не обязательно должен быть Get. Помимо того, что мы можем получать, мы можем задавать Set (помните, Set-ExecutionPolicy), запускать Start, останавливать Stop, выводить Out, создавать New и многие другие. Название командлета ни чем не ограничивается и, когда мы будем с вами создавать свой собственный, сможем назвать его так, как душе угодно.

Попробуем выполнить вывод в файл:

"Hello, Habr!" | Out-File C:test.txt
& C:test.txt

Кстати, аналогично можно записать так:

"Hello, Habr!" > C:test.txt
& C:test.txt
Комментарии

Мы все знаем, использовать комментарии является хорошим тоном.

Комментарии в PowerShell бывают строчные — # и блочные — <##>:

Обратим внимание, на код из примера:

Get-WmiObject -Class Win32_OperatingSystem | SELECT Caption

Для тех, кто знаком с WMI, кто делает это на старом добром VBScript, помните, сколько кода надо написать?

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject("winmgmts:\" & strComputer & "rootcimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_OperatingSystem",,48)
For Each objItem in colItems
    Wscript.Echo "Caption: " & objItem.Caption
Next
Конвейер

Конвейер (|) — передаёт выходные данные одной команды во входные данные на обработку другой команде. Мы использовали конвейер ранее, получая все свойства объекта или, в предыдущем примере, выбирая из набора данных только поле Caption.

Чтобы понять принцип конвейера, давайте выполним код:

Get-Service | Sort-Object -property Status

Что произойдёт: получаем все службы (Get-Service), передаём все полученные службы на сортировку в командлет Sort-Object и указываем, что хотим отсортировать их по параметру Status. На выводе мы получим сначала все службы со статусом Stop, а потом все службы со статусом Running.

В примере ниже мы сначала получим все запущенные службы. После первого конвейера проходимся по каждому элементу, выбираем только те службы, у которых статус Running и на втором конвейере выбираем, что хотим на выводе увидеть только displayname служб:

Get-Service | WHERE {$_.status -eq "Running"} | SELECT displayname

В примере мы используем $_. Данная запись означает текущий элемент в конвейере.

Послесловие

В этой части мы научились запускать PowerShell, разобрались с политикой выполнения сценариев. Поняли, что такое командлеты, знаем, как передавать их по конвейеру и как получить их свойства. Если мы что-то забудем, обязательно Get-Help.

Все это знания нужные для того, чтобы сделать первый прыжок в язык. Поверьте, ещё много интересного!

Продолжение следует…

Дополнительная информация

  • Jump Start в PowerShell (часть II);
  • Скачать PowerShell v.5 бесплатно без регистрации;
  • Замечательный курс на MVA: Расширенные возможности и написание скриптов в PowerShell 3.0 (Jump Start). Мой совет — посмотрите первые 3 блока, окунитесь в язык, попробуйте повыполнять какие-нибудь задачи и через недельку смотрите оставшиеся блоки. Русские субтитры;
  • Видео в тему на TechDays;
    Галерея скриптов PowerShell на Technet;
    Блог разработчиков PowerShell.

Example

Possible values are Continue | Ignore | Inquire | SilentlyContinue | Stop | Suspend.

Value of this parameter will determine how the cmdlet will handle non-terminating errors (those generated from Write-Error for example; to learn more about error handling see [topic not yet created]).

Default value (if this parameter is omitted) is Continue.

-ErrorAction Continue

This option will produce an error message and will continue with execution.

PS C:> Write-Error "test" -ErrorAction Continue ; Write-Host "Second command"

-ErorrAction Continue

-ErrorAction Ignore

This option will not produce any error message and will continue with execution. Also no errors will be added to $Error automatic variable.
This option was introduced in v3.

PS C:> Write-Error "test" -ErrorAction Ignore ; Write-Host "Second command"

-ErorrAction Ignore

-ErrorAction Inquire

This option will produce an error message and will prompt user to choose an action to take.

PS C:> Write-Error "test" -ErrorAction Inquire ; Write-Host "Second command"

-ErorrAction Inquire

-ErrorAction SilentlyContinue

This option will not produce an error message and will continue with execution. All errors will be added to $Error automatic variable.

PS C:> Write-Error "test" -ErrorAction SilentlyContinue ; Write-Host "Second command"

-ErorrAction SilentlyContinue

-ErrorAction Stop

This option will produce an error message and will not continue with execution.

PS C:> Write-Error "test" -ErrorAction Stop ; Write-Host "Second command"

-ErorrAction Stop

-ErrorAction Suspend

Only available in Powershell Workflows. When used, if the command runs into an error, the workflow is suspended. This allows investigation of such error and gives a possibility to resume the workflow. To learn more about Workflow system, see [topic not yet created].

This section briefly demonstrates how to use each of PowerShell’s statements, variables and parameters that are related to the reporting or handling of errors.

$Error is an automatic global variable in PowerShell which always contains an ArrayList of zero or more ErrorRecord objects. As new errors occur, they are added to the beginning of this list, so you can always get information about the most recent error by looking at $Error[0]. Both Terminating and Non-Terminating errors will be contained in this list.

Aside from accessing the objects in the list with array syntax, there are two other common tasks that are performed with the $Error variable: you can check how many errors are currently in the list by checking the $Error.Count property, and you can remove all errors from the list with the $Error.Clear() method. For example:

Figure 2.1: Using $Error to access error information, check the count, and clear the list.

If you’re planning to make use of the $Error variable in your scripts, keep in mind that it may already contain information about errors that happened in the current PowerShell session before your script was even started. Also, some people consider it a bad practice to clear the $Error variable inside a script; since it’s a variable global to the PowerShell session, the person that called your script might want to review the contents of $Error after it completes.

The ErrorVariable common parameter provides you with an alternative to using the built-in $Error collection. Unlike $Error, your ErrorVariable will only contain errors that occurred from the command you’re calling, instead of potentially having errors from elsewhere in the current PowerShell session. This also avoids having to clear the $Error list (and the breach of etiquette that entails.)

When using ErrorVariable, if you want to append to the error variable instead of overwriting it, place a + sign in front of the variable’s name. Note that you do not use a dollar sign when you pass a variable name to the ErrorVariable parameter, but you do use the dollar sign later when you check its value.

The variable assigned to the ErrorVariable parameter will never be null; if no errors occurred, it will contain an ArrayList object with a Count of 0, as seen in figure 2.2:

Figure 2.2: Demonstrating the use of the ErrorVariable parameter.

By default, the $Error variable can only contain a maximum of 256 errors before it starts to lose the oldest ones on the list. You can adjust this behavior by modifying the $MaximumErrorCount variable.

ErrorAction and $ErrorActionPreference

There are several ways you can control PowerShell’s handling / reporting behavior. The ones you will probably use most often are the ErrorAction common parameter and the $ErrorActionPreference variable.

The ErrorAction parameter can be passed to any Cmdlet or Advanced Function, and can have one of the following values: Continue (the default), SilentlyContinue, Stop, Inquire, Ignore (only in PowerShell 3.0 or later), and Suspend (only for workflows; will not be discussed further here.) It affects how the Cmdlet behaves when it produces a non-terminating error.

  • The default value of Continue causes the error to be written to the Error stream and added to the $Error variable, and then the Cmdlet continues processing.

  • A value of SilentlyContinue only adds the error to the $Error variable; it does not write the error to the Error stream (so it will not be displayed at the console).

  • A value of Ignore both suppresses the error message and does not add it to the $Error variable. This option was added with PowerShell 3.0.

  • A value of Stop causes non-terminating errors to be treated as terminating errors instead, immediately halting the Cmdlet’s execution. This also enables you to intercept those errors in a Try/Catch or Trap statement, as described later in this section.

  • A value of Inquire causes PowerShell to ask the user whether the script should continue or not when an error occurs.

The $ErrorActionPreference variable can be used just like the ErrorAction parameter, with a couple of exceptions: you cannot set $ErrorActionPreference to either Ignore or Suspend. Also, $ErrorActionPreference affects your current scope in addition to any child commands you call; this subtle difference has the effect of allowing you to control the behavior of errors that are produced by .NET methods, or other causes such as PowerShell encountering a «command not found» error.

Figure 2.3 demonstrates the effects of the three most commonly used $ErrorActionPreference settings.

Figure 2.3: Behavior of $ErrorActionPreference

The Try/Catch/Finally statements, added in PowerShell 2.0, are the preferred way of handling terminating errors. They cannot be used to handle non-terminating errors, unless you force those errors to become terminating errors with ErrorAction or $ErrorActionPreference set to Stop.

To use Try/Catch/Finally, you start with the «Try» keyword followed by a single PowerShell script block. Following the Try block can be any number of Catch blocks, and either zero or one Finally block. There must be a minimum of either one Catch block or one Finally block; a Try block cannot be used by itself.

The code inside the Try block is executed until it is either complete, or a terminating error occurs. If a terminating error does occur, execution of the code in the Try block stops. PowerShell writes the terminating error to the $Error list, and looks for a matching Catch block (either in the current scope, or in any parent scopes.) If no Catch block exists to handle the error, PowerShell writes the error to the Error stream, the same thing it would have done if the error had occurred outside of a Try block.

Catch blocks can be written to only catch specific types of Exceptions, or to catch all terminating errors. If you do define multiple catch blocks for different exception types, be sure to place the more specific blocks at the top of the list; PowerShell searches catch blocks from top to bottom, and stops as soon as it finds one that is a match.

If a Finally block is included, its code is executed after both the Try and Catch blocks are complete, regardless of whether an error occurred or not. This is primarily intended to perform cleanup of resources (freeing up memory, calling objects’ Close() or Dispose() methods, etc.)

Figure 2.4 demonstrates the use of a Try/Catch/Finally block:

Figure 2.4: Example of using try/catch/finally.

Notice that «Statement after the error» is never displayed, because a terminating error occurred on the previous line. Because the error was based on an IOException, that Catch block was executed, instead of the general «catch-all» block below it. Afterward, the Finally executes and changes the value of $testVariable.

Also notice that while the Catch block specified a type of [System.IO.IOException], the actual exception type was, in this case, [System.IO.DirectoryNotFoundException]. This works because DirectoryNotFoundException is inherited from IOException, the same way all exceptions share the same base type of System.Exception. You can see this in figure 2.5:

Figure 2.5: Showing that IOException is the Base type for DirectoryNotFoundException

Trap statements were the method of handling terminating errors in PowerShell 1.0. As with Try/Catch/Finally, the Trap statement has no effect on non-terminating errors.

Trap is a bit awkward to use, as it applies to the entire scope where it is defined (and child scopes as well), rather than having the error handling logic kept close to the code that might produce the error the way it is when you use Try/Catch/Finally. For those of you familiar with Visual Basic, Trap is a lot like «On Error Goto». For that reason, Trap statements don’t see a lot of use in modern PowerShell scripts, and I didn’t include them in the test scripts or analysis in Section 3 of this ebook.

For the sake of completeness, here’s an example of how to use Trap:

Figure 2.6: Use of the Trap statement

As you can see, Trap blocks are defined much the same way as Catch blocks, optionally specifying an Exception type. Trap blocks may optionally end with either a Break or Continue statement. If you don’t use either of those, the error is written to the Error stream, and the current script block continues with the next line after the error. If you use Break, as seen in figure 2.5, the error is written to the Error stream, and the rest of the current script block is not executed. If you use Continue, the error is not written to the error stream, and the script block continues execution with the next statement.

The $LASTEXITCODE Variable

When you call an executable program instead of a PowerShell Cmdlet, Script or Function, the $LASTEXITCODE variable automatically contains the process’s exit code. Most processes use the convention of setting an exit code of zero when the code finishes successfully, and non-zero if an error occurred, but this is not guaranteed. It’s up to the developer of the executable to determine what its exit codes mean.

Note that the $LASTEXITCODE variable is only set when you call an executable directly, or via PowerShell’s call operator (&) or the Invoke-Expression cmdlet. If you use another method such as Start-Process or WMI to launch the executable, they have their own ways of communicating the exit code to you, and will not affect the current value of $LASTEXITCODE.

Figure 2.7: Using $LASTEXITCODE.

The $? variable is a Boolean value that is automatically set after each PowerShell statement or pipeline finishes execution. It should be set to True if the previous command was successful, and False if there was an error.
When the previous command was a PowerShell statement, $? will be set to False if any errors occurred (even if ErrorAction was set to SilentlyContinue or Ignore).
If the previous command was a call to a native exe, $? will be set to False if the $LASTEXITCODE variable does not equal zero. If the $LASTEXITCODE variable equals zero, then in theory $? should be set to True. However, if the native command writes something to the standard error stream then $? could be set to False even if $LASTEXITCODE equals zero. For example, the PowerShell ISE will create and append error objects to $Error for standard error stream output and consequently $? will be False regardless of the value of $LASTEXITCODE. Redirecting the standard error stream into a file will result in a similar behavior even when the PowerShell host is the regular console. So it is probably best to test the behavior in your specific environment if you want to rely on $? being set correctly to True.

Just be aware that the value of this variable is reset after every statement. You must check its value immediately after the command you’re interested in, or it will be overwritten (probably to True). Figure 2.8 demonstrates this behavior. The first time $? is checked, it is set to False, because the Get-Item encountered an error. The second time $? was checked, it was set to True, because the previous command was successful; in this case, the previous command was «$?» from the first time the variable’s value was displayed.

Figure 2.8: Demonstrating behavior of the $? variable.

The $? variable doesn’t give you any details about what error occurred; it’s simply a flag that something went wrong. In the case of calling executable programs, you need to be sure that they return an exit code of 0 to indicate success and non-zero to indicate an error before you can rely on the contents of $?.

That covers all of the techniques you can use to either control error reporting or intercept and handle errors in a PowerShell script. To summarize:

  • To intercept and react to non-terminating errors, you check the contents of either the automatic $Error collection, or the variable you specified as the ErrorVariable. This is done after the command completes; you cannot react to a non-terminating error before the Cmdlet or Function finishes its work.

  • To intercept and react to terminating errors, you use either Try/Catch/Finally (preferred), or Trap (old and not used much now.) Both of these constructs allow you to specify different script blocks to react to different types of Exceptions.

  • Using the ErrorAction parameter, you can change how PowerShell cmdlets and functions report non-terminating errors. Setting this to Stop causes them to become terminating errors instead, which can be intercepted with Try/Catch/Finally or Trap.

  • $ErrorActionPreference works like ErrorAction, except it can also affect PowerShell’s behavior when a terminating error occurs, even if those errors came from a .NET method instead of a cmdlet.

  • $LASTEXITCODE contains the exit code of external executables. An exit code of zero usually indicates success, but that’s up to the author of the program.

  • $? can tell you whether the previous command was successful, though you have to be careful about using it with external commands. If they don’t follow the convention of using an exit code of zero as an indicator of success or if they write to the standard error stream then the resulting value in $? may not be what you expect. You also need to make sure you check the contents of $? immediately after the command you are interested in.

В Powershell существует несколько уровней ошибок и несколько способов их обработать. Проблемы одного уровня (Non-Terminating Errors) можно решить с помощью привычных для Powershell команд. Другой уровень ошибок (Terminating Errors) решается с помощью исключений (Exceptions) стандартного, для большинства языков, блока в виде Try, Catch и Finally. 

Как Powershell обрабатывает ошибки

До рассмотрения основных методов посмотрим на теоретическую часть.

Автоматические переменные $Error

В Powershell существует множество переменных, которые создаются автоматически. Одна из таких переменных — $Error хранит в себе все ошибки за текущий сеанс PS. Например так я выведу количество ошибок и их сообщение за весь сеанс:

Get-TestTest
$Error
$Error.Count

Переменная $Error в Powershell

При отсутствии каких либо ошибок мы бы получили пустой ответ, а счетчик будет равняться 0:

Счетчик ошибок с переменной $Error в Powershell

Переменная $Error являет массивом и мы можем по нему пройтись или обратиться по индексу что бы найти нужную ошибку:

$Error[0]

foreach ($item in $Error){$item}

Вывод ошибки по индексу в Powershell c $Error

Свойства объекта $Error

Так же как и все что создается в Powershell переменная $Error так же имеет свойства (дополнительную информацию) и методы. Названия свойств и методов можно увидеть через команду Get-Member:

$Error | Get-Member

Свойства переменной $Error в Powershell

Например, с помощью свойства InvocationInfo, мы можем вывести более структурный отчет об ошибки:

$Error[0].InvocationInfo

Детальная информация об ошибке с $Error в Powershell

Методы объекта $Error

Например мы можем очистить логи ошибок используя clear:

$Error.clear()

Очистка логов об ошибке в Powershell с $Error

Критические ошибки (Terminating Errors)

Критические (завершающие) ошибки останавливают работу скрипта. Например это может быть ошибка в названии командлета или параметра. В следующем примере команда должна была бы вернуть процессы «svchost» дважды, но из-за использования несуществующего параметра ‘—Error’ не выполнится вообще:

'svchost','svchost' | % {Get-Process -Name $PSItem} --Error 

Критические ошибки в Powershell Terminating Errors

Не критические ошибки (Non-Terminating Errors)

Не критические (не завершающие) ошибки не остановят работу скрипта полностью, но могут вывести сообщение об этом. Это могут быть ошибки не в самих командлетах Powershell, а в значениях, которые вы используете. На предыдущем примере мы можем допустить опечатку в названии процессов, но команда все равно продолжит работу:

'svchost111','svchost' | % {Get-Process -Name $PSItem}

Не критические ошибки в Powershell Non-Terminating Errors

Как видно у нас появилась информация о проблеме с первым процессом ‘svchost111’, так как его не существует. Обычный процесс ‘svchost’ он у нас вывелся корректно.

Параметр ErrorVariable

Если вы не хотите использовать автоматическую переменную $Error, то сможете определять свою переменную индивидуально для каждой команды. Эта переменная определяется в параметре ErrorVariable:

'svchost111','svchost' | % {Get-Process -Name $PSItem } -ErrorVariable my_err_var
$my_err_var

Использование ErrorVariable в Powershell

Переменная будет иметь те же свойства, что и автоматическая:

$my_err_var.InvocationInfo

Свойства  ErrorVariable в Powershell

Обработка некритических ошибок

У нас есть два способа определения последующих действий при ‘Non-Terminating Errors’. Это правило можно задать локально и глобально (в рамках сессии). Мы сможем полностью остановить работу скрипта или вообще отменить вывод ошибок.

Приоритет ошибок с $ErrorActionPreference

Еще одна встроенная переменная в Powershell $ErrorActionPreference глобально определяет что должно случится, если у нас появится обычная ошибка. По умолчанию это значение равно ‘Continue’, что значит «вывести информацию об ошибке и продолжить работу»:

$ErrorActionPreference

Определение $ErrorActionPreference в Powershell

Если мы поменяем значение этой переменной на ‘Stop’, то поведение скриптов и команд будет аналогично критичным ошибкам. Вы можете убедиться в этом на прошлом скрипте с неверным именем процесса:

$ErrorActionPreference = 'Stop'
'svchost111','svchost' | % {Get-Process -Name $PSItem}

Определение глобальной переменной $ErrorActionPreference в Powershell

Т.е. скрипт был остановлен в самом начале. Значение переменной будет храниться до момента завершения сессии Powershell. При перезагрузке компьютера, например, вернется значение по умолчанию.

Ниже значение, которые мы можем установить в переменной $ErrorActionPreference:

  • Continue — вывод ошибки и продолжение работы;
  • Inquire — приостановит работу скрипта и спросит о дальнейших действиях;
  • SilentlyContinue — скрипт продолжит свою работу без вывода ошибок;
  • Stop — остановка скрипта при первой ошибке.

Самый частый параметр, который мне приходится использовать — SilentlyContinue:

$ErrorActionPreference = 'SilentlyContinue'
'svchost111','svchost' | % {Get-Process -Name $PSItem}

Игнорирование ошибок в Powershell с ErrorActionPreference и SilentlyContinue

Использование параметра ErrorAction

Переменная $ErrorActionPreference указывает глобальный приоритет, но мы можем определить такую логику в рамках команды с параметром ErrorAction. Этот параметр имеет больший приоритет чем $ErrorActionPreference. В следующем примере, глобальная переменная определяет полную остановку скрипта, а в параметр ErrorAction говорит «не выводить ошибок и продолжить работу»:

$ErrorActionPreference = 'Stop'
'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'SilentlyContinue'}

Использование параметра ErrorAction в ошибках с Powershell

Кроме ‘SilentlyContinue’ мы можем указывать те же параметры, что и в переменной $ErrorActionPreference. 

Значение Stop, в обоих случаях, делает ошибку критической.

Обработка критических ошибок и исключений с Try, Catch и Finally

Когда мы ожидаем получить какую-то ошибку и добавить логику нужно использовать Try и Catch. Например, если в вариантах выше мы определяли нужно ли нам отображать ошибку или останавливать скрипт, то теперь сможем изменить выполнение скрипта или команды вообще. Блок Try и Catch работает только с критическими ошибками и в случаях если $ErrorActionPreference или ErrorAction имеют значение Stop.

Например, если с помощью Powershell мы пытаемся подключиться к множеству компьютеров один из них может быть выключен — это приведет к ошибке. Так как эту ситуацию мы можем предвидеть, то мы можем обработать ее. Процесс обработки ошибок называется исключением (Exception).

Синтаксис и логика работы команды следующая:

try {
    # Пытаемся подключиться к компьютеру
}
catch [Имя исключения 1],[Имя исключения 2]{
    # Раз компьютер не доступен, сделать то-то
}
finally {
    # Блок, который выполняется в любом случае последним
}

Блок try мониторит ошибки и если она произойдет, то она добавится в переменную $Error и скрипт перейдет к блоку Catch. Так как ошибки могут быть разные (нет доступа, нет сети, блокирует правило фаервола и т.д.) то мы можем прописывать один блок Try и несколько Catch:

try {
    # Пытаемся подключится
}
catch ['Нет сети']['Блокирует фаервол']{
    # Записываем в файл
}
catch ['Нет прав на подключение']{
    # Подключаемся под другим пользователем
}

Сам блок finally — не обязательный и используется редко. Он выполняется самым последним, после try и catch и не имеет каких-то условий.

Catch для всех типов исключений

Как и было показано выше мы можем использовать блок Catch для конкретного типа ошибок, например при проблемах с доступом. Если в этом месте ничего не указывать — в этом блоке будут обрабатываться все варианты ошибок:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch {
   Write-Host "Какая-то неисправность" -ForegroundColor RED
}

Игнорирование всех ошибок с try и catch в Powershell

Такой подход не рекомендуется использовать часто, так как вы можете пропустить что-то важное.

Мы можем вывести в блоке catch текст ошибки используя $PSItem.Exception:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch {
   Write-Host "Какая-то неисправность" -ForegroundColor RED
   $PSItem.Exception
}

Переменная PSITem в блоке try и catch в Powershell

Переменная $PSItem хранит информацию о текущей ошибке, а глобальная переменная $Error будет хранит информацию обо всех ошибках. Так, например, я выведу одну и ту же информацию:

$Error[0].Exception

Вывод сообщения об ошибке в блоке try и catch в Powershell

Создание отдельных исключений

Что бы обработать отдельную ошибку сначала нужно найти ее имя. Это имя можно увидеть при получении свойств и методов у значения переменной $Error:

$Error[0].Exception | Get-Member

Поиск имени для исключения ошибки в Powershell

Так же сработает и в блоке Catch с $PSItem:

Наименование ошибок для исключений в Powershell

Для вывода только имени можно использовать свойство FullName:

$Error[0].Exception.GetType().FullName

Вывод типа ошибок и их названия в Powershell

Далее, это имя, мы вставляем в блок Catch:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch [Microsoft.PowerShell.Commands.ProcessCommandException]{
   Write-Host "Произошла ошибка" -ForegroundColor RED
   $PSItem.Exception
}

Указываем исключение ошибки в блоке Try Catch Powershell

Так же, как и было описано выше мы можем усложнять эти блоки как угодно указывая множество исключений в одном catch.

Выброс своих исключений

Иногда нужно создать свои собственные исключения. Например мы можем запретить добавлять через какой-то скрипт названия содержащие маленькие буквы или сотрудников без указания возраста и т.д. Способов создать такие ошибки — два и они тоже делятся на критические и обычные.

Выброс с throw

Throw — выбрасывает ошибку, которая останавливает работу скрипта. Этот тип ошибок относится к критическим. Например мы можем указать только текст для дополнительной информации:

$name = 'AD.1'

if ($name -match '.'){
   throw 'Запрещено использовать точки в названиях'
}

Выброс ошибки с throw в Powershell

Если нужно, то мы можем использовать исключения, которые уже были созданы в Powershell:

$name = 'AD.1'

if ($name -like '*.*'){
   throw [System.IO.FileNotFoundException]'Запрещено использовать точки в названиях'
}

Выброс ошибки с throw в Powershell

Использование Write-Error

Команда Write-Error работает так же, как и ключ ErrorAction. Мы можем просто отобразить какую-то ошибку и продолжить выполнение скрипта:

$names = @('CL1', 'AD.1', 'CL3')

foreach ($name in $names){
   if ($name -like '*.*'){
      Write-Error -Message 'Обычная ошибка'
   }
   else{
      $name
   }
}

Использование Write-Error для работы с исключениями в Powershell

При необходимости мы можем использовать параметр ErrorAction. Значения этого параметра были описаны выше. Мы можем указать значение ‘Stop’, что полностью остановит выполнение скрипта:

$names = @('CL1', 'AD.1', 'CL3')

foreach ($name in $names){
   if ($name -like '*.*'){
      Write-Error -Message 'Обычная ошибка' -ErrorAction 'Stop'
   }
   else{
      $name
   }
}

Использование Write-Error и Stop в Powershell

Отличие команды Write-Error с ключом ErrorAction от обычных команд в том, что мы можем указывать исключения в параметре Exception:

Write-Error -Message 'Обычная ошибка' -ErrorAction 'Stop'

Write-Error -Message 'Исключение' -Exception [System.IO.FileNotFoundException] -ErrorAction 'Stop'

Свойства Write-Errror в Powershell

В Exception мы так же можем указывать сообщение. При этом оно будет отображаться в переменной $Error:

Write-Error -Exception [System.IO.FileNotFoundException]'Моё сообщение'

Свойства Write-Errror в Powershell 

Теги:

#powershell

#ошибки

Понравилась статья? Поделить с друзьями:
  • Powershell get error code
  • Powershell generate error
  • Powershell exe системная ошибка
  • Postgresql как изменить пароль пользователя postgres
  • Postgresql как изменить название столбца