Error Handling method call on a null-valued expression - powershell

I am getting the following non-terminating error in PowerShell. I want to catch this and write-host a generic statement, rather than see the error.
Here is the output:
You cannot call a method on a null-valued expression. At C:\PowerShell
Scripts\windowsUpdates.ps1:17 char:2
+ $a = $key.GetValue("LastSuccessTime")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], ParentContainsErrorRec ordException
+ FullyQualifiedErrorId : InvokeMethodOnNull
My catch statement is currently not "catching" this.
Any help is appreciated.

I figured this out. I simply added if($variable -eq $null){write-host "message";}.

There are a few options
Depending on where you are getting the null variable from you could add
-ErrorAction Stop"
after the command so that it treats the non-terminating error as a terminating error.
If that's not an option you could throw an error to force your catch statement to catch the error
if($variable -eq $null){Throw("Variable is Null")}
Or you can make it so that all errors are treated as terminating errors so your catch statement will catch anything that is thrown
$ErrorActionPreference = "Stop"
For more information look at the MSDN documentation here: http://blogs.msdn.com/b/kebab/archive/2013/06/09/an-introduction-to-error-handling-in-powershell.aspx

Related

Suppressing error messages in PowerShell ErrorVariable

Using the snippet below, I'm trying to find a way to suppress the "Attempted to divide by zero" error, and be left with just the custom error message in $err. I've tried combinations of try/catch, Invoke-Command/Expression, redirecting 2>$null, but in every case the error stream still contains the "attempted to divide by zero" message.
function func1 {
[CmdletBinding()]
param()
func2 -ErrorAction SilentlyContinue -ErrorVariable er
if ($er.Count -ge 1) {
Write-Error -Message "Custom error message!"
}
}
function func2 {
[CmdletBinding()]
param()
1/0
}
func1 -ErrorAction SilentlyContinue -ErrorVariable err
$err
populates $err with two items:
Attempted to divide by zero.
At line:15 char:3
+ 1/0
+ ~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
func1 : Custom error message!
At line:18 char:1
+ func1 -ErrorAction SilentlyContinue -ErrorVariable err
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,func1
Thanks so much for your thoughts!
Unfortunately the solution is somewhat tricky, because the error that occurs in func2 is invariably recorded in the $err variable passed to -ErrorVariable in the func1 invocation, and is already stored there when the func2 call returns.
Therefore you need to manually remove it:
Replace:
if ($er.Count -ge 1) {
Write-Error -Message "Custom error message!"
}
with:
if ($er.Count -ge 1) {
if ($PSBoundParameters.ContainsKey('ErrorVariable')) {
# Remove the most recently added error from the $err variable.
$PSCmdlet.GetVariableValue($PSBoundParameters['ErrorVariable']).RemoveAt(0)
}
Write-Error -Message "Custom error message!"
}
The automatic $PSBoundParameters variable allows you to check if the common -ErrorAction parameter was specified and with what value (i.e., what target variable name).
The automatic $PSCmdlet variable's .GetVariableValue() method allows access to variables defined in the caller's scope, which enables retrieving the target variable value, which is a [System.Collections.ArrayList] instance whose .RemoveAt() method can be called to remove the most recently added error, which is at index 0.
Asides:
1/0 causes a statement-terminating error, whereas the common -ErrorAction parameter only acts on non-terminating errors (to catch terminating errors you need try / catch). However, because this statement-terminating error occurs inside a function, execution inside that function (potentially) continues, and to the caller the error is in effect a non-terminating one.
For a comprehensive overview of PowerShell's bewilderingly complex error handling, see GitHub docs issue #1583.

how to Stop or exit if getting the specific error on powershell command

PS C:\Users\Administrator> Initialize-Tpm
Initialize-Tpm : The device is not ready for use. (Exception from HRESULT: 0x800710DF)
At line:1 char:1
Initialize-Tpm
+ CategoryInfo : NotSpecified: (Microsoft.Tpm.C...alizeTpmCommand:InitializeTpmCommand) [Initialize-Tpm], TpmWmiException
+ FullyQualifiedErrorId : TpmError,Microsoft.Tpm.Commands.InitializeTpmCommand
simply use try/catch:
try {
#Set erroraction to stop to ensure any error is a terminating error
Initialize-Tpm -ErrorAction:stop
}
Catch {
#return error message and stop processing
throw $_
}
Instead of throw you could also use return to stop the processing or exit (but exit will terminate the process).
You can also tell catch to react only on specific errors:
catch [exception]{
}
see: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-7.2

Why can I not catch my non-terminating error?

I'd like to show someone an example on how to catch non terminating errors in PowerShell.
I created this function:
# This will write a non-terminating error
function NonTerminatingErrorExample {
Param($i = 5)
if ($i -gt 4) {
Write-Error "I expected a value less or equal to 4!"
}
Write-Host "However, I can still continue the execution"
}
However, I can't catch it with -ErrorAction Stop
Try {
NonTerminatingErrorExample -ErrorAction Stop
} Catch {
Write-Host "Now you see this message."
}
I never get the catch block returned. Why?
NonTerminatingErrorExample : I expected a value less or equal to 4!
In Zeile:32 Zeichen:5
+ NonTerminatingErrorExample -ErrorAction Stop
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,NonTerminatingErrorExample
However, I can still continue the execution
Your function isn't a cmdlet and therefore doesn't support -ErrorAction. Add [CmdletBinding()] to the body to make it one and the parameter will be supported. You can see the difference if you Get-Help both versions: as a function, there is no [<CommonParameters>].

System.InvalidOperationException not being catched in powershell script

am trying to catch a specific exception in a powershell script. Thecodeblock underneath is trying to do this, when the given exception is being thrown the script should ignore it:
try
{
Stop-WebAppPool $webServerSettings.applicationPool
}
catch [System.InvalidOperationException]
{
}
Unfortunately this does not seem to work, I still am getting this response:
Stop-WebAppPool : Object on target path is already stopped.
At F:\Portal.Update.ps1:12 char:2
+ Stop-WebAppPool $webServerSettings.applicationPool
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Stop-WebAppPool], InvalidOperationException
+ FullyQualifiedErrorId : InvalidOperation,Microsoft.IIs.PowerShell.Provider.StopAppPoolCommand
There are a few options I tried:
Passing the 'ErrorAction Stop' parameter value to the command. Now I do not have the error, but the script stops, that is not what I want.
Passing the 'ErrorAction SilentlyContinue' parameter value to the command. But this will only work when catching all types of exceptions, which I do not want because if the application pool does not exist, I want the exception to be thrown.
UPDATE:
I found a way to work around this problem: Just read out the $_.exception variable and with GetType() get the real exception type, but having a function in a catch does not seem to right way to go:
try
{
Stop-WebAppPool $webServerSettings.applicationPool
}
catch
{
if ($_.Exception.GetType().Name -eq "InvalidOperationException")
{
return
}
throw
}
Try,
try
{
Stop-WebAppPool $webServerSettings.applicationPool -ErrorAction Stop
}
Catch
{
"Unable to stop Web App Pool"
}
-ErrorAction Stop tells PowerShell to stop when it encounters an error; this suppresses the error output you were seeing.

How do I retain ScriptStackTrace in an exception thrown from within an Invoke-Command on a remote computer?

I'm writing a Powershell script which executes one of the steps in my build/deploy process, and it needs to run some actions on a remote machine. The script is relatively complex, so if an error occurs during that remote activity I want a detailed stack trace of where in the script the error occurred (over and above the logging that is already produced).
The problem arises in that Invoke-Command loses stack trace information when relaying terminating exceptions from a remote machine. If a script block is invoked on the local machine:
Invoke-Command -ScriptBlock {
throw "Test Error";
}
The required exception detail is returned:
Test Error
At C:\ScriptTest\Test2.ps1:4 char:2
+ throw "Test Error";
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
But if run remotely:
Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
throw "Test Error";
}
The exception stack trace points to the whole Invoke-Command block:
Test Error
At C:\ScriptTest\Test2.ps1:3 char:1
+ Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
I can transport the exception back to the local machine manually:
$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
try
{
throw "Test Error";
}
catch
{
return $_;
}
}
throw $exception;
But re-throwing it loses the stack trace:
Test Error
At C:\ScriptTest\Test2.ps1:14 char:1
+ throw $exception;
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:PSObject) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
If I write the exception to Output:
$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
try
{
throw "Test Error";
}
catch
{
return $_;
}
}
Write-Output $exception;
I get the correct stack trace information:
Test Error
At line:4 char:3
+ throw "Test Error";
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
But as it's not on the Error stream it isn't picked up correctly by my build tools. If I try Write-Error, I have a similar problem to re-throwing the exception and the stack trace points to the wrong part of the script.
So my question is - how do I get Powershell to report the exception from a remote machine as if it had been raised locally, with the same stack trace information and on the Error stream?
When you run some code and it fails, you receive an ErrorRecord that reflects the code you (local computer) executed. So when you use throw "error" you can access the invocationinfo and exception for that code.
When you use Invoke-Command, you are not executing throw "error" anymore, the remote computer is. You (local computer) are executing Invoke-Command ...., which is why the ErrorRecord you get reflects that (and not the real exception like you wanted). This is the way it has to be since an exception may be coming from the scriptblock the remote comptuer executed, but it could just as well be an exception from Invoke-Command itself because it couldn't connect to the remote computer or something similar.
When the exception is originally thrown on the remote computer, Invoke-Command/PowerShell throws a RemoteException on the local computer.
#Generate errors
try { Invoke-Command -ComputerName localhost -ScriptBlock { throw "error" } }
catch { $remoteexception = $_ }
try { throw "error" }
catch { $localexception = $_ }
#Get exeception-types
$localexception.Exception.GetType().Name
RuntimeException
$remoteexception.Exception.GetType().Name
RemoteException
This exception-type has a few extra properties, including SerializedRemoteException and SerializedRemoteInvocationInfo which contains the information from the exception that was thrown in the remote session. Using these, you can receive the "internal" exception.
SerializedRemoteException: Gets the original exception that was thrown by the remote instance of Windows PowerShell.
SerializedRemoteInvocationInfo: Gets the invocation information for the remote instance of Windows PowerShell.
Sample:
#Show command that threw error
$localexception.InvocationInfo.PositionMessage
At line:4 char:7
+ try { throw "error" }
+ ~~~~~~~~~~~~~
$remoteexception.Exception.SerializedRemoteInvocationInfo.PositionMessage
At line:1 char:2
+ throw "error"
+ ~~~~~~~~~~~~~
You can then write a simple function to extract the information dynamically, ex:
function getExceptionInvocationInfo ($ex) {
if($ex.Exception -is [System.Management.Automation.RemoteException]) {
$ex.Exception.SerializedRemoteInvocationInfo.PositionMessage
} else {
$ex.InvocationInfo.PositionMessage
}
}
function getException ($ex) {
if($ex.Exception -is [System.Management.Automation.RemoteException]) {
$ex.Exception.SerializedRemoteException
} else {
$ex.Exception
}
}
getExceptionInvocationInfo $localexception
At line:4 char:7
+ try { throw "error" }
+ ~~~~~~~~~~~~~
getExceptionInvocationInfo $remoteexception
At line:1 char:2
+ throw "error"
+ ~~~~~~~~~~~~~
Be aware that the SerializedRemoteExpcetion is shown as PSObject because of the serialization/deserialization during network transfer, so if you're going to check the exception-type you need to extract it from psobject.TypeNames.
$localexception.Exception.GetType().FullName
System.Management.Automation.ItemNotFoundException
$remoteexception.Exception.SerializedRemoteException.GetType().FullName
System.Management.Automation.PSObject
#Get TypeName from psobject
$remoteexception.Exception.SerializedRemoteException.psobject.TypeNames[0]
Deserialized.System.Management.Automation.ItemNotFoundException
I am sure someone with more experience can help but I would like to give you something to chew on in the mean time. Sounds like you want to be using throw since you are looking for terminating exceptions. Write-Error does write to the error stream but it is not terminating. Regardless of your choice there my suggestion is still the same.
Capturing the exception into a variable is a good start for this so I would recommend this block from your example:
$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
try
{
throw "Test Error";
}
catch
{
return $_;
}
}
$exception in this case should be a Deserialized.System.Management.Automation.ErrorRecord. You can send custom objects to throw...
You can also throw an ErrorRecord object or a Microsoft .NET Framework exception.
but in this case it does not work which is likely due to the deserialization. At one point I tried to create my own error object but some of the needed properties were read only so I skipped that.
PS M:\Scripts> throw $exception
Test Error
At line:1 char:1
+ throw $return
+ ~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:PSObject) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
However just writing to the output stream gives you the correct information as you have seen.
PS M:\Scripts> $exception
Test Error
At line:4 char:9
+ throw "Test Error";
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
$exception is an object will properties that contain all of this information so a possible way to get what you need would be to make a custom error string from the desired properties. However that turned out to be more work that it was worth. A simple compromise would be to use Out-String to convert that useful output so that it can be returned as an error.
PS M:\Scripts> throw ($return | out-string)
Test Error
At line:4 char:9
+ throw "Test Error";
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
At line:1 char:1
+ throw ($return | out-string)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error
At ...Test Error
:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
At line:4 char:9
+ throw "Test Error";
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
So now we have a proper terminating error with information from both relative to the scriptblock and to where in the calling code the error is generated. You will see some repetition obviously but maybe this will be a start for you.
If there is other information you need specifically I would delve into the $exception object with Get-Member to see if you can find something specific that you are looking for. Some notable properties here would be
$exception.ErrorCategory_Reason
$exception.PSComputerName
$exception.InvocationInfo.Line
$exception.InvocationInfo.CommandOrigin
The last one would read something like this.
PS M:\Scripts> $exception.InvocationInfo.PositionMessage
At line:4 char:9
+ throw "Test Error";
+ ~~~~~~~~~~~~~~~~~~
Hoping someone with more experience can comment on this, my comment seems to have gone overlooked.
I found this article which offers some insight into using Enter-PSSession
Or even better, create a persistent session
$session = New-PSSession localhost
Invoke-Command $session {ping example.com}
Invoke-Command $session {$LASTEXITCODE}
To do debugging using tracing, remember that you need to set the VerbosePreference, DebugPreference etc in the remote session
Invoke-Command $session {$VerbosePreference = ‘continue’}
Invoke-Command $session {Write-Verbose hi}