install.ps1 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. # install.ps1 - CommModifyKit 驱动 + CommModifyKit.dll 安装脚本
  2. # 必须以管理员权限运行
  3. # 用法:以管理员身份打开 PowerShell,执行 .\install.ps1
  4. #Requires -Version 5.1
  5. #Requires -RunAsAdministrator
  6. $ErrorActionPreference = 'Continue'
  7. $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
  8. Write-Host '=== CommModifyKit 安装脚本 ===' -ForegroundColor Cyan
  9. Write-Host "脚本目录: $ScriptDir"
  10. Write-Host ""
  11. # ============================================================================
  12. # 1. 查找构建产物
  13. # 发布包场景:所有文件与脚本同目录
  14. # 开发场景:脚本在 installer\ 子目录,需回退到上级
  15. # ============================================================================
  16. $DriverSysCandidates = @(
  17. Join-Path $ScriptDir "CommModifyKit.sys"
  18. Join-Path $ScriptDir "..\build\Release\x64\CommModifyKit.sys"
  19. Join-Path $ScriptDir "..\build\Debug\x64\CommModifyKit.sys"
  20. Join-Path $ScriptDir "..\driver\build\Release\x64\CommModifyKit.sys"
  21. Join-Path $ScriptDir "..\driver\build\Debug\x64\CommModifyKit.sys"
  22. )
  23. $DriverInfCandidates = @(
  24. Join-Path $ScriptDir "CommModifyKit.inf"
  25. Join-Path $ScriptDir "..\driver\CommModifyKit.inf"
  26. Join-Path $ScriptDir "..\build\Release\x64\CommModifyKit.inf"
  27. Join-Path $ScriptDir "..\build\Debug\x64\CommModifyKit.inf"
  28. Join-Path $ScriptDir "..\driver\build\Release\x64\CommModifyKit.inf"
  29. Join-Path $ScriptDir "..\driver\build\Debug\x64\CommModifyKit.inf"
  30. )
  31. $DllCandidates = @(
  32. Join-Path $ScriptDir "CommModifyKit.dll"
  33. Join-Path $ScriptDir "..\build\Release\Win32\CommModifyKit.dll"
  34. Join-Path $ScriptDir "..\build\Debug\Win32\CommModifyKit.dll"
  35. Join-Path $ScriptDir "..\build\Release\x64\CommModifyKit.dll"
  36. Join-Path $ScriptDir "..\build\Debug\x64\CommModifyKit.dll"
  37. )
  38. # 检查 DLL 架构(32位服务必须使用32位DLL)
  39. function Test-PeArch {
  40. param([string]$Path)
  41. if (-not (Test-Path $Path)) { return 0 }
  42. # 只读前 512 字节(PE 头足够)
  43. try {
  44. $fs = [System.IO.File]::OpenRead($Path)
  45. } catch {
  46. return 0 # 文件被锁定或无法访问
  47. }
  48. try {
  49. $bytes = New-Object byte[] 512
  50. $read = $fs.Read($bytes, 0, 512)
  51. if ($read -lt 64) { return 0 }
  52. $peOffset = [System.BitConverter]::ToInt32($bytes, 60)
  53. if ($peOffset -eq 0 -or $peOffset + 6 -gt $read) { return 0 }
  54. $machine = [System.BitConverter]::ToUInt16($bytes, $peOffset + 4)
  55. return $machine # 0x14c=i386(32bit), 0x8664=x64
  56. } finally {
  57. $fs.Close()
  58. }
  59. }
  60. $DriverSys = $DriverSysCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
  61. $DriverInf = $DriverInfCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
  62. # 查找 32 位 DLL(跳过 64 位)
  63. $DllPath = $null
  64. foreach ($candidate in $DllCandidates) {
  65. if (Test-Path $candidate) {
  66. $arch = Test-PeArch $candidate
  67. if ($arch -eq 0x14c) {
  68. $DllPath = $candidate
  69. break
  70. }
  71. }
  72. }
  73. if (-not $DriverSys) {
  74. Write-Error "未找到 CommModifyKit.sys,请先用 WDK + VS2019 编译 driver 项目"
  75. exit 1
  76. }
  77. if (-not $DriverInf) {
  78. Write-Error "未找到 CommModifyKit.inf"
  79. exit 1
  80. }
  81. # 校验驱动 .sys 必须为 64 位(x64),32 位驱动无法在 64 位内核加载
  82. $driverArch = Test-PeArch $DriverSys
  83. if ($driverArch -ne 0x8664) {
  84. $archName = switch ($driverArch) {
  85. 0x14c { 'i386 (32-bit)' }
  86. 0 { 'unknown/unreadable' }
  87. default { "0x$('{0:X4}' -f $driverArch)" }
  88. }
  89. Write-Error "CommModifyKit.sys 架构为 $archName,必须为 x64 (64-bit)。请检查 driver 项目平台配置。"
  90. exit 1
  91. }
  92. Write-Host "驱动 .sys: $DriverSys (x64)" -ForegroundColor Green
  93. Write-Host "驱动 .inf: $DriverInf" -ForegroundColor Green
  94. if ($DllPath) {
  95. Write-Host "用户态 DLL: $DllPath (32-bit)" -ForegroundColor Green
  96. } else {
  97. Write-Warning "未找到 32 位 CommModifyKit.dll,请编译 Win32 平台"
  98. }
  99. Write-Host ""
  100. # ============================================================================
  101. # 2. 拷贝驱动文件到 System32\drivers
  102. # ============================================================================
  103. # 检测 32 位 PowerShell(WoW64 会将 System32 重定向到 SysWOW64)
  104. $Is32bitPS = ([System.IntPtr]::Size -eq 4)
  105. # 文件操作路径(32 位 PowerShell 需用 Sysnative 绕过 WoW64 重定向)
  106. $FileDriverDir = if ($Is32bitPS) {
  107. Write-Warning "检测到 32 位 PowerShell,文件操作使用 Sysnative 路径"
  108. Join-Path $env:windir "Sysnative\drivers"
  109. } else {
  110. Join-Path $env:windir "System32\drivers"
  111. }
  112. $TargetDriverSys = Join-Path $FileDriverDir "CommModifyKit.sys"
  113. $TargetDriverInf = Join-Path $FileDriverDir "CommModifyKit.inf"
  114. # 服务 binPath 路径(始终 System32\drivers,SCM/内核为 64 位,不识别 Sysnative)
  115. $ServiceBinPath = Join-Path $env:windir "System32\drivers\CommModifyKit.sys"
  116. # 先停止已存在的服务,避免 .sys 文件被锁定
  117. $existingSvc = Get-Service -Name 'CommModifyKit' -ErrorAction SilentlyContinue
  118. if ($existingSvc -and $existingSvc.Status -eq 'Running') {
  119. Write-Host "停止现有驱动服务..."
  120. sc.exe stop CommModifyKit 2>&1 | Out-Null
  121. Start-Sleep 2
  122. # 检查是否真的停止了
  123. $svcAfterStop = Get-Service -Name 'CommModifyKit' -ErrorAction SilentlyContinue
  124. if ($svcAfterStop -and $svcAfterStop.Status -eq 'Running') {
  125. Write-Warning "驱动服务无法停止(可能被设备栈引用),.sys 文件可能被锁定"
  126. Write-Host "请先运行 uninstall.ps1 移除 UpperFilters,重启后再安装" -ForegroundColor Yellow
  127. }
  128. }
  129. Write-Host "拷贝驱动文件到 $FileDriverDir ..."
  130. $copyOk = $true
  131. try {
  132. Copy-Item -Force $DriverSys $TargetDriverSys -ErrorAction Stop
  133. Copy-Item -Force $DriverInf $TargetDriverInf -ErrorAction Stop
  134. Write-Host " 完成" -ForegroundColor Green
  135. } catch {
  136. Write-Warning "拷贝失败: $_"
  137. Write-Warning ".sys 文件被锁定,请先卸载驱动并重启"
  138. $copyOk = $false
  139. }
  140. if (-not $copyOk) {
  141. # 仍然继续尝试创建服务等步骤(可能旧 .sys 仍可用)
  142. Write-Host "继续安装(使用现有 .sys 文件)..." -ForegroundColor Yellow
  143. }
  144. # ============================================================================
  145. # 2.5 注册驱动包到驱动存储区(关键步骤!)
  146. # ============================================================================
  147. # 先检查测试签名状态,pnputil 要求驱动有签名
  148. # bcdedit 输出 "testsigning Yes" 或 "test signing Yes"(不同本地化版本)
  149. $TestSigningEnabled = (bcdedit /enum | Select-String 'test\s*signing\s+Yes' -Quiet)
  150. if (-not $TestSigningEnabled) {
  151. Write-Host ""
  152. Write-Warning "测试签名未启用,驱动无法加载"
  153. Write-Host "正在启用测试签名..." -ForegroundColor Yellow
  154. bcdedit /set testsigning on | Out-Null
  155. Write-Host "测试签名已启用" -ForegroundColor Green
  156. Write-Warning "需要重启计算机才能生效!"
  157. Write-Host ""
  158. Write-Host "请重启后再次运行此脚本" -ForegroundColor Yellow
  159. exit 0
  160. }
  161. # 对驱动进行测试签名(如果未签名)
  162. Write-Host "检查驱动签名状态..."
  163. $sigState = (Get-AuthenticodeSignature $TargetDriverSys).Status
  164. if ($sigState -ne 'Valid') {
  165. Write-Host "驱动未签名,使用测试证书签名..." -ForegroundColor Yellow
  166. # 创建或获取测试证书(用 Subject 匹配,检查未过期)
  167. $testCert = Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" -and $_.NotAfter -gt (Get-Date) } | Select-Object -First 1
  168. if (-not $testCert) {
  169. # 清理过期的旧证书
  170. Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" } | Remove-Item -Force -ErrorAction SilentlyContinue
  171. Write-Host "创建测试证书(有效期 10 年)..."
  172. $testCert = New-SelfSignedCertificate -Subject "CN=CommModifyKitTest" -CertStoreLocation "Cert:\LocalMachine\My" -Type CodeSigningCert -KeyExportPolicy Exportable -KeyLength 2048 -KeyAlgorithm RSA -KeyUsage DigitalSignature -NotAfter (Get-Date).AddYears(10)
  173. }
  174. # 将证书添加到 Trusted Root(测试签名模式下必须)
  175. $rootCert = Get-ChildItem -Path Cert:\LocalMachine\Root -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" } | Select-Object -First 1
  176. if (-not $rootCert) {
  177. Write-Host "添加证书到受信任的根证书颁发机构..."
  178. $certPath = Join-Path $env:TEMP "CommModifyKitTest.cer"
  179. Export-Certificate -Cert $testCert -FilePath $certPath -Force | Out-Null
  180. # 尝试方法 1: Import-Certificate
  181. $importSuccess = $false
  182. try {
  183. Import-Certificate -FilePath $certPath -CertStoreLocation "Cert:\LocalMachine\Root" -ErrorAction Stop | Out-Null
  184. $importSuccess = $true
  185. Write-Host " 完成(Import-Certificate)" -ForegroundColor Green
  186. } catch {
  187. Write-Warning "Import-Certificate 失败: $($_.Exception.Message)"
  188. # 尝试方法 2: certutil.exe
  189. Write-Host " 尝试使用 certutil..."
  190. $certutilResult = & certutil.exe -addstore -f "Root" $certPath 2>&1
  191. if ($LASTEXITCODE -eq 0) {
  192. $importSuccess = $true
  193. Write-Host " 完成(certutil)" -ForegroundColor Green
  194. } else {
  195. Write-Warning "certutil 导入失败: $certutilResult"
  196. }
  197. }
  198. # 清理临时证书文件(仅在成功导入后)
  199. if ($importSuccess) {
  200. Remove-Item $certPath -Force -ErrorAction SilentlyContinue
  201. } else {
  202. Write-Warning "无法将测试证书添加到受信任的根证书颁发机构"
  203. Write-Host "证书文件已保留: $certPath" -ForegroundColor Yellow
  204. Write-Host "请手动导入证书:" -ForegroundColor Yellow
  205. Write-Host " 1. 双击 $certPath" -ForegroundColor Yellow
  206. Write-Host " 2. 选择 '安装证书' → '本地计算机' → '将所有的证书都放入下列存储' → '受信任的根证书颁发机构'" -ForegroundColor Yellow
  207. }
  208. }
  209. # 签名驱动:先尝试带时间戳,失败则不带时间戳,再失败用 signtool
  210. Write-Host "签名驱动..."
  211. $signOk = $false
  212. # 尝试 1: Set-AuthenticodeSignature 带时间戳
  213. $signResult = Set-AuthenticodeSignature -FilePath $TargetDriverSys -Certificate $testCert -TimeStampServer "http://timestamp.digicert.com" -HashAlgorithm SHA256
  214. if ($signResult.Status -eq 'Valid' -or $signResult.Status -eq 'UnknownError') {
  215. Write-Host " 完成(带时间戳)" -ForegroundColor Green
  216. $signOk = $true
  217. }
  218. # 尝试 2: Set-AuthenticodeSignature 不带时间戳
  219. if (-not $signOk) {
  220. Write-Host " 时间戳服务器不可达,尝试不带时间戳..."
  221. $signResult = Set-AuthenticodeSignature -FilePath $TargetDriverSys -Certificate $testCert -HashAlgorithm SHA256
  222. if ($signResult.Status -eq 'Valid' -or $signResult.Status -eq 'UnknownError') {
  223. Write-Host " 完成(无时间戳)" -ForegroundColor Green
  224. $signOk = $true
  225. }
  226. }
  227. # 尝试 3: signtool(如果安装了 Windows SDK)
  228. if (-not $signOk) {
  229. $signtoolPaths = @(
  230. "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe",
  231. "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.22000.0\x64\signtool.exe",
  232. "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe"
  233. )
  234. $signtool = $signtoolPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
  235. # 通配符后备搜索
  236. if (-not $signtool) {
  237. $signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName
  238. }
  239. if ($signtool) {
  240. Write-Host " 尝试 signtool..."
  241. # 用 /sha1 指纹精确指定证书,避免 /n 子串匹配选到过期的同名证书
  242. & $signtool sign /s MY /sha1 "$($testCert.Thumbprint)" /fd SHA256 $TargetDriverSys 2>&1 | Out-Null
  243. if ($LASTEXITCODE -eq 0) {
  244. Write-Host " 完成(signtool)" -ForegroundColor Green
  245. $signOk = $true
  246. }
  247. }
  248. }
  249. if (-not $signOk) {
  250. Write-Warning "签名失败: $($signResult.Status) - $($signResult.StatusMessage)"
  251. Write-Warning "驱动可能无法加载,请确认测试签名已启用并重启"
  252. }
  253. } else {
  254. Write-Host "驱动已签名" -ForegroundColor Green
  255. }
  256. # pnputil /add-driver 需要 .cat 目录文件,开发阶段通常没有
  257. # 失败后通过 sc.exe create 直接加载驱动(测试签名模式)
  258. Write-Host "尝试注册驱动包到驱动存储区..."
  259. try {
  260. $result = pnputil /add-driver $DriverInf /install 2>&1
  261. if ($LASTEXITCODE -eq 0) {
  262. Write-Host " 完成" -ForegroundColor Green
  263. if ($result -match '(oem\d+\.inf)') {
  264. Write-Host " 驱动包: $($matches[1])" -ForegroundColor Green
  265. }
  266. } else {
  267. Write-Host " pnputil 注册失败(无 .cat 目录文件,属正常),将通过 sc.exe 直接加载" -ForegroundColor Yellow
  268. }
  269. } catch {
  270. Write-Host " pnputil 不可用,将通过 sc.exe 直接加载" -ForegroundColor Yellow
  271. }
  272. # ============================================================================
  273. # 3. 创建并启动驱动服务
  274. # ============================================================================
  275. $ServiceName = 'CommModifyKit'
  276. # 先删除旧服务(可能配置不一致)
  277. $ExistingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
  278. if ($ExistingService) {
  279. if ($ExistingService.Status -eq 'Running') {
  280. sc.exe stop $ServiceName 2>&1 | Out-Null
  281. Start-Sleep 2
  282. }
  283. sc.exe delete $ServiceName 2>&1 | Out-Null
  284. Start-Sleep 1
  285. }
  286. Write-Host "创建服务 $ServiceName ..."
  287. sc.exe create $ServiceName binPath= $ServiceBinPath type= kernel start= demand error= normal | Out-Null
  288. if ($LASTEXITCODE -eq 0) {
  289. Write-Host " 完成" -ForegroundColor Green
  290. Write-Host "启动服务..."
  291. sc.exe start $ServiceName 2>&1 | Out-Null
  292. if ($LASTEXITCODE -eq 0) {
  293. Write-Host " 完成" -ForegroundColor Green
  294. } else {
  295. Write-Warning " 启动失败(可能需要重启后由 PnP 自动加载)"
  296. }
  297. } else {
  298. Write-Warning " 创建服务失败"
  299. }
  300. # ============================================================================
  301. # 3.5 注册为 Ports 类 UpperFilters
  302. # 注意:仅当驱动服务确认运行中时才注册 UpperFilters,否则会导致 PnP 尝试
  303. # attach 一个不存在的过滤器,使所有 Ports 类设备启动失败(mode 显示
  304. # "访问设备失败")。
  305. # ============================================================================
  306. # 先确认服务确实在运行(避免驱动加载失败时污染 UpperFilters)
  307. $svcState = (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue).Status
  308. if ($svcState -ne 'Running') {
  309. Write-Warning "驱动服务未运行(状态: $svcState),跳过 UpperFilters 注册"
  310. Write-Host "请检查驱动签名/测试签名模式,重启后再次运行此脚本" -ForegroundColor Yellow
  311. Write-Host "未注册 UpperFilters,串口不会被过滤,但驱动也未生效" -ForegroundColor Yellow
  312. } else {
  313. $PortsClassGuid = "{4D36E978-E325-11CE-BFC1-08002BE10318}"
  314. $ClassKey = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\$PortsClassGuid"
  315. # 读取现有 UpperFilters,过滤掉空字符串(历史遗留的空字符串会导致
  316. # PnP 加载空名称过滤器,使所有 Ports 类设备启动失败)
  317. $currentFilters = @()
  318. $existingFilters = (Get-ItemProperty -Path $ClassKey -Name "UpperFilters" -ErrorAction SilentlyContinue)
  319. if ($existingFilters -and $existingFilters.UpperFilters) {
  320. $currentFilters = @($existingFilters.UpperFilters | Where-Object { $_ -and $_.Trim() -ne '' })
  321. }
  322. if ($currentFilters -notcontains "CommModifyKit") {
  323. $newFilters = @($currentFilters) + "CommModifyKit"
  324. Set-ItemProperty -Path $ClassKey -Name "UpperFilters" -Value $newFilters -Type MultiString
  325. Write-Host "已将 CommModifyKit 添加到 Ports 类 UpperFilters" -ForegroundColor Green
  326. # 设置 Ports 类的 Security 值
  327. # WDF filter device 不支持 WdfDeviceInitAssignSDDLString(返回 STATUS_INVALID_SECURITY_DESCR)
  328. # Filter device 会继承类键的 Security 值。如果不设置,使用 WDF 默认 SDDL
  329. # (仅 System+Admins),导致非 elevated 进程无法打开 \\.\COMx
  330. try {
  331. $sd = New-Object System.Security.AccessControl.CommonSecurityDescriptor(
  332. $false, $false,
  333. "D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;AU)(A;;GRGW;;;WD)")
  334. $binary = New-Object byte[] $sd.BinaryLength
  335. $sd.GetBinaryForm($binary, 0)
  336. Set-ItemProperty -Path $ClassKey -Name "Security" -Value $binary -Type Binary
  337. Write-Host "已设置 Ports 类 Security 值(允许 Authenticated Users 读写)" -ForegroundColor Green
  338. } catch {
  339. Write-Warning "设置 Ports 类 Security 值失败: $($_.Exception.Message)"
  340. }
  341. # 重新枚举设备使 UpperFilter 生效
  342. pnputil /scan-devices | Out-Null
  343. Write-Host "已触发设备重新枚举" -ForegroundColor Green
  344. } else {
  345. Write-Host "CommModifyKit 已在 UpperFilters 中" -ForegroundColor Green
  346. }
  347. }
  348. # ============================================================================
  349. # 4. 拷贝 DLL 到 CommModifyService 输出目录(如果检测到)
  350. # ============================================================================
  351. if ($DllPath) {
  352. $ServiceExeCandidates = @(
  353. Join-Path $ScriptDir "CommModifyService.exe"
  354. Join-Path $ScriptDir "..\CommModifyService\x64\Release\CommModifyService.exe"
  355. Join-Path $ScriptDir "..\CommModifyService\Win32\Release\CommModifyService.exe"
  356. Join-Path $ScriptDir "..\CommModifyService\x64\Debug\CommModifyService.exe"
  357. Join-Path $ScriptDir "..\CommModifyService\Win32\Debug\CommModifyService.exe"
  358. )
  359. $ServiceExe = $ServiceExeCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
  360. if ($ServiceExe) {
  361. $ServiceDir = Split-Path -Parent $ServiceExe
  362. $TargetDll = Join-Path $ServiceDir "CommModifyKit.dll"
  363. $srcReal = (Resolve-Path $DllPath -ErrorAction SilentlyContinue).Path
  364. $dstReal = if (Test-Path $TargetDll) { (Resolve-Path $TargetDll).Path } else { $null }
  365. if ($srcReal -and $dstReal -and $srcReal -eq $dstReal) {
  366. Write-Host "DLL 已在目标目录,跳过拷贝" -ForegroundColor Green
  367. } else {
  368. Write-Host "拷贝 DLL 到 $TargetDll ..."
  369. Copy-Item -Force $DllPath $TargetDll
  370. Write-Host " 完成" -ForegroundColor Green
  371. }
  372. } else {
  373. Write-Warning "未检测到 CommModifyService.exe,请手动拷贝 CommModifyKit.dll 到其目录"
  374. }
  375. }
  376. Write-Host ""
  377. Write-Host '=== 安装完成 ===' -ForegroundColor Cyan
  378. Write-Host ""
  379. Write-Host "验证步骤:" -ForegroundColor Yellow
  380. Write-Host " 1. 确认驱动服务: sc.exe query CommModifyKit"
  381. Write-Host " 2. 确认串口可用: mode"
  382. Write-Host " 3. 确认 UpperFilters: reg query ""HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E978-E325-11CE-BFC1-08002BE10318}"" /v UpperFilters"
  383. Write-Host ""
  384. Write-Host "如果串口显示 '访问设备失败',请重启计算机使 UpperFilters 生效" -ForegroundColor Yellow
  385. Write-Host " 重启命令: shutdown /r /t 0" -ForegroundColor Yellow