Add COM registration verification script
Test-ComRegistration.ps1 registers all 10 COM server DLLs via regasm and verifies all 50 CLSIDs appear in the registry. Supports -Build to compile first and -Unregister to clean up. Checks both native and WOW6432Node registry paths for 32-bit COM on 64-bit Windows. Result: 50/50 CLSIDs pass, 0 GUID mismatches vs original Decal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f0b6fedc9b
commit
6a67ec2056
2 changed files with 356 additions and 0 deletions
111
docs/plans/2026-02-08-com-registration-test.md
Normal file
111
docs/plans/2026-02-08-com-registration-test.md
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
# COM Registration Test - Implementation Plan
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Verify all 10 COM server DLLs register correctly with regasm and their CLSIDs match the original Decal v2.9.8.3.
|
||||||
|
|
||||||
|
**Architecture:** Build a PowerShell verification script that: (1) registers each DLL with regasm, (2) queries the registry for expected CLSIDs, (3) reports pass/fail per GUID. We also cross-reference our [Guid] attributes against the original .rgs files from source_checkout.
|
||||||
|
|
||||||
|
**Tech Stack:** regasm.exe (.NET Framework 4.x), PowerShell, Windows Registry
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Cross-Reference GUIDs Against Original .rgs Files
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Read: `source_checkout/Decal/*.rgs` (original ATL registry scripts)
|
||||||
|
- Read: `source_checkout/DecalControls/*.rgs`
|
||||||
|
- Read: `source_checkout/DecalFilters/*.rgs`
|
||||||
|
- Read: `source_checkout/DecalInput/*.rgs`
|
||||||
|
- Read: `source_checkout/DecalNet/*.rgs`
|
||||||
|
- Read: `source_checkout/DecalDat/*.rgs`
|
||||||
|
- Read: All `*Impl.cs` files in `reconstructed/Managed/Decal.*/`
|
||||||
|
|
||||||
|
**Step 1: Extract GUIDs from .rgs files**
|
||||||
|
Parse each .rgs file for CLSID patterns like `{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}`.
|
||||||
|
|
||||||
|
**Step 2: Extract GUIDs from our [Guid("...")] attributes**
|
||||||
|
Grep all Impl.cs files for Guid attributes.
|
||||||
|
|
||||||
|
**Step 3: Compare and report mismatches**
|
||||||
|
Any GUID in our code that doesn't match the original .rgs is a critical bug.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Write the COM Registration Test Script
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `reconstructed/tools/Test-ComRegistration.ps1`
|
||||||
|
|
||||||
|
**Step 1: Write PowerShell script**
|
||||||
|
|
||||||
|
The script should:
|
||||||
|
1. Find regasm.exe (32-bit .NET Framework 4.x)
|
||||||
|
2. Build the solution if needed
|
||||||
|
3. Register each of the 10 DLLs with `regasm /codebase`
|
||||||
|
4. Query registry for each expected CLSID at `HKCR\CLSID\{GUID}\InprocServer32`
|
||||||
|
5. Verify the registered DLL path points to our built assembly
|
||||||
|
6. Report pass/fail per DLL and per GUID
|
||||||
|
7. Optionally unregister with `regasm /unregister`
|
||||||
|
|
||||||
|
**Step 2: Run the script**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File reconstructed/tools/Test-ComRegistration.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: All 55 CLSIDs register and appear in registry.
|
||||||
|
|
||||||
|
**Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add reconstructed/tools/Test-ComRegistration.ps1
|
||||||
|
git commit -m "feat: add COM registration verification script"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Run Registration and Fix Any Issues
|
||||||
|
|
||||||
|
**Step 1: Execute registration**
|
||||||
|
Run regasm on each DLL and capture output.
|
||||||
|
|
||||||
|
**Step 2: Investigate any failures**
|
||||||
|
Common issues:
|
||||||
|
- Missing dependencies (Interop DLLs not in same directory)
|
||||||
|
- x86 vs x64 regasm mismatch
|
||||||
|
- Missing ComVisible or Guid attributes
|
||||||
|
|
||||||
|
**Step 3: Fix and re-test until all pass**
|
||||||
|
|
||||||
|
**Step 4: Commit fixes**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Unregister and Document Results
|
||||||
|
|
||||||
|
**Step 1: Run unregister to clean up**
|
||||||
|
```powershell
|
||||||
|
regasm /unregister Decal.DecalDat.dll
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Verify CLSIDs are removed from registry**
|
||||||
|
|
||||||
|
**Step 3: Document results in a test report**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DLL-to-GUID Reference (55 CLSIDs across 10 DLLs)
|
||||||
|
|
||||||
|
| DLL | Classes | CLSIDs |
|
||||||
|
|-----|---------|--------|
|
||||||
|
| Decal.DecalDat.dll | 3 | DatService, DatStream, DatLibrary |
|
||||||
|
| Decal.DHS.dll | 2 | HotkeySystem, Hotkey |
|
||||||
|
| Decal.SpellFilter.dll | 3 | Spells, Spell, Component |
|
||||||
|
| Decal.DecalInput.dll | 6 | InputService, Hotkey, WndMsg, WinMsgHook, Timer, InputBuffer |
|
||||||
|
| Decal.DecalNet.dll | 2 | NetService, WebRequest |
|
||||||
|
| Decal.DecalFilters.dll | 7 | World, WorldObject, WorldIterator, CharacterStats, EchoFilter, EchoFilter2, Prefilter |
|
||||||
|
| Decal.Core.dll | 4 | DecalCore, ACHooks, PluginSite2, DecalEnum |
|
||||||
|
| Decal.DecalControls.dll | 17 | Checkbox, List, StaticText, Choice, Progress, FixedLayout, BorderLayout, PageLayout, TextColumn, IconColumn, CheckColumn, DerethMap, Edit, Scroller, PushButton, Notebook, Slider |
|
||||||
|
| Decal.DecalRender.dll | 4 | RenderService, HUDBackground, HUDView, RenderTargetWrapper |
|
||||||
|
| Decal.D3DService.dll | 2 | D3DService, D3DObj |
|
||||||
245
tools/Test-ComRegistration.ps1
Normal file
245
tools/Test-ComRegistration.ps1
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Tests COM registration for the OpenDecal project.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Builds the Decal solution (optionally), registers 10 COM server DLLs using
|
||||||
|
regasm /codebase, and verifies that every expected CLSID appears in the
|
||||||
|
registry under HKCR:\CLSID\{GUID}\InprocServer32.
|
||||||
|
|
||||||
|
.PARAMETER Build
|
||||||
|
If specified, builds Decal.sln (Release) before registration.
|
||||||
|
|
||||||
|
.PARAMETER Unregister
|
||||||
|
If specified, unregisters all DLLs instead of registering them.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\Test-ComRegistration.ps1 -Build
|
||||||
|
Builds the solution, registers all DLLs, and verifies CLSIDs.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\Test-ComRegistration.ps1 -Unregister
|
||||||
|
Unregisters all DLLs.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$Build,
|
||||||
|
[switch]$Unregister
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
|
||||||
|
# ── Paths ────────────────────────────────────────────────────────────────────
|
||||||
|
$RegAsm = 'C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe'
|
||||||
|
$SolutionRoot = Split-Path -Parent $PSScriptRoot # reconstructed\
|
||||||
|
$ManagedRoot = Join-Path $SolutionRoot 'Managed'
|
||||||
|
$SolutionFile = Join-Path $ManagedRoot 'Decal.sln'
|
||||||
|
|
||||||
|
# ── Verify regasm exists ─────────────────────────────────────────────────────
|
||||||
|
if (-not (Test-Path $RegAsm)) {
|
||||||
|
Write-Error "RegAsm.exe not found at $RegAsm"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── DLL → CLSID map ─────────────────────────────────────────────────────────
|
||||||
|
# Each key is the relative path from $ManagedRoot to the built DLL.
|
||||||
|
# Each value is an ordered list of [CLSID, FriendlyName] pairs.
|
||||||
|
$DllMap = [ordered]@{
|
||||||
|
|
||||||
|
'Decal.Core\bin\Release\Decal.Core.dll' = @(
|
||||||
|
,@('CB8875CD-ABC2-42AD-8175-8908706EED37', 'ACHooks')
|
||||||
|
,@('4557D5A1-00DB-48F6-ACB3-4FEF30E2F358', 'DecalCore')
|
||||||
|
,@('6DE65A82-C451-46E6-A82D-92137BE851AD', 'DecalEnum')
|
||||||
|
,@('E2284FC7-17E5-4846-ADAB-07953273C5FB', 'PluginSite2')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.DecalControls\bin\Release\Decal.DecalControls.dll' = @(
|
||||||
|
,@('5AE37451-F79C-478A-834E-EDCF95F01B0E', 'Checkbox')
|
||||||
|
,@('E099DC60-0F19-4690-AB7C-8B5834DA286E', 'Choice')
|
||||||
|
,@('3035299A-C5FB-4CC7-A63C-66400B80DCA4', 'DerethMap')
|
||||||
|
,@('8E8F88D2-AA47-474E-9DB7-912D49C8BE9D', 'Edit')
|
||||||
|
,@('CD6556CD-F8D9-4CB0-AB7C-7C33058294E4', 'FixedLayout')
|
||||||
|
,@('CA121762-31BB-4073-8597-33BAB4BDCAA3', 'BorderLayout')
|
||||||
|
,@('5AD04D45-D4BF-4729-8A2E-5D37CF726CAA', 'PageLayout')
|
||||||
|
,@('3839008F-AF5B-43FC-9F6A-3376B99E3DAF', 'List')
|
||||||
|
,@('864DEABF-D079-4B61-A8CF-081418179239', 'TextColumn')
|
||||||
|
,@('F12A2C4C-3B78-46EB-8722-68B27A75AE55', 'IconColumn')
|
||||||
|
,@('48E444F1-8E30-4E4C-B203-4C87FC901586', 'CheckColumn')
|
||||||
|
,@('ED14E7C5-11BE-4DFD-9829-873AB6BE3CFC', 'Notebook')
|
||||||
|
,@('FE361225-6BB7-4AE0-A10C-8A6420621680', 'Progress')
|
||||||
|
,@('AE4525BE-81D1-40FB-9170-77172077EB49', 'PushButton')
|
||||||
|
,@('8FC80D21-1731-4816-9AD3-B0364D5F2C27', 'Scroller')
|
||||||
|
,@('5D14557A-1268-43C6-A283-3B5B271359AA', 'Slider')
|
||||||
|
,@('4887101C-A9F9-495B-921B-EF22822CFB97', 'StaticText')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.DecalDat\bin\Release\Decal.DecalDat.dll' = @(
|
||||||
|
,@('37B083F0-276E-43AD-8D26-3F7449B519DC', 'DatService')
|
||||||
|
,@('9F7F6CD9-D164-418D-8CB5-3B9ACD70BEAF', 'DatStream')
|
||||||
|
,@('6FA05FDA-B4B5-4386-AB45-92D7E6A5D698', 'DatLibrary')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.DecalFilters\bin\Release\Decal.DecalFilters.dll' = @(
|
||||||
|
,@('50A7E9EC-AB12-4484-9C28-C2A39274A636', 'WorldObject')
|
||||||
|
,@('2681B113-294E-4ABF-B543-624194846BE1', 'WorldIterator')
|
||||||
|
,@('443D4A68-5422-4E0C-9460-973F8FBDB190', 'Prefilter')
|
||||||
|
,@('53092D1B-F0B0-46FF-BF11-8F031EC9B137', 'World')
|
||||||
|
,@('8C2FA400-315D-41DE-B063-D6EF04F12E1F', 'EchoFilter')
|
||||||
|
,@('34239EAD-6317-4C40-A405-193BA5232DD8', 'EchoFilter2')
|
||||||
|
,@('4540C969-08D1-46BF-97AD-6B19D3C10BEE', 'CharacterStats')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.DecalInput\bin\Release\Decal.DecalInput.dll' = @(
|
||||||
|
,@('F183506A-3664-49D6-8CA4-CFD295F2811D', 'Hotkey')
|
||||||
|
,@('85AB0296-124E-4E68-A6A8-FCF5721AC09B', 'WndMsg')
|
||||||
|
,@('B33307BA-706D-474A-80B9-70BB8D13EF3E', 'InputService')
|
||||||
|
,@('F3170E85-517E-43A4-B7B4-6F006A7B1B85', 'WinMsgHook')
|
||||||
|
,@('79497C87-92E1-416B-AE5C-9D6C4C59133C', 'Timer')
|
||||||
|
,@('F0A17A04-7F8F-4A17-A41D-8C297A1E929B', 'InputBuffer')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.DecalNet\bin\Release\Decal.DecalNet.dll' = @(
|
||||||
|
,@('15631E36-55CB-4D16-ADE7-74D9F8A5F4B6', 'WebRequest')
|
||||||
|
,@('C8C406F8-BA2E-4964-8B04-FF38394A8E0E', 'NetService')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.DecalRender\bin\Release\Decal.DecalRender.dll' = @(
|
||||||
|
,@('FB3C8286-88ED-4B4D-B413-94B40F346239', 'RenderService')
|
||||||
|
,@('218432A3-8960-4D91-BBC6-8CDC105C191E', 'HUDBackground')
|
||||||
|
,@('EF810EF1-C843-4563-A3DE-19CE8BEEEEC0', 'HUDView')
|
||||||
|
,@('175D6713-E942-41C0-8BC9-90A565024F76', 'RenderTargetWrapper')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.D3DService\bin\Release\Decal.D3DService.dll' = @(
|
||||||
|
,@('F0CC07A0-2C89-4FA4-9356-714665BC2F8B', 'D3DService')
|
||||||
|
,@('81E79859-2783-4B9A-ADC4-308073F5BB3F', 'D3DObj')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.DHS\bin\Release\Decal.DHS.dll' = @(
|
||||||
|
,@('6B6B9FA8-37DE-4FA3-8C60-52BD6A2F9855', 'HotkeySystem')
|
||||||
|
,@('F5AA853D-CCF3-4562-A654-3A68AB583BCC', 'Hotkey')
|
||||||
|
)
|
||||||
|
|
||||||
|
'Decal.SpellFilter\bin\Release\Decal.SpellFilter.dll' = @(
|
||||||
|
,@('C2D43735-BE7E-4829-AF73-F2E7E820EB16', 'Spells')
|
||||||
|
,@('3040368A-B77A-4FE1-B8CC-D77FA3F36A6D', 'Spell')
|
||||||
|
,@('20425F2F-54FF-4EAE-9538-36699E3DFB56', 'Component')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Helper: coloured write ───────────────────────────────────────────────────
|
||||||
|
function Write-Status {
|
||||||
|
param(
|
||||||
|
[string]$Label,
|
||||||
|
[string]$Message,
|
||||||
|
[ConsoleColor]$Color = 'White'
|
||||||
|
)
|
||||||
|
Write-Host " [$Label] " -ForegroundColor $Color -NoNewline
|
||||||
|
Write-Host $Message
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Administrator check ──────────────────────────────────────────────────────
|
||||||
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
|
$principal = New-Object Security.Principal.WindowsPrincipal $identity
|
||||||
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||||
|
Write-Error 'This script must be run as Administrator (regasm requires elevated privileges).'
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Optional build step ──────────────────────────────────────────────────────
|
||||||
|
if ($Build) {
|
||||||
|
Write-Host "`n=== Building Decal.sln (Release) ===" -ForegroundColor Cyan
|
||||||
|
$buildArgs = @('build', $SolutionFile, '-c', 'Release')
|
||||||
|
& dotnet @buildArgs
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Build failed with exit code $LASTEXITCODE"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host ''
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Counters ─────────────────────────────────────────────────────────────────
|
||||||
|
$totalCLSIDs = 0
|
||||||
|
$passedCLSIDs = 0
|
||||||
|
$failedCLSIDs = 0
|
||||||
|
|
||||||
|
# ── Process each DLL ─────────────────────────────────────────────────────────
|
||||||
|
foreach ($relPath in $DllMap.Keys) {
|
||||||
|
$dllPath = Join-Path $ManagedRoot $relPath
|
||||||
|
$dllName = Split-Path -Leaf $dllPath
|
||||||
|
|
||||||
|
Write-Host "`n=== $dllName ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Check the DLL exists
|
||||||
|
if (-not (Test-Path $dllPath)) {
|
||||||
|
Write-Status 'SKIP' "$dllPath not found. Build the solution first (-Build)." Yellow
|
||||||
|
$clsids = $DllMap[$relPath]
|
||||||
|
$totalCLSIDs += $clsids.Count
|
||||||
|
$failedCLSIDs += $clsids.Count
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Register or unregister ───────────────────────────────────────────
|
||||||
|
if ($Unregister) {
|
||||||
|
Write-Host " Unregistering..." -ForegroundColor Yellow
|
||||||
|
$output = & $RegAsm /unregister /nologo $dllPath 2>&1
|
||||||
|
$regExitCode = $LASTEXITCODE
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host " Registering..." -ForegroundColor Yellow
|
||||||
|
$output = & $RegAsm /codebase /nologo $dllPath 2>&1
|
||||||
|
$regExitCode = $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show regasm output
|
||||||
|
if ($output) {
|
||||||
|
foreach ($line in $output) {
|
||||||
|
Write-Host " $line" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($regExitCode -ne 0) {
|
||||||
|
Write-Status 'WARN' "regasm exited with code $regExitCode" Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Verify CLSIDs ────────────────────────────────────────────────────
|
||||||
|
if (-not $Unregister) {
|
||||||
|
$clsids = $DllMap[$relPath]
|
||||||
|
foreach ($entry in $clsids) {
|
||||||
|
$guid = $entry[0]
|
||||||
|
$name = $entry[1]
|
||||||
|
$totalCLSIDs++
|
||||||
|
|
||||||
|
# 32-bit regasm registers under WOW6432Node on 64-bit Windows
|
||||||
|
$regKey64 = "Registry::HKEY_CLASSES_ROOT\CLSID\{$guid}\InprocServer32"
|
||||||
|
$regKey32 = "Registry::HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{$guid}\InprocServer32"
|
||||||
|
if ((Test-Path $regKey64) -or (Test-Path $regKey32)) {
|
||||||
|
Write-Status 'PASS' "{$guid} $name" Green
|
||||||
|
$passedCLSIDs++
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Status 'FAIL' "{$guid} $name" Red
|
||||||
|
$failedCLSIDs++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Summary ──────────────────────────────────────────────────────────────────
|
||||||
|
Write-Host "`n============================================" -ForegroundColor Cyan
|
||||||
|
if ($Unregister) {
|
||||||
|
Write-Host " Unregistration complete." -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$summaryColor = if ($failedCLSIDs -eq 0) { 'Green' } else { 'Red' }
|
||||||
|
Write-Host " Total CLSIDs checked : $totalCLSIDs" -ForegroundColor $summaryColor
|
||||||
|
Write-Host " Passed : $passedCLSIDs" -ForegroundColor Green
|
||||||
|
Write-Host " Failed : $failedCLSIDs" -ForegroundColor $(if ($failedCLSIDs -eq 0) { 'Green' } else { 'Red' })
|
||||||
|
}
|
||||||
|
Write-Host "============================================`n" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
if ($failedCLSIDs -gt 0) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
exit 0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue