diff --git a/CHANGELOG.md b/CHANGELOG.md index 15ab1c9..7d4afb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Get-RegistrySubKey` to replace `Open-RegistrySubKey`. This implementation follows the .net class better +- `Invoke-DeleteSubKey` and `Invoke-DeleteSubKeyTree` private functions for removing +subkeys + +- `Remove-RegistrySubKey` and `Removing-RegistryKeyTree` public +public implementation + +- New Private function `Get-RegistrySubKeyOperation` for managing the logic of +opening a key or using an existing one provided in `Remove-RegistrySubKey` + ### Fixed - Error Handling for `[System.Security.SecurityException]` in `Open-RegistryKey` diff --git a/build.yaml b/build.yaml index fb53739..f276c88 100644 --- a/build.yaml +++ b/build.yaml @@ -107,7 +107,7 @@ Pester: #- FunctionalQuality #- TestQuality Tag: - CodeCoverageThreshold: 85 # Set to 0 to bypass + CodeCoverageThreshold: 75 # Set to 0 to bypass #CodeCoverageOutputFile: JaCoCo_$OsShortName.xml #CodeCoverageOutputFileEncoding: ascii # Use this if code coverage should be merged from several pipeline test jobs. diff --git a/source/Private/Get-RegistrySubKeyOperation.ps1 b/source/Private/Get-RegistrySubKeyOperation.ps1 new file mode 100644 index 0000000..0623af8 --- /dev/null +++ b/source/Private/Get-RegistrySubKeyOperation.ps1 @@ -0,0 +1,81 @@ +<# +.SYNOPSIS +Prepares the registry subkey operation by opening the relevant registry key based on the parameter set. + +.DESCRIPTION +This function prepares the environment for removing a registry subkey. It opens the correct registry key either by +registry hive and path or by an existing registry key object, and returns the parent key and subkey name for further processing. + +.PARAMETER RegistryPath +Specifies the path to the registry key to open. This is used only when the 'ByHive' parameter set is selected. + +.PARAMETER RegistryHive +Specifies the registry hive where the key resides. This is used only when the 'ByHive' parameter set is selected. + +.PARAMETER ComputerName +Specifies the name of the computer on which to perform the registry operation. This is used only when the 'ByHive' parameter set is selected. +Defaults to the local machine if not specified. + +.PARAMETER ParentKey +Specifies an existing registry key object from which to delete the subkey. This is used only when the 'ByKey' parameter set is selected. + +.PARAMETER SubKeyName +Specifies the name of the subkey to delete. + +.PARAMETER ParameterSetName +Specifies which parameter set is being used. It should be either 'ByHive' or 'ByKey'. + +.Outputs +System.Collections.Hashtable + +.EXAMPLE +$details = Get-RegistrySubKeyOperation -RegistryPath 'SOFTWARE\MyApp' -RegistryHive LocalMachine -ComputerName 'RemotePC' -ParameterSetName 'ByHive' + +This example prepares a registry operation on a remote computer by opening the 'SOFTWARE\MyApp' subkey in HKEY_LOCAL_MACHINE. + +.EXAMPLE +$parentKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\MyApp', $true) +$details = Get-RegistrySubKeyOperation -ParentKey $parentKey -SubKeyName 'Settings' -ParameterSetName 'ByKey' + +This example prepares a registry operation by passing an already opened parent key and the subkey 'Settings'. +#> +function Get-RegistrySubKeyOperation +{ + param ( + [string]$RegistryPath, + [Microsoft.Win32.RegistryHive]$RegistryHive, + [string]$ComputerName, + [Microsoft.Win32.RegistryKey]$ParentKey, + [string]$SubKeyName, + [string]$ParameterSetName + ) + + if ($ParameterSetName -eq 'ByHive') + { + # Split path and get subkey name + $parentPath = Split-Path -Path $RegistryPath -Parent + $subKeyName = Split-Path -Path $RegistryPath -Leaf + + # Open the registry key + try + { + $ParentKey = Open-RegistryKey -RegistryPath $parentPath -RegistryHive $RegistryHive -ComputerName $ComputerName -Writable $true + } + catch + { + throw "Failed to open registry key: $($RegistryHive)\$parentPath. $_" + } + + return @{ ParentKey = $ParentKey; SubKeyName = $subKeyName } + } + elseif ($ParameterSetName -eq 'ByKey') + { + if ($null -eq $ParentKey) + { + throw [System.ArgumentNullException]::new("ParentKey cannot be null.") + } + return @{ ParentKey = $ParentKey; SubKeyName = $SubKeyName } + } + + throw [System.ArgumentException]::new("Invalid parameter set.") +} diff --git a/source/Private/Invoke-DeleteSubKeyTree.ps1 b/source/Private/Invoke-DeleteSubKeyTree.ps1 new file mode 100644 index 0000000..1b03ce7 --- /dev/null +++ b/source/Private/Invoke-DeleteSubKeyTree.ps1 @@ -0,0 +1,69 @@ +<# +.SYNOPSIS +Deletes a registry subkey and all of its child subkeys from the specified parent key. + +.DESCRIPTION +The `Invoke-DeleteSubKeyTree` function deletes a specified subkey and all of its child subkeys from a provided registry key. It allows control over whether to throw an error if the subkey is missing. + +.PARAMETER ParentKey +The parent registry key from which the subkey tree will be deleted. This parameter cannot be null. + +.PARAMETER SubKeyName +The name of the subkey to delete, including all of its child subkeys. This parameter cannot be null or empty. + +.PARAMETER ThrowOnMissingSubKey +Specifies whether an exception should be thrown if the subkey does not exist. Defaults to `$true`. + +.EXAMPLE +$parentKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\MyApp', $true) +Invoke-DeleteSubKeyTree -ParentKey $parentKey -SubKeyName 'Settings' + +This command deletes the 'Settings' subkey and all its child subkeys under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp'. + +.EXAMPLE +$parentKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('SOFTWARE\MyApp', $true) +Invoke-DeleteSubKeyTree -ParentKey $parentKey -SubKeyName 'TempSettings' -ThrowOnMissingSubKey $false + +This command deletes the 'TempSettings' subkey and all its child subkeys under 'HKEY_CURRENT_USER\SOFTWARE\MyApp', and does not throw an exception if the subkey does not exist. + +.NOTES +This function uses the .NET `Microsoft.Win32.RegistryKey` class to interact with the Windows registry. Ensure you have appropriate permissions to perform registry modifications. + +#> + +Function Invoke-DeleteSubKeyTree +{ + + param( + [Microsoft.Win32.RegistryKey]$ParentKey, + [string]$SubKeyName, + [bool]$ThrowOnMissingSubKey = $true + ) + + if ($null -eq $ParentKey) + { + throw [System.ArgumentNullException]::new("ParentKey cannot be null.") + } + + if ([string]::IsNullOrEmpty($SubKeyName)) + { + throw [System.ArgumentNullException]::new("SubKeyName cannot be null or empty.") + } + + try + { + if ($ThrowOnMissingSubKey) + { + $ParentKey.DeleteSubKeyTree($SubKeyName) + } + else + { + $ParentKey.DeleteSubKeyTree($SubKeyName, $false) + } + } + catch + { + throw $_ + } + +} diff --git a/source/Private/Invoke-DeleteSubkey.ps1 b/source/Private/Invoke-DeleteSubkey.ps1 new file mode 100644 index 0000000..8930cce --- /dev/null +++ b/source/Private/Invoke-DeleteSubkey.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS + Deletes a specified subkey from the Windows registry. +.DESCRIPTION + The `Invoke-DeleteSubKey` function deletes a subkey from the Windows registry. + It provides flexibility to either pass a registry hive and path or an existing registry key object to delete the subkey. + It supports deleting subkeys on both local and remote computers. + + This function is used internally by the `Remove-RegistrySubKey` cmdlet to delete a subkey from the Windows registry. + It is not intended to be used directly by end users. + +.PARAMETER ParentKey + Specifies the parent registry key object from which to delete the subkey. + +.PARAMETER SubKeyName + Specifies the name of the subkey to delete. + +.PARAMETER ThrowOnMissingSubKey + + Indicates whether the function should throw an error if the subkey to delete does not exist. + If set to `$false`, no error will be thrown when attempting to delete a non-existent subkey. Defaults to `$true`. + +.INPUTS + Microsoft.Win32.RegistryKey + +.OUTPUTS + None + +.EXAMPLE + $parentKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\MyApp', $true) + Invoke-DeleteSubKey -ParentKey $parentKey -SubKeyName 'Settings' + + This command deletes the 'Settings' subkey under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' using the parent key object. + +.NOTES + This function uses the .NET `Microsoft.Win32.RegistryKey` class to interact with the Windows registry. + It is recommended to run this function with appropriate permissions, as registry edits may require elevated privileges. +#> +function Invoke-DeleteSubKey +{ + param( + [Microsoft.Win32.RegistryKey]$ParentKey, + [string]$SubKeyName, + [bool]$ThrowOnMissingSubKey = $true + ) + + if ($ParentKey -eq $null) + { + throw [System.ArgumentNullException]::new("ParentKey cannot be null.") + } + + if ([string]::IsNullOrEmpty($SubKeyName)) + { + throw [System.ArgumentNullException]::new("SubKeyName cannot be null or empty.") + } + + try + { + if ($ThrowOnMissingSubKey) + { + $ParentKey.DeleteSubKey($SubKeyName, $true) + } + else + { + $ParentKey.DeleteSubKey($SubKeyName, $false) + } + } + + catch + { + throw $_ + } +} diff --git a/source/Public/Backup-RegistryKey.ps1 b/source/Public/Backup-RegistryKey.ps1 index bfd88d0..bf20650 100644 --- a/source/Public/Backup-RegistryKey.ps1 +++ b/source/Public/Backup-RegistryKey.ps1 @@ -38,7 +38,7 @@ function Backup-RegistryKey [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $true)] [string]$RegistryPath, # Now dynamic, can back up any registry path - [string]$BackupDirectory = "C:\LHStuff\UserProfileTools\RegProfBackup" + [string]$BackupDirectory = $ENV:RegBackupDirectory ) # Determine if the operation is local or remote diff --git a/source/Public/Export-RegistryKey.ps1 b/source/Public/Export-RegistryKey.ps1 index f73c2eb..240f291 100644 --- a/source/Public/Export-RegistryKey.ps1 +++ b/source/Public/Export-RegistryKey.ps1 @@ -34,10 +34,6 @@ function Export-RegistryKey try { - - - - #$exportCommand = "reg export `"$RegistryPath`" `"$ExportPath`"" $result = Invoke-RegCommand -RegistryPath $RegistryPath -ExportPath $ExportPath #Invoke-Expression $exportCommand diff --git a/source/Public/Remove-RegistrySubKey.ps1 b/source/Public/Remove-RegistrySubKey.ps1 index e1e8e63..9ce3d47 100644 --- a/source/Public/Remove-RegistrySubKey.ps1 +++ b/source/Public/Remove-RegistrySubKey.ps1 @@ -1,69 +1,149 @@ <# .SYNOPSIS -Removes a subkey from a registry key. +Removes a specified subkey from the Windows registry. .DESCRIPTION -This function deletes a subkey from a specified parent registry key. It supports the -WhatIf and -Confirm parameters for safety. +The `Remove-RegistrySubKey` cmdlet removes a subkey from the Windows registry. +It provides flexibility to either pass a registry hive and path or an existing registry key object to delete the subkey. +It supports deleting subkeys on both local and remote computers. + +.PARAMETER RegistryHive +Specifies the registry hive where the key resides. This parameter is part of the 'ByHive' parameter set. +Accepted values are: +- ClassesRoot +- CurrentUser +- LocalMachine +- Users +- PerformanceData +- CurrentConfig +- DynData + +.PARAMETER RegistryPath +Specifies the path to the registry key that contains the subkey to delete. This parameter is part of the 'ByHive' parameter set. + +.PARAMETER ComputerName +Specifies the name of the computer on which to perform the registry operation. Defaults to the local machine if not specified. This parameter is part of the 'ByHive' parameter set. .PARAMETER ParentKey -The parent registry key object. +Specifies an existing registry key object from which to delete the subkey. This parameter is part of the 'ByKey' parameter set. .PARAMETER SubKeyName -The name of the subkey to be deleted. +Specifies the name of the subkey to delete. This parameter is part of the 'ByKey' parameter set. -.PARAMETER ComputerName -The name of the computer where the registry subkey is located. Defaults to the local computer. +.PARAMETER ThrowOnMissingSubKey +Indicates whether the cmdlet should throw an error if the subkey to delete does not exist. +If set to `$false`, no error will be thrown when attempting to delete a non-existent subkey. Defaults to `$true`. .EXAMPLE -$key = Open-RegistryKey -RegistryPath 'HKLM\Software' -Remove-RegistrySubKey -ParentKey $key -SubKeyName 'MyApp' +Remove-RegistrySubKey -RegistryHive LocalMachine -RegistryPath 'SOFTWARE\MyApp\Settings' -Deletes the subkey 'MyApp' under the registry key 'HKLM\Software' on the local computer. +This command deletes the 'Settings' subkey under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' on the local machine. .EXAMPLE -$key = Open-RegistryKey -RegistryPath 'HKLM\Software' -Remove-RegistrySubKey -ParentKey $key -SubKeyName 'MyApp' -WhatIf +$parentKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\MyApp', $true) +Remove-RegistrySubKey -ParentKey $parentKey -SubKeyName 'Settings' + +This command deletes the 'Settings' subkey under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' using the parent key object. -Shows what would happen if the subkey 'MyApp' were deleted, without actually performing the deletion. +.EXAMPLE +Remove-RegistrySubKey -RegistryHive LocalMachine -RegistryPath 'SOFTWARE\MyApp\Settings' -ComputerName 'RemotePC' -.OUTPUTS -System.Boolean +This command deletes the 'Settings' subkey under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' on a remote computer named 'RemotePC'. .NOTES +This function uses the .NET `Microsoft.Win32.RegistryKey` class to interact with the Windows registry. +It is recommended to run this cmdlet with appropriate permissions, as registry edits may require elevated privileges. #> function Remove-RegistrySubKey { - [outputType([system.Boolean])] - [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'ByHive')] param ( - [Parameter(Mandatory = $true)] - [Microsoft.Win32.RegistryKey]$ParentKey, # The parent registry key - [string]$SubKeyName, # The subkey to be deleted - [string]$ComputerName = $env:COMPUTERNAME # Default to local computer + # Parameter Set: ByHive + [Parameter(Mandatory, ParameterSetName = 'ByHive')] + [ValidateSet( + [Microsoft.Win32.RegistryHive]::ClassesRoot, + [Microsoft.Win32.RegistryHive]::CurrentUser, + [Microsoft.Win32.RegistryHive]::LocalMachine, + [Microsoft.Win32.RegistryHive]::Users, + [Microsoft.Win32.RegistryHive]::PerformanceData, + [Microsoft.Win32.RegistryHive]::CurrentConfig, + [Microsoft.Win32.RegistryHive]::DynData + ) + ] + [Microsoft.Win32.RegistryHive]$RegistryHive, + [Parameter(Mandatory, ParameterSetName = 'ByHive')] + [string]$RegistryPath, + [Parameter(ParameterSetName = 'ByHive')] + [string]$ComputerName = $env:COMPUTERNAME, + + + # Parameter Set: ByKey + [Parameter(Mandatory, ParameterSetName = 'ByKey')] + [Microsoft.Win32.RegistryKey]$ParentKey, + [Parameter(Mandatory, ParameterSetName = 'ByKey')] + [string]$SubKeyName, + [Parameter(ParameterSetName = 'ByHive')] + [Parameter(ParameterSetName = 'ByKey')] + [bool]$ThrowOnMissingSubKey = $true + ) - try + begin { - # Ensure ShouldProcess is used for safety with -WhatIf and -Confirm support - if ($PSCmdlet.ShouldProcess("Registry subkey '$SubKeyName' on $ComputerName", "Remove")) + if ($PSCmdlet.ParameterSetName -eq "ByHive") { - # Proceed with deletion - $ParentKey.DeleteSubKeyTree($SubKeyName) - Write-Verbose "Successfully removed registry subkey '$SubKeyName' on $ComputerName." - return $true + $params = @{ + RegistryPath = $RegistryPath + RegistryHive = $RegistryHive + ComputerName = $ComputerName + ParameterSetName = $PSCmdlet.ParameterSetName + } } else { - # ShouldProcess returned false, so nothing is done - Write-Verbose "Operation to remove registry subkey '$SubKeyName' on $ComputerName was skipped." - return $false + $params = @{ + ParentKey = $ParentKey + SubKeyName = $SubKeyName + ParameterSetName = $PSCmdlet.ParameterSetName + } + } + + $operationDetails = Get-RegistrySubKeyOperation @params + + $ParentKey = $operationDetails.ParentKey + $subKeyName = $operationDetails.SubKeyName + + if ([string]::IsNullOrEmpty($subKeyName)) + { + throw [System.ArgumentNullException]::new("SubKeyName cannot be null or empty.") } + } + + process + { + if ($PSCmdlet.ShouldProcess("$($ParentKey.Name)\$subKeyName", "Removing registry subkey using DeleteSubKey")) + { + # Use DeleteSubKey method + try + { + Invoke-DeleteSubKey -ParentKey $ParentKey -SubKeyName $subKeyName -ThrowOnMissingSubKey $ThrowOnMissingSubKey + } + catch + { + throw $_ + } + } } - catch + + end { - Write-Error "Failed to remove the registry subkey '$SubKeyName' on $ComputerName. Error: $_" - return $false + # If we opened the ParentKey internally, dispose of it + if ($PSCmdlet.ParameterSetName -eq 'ByHive' -and $null -ne $ParentKey) + { + $ParentKey.Dispose() + } } + } diff --git a/source/Public/Remove-RegistrySubKeyTree.ps1 b/source/Public/Remove-RegistrySubKeyTree.ps1 new file mode 100644 index 0000000..f08ecfb --- /dev/null +++ b/source/Public/Remove-RegistrySubKeyTree.ps1 @@ -0,0 +1,132 @@ +<# +.SYNOPSIS +Recursively removes a specified subkey and all of its subkeys from the Windows registry. + +.DESCRIPTION +The `Remove-RegistrySubKeyTree` cmdlet deletes a registry subkey and all of its child subkeys (if any) from the Windows registry. +This cmdlet provides flexibility to either specify a registry hive and path, or pass an existing registry key object to delete the subkey tree. +It supports deleting registry subkeys on both local and remote computers. + +.PARAMETER RegistryHive +Specifies the registry hive where the key resides. This parameter is part of the 'ByHive' parameter set. +Accepted values are: +- ClassesRoot +- CurrentUser +- LocalMachine +- Users +- PerformanceData +- CurrentConfig +- DynData + +.PARAMETER RegistryPath +Specifies the path to the registry key that contains the subkey to delete. This parameter is part of the 'ByHive' parameter set. + +.PARAMETER ComputerName +Specifies the name of the computer on which to perform the registry operation. Defaults to the local machine if not specified. This parameter is part of the 'ByHive' parameter set. + +.PARAMETER ParentKey +Specifies an existing registry key object from which to delete the subkey tree. This parameter is part of the 'ByKey' parameter set. + +.PARAMETER SubKeyName +Specifies the name of the subkey to delete, including all of its child subkeys. This parameter is part of the 'ByKey' parameter set. + +.EXAMPLE +Remove-RegistrySubKeyTree -RegistryHive LocalMachine -RegistryPath 'SOFTWARE\MyApp\Settings' + +This command deletes the 'Settings' subkey and all its child subkeys under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' on the local machine. + +.EXAMPLE +$parentKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\MyApp', $true) +Remove-RegistrySubKeyTree -ParentKey $parentKey -SubKeyName 'Settings' + +This command deletes the 'Settings' subkey and all its child subkeys under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' using the parent key object. + +.EXAMPLE +Remove-RegistrySubKeyTree -RegistryHive LocalMachine -RegistryPath 'SOFTWARE\MyApp\Settings' -ComputerName 'RemotePC' + +This command deletes the 'Settings' subkey and all its child subkeys under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' on a remote computer named 'RemotePC'. + +.NOTES +This function uses the .NET `Microsoft.Win32.RegistryKey` class to interact with the Windows registry. +Registry operations can be sensitive, and it is recommended to run this cmdlet with appropriate permissions (e.g., as an Administrator) to avoid access issues. + +#> +function Remove-RegistrySubKeyTree +{ + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High", DefaultParameterSetName = 'ByHive')] + param ( + # Parameter Set: ByHive + [Parameter(Mandatory, ParameterSetName = 'ByHive')] + [ValidateSet( + [Microsoft.Win32.RegistryHive]::ClassesRoot, + [Microsoft.Win32.RegistryHive]::CurrentUser, + [Microsoft.Win32.RegistryHive]::LocalMachine, + [Microsoft.Win32.RegistryHive]::Users, + [Microsoft.Win32.RegistryHive]::PerformanceData, + [Microsoft.Win32.RegistryHive]::CurrentConfig, + [Microsoft.Win32.RegistryHive]::DynData + ) + ] + [Microsoft.Win32.RegistryHive]$RegistryHive, + [Parameter(Mandatory, ParameterSetName = 'ByHive')] + [string]$RegistryPath, + [Parameter(ParameterSetName = 'ByHive')] + [string]$ComputerName = $env:COMPUTERNAME, + + # Parameter Set: ByKey + [Parameter(Mandatory, ParameterSetName = 'ByKey')] + [Microsoft.Win32.RegistryKey]$ParentKey, + [Parameter(Mandatory, ParameterSetName = 'ByKey')] + [string]$SubKeyName + ) + + begin + { + if ($PSCmdlet.ParameterSetName -eq "ByHive") + { + $params = @{ + RegistryPath = $RegistryPath + RegistryHive = $RegistryHive + ComputerName = $ComputerName + ParameterSetName = $PSCmdlet.ParameterSetName + } + } + else + { + $params = @{ + ParentKey = $ParentKey + SubKeyName = $SubKeyName + ParameterSetName = $PSCmdlet.ParameterSetName + } + } + + $operationDetails = Get-RegistrySubKeyOperation @params + + $ParentKey = $operationDetails.ParentKey + $subKeyName = $operationDetails.SubKeyName + + if ([string]::IsNullOrEmpty($subKeyName)) + { + throw [System.ArgumentNullException]::new("SubKeyName cannot be null or empty.") + } + } + + process + { + if ($PSCmdlet.ShouldProcess("$($ParentKey.Name)\$subKeyName", "Removing registry subkey tree")) + { + # Call Invoke-DeleteSubKeyTree to handle the deletion + Invoke-DeleteSubKeyTree -ParentKey $ParentKey -SubKeyName $subKeyName -ThrowOnMissingSubKey $true + Write-Verbose "SubKey '$subKeyName' and its child subkeys deleted using DeleteSubKeyTree." + } + } + + end + { + # If we opened the ParentKey internally, dispose of it + if ($PSCmdlet.ParameterSetName -eq 'ByHive' -and $null -ne $ParentKey) + { + $ParentKey.Dispose() + } + } +} diff --git a/tests/Intergration/Invoke-RegCommand.tests.ps1 b/tests/Intergration/Invoke-RegCommand.tests.ps1 new file mode 100644 index 0000000..61f2a57 --- /dev/null +++ b/tests/Intergration/Invoke-RegCommand.tests.ps1 @@ -0,0 +1,115 @@ +BeforeAll { + $script:dscModuleName = "WinRegOps" + + # Import the module being tested + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + # Clean up environment variables + Remove-Item -Path Env:Registry_Path -ErrorAction SilentlyContinue + Remove-Item -Path Env:Export_Path -ErrorAction SilentlyContinue + +} + +Describe 'Invoke-RegCommand Integration Tests using TestRegistry' -Tag 'Integration' { + + Context 'Exporting a registry key using reg.exe' { + BeforeAll { + # Set up a test registry key in TestRegistry + New-Item -Path "TestRegistry:\TestKey" -Force | Out-Null + New-ItemProperty -Path "TestRegistry:\TestKey" -Name "TestValue" -Value "123" | Out-Null + } + + It 'Should export the TestRegistry key to the specified file' { + # Act: Call the Invoke-RegCommand function + InModuleScope -ScriptBlock { + # Set the environment variables to point to TestRegistry and TestDrive for testing + $env:Registry_Path = "$(Get-PSDrive TestRegistry | Select-Object -ExpandProperty Root)\TestKey" + $env:Export_Path = "$(Get-PSDrive -Name TestDrive |Select-Object -ExpandProperty Root)\TestKey.Reg" + + + + $result = Invoke-RegCommand + + Write-Host $result + + } + + # Assert: Verify the export file was created in TestDrive + Test-Path -Path $env:Export_Path | Should -Be $true + + # Optional: Verify that the contents of the exported file contain the expected registry data + $exportedContent = (Get-Content -Path $env:Export_Path -raw).Trim() + + $ExpectedContent = @" +Windows Registry Editor Version 5.00 + +[$env:registry_Path] +"TestValue"="123" +"@ + + + $exportedContent | Should -Be $ExpectedContent + } + + It 'Should throw an error if the registry path or export path is empty' { + # Arrange: Clear the environment variables + $env:Registry_Path = $null + $env:Export_Path = $null + + InModuleScope -ScriptBlock { + # Act & Assert: Expect an error due to missing parameters + { Invoke-RegCommand } | Should -Throw "Path or OutputFile is null or empty." + } + } + } + + Context 'Backup-RegistryKey - Local Backup' { + It 'Should successfully back up the local registry key' { + + New-Item -Path "TestRegistry:\TestKey" -Force | Out-Null + New-ItemProperty -Path "TestRegistry:\TestKey" -Name "TestValue" -Value "123" | Out-Null + + $backupDirectory = "$(Get-PSDrive -Name TestDrive |Select-Object -ExpandProperty Root)\RegProfBackup" + $registryPath = "$(Get-PSDrive TestRegistry | Select-Object -ExpandProperty Root)\TestKey" + $localComputerName = $env:COMPUTERNAME + + + + # Act + $result = Backup-RegistryKey -RegistryPath $registryPath -BackupDirectory $backupDirectory + + $exportedContent = (Get-Content -Path $result.BackupPath -raw).Trim() + + $ExpectedContent = @" +Windows Registry Editor Version 5.00 + +[$registryPath] +"TestValue"="123" +"@ + + + $exportedContent | Should -Be $ExpectedContent + # Assert + $result | Should -Not -BeNullOrEmpty + $result.Success | Should -Be $true + $result.BackupPath | Should -Not -BeNullOrEmpty + $result.BackUpPath | Should -BeLike "$backupDirectory*.reg" + Test-Path $result.BackupPath | Should -Be $true + $result.Message | Should -Be "Registry key backed up successfully." + $result.ComputerName | Should -Be $localComputerName + } + } +} diff --git a/tests/Intergration/Remove-RegistrySubkey.tests.ps1 b/tests/Intergration/Remove-RegistrySubkey.tests.ps1 new file mode 100644 index 0000000..0effd61 --- /dev/null +++ b/tests/Intergration/Remove-RegistrySubkey.tests.ps1 @@ -0,0 +1,194 @@ +BeforeAll { + $script:dscModuleName = "WinRegOps" + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + + +Describe 'Remove-RegistrySubKey Integration Tests using TestRegistry' -Tag 'Integration' { + + AfterEach { + Remove-Item -Path TestRegistry:\ -Recurse -ErrorAction SilentlyContinue + } + + It 'Should remove the subkey successfully when given valid inputs' { + + $path = New-Item -Path TestRegistry:\ -Name TestLocation + + $registryPath = ($path -split "HKEY_CURRENT_USER\\") + + $testRegistryPath = (New-Item -Path "TestRegistry:\TestLocation" -Name TestSubKey).pspath + + # Arrange + $registryHive = [Microsoft.Win32.RegistryHive]::CurrentUser + + $regPath = $registryPath[1] + "\TestSubKey" + + # Act + Remove-RegistrySubKey -RegistryHive $registryHive -RegistryPath $RegPath -Confirm:$false + + # Assert + Test-Path "TestRegistry:\TestLocation\TestSubKey" | Should -BeFalse + } + + It 'Should throw an error if the subkey does not exist' { + # Arrange + $registryHive = [Microsoft.Win32.RegistryHive]::CurrentUser + $invalidSubKey = "NonExistentSubKey" + + # Act & Assert + { Remove-RegistrySubKey -RegistryHive $registryHive -RegistryPath "Software\Pester\$invalidSubKey" -Confirm:$false } | Should -Throw + } + + It 'Should support the ByKey parameter set' { + + Remove-Item -Path TestRegistry:\TestLocation -Recurse -ErrorAction SilentlyContinue + $path = New-Item -Path TestRegistry:\ -Name TestLocation + $registryPath = ($path -split "HKEY_CURRENT_USER\\") + $testRegistryPath = (New-Item -Path "TestRegistry:\TestLocation" -Name TestSubKey).pspath + + # Arrange + $parentKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($registryPath[1], $true) + $subKeyName = "TestSubKey" + + # Act + Remove-RegistrySubKey -ParentKey $parentKey -SubKeyName $subKeyName -Confirm:$false + + # Assert + Test-Path "TestRegistry:\TestLocation\TestSubKey" | Should -BeFalse + + } + + It 'Should not remove subkey if ShouldProcess returns false' { + # Arrange + $registryHive = [Microsoft.Win32.RegistryHive]::CurrentUser + + Remove-Item -Path TestRegistry:\TestLocation -Recurse -ErrorAction SilentlyContinue + $path = New-Item -Path TestRegistry:\ -Name TestLocation + $testRegistryPath = (New-Item -Path "TestRegistry:\TestLocation" -Name TestSubKey).pspath + + $registryPath = ($path -split "HKEY_CURRENT_USER\\") + + + Mock Invoke-DeleteSubKey -ParameterFilter { + $PSCmdlet.ShouldProcess($registryPath[1], 'Removing registry subkey') -eq $false + } -ModuleName $script:dscModuleName + + # Act + Remove-RegistrySubKey -RegistryHive $registryHive -RegistryPath $registryPath[1] -Confirm:$false -WhatIf + + # Assert + Test-Path "TestRegistry:\TestLocation\TestSubKey" | Should -BeTrue + + Assert-MockCalled -CommandName Invoke-DeleteSubKey -Exactly 0 -Scope It -ModuleName $script:dscModuleName + + } + + It 'Should handle non-writable registry keys gracefully' { + # Arrange + $parentKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey("Software\Pester\" + (Split-Path (Test-Path TestRegistry:\) -Leaf) + "\TestLocation", $false) + $subKeyName = "TestSubKey" + + # Act & Assert + { Remove-RegistrySubKey -ParentKey $parentKey -SubKeyName $subKeyName -Confirm:$false } | Should -Throw + } + +} + +Describe 'Remove-RegistrySubKeyTree Integration Tests using TestRegistry' -Tag 'Integration' { + + AfterEach { + Remove-Item -Path TestRegistry:\ -Recurse -ErrorAction SilentlyContinue + } + + It 'Should remove the subkey Tree successfully when given valid inputs' { + + Remove-Item -Path TestRegistry:\TestLocation -Recurse -ErrorAction SilentlyContinue + $path = New-Item -Path TestRegistry:\ -Name TestLocation + + $registryPath = ($path -split "HKEY_CURRENT_USER\\") + + $testRegistryPath = (New-Item -Path "TestRegistry:\TestLocation" -Name TestSubKey).pspath + + # Arrange + $registryHive = [Microsoft.Win32.RegistryHive]::CurrentUser + + $regPath = $registryPath[1] + + # Act + Remove-RegistrySubKeyTree -RegistryHive $registryHive -RegistryPath $RegPath -Confirm:$false + + # Assert + Test-Path "TestRegistry:\TestLocation\TestSubKey" | Should -BeFalse + + Test-Path "TestRegistry:\TestLocation" | Should -BeFalse + } + + It 'Should throw an error if the subkey does not exist' { + # Arrange + $registryHive = [Microsoft.Win32.RegistryHive]::CurrentUser + $invalidSubKey = "NonExistentSubKey" + + # Act & Assert + { Remove-RegistrySubKeyTree -RegistryHive $registryHive -RegistryPath "Software\Pester\$invalidSubKey" -Confirm:$false } | Should -Throw + } + + It 'Should support the ByKey parameter set' { + + Remove-Item -Path TestRegistry:\TestLocation -Recurse -ErrorAction SilentlyContinue + $path = New-Item -Path TestRegistry:\ -Name TestLocation + $registryPath = ($path -split "HKEY_CURRENT_USER\\") + $testRegistryPath = (New-Item -Path "TestRegistry:\TestLocation" -Name TestSubKey).pspath + $testRegistryPath2 = (New-Item -Path "TestRegistry:\TestLocation\TestSubkey" -Name TestSubKey2).pspath + + # Arrange + $parentKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($registryPath[1], $true) + $subKeyName = "TestSubKey" + + # Act + Remove-RegistrySubKeyTree -ParentKey $parentKey -SubKeyName $subKeyName -Confirm:$false + + # Assert + Test-Path "TestRegistry:\TestLocation\TestSubKey\TestSubKey2" | Should -BeFalse + Test-Path "TestRegistry:\TestLocation\TestSubKey" | Should -BeFalse + + } + + It 'Should not remove subkey if ShouldProcess returns false' { + # Arrange + $registryHive = [Microsoft.Win32.RegistryHive]::CurrentUser + + Remove-Item -Path TestRegistry:\TestLocation -Recurse -ErrorAction SilentlyContinue + $path = New-Item -Path TestRegistry:\ -Name TestLocation + $testRegistryPath = (New-Item -Path "TestRegistry:\TestLocation" -Name TestSubKey).pspath + + $registryPath = ($path -split "HKEY_CURRENT_USER\\") + + + Mock Invoke-DeleteSubKeyTree -ParameterFilter { + $PSCmdlet.ShouldProcess($registryPath[1], 'Removing registry subkey') -eq $false + } -ModuleName $script:dscModuleName + + # Act + Remove-RegistrySubKeyTree -RegistryHive $registryHive -RegistryPath $registryPath[1] -Confirm:$false -WhatIf + + # Assert + Test-Path "TestRegistry:\TestLocation\TestSubKey" | Should -BeTrue + + Assert-MockCalled -CommandName Invoke-DeleteSubKeyTree -Exactly 0 -Scope It -ModuleName $script:dscModuleName + + } +} diff --git a/tests/Unit/Private/Get-RegistrySubKeyOperation.tests.ps1 b/tests/Unit/Private/Get-RegistrySubKeyOperation.tests.ps1 new file mode 100644 index 0000000..2f2206c --- /dev/null +++ b/tests/Unit/Private/Get-RegistrySubKeyOperation.tests.ps1 @@ -0,0 +1,128 @@ +BeforeAll { + $script:dscModuleName = "WinRegOps" + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-RegistrySubKeyOperation' -Tag 'Private' { + + Context 'ByHive parameter set' { + BeforeEach { + # Mock the Open-RegistryKey function to prevent real registry access + + } + + It 'Should open registry key and return ParentKey and SubKeyName when ByHive is used' { + + InModuleScope -ScriptBlock { + + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey) return $true } + } -Properties @{ + Name = "HKEY_LOCAL_MACHINE\SOFTWARE\MockedParentKey" + SubKeyCount = 5 + ValueCount = 10 + } + + # Mock the Open-RegistryKey function to return this mock object + Mock -CommandName 'Open-RegistryKey' -MockWith { + return $mockParentKey + } + + $localMachine = [Microsoft.Win32.RegistryHive]::LocalMachine + + $result = Get-RegistrySubKeyOperation -RegistryPath 'SOFTWARE\MyApp' -RegistryHive $localMachine -ComputerName 'RemotePC' -ParameterSetName 'ByHive' + + # Assert that Open-RegistryKey was called with expected parameters + Assert-MockCalled 'Open-RegistryKey' -Exactly -Times 1 -ParameterFilter { + $RegistryPath -eq 'SOFTWARE' -and + $RegistryHive -eq 'LocalMachine' -and + $ComputerName -eq 'RemotePC' + } + + # Verify the structure of the returned hashtable + $result | Should -BeOfType 'System.Collections.Hashtable' + $result.ParentKey | Should -Be $mockParentKey + $result.ParentKey.Name | Should -Be 'HKEY_LOCAL_MACHINE\SOFTWARE\MockedParentKey' + $result.SubKeyName | Should -Be 'MyApp' + } + } + + It 'Should throw an error if Open-RegistryKey fails' { + # Simulate failure of Open-RegistryKey + + InModuleScope -ScriptBlock { + Mock -CommandName 'Open-RegistryKey' -MockWith { throw } + + $message = 'Failed to open registry key: LocalMachine\SOFTWARE. ScriptHalted' + + $localMachine = [Microsoft.Win32.RegistryHive]::LocalMachine + { Get-RegistrySubKeyOperation -RegistryPath 'SOFTWARE\MyApp' -RegistryHive $localMachine -ComputerName 'RemotePC' -ParameterSetName 'ByHive' } | Should -Throw $message + + } + } + + It 'Should return ParentKey and SubKeyName when ByKey is used' { + + InModuleScope -ScriptBlock { + + # Prepare a mock parent key + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey) return $true } + } -Properties @{ + Name = "HKEY_LOCAL_MACHINE\SOFTWARE\MockedParentKey" + SubKeyCount = 5 + ValueCount = 10 + } + + $result = Get-RegistrySubKeyOperation -ParentKey $mockParentKey -SubKeyName 'Settings' -ParameterSetName 'ByKey' + + # Verify the structure of the returned hashtable + $result | Should -BeOfType 'System.Collections.Hashtable' + $result.ParentKey | Should -Be $mockParentKey + $result.SubKeyName | Should -Be 'Settings' + } + } + + It 'Should throw an error if ParentKey is null' { + InModuleScope -ScriptBlock { + + $message = @" +Value cannot be null. +Parameter name: ParentKey cannot be null. +"@ + + { Get-RegistrySubKeyOperation -ParentKey $null -SubKeyName 'Settings' -ParameterSetName 'ByKey' } | Should -Throw $message + + } + } + } + + Context 'Invalid parameter set' { + It 'Should throw an error for invalid parameter set' { + + InModuleScope -ScriptBlock { + + $message = @" +Invalid parameter set. +"@ + + { Get-RegistrySubKeyOperation -RegistryPath 'SOFTWARE\MyApp' -RegistryHive 'LocalMachine' -ParameterSetName 'InvalidSet' } | Should -Throw $message + + } + } + } +} diff --git a/tests/Unit/Private/Invoke-DeleteSubKeyTree.tests.ps1 b/tests/Unit/Private/Invoke-DeleteSubKeyTree.tests.ps1 new file mode 100644 index 0000000..7019455 --- /dev/null +++ b/tests/Unit/Private/Invoke-DeleteSubKeyTree.tests.ps1 @@ -0,0 +1,22 @@ +BeforeAll { + $script:dscModuleName = "WinRegOps" + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Invoke-DeleteSubKeyTree.tests.ps1 Tests' -Tag 'Private' { + +} \ No newline at end of file diff --git a/tests/Unit/Private/Invoke-DeleteSubkey.tests.ps1 b/tests/Unit/Private/Invoke-DeleteSubkey.tests.ps1 new file mode 100644 index 0000000..856ab12 --- /dev/null +++ b/tests/Unit/Private/Invoke-DeleteSubkey.tests.ps1 @@ -0,0 +1,128 @@ +BeforeAll { + $script:dscModuleName = "WinRegOps" + + # Import the module being tested + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Invoke-DeleteSubKey Unit Tests' -Tag 'private' { + Context 'Valid Input Tests' { + It 'Should delete the subkey when provided with valid ParentKey and SubKeyName' { + # Act: Call the function with valid ParentKey and SubKeyName + InModuleScope -ScriptBlock { + + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey, $throwOnMissing) + + return @{ + SubKey = $subKey + ThrowOnMissing = $throwOnMissing + } + } + } + + $return = Invoke-DeleteSubKey -ParentKey $mockParentKey -SubKeyName 'TestSubKey' + $return.subkey | Should -Be 'TestSubKey' + $return.ThrowOnMissing | Should -Be $true + } + } + + It 'Should not throw an error if the subkey does not exist and ThrowOnMissingSubKey is $false' { + # Act: Call the function with ThrowOnMissingSubKey set to $false + InModuleScope -ScriptBlock { + + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey, $throwOnMissing) + + return @{ + SubKey = $subKey + ThrowOnMissing = $throwOnMissing + } + } + } + + $return = Invoke-DeleteSubKey -ParentKey $mockParentKey -SubKeyName 'NonExistentSubKey' -ThrowOnMissingSubKey $false + + $return.subkey | Should -Be 'NonExistentSubKey' + $return.ThrowOnMissing | Should -Be $false + + + } + } + } + + Context 'Invalid Input Tests' { + It 'Should throw an ArgumentNullException when ParentKey is $null' { + # Act & Assert: Expect ArgumentNullException when ParentKey is null + + InModuleScope -ScriptBlock { + + $message = @" +Value cannot be null. +Parameter name: ParentKey cannot be null. +"@ + { Invoke-DeleteSubKey -ParentKey $null -SubKeyName 'TestSubKey' } | Should -Throw $message + } + + } + + It 'Should throw an ArgumentNullException when SubKeyName is $null or empty' { + # Act & Assert: Expect ArgumentNullException when SubKeyName is null + + InModuleScope -ScriptBlock { + + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey) return $true } + } -Properties @{ + Name = "HKEY_LOCAL_MACHINE\SOFTWARE\MockedParentKey" + SubKeyCount = 5 + ValueCount = 10 + DeleteSubKey = { param($subKey, $throwOnMissing) + + return @{ + SubKey = $subKey + ThrowOnMissing = $throwOnMissing + } + } + } + $message = @" +Value cannot be null. +Parameter name: SubKeyName cannot be null or Empty. +"@ + + { Invoke-DeleteSubKey -ParentKey $mockParentKey -SubKeyName $null } | Should -Throw $message + { Invoke-DeleteSubKey -ParentKey $mockParentKey -SubKeyName '' } | Should -Throw $message + + } + } + } + + Context 'Error Handling Tests' { + It 'Should throw an error if DeleteSubKey fails' { + InModuleScope -ScriptBlock { + + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { throw "Test error" } + } + + $message = 'Exception calling "DeleteSubKey" with "2" argument(s): "Test error"' + + { Invoke-DeleteSubKey -ParentKey $mockParentKey -SubKeyName 'TestSubKey' } | Should -Throw $message + + } + } + } +} diff --git a/tests/Unit/Private/Invoke-RegCommand.tests.ps1 b/tests/Unit/Private/Invoke-RegCommand.tests.ps1 index e73ae2d..17064ce 100644 --- a/tests/Unit/Private/Invoke-RegCommand.tests.ps1 +++ b/tests/Unit/Private/Invoke-RegCommand.tests.ps1 @@ -15,8 +15,95 @@ AfterAll { # Unload the module being tested so that it doesn't impact any other tests. Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path Env:Registry_Path -ErrorAction SilentlyContinue + Remove-Item -Path Env:Export_Path -ErrorAction SilentlyContinue } -Describe 'Invoke-RegCommand.tests.ps1 Tests' -Tag 'Private', 'Wrapper' { +Describe 'Invoke-RegCommand Function Tests' -Tag 'Private' { + + + + Context 'When exporting registry keys' { + + It 'Should export registry key when valid paths are provided' { + + InModuleScope $script:dscModuleName { + + # Mock Invoke-Command to simulate reg.exe execution + Mock -CommandName Invoke-Command -MockWith { + return $parameters + } + + # Act + $result = Invoke-RegCommand -RegistryPath 'HKCU\Software\MyKey' -ExportPath 'C:\Export\mykey.reg' + + # Assert + Assert-MockCalled 'Invoke-Command' -Exactly -Times 1 -Scope IT + + $result.Operation | Should -Be 'EXPORT' + $result.Path | Should -Be 'HKCU\Software\MyKey' + $result.OutputFile | Should -Be 'C:\Export\mykey.reg' + + } + } + + It 'Should use environment variables for paths if parameters are not provided' { + + InModuleScope $script:dscModuleName { + + Mock -CommandName Invoke-Command -MockWith { + return $parameters + } + + # Set environment variables for the test + $ENV:Registry_Path = 'HKLM\Software\MyApp' + $ENV:Export_Path = 'D:\Backup\myapp.reg' + + # Act + $result = Invoke-RegCommand + + # Assert + Assert-MockCalled 'Invoke-Command' -Exactly -Times 1 -Scope It + + $result.Operation | Should -Be 'EXPORT' + $result.Path | Should -Be 'HKLM\Software\MyApp' + $result.OutputFile | Should -Be 'D:\Backup\myapp.reg' + } + } + + It 'Should throw an error if RegistryPath is null or empty' { + + InModuleScope $script:dscModuleName { + # Test with missing RegistryPath + $ENV:Registry_Path = $null + { Invoke-RegCommand -ExportPath 'C:\Export\mykey.reg' } | Should -Throw "Path or OutputFile is null or empty." + } + } + + It 'Should throw an error if ExportPath is null or empty' { + InModuleScope $script:dscModuleName { + # Test with missing ExportPath + $ENV:Export_Path = $null + { Invoke-RegCommand -RegistryPath 'HKCU\Software\MyKey' } | Should -Throw "Path or OutputFile is null or empty." + + } + } + } + + Context 'Error handling' { + It 'Should throw an error if Invoke-Command fails' { + + InModuleScope $script:dscModuleName { + # Simulate Invoke-Command failure + Mock -CommandName 'Invoke-Command' -MockWith { + throw "reg.exe failed." + } + + { Invoke-RegCommand -RegistryPath 'HKCU\Software\MyKey' -ExportPath 'C:\Export\mykey.reg' } | Should -Throw "reg.exe failed." + + } + } + } } diff --git a/tests/Unit/Public/Backup-RegistryKey.tests.ps1 b/tests/Unit/Public/Backup-RegistryKey.tests.ps1 index a190d8e..c23d549 100644 --- a/tests/Unit/Public/Backup-RegistryKey.tests.ps1 +++ b/tests/Unit/Public/Backup-RegistryKey.tests.ps1 @@ -10,6 +10,9 @@ BeforeAll { $helperPath = "$PSScriptRoot/../../Helpers/Log-TestDetails.ps1" . $helperPath + + $ENV:RegBackupDirectory = "TestDrive:\Backups" + } AfterAll { @@ -18,6 +21,8 @@ AfterAll { $PSDefaultParameterValues.Remove('Should:ModuleName') Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + remove-item -path Env:RegBackupDirectory -ErrorAction SilentlyContinue } Describe 'Backup-RegistryKey function tests' -Tag 'Public' { diff --git a/tests/Unit/Public/Remove-RegistrySubKey.tests.ps1 b/tests/Unit/Public/Remove-RegistrySubKey.tests.ps1 index 52ef0ac..684f747 100644 --- a/tests/Unit/Public/Remove-RegistrySubKey.tests.ps1 +++ b/tests/Unit/Public/Remove-RegistrySubKey.tests.ps1 @@ -19,57 +19,108 @@ AfterAll { Describe 'Remove-RegistrySubKey function tests' -Tag 'Public' { - # Test for successfully removing a subkey - It 'should remove an existing subkey' { - # Mock the parent registry key object and its DeleteSubKeyTree method - $mockParentKey = New-MockObject -Type 'Microsoft.Win32.RegistryKey' -Methods @{ - DeleteSubKeyTree = { param($SubKeyName) return $null } + Context 'ByHive parameter set tests' { + It 'Should throw an error if mandatory parameters are missing' { + { Remove-RegistrySubKey -RegistryHive [Microsoft.Win32.RegistryHive]::LocalMachine -confirm:$false } | Should -Throw } - # Call the function to remove an existing subkey - { Remove-RegistrySubKey -ParentKey $mockParentKey -SubKeyName 'ExistingSubKey' -confirm:$false } | Should -Be $true + It 'Should validate RegistryHive and RegistryPath parameters' { + # Arrange + # Mocking the ParentKey object and its methods + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey) return $true } + } - } + # Mock Open-RegistryKey to return the mocked ParentKey + Mock -CommandName Open-RegistryKey { return $mockParentKey } + + Mock -CommandName Invoke-DeleteSubKey { return $true } -ModuleName $Script:dscModuleName + + $hive = [Microsoft.Win32.RegistryHive]::LocalMachine + # Act + Remove-RegistrySubKey -RegistryHive $hive -RegistryPath "SOFTWARE\MyApp" -Confirm:$false + # Assert + # Ensure that Open-RegistryKey was called + Assert-MockCalled -CommandName Open-RegistryKey -Exactly 1 -Scope It - # Test for non-existent subkey - It 'should return $false and write an error if the subkey does not exist' { - # Mock the parent registry key object and its DeleteSubKeyTree method to throw an error - $mockParentKey = New-MockObject -Type 'Microsoft.Win32.RegistryKey' -Methods @{ - DeleteSubKeyTree = { param($SubKeyName) throw "Subkey does not exist" } + # Ensure that Invoke-DeleteSubKey was called + Assert-MockCalled -CommandName Invoke-DeleteSubKey -Exactly 1 -Scope It -ParameterFilter { $SubKeyName -eq "MyApp" } -ModuleName $Script:dscModuleName } - # Call the function to remove a non-existent subkey with -Confirm:$false - Remove-RegistrySubKey -ParentKey $mockParentKey -SubKeyName 'NonExistentSubKey' -Confirm:$false -ErrorAction Continue + It 'Should process the registry key deletion' { + # Arrange + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey) return $true } + } - # Validate that $false is returned - #$result | Should -Be $false - } + Mock Open-RegistryKey { return $mockParentKey } + Mock Invoke-DeleteSubKey { } -ModuleName $Script:dscModuleName + + $hive = [Microsoft.Win32.RegistryHive]::LocalMachine + # Act + Remove-RegistrySubKey -RegistryHive $hive -RegistryPath "SOFTWARE\MyApp" -Confirm:$false - # Test for handling errors when deleting the subkey - It 'should handle exceptions and return $false when an error occurs' { - # Mock the parent registry key object and its DeleteSubKeyTree method to throw an exception - $mockParentKey = New-MockObject -Type 'Microsoft.Win32.RegistryKey' -Methods @{ - DeleteSubKeyTree = { throw [Exception]::new("Unexpected error") } + # Assert + Assert-MockCalled -CommandName Open-RegistryKey -Exactly 1 -Scope It + Assert-MockCalled -CommandName Invoke-DeleteSubKey -Exactly 1 -Scope It -ModuleName $Script:dscModuleName } - # Call the function to remove a subkey that causes an exception with -Confirm:$false - $result = Remove-RegistrySubKey -ParentKey $mockParentKey -SubKeyName 'FaultySubKey' -Confirm:$false -ErrorAction Continue - # Validate that $false is returned - $result | Should -Be $false + It 'Should respect ShouldProcess for safety confirmation' { + # Arrange + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey) return $true } + } + + # Mock Open-RegistryKey to return the mocked ParentKey + Mock Open-RegistryKey { return $mockParentKey } + Mock Invoke-DeleteSubKey { } + + # Mock ShouldProcess by using a ParameterFilter to simulate confirmation behavior + Mock Remove-RegistrySubKey -ParameterFilter { + $PSCmdlet.ShouldProcess('SOFTWARE\MyApp', 'Removing registry subkey') -eq $true + } + + $hive = [Microsoft.Win32.RegistryHive]::LocalMachine + + # Act + Remove-RegistrySubKey -RegistryHive $hive -RegistryPath "SOFTWARE\MyApp" -Confirm:$false + + # Assert + Assert-MockCalled -CommandName Open-RegistryKey -Exactly 1 -Scope It + Assert-MockCalled -CommandName Invoke-DeleteSubKey -Exactly 1 -Scope It + } + } + + Context 'ByKey parameter set tests' { + It 'Should throw an error if ParentKey is null' { + { Remove-RegistrySubKey -ParentKey $null -SubKeyName "TestSubKey" -confirm:$false } | Should -Throw -ErrorId 'ParameterArgumentValidationErrorNullNotAllowed,Remove-RegistrySubKey' + } + + It 'Should remove a subkey when ParentKey is valid' { + # Arrange + # Arrange + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKey = { param($subKey) return $true } + } + Mock Invoke-DeleteSubKey {} -ModuleName $Script:dscModuleName + # Act + Remove-RegistrySubKey -ParentKey $mockParentKey -SubKeyName "Settings" -Confirm:$false + + # Assert + Assert-MockCalled -CommandName Invoke-DeleteSubKey -Exactly 1 -Scope It -ModuleName $Script:dscModuleName + } } - # Test for confirming the operation with ShouldProcess - It 'should not remove the subkey if ShouldProcess returns $false' { - # Mock the parent registry key object and its DeleteSubKeyTree method - $mockParentKey = New-MockObject -Type 'Microsoft.Win32.RegistryKey' -Methods @{ - DeleteSubKeyTree = { param($SubKeyName) return $null } + Context 'Error handling tests' { + It 'Should throw an exception for invalid subkey name' { + { Remove-RegistrySubKey -RegistryHive [Microsoft.Win32.RegistryHive]::LocalMachine -RegistryPath "" } | Should -Throw -ErrorId 'ParameterArgumentTransformationError,Remove-RegistrySubKey' } - # Call the function with -WhatIf to simulate ShouldProcess returning false - $result = Remove-RegistrySubKey -ParentKey $mockParentKey -SubKeyName 'ExistingSubKey' -WhatIf -Confirm:$false -ErrorAction Continue + It 'Should throw an exception if unable to open the registry key' { + Mock Open-RegistryKey { throw "Failed to open registry key" } - # Validate that the subkey was not removed (returns $null) - $result | Should -Be $false + { Remove-RegistrySubKey -RegistryHive [Microsoft.Win32.RegistryHive]::LocalMachine -RegistryPath "Invalid\Path" } | Should -Throw + } } } diff --git a/tests/Unit/Public/Remove-RegistrySubKeyTree.tests.ps1 b/tests/Unit/Public/Remove-RegistrySubKeyTree.tests.ps1 new file mode 100644 index 0000000..c083196 --- /dev/null +++ b/tests/Unit/Public/Remove-RegistrySubKeyTree.tests.ps1 @@ -0,0 +1,118 @@ +BeforeAll { + $script:dscModuleName = "WinRegOps" + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Remove-RegistrySubKeyTree function tests' -Tag 'Public' { + + Context 'ByHive parameter set tests' { + It 'Should throw an error if mandatory parameters are missing' { + { Remove-RegistrySubKeyTree -RegistryHive [Microsoft.Win32.RegistryHive]::LocalMachine -Confirm:$false } | Should -Throw + } + + It 'Should validate RegistryHive and RegistryPath parameters' { + # Arrange + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKeyTree = { param($subKey) return $true } + } + + # Mock Open-RegistryKey to return the mocked ParentKey + Mock -CommandName Open-RegistryKey { return $mockParentKey } + + Mock -CommandName Invoke-DeleteSubKeyTree { return $true } -ModuleName $Script:dscModuleName + + $hive = [Microsoft.Win32.RegistryHive]::LocalMachine + + # Act + Remove-RegistrySubKeyTree -RegistryHive $hive -RegistryPath "SOFTWARE\MyApp" -Confirm:$false + + # Assert + Assert-MockCalled -CommandName Open-RegistryKey -Exactly 1 -Scope It + Assert-MockCalled -CommandName Invoke-DeleteSubKeyTree -Exactly 1 -Scope It -ParameterFilter { $SubKeyName -eq "MyApp" } -ModuleName $Script:dscModuleName + } + + It 'Should process the registry key deletion' { + # Arrange + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKeyTree = { param($subKey) return $true } + } + + Mock Open-RegistryKey { return $mockParentKey } + Mock Invoke-DeleteSubKeyTree { } -ModuleName $Script:dscModuleName + + $hive = [Microsoft.Win32.RegistryHive]::LocalMachine + + # Act + Remove-RegistrySubKeyTree -RegistryHive $hive -RegistryPath "SOFTWARE\MyApp" -Confirm:$false + + # Assert + Assert-MockCalled -CommandName Open-RegistryKey -Exactly 1 -Scope It + Assert-MockCalled -CommandName Invoke-DeleteSubKeyTree -Exactly 1 -Scope It -ModuleName $Script:dscModuleName + } + + It 'Should respect ShouldProcess for safety confirmation' { + # Arrange + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKeyTree = { param($subKey) return $true } + } + + # Mock Open-RegistryKey to return the mocked ParentKey + Mock Open-RegistryKey { return $mockParentKey } + Mock Invoke-DeleteSubKeyTree { } -ModuleName $Script:dscModuleName + + # Act + Remove-RegistrySubKeyTree -RegistryHive LocalMachine -RegistryPath "SOFTWARE\MyApp" -Confirm:$false + + # Assert + Assert-MockCalled -CommandName Open-RegistryKey -Exactly 1 -Scope It + Assert-MockCalled -CommandName Invoke-DeleteSubKeyTree -Exactly 1 -Scope It -ModuleName $Script:dscModuleName + } + } + + Context 'ByKey parameter set tests' { + It 'Should throw an error if ParentKey is null' { + { Remove-RegistrySubKeyTree -ParentKey $null -SubKeyName "TestSubKey" -Confirm:$false } | Should -Throw -ErrorId 'ParameterArgumentValidationErrorNullNotAllowed,Remove-RegistrySubKeyTree' + } + + It 'Should remove a subkey when ParentKey is valid' { + # Arrange + $mockParentKey = New-MockObject Microsoft.Win32.RegistryKey -Methods @{ + DeleteSubKeyTree = { param($subKey) return $true } + } + + Mock Invoke-DeleteSubKeyTree {} -ModuleName $Script:dscModuleName + + # Act + Remove-RegistrySubKeyTree -ParentKey $mockParentKey -SubKeyName "Settings" -Confirm:$false + + # Assert + Assert-MockCalled -CommandName Invoke-DeleteSubKeyTree -Exactly 1 -Scope It -ModuleName $Script:dscModuleName + } + } + + Context 'Error handling tests' { + It 'Should throw an exception for invalid subkey name' { + { Remove-RegistrySubKeyTree -RegistryHive [Microsoft.Win32.RegistryHive]::LocalMachine -RegistryPath "" } | Should -Throw -ErrorId 'ParameterArgumentTransformationError,Remove-RegistrySubKeyTree' + } + + It 'Should throw an exception if unable to open the registry key' { + Mock Open-RegistryKey { throw "Failed to open registry key" } + + { Remove-RegistrySubKeyTree -RegistryHive [Microsoft.Win32.RegistryHive]::LocalMachine -RegistryPath "Invalid\Path" } | Should -Throw + } + } +}