So PowerShell error reporting seems like a huge mess considering that there are four* ways to report an error (not including the effects of $ErrorActionPreference, try/catch, and trap blocks which have the potential to alter how an error is reported by another source), with minute and poorly documented differences, and even worse there's no real instruction on when to use the different types. (I realize it varies on the situation, but what's the "standard" - what will others be expecting me to do?)
That's not really the main point here though. I've decided to go with the simplest option when possible as that's what I expect most others will be using, so at least we'll have consistency - but I'm either not understanding something properly or it's broken.
According to the PowerShell documentation, the Activity property of ErrorCategoryInfo class (observed in the CategoryInfo property of System.Management.Automation.ErrorRecord) should be a "text description of the operation which encountered the error". Considering the fact that the value is settable and (in the case of compiled cmdlets) indicates the name of the cmdlet unless otherwise specified, I would expect the same functionality in script-based error reporting methods, but I'm doing something wrong or I've found a bug in PowerShell that has pervaded since PowerShell 1.0.
Consider the following example:
using namespace System.Management.Automation;
function Invoke-Error1 {
[CmdletBinding()]
param()
process {
$ex = [InvalidOperationException]::new()
$er = [ErrorRecord]::new($ex, 'OperationTest', [ErrorCategory]::InvalidOperation, 'MyErrorTarget')
$er.CategoryInfo.Activity = 'MyActivity'
Write-Error -ErrorRecord $er
}
}
Invoking this function will result in the following error.
invoke-error1 : Operation is not valid due to the current state of the object.
At line:1 char:1
+ invoke-error1
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (MyErrorTarget:String) [Write-Error], InvalidOperationException
+ FullyQualifiedErrorId : OperationTest,Invoke-Error1
When investigating further, you'll notice that the CategoryInfo.Activity property has been replaced by Write-Error. The value I set ("MyActivity") is ignored. If this were expected, I wouldn't expect a parameter on the Write-Error method to set the error category - and yet one seems to exist. (See Get-Help Write-Error -Parameter CategoryActivity. I can't see anything else that this might be intended to do.)
This can be confirmed through the following function.
function Invoke-Error2 {
[CmdletBinding()]
param()
process {
$Params = @{
Exception = [InvalidOperationException]::new()
ErrorId = 'OperationTest'
Category = [ErrorCategory]::InvalidOperation
TargetObject = 'MyErrorTarget'
CategoryActivity = 'MyActivity'
}
Write-Error @Params
}
}
The error reported is identical in all ways to the error from Invoke-Error1 aside from the name of the cmdlet and associated InvocationInfo. Specifically, note again that $Error[0].CategoryInfo.Activity -eq 'Write-Error' instead of "MyActivity".
And yet, there is a way to write an error that does properly write the error activity with the error... but it seems to be the lowest on the list of recommended ways to report an error since it's generally the same as Write-Error.
function Invoke-Error3 {
[CmdletBinding()]
param()
process {
$ex = [InvalidOperationException]::new()
$er = [ErrorRecord]::new($ex, 'OperationTest', [ErrorCategory]::InvalidOperation, 'MyErrorTarget')
$er.CategoryInfo.Activity = 'MyActivity'
$PSCmdlet.WriteError($er)
}
}
This function is identical in all ways to Invoke-Error1 except that I use $PSCmdlet.WriteError instead of Write-Error. According to certain community standards, it's preferred to use built-in functions over .NET calls, though of course when behavior differs it makes sense to use the procedure that produces the desired result.
This is the most similar method to how any compiled cmdlet writes an error. As a result, any error written by a cmdlet will use either a manually set Activity value, or use the default of the cmdlet's name. I believe the latter of these is somehow what's happening with Write-Error.
That being said, I can't feel like I'm missing something here. The CategoryInfo property and associated CategoryActivity parameter of Write-Error have existed since PowerShell 1.0. Certainly if this functionality never worked someone would have noticed by now? So what am I doing wrong? How would I properly report the error? Going back to the purpose of the Activity property of ErrorCategoryInfo, "Write-Error" is not the operation that encountered the error - my function did, and I want to report it using Write-Error. Is this possible?
*If you aren't aware, the errors that can be reported from a PowerShell script or script function are the following:
- Script-Terminating (via
throw) - Statement-Terminating (via
$PSCmdlet.ThrowTerminatingError) - Non-Terminating Error (via
Write-Error) - Non-Terminating Error (via
$PSCmdlet.WriteError). This is much harder to distinguish fromWrite-Errorbut does behave differently - note this question, and the fact that$ErrorActionPreferencedoes not affect the behavior of an error emitted directly by$PSCmdlet.WriteError(though$ErrorActionPreferencewill affect behavior of an error emitted indirectly from$PSCmdlet.WriteErrorif the direct source is a script). This can be tested by setting$ErrorActionPreferenceinside your function that proceeds to call$PSCmdlet.WriteErrorvsWrite-Error. - In addition to these methods, any non-terminating error can be turned into a statement-terminating error via
$ErrorActionPreferenceor the-ErrorActioncommon parameter. The$?variable can be used in error reporting but seems like a terrible way to do so based on how much the result of that variable varies.try/catchblocks can be used to catch errors and access them through the$PSItemvariable, but within that block if you're executing from a function defined in a module you may or may not see the error through$Errordepending on whether or not the command that reported the error is defined in your module. Writing a permeation of a caught exception may yield two errors being added to the$global:Errorvariable - the caught exception that I'd expect to be ignored will be placed in$global:Errorif the command that caught it is defined in a module other than the one the error was thrown from, and the new error that I reported.