Files
MBN_STOCK_WEBVIEW/scripts/Inspect-K3DRegistration.ps1

339 lines
9.7 KiB
PowerShell

#Requires -Version 5.1
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$expectedTypeLibId = '{2B7F2D64-3A8D-401C-BE73-5C0747BA342C}'
$expectedTypeLibVersion = '1.0'
$expectedThreadingModel = 'Apartment'
$amd64Machine = 0x8664
$expectedClasses = @(
[pscustomobject]@{
Name = 'KAEngine'
ClassId = '{D756CDBE-AA31-42B2-9CC7-018753CA61BF}'
ProgId = 'K3DAsyncEngine.KAEngine.1'
},
[pscustomobject]@{
Name = 'KAEventHandler'
ClassId = '{39828C77-EFF0-4E59-979B-8673C028C718}'
ProgId = 'K3DAsyncEngine.KAEventHandler.1'
}
)
function Get-RegistryValue {
param(
[Parameter(Mandatory = $true)]
[Microsoft.Win32.RegistryKey] $BaseKey,
[Parameter(Mandatory = $true)]
[string] $SubKey,
[string] $Name = ''
)
$key = $BaseKey.OpenSubKey($SubKey, $false)
if ($null -eq $key) {
return $null
}
try {
return $key.GetValue(
$Name,
$null,
[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
}
finally {
$key.Dispose()
}
}
function Assert-TextEqual {
param(
[AllowNull()]
[object] $Actual,
[Parameter(Mandatory = $true)]
[string] $Expected,
[Parameter(Mandatory = $true)]
[string] $Description
)
if ($null -eq $Actual -or
-not [string]::Equals(
[string] $Actual,
$Expected,
[StringComparison]::OrdinalIgnoreCase)) {
throw "$Description mismatch. Expected '$Expected'; found '$Actual'."
}
}
function Assert-GuidEqual {
param(
[AllowNull()]
[object] $Actual,
[Parameter(Mandatory = $true)]
[string] $Expected,
[Parameter(Mandatory = $true)]
[string] $Description
)
$actualGuid = [Guid]::Empty
$expectedGuid = [Guid]::Empty
if ($null -eq $Actual -or
-not [Guid]::TryParse([string] $Actual, [ref] $actualGuid) -or
-not [Guid]::TryParse($Expected, [ref] $expectedGuid) -or
$actualGuid -ne $expectedGuid) {
throw "$Description mismatch. Expected '$Expected'; found '$Actual'."
}
}
function Resolve-RegisteredFilePath {
param(
[AllowNull()]
[object] $RegistryValue,
[Parameter(Mandatory = $true)]
[string] $Description
)
if ($null -eq $RegistryValue -or [string]::IsNullOrWhiteSpace([string] $RegistryValue)) {
throw "$Description is not registered."
}
$expanded = [Environment]::ExpandEnvironmentVariables([string] $RegistryValue)
$expanded = $expanded.Trim().Trim('"')
if (-not [IO.Path]::IsPathRooted($expanded)) {
throw "$Description is not an absolute path."
}
$fullPath = [IO.Path]::GetFullPath($expanded)
if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) {
throw "$Description does not exist: $fullPath"
}
return $fullPath
}
function Get-PeMachine {
param(
[Parameter(Mandatory = $true)]
[string] $Path
)
$stream = [IO.File]::Open(
$Path,
[IO.FileMode]::Open,
[IO.FileAccess]::Read,
([IO.FileShare]::ReadWrite -bor [IO.FileShare]::Delete))
$reader = [IO.BinaryReader]::new($stream)
try {
if ($stream.Length -lt 64 -or $reader.ReadUInt16() -ne 0x5A4D) {
throw "Not a PE file: $Path"
}
$stream.Position = 0x3C
$peOffset = $reader.ReadInt32()
if ($peOffset -lt 0 -or ($peOffset + 6) -gt $stream.Length) {
throw "Invalid PE header offset: $Path"
}
$stream.Position = $peOffset
if ($reader.ReadUInt32() -ne 0x00004550) {
throw "Invalid PE signature: $Path"
}
$machineCode = $reader.ReadUInt16()
$machineName = switch ($machineCode) {
0x014C { 'I386' }
0x8664 { 'AMD64' }
0xAA64 { 'ARM64' }
default { 'Unknown' }
}
return [pscustomobject]@{
Code = $machineCode
Name = $machineName
}
}
finally {
$reader.Dispose()
$stream.Dispose()
}
}
function Assert-Amd64File {
param(
[Parameter(Mandatory = $true)]
[string] $Path,
[Parameter(Mandatory = $true)]
[string] $Description
)
$machine = Get-PeMachine -Path $Path
if ($machine.Code -ne $amd64Machine) {
throw "$Description is not AMD64. PE machine is $($machine.Name) (0x$('{0:X4}' -f $machine.Code))."
}
}
function Test-RegistryKeyExists {
param(
[Parameter(Mandatory = $true)]
[Microsoft.Win32.RegistryKey] $BaseKey,
[Parameter(Mandatory = $true)]
[string] $SubKey
)
$key = $BaseKey.OpenSubKey($SubKey, $false)
if ($null -eq $key) {
return $false
}
$key.Dispose()
return $true
}
function Assert-NoCurrentUserOverrides {
param(
[Parameter(Mandatory = $true)]
[Microsoft.Win32.RegistryKey] $CurrentUserRoot
)
$paths = @("Software\Classes\TypeLib\$expectedTypeLibId")
foreach ($registration in $expectedClasses) {
$paths += "Software\Classes\CLSID\$($registration.ClassId)"
$paths += "Software\Classes\$($registration.ProgId)"
}
foreach ($path in $paths) {
if (Test-RegistryKeyExists -BaseKey $CurrentUserRoot -SubKey $path) {
throw "A per-user Registry64 K3D COM override is present at HKCU:\$path. Remove the override through the approved vendor recovery procedure before use."
}
}
}
function Assert-ComClassRegistration {
param(
[Parameter(Mandatory = $true)]
[Microsoft.Win32.RegistryKey] $MachineRoot,
[Parameter(Mandatory = $true)]
[object] $Registration
)
$classesPrefix = 'SOFTWARE\Classes'
$progIdClassId = Get-RegistryValue `
-BaseKey $MachineRoot `
-SubKey "$classesPrefix\$($Registration.ProgId)\CLSID"
Assert-GuidEqual `
-Actual $progIdClassId `
-Expected $Registration.ClassId `
-Description "$($Registration.Name) ProgID to CLSID mapping"
$classProgId = Get-RegistryValue `
-BaseKey $MachineRoot `
-SubKey "$classesPrefix\CLSID\$($Registration.ClassId)\ProgID"
Assert-TextEqual `
-Actual $classProgId `
-Expected $Registration.ProgId `
-Description "$($Registration.Name) CLSID to ProgID mapping"
$classTypeLibId = Get-RegistryValue `
-BaseKey $MachineRoot `
-SubKey "$classesPrefix\CLSID\$($Registration.ClassId)\TypeLib"
Assert-GuidEqual `
-Actual $classTypeLibId `
-Expected $expectedTypeLibId `
-Description "$($Registration.Name) TypeLib GUID"
$classTypeLibVersion = Get-RegistryValue `
-BaseKey $MachineRoot `
-SubKey "$classesPrefix\CLSID\$($Registration.ClassId)\Version"
if ($null -ne $classTypeLibVersion -and
-not [string]::IsNullOrWhiteSpace([string] $classTypeLibVersion)) {
Assert-TextEqual `
-Actual $classTypeLibVersion `
-Expected $expectedTypeLibVersion `
-Description "$($Registration.Name) TypeLib version"
}
$inprocKey = "$classesPrefix\CLSID\$($Registration.ClassId)\InprocServer32"
$serverPath = Resolve-RegisteredFilePath `
-RegistryValue (Get-RegistryValue -BaseKey $MachineRoot -SubKey $inprocKey) `
-Description "$($Registration.Name) InprocServer32 path"
$threadingModel = Get-RegistryValue `
-BaseKey $MachineRoot `
-SubKey $inprocKey `
-Name 'ThreadingModel'
Assert-TextEqual `
-Actual $threadingModel `
-Expected $expectedThreadingModel `
-Description "$($Registration.Name) threading model"
Assert-Amd64File -Path $serverPath -Description "$($Registration.Name) COM server DLL"
return [pscustomobject][ordered]@{
Name = $Registration.Name
ClassId = $Registration.ClassId
ProgId = $Registration.ProgId
ServerPath = $serverPath
ServerMachine = 'AMD64'
ThreadingModel = $threadingModel
ReciprocalMapping = $true
}
}
if (-not [Environment]::Is64BitOperatingSystem) {
throw 'K3D x64 registration requires a 64-bit Windows operating system.'
}
$machineRoot = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
[Microsoft.Win32.RegistryHive]::LocalMachine,
[Microsoft.Win32.RegistryView]::Registry64)
$currentUserRoot = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
[Microsoft.Win32.RegistryHive]::CurrentUser,
[Microsoft.Win32.RegistryView]::Registry64)
try {
Assert-NoCurrentUserOverrides -CurrentUserRoot $currentUserRoot
$typeLibKey = "SOFTWARE\Classes\TypeLib\$expectedTypeLibId\$expectedTypeLibVersion\0\win64"
$typeLibPath = Resolve-RegisteredFilePath `
-RegistryValue (Get-RegistryValue -BaseKey $machineRoot -SubKey $typeLibKey) `
-Description 'K3D x64 type library path'
Assert-Amd64File -Path $typeLibPath -Description 'K3D type library DLL'
$classReports = @()
foreach ($registration in $expectedClasses) {
$classReports += Assert-ComClassRegistration `
-MachineRoot $machineRoot `
-Registration $registration
}
[pscustomobject][ordered]@{
Status = 'Valid'
RegistryHive = 'HKLM'
RegistryView = 'Registry64'
CurrentUserOverrides = 'None'
TypeLibId = $expectedTypeLibId
TypeLibVersion = $expectedTypeLibVersion
TypeLibTarget = 'win64'
TypeLibPath = $typeLibPath
TypeLibMachine = 'AMD64'
Classes = $classReports
PowerShellProcessIs64Bit = [Environment]::Is64BitProcess
ComActivated = $false
}
}
finally {
$currentUserRoot.Dispose()
$machineRoot.Dispose()
}