A Registry Scripting Challenge Solution
In the September 2024 round-up, I gave you a PowerShell scripting challenge. The best way to learn PowerShell and improve your skills is to do it. The methodology you use to derive a solution is just as important as the solution itself. Yes, it is important to learn PowerShell techniques, but how you approach a problem is just as important. The emphasis is not on the destination but the journey.
In this article, I will walk you through my solution to the challenge, which is by no means the only solution, or even the best solution for you.
The Challenge
For those of you who missed it, here was the challenge I presented.
Create a key under HKCU:
called PSProfile
. Under this key, create a key for each of the 4 PowerShell profile scripts, e.g. AllUsersAllHosts
. Under each key, add values for the file path, the file size, the last modified date, and an MD5 file hash of the file. You will need to account for profiles that may not exist.
Store the file size as a QWord. If your profile is using a symbolic link, you may need to resolve the link to get the actual file path and size.
Bonus: Store the script contents as a binary value in the registry and write a function or script to view the binary key value.
If you want to take some time to work on a solution, please do. You can always return to see my solution.
Using CoPilot
I want to point out that I didn't start with a solution. I gave myself the challenge as well. Normally, I would have opened VS Code and started scripting. But this is 2024 and AI-assisted coding is now a thing. I decided to use GitHub CoPilot to see what it would suggest. I wanted to see if it would help me come up with a solution or at least give me some ideas. In VS Code, I used the challenge above as a prompt. Here is what CoPilot suggested:
# Define the profile paths
$profiles = @{
AllUsersAllHosts = $PROFILE.AllUsersAllHosts
AllUsersCurrentHost = $PROFILE.AllUsersCurrentHost
CurrentUserAllHosts = $PROFILE.CurrentUserAllHosts
CurrentUserCurrentHost = $PROFILE.CurrentUserCurrentHost
}
# Create the main registry key
$mainKey = 'HKCU:\PSProfile'
if (-not (Test-Path $mainKey)) {
New-Item -Path $mainKey -Force
}
# Function to calculate MD5 hash
function Get-FileHashMD5 {
param (
[string]$filePath
)
if (Test-Path $filePath) {
$hash = Get-FileHash -Path $filePath -Algorithm MD5
return $hash.Hash
}
return $null
}
# Iterate over each profile and create subkeys with values
foreach ($profile in $profiles.GetEnumerator()) {
$subKey = "$mainKey\$($profile.Key)"
if (-not (Test-Path $subKey)) {
New-Item -Path $subKey -Force
}
$filePath = $profile.Value
if (Test-Path $filePath) {
$fileInfo = Get-Item $filePath
$fileSize = $fileInfo.Length
$lastModified = $fileInfo.LastWriteTime
$md5Hash = Get-FileHashMD5 -filePath $filePath
Set-ItemProperty -Path $subKey -Name 'FilePath' -Value $filePath
Set-ItemProperty -Path $subKey -Name 'FileSize' -Value $fileSize
Set-ItemProperty -Path $subKey -Name 'LastModified' -Value $lastModified
Set-ItemProperty -Path $subKey -Name 'MD5Hash' -Value $md5Hash
}
}
I like that CoPilot included comments. The code also tests for the existence of the registry key and only creates it if it doesn't exist. To be honest, I don't have many concerns with the suggested code. It is definitely a good starting point, and it works.

But let's see what I can do with it. I deleted the registry entries and started with a clean slate.
My Improvements
My solution, and most likely yours is a PowerShell script file. CoPilot took a hard-coded approach to getting the profile paths. I prefer a more dynamic approach. I know the paths are stored as NoteProperties in the $PROFILE
variable. I can use this to get the profile paths by querying the underlying PSObject
:
$profile.PSObject.properties |
Where-Object MemberType -eq NoteProperty |
ForEach-Object -Begin {
#initialize an empty hashtable to store the profile paths
$profiles = @{}
} -Process {
#add each profile name and path to the hashtable
$profiles.Add($_.Name, $_.Value)
}
I added each profile name and path to a new hashtable.
PS C:\> $profiles
Name Value
---- -----
CurrentUserCurrentHost C:\Users\Jeff\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
AllUsersAllHosts C:\Program Files\PowerShell\7\profile.ps1
CurrentUserAllHosts C:\Users\Jeff\Documents\PowerShell\profile.ps1
AllUsersCurrentHost C:\Program Files\PowerShell\7\Microsoft.PowerShell_profile.ps1
Next, I need to create the registry key if it doesn't exist.
# Create the main registry key
$mainKey = 'HKCU:\PSProfile'
#create the registry key if it doesn't exist
if (-not (Test-Path $mainKey)) {
[void](New-Item -Path $mainKey -Force)
}
This is similar to what CoPilot suggested. I used the [void]
cast to suppress the output of New-Item
. This is preferred over piping to Out-Null
.
Now I can follow CoPilot's lead and create child registry keys for each profile, based on items in the $profiles
hashtable.
foreach ($profile in $profiles.GetEnumerator()) {
Write-Host "Processing $($profile.Key)" -ForegroundColor Green
$subKey = Join-Path -Path $mainKey -childPath $profile.Key
#create the registry subkey if it doesn't exist
if (-not (Test-Path $subKey)) {
[void](New-Item -Path $subKey -Force)
}
#assign the file path to a variable for clarity
$filePath = $profile.Value
if (Test-Path $filePath) {
#get the profile script if it exists
$fileInfo = Get-Item $filePath
$data = [PSCustomObject]@{
FilePath = $filePath
FileSize = $fileInfo.Length
LastModified = $fileInfo.LastWriteTime
MD5Hash = (Get-FileHash -Algorithm MD5 -Path $filePath).hash
Content = (Get-Content -Path $filePath -AsByteStream) -as [byte[]]
}
If ($fileInfo.LinkTarget) {
$data | Add-Member -MemberType NoteProperty -Name 'LinkTarget' -Value $fileInfo.LinkTarget -Force
$data.FileSize = (Get-Item -Path $fileInfo.LinkTarget).Length
}
$data | Set-ItemProperty -Path $SubKey
}
} #foreach profile