Why I get error message printed on the console when running these two simple samples ?
I want that I get "Error testing :)" printed on the console insted of:
Get-WmiObject : The RPC server is
unavailable. (Exception from HRESULT:
0x800706BA) At line:3 char:15
+ Get-WmiObject <<<< -ComputerName possibly.nonexisting.domain.com
-Credential (Get-Credential) -Class Win32_logicaldisk
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject],
COMException
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
or
Attempted to divide by zero. At line:3
char:13
+ $i = 1/ <<<< 0
+ CategoryInfo : NotSpecified: (:) [],
ParentContainsErrorRecordException
+ FullyQualifiedErrorId : RuntimeException
First example:
try
{
$i = 1/0
Write-Host $i
}
catch [Exception]
{
Write-Host "Error testing :)"
}
Second example:
try
{
Get-WmiObject -ComputerName possibly.nonexisting.domain.com -Credential (Get-Credential) -Class Win32_logicaldisk
}
catch [Exception]
{
Write-Host "Error testing :)"
}
Thank you very much!
First example
The error happens at compile/parsing time (PowerShell is clever enough), so that the code is not even executed and it cannot catch anything, indeed. Try this code instead and you will catch an exception:
try
{
$x = 0
$i = 1/$x
Write-Host $i
}
catch [Exception]
{
Write-Host "Error testing :)"
}
Second example
If you set $ErrorActionPreference = 'Stop' globally then you will get "Error testing :)" printed, as expected. But your $ErrorActionPreference is presumably 'Continue': in that case there is no terminating error/exception and you just get the non terminating error message printed to the host by the engine.
Instead of the global $ErrorActionPreference option you can also play with Get-WmiObject parameter ErrorAction. Try to set it to Stop and you will catch an exception.
try
{
Get-WmiObject -ErrorAction Stop -ComputerName possibly.nonexisting.domain.com -Credential (Get-Credential) -Class Win32_logicaldisk
}
catch [Exception]
{
Write-Host "Error testing :)"
}
Related
I have a funktion to list all email-addresses on a specified user in Office365 and I use this function to check if an email-address is available.
Why is the Try-Catch not working in VS Code but in PS ISE?
Function ListAllEmailAdresses
{
param ($ID)
Try
{
$Mailbox = Get-Mailbox -Identity $ID -ErrorAction Stop
$VarUser = Get-User -Identity $ID -ErrorAction Stop
Write-Output "Email is occupied by: $Mailbox, $($VarUser.Title)"
Write-Output $Mailbox.EmailAddresses
}
Catch
{
Write-Output "E-mail $ID is available"
}
}
when calling this function in VS Code the Try-Catch is not working.
PS C:\script> ListAllEmailAdresses -ID 'lidingo#elon.se'
The operation couldn't be performed because object 'lidingo#elon.se' couldn't be found on 'VI1PR03DC0025.eurprd03.prod.outlook.com'.
+ CategoryInfo : NotSpecified: (:) [Get-Mailbox], ManagementObjectNotFoundException
+ FullyQualifiedErrorId : [Server=VI1PR03MB3165,RequestId=62016320-c687-41ed-9949-4b0fe854d821,TimeStamp=2017-08-09 13:54:40] [FailureCategory=Cmdlet-ManagementObjectNotFoundException] 2CD51ECE,Microsoft.Exchange.Management.RecipientTasks.GetMailbox
+ PSComputerName : ps.outlook.com
The operation couldn't be performed because object 'lidingo#elon.se' couldn't be found on 'VI1PR03DC0025.eurprd03.prod.outlook.com'.
+ CategoryInfo : NotSpecified: (:) [Get-User], ManagementObjectNotFoundException
+ FullyQualifiedErrorId : [Server=VI1PR03MB3165,RequestId=db435869-8b64-4edf-bb90-a2eb836b07fa,TimeStamp=2017-08-09 13:54:41] [FailureCategory=Cmdlet-ManagementObjectNotFoundException] DAFB12B9,Microsoft.Exchange.Management.RecipientTasks.GetUser
+ PSComputerName : ps.outlook.com
Email is occupied by: ,
The PS Error say that the called E-mailadrdress could not be found and therefore it should got to the Catch-code. But it doesnt :(
If I call the same function in Powershell ISE I get this result:
PS C:\Script> ListAllEmailAdresses -ID 'lidingo#elon.se'
E-mail lidingo#elon.se is available
The PS Error is catched and I get the wanted output.
/Anders
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}
I'm writing a simple script to parse some event logs but I need to silence some errors for times when there are no results, or if the instanceid is invalid:
PS C:\> get-eventlog Application -instanceid 1111
Get-EventLog : No matches found
At line:1 char:13
+ get-eventlog <<<< Application -instanceid 1111
+ CategoryInfo : ObjectNotFound: (:) [Get-EventLog], ArgumentException
+ FullyQualifiedErrorId : GetEventLogNoEntriesFound,Microsoft.PowerShell.Commands.GetEventLogCommand
I can do that and silence it, but that would also silence other errors:
PS C:\> try { get-eventlog Application -instanceid 1111 -erroraction stop } catch { }
I tried this but it doesn't work:
PS C:\> try { get-eventlog Application -instanceid 1111 -erroraction stop } catch [ObjectNotFound] { }
Unable to find type [ObjectNotFound]: make sure that the assembly containing this type is loaded.
At line:1 char:91
+ try { get-eventlog Application -instanceid 1111 -erroraction stop } catch [ObjectNotFound] <<<< { }
+ CategoryInfo : InvalidOperation: (ObjectNotFound:String) [], RuntimeException
+ FullyQualifiedErrorId : TypeNotFound
you can use -ErrorAction SilentlyContinue and check your $error variable after it,
$error[0]
It will always contains the last error object.
By no means the only option but you could try something like this:
$result = get-eventlog Application -instanceid 1111 -erroraction silentlycontinue
if($result){
Write-Host "Found some."
} else{
Write-Host "wah wah wah waaaah... you know like the trombone sound"
}
Once again I dont read a post fully. To make better on my answer I offer up this which might help your try block woes
try {
get-eventlog Application -instanceid 1111 -ErrorAction Stop
} Catch [Exception]{
$theError = $_
Switch($theError .Exception.GetType().FullName){
System.InvalidOperationException{Write-Host "This happened: $($theError.Exception.Message)"}
System.ArgumentException {Write-Host "This happened: $($theError.Exception.Message)"}
default{"Something else happened: $($theError.Exception.GetType().FullName)"}
}
}
Use -Stop to create a terminating error. Capture any exceptions and put the error object into variable so it can be used in other scopes later. Get the exception name and use a switch statement on it to determine appropriate action. In your case of "No matches found" that throws a [System.ArgumentException] which you can tell from looking at the value of $_.Exception.GetType().FullName. Capture the specific errors in the switch statement and if you have not already caught a particular exception before you can see the details in default.
For what its worth [System.InvalidOperationException] occurred when I replaced "Application" in the cmdlet call to "Fizgig"
you should try following syntax to get your error while not stopping script execution :
try
{
# check for eventLog
Get-EventLog -LogName "Application" -InstanceId 1111 -ErrorAction Stop
}
catch
{
# send error as ID
Write-Warning "Error -Message $($_.Exception.Message) -Line $($_.InvocationInfo.ScriptLineNumber) -Time $(Get-Date -Format 'HH.mm.ss.fff')"
}
I want to output a custom error message to a log, when a GWMI query fails. I can catch the exception, but apparently only the last one, because the output in my error file only has the name of the last computer in the list. I am using a list of computers I know do not have WMI enabled. There should be an entry for each one.
I have a list of domain computers in a text file, each on a single line, no trailing characters. I loop through the file to get network information, using GWMI. Some of the computers do not have WMI enabled, and I want to know which ones. My current script just throws a:
gwmi : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)
At line:12 char:17
+ $base = gwmi win32_networkadapterconfiguration -computername $comp | whe ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], COMException
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
whenever it loops through a machine that does not have wmi enabled.
This does not identify the computer that threw the exception. What I would like is for every time the RPC server is unavailable... error is thrown, for a custom error message to be written to a log with the name of the computer that threw the exception.
My script:
$computers = Get-Content -path f:\scripts\docs\computer_list_test.txt
if (F:\scripts\wmi_mac_output.txt){
rm F:\scripts\wmi_mac_output.txt
}
foreach ($comp in $computers) {
try
{
$base = gwmi win32_networkadapterconfiguration -computername $comp -ErrorAction Stop | where {$_.dnsdomain -eq "mydomain.com"}
$machine = $base.DNSHostName
$mac = $base.MACAddress
$ip = $base.IPAddress
"<COMPUTER>`n`tname: $machine`n`tMAC: $mac`n`tIP: $ip`n</COMPUTER>" | Out-File F:\scripts\wmi_mac_output.txt
}
catch [Exception]
{
if ($_.Exception.GetType().Name -eq "COMException")
{
"$comp has no winrm" > f:\scripts\docs\error.txt
}
}
}
Thank you.
I can catch the exception, but apparently only the last one, because the output in my error file only has the name of the last computer in the list.
The issue is that the error.txt is being overwritten instead of appended.
"$comp has no winrm" > f:\scripts\docs\error.txt
Change to:
"$comp has no winrm" >> f:\scripts\docs\error.txt
I have a problem with the catch command. I have the following script I'm trying to process:
Try
{
Add-Computer -DomainName "MyDomain.Dom" -Credential $DomainCred -PassThru -ErrorAction Stop
}
Catch [System.InvalidOperationException]
{
"Your computer is unable to contact the domain"
}
Every time I run this though I am not getting anything in the catch block. Here is the error reported that I get from the script:
PSMessageDetails :
Exception : System.InvalidOperationException: This command cannot be executed on target computer('') due to following error: The specified domain either does not exist or could not
be contacted.
TargetObject :
CategoryInfo : InvalidOperation: (MYPC:String) [Add-Computer], InvalidOperationException
FullyQualifiedErrorId : InvalidOperationException,Microsoft.PowerShell.Commands.AddComputerCommand
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
PipelineIterationInfo : {0, 1}
Any ideas?
A working solution (thanks to PK and Patrick for their combined contributions):
Try
{
Add-Computer -DomainName "MyDomain.Dom" -Credential $DomainCred -PassThru -ErrorAction Stop
}
Catch [System.Management.Automation.RuntimeException]
{
"Your computer is unable to contact the domain"
}
Try catching System.Management.Automation.RuntimeException instead of System.InvalidOperationException.
Try
{
Add-Computer -DomainName "MyDomain.Dom" -Credential $DomainCred
}
Catch [System.Management.Automation.RuntimeException]
{
'Error: {0}' -f $_.Exception.Message
}
Add "-ErrorActionPreference Stop" to your cmdlet.
For instance,
Add-Computer -DomainName "MyDomain.Dom" -Credential $DomainCred -EA Stop
There does seem to be a few inconsistencies with the ways that different cmdlets process errors, especially those "add-on" cmdlets like the Active Directory ones. However, I think the basic idea is that PowerShell catch only catches terminating errors, of which your exception above isn't by default. So by using -EA Stop you're forcing it be a terminating error, which triggers the catch block.
Here's Ed Wilson on the subject: Write PowerShell Functions That Accept Pipelined Input
I was able to get this to work:
Try
{
Add-Computer -DomainName "MyDomain.Dom" -Credential $DomainCred -PassThru -ErrorAction Stop
}
Catch
{
"Your computer is unable to contact the domain"
}
-PassThru on the Add-Computer command returns the results of the command to the shell.
-ErrorAction Stop tells PowerShell to stop when it encounters an error; this suppresses the error output you were seeing.
FullName
--------
System.Management.Automation.RuntimeException
The object of type "Microsoft.PowerShell.Commands.Internal.Format.FormatStartData" is not valid or not in the correct sequence. This is likely caused by a user-specified "f
ormat-list" command which is conflicting with the default formatting.
+ CategoryInfo : InvalidData: (:) [out-lineoutput], InvalidOperationException
+ FullyQualifiedErrorId : ConsoleLineOutputOutOfSequencePacket,Microsoft.PowerShell.Commands.OutLineOutputCommand
Putting the -Passthru on it allowed it to catch the error.