From fea4a5deeab601cec59b71c0913e30775047485e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 23 Jan 2024 17:54:12 +0100 Subject: [PATCH] Add new command New-ErrorRecord (#118) --- CHANGELOG.md | 13 ++ source/Public/New-ArgumentException.ps1 | 12 +- source/Public/New-ErrorRecord.ps1 | 129 ++++++++++++++++++ source/Public/New-InvalidDataException.ps1 | 4 +- .../Public/New-InvalidOperationException.ps1 | 14 +- source/Public/New-InvalidResultException.ps1 | 14 +- source/Public/New-NotImplementedException.ps1 | 12 +- source/Public/New-ObjectNotFoundException.ps1 | 14 +- tests/Unit/Public/New-ErrorRecord.Tests.ps1 | 78 +++++++++++ 9 files changed, 232 insertions(+), 58 deletions(-) create mode 100644 source/Public/New-ErrorRecord.ps1 create mode 100644 tests/Unit/Public/New-ErrorRecord.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d2c5b..bf94dc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 was set. - `New-Exception` - New command that creates and returns an `[System.Exception]`. +- `New-ErrorRecord` + - New command that creates and returns an `[System.Management.Automation.ErrorRecord]` + ([issue #99](https://github.com/dsccommunity/DscResource.Common/issues/99)). - `New-ArgumentException` - Now takes a parameter `PassThru` that returns the error record that was created (and does not throw). @@ -55,10 +58,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `New-ArgumentException` - Now has a command alias `New-InvalidArgumentException` and the command was renamed to match the exception name. + - Now uses the new command `New-ErrorRecord`. - `New-InvalidDataException` - The parameter `Message` has a parameter alias `ErrorMessage` to make the command have the same parameter names as the other `New-*Exception` commands. + - Now uses the new command `New-ErrorRecord`. +- `New-InvalidOperationException` + - Now uses the new command `New-ErrorRecord`. +- `New-InvalidResultException` + - Now uses the new command `New-ErrorRecord`. +- `New-NotImplementedException` + - Now uses the new command `New-ErrorRecord`. +- `New-ObjectNotFoundException` + - Now uses the new command `New-ErrorRecord`. ### Fixed diff --git a/source/Public/New-ArgumentException.ps1 b/source/Public/New-ArgumentException.ps1 index 300f8d9..febbd7d 100644 --- a/source/Public/New-ArgumentException.ps1 +++ b/source/Public/New-ArgumentException.ps1 @@ -16,7 +16,7 @@ .OUTPUTS None - System.Management.Automation.ErrorRecord + System.ArgumentException .EXAMPLE New-ArgumentException -ArgumentName 'Action' -Message 'My error message' @@ -36,6 +36,7 @@ function New-ArgumentException [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] [Alias('New-InvalidArgumentException')] + [OutputType([System.ArgumentException])] param ( [Parameter(Mandatory = $true)] @@ -53,15 +54,10 @@ function New-ArgumentException $PassThru ) - $argumentException = New-Object -TypeName 'ArgumentException' ` + $argumentException = New-Object -TypeName 'System.ArgumentException' ` -ArgumentList @($Message, $ArgumentName) - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @($argumentException, $ArgumentName, 'InvalidArgument', $null) - } - - $errorRecord = New-Object @newObjectParameters + $errorRecord = New-ErrorRecord -Exception $argumentException -ErrorId $ArgumentName -ErrorCategory 'InvalidArgument' if ($PassThru.IsPresent) { diff --git a/source/Public/New-ErrorRecord.ps1 b/source/Public/New-ErrorRecord.ps1 new file mode 100644 index 0000000..c8b5823 --- /dev/null +++ b/source/Public/New-ErrorRecord.ps1 @@ -0,0 +1,129 @@ +<# + .SYNOPSIS + Creates a new ErrorRecord. + + .DESCRIPTION + The New-ErrorRecord function creates a new ErrorRecord with the specified parameters. + + .PARAMETER ErrorRecord + Specifies an existing ErrorRecord. + + .PARAMETER Exception + Specifies the exception that caused the error. + + If an error record is passed to parameter ErrorRecord and if the wrapped exception + in the error record contains a `[System.Management.Automation.ParentContainsErrorRecordException]`, + the new ErrorRecord should have this exception as its Exception instead. + + .PARAMETER ErrorCategory + Specifies the category of the error. + + .PARAMETER TargetObject + Specifies the object that was being manipulated when the error occurred. + + .PARAMETER ErrorId + Specifies a string that uniquely identifies the error. + + .EXAMPLE + $ex = New-Exception -Message 'An error occurred.' + $errorRecord = New-ErrorRecord -Exception $ex -ErrorCategory 'InvalidOperation' + + This example creates a new ErrorRecord with the specified parameters. Passing + 'InvalidOperation' which is one available value of the enum `[System.Management.Automation.ErrorCategory]`. + + .EXAMPLE + $ex = New-Exception -Message 'An error occurred.' + $errorRecord = New-ErrorRecord -Exception $ex -ErrorCategory 'InvalidOperation' -TargetObject $myObject + + This example creates a new ErrorRecord with the specified parameters. TargetObject + is set to the object that was being manipulated when the error occurred. + + .EXAMPLE + $ex = New-Exception -Message 'An error occurred.' + $errorRecord = New-ErrorRecord -Exception $ex -ErrorCategory 'InvalidOperation' -ErrorId 'MyErrorId' + + This example creates a new ErrorRecord with the specified parameters. Passing + ErrorId that will be set as the FullyQualifiedErrorId in the error record. + + .EXAMPLE + $existingErrorRecord = [System.Management.Automation.ErrorRecord]::new( + [System.Management.Automation.ParentContainsErrorRecordException]::new('Existing error'), + 'ExistingErrorId', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $null + ) + $newException = [System.Exception]::new('New error') + $newErrorRecord = New-ErrorRecord -ErrorRecord $existingErrorRecord -Exception $newException + $newErrorRecord.Exception.Message + + This example first creates an emulated ErrorRecord that contain a `ParentContainsErrorRecordException` + which will be replaced by the new exception passed to New-ErrorRecord. The + result of `$newErrorRecord.Exception.Message` will be 'New error'. + + .INPUTS + System.Management.Automation.ErrorRecord, System.Exception, System.Management.Automation.ErrorCategory, System.Object, System.String + + .OUTPUTS + System.Management.Automation.ErrorRecord + + .NOTES + The function supports two parameter sets: 'ErrorRecord' and 'Exception'. + If the 'ErrorRecord' parameter set is used, the function creates a new ErrorRecord based on an existing one and an exception. + If the 'Exception' parameter set is used, the function creates a new ErrorRecord based on an exception, an error category, a target object, and an error ID. +#> +function New-ErrorRecord +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'The command does not change state.')] + [CmdletBinding(DefaultParameterSetName = 'Exception')] + [OutputType([System.Management.Automation.ErrorRecord])] + param + ( + [Parameter(Mandatory = $true, ParameterSetName = 'ErrorRecord')] + [System.Management.Automation.ErrorRecord] + $ErrorRecord, + + [Parameter(Mandatory = $true, ParameterSetName = 'ErrorRecord')] + [Parameter(Mandatory = $true, ParameterSetName = 'Exception')] + [System.Exception] + $Exception, + + [Parameter(Mandatory = $true, ParameterSetName = 'Exception')] + [System.Management.Automation.ErrorCategory] + $ErrorCategory, + + [Parameter(ParameterSetName = 'Exception')] + [System.Object] + $TargetObject = $null, + + [Parameter(ParameterSetName = 'Exception')] + [System.String] + $ErrorId = $null + ) + + switch ($PSCmdlet.ParameterSetName) + { + 'ErrorRecord' + { + $errorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList @( + $ErrorRecord, + $Exception + ) + + break + } + + 'Exception' + { + $errorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList @( + $Exception, + $ErrorId, + $ErrorCategory, + $TargetObject + ) + + break + } + } + + return $errorRecord +} diff --git a/source/Public/New-InvalidDataException.ps1 b/source/Public/New-InvalidDataException.ps1 index 11e45f7..3b0c9db 100644 --- a/source/Public/New-InvalidDataException.ps1 +++ b/source/Public/New-InvalidDataException.ps1 @@ -42,9 +42,7 @@ function New-InvalidDataException -TypeName 'System.InvalidOperationException' ` -ArgumentList $Message - $errorRecord = New-Object ` - -TypeName 'System.Management.Automation.ErrorRecord' ` - -ArgumentList $exception, $ErrorId, $errorCategory, $null + $errorRecord = New-ErrorRecord -Exception $exception -ErrorId $ErrorId -ErrorCategory $errorCategory throw $errorRecord } diff --git a/source/Public/New-InvalidOperationException.ps1 b/source/Public/New-InvalidOperationException.ps1 index 28f4ce5..f54a902 100644 --- a/source/Public/New-InvalidOperationException.ps1 +++ b/source/Public/New-InvalidOperationException.ps1 @@ -69,17 +69,7 @@ function New-InvalidOperationException -ArgumentList @($Message, $ErrorRecord.Exception) } - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( - $invalidOperationException.ToString(), - 'MachineStateIncorrect', - 'InvalidOperation', - $null - ) - } - - $errorRecordToReturn = New-Object @newObjectParameters + $errorRecord = New-ErrorRecord -Exception $invalidOperationException.ToString() -ErrorId 'MachineStateIncorrect' -ErrorCategory 'InvalidOperation' if ($PassThru.IsPresent) { @@ -87,6 +77,6 @@ function New-InvalidOperationException } else { - throw $errorRecordToReturn + throw $errorRecord } } diff --git a/source/Public/New-InvalidResultException.ps1 b/source/Public/New-InvalidResultException.ps1 index 9332a3e..e1353d3 100644 --- a/source/Public/New-InvalidResultException.ps1 +++ b/source/Public/New-InvalidResultException.ps1 @@ -55,17 +55,7 @@ function New-InvalidResultException $exception = New-Exception @PSBoundParameters - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( - $exception.ToString(), - 'MachineStateIncorrect', - 'InvalidResult', - $null - ) - } + $errorRecord = New-ErrorRecord -Exception $exception.ToString() -ErrorId 'MachineStateIncorrect' -ErrorCategory 'InvalidResult' - $errorRecordToThrow = New-Object @newObjectParameters - - throw $errorRecordToThrow + throw $errorRecord } diff --git a/source/Public/New-NotImplementedException.ps1 b/source/Public/New-NotImplementedException.ps1 index 525aa65..95a08d9 100644 --- a/source/Public/New-NotImplementedException.ps1 +++ b/source/Public/New-NotImplementedException.ps1 @@ -66,17 +66,7 @@ function New-NotImplementedException -ArgumentList @($Message, $ErrorRecord.Exception) } - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( - $notImplementedException.ToString(), - 'MachineStateIncorrect', - 'NotImplemented', - $null - ) - } - - $errorRecord = New-Object @newObjectParameters + $errorRecord = New-ErrorRecord -Exception $notImplementedException.ToString() -ErrorId 'MachineStateIncorrect' -ErrorCategory 'NotImplemented' if ($PassThru.IsPresent) { diff --git a/source/Public/New-ObjectNotFoundException.ps1 b/source/Public/New-ObjectNotFoundException.ps1 index c593f1a..2aaf57c 100644 --- a/source/Public/New-ObjectNotFoundException.ps1 +++ b/source/Public/New-ObjectNotFoundException.ps1 @@ -49,17 +49,7 @@ function New-ObjectNotFoundException $exception = New-Exception @PSBoundParameters - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( - $exception.ToString(), - 'MachineStateIncorrect', - 'ObjectNotFound', - $null - ) - } + $errorRecord = New-ErrorRecord -Exception $exception.ToString() -ErrorId 'MachineStateIncorrect' -ErrorCategory 'ObjectNotFound' - $errorRecordToThrow = New-Object @newObjectParameters - - throw $errorRecordToThrow + throw $errorRecord } diff --git a/tests/Unit/Public/New-ErrorRecord.Tests.ps1 b/tests/Unit/Public/New-ErrorRecord.Tests.ps1 new file mode 100644 index 0000000..f6fa494 --- /dev/null +++ b/tests/Unit/Public/New-ErrorRecord.Tests.ps1 @@ -0,0 +1,78 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:moduleName = 'DscResource.Common' + + # Make sure there are not other modules imported that will conflict with mocks. + Get-Module -Name $script:moduleName -All | Remove-Module -Force + + # Re-import the module using force to get any code changes between runs. + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Module -Name $script:moduleName +} + +Describe 'New-ErrorRecord' { + Context 'ErrorRecord parameter set' { + It 'creates a new ErrorRecord based on an existing one and an exception' { + $existingErrorRecord = [System.Management.Automation.ErrorRecord]::new( + [System.Management.Automation.ParentContainsErrorRecordException]::new('Existing error'), + 'ExistingErrorId', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $null + ) + $newException = [System.Exception]::new('New error') + $newErrorRecord = New-ErrorRecord -ErrorRecord $existingErrorRecord -Exception $newException + + $newErrorRecord.Exception.Message | Should -Be 'New error' + $newErrorRecord.FullyQualifiedErrorId | Should -Be 'ExistingErrorId' + $newErrorRecord.CategoryInfo.Category | Should -Be 'InvalidOperation' + } + } + + Context 'Exception parameter set' { + It 'creates a new ErrorRecord based on an exception, an error category, a target object, and an error ID' { + $exception = [System.Exception]::new('An error occurred.') + $targetObject = New-Object -TypeName PSObject + $errorRecord = New-ErrorRecord -Exception $exception -ErrorCategory 'InvalidOperation' -TargetObject $targetObject -ErrorId 'MyErrorId' + + $errorRecord.Exception.Message | Should -Be 'An error occurred.' + $errorRecord.FullyQualifiedErrorId | Should -Be 'MyErrorId' + $errorRecord.CategoryInfo.Category | Should -Be 'InvalidOperation' + $errorRecord.TargetObject | Should -Be $targetObject + } + } +}