<#
.SYNOPSIS
Dumps everything that decides whether Windows will load the Soundka APO.

.DESCRIPTION
"audiodg is running but the APO is not loaded" has several unrelated causes and
the page cannot tell them apart: the endpoint may not be bound, the bound
endpoint may not be the one the audio is going to, the endpoint may have come
back with a new GUID after a reconnect, or the module may be missing from disk.
This prints the state behind each of them in one pass, so a support round trip
carries an answer instead of a guess.

Run from an elevated PowerShell, ideally while audio is playing.
#>
[CmdletBinding()]
param(
    # Where the portable package was unpacked. Defaults to this script's parent.
    [string]$PackageRoot = '',

    # Pushes a tone to the bound endpoint and watches whether audiodg loads the
    # APO. "audiodg is running but our module is absent" has two unrelated
    # causes -- the audio is going somewhere else, or Windows refuses to load
    # the effect on this endpoint -- and only playing to that exact endpoint
    # tells them apart.
    [switch]$Play,
    [ValidateRange(4, 120)]
    [int]$PlaySeconds = 12,

    # Temporarily reduces the endpoint's mode-effect chain to our CLSID alone,
    # probes, and puts the chain back. Answers whether another effect sharing
    # the chain is what stops ours from being instantiated -- which no amount of
    # reading the registry can tell you, because a chain that fails as a whole
    # looks exactly like a chain that was ignored.
    [switch]$SoloChain
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'

# This report is redirected to a file by the .cmd wrapper and then read by
# someone else. Without this, Write-Host goes out in the console code page and
# every Chinese device name -- the one thing a route problem hinges on -- lands
# in the file as mojibake, while the license tool's own UTF-8 output does not:
# one file, two encodings, and the useful half unreadable.
try {
    [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
} catch {
    # A redirected handle can refuse the change; the report is still readable
    # for everything except non-ASCII names.
}

# Bumped whenever this script changes in a way that affects what it reports.
$diagnosticVersion = '2026-08-20.1'
$renderClsid = '{631D9145-CACF-44D7-861B-5D82ADD8C84F}'
$renderRoot = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render'
$modeEffect = '{d04e05a6-594b-4fb6-a80d-01af5eed7d1d},14'
$modeList = '{d3993a3f-99c2-4402-b5ec-a92a0367664b},6'
$friendlyName = '{a45c254e-df1c-4efd-8020-67d146a850e0},2'
# PKEY_AudioEngine_DeviceFormat: the shared-mode format Windows mixes at for
# this endpoint. The algorithm only runs at 44100 or 48000, so an endpoint left
# at 96 kHz passes audio through untouched even when everything else is right --
# and nothing else in this report would show why.
# The shared-mode format Windows mixes at for this endpoint. The algorithm only
# runs at 44100 or 48000, so an endpoint left at 96 kHz passes audio through
# untouched even when everything else is right -- and nothing else in this
# report would show why.
$deviceFormatKeys = @(
    '{f19f064d-082c-4e27-bc73-6882a1bb8e4c},0',
    '{e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},0')

# The value is a PROPVARIANT blob: 4 bytes of type, 4 of length, then a
# WAVEFORMATEX whose nSamplesPerSec sits at its own offset 4.
function Get-EndpointSampleRate($endpointPath) {
    $properties = Get-ItemProperty (Join-Path $endpointPath 'Properties') -ErrorAction SilentlyContinue
    if ($null -eq $properties) { return $null }
    foreach ($key in $deviceFormatKeys) {
        if ($properties.PSObject.Properties.Name -notcontains $key) { continue }
        $blob = $properties.$key
        if ($blob -isnot [byte[]] -or $blob.Length -lt 16) { continue }
        $rate = [System.BitConverter]::ToUInt32($blob, 12)
        # Sanity-checked rather than trusted: a wrong offset yields a number
        # like 1, and a bogus rate printed as fact would send someone chasing it.
        if ($rate -ge 8000 -and $rate -le 768000) { return $rate }
    }
    return $null
}
if ([string]::IsNullOrWhiteSpace($PackageRoot)) {
    $PackageRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
}

function Write-Section($title) {
    Write-Host ''
    Write-Host "=== $title ===" -ForegroundColor Cyan
}

# The report is long and the deciding line is usually somewhere in the middle,
# where it is easy to read past. Checks are recorded in the order they have to
# hold, so the verdict can name the first one that failed instead of leaving the
# reader to find it.
$script:checks = @()
function Add-Check($name, $ok, $fix) {
    $script:checks += [pscustomobject]@{ Name = $name; Ok = [bool]$ok; Fix = $fix }
}

function Test-Elevated {
    $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    return ([System.Security.Principal.WindowsPrincipal]$identity).IsInRole(
        [System.Security.Principal.WindowsBuiltInRole]::Administrator)
}

Write-Section 'Environment'
# Stamped so a report can be matched to the build that produced it. Three
# reports in a row arrived from a stale copy of this script and were each
# analysed as new evidence; without a version in the output there is no way to
# notice that from the report alone.
Write-Host ("Diagnostic version   : {0}" -f $diagnosticVersion)
foreach ($module in @(
        @('packaged APO', (Join-Path $PackageRoot 'bin\soundka_windows_apo.dll')),
        @('deployed APO', (Join-Path $env:ProgramData 'Soundka\live-test\soundka_windows_apo.dll')))) {
    if (Test-Path -LiteralPath $module[1]) {
        $hash = (Get-FileHash -LiteralPath $module[1] -Algorithm SHA256).Hash.Substring(0, 12)
        $stamp = (Get-Item -LiteralPath $module[1]).LastWriteTime.ToString('MM-dd HH:mm')
        Write-Host ("{0,-21}: {1}  {2}" -f $module[0], $hash, $stamp)
    } else {
        Write-Host ("{0,-21}: missing" -f $module[0]) -ForegroundColor Yellow
    }
}
$elevated = Test-Elevated
Write-Host ("Elevated             : {0}" -f $elevated)
if (-not $elevated) {
    Write-Host 'Not elevated: endpoint properties below may read as missing.' -ForegroundColor Yellow
}
$os = Get-CimInstance Win32_OperatingSystem
Write-Host ("OS                   : {0}" -f $os.Caption)
Write-Host ("Build                : {0}" -f $os.BuildNumber)
Write-Host ("Package root         : {0}" -f $PackageRoot)

# Our module is self-signed, which is why the stack needs
# DisableProtectedAudioDG. These two features enforce code integrity above that
# switch and refuse unsigned binaries outright -- silently, with the module
# simply never appearing in the process that tried to load it.
# Reads a value that is often absent entirely. Get-ItemProperty returns an
# object without the property in that case, which StrictMode turns into a
# terminating error, so the whole report would die on an optional check.
function Get-RegistryValue($path, $name) {
    $item = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue
    if ($null -eq $item) { return $null }
    if ($item.PSObject.Properties.Name -notcontains $name) { return $null }
    return $item.$name
}

$smartAppControl = Get-RegistryValue `
    'HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy' 'VerifiedAndReputablePolicyState'
$smartText = switch ($smartAppControl) {
    0 { 'off' }
    1 { 'ENFORCED  <- blocks unsigned modules' }
    2 { 'evaluation  <- may block unsigned modules' }
    default { 'not present (off)' }
}
Write-Host ("Smart App Control    : {0}" -f $smartText) -ForegroundColor $(
    if ($smartAppControl -in @(1, 2)) { 'Red' } else { 'Gray' })

$hvci = Get-RegistryValue `
    'HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity' `
    'Enabled'
Write-Host ("Memory integrity     : {0}" -f $(if ($hvci -eq 1) { 'on' } else { 'off' }))

Write-Section 'Code integrity policy'
# Our module is unsigned. A user-mode code integrity policy refuses to load
# unsigned code into a process and says nothing to the caller, which is exactly
# what "the engine never even tried" looks like from outside. Device Guard
# reports both enforcement states, and the CodeIntegrity event log names the
# file it blocked, so this either produces the answer or eliminates it.
try {
    $guard = Get-CimInstance -ClassName Win32_DeviceGuard `
        -Namespace 'root\Microsoft\Windows\DeviceGuard' -ErrorAction Stop
    $enforcement = @{ 0 = 'off'; 1 = 'audit only'; 2 = 'ENFORCED' }
    $kernelState = $guard.CodeIntegrityPolicyEnforcementStatus
    $userState = $guard.UsermodeCodeIntegrityPolicyEnforcementStatus
    Write-Host ("Kernel-mode CI policy: {0}" -f $(
        if ($enforcement.ContainsKey([int]$kernelState)) { $enforcement[[int]$kernelState] } else { $kernelState }))
    Write-Host ("User-mode CI policy  : {0}" -f $(
        if ($enforcement.ContainsKey([int]$userState)) { $enforcement[[int]$userState] } else { $userState })) `
        -ForegroundColor $(if ([int]$userState -eq 2) { 'Red' } else { 'Gray' })
    if ([int]$userState -eq 2) {
        Write-Host '  ^ Unsigned user-mode modules are blocked on this machine.' -ForegroundColor Red
        Write-Host '    That would stop audiodg loading our DLL with no error anywhere.' -ForegroundColor Red
    }
} catch {
    Write-Host 'Device Guard status unavailable on this SKU.'
}

# Named so we know who to ask for an exclusion, if it comes to that.
try {
    $products = @(Get-CimInstance -Namespace 'root\SecurityCenter2' `
        -ClassName AntiVirusProduct -ErrorAction Stop)
    Write-Host ("Antivirus            : {0}" -f $(
        if ($products.Count) { ($products | ForEach-Object { $_.displayName }) -join ', ' }
        else { 'none registered' }))
} catch {
    Write-Host 'Antivirus            : could not query'
}

# A block leaves a record here with the full path, which turns "something is
# stopping it" into "this component stopped this file".
$blocked = @()
foreach ($logName in @('Microsoft-Windows-CodeIntegrity/Operational',
        'Microsoft-Windows-AppLocker/EXE and DLL')) {
    try {
        $blocked += @(Get-WinEvent -FilterHashtable @{
                LogName = $logName
                StartTime = (Get-Date).AddDays(-1)
            } -MaxEvents 200 -ErrorAction Stop |
            Where-Object { $_.Message -match 'soundka|audiodg' } |
            Select-Object -First 10 |
            ForEach-Object { "[$logName] $($_.TimeCreated) id=$($_.Id) $($_.Message -replace '\s+', ' ')" })
    } catch { }
}
if ($blocked.Count -eq 0) {
    Write-Host 'CI / AppLocker events: none mentioning our module in the last day'
} else {
    Write-Host 'CI / AppLocker events:' -ForegroundColor Red
    $blocked | ForEach-Object { Write-Host ("  " + $_.Substring(0, [Math]::Min(260, $_.Length))) -ForegroundColor Red }
}

Write-Section 'Prerequisites'
$clsidKey = "HKLM:\SOFTWARE\Classes\CLSID\$renderClsid\InprocServer32"
$registeredPath = $null
if (Test-Path $clsidKey) {
    $registeredPath = (Get-ItemProperty $clsidKey).'(default)'
    Write-Host ("CLSID registered     : yes -> {0}" -f $registeredPath)
    Write-Host ("  module on disk     : {0}" -f (Test-Path -LiteralPath $registeredPath -PathType Leaf))
    foreach ($companion in @('fse.dll', 'model_weights.enc')) {
        $path = Join-Path (Split-Path $registeredPath) $companion
        Write-Host ("  {0,-17}: {1}" -f $companion, (Test-Path -LiteralPath $path -PathType Leaf))
    }
} else {
    Write-Host 'CLSID registered     : NO  <- the launcher never completed' -ForegroundColor Red
}
Add-Check 'COM CLSID registered' (Test-Path $clsidKey) `
    'Run the launcher (as administrator) and read its output: it stops at the first error.'
# A CLSID with only an InprocServer32 can be created by anyone but is not an APO
# as far as the audio engine is concerned: it never attempts to load it, and
# says nothing. Everything else can look perfect while this key is missing.
$apoRegistrationKey =
    "HKLM:\SOFTWARE\Classes\AudioEngine\AudioProcessingObjects\{631D9145-CACF-44D7-861B-5D82ADD8C84F}"
if (Test-Path $apoRegistrationKey) {
    $registration = Get-ItemProperty $apoRegistrationKey
    Write-Host ("APO registered w/engine: yes  ({0}, flags {1}, {2} interface(s))" -f
        $registration.FriendlyName, $registration.Flags, $registration.NumAPOInterfaces)
} else {
    Write-Host 'APO registered w/engine: NO' -ForegroundColor Red
    Write-Host ("  {0} is missing." -f $apoRegistrationKey) -ForegroundColor Red
    Write-Host '  The engine does not know this CLSID is an APO, so it never' -ForegroundColor Red
    Write-Host '  tries to load it. Re-run the launcher to write it.' -ForegroundColor Red
}
Add-Check 'APO registered with the audio engine' (Test-Path $apoRegistrationKey) `
    'Run the launcher; it writes this registration at startup.'

$audioPolicy = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Audio' -ErrorAction SilentlyContinue
$protected = if ($audioPolicy -and ($audioPolicy.PSObject.Properties.Name -contains 'DisableProtectedAudioDG')) {
    $audioPolicy.DisableProtectedAudioDG
} else {
    '<absent>'
}
Write-Host ("DisableProtectedAudioDG: {0}  (must be 1 for an unsigned module)" -f $protected)
Add-Check 'DisableProtectedAudioDG = 1' ($protected -eq 1) `
    'Run the launcher; it sets this and restarts the audio stack.'

Write-Section 'COM activation'
# The one thing every other check assumes and none of them tests: that this
# machine can actually turn the registered CLSID into an object. audiodg does
# exactly this, and when it fails it fails silently -- no module in the process,
# no error anywhere, identical to Windows deciding not to use the effect.
if (Test-Path $clsidKey) {
    $threading = (Get-ItemProperty $clsidKey).ThreadingModel
    Write-Host ("ThreadingModel       : {0}" -f $(
        if ($threading) { $threading } else { '<absent>  (must be Both)' })) `
        -ForegroundColor $(if ($threading -eq 'Both') { 'Gray' } else { 'Red' })

    # Loads our DLL into this process. Pointed at a private mapping name so it
    # cannot disturb a live session's diagnostics.
    $previousMap = $env:SOUNDKA_CONTROL_MAP
    $env:SOUNDKA_CONTROL_MAP = "Local\SoundkaDiagnose-$PID"
    try {
        $type = [Type]::GetTypeFromCLSID(
            [Guid]'631D9145-CACF-44D7-861B-5D82ADD8C84F', $false)
        $instance = [Activator]::CreateInstance($type)
        if ($instance) {
            Write-Host 'CoCreateInstance     : OK, the module loads and instantiates' -ForegroundColor Green
            Add-Check 'The module can be created by COM' $true ''
            [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($instance)
        } else {
            Write-Host 'CoCreateInstance     : returned nothing' -ForegroundColor Red
        }
    } catch {
        $inner = $_.Exception
        while ($inner.InnerException) { $inner = $inner.InnerException }
        $code = try { '0x{0:X8}' -f $inner.HResult } catch { '?' }
        Write-Host ("CoCreateInstance     : FAILED {0}" -f $code) -ForegroundColor Red
        Write-Host ("  {0}" -f $inner.Message) -ForegroundColor Red
        Write-Host '  This is the cause: audiodg cannot create the effect either.' -ForegroundColor Red
        Add-Check 'The module can be created by COM' $false `
            'The DLL itself cannot be activated on this machine; see the HRESULT above.'
        switch ($code) {
            '0x8007007E' { Write-Host '  0x8007007E = a DLL it depends on is missing.' -ForegroundColor Red }
            '0x800700C1' { Write-Host '  0x800700C1 = wrong architecture (32/64 bit).' -ForegroundColor Red }
            '0x80040154' { Write-Host '  0x80040154 = class not registered in this view.' -ForegroundColor Red }
            '0x80070005' { Write-Host '  0x80070005 = access denied, likely blocked by security software.' -ForegroundColor Red }
        }
    } finally {
        $env:SOUNDKA_CONTROL_MAP = $previousMap
    }
} else {
    Write-Host 'CLSID is not registered; nothing to activate.' -ForegroundColor Red
}

Write-Section 'License'
$licenseTool = Join-Path $PackageRoot 'bin\soundka_license_tool.exe'
if (Test-Path -LiteralPath $licenseTool -PathType Leaf) {
    Write-Host ("Machine ID           : {0}" -f (& $licenseTool machine-id))
    $installed = Join-Path $env:ProgramData 'Soundka\soundka.license'
    if (Test-Path -LiteralPath $installed -PathType Leaf) {
        & $licenseTool verify $installed
    } else {
        Write-Host "No license at $installed" -ForegroundColor Yellow
    }
} else {
    Write-Host "License tool not found at $licenseTool" -ForegroundColor Yellow
}

Write-Section 'Render endpoints'
# DEVICE_STATE_ACTIVE is 1. Anything else means Windows will not build a stream
# on it, so a binding there can never load the APO.
$script:playbackSawApo = $null
$boundActive = @()
try {
    $mmKey = Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render' -ErrorAction Stop
    $bound = @()
    $active = @()
    foreach ($endpoint in Get-ChildItem $mmKey.PSPath -ErrorAction SilentlyContinue) {
        $guid = $endpoint.PSChildName
        $state = (Get-ItemProperty $endpoint.PSPath -ErrorAction SilentlyContinue).DeviceState
        $name = (Get-ItemProperty (Join-Path $endpoint.PSPath 'Properties') -ErrorAction SilentlyContinue).$friendlyName
        $fx = Get-ItemProperty (Join-Path $endpoint.PSPath 'FxProperties') -ErrorAction SilentlyContinue
        $chain = if ($fx -and ($fx.PSObject.Properties.Name -contains $modeEffect)) { @($fx.$modeEffect) } else { @() }
        $hasModes = $fx -and ($fx.PSObject.Properties.Name -contains $modeList)
        $isBound = ($chain -join ' ') -match [regex]::Escape($renderClsid)
        if ($state -eq 1) {
            $active += [pscustomobject]@{
                Guid = $guid; Name = $name; Bound = $isBound; ModeList = [bool]$hasModes
                Rate = Get-EndpointSampleRate $endpoint.PSPath
            }
        }
        if ($isBound) {
            $bound += [pscustomobject]@{
                Guid = $guid; Name = $name; State = $state; ModeList = [bool]$hasModes
            }
            if ($state -eq 1) {
                $boundActive += [pscustomobject]@{ Guid = $guid; Name = $name }
            }
        }
    }

    Write-Host 'ACTIVE endpoints (state=1) -- only these can carry audio:'
    if ($active.Count -eq 0) {
        Write-Host '  none' -ForegroundColor Red
    } else {
        $active | ForEach-Object {
            $rate = if ($null -eq $_.Rate) { '?' } else { "$($_.Rate) Hz" }
            $flag = if ($_.Rate -and $_.Rate -notin @(44100, 48000)) { '  <- algorithm needs 44100 or 48000' } else { '' }
            '  {0}  bound={1,-5} modeList={2,-5} {3,-10} {4}{5}' -f
                $_.Guid.Substring(0, 10), $_.Bound, $_.ModeList, $rate, $_.Name, $flag
        } | Write-Host
    }

    Write-Host ''
    Write-Host 'Endpoints carrying our CLSID:'
    if ($bound.Count -eq 0) {
        Write-Host '  NONE  <- nothing is bound, so Windows never loads the APO' -ForegroundColor Red
        Add-Check 'An endpoint is bound to the effect' $false `
            'Open the web page, select the output device in use and press the bind button.'
    } else {
        $bound | ForEach-Object {
            $warn = if ($_.State -ne 1) { '   <- NOT ACTIVE, this binding is dead' } else { '' }
            '  {0}  state={1,-3} modeList={2,-5} {3}{4}' -f
                $_.Guid.Substring(0, 10), $_.State, $_.ModeList, $_.Name, $warn
        } | Write-Host
        Add-Check 'An endpoint is bound to the effect' $true ''
        Add-Check 'The bound endpoint is active' ($null -ne ($bound | Where-Object { $_.State -eq 1 })) `
            'The bound endpoint is gone (a reconnect gives the device a new id). Re-bind it.'
        if (-not ($bound | Where-Object { $_.State -eq 1 })) {
            Write-Host '  Every bound endpoint is inactive: the device most likely' -ForegroundColor Red
            Write-Host '  reconnected and came back with a new GUID. Re-bind it.' -ForegroundColor Red
        }
    }
} catch {
    Write-Host "Unable to read endpoint properties: $($_.Exception.Message)" -ForegroundColor Red
}

Write-Section 'Bound endpoint details'
# Reaching this far with everything green and still no APO means the answer is
# in the endpoint's own property store, not in our registration. Dumped whole
# rather than probed field by field, because the interesting value is whichever
# one differs from a machine where it works.
$inspect = @()
if (Test-Path Variable:\bound) { $inspect = @($bound) }
if ($inspect.Count -eq 0) {
    Write-Host 'No endpoint carries our CLSID, so there is nothing to inspect.'
} else {
    foreach ($target in $inspect) {
        $endpointKey = Join-Path $renderRoot $target.Guid
        Write-Host ("{0}  {1}" -f $target.Guid, $target.Name)

        # PKEY_AudioEndpoint_Disable_SysFx. Set by the "disable all sound
        # effects" checkbox in Windows sound settings, and by some OEM audio
        # utilities. When it is 1 Windows loads no APO at all on the endpoint,
        # whatever the effect chain says.
        $props = Get-ItemProperty (Join-Path $endpointKey 'Properties') -ErrorAction SilentlyContinue
        $disableKey = '{1da5d803-d492-4edd-8c23-e0c0ffee7f0e},5'
        $disable = if ($props -and ($props.PSObject.Properties.Name -contains $disableKey)) {
            $props.$disableKey
        } else {
            '<absent>'
        }
        Write-Host ("  Disable_SysFx        : {0}" -f $disable) -ForegroundColor $(
            if ($disable -eq 1) { 'Red' } else { 'Gray' })
        if ($disable -eq 1) {
            Write-Host '  ^ All sound effects are disabled on this endpoint.' -ForegroundColor Red
            Write-Host '    Sound settings -> this device -> uncheck "disable all"' -ForegroundColor Red
        }
        foreach ($extra in @(
                @('FormFactor    ', '{1da5d803-d492-4edd-8c23-e0c0ffee7f0e},0'),
                @('JackSubType   ', '{1da5d803-d492-4edd-8c23-e0c0ffee7f0e},8'),
                @('EventDriven   ', '{1da5d803-d492-4edd-8c23-e0c0ffee7f0e},7'))) {
            $value = if ($props -and ($props.PSObject.Properties.Name -contains $extra[1])) {
                $props.($extra[1])
            } else {
                '<absent>'
            }
            Write-Host ("  {0}       : {1}" -f $extra[0].Trim(), $value)
        }

        Write-Host '  FxProperties:'
        $fxKey = Join-Path $endpointKey 'FxProperties'
        $fx = Get-ItemProperty $fxKey -ErrorAction SilentlyContinue
        if (-not $fx) {
            Write-Host '    (none)' -ForegroundColor Red
        } else {
            $fx.PSObject.Properties |
                Where-Object { $_.Name -notlike 'PS*' } |
                ForEach-Object {
                    $value = $_.Value
                    if ($value -is [byte[]]) {
                        $value = "(binary, $($value.Length) bytes)"
                    } elseif ($value -is [array]) {
                        $value = $value -join ' | '
                    }
                    Write-Host ("    {0}`n        = {1}" -f $_.Name, $value)
                }
        }

        # An endpoint declares its effects through either the legacy single-CLSID
        # properties or the composite list properties. We only ever write the
        # composite mode entry (,14), so an endpoint whose legacy mode slot (,6)
        # is already taken -- by the inbox Microsoft effects, or by a vendor --
        # never reaches our entry, and every other check still looks correct.
        $legacyStream = '{d04e05a6-594b-4fb6-a80d-01af5eed7d1d},5'
        $legacyMode = '{d04e05a6-594b-4fb6-a80d-01af5eed7d1d},6'
        $hasLegacyMode = $fx -and ($fx.PSObject.Properties.Name -contains $legacyMode)
        $hasLegacyStream = $fx -and ($fx.PSObject.Properties.Name -contains $legacyStream)
        # Properties keyed by our own CLSID are residue from the retired PnP
        # package, pointing at a software device that no longer exists. They are
        # ours, they are stale, and the engine may be resolving the CLSID
        # through them instead of through the registration we write.
        $residue = @()
        if ($fx) {
            $residue = @($fx.PSObject.Properties.Name |
                Where-Object { $_ -like "$renderClsid,*" })
        }
        if ($residue.Count -gt 0) {
            Write-Host ("  STALE PnP residue    : {0}" -f ($residue -join ', ')) -ForegroundColor Red
            Write-Host '  ^ Left by an old install. Run the stop script, which now' -ForegroundColor Red
            Write-Host '    removes these, then start and bind again.' -ForegroundColor Red
        }
        Write-Host ("  legacy FX slots      : stream={0} mode={1}" -f
            $hasLegacyStream, $hasLegacyMode) -ForegroundColor $(
            if ($hasLegacyMode) { 'Red' } else { 'Gray' })
        if ($hasLegacyMode) {
            Write-Host '  ^ This endpoint uses the legacy effect properties, and its' -ForegroundColor Red
            Write-Host '    mode slot already holds another APO. Our composite entry' -ForegroundColor Red
            Write-Host '    is inert here. This is the likely cause.' -ForegroundColor Red
        }

        # Whether we added the mode list or the vendor already had one. A vendor
        # list is preserved untouched, so if it names modes the stream never
        # uses, the effect is skipped and nothing here looks wrong.
        $marker = "HKLM:\SOFTWARE\Soundka\DevBinding\$($target.Guid)"
        if (Test-Path $marker) {
            $state = Get-ItemProperty $marker
            $added = if ($state.PSObject.Properties.Name -contains 'AddedModeList') {
                $state.AddedModeList
            } else {
                '<absent>'
            }
            Write-Host ("  AddedModeList marker : {0}" -f $added)
        } else {
            Write-Host '  AddedModeList marker : <no binding record>'
        }
    }
}

Write-Section 'Staged module trust'
# A package that travelled as a zip carries mark-of-the-web on every extracted
# file, and the copy into ProgramData preserves it. That does not stop an
# ordinary LoadLibrary, but it does interact with SmartScreen and some endpoint
# protection, and it is invisible in Explorer.
if ($registeredPath -and (Test-Path -LiteralPath $registeredPath -PathType Leaf)) {
    foreach ($file in @($registeredPath,
            (Join-Path (Split-Path $registeredPath) 'fse.dll'))) {
        if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { continue }
        $zone = Get-Content -LiteralPath $file -Stream 'Zone.Identifier' -ErrorAction SilentlyContinue
        $signature = Get-AuthenticodeSignature -LiteralPath $file -ErrorAction SilentlyContinue
        Write-Host ("{0}" -f (Split-Path $file -Leaf))
        Write-Host ("  mark-of-the-web    : {0}" -f $(if ($zone) { ($zone -join '; ') } else { 'none' }))
        Write-Host ("  signature          : {0}" -f $(if ($signature) { $signature.Status } else { 'unknown' }))
    }
}

Write-Section 'Control service and shared mapping'
# Every parameter and every diagnostic counter travels through this mapping. The
# APO no-ops all of its reporting when it cannot attach, so a module that loaded
# correctly still shows zero blocks processed, an empty trace and a page that
# says nothing is loaded. None of that is visible without checking the mapping
# itself, which nothing here did until now.
$service = @(Get-Process -Name 'soundka_control_demo' -ErrorAction SilentlyContinue)
if ($service.Count -eq 0) {
    Write-Host 'Control service      : NOT RUNNING' -ForegroundColor Red
    Write-Host '  The mapping only exists while it runs, and closing the launcher' -ForegroundColor Red
    Write-Host '  window stops it. The APO then has nothing to attach to.' -ForegroundColor Red
} else {
    Write-Host ("Control service      : running, pid {0}" -f ($service | ForEach-Object { $_.Id }) -join ', ')
}
Add-Check 'The control service is running' ($service.Count -gt 0) `
    'Start it with the launcher and leave its window open; the mapping lives in that process.'

$mappingName = 'Global\SoundkaControlV1'
$mappingOpen = $false
try {
    $mapping = [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting($mappingName)
    $mapping.Dispose()
    $mappingOpen = $true
    Write-Host ("Shared mapping       : {0} exists" -f $mappingName)
} catch {
    Write-Host ("Shared mapping       : {0} CANNOT BE OPENED" -f $mappingName) -ForegroundColor Red
    Write-Host ("  {0}" -f $_.Exception.Message) -ForegroundColor Red
}
Add-Check 'The shared control mapping exists' $mappingOpen `
    'Re-run the launcher as administrator: creating a Global mapping needs elevation.'

Write-Section 'APO load trace'
# Written by the module itself, so unlike the audiodg module list it cannot miss
# a load that happened between two samples, and unlike the shared mapping it
# survives a module that fails before it ever opens that mapping.
$tracePath = Join-Path $env:ProgramData 'Soundka\apo-trace.log'
if (-not (Test-Path -LiteralPath $tracePath)) {
    Write-Host "No trace file at $tracePath (re-run the launcher to create it)." -ForegroundColor Yellow
} else {
    # An empty trace was read as "the module never ran" for several rounds while
    # the real reason was that audiodg, running below medium integrity, could
    # not write to a file left at the default label. The label is reported here
    # so the file's silence is never again mistaken for the module's.
    $label = (& icacls $tracePath 2>$null | Select-String 'Mandatory Label')
    if ($label) {
        Write-Host ("integrity label      : {0}" -f ($label -join ' ').Trim())
    } else {
        Write-Host 'integrity label      : none (default medium)' -ForegroundColor Red
        Write-Host '  audiodg runs lower than that and cannot write here, so an' -ForegroundColor Red
        Write-Host '  empty trace says nothing about whether the module ran.' -ForegroundColor Red
    }
    $lines = @(Get-Content -LiteralPath $tracePath -ErrorAction SilentlyContinue)
    if ($lines.Count -eq 0) {
        Write-Host 'The trace file is empty: Windows never loaded the module at all.' -ForegroundColor Red
        Write-Host 'So this is not the APO failing -- it is never being asked to run.' -ForegroundColor Red
        Add-Check 'The module has been loaded at least once' $false `
            'Nothing has ever loaded it. Work through the failed check above this one.'
    } else {
        Add-Check 'The module has been loaded at least once' $true ''
        # Which processes ever wrote is the question the raw lines make hard to
        # see. The diagnostic's own CoCreateInstance writes here too, so a trace
        # containing only its pid means no audio process has run the module.
        $tracePids = @($lines |
            ForEach-Object { if ($_ -match 'pid=(\d+)') { $matches[1] } } |
            Sort-Object -Unique)
        $enginePid = (Get-Process audiodg -ErrorAction SilentlyContinue |
            Select-Object -First 1).Id
        Write-Host ("distinct pids        : {0}" -f ($tracePids -join ', '))
        if ($enginePid) {
            Write-Host ("current audiodg pid  : {0}  {1}" -f $enginePid,
                $(if ($tracePids -contains "$enginePid") { '(has written here)' }
                  else { '(has NOT written here)' }))
        }
        Write-Host ("{0} entries, last 25:" -f $lines.Count)
        $lines | Select-Object -Last 25 | ForEach-Object { Write-Host "  $_" }
    }
}

Write-Section 'audiodg'
$audiodg = Get-Process audiodg -ErrorAction SilentlyContinue
if (-not $audiodg) {
    Write-Host 'audiodg is not running: nothing is playing audio right now.'
    Write-Host 'Start playback and run this again -- the APO only loads when a stream exists.'
} else {
    Write-Host ("pid                  : {0}" -f $audiodg.Id)
    $modules = @()
    try {
        $modules = $audiodg.Modules | ForEach-Object { $_.ModuleName }
    } catch {
        Write-Host 'Cannot enumerate modules (needs elevation).' -ForegroundColor Yellow
    }
    if ($modules.Count -gt 0) {
        Write-Host ("modules              : {0}" -f $modules.Count)
        Write-Host ("soundka_windows_apo  : {0}" -f ($modules -contains 'soundka_windows_apo.dll'))
        Add-Check 'The audio engine has the effect loaded' ($modules -contains 'soundka_windows_apo.dll') `
            'Play audio to the bound endpoint, then re-run. If it stays absent with audio flowing, send this report on.'
        Write-Host ("fse.dll              : {0}" -f ($modules -contains 'fse.dll'))

        # Whether this machine's audio engine loads *any* effect module tells
        # us which question we are answering: one where our effect is singled
        # out, or one where the endpoint runs no effects at all. A bare list of
        # names is the only way to see that from a distance.
        $system32 = "$env:SystemRoot\System32\"
        $thirdParty = @()
        try {
            $thirdParty = @($audiodg.Modules |
                Where-Object { $_.FileName -notlike "$system32*" } |
                ForEach-Object { $_.ModuleName })
        } catch { }
        Write-Host 'modules outside System32 (effects and vendor code live here):'
        if ($thirdParty.Count -eq 0) {
            Write-Host '  none  <- the engine is running no third-party effects at all'
        } else {
            $thirdParty | ForEach-Object { Write-Host "  $_" }
        }
        # APO-ish inbox modules, to show whether the effects pipeline runs here.
        $effectish = @($modules | Where-Object {
            $_ -match 'apo|Fx|Effect|audioeng|AudioSes|msaud' })
        Write-Host ("inbox effect modules : {0}" -f $(
            if ($effectish.Count) { $effectish -join ', ' } else { 'none' }))
    }
}

if ($Play) {
    Write-Section 'Playback probe'
    $hold = Join-Path $PackageRoot 'bin\soundka_endpoint_hold.exe'
    if (-not (Test-Path -LiteralPath $hold -PathType Leaf)) {
        Write-Host "Tool not found: $hold" -ForegroundColor Red
    } elseif ($boundActive.Count -eq 0) {
        Write-Host 'No bound endpoint is active, so there is nothing to play to.' -ForegroundColor Red
    } else {
        $target = $boundActive[0]
        # The endpoint id the audio APIs use is the container prefix plus the
        # GUID that names the registry key.
        $endpointId = '{0.0.0.00000000}.' + $target.Guid
        Write-Host ("Playing a tone to the bound endpoint for {0}s:" -f $PlaySeconds)
        Write-Host ("  {0}  {1}" -f $target.Guid, $target.Name)
        Write-Host '  (audible: this writes only to that endpoint and does not change the default device)'
        # Captured, not discarded: if the tool cannot open the endpoint it says
        # so and exits non-zero, and without that "no APO loaded" would be read
        # as Windows refusing the effect when in fact nothing ever played.
        $holdLog = Join-Path $env:TEMP 'soundka-endpoint-hold.log'
        $process = Start-Process -FilePath $hold `
            -ArgumentList @('--endpoint', $endpointId, '--seconds', "$PlaySeconds",
                '--tone', '--tone-level', '0.2') `
            -PassThru -WindowStyle Hidden `
            -RedirectStandardError $holdLog
        # Touching Handle caches it, without which ExitCode later reads as empty
        # on a Start-Process object -- and an empty code compared against 0 made
        # this report claim a tone had failed when it had played perfectly.
        $null = $process.Handle
        $sawApo = $false
        $sawAny = $false
        # Module enumeration fails outright without elevation. Counting that as
        # "the APO is absent" would be a false negative in the one place it is
        # most expensive: it reads exactly like Windows refusing to load us.
        $enumerated = $false
        for ($tick = 0; $tick -lt $PlaySeconds; ++$tick) {
            Start-Sleep -Seconds 1
            $engine = Get-Process audiodg -ErrorAction SilentlyContinue
            if (-not $engine) {
                Write-Host ("  t+{0,-3}s audiodg not running" -f ($tick + 1))
                continue
            }
            $sawAny = $true
            $names = $null
            try { $names = @($engine.Modules | ForEach-Object { $_.ModuleName }) } catch { }
            if ($null -eq $names -or $names.Count -eq 0) {
                Write-Host ("  t+{0,-3}s pid={1,-6} modules=<cannot read, needs elevation>" -f
                    ($tick + 1), $engine.Id)
                continue
            }
            $enumerated = $true
            $apo = $names -contains 'soundka_windows_apo.dll'
            $fse = $names -contains 'fse.dll'
            if ($apo) { $sawApo = $true }
            Write-Host ("  t+{0,-3}s pid={1,-6} modules={2,-4} apo={3,-6} fse={4}" -f
                ($tick + 1), $engine.Id, $names.Count, $apo, $fse)
        }
        # The tool plays for the requested duration and exits on its own, so
        # having exited by now is the normal outcome, not an early one. Only a
        # non-zero exit code means it failed to play.
        $playedCleanly = $true
        if (-not $process.HasExited) {
            $process.WaitForExit(5000) | Out-Null
        }
        if ($process.HasExited) {
            $exitCode = try { $process.ExitCode } catch { $null }
            # Unknown is not failure: only a code we actually read and that is
            # non-zero means the tone did not play.
            if ($null -ne $exitCode -and $exitCode -ne 0) {
                $playedCleanly = $false
                Write-Host ("  tone tool failed, exit code {0}" -f $exitCode) -ForegroundColor Red
            }
        } else {
            Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
        }
        if (Test-Path -LiteralPath $holdLog) {
            $raw = Get-Content -LiteralPath $holdLog -Raw -ErrorAction SilentlyContinue
            $holdErrors = if ($null -eq $raw) { '' } else { $raw.Trim() }
            if ($holdErrors) {
                $playedCleanly = $false
                Write-Host '  tone tool reported:' -ForegroundColor Red
                $holdErrors -split "`n" | ForEach-Object { Write-Host "    $_" -ForegroundColor Red }
            }
            Remove-Item -LiteralPath $holdLog -Force -ErrorAction SilentlyContinue
        }

        Write-Host ''
        $script:playbackSawApo = $sawApo
        if (-not $playedCleanly) {
            Write-Host 'The tone never played to this endpoint, so this probe says' -ForegroundColor Red
            Write-Host 'nothing about whether the APO would load. Fix the error above.' -ForegroundColor Red
        } elseif (-not $enumerated -and $sawAny) {
            Write-Host 'Could not read audiodg module list at any point, so this probe' -ForegroundColor Yellow
            Write-Host 'proves nothing either way. Re-run from an elevated shell.' -ForegroundColor Yellow
        } elseif ($sawApo) {
            Write-Host 'The APO loads on this endpoint.' -ForegroundColor Green
            Write-Host 'So the earlier "not loaded" reading means the audio you were'
            Write-Host 'playing went to a different device. Check which endpoint your'
            Write-Host 'player actually outputs to; binding does not reroute anything.'
        } elseif (-not $sawAny) {
            Write-Host 'audiodg never appeared: the tone did not reach the audio engine.' -ForegroundColor Red
        } else {
            Write-Host 'Windows refuses to load the APO on this endpoint even with' -ForegroundColor Red
            Write-Host 'audio flowing to it. The endpoint is bound and active, so this' -ForegroundColor Red
            Write-Host 'is not a routing problem -- send this whole report on.' -ForegroundColor Red
        }
    }
}

if ($SoloChain) {
    Write-Section 'Solo-chain probe'
    $renderClsid = '{631D9145-CACF-44D7-861B-5D82ADD8C84F}'
    $modeEffect = '{d04e05a6-594b-4fb6-a80d-01af5eed7d1d},14'
    $target = if ($boundActive.Count -gt 0) { $boundActive[0] } else { $null }
    if (-not $target) {
        Write-Host 'No active bound endpoint, nothing to probe.' -ForegroundColor Red
    } elseif (-not (Test-Path -LiteralPath (Join-Path $PackageRoot 'bin\soundka_endpoint_hold.exe'))) {
        Write-Host 'Tone tool missing.' -ForegroundColor Red
    } else {
        $fxPath = "$renderRoot\$($target.Guid)\FxProperties" -replace '^HKLM:\\', ''
        $rights = [System.Security.AccessControl.RegistryRights]::QueryValues -bor
            [System.Security.AccessControl.RegistryRights]::SetValue
        $hive = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', 'Registry64')
        $original = $null
        try {
            $key = $hive.OpenSubKey(
                $fxPath, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, $rights)
            if ($null -eq $key) { throw "cannot open $fxPath for writing" }
            try {
                $original = @($key.GetValue($modeEffect, @()))
                Write-Host ("original chain : {0}" -f ($original -join ' , '))
                if ($original.Count -le 1) {
                    Write-Host 'Chain already holds only one entry; nothing to isolate.'
                } else {
                    # Must be a typed string array: a plain Object[] is rejected
                    # by SetValue for MultiString.
                    $key.SetValue($modeEffect, [string[]]@($renderClsid),
                        [Microsoft.Win32.RegistryValueKind]::MultiString)
                    Write-Host ("probe chain    : {0}" -f $renderClsid)
                }
            } finally { $key.Dispose() }

            if ($original.Count -gt 1) {
                Write-Host 'restarting the audio stack...'
                Restart-Service -Name Audiosrv -Force -ErrorAction SilentlyContinue
                Start-Sleep -Seconds 3
                $tone = Start-Process -FilePath (Join-Path $PackageRoot 'bin\soundka_endpoint_hold.exe') `
                    -ArgumentList @('--endpoint', ('{0.0.0.00000000}.' + $target.Guid),
                        '--seconds', '10', '--tone', '--tone-level', '0.2') `
                    -PassThru -WindowStyle Hidden
                $solo = $false
                for ($tick = 0; $tick -lt 8; ++$tick) {
                    Start-Sleep -Seconds 1
                    $engine = Get-Process audiodg -ErrorAction SilentlyContinue
                    if (-not $engine) { continue }
                    $names = @()
                    try { $names = @($engine.Modules | ForEach-Object { $_.ModuleName }) } catch { }
                    if ($names -contains 'soundka_windows_apo.dll') { $solo = $true }
                    Write-Host ("  t+{0,-3}s modules={1,-4} apo={2}" -f
                        ($tick + 1), $names.Count, ($names -contains 'soundka_windows_apo.dll'))
                }
                if (-not $tone.HasExited) { Stop-Process -Id $tone.Id -Force -ErrorAction SilentlyContinue }
                Write-Host ''
                # Only meaningful against the full-chain result. Declaring "the
                # other effect blocks us" while the playback probe already saw
                # the module load with the whole chain present puts two opposite
                # conclusions in one report.
                if ($solo -and $script:playbackSawApo) {
                    Write-Host 'Loads either way: the chain contents are not a factor here.' -ForegroundColor Green
                } elseif ($solo -and $null -eq $script:playbackSawApo) {
                    Write-Host 'Loads with the chain to ourselves. Re-run with -Play to' -ForegroundColor Yellow
                    Write-Host 'compare against the full chain before concluding.' -ForegroundColor Yellow
                } elseif ($solo) {
                    Write-Host 'LOADS ALONE: the other effect in the chain is what blocks us.' -ForegroundColor Green
                } else {
                    Write-Host 'STILL NOT LOADED with the chain to ourselves, so the chain' -ForegroundColor Red
                    Write-Host 'contents are not the cause. Something else rejects the module.' -ForegroundColor Red
                }
            }
        } catch {
            Write-Host "Solo-chain probe failed: $($_.Exception.Message)" -ForegroundColor Red
        } finally {
            # Always put the endpoint back exactly as found.
            if ($null -ne $original -and $original.Count -gt 1) {
                $key = $hive.OpenSubKey(
                    $fxPath, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, $rights)
                if ($null -ne $key) {
                    try {
                        $key.SetValue($modeEffect, [string[]]$original,
                            [Microsoft.Win32.RegistryValueKind]::MultiString)
                    } finally { $key.Dispose() }
                }
                Restart-Service -Name Audiosrv -Force -ErrorAction SilentlyContinue
                Write-Host ("restored chain : {0}" -f ($original -join ' , '))
            }
        }
    }
}

Write-Section 'Verdict'
# Each check depends on the ones before it, so the first failure is the cause and
# everything after it is a consequence. Naming it here means the answer is not
# buried in the middle of a long report.
foreach ($check in $script:checks) {
    Write-Host ("  [{0}] {1}" -f $(if ($check.Ok) { 'ok  ' } else { 'FAIL' }), $check.Name) `
        -ForegroundColor $(if ($check.Ok) { 'Gray' } else { 'Red' })
}
$firstFailure = $script:checks | Where-Object { -not $_.Ok } | Select-Object -First 1
Write-Host ''
if ($null -eq $firstFailure) {
    Write-Host 'Everything checked here is in order.' -ForegroundColor Green
} else {
    Write-Host ("Cause: {0}" -f $firstFailure.Name) -ForegroundColor Red
    if ($firstFailure.Fix) {
        Write-Host ("Do this: {0}" -f $firstFailure.Fix) -ForegroundColor Red
    }
    Write-Host 'Checks after this one fail as a consequence; fix this one first.'
}
if (-not $Play) {
    Write-Host ''
    Write-Host 'Re-run with -Play to push a tone to the bound endpoint and settle'
    Write-Host 'whether the APO loads there at all.'
}
