Skip to content

Commit

Permalink
added supporting methods like GetValueNames and GetValueKind (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
LarryWisherMan authored Sep 18, 2024
1 parent c5f244c commit 2381d96
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 0 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ public implementation
- New Private function `Get-RegistrySubKeyOperation` for managing the logic of
opening a key or using an existing one provided in `Remove-RegistrySubKey`

- **`New-RegistryKeyValuesObject`**:
- Retrieves and exports all the values of a specified registry key or subkey
into a custom object. The object includes the registry path, backup date, user,
computer name, and a dictionary of the key's values (including data type and value).

- Supports backing up both root keys and subkeys.

- **`Get-RegistryValueNames`**:
- Retrieves all the value names from a specified registry key. This function
simplifies the process of listing all registry values for a given key.

- **`Get-RegistryValueKind`**:
- Retrieves the type (kind) of a specified registry key value. This allows for
easy identification of value types such as `String`, `DWord`, and `Binary`
within registry keys.

### Fixed

- Error Handling for `[System.Security.SecurityException]` in `Open-RegistryKey`
Expand Down
17 changes: 17 additions & 0 deletions source/NotImplimented/Update-JsonFile.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function Update-JsonFile {
param (
[Parameter(Mandatory = $true)]
[string]$OutputFile,

[Parameter(Mandatory = $true)]
[array]$RegistryData # Generic data for registry keys
)

if (Test-Path $OutputFile) {
$existingData = Get-Content -Path $OutputFile | ConvertFrom-Json
$existingData += $RegistryData
$existingData | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputFile
} else {
$RegistryData | ConvertTo-Json -Depth 10 | Out-File -FilePath $OutputFile
}
}
55 changes: 55 additions & 0 deletions source/Public/Get-RegistryValueKind.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<#
.SYNOPSIS
Retrieves the value kind (type) of a specified registry key value.
.DESCRIPTION
This function wraps the RegistryKey.GetValueKind method to retrieve the type (kind) of a registry value.
You can supply a base registry key and the value name. It returns the type of the value (e.g., String, DWord, Binary).
.PARAMETER BaseKey
The base registry key from which to retrieve the value kind. This should be an open RegistryKey object.
.PARAMETER ValueName
The name of the registry value for which to retrieve the kind. This is a required parameter.
.OUTPUTS
Microsoft.Win32.RegistryValueKind
The type of the value (e.g., String, DWord, Binary).
.EXAMPLE
$baseKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SOFTWARE\MyApp")
Get-RegistryValueKind -BaseKey $baseKey -ValueName "Setting1"
Description:
Retrieves the kind (type) of the "Setting1" value from the "SOFTWARE\MyApp" registry key.
.NOTES
This function wraps around the .NET Framework's RegistryKey.GetValueKind method to make it easier to retrieve registry value types in PowerShell.
#>

function Get-RegistryValueKind
{
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[Microsoft.Win32.RegistryKey]$BaseKey,

[Parameter(Mandatory = $true, Position = 1)]
[string]$ValueName
)

try
{
# Retrieve the kind (type) of the registry value
return $BaseKey.GetValueKind($ValueName)
}
catch [System.Security.SecurityException]
{
$errorMessage = "Access to the registry key is denied."
throw [System.Security.SecurityException] $errorMessage
}
catch
{
throw $_
}
}
52 changes: 52 additions & 0 deletions source/Public/Get-RegistryValueNames.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<#
.SYNOPSIS
Retrieves all the value names of a specified registry key.
.DESCRIPTION
This function wraps the RegistryKey.GetValueNames method to retrieve all the value names of a registry key.
You can supply a base registry key, and the function will return an array of the value names present in the registry key.
.PARAMETER BaseKey
The base registry key from which to retrieve the value names. This should be an open RegistryKey object.
.OUTPUTS
String[]
An array of strings representing the names of all the values stored in the registry key.
.EXAMPLE
$baseKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SOFTWARE\MyApp")
Get-RegistryValueNames -BaseKey $baseKey
Description:
Retrieves all the value names from the "SOFTWARE\MyApp" registry key.
.NOTES
This function wraps around the .NET Framework's RegistryKey.GetValueNames method to make it easier to retrieve value names in PowerShell.
#>

function Get-RegistryValueNames
{
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'The function name is appropriate.')]

[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[Microsoft.Win32.RegistryKey]$BaseKey
)

try
{
# Retrieve all the value names in the registry key
return $BaseKey.GetValueNames()
}
catch [System.Security.SecurityException]
{
$errorMessage = "Access to the registry key is denied."
throw [System.Security.SecurityException] $errorMessage
}
catch
{
throw $_
}
}

107 changes: 107 additions & 0 deletions source/Public/New-RegistryKeyValuesObject.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<#
.SYNOPSIS
Creates a custom object representing the values of a registry key or subkey.
.DESCRIPTION
The New-RegistryKeyValuesObject function retrieves all the values of a specified registry key or subkey and returns a custom object containing the registry path, backup date, user, computer name, and the key's values.
This function is flexible, allowing you to specify a subkey within the registry key, or work with the root key itself. Each value is stored along with its data type.
.PARAMETER RegistryKey
The base registry key to be queried. This parameter is mandatory and must be a valid `Microsoft.Win32.RegistryKey` object.
.PARAMETER SubKeyName
The name of the optional subkey within the `RegistryKey`. If provided, the function will operate on this subkey. If not provided, it will operate on the root registry key.
.PARAMETER User
The username of the individual performing the backup. Defaults to the current user's name from `$env:USERNAME`.
.PARAMETER ComputerName
The name of the computer where the registry key resides. Defaults to the current computer name from `$env:COMPUTERNAME`.
.OUTPUTS
PSCustomObject
Returns a custom object with the following properties:
- RegistryPath: The full path of the registry key or subkey.
- BackupDate: The date and time of the backup.
- ByUser: The username of the individual performing the backup.
- ComputerName: The name of the computer.
- Values: A dictionary of all the values within the registry key, where each entry contains:
- Value: The data stored in the registry key value.
- Type: The data type of the registry value (e.g., String, DWord, Binary).
.EXAMPLE
# Example 1: Export values from the root registry key
$remoteRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, "RemotePC")
$registryKey = $remoteRegistry.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList")
$registryData = New-RegistryKeyValuesObject -RegistryKey $registryKey
This example opens the `ProfileList` key on a remote computer and exports the values to a custom object.
.EXAMPLE
# Example 2: Export values from a specific subkey
$remoteRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, "RemotePC")
$registryKey = $remoteRegistry.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList")
$registryData = New-RegistryKeyValuesObject -RegistryKey $registryKey -SubKeyName "S-1-5-21-12345"
This example exports the values from a specific subkey (`S-1-5-21-12345`) within the `ProfileList` key on a remote computer.
.NOTES
- The function is useful for backing up registry data or auditing changes within a registry key.
- Ensure that you have appropriate permissions to access the registry keys.
.LINK
https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.registrykey?view=net-8.0
#>

function New-RegistryKeyValuesObject
{
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'No state changes are made in this function.')]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Microsoft.Win32.RegistryKey]$RegistryKey, # Generic registry key

[string]$SubKeyName, # Optional subkey name within the registry key

[string]$User = $env:USERNAME,

[string]$ComputerName = $env:COMPUTERNAME
)

# Automatically set RegistryPath to the name of the registry key or subkey if provided
if ($SubKeyName)
{
$subKey = Get-RegistrySubKey -BaseKey $RegistryKey -Name $SubKeyName -writable $false
$RegistryPath = $subKey.Name
$selectedKey = $subKey
}
else
{
$RegistryPath = $RegistryKey.Name
$selectedKey = $RegistryKey
}

# Create the registry data object
$registryData = [PSCustomObject]@{
RegistryPath = $RegistryPath
BackupDate = Get-Date
ByUser = $User
ComputerName = $ComputerName
Values = @{}
}

# Collect all value names and types from the subkey or root key
foreach ($valueName in (Get-RegistryValueNames -baseKey $selectedKey))
{
$value = Get-RegistryValue -BaseKey $selectedKey -ValueName $valueName
$valueType = (Get-RegistryValueKind -BaseKey $selectedKey -ValueName $valueName).tostring()
$registryData.Values[$valueName] = @{
Value = $value
Type = $valueType
}
}

return $registryData
}

53 changes: 53 additions & 0 deletions tests/Unit/Public/Get-RegistryValueKind.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
BeforeAll {
$script:dscModuleName = "WinRegOps"

# Import the module containing the registry functions
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-RegistryValueKind Function Tests' -Tag 'Public' {
Context 'When retrieving the kind of a registry value using New-MockObject' {
# Mock the registry key object and the GetValueKind method

It 'Should return the correct value kind (DWord) for the specified value name' {

$mockBaseKey = New-MockObject -Type Microsoft.Win32.RegistryKey -Methods @{
GetValueKind = { return [Microsoft.Win32.RegistryValueKind]::DWord }
}

# Arrange
$mockValueName = 'TestValue'

# Act
$result = Get-RegistryValueKind -BaseKey $mockBaseKey -ValueName $mockValueName

# Assert
$result | Should -BeOfType 'Microsoft.Win32.RegistryValueKind'
$result | Should -Be 'DWord'
}

It 'Should throw a SecurityException if access is denied' {
# Arrange: Mock the behavior for a security exception
$mockBaseKey = New-MockObject -Type Microsoft.Win32.RegistryKey -Methods @{
GetValueKind = { throw [System.Security.SecurityException] "Access to the registry key is denied." }
}
$mockValueName = 'TestValue'

# Act & Assert
{ Get-RegistryValueKind -BaseKey $mockBaseKey -ValueName $mockValueName } | Should -Throw 'Exception calling "GetValueKind" with "1" argument(s): "Access to the registry key is denied."'
}
}
}
52 changes: 52 additions & 0 deletions tests/Unit/Public/Get-RegistryValueNames.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
BeforeAll {
$script:dscModuleName = "WinRegOps"

# Import the module containing the registry functions
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-RegistryValueNames Function Tests' -Tag 'Public' {
Context 'When retrieving all value names from a registry key using New-MockObject' {

It 'Should return all the value names from the registry key' {
# Mock the registry key object and the GetValueNames method
$mockBaseKey = New-MockObject -Type Microsoft.Win32.RegistryKey -Methods @{
GetValueNames = { return @('Value1', 'Value2', 'Value3') }
}

# Arrange
# No additional arrangements needed

# Act
$result = Get-RegistryValueNames -BaseKey $mockBaseKey
# Assert
$result | Should -BeOfType [string]
$result | Should -Contain 'Value1'
$result | Should -Contain 'Value2'
$result | Should -Contain 'Value3'
}

It 'Should throw a SecurityException if access is denied' {
# Mock the registry key object and the GetValueNames method to throw a SecurityException
$mockBaseKey = New-MockObject -Type Microsoft.Win32.RegistryKey -Methods @{
GetValueNames = { throw [System.Security.SecurityException] "Access to the registry key is denied." }
}

# Act & Assert
{ Get-RegistryValueNames -BaseKey $mockBaseKey } | Should -Throw 'Exception calling "GetValueNames" with "0" argument(s): "Access to the registry key is denied."'
}
}
}
Loading

0 comments on commit 2381d96

Please sign in to comment.