腳本的調試向來是一個艱巨的任務,在powershell出現以前簡直是一場災難。在powershell中微軟終於做出了諸多改進,不但有了$Error、-whatif,也有了ISE.而在語法上也增加了try-catch-finally,終於可以便利的進行調試和錯誤處理了。 在該語法中,finally並不是必需的,但是個人並不建議去掉該部分。建議將功能的預處理放在try部分,但沒有錯誤時,再在finally完成功能。 下面將用一段代碼演示如何進行錯誤處理。主要功能是將一段字符串寫道硬盤上一個新建的文件中,完成后移出變量。-NoClobber表示不覆蓋現有文件。 Try { $strContent = "try catch finally" Out-File -FilePath d:\test.txt -InputObject $strContent -NoClobber Write-Host "文件創建成功" } Catch [System.UnauthorizedAccessException] { Write-Host "訪問失敗。錯誤原因:"$Error[0] } Catch [System.IO.DirectoryNotFoundException] { Write-Host "訪問失敗。錯誤原因:"$Error[0] } Catch { Write-Host "訪問失敗。錯誤原因:"$Error[0] } Finally { Remove-Variable strContent } 按照目前的腳本運行后,成功運行,沒有任何錯誤。如下圖 ![]() 再次運行該腳本,會報下圖的錯誤。這正是-NoClobber發揮了作用。而我們通過System.IO.DirectoryNotFoundException捕獲了該異常。在catch部分,我們可以主動去捕獲可以想到的錯誤,這樣可以提高腳本的友好性,並可以對此類錯誤進行主動處理,提高腳本的可用性。 ![]() 而我們將輸出文件的保存位置更改為d:\temp\這個並不存在的目錄后,可以發現報如下圖的提示,而這正是我們設計的結果。 ![]() |
PowerShell Tutorial – Try Catch Finally and error handling in PowerShell
One of the key parts of any good PowerShell script is error handling. Even in the shortest script, being able to handle errors helps to ensure that an unexpected event will not go on to wreck the system you are working on. Take the example below. Every week in our sample company (MyCompany.Com) Human Resources are going to upload a list telling us who should have access to the Expenses database. If a name isn’t in the list from HR we’re going to remove it from the group and that user will no longer be able to log expense claims:
$AuthorizedUsers= Get-Content \\ FileServer\HRShare\UserList.txt $CurrentUsers=Get-ADGroupMember "Expenses Claimants" Foreach($User in $CurrentUsers) { If($AuthorizedUsers -notcontains $User) { Remove-ADGroupMember -Identity "Expenses Claimants" -User $User } }
Now, you can see where this is going to go wrong. One week HR doesn’t get around to uploading the list or, just as we are about to access the list, the file server dies. Suddenly PowerShell throws an error on the Get-Content cmdlet and the $AuthorizedUser variable remains empty. Because our script doesn’t handle errors, it continues to run and, in a very short space of time, it has removed every user from our expenses group. Pretty soon the irate phone calls start flooding in and life gets a little less happy. The way to avoid all this is to catch the errors and then handle the event that caused them (which in this case is halt the script and have a shout at someone in HR).
Terminating and Non-Terminating Errors
One of the key things to know when catching errors is that only certain errors can be caught by default. Errors come in two types – terminating and non-terminating. A terminating error is an error that will halt a function or operation. If you make a syntax error or run out of memory, that is a terminating error. Terminating errors can be caught and handled. Non-terminating errors allow Powershell to continue and usually come from cmdlets or other managed situations. Under normal circumstances they cannot be caught by Try-Catch-Finally. The Get-Content error in the example above is a non-terminating error.
Treating Non-Terminating Errors as Terminating
So how do you catch a Non-Terminating error? Basically, you tell PowerShell to treat it as terminating. To do this you use the ErrorAction parameter. Every PowerShell cmdlet supports ErrorAction. By specifying -ErrorAction Stop on the end of a cmdlet you ensure that any errors it throws are treated as terminating and can be caught. In our example above we are going to change our Get-Content line to:
$AuthorizedUsers= Get-Content \\ FileServer\HRShare\UserList.txt -ErrorAction Stop
Treating All Errors as Terminating
It is also possible to treat all errors as terminating using the ErrorActionPreference variable. You can do this either for the script your are working with or for the whole PowerShell session. To set it in a script, make the first line $ErrorActionPreference = Stop. To set it for the session, type $ErrorActionPreference = Stop at the PowerShell console.
Catching a Terminating Error
Once you have ensured that the error you are trying to catch is going to be treated as terminating, you can build a Try Catch block around the command (or commands) that might cause the error. The first stage is to surround the section of your script that may throw the error with a Try block. In our example the Get-Content line becomes:
Try { $AuthorizedUsers= Get-Content \\ FileServer\HRShare\UserList.txt -ErrorAction Stop }
Immediately after the Try block you must place a Catch block to deal with the error. The Catch block is only accessed if a terminating error occurs, otherwise it is ignored. In our example we are going to email an admin to say that there has been an error and then halt the script. Our Get-Content line is now:
Try { $AuthorizedUsers= Get-Content \\ FileServer\HRShare\UserList.txt -ErrorAction Stop } Catch { Send-MailMessage -From ExpensesBot@MyCompany.Com -To WinAdmin@MyCompany.Com -Subject "HR File Read Failed!" -SmtpServer EXCH01.AD.MyCompany.Com Break }
Accessing The Error Record
Once you are inside a catch block you can access the error record, which is stored in the current object variable, $_. Error records have various useful properties, but the main one you will want to access is $_.Exception. Exceptions are what we are really dealing with here as we catch and deal with errors – exceptions are the unexpected event that caused the error (the error record itself is actually only really a wrapper for presenting the exception to the PowerShell user). It is the exception that we are catching and the exception that contains all the really useful information about the problem. If there was a further underlying problem that caused our exception, it is also recorded at $_.exception.innerexception (and so on – the next underlying exception is stored at $_.exception.innerexception.innerexception etc.). For the purposes of our example we are going to use $_.Exception to put some extra information into our notification email, using the $_.Exception.Message and $_.Exception.ItemName properties:
Try { $AuthorizedUsers= Get-Content \\ FileServer\HRShare\UserList.txt -ErrorAction Stop } Catch { $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName Send-MailMessage -From ExpensesBot@MyCompany.Com -To WinAdmin@MyCompany.Com -Subject "HR File Read Failed!" -SmtpServer EXCH01.AD.MyCompany.Com -Body "We failed to read file $FailedItem. The error message was $ErrorMessage" Break }
Catching Specific Exceptions
Now, as our example stands we are catching any errors that occur during the file read and dealing with all of them in the same way. You can however catch specific exceptions and deal with them differently, but – and it’s a big but – only if the original error is terminating. Because the Get-Content cmdlet throws non-terminating errors (that we have only treated as terminating using ErrorAction) we cannot specifically catch the different exceptions that the cmdlet might throw. This is a feature of PowerShell and applies to any non-terminating error, regardless of the ErrorActionPreference and cannot be changed. Still, we can deal with other terminating exceptions, such as an out of memory error, that could crop up during the read operation. For the purposes of this example that is what we will do.
You catch specific terminating errors by specifying the exception name immediately after the Catch keyword. In our example we want to catch a System.OutOfMemory exception and, if we get one, will take the no nonsense approach of rebooting the computer immediately. We will also include a general catch block after our file not found block to catch all other exceptions:
Try { $AuthorizedUsers= Get-Content \\ FileServer\HRShare\UserList.txt -ErrorAction Stop } Catch [System.OutOfMemoryException] { Restart-Computer localhost } Catch { $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName Send-MailMessage -From ExpensesBot@MyCompany.Com -To WinAdmin@MyCompany.Com -Subject "HR File Read Failed!" -SmtpServer EXCH01.AD.MyCompany.Com -Body "We failed to read file $FailedItem. The error message was $ErrorMessage" Break }
Finally, Using Finally
The last part of Try Catch Finally is the Finally block. This must be defined immediately after the Catch block and runs every time, regardless of whether there was an error or not. In this way you can perform actions that need to be made regardless of whether an operation succeeds or fails. In our example we are going to log that a file read was attempted. Our Get-Content line now looks like:
Try { $AuthorizedUsers = Get-Content \\ FileServer\HRShare\UserList.txt -ErrorAction Stop } Catch [System.OutOfMemoryException] { Restart-Computer localhost } Catch { $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName Send-MailMessage -From ExpensesBot@MyCompany.Com -To WinAdmin@MyCompany.Com -Subject "HR File Read Failed!" -SmtpServer EXCH01.AD.MyCompany.Com -Body "We failed to read file $FailedItem. The error message was $ErrorMessage" Break } Finally { $Time=Get-Date "This script made a read attempt at $Time" | out-file c:\logs\ExpensesScript.log -append }