Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/guid.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: GUID validation

on:
push:
branches: [master]
paths: ['Test-IsGuid.ps1', 'Tests/Guid.Tests.ps1', '.github/workflows/guid.yml']
pull_request:
paths: ['Test-IsGuid.ps1', 'Tests/Guid.Tests.ps1', '.github/workflows/guid.yml']
workflow_dispatch:

permissions:
contents: read

jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Install and run Pester
run: |
$ErrorActionPreference = 'Stop'
Install-Module Pester -RequiredVersion 5.7.1 -Scope CurrentUser -Force -Repository PSGallery
Import-Module Pester -RequiredVersion 5.7.1 -Force
$configuration = New-PesterConfiguration
$configuration.Run.Path = './Tests'
$configuration.Run.Exit = $true
$configuration.Output.Verbosity = 'Detailed'
Invoke-Pester -Configuration $configuration
38 changes: 38 additions & 0 deletions .github/workflows/registry.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Registry reporting

on:
push:
branches: [master]
paths: ['Export-Registry.ps1', 'Test-IsRegistryKey.ps1', 'Private/Open-StandaloneRegistryKey.ps1', 'Tests/Registry.Tests.ps1', '.github/workflows/registry.yml']
pull_request:
paths: ['Export-Registry.ps1', 'Test-IsRegistryKey.ps1', 'Private/Open-StandaloneRegistryKey.ps1', 'Tests/Registry.Tests.ps1', '.github/workflows/registry.yml']
workflow_dispatch:

permissions:
contents: read

jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Install and run Pester
run: |
$ErrorActionPreference = 'Stop'
Install-Module Pester -RequiredVersion 5.7.1 -Scope CurrentUser -Force -Repository PSGallery
Import-Module Pester -RequiredVersion 5.7.1 -Force
$configuration = New-PesterConfiguration
$configuration.Run.Path = './Tests'
$configuration.Run.Exit = $true
$configuration.Output.Verbosity = 'Detailed'
Invoke-Pester -Configuration $configuration
219 changes: 62 additions & 157 deletions Export-Registry.ps1
Original file line number Diff line number Diff line change
@@ -1,182 +1,87 @@
#Requires -Version 7.4

. (Join-Path $PSScriptRoot 'Private/Open-StandaloneRegistryKey.ps1')

function Export-Registry
{
<#
.SYNOPSIS
Export registry item properties.

.DESCRIPTION
Export item properties for a given registry key.

By default results will be written to the pipeline unless the -ExportFormat parameter is used.

.PARAMETER KeyPath
A string representing the Key(s) to export in the PsDrive format IE: HKCU:\SOFTWARE\TestSoftware

.PARAMETER ExportFormat
A string representing the format to use for the export.

Possible values are:

- CSV
- XML

PArameter is used in conjunction with the ExportPath paramter.

.PARAMETER ExportPath
A string representing the path where keys should be exported.

.PARAMETER NoBinaryData
When parameter is specified any binary data present in the registry key is removed.

.EXAMPLE
PS C:\> Export-RegistryNew -KeyPath 'HKCU:\SOFTWARE\TestSoftware'

.NOTES
Additional information about the function.
.SYNOPSIS
Reports values from literal Windows Registry keys to objects, CSV or CLIXML.
.DESCRIPTION
Reads values on the supplied keys only, not descendant keys. Missing keys
warn and continue. Empty keys yield no rows. The unnamed value has Name ''.
Path is a stable native key-name string. XML preserves typed values; CSV is
a text report and does not preserve array/binary types. Neither is a .reg
backup. Existing export files are overwritten only after ShouldProcess.
#>

[CmdletBinding(DefaultParameterSetName = 'PrintOnly')]
param
(
[Parameter(ParameterSetName = 'PrintOnly',
Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
Position = 0,
HelpMessage = 'Enter a registry path using the PSDrive format (IE: HKCU:\SOFTWARE\TestSoftware')]
[Parameter(ParameterSetName = 'Export',
Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[CmdletBinding(DefaultParameterSetName = 'PrintOnly', SupportsShouldProcess)]
[OutputType([pscustomobject])]
param (
[Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias('PSPath')]
[string[]]
$KeyPath,
[Parameter(ParameterSetName = 'Export',
Mandatory = $true)]
[ValidateSet('xml', 'csv', 'reg', IgnoreCase = $true)]
[ValidateNotNullOrEmpty()]
[string]
$ExportFormat,
[Parameter(ParameterSetName = 'Export',
Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]
$ExportPath,
[Parameter(ParameterSetName = 'Export')]
[switch]
$NoBinaryData
)
[string[]]$KeyPath,

begin
{
# Initialize results array
[System.Collections.ArrayList]$returnData = @()
}
[Parameter(ParameterSetName = 'Export', Mandatory)]
[ValidateSet('xml', 'csv')]
[string]$ExportFormat,

[Parameter(ParameterSetName = 'Export', Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$ExportPath,

[switch]$NoBinaryData
)
begin { $rows = [System.Collections.Generic.List[object]]::new() }
process
{
# Go through all paths
foreach ($path in $KeyPath)
{
if ((Test-IsRegistryKey -KeyPath $path) -eq $true)
$key = Open-StandaloneRegistryKey -Path $path
if ($null -eq $key)
{
Write-Verbose "Getting properties for key: $path"

# Get registry item
$paramGetItem = @{
Path = $path
ErrorAction = 'Stop'
}

[Microsoft.Win32.RegistryKey]$regItem = Get-Item @paramGetItem

# Get key properties
[array]$regItemProperties = $regItem.'Property'

if ($regItemProperties.Count -gt 0)
{
# Enumerate properties
foreach ($property in $regItemProperties)
{
Write-Verbose "Exporting $property"

# Append data to return array
[void]($returnData.Add([pscustomobject]@{
'Path' = $regItem
'Name' = $property
'Value' = $regItem.GetValue($property, $null, 'DoNotExpandEnvironmentNames')
'Type' = $regItem.GetValueKind($property)
'Computername' = $env:computername
}))
}
}
else
{
# Return default object
[void]($returnData.Add([pscustomobject]@{
'Path' = $regItem
'Name' = '(Default)'
'Value' = $null
'Type' = 'String'
'Computername' = $env:computername
}))
}
Write-Warning "Key $path does not exist."
continue
}
else
try
{
Write-Warning -Message "Key $path does not exist"

continue
foreach ($name in $key.GetValueNames())
{
$kind = $key.GetValueKind($name)
if ($NoBinaryData -and $kind -eq [Microsoft.Win32.RegistryValueKind]::Binary) { continue }
$rows.Add([pscustomobject]@{
Path = [string]$key.Name
Name = [string]$name
Value = $key.GetValue($name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
Type = [string]$kind
Computername = [Environment]::MachineName
})
}
}
finally { $key.Dispose() }
}
}

end
{
# Check we have results
if ($null -ne $returnData)
if ($PSCmdlet.ParameterSetName -eq 'PrintOnly')
{
switch ($PSCmdlet.ParameterSetName)
{
'Export'
{
# Remove binary data
if ($PSBoundParameters.ContainsKey('NoBinaryData'))
{
Write-Verbose -Message 'Removing binary data from return values'

# Remove binary data
$returnData = $returnData | Where-Object { $_.Type -ne 'Binary' }
}

switch ($ExportFormat)
{
'csv'
{
# Export to CSV
$returnData | Export-Csv -Path $ExportPath -NoTypeInformation -Force
}
'xml'
{
# Export to XML and overwrite
$returnData | Export-Clixml -Path $ExportPath -Force
}
}

Write-Verbose -Message "Data written to $ExportPath"
}

default
{
Write-Verbose -Message 'No data will be exported'

# Print on screen only
$returnData
}
}
$rows.ToArray()
}
else
elseif ($rows.Count -eq 0)
{
Write-Warning -Message 'No found - No export will be created'
Write-Warning 'No values matched; no export file was created or changed.'
}
elseif ($PSCmdlet.ShouldProcess($ExportPath, "Overwrite $ExportFormat registry report"))
{
if ($ExportFormat -eq 'csv')
{
$rows.ToArray() | Export-Csv -LiteralPath $ExportPath -NoTypeInformation -Force -ErrorAction Stop
}
else
{
$rows.ToArray() | Export-Clixml -LiteralPath $ExportPath -Force -ErrorAction Stop
}
Write-Verbose "Data written to $ExportPath"
}
}
}
}
25 changes: 25 additions & 0 deletions Private/Open-StandaloneRegistryKey.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
function Open-StandaloneRegistryKey
{
[CmdletBinding()]
param ([Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Path)

if (-not $IsWindows)
{
throw [PlatformNotSupportedException]::new('Registry functions require the Windows Registry provider.')
}
$provider = $null
$drive = $null
[void]$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path, [ref]$provider, [ref]$drive)
if ($provider.Name -ne 'Registry')
{
throw 'Path must use the Registry provider.'
}
try
{
return Get-Item -LiteralPath $Path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException]
{
return $null
}
}
Loading
Loading