I'm searching for two days now for a solution or something that helps me to understand why.
For some actions I want to send any error to the "-ErrorVariable create_error". No problem.
After that, I just want the exception of the error message:
$create_error = $create_error.Exception
The output of the variable is now:
$create_error
Access to the path '2021' is denied.
But if I use this variable as body text for an email, I get the following email text:
System.UnauthorizedAccessException: Access to the path '2021' is denied.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, Boolean checkHost)
at System.IO.DirectoryInfo.CreateSubdirectoryHelper(String path, Object directorySecurity)
at System.IO.DirectoryInfo.CreateSubdirectory(String path)
at Microsoft.PowerShell.Commands.FileSystemProvider.CreateDirectory(String path, Boolean streamOutput)
Sendmail:
Send-MailMessage -To $to -From $from -Subject $subject -Body $create_error
Any ideas how I get in an email only the human readable error message?
Thank you!
Best regards!
tl;dr:
To get just the error messages associated with PowerShell error records collected with the common -ErrorVariable parameter:
$create_error.ForEach('ToString') # Call .ToString() on all records
Note: If your variable contains only a single error record (not wrapped in a collection), simply call .ToString(), such as when accessing a single error in the session-wide automatic $Error collection:
Get-Item NoSuch -ErrorAction SilentlyContinue # provoke an error
# Access the most recent error individually:
$Error[0].ToString() # -> 'Cannot find path '...\NoSuch' because it does not exist.'
Background information:
The common -ErrorVariable parameter collects the non-terminating errors that occur during execution of a given command as System.Management.Automation.ErrorRecord objects in an array-like collection (System.Collections.ArrayList) - even if only one error is collected.
Note: Terminating errors, by contrast - which cmdlets rarely emit - abort the entire command, so that requests to collect the command's own stream output with common parameters such as -ErrorVariable and -OutVariable are not honored. To handle such errors, you need a try ... catch ... finally statement. See this answer for details, and this GitHub docs issue for a comprehensive overview of PowerShell's surprisingly complex error handling.
These error records wrap .NET exceptions by supplementing them with additional, PowerShell-relevant metadata, such as the location of the error in a script file (*.ps1).
There are two separate fields that may contain the message (string) that characterizes the error at hand best:
.Exception.Message
optionally, .ErrorDetails.Message, which can be used to override .Exception.Message in the default display format of the error in the console.
The .ToString() method wraps this logic, so it is the simplest way to get the error message.
Sample code:
Note: Typically, .ErrorDetails is $null. Setting it requires an extra step when constructing an error record.
Here's an example in PowerShell (though note that it's more common to construct error records in compiled cmdlets):
# Create an error record.
$err = [System.Management.Automation.ErrorRecord]::new(
[InvalidOperationException]::new('foo'), # the exception being wrapped
'errId', # error ID
'InvalidOperation', # category
$null # the target object
)
# Then assign an ErrorDetails instance with a custom message.
$err.ErrorDetails = [System.Management.Automation.ErrorDetails] 'bar'
Now, $err.Exception.Message reports 'foo', and $err.ErrorDetails.Message 'bar'. When you call .ToString(), the .ErrorDetails.Message value takes precedence; if .ErrorDetails were $null, 'foo' would be reported:
PS> $err.ToString()
bar
Related
I want to include Whatif and Confirm to my functions but I encountered an issue with these parameters.
My functions are structured like this:
function Get-Stuff {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
param ( {...} )
process {
if ($PSCmdlet.ShouldProcess($Name, "Delete user")) {
$result = Invoke-RestMethod #restBody
}
}
end {
Write-Output -InputObject $result
Remove-Variable -Name result
}
}
I took on a habit to clean up my variables in the end-block with Remove-Variable. When I use now the -WhatIf or the -Confirm parameter (and denying it), I get an error that the $result variable is null.
ErrorRecord : Cannot find a variable with the name 'result'.
I understand that the RestMethod is skipped in this case but I would assume that the rest of the function would not be executed further.
My question is now, does one add an else-clause to end the continuing execution of the function or do I use these parameters incorrectly?
There's no good reason to remove your variables in the end block, since they go out of scope automatically anyway, given that they're local to your function.
(The only thing that makes sense is to .Dispose() of variables containing objects that implement the System.IDisposable interface; if releasing memory as quickly as possible is paramount - at the expense of blocking execution temporarily - you can additionally call [GC]::Collect(); [GC]::WaitForPendingFinalizers())
If you still want to call Remove-Variable, you have two options:
Simply ignore a non-existent variable by adding -ErrorAction Ignore to the Remove-Variable call.
Remove-Variable -Name result -ErrorAction Ignore
Alternatively, protect the call - and the Write-Output object - with an explicit test for the existence of the variable:
if (Get-Variable -Scope Local result -ErrorAction Ignore) {
$result # short for: Write-Output -InputObject
Remove-Variable -Name result
}
Also note that it's typical for output objects to be emitted directly from the process block - emitting from the end block is only a necessity for commands that for conceptual reasons must collect all input first, such as Sort-Object.
Emitting output objects from the process block - which is invoked for each input object - ensures the streaming output behavior - emitting objects one by one, as soon as they're available - that the pipeline was designed for.
I'm trying to grasp the Write-Error and $Error automatic variable with the code sample below, which is supposed to generate a single error:
function Test-Error
{
[CmdletBinding()]
param()
Write-Error -Message "Error message" -Category PermissionDenied -ErrorId SampleID
}
$Error.Clear()
$ErrorActionPreference = "Stop"
Test-Error
According to documentation:
$Error Contains an array of error objects that represent the most
recent errors. The most recent error is the first error object in the
array $Error[0].
OK so let's take a look at error contents now:
$Error[0] | select -property *
This will print out the error, (I'm not pasting it here because it's not relevant to the question)
So according to docs this the error, but it looks like docs are wrong:
$Error[1] | select -property *
This will also print out something!
The question is how is that single error happens to be split into 2 pieces?
Isn't just $Error[0] supposed to contain last error as the docs say?
Obviously I did clear the variable so only single element is supposed to be in the array, not 2.
both elements seem to be pointing to single error, but what's the purpose of 2 array elements for single error?
and if the $Error variable happens to have 30 or so errors how do tell which 2 are relating to same error? because that means 60 elements for 30 errors!
The other error is due to $ErrorActionPreference = "Stop" - the error message that you did not post would have clarified it, the Message property is The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: Error message. If you do not use that, you get only one element in $Error.
I'm using a function that I call from another script. It prompts a user for input until it gets back something that is not empty or null.
function GetUserInputValue($InputValue)
{
do{
$UserValue = Read-Host -Prompt $InputValue
if (!$UserValue) { $InputValue + ' cannot be empty' }
}while(!$UserValue)
$UserValue
return $UserValue
}
The issue is quite strange and likely a result of my lack of powershell experience. When I run the code and provide empty results, the messages from the if statement queue up and only display when I finally provide a valid input. See my console output below.
Console Results
test:
test:
test:
test:
test:
test:
test: 1
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
1
I can make this work however in the main file with hard coded values.
do{
$Server = Read-Host -Prompt 'Server'
if (!$Server) { 'Server cannot be empty' }
}while(!$Server)
I'm working Visual Studio Code. This is a function I have in another file I've named functions.ps1.
I call this from my main file like this,
$test = GetUserInputValue("test")
$test
When you put a naked value in a script like "here's a message" or 5 or even a variable by itself $PID what you're implicitly doing is calling Write-Output against that value.
That returns the object to the pipeline, and it gets added to the objects that that returns. So in a function, it's the return value of the function, in a ForEach-Object block it's the return value of the block, etc. This bubbles all the back up the stack / pipeline.
When it has nowhere higher to go, the host handles it.
The console host (powershell.exe) or ISE host (powershell_ise.exe) handle this by displaying the object on the console; this just happens to be the way they handle it. Another host (a custom C# application for example can host the powershell runtime) might handle it differently.
So what's happening here is that you are returning the message that you want to display, as part of the return value of your function, which is not what you want.
Instead, you should use Write-Host, as this writes directly to the host, skipping the pipeline. This is the correct command to use when you want to display a message to the user that must be shown (for other information you can use different commands like Write-Verbose, Write-Warning, Write-Error, etc.).
Doing this will give you the correct result, and prevent your informational message from being part of the return value of your function.
Speaking of which, you are returning the value twice. You don't need to do:
$UserValue
return $UserValue
The first one returns the value anyway (see the top of this answer); the second one does the same thing except that it returns immediately. Since it's at the end of the function anyway, you can use wither one, but only use one.
One more note: do not call PowerShell functions with parentheses:
$test = GetUserInputValue("test")
This works only because the function has a single parameter. If it had multiple params and you attempted to call it like a method (with parentheses and commas) it would not work correctly. You should separate arguments with spaces, and you should usually call parameters by name:
$test = GetUserInputValue "test"
# better:
$test = GetUserInputValue -InputValue "test"
I have PS module that contains a number of scripts for individual functions. There is also a "library" script with a number of helper functions that get called by the functions used in the module.
Let's call the outer function ReadWeb, and it uses a helper function ParseXML.
I encountered an issue this week with error handling in the inner helper ParseXML function. That function contains a try/catch, and in the catch I interrogate:
$Error[0].Exception.InnerException.Message
...in order to pass the error back to the outer scope as a variable and determine if ParseXML worked.
For a particular case, I was getting an indexing error when I called ReadWeb. The root cause turned out to be the $Error object in the Catch block in ParseXML was coming back $Null.
I changed the error handling to check for a $Error -eq $Null and if it is, use $_ in the Catch to determine what the error message is.
My question is: what would cause $Error to be $null inside the Catch?
Note: This is written from the perspective of Windows PowerShell 5.1 and PowerShell (Core) 7.2.3 - it is possible that earlier Windows PowerShell versions behaved differently, though I suspect they didn't.
Accessing the error at hand inside a catch block should indeed be done via the automatic $_ variable.
Inside a module, $Error isn't $null, but surprisingly is an always-empty collection (of type System.Collections.ArrayList); therefore, $Error[0] - which in catch blocks outside modules is the same as $_ - is unexpectedly $null in modules:
What technically happens - and this may be a bug - is that modules have an unused, module-local copy of $Error, which shadows (hides) the true $Error variable that is located in the global scope.
When an error is automatically recorded from inside a module, however, it is added to the global $Error collection - just like in code running outside of modules.
Therefore, a workaround is to use $global:Error instead (which is only necessary if you need access to previous errors; as stated, for the current one, use $_).
The following sample code illustrates the problem:
$Error.Clear()
# Define a dynamic module that exports sample function 'foo'.
$null = New-Module {
Function foo {
try {
1 / 0 # Provoke an error that can be caught.
}
catch {
$varNames = '$Error', '$global:Error', '$_'
$varValues = $Error, $global:Error, $_
foreach ($i in 0..($varNames.Count-1)) {
[pscustomobject] #{
Name = $varNames[$i]
Type = $varValues[$i].GetType().FullName
Value = $varValues[$i]
}
}
}
}
}
foo
Output; note how the value of $Error is {}, indicating an empty collection:
Name Type Value
---- ---- -----
$Error System.Collections.ArrayList {}
$global:Error System.Collections.ArrayList {Attempted to divide by zero.}
$_ System.Management.Automation.ErrorRecord Attempted to divide by zero.
Edit: answer based on Powershell 3.
$error is an automatic variable handled by Powershell: 3rd ยง of the LONG DESCRIPTION in about_Try_Catch_Finally.
It is considered as the context of the Catch block, thus being available as $_.
Since Catch block is a different block than Try, the $error automatic variable is reset and valued $null.
$error and try / catch are different beasts in PowerShell.
try / catch catches terminating errors, but won't catch a Write-Error (cause it's non terminating).
$error is a list of all errors encountered (including ones swallowed up when -ErrorAction silentlycontinue is used).
$_ is the current error in a try/catch block.
I'd guess that your underlying function calls Write-Error, and you want that to go cleanly into a try/catch. To make this be a terminating error as well, use -ErrorAction Stop.
Using Powershell, how can I add an entry to the $Error ArrayCollection (or possibly, more WHAT should I add to the collection)?
To my undertanding using Write-Error will both output the error AND add the entry to the errors collection. But I would just like to have an entry added directly to the collection without outputting at that time.
The ErrorAction switch should be able to handle that.
write-error "Something bad happened" -ErrorAction:SilentlyContinue