Ariver
2026-07-03 1f725abec29ad465f3c73c28c360818139761212
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
param(
    [switch]$CollectOnly,
    [switch]$NoLaunch,
    [int]$WaitSeconds = 45
)
 
$ErrorActionPreference = "Stop"
 
function Write-Step {
    param([string]$Message)
    Write-Host ""
    Write-Host "== $Message ==" -ForegroundColor Cyan
}
 
function Add-Result {
    param(
        [System.Collections.Generic.List[string]]$Results,
        [string]$Name,
        [bool]$Passed,
        [string]$Detail = ""
    )
    $state = if ($Passed) { "PASS" } else { "NEEDS_EVIDENCE" }
    $line = if ($Detail) { "$state`t$Name`t$Detail" } else { "$state`t$Name" }
    $Results.Add($line) | Out-Null
    $color = if ($Passed) { "Green" } else { "Yellow" }
    Write-Host "[$state] $Name $Detail" -ForegroundColor $color
}
 
$PackageDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ExePath = Join-Path $PackageDir "PrivateVoice Dictation.exe"
$RequiredFiles = @(
    "PrivateVoice Dictation.exe",
    "onnxruntime.dll",
    "sherpa-onnx-c-api.dll",
    "sherpa-onnx-cxx-api.dll"
)
$AppDataDir = Join-Path $env:APPDATA "PrivateVoice Input"
$LogPath = Join-Path $AppDataDir "app.log"
$Timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$EvidenceDir = Join-Path $PackageDir "qa-evidence-$Timestamp"
$TargetPath = Join-Path $EvidenceDir "qa-target.txt"
$Results = [System.Collections.Generic.List[string]]::new()
 
New-Item -ItemType Directory -Path $EvidenceDir -Force | Out-Null
 
Write-Step "PrivateVoice Windows preview QA"
"GeneratedAt: $(Get-Date -Format o)" | Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "qa-summary.txt")
"PackageDir: $PackageDir" | Add-Content -Encoding UTF8 (Join-Path $EvidenceDir "qa-summary.txt")
"AppDataDir: $AppDataDir" | Add-Content -Encoding UTF8 (Join-Path $EvidenceDir "qa-summary.txt")
 
Write-Step "Checking package files"
foreach ($file in $RequiredFiles) {
    $path = Join-Path $PackageDir $file
    Add-Result $Results "file exists: $file" (Test-Path $path) $path
}
 
Write-Step "Collecting system information"
try {
    Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, OsHardwareAbstractionLayer, CsSystemType, CsProcessors, CsTotalPhysicalMemory |
        Format-List | Out-String | Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "system-info.txt")
    Add-Result $Results "system info collected" $true
} catch {
    Add-Result $Results "system info collected" $false $_.Exception.Message
}
 
Write-Step "Collecting Windows runtime preflight"
try {
    $webview2Entries = @()
    $uninstallRoots = @(
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
        "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
        "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
    )
    foreach ($root in $uninstallRoots) {
        $webview2Entries += Get-ItemProperty $root -ErrorAction SilentlyContinue |
            Where-Object { $_.DisplayName -match "WebView2" } |
            Select-Object DisplayName, DisplayVersion, Publisher, InstallLocation
    }
    if ($webview2Entries.Count -gt 0) {
        $webview2Entries | Format-List | Out-String | Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "webview2-runtime.txt")
    } else {
        "No WebView2 Runtime entry found in standard uninstall registry paths. App startup logs remain authoritative." |
            Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "webview2-runtime.txt")
    }
    Add-Result $Results "WebView2 registry inventory collected" $true "entries=$($webview2Entries.Count)"
} catch {
    Add-Result $Results "WebView2 registry inventory collected" $false $_.Exception.Message
}
 
try {
    if (Get-Command Get-PnpDevice -ErrorAction SilentlyContinue) {
        Get-PnpDevice -Class AudioEndpoint -ErrorAction SilentlyContinue |
            Select-Object Status, Class, FriendlyName, InstanceId |
            Format-Table -AutoSize | Out-String |
            Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "audio-endpoints.txt")
    } else {
        Get-CimInstance Win32_SoundDevice -ErrorAction SilentlyContinue |
            Select-Object Status, Name, Manufacturer, DeviceID |
            Format-Table -AutoSize | Out-String |
            Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "audio-endpoints.txt")
    }
    Add-Result $Results "audio endpoint inventory collected" $true
} catch {
    Add-Result $Results "audio endpoint inventory collected" $false $_.Exception.Message
}
 
if (-not $CollectOnly -and -not $NoLaunch) {
    Write-Step "Launching app"
    if (-not (Test-Path $ExePath)) {
        throw "Missing executable: $ExePath"
    }
    Start-Process -FilePath $ExePath -WorkingDirectory $PackageDir | Out-Null
}
 
Write-Step "Waiting for app log"
$deadline = (Get-Date).AddSeconds($WaitSeconds)
while ((Get-Date) -lt $deadline -and -not (Test-Path $LogPath)) {
    Start-Sleep -Milliseconds 500
}
Add-Result $Results "app log exists" (Test-Path $LogPath) $LogPath
 
Write-Step "Checking process"
$processes = Get-Process | Where-Object { $_.ProcessName -like "PrivateVoice*" -or $_.Path -eq $ExePath } | Select-Object Id, ProcessName, Path
$processes | Format-Table -AutoSize | Out-String | Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "processes.txt")
Add-Result $Results "PrivateVoice process visible" (($processes | Measure-Object).Count -gt 0)
 
if (-not $CollectOnly) {
    Write-Step "Manual runtime check"
    @(
        "PrivateVoice Windows Preview QA Target",
        "Click after the marker below, dictate one short Chinese sentence, then press Ctrl+S in Notepad.",
        "DICTATION_RESULT:"
    ) | Set-Content -Encoding UTF8 $TargetPath
    try {
        Start-Process -FilePath "notepad.exe" -ArgumentList "`"$TargetPath`"" | Out-Null
        Add-Result $Results "Notepad target launched" $true $TargetPath
    } catch {
        Add-Result $Results "Notepad target launched" $false $_.Exception.Message
    }
    Write-Host "1. Keep this PowerShell window open." -ForegroundColor White
    Write-Host "2. In PrivateVoice, make sure the SenseVoice model is installed and the engine is ready." -ForegroundColor White
    Write-Host "3. Use the Notepad window opened by this script as the target input field." -ForegroundColor White
    Write-Host "4. Click after DICTATION_RESULT:, use the configured hotkey, and speak one short Chinese sentence." -ForegroundColor White
    Write-Host "5. Confirm the recognized text appears in Notepad, press Ctrl+S in Notepad, then return here." -ForegroundColor White
    Read-Host "Press Enter after you save the Notepad target file"
}
 
if (Test-Path $TargetPath) {
    Copy-Item $TargetPath (Join-Path $EvidenceDir "qa-target-captured.txt") -Force
    $targetText = Get-Content $TargetPath -Raw -ErrorAction SilentlyContinue
    $marker = "DICTATION_RESULT:"
    $markerIndex = $targetText.IndexOf($marker)
    $dictationText = ""
    if ($markerIndex -ge 0) {
        $dictationText = $targetText.Substring($markerIndex + $marker.Length).Trim()
    }
    $dictationText | Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "qa-target-dictation-text.txt")
    Add-Result $Results "target file saved with dictation text" ($dictationText.Length -gt 0) "chars=$($dictationText.Length)"
}
 
Write-Step "Collecting logs"
if (Test-Path $LogPath) {
    Copy-Item $LogPath (Join-Path $EvidenceDir "app.log") -Force
    $logText = Get-Content $LogPath -Raw -ErrorAction SilentlyContinue
    Add-Result $Results "startup log found" ($logText -match "PrivateVoice Dictation starting")
    Add-Result $Results "engine ready log found" ($logText -match "ASR engine ready|Engine initialized")
    Add-Result $Results "recording log found" ($logText -match "Recording started")
    Add-Result $Results "recognized text log found" ($logText -match "Recognized:")
    Add-Result $Results "paste success log found" ($logText -match "PERF paste_done.*result=success|PERF fallback_type_done.*result=success")
}
 
$ConfigPath = Join-Path $AppDataDir "config.json"
if (Test-Path $ConfigPath) {
    Copy-Item $ConfigPath (Join-Path $EvidenceDir "config.json") -Force
}
 
$Results | Set-Content -Encoding UTF8 (Join-Path $EvidenceDir "qa-results.tsv")
$Results | Add-Content -Encoding UTF8 (Join-Path $EvidenceDir "qa-summary.txt")
 
Write-Step "Evidence package"
$ZipPath = "$EvidenceDir.zip"
if (Test-Path $ZipPath) {
    Remove-Item $ZipPath -Force
}
Compress-Archive -Path (Join-Path $EvidenceDir "*") -DestinationPath $ZipPath
Write-Host "Evidence directory: $EvidenceDir" -ForegroundColor Green
Write-Host "Evidence zip:       $ZipPath" -ForegroundColor Green
 
Write-Host ""
Write-Host "Send back the evidence zip if any check is NEEDS_EVIDENCE or the app does not behave correctly." -ForegroundColor Yellow