I have a script that creates new AD Objects (via New-ADObject, as it happens). If the object already exists, I need to catch and handle that. However, the exception type isn't nearly as explicit as the FullyQualifiedErrorId. Observe the below:
> $Error[-1] | Format-List -Property * -Force
writeErrorStream : True
PSMessageDetails :
Exception : Microsoft.ActiveDirectory.Management.ADException: An attempt was made to add an object to the directory with
a name that is already in use ---> System.ServiceModel.FaultException: The supplied entry already exists.
--- End of inner exception stack trace ---
at Microsoft.ActiveDirectory.Management.AdwsConnection.ThrowExceptionForExtendedError(String
extendedErrorMessage, Exception innerException)
at Microsoft.ActiveDirectory.Management.AdwsConnection.ThrowExceptionForFaultDetail(FaultDetail
faultDetail, FaultException faultException)
at Microsoft.ActiveDirectory.Management.AdwsConnection.ThrowException(AdwsFault adwsFault, FaultException
faultException)
at Microsoft.ActiveDirectory.Management.AdwsConnection.Create(ADAddRequest request)
at Microsoft.ActiveDirectory.Management.ADWebServiceStoreAccess.Microsoft.ActiveDirectory.Management.IADSy
ncOperations.Add(ADSessionHandle handle, ADAddRequest request)
at Microsoft.ActiveDirectory.Management.ADActiveObject.Create()
at Microsoft.ActiveDirectory.Management.Commands.ADNewCmdletBase`3.ProcessRecordOverride()
at Microsoft.ActiveDirectory.Management.Commands.ADCmdletBase.ProcessRecord()
TargetObject : ou=Domain Controllers,DC=cryotest,DC=testdom
CategoryInfo : NotSpecified: (ou=Domain Contr...test,DC=afcdom1:String) [New-ADObject], ADException
FullyQualifiedErrorId : An attempt was made to add an object to the directory with a name that is already in
use,Microsoft.ActiveDirectory.Management.Commands.NewADObject
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at Import-ADObjectOfClass, C:\Users\administrator\Desktop\Import-ADObjects.ps1: line 103
at <ScriptBlock>, C:\Users\administrator\Desktop\Import-ADObjects.ps1: line 137
at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {1, 1}
How can I make use of the more verbose information here in my Catch block?
The FullyQualifiedErrorId is just the the .Message property of the exception object along with the the fully-qualified name of the class where the exception originated.
You can't catch by FullyQualifiedErrorId, but you can catch by exception type:
try {
# Do something that causes the 'name already in use' exception you're getting.
} catch [System.ActiveDirectory.Management.ADException] {
if ($_.Exception.Message -ilike "*already in use") {
# Do something to handle the error condition.
}
}
Note that this won't be a portable solution across different languages, since the exception message may be localized on non-English builds of Windows.
In addition, you may have to modify your try block to include -ErrorAction Stop to ensure the error is caught.
If the error thrown by New-ADObject is not a terminating error using catch will not help. One thing you can do is use the ErrorAction parameter to make the error a terminating error:
try{
New-ADObject ... -ErrorAction Stop
}
catch{
... handle the error ....
}
I don't know if you can catch the exceptions by the FullyQualifiedErrorId, but I found this way to get it and it works for me:
$InerrMessage= $_.FullyQualifiedErrorId
Related
I am trying to find out a way to frame try catch block with various possible exception types. I got some clues from other questions to check $Error[0].Exception.GetType().FullName.
However, I am still not able to figure-out where to get the Exception class type that you have to put in front of catch keyword.
For example, when I try:
try { 1/0 } catch { $Error[0].Exception.GetType().FullName }
I get:
System.Management.Automation.RuntimeException
However, when I run below:
try { 1/0 } catch [DivideByZeroException]{ "DivideByZeroException" } catch { $Error[0].Exception.GetType().FullName }
I get:
DivideByZeroException
Where is [DivideByZeroException] found in $Error[0] in above case?
Since I can't find it anywhere in the properties of $Error[0]:
PS C:\Temp> $Error[0] | Select *
PSMessageDetails :
Exception : System.Management.Automation.RuntimeException: Attempted to divide by zero.
---> System.DivideByZeroException: Attempted to divide by zero.
--- End of inner exception stack trace ---
at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(
FunctionContext funcContext, Exception exception)
at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(Int
erpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction
.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction
.Run(InterpretedFrame frame)
TargetObject :
CategoryInfo : NotSpecified: (:) [], RuntimeException
FullyQualifiedErrorId : RuntimeException
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}
The DivideByZeroException instance is in stored in the .InnerException property of the .Exception property value of the System.Management.Automation.ErrorRecord instance stored in $Error[0], reflecting the most recent error:
PS> try { 1 / 0 } catch {}; $Error[0].Exception.InnerException.GetType().FullName
System.DivideByZeroException
That is, the RuntimeException wraps the DivideByZeroException exception.
Seemingly, because you're using a type-qualified catch block, inside that catch block, the [ErrorRecord] instance reflected in the automatic $_ variable contains the specified exception directly in .Exception - unlike in the corresponding entry in the automatic $Error variable:
PS> try { 1 / 0 } catch [DivideByZeroException] {
$_.Exception.GetType().FullName;
$Error[0].Exception.GetType().FullName
}
System.DivideByZeroException # type of $_.Exception
System.Management.Automation.RuntimeException # type of $Error[0].Exception
In other words:
In an unqualified catch block, $_ is equivalent to $Error[0] (the latter can also be accessed later), and contains the (outer) exception in .Exception and - if applicable - an inner exception in .Exception.InnerException.
In a type-qualified catch block, you can catch an inner exception - as (also later) reflected in $Error[0].Exception.InnerException - directly, in which case $_.Exception inside the qualified catch block contains that inner exception.
When I run the following pester test I expect it to catch the expected error but it doesn't. But when I run the test with a different function with a different throw statement it works.
Pester Test:
Describe "Remove-GenericCredential Function Tests" {
$InternetOrNetworkAddress = 'https://PesterTestUser#PesterTestURl.com'
Context "Test: Remove-GenericCredential -InternetOrNetworkAddress '$InternetOrNetworkAddress' (Credential does not exist)" {
It "This Command threw an error. The credential does not exist." { { (Remove-GenericCredential -InternetOrNetworkAddress $InternetOrNetworkAddress -Confirm:$false) } | should throw "Remove-GenericCredential : Credential $InternetOrNetworkAddress not found" }
}
}
Error that isn't caught:
Remove-GenericCredential : Credential https://PesterTestUser#PesterTestURl.com not found
At C:\Users\klocke7\Documents\WindowsPowerShell\Modules\Ford_CredentialManager\Tests\Remove-GenericCredential.Tests.ps1:30 char:76
+ ... xist." { { (Remove-GenericCredential -InternetOrNetworkAddress $Inter ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Remove-GenericCredential
[-] This Command threw an error. The credential does not exist. 44ms
Expected: the expression to throw an exception with message {Remove-GenericCredential : Credential https://PesterTestUser#PesterTestURl.com not found}, an exception was not raised, message was {}
from C:\Users\klocke7\Documents\WindowsPowerShell\Modules\Ford_CredentialManager\Tests\New-GitHubCredential.Tests.ps1:59 char:176
+ ... e $UserName -Token 'NotAGitHubTokenSpecialCharacters!##$%^&*') } | sh ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
at <ScriptBlock>, C:\Users\klocke7\Documents\WindowsPowerShell\Modules\Ford_CredentialManager\Tests\Remove-GenericCredential.Tests.ps1: line 30
30: It "This Command threw an error. The credential does not exist." { { (Remove-GenericCredential -InternetOrNetworkAddress $InternetOrNetworkAddress -Confirm:$false) } | should throw 'Remove-GenericCredential : Credential https://PesterTestUser#PesterTestURl.com not found' }
Per the other answer, the function is throwing a non-terminating error and as such its not being considered to match the test of Should Throw, which is checking for terminating errors.
There are two ways you could address this:
You could change it so that it throws a terminating error by changing your Write-Error to Throw.
You could change the test to force the function to throw a terminating error even when non-terminating ones occur by using -ErrorAction Stop when you invoke it (I can see you're using -Confirm, I assume you have used [cmdletbinding()] in the function to add the common parameters like -ErrorAction).
Here's an example of the second solution (I've simulated the function at the top so that I could test this, but you don't need to include that in your test script):
Function Remove-GenericCredential {
[cmdletbinding(supportsshouldprocess)]
Param(
$InternetOrNetworkAddress
)
Write-Error "Remove-GenericCredential : Credential $InternetOrNetworkAddress not found"
}
Describe "Remove-GenericCredential Function Tests" {
$InternetOrNetworkAddress = 'https://PesterTestUser#PesterTestURl.com'
Context "Test: Remove-GenericCredential -InternetOrNetworkAddress '$InternetOrNetworkAddress' (Credential does not exist)" {
It "This Command threw an error. The credential does not exist." {
{ (Remove-GenericCredential -InternetOrNetworkAddress $InternetOrNetworkAddress -Confirm:$false -ErrorAction Stop) } | should throw "Remove-GenericCredential : Credential $InternetOrNetworkAddress not found" }
}
}
From help Write-Error:
The Write-Error cmdlet declares a non-terminating error. By default,
errors are sent in the error stream to the host program to be
displayed, along with output.
To write a non-terminating error, enter an error message string, an ErrorRecord object, or an Exception object. Use the other parameters of Write-Error to populate the error record.
Non-terminating errors write an error to the error stream, but they do not stop command processing. If a non-terminating error is declared on one item in a collection of input items, the command continues to process the other items in the collection.
To declare a terminating error, use the Throw keyword. For more information, see about_Throw
(http://go.microsoft.com/fwlink/?LinkID=145153).
This is probably because the cmdlet is throwing a non-terminating error because Pester only asserts against terminating errors. In this case need to rewrite the test as follows (using old Pester v3 synatax as per your example):
It "Test a non-terminating error gets thrown" {
$errorThrown = $false;
try
{
Invoke-CmdletThatThrowsNonTerminatingError -ErrorAction Stop
}
catch
{
$errorThrown = $true
}
$errorThrown | Should Be $true
}
In the catch block you can also get exception details such as message or exception type on the $_ ob
When I purposely throw an exception the stack trace shows the code that decided to throw the exception.
Example error message!!
At C:\path\to\script.ps1:101 char:37
+ else { throw $message; }
+ ~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Example error message!!:String) [], RuntimeException
+ FullyQualifiedErrorId : Example error message!!
Is there a way in PowerShell that I can have it show the line that called my code. Because it is a problem with the calling code, not the throw line.
I'm a C# developer who is trying to build something useful using PowerShell. That's why I'm keep trying to use well-known idioms from .NET world in PowerShell.
I'm writing a script that has different layer of abstractions: database operations, file manipulation etc. At some point I would like to catch an error and wrap it into something more meaningful for the end user. This is a common pattern for C#/Java/C++ code:
Function LowLevelFunction($arg)
{
# Doing something very useful here!
# but this operation could throw
if (!$arg) {throw "Ooops! Can't do this!"}
}
Now, I would like to call this function and wrap an error:
Function HighLevelFunction
{
Try
{
LowLevelFunction
}
Catch
{
throw "HighLevelFunction failed with an error!`nPlease check inner exception for more details!`n$_"
}
}
This approach is almost what I need, because HighLevelFunction will throw new error and the root cause of the original error would be lost!
In C# code I always can throw new exception and provide original exception as an inner exception. In this case HighLevelFunction would be able to communicate their errors in a form more meaningful for their clients but still will provide inner details for diagnostic purposes.
The only way to print original exception in PowerShell is to use $Error variable that stores all the exceptions. This is OK, but the user of my script (myself for now) should do more things that I would like.
So the question is: Is there any way to raise an exception in PowerShell and provide original error as an inner error?
You can throw a new exception in your catch block and specify the base exception:
# Function that will throw a System.IO.FileNotFoundExceptiopn
function Fail-Read {
[System.IO.File]::ReadAllLines( 'C:\nonexistant' )
}
# Try to run the function
try {
Fail-Read
} catch {
# Throw a new exception, specifying the inner exception
throw ( New-Object System.Exception( "New Exception", $_.Exception ) )
}
# Check the exception here, using getBaseException()
$error[0].Exception.getBaseException().GetType().ToString()
Unfortunately when throwing a new exception from the catch block as described by this answer, the script stack trace (ErrorRecord.ScriptStackTrace) will be reset to the location of the throw. This means the root origin of the inner exception will be lost, making debugging of complex code much harder.
There is an alternative solution that uses ErrorRecord.ErrorDetails to define a high-level message and $PSCmdlet.WriteError() to preserve the script stack trace. It requires that the code is written as an advanced function cmdlet. The solution doesn't use nested exceptions, but still fulfills the requirement "to catch an error and wrap it into something more meaningful for the end user".
#------ High-level function ----------------------------------------------
function Start-Foo {
[CmdletBinding()] param()
try {
# Some internal code that throws an exception
Get-ChildItem ~foo~ -ErrorAction Stop
}
catch {
# Define a more user-friendly error message.
# This sets ErrorRecord.ErrorDetails.Message
$_.ErrorDetails = 'Could not start the Foo'
# Rethrows (if $ErrorActionPreference is 'Stop') or reports the error normally,
# preserving $_.ScriptStackTrace.
$PSCmdlet.WriteError( $_ )
}
}
#------ Code that uses the high-level function ---------------------------
$DebugPreference = 'Continue' # Enable the stack trace output
try {
Start-Foo -ErrorAction Stop
}
catch {
$ErrorView = 'NormalView' # to see the original exception info
Write-Error -ErrorRecord $_
''
Write-Debug "`n--- Script Stack Trace ---`n$($_.ScriptStackTrace)" -Debug
}
Output:
D:\my_temp\ErrorDetailDemo.ps1 : Could not start the Foo
+ CategoryInfo : ObjectNotFound: (D:\my_temp\~foo~:String) [Write-Error], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,ErrorDetailDemo.ps1
DEBUG:
--- Script Stack Trace ---
at Start-Foo, C:\test\ErrorDetailDemo.ps1: line 5
at , C:\test\ErrorDetailDemo.ps1: line 14
Our high-level error message 'Could not start the Foo' hides the error message of the underlying exception, but no information is lost (you could access the original error message through $_.Exception.Message from within the catch handler).
Note: There is also a field ErrorDetails.RecommendedAction which you could set as you see fit. For simplicity I didn't use it in the sample code, but you could set it like this $_.ErrorDetails.RecommendedAction = 'Install the Foo'.
Is there a way to call Windows Runtime (WinRT) classes (or objects) from a PowerShell script? I know that you can call COM objects, which WinRT classes are supposed to be "exposed" as ... but so far my attempts have failed...
This is my code I'm trying:
$lockscreen = New-Object -comObject Windows.System.UserProfile.LockScreen
Which gives me the following error:
New-Object : Retrieving the COM class factory for component with CLSID {00000000-0000-0000-0000-000000000000} failed
due to the following error: 80040154 Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).
Does anyone know the correct "COM Class" that I should be using for WinRT classes?
just reference the type, to "load" the assembly...
[Windows.System.UserProfile.LockScreen,Windows.System.UserProfile,ContentType=WindowsRuntime]
[Windows.System.UserProfile.LockScreen]::OriginalImageFile
if you don't want the type to be returned in your powershell results then
$null = [Windows.System.UserProfile.LockScreen,Windows.System.UserProfile,ContentType=WindowsRuntime]
Here is something hacky that seems to work:
PS> new-object "Windows.System.UserProfile.LockScreen,Windows.System.UserProfile,ContentType=WindowsRuntime"
new-object : Constructor not found. Cannot find an appropriate constructor for type
Windows.System.UserProfile.LockScreen,Windows.System.UserProfile,ContentType=WindowsRuntime.
At line:1 char:1
+ new-object "Windows.System.UserProfile.LockScreen,Windows.System.UserProfile,Con ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : CannotFindAppropriateCtor,Microsoft.PowerShell.Commands.NewObjectCommand
PS> [Windows.System.UserProfile.LockScreen]::OriginalImageFile
AbsolutePath : C:/Windows/Web/Screen/img100.png
AbsoluteUri : file:///C:/Windows/Web/Screen/img100.png
LocalPath : C:\Windows\Web\Screen\img100.png
Authority :
HostNameType : Basic
IsDefaultPort : True
IsFile : True
IsLoopback : True
PathAndQuery : C:/Windows/Web/Screen/img100.png
...
Note that the first call fails because LockScreen has no constructor but that call does something to pull in the WinRT projection/metadata such that you can now call the static methods/properties on the LockScreen class.
DISCLAIMER: there isn't any documentation that I can find on this New-Object syntax so it is entirely possible that Microsoft could change it considering it is essentially a "hidden" and probably not fully developed feature.