feat: add safe Tornado K3D playout adapter
This commit is contained in:
338
scripts/Inspect-K3DRegistration.ps1
Normal file
338
scripts/Inspect-K3DRegistration.ps1
Normal file
@@ -0,0 +1,338 @@
|
||||
#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()
|
||||
}
|
||||
Reference in New Issue
Block a user