# install.ps1 - CommModifyKit 驱动 + CommModifyKit.dll 安装脚本 # 必须以管理员权限运行 # 用法:以管理员身份打开 PowerShell,执行 .\install.ps1 #Requires -Version 5.1 #Requires -RunAsAdministrator $ErrorActionPreference = 'Continue' $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path Write-Host '=== CommModifyKit 安装脚本 ===' -ForegroundColor Cyan Write-Host "脚本目录: $ScriptDir" Write-Host "" # ============================================================================ # 1. 查找构建产物 # 发布包场景:所有文件与脚本同目录 # 开发场景:脚本在 installer\ 子目录,需回退到上级 # ============================================================================ $DriverSysCandidates = @( Join-Path $ScriptDir "CommModifyKit.sys" Join-Path $ScriptDir "..\build\Release\x64\CommModifyKit.sys" Join-Path $ScriptDir "..\build\Debug\x64\CommModifyKit.sys" Join-Path $ScriptDir "..\driver\build\Release\x64\CommModifyKit.sys" Join-Path $ScriptDir "..\driver\build\Debug\x64\CommModifyKit.sys" ) $DriverInfCandidates = @( Join-Path $ScriptDir "CommModifyKit.inf" Join-Path $ScriptDir "..\driver\CommModifyKit.inf" Join-Path $ScriptDir "..\build\Release\x64\CommModifyKit.inf" Join-Path $ScriptDir "..\build\Debug\x64\CommModifyKit.inf" Join-Path $ScriptDir "..\driver\build\Release\x64\CommModifyKit.inf" Join-Path $ScriptDir "..\driver\build\Debug\x64\CommModifyKit.inf" ) $DllCandidates = @( Join-Path $ScriptDir "CommModifyKit.dll" Join-Path $ScriptDir "..\build\Release\Win32\CommModifyKit.dll" Join-Path $ScriptDir "..\build\Debug\Win32\CommModifyKit.dll" Join-Path $ScriptDir "..\build\Release\x64\CommModifyKit.dll" Join-Path $ScriptDir "..\build\Debug\x64\CommModifyKit.dll" ) # 检查 DLL 架构(32位服务必须使用32位DLL) function Test-PeArch { param([string]$Path) if (-not (Test-Path $Path)) { return 0 } # 只读前 512 字节(PE 头足够) try { $fs = [System.IO.File]::OpenRead($Path) } catch { return 0 # 文件被锁定或无法访问 } try { $bytes = New-Object byte[] 512 $read = $fs.Read($bytes, 0, 512) if ($read -lt 64) { return 0 } $peOffset = [System.BitConverter]::ToInt32($bytes, 60) if ($peOffset -eq 0 -or $peOffset + 6 -gt $read) { return 0 } $machine = [System.BitConverter]::ToUInt16($bytes, $peOffset + 4) return $machine # 0x14c=i386(32bit), 0x8664=x64 } finally { $fs.Close() } } $DriverSys = $DriverSysCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 $DriverInf = $DriverInfCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 # 查找 32 位 DLL(跳过 64 位) $DllPath = $null foreach ($candidate in $DllCandidates) { if (Test-Path $candidate) { $arch = Test-PeArch $candidate if ($arch -eq 0x14c) { $DllPath = $candidate break } } } if (-not $DriverSys) { Write-Error "未找到 CommModifyKit.sys,请先用 WDK + VS2019 编译 driver 项目" exit 1 } if (-not $DriverInf) { Write-Error "未找到 CommModifyKit.inf" exit 1 } # 校验驱动 .sys 必须为 64 位(x64),32 位驱动无法在 64 位内核加载 $driverArch = Test-PeArch $DriverSys if ($driverArch -ne 0x8664) { $archName = switch ($driverArch) { 0x14c { 'i386 (32-bit)' } 0 { 'unknown/unreadable' } default { "0x$('{0:X4}' -f $driverArch)" } } Write-Error "CommModifyKit.sys 架构为 $archName,必须为 x64 (64-bit)。请检查 driver 项目平台配置。" exit 1 } Write-Host "驱动 .sys: $DriverSys (x64)" -ForegroundColor Green Write-Host "驱动 .inf: $DriverInf" -ForegroundColor Green if ($DllPath) { Write-Host "用户态 DLL: $DllPath (32-bit)" -ForegroundColor Green } else { Write-Warning "未找到 32 位 CommModifyKit.dll,请编译 Win32 平台" } Write-Host "" # ============================================================================ # 2. 拷贝驱动文件到 System32\drivers # ============================================================================ # 检测 32 位 PowerShell(WoW64 会将 System32 重定向到 SysWOW64) $Is32bitPS = ([System.IntPtr]::Size -eq 4) # 文件操作路径(32 位 PowerShell 需用 Sysnative 绕过 WoW64 重定向) $FileDriverDir = if ($Is32bitPS) { Write-Warning "检测到 32 位 PowerShell,文件操作使用 Sysnative 路径" Join-Path $env:windir "Sysnative\drivers" } else { Join-Path $env:windir "System32\drivers" } $TargetDriverSys = Join-Path $FileDriverDir "CommModifyKit.sys" $TargetDriverInf = Join-Path $FileDriverDir "CommModifyKit.inf" # 服务 binPath 路径(始终 System32\drivers,SCM/内核为 64 位,不识别 Sysnative) $ServiceBinPath = Join-Path $env:windir "System32\drivers\CommModifyKit.sys" # 先停止已存在的服务,避免 .sys 文件被锁定 $existingSvc = Get-Service -Name 'CommModifyKit' -ErrorAction SilentlyContinue if ($existingSvc -and $existingSvc.Status -eq 'Running') { Write-Host "停止现有驱动服务..." sc.exe stop CommModifyKit 2>&1 | Out-Null Start-Sleep 2 # 检查是否真的停止了 $svcAfterStop = Get-Service -Name 'CommModifyKit' -ErrorAction SilentlyContinue if ($svcAfterStop -and $svcAfterStop.Status -eq 'Running') { Write-Warning "驱动服务无法停止(可能被设备栈引用),.sys 文件可能被锁定" Write-Host "请先运行 uninstall.ps1 移除 UpperFilters,重启后再安装" -ForegroundColor Yellow } } Write-Host "拷贝驱动文件到 $FileDriverDir ..." $copyOk = $true try { Copy-Item -Force $DriverSys $TargetDriverSys -ErrorAction Stop Copy-Item -Force $DriverInf $TargetDriverInf -ErrorAction Stop Write-Host " 完成" -ForegroundColor Green } catch { Write-Warning "拷贝失败: $_" Write-Warning ".sys 文件被锁定,请先卸载驱动并重启" $copyOk = $false } if (-not $copyOk) { # 仍然继续尝试创建服务等步骤(可能旧 .sys 仍可用) Write-Host "继续安装(使用现有 .sys 文件)..." -ForegroundColor Yellow } # ============================================================================ # 2.5 注册驱动包到驱动存储区(关键步骤!) # ============================================================================ # 先检查测试签名状态,pnputil 要求驱动有签名 # bcdedit 输出 "testsigning Yes" 或 "test signing Yes"(不同本地化版本) $TestSigningEnabled = (bcdedit /enum | Select-String 'test\s*signing\s+Yes' -Quiet) if (-not $TestSigningEnabled) { Write-Host "" Write-Warning "测试签名未启用,驱动无法加载" Write-Host "正在启用测试签名..." -ForegroundColor Yellow bcdedit /set testsigning on | Out-Null Write-Host "测试签名已启用" -ForegroundColor Green Write-Warning "需要重启计算机才能生效!" Write-Host "" Write-Host "请重启后再次运行此脚本" -ForegroundColor Yellow exit 0 } # 对驱动进行测试签名(如果未签名) Write-Host "检查驱动签名状态..." $sigState = (Get-AuthenticodeSignature $TargetDriverSys).Status if ($sigState -ne 'Valid') { Write-Host "驱动未签名,使用测试证书签名..." -ForegroundColor Yellow # 创建或获取测试证书(用 Subject 匹配,检查未过期) $testCert = Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" -and $_.NotAfter -gt (Get-Date) } | Select-Object -First 1 if (-not $testCert) { # 清理过期的旧证书 Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" } | Remove-Item -Force -ErrorAction SilentlyContinue Write-Host "创建测试证书(有效期 10 年)..." $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) } # 将证书添加到 Trusted Root(测试签名模式下必须) $rootCert = Get-ChildItem -Path Cert:\LocalMachine\Root -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" } | Select-Object -First 1 if (-not $rootCert) { Write-Host "添加证书到受信任的根证书颁发机构..." $certPath = Join-Path $env:TEMP "CommModifyKitTest.cer" Export-Certificate -Cert $testCert -FilePath $certPath -Force | Out-Null # 尝试方法 1: Import-Certificate $importSuccess = $false try { Import-Certificate -FilePath $certPath -CertStoreLocation "Cert:\LocalMachine\Root" -ErrorAction Stop | Out-Null $importSuccess = $true Write-Host " 完成(Import-Certificate)" -ForegroundColor Green } catch { Write-Warning "Import-Certificate 失败: $($_.Exception.Message)" # 尝试方法 2: certutil.exe Write-Host " 尝试使用 certutil..." $certutilResult = & certutil.exe -addstore -f "Root" $certPath 2>&1 if ($LASTEXITCODE -eq 0) { $importSuccess = $true Write-Host " 完成(certutil)" -ForegroundColor Green } else { Write-Warning "certutil 导入失败: $certutilResult" } } # 清理临时证书文件(仅在成功导入后) if ($importSuccess) { Remove-Item $certPath -Force -ErrorAction SilentlyContinue } else { Write-Warning "无法将测试证书添加到受信任的根证书颁发机构" Write-Host "证书文件已保留: $certPath" -ForegroundColor Yellow Write-Host "请手动导入证书:" -ForegroundColor Yellow Write-Host " 1. 双击 $certPath" -ForegroundColor Yellow Write-Host " 2. 选择 '安装证书' → '本地计算机' → '将所有的证书都放入下列存储' → '受信任的根证书颁发机构'" -ForegroundColor Yellow } } # 签名驱动:先尝试带时间戳,失败则不带时间戳,再失败用 signtool Write-Host "签名驱动..." $signOk = $false # 尝试 1: Set-AuthenticodeSignature 带时间戳 $signResult = Set-AuthenticodeSignature -FilePath $TargetDriverSys -Certificate $testCert -TimeStampServer "http://timestamp.digicert.com" -HashAlgorithm SHA256 if ($signResult.Status -eq 'Valid' -or $signResult.Status -eq 'UnknownError') { Write-Host " 完成(带时间戳)" -ForegroundColor Green $signOk = $true } # 尝试 2: Set-AuthenticodeSignature 不带时间戳 if (-not $signOk) { Write-Host " 时间戳服务器不可达,尝试不带时间戳..." $signResult = Set-AuthenticodeSignature -FilePath $TargetDriverSys -Certificate $testCert -HashAlgorithm SHA256 if ($signResult.Status -eq 'Valid' -or $signResult.Status -eq 'UnknownError') { Write-Host " 完成(无时间戳)" -ForegroundColor Green $signOk = $true } } # 尝试 3: signtool(如果安装了 Windows SDK) if (-not $signOk) { $signtoolPaths = @( "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe", "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.22000.0\x64\signtool.exe", "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" ) $signtool = $signtoolPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 # 通配符后备搜索 if (-not $signtool) { $signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName } if ($signtool) { Write-Host " 尝试 signtool..." # 用 /sha1 指纹精确指定证书,避免 /n 子串匹配选到过期的同名证书 & $signtool sign /s MY /sha1 "$($testCert.Thumbprint)" /fd SHA256 $TargetDriverSys 2>&1 | Out-Null if ($LASTEXITCODE -eq 0) { Write-Host " 完成(signtool)" -ForegroundColor Green $signOk = $true } } } if (-not $signOk) { Write-Warning "签名失败: $($signResult.Status) - $($signResult.StatusMessage)" Write-Warning "驱动可能无法加载,请确认测试签名已启用并重启" } } else { Write-Host "驱动已签名" -ForegroundColor Green } # pnputil /add-driver 需要 .cat 目录文件,开发阶段通常没有 # 失败后通过 sc.exe create 直接加载驱动(测试签名模式) Write-Host "尝试注册驱动包到驱动存储区..." try { $result = pnputil /add-driver $DriverInf /install 2>&1 if ($LASTEXITCODE -eq 0) { Write-Host " 完成" -ForegroundColor Green if ($result -match '(oem\d+\.inf)') { Write-Host " 驱动包: $($matches[1])" -ForegroundColor Green } } else { Write-Host " pnputil 注册失败(无 .cat 目录文件,属正常),将通过 sc.exe 直接加载" -ForegroundColor Yellow } } catch { Write-Host " pnputil 不可用,将通过 sc.exe 直接加载" -ForegroundColor Yellow } # ============================================================================ # 3. 创建并启动驱动服务 # ============================================================================ $ServiceName = 'CommModifyKit' # 先删除旧服务(可能配置不一致) $ExistingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue if ($ExistingService) { if ($ExistingService.Status -eq 'Running') { sc.exe stop $ServiceName 2>&1 | Out-Null Start-Sleep 2 } sc.exe delete $ServiceName 2>&1 | Out-Null Start-Sleep 1 } Write-Host "创建服务 $ServiceName ..." sc.exe create $ServiceName binPath= $ServiceBinPath type= kernel start= demand error= normal | Out-Null if ($LASTEXITCODE -eq 0) { Write-Host " 完成" -ForegroundColor Green Write-Host "启动服务..." sc.exe start $ServiceName 2>&1 | Out-Null if ($LASTEXITCODE -eq 0) { Write-Host " 完成" -ForegroundColor Green } else { Write-Warning " 启动失败(可能需要重启后由 PnP 自动加载)" } } else { Write-Warning " 创建服务失败" } # ============================================================================ # 3.5 注册为 Ports 类 UpperFilters # 注意:仅当驱动服务确认运行中时才注册 UpperFilters,否则会导致 PnP 尝试 # attach 一个不存在的过滤器,使所有 Ports 类设备启动失败(mode 显示 # "访问设备失败")。 # ============================================================================ # 先确认服务确实在运行(避免驱动加载失败时污染 UpperFilters) $svcState = (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue).Status if ($svcState -ne 'Running') { Write-Warning "驱动服务未运行(状态: $svcState),跳过 UpperFilters 注册" Write-Host "请检查驱动签名/测试签名模式,重启后再次运行此脚本" -ForegroundColor Yellow Write-Host "未注册 UpperFilters,串口不会被过滤,但驱动也未生效" -ForegroundColor Yellow } else { $PortsClassGuid = "{4D36E978-E325-11CE-BFC1-08002BE10318}" $ClassKey = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\$PortsClassGuid" # 读取现有 UpperFilters,过滤掉空字符串(历史遗留的空字符串会导致 # PnP 加载空名称过滤器,使所有 Ports 类设备启动失败) $currentFilters = @() $existingFilters = (Get-ItemProperty -Path $ClassKey -Name "UpperFilters" -ErrorAction SilentlyContinue) if ($existingFilters -and $existingFilters.UpperFilters) { $currentFilters = @($existingFilters.UpperFilters | Where-Object { $_ -and $_.Trim() -ne '' }) } if ($currentFilters -notcontains "CommModifyKit") { $newFilters = @($currentFilters) + "CommModifyKit" Set-ItemProperty -Path $ClassKey -Name "UpperFilters" -Value $newFilters -Type MultiString Write-Host "已将 CommModifyKit 添加到 Ports 类 UpperFilters" -ForegroundColor Green # 设置 Ports 类的 Security 值 # WDF filter device 不支持 WdfDeviceInitAssignSDDLString(返回 STATUS_INVALID_SECURITY_DESCR) # Filter device 会继承类键的 Security 值。如果不设置,使用 WDF 默认 SDDL # (仅 System+Admins),导致非 elevated 进程无法打开 \\.\COMx try { $sd = New-Object System.Security.AccessControl.CommonSecurityDescriptor( $false, $false, "D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;AU)(A;;GRGW;;;WD)") $binary = New-Object byte[] $sd.BinaryLength $sd.GetBinaryForm($binary, 0) Set-ItemProperty -Path $ClassKey -Name "Security" -Value $binary -Type Binary Write-Host "已设置 Ports 类 Security 值(允许 Authenticated Users 读写)" -ForegroundColor Green } catch { Write-Warning "设置 Ports 类 Security 值失败: $($_.Exception.Message)" } # 重新枚举设备使 UpperFilter 生效 pnputil /scan-devices | Out-Null Write-Host "已触发设备重新枚举" -ForegroundColor Green } else { Write-Host "CommModifyKit 已在 UpperFilters 中" -ForegroundColor Green } } # ============================================================================ # 4. 拷贝 DLL 到 CommModifyService 输出目录(如果检测到) # ============================================================================ if ($DllPath) { $ServiceExeCandidates = @( Join-Path $ScriptDir "CommModifyService.exe" Join-Path $ScriptDir "..\CommModifyService\x64\Release\CommModifyService.exe" Join-Path $ScriptDir "..\CommModifyService\Win32\Release\CommModifyService.exe" Join-Path $ScriptDir "..\CommModifyService\x64\Debug\CommModifyService.exe" Join-Path $ScriptDir "..\CommModifyService\Win32\Debug\CommModifyService.exe" ) $ServiceExe = $ServiceExeCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 if ($ServiceExe) { $ServiceDir = Split-Path -Parent $ServiceExe $TargetDll = Join-Path $ServiceDir "CommModifyKit.dll" $srcReal = (Resolve-Path $DllPath -ErrorAction SilentlyContinue).Path $dstReal = if (Test-Path $TargetDll) { (Resolve-Path $TargetDll).Path } else { $null } if ($srcReal -and $dstReal -and $srcReal -eq $dstReal) { Write-Host "DLL 已在目标目录,跳过拷贝" -ForegroundColor Green } else { Write-Host "拷贝 DLL 到 $TargetDll ..." Copy-Item -Force $DllPath $TargetDll Write-Host " 完成" -ForegroundColor Green } } else { Write-Warning "未检测到 CommModifyService.exe,请手动拷贝 CommModifyKit.dll 到其目录" } } Write-Host "" Write-Host '=== 安装完成 ===' -ForegroundColor Cyan Write-Host "" Write-Host "验证步骤:" -ForegroundColor Yellow Write-Host " 1. 确认驱动服务: sc.exe query CommModifyKit" Write-Host " 2. 确认串口可用: mode" Write-Host " 3. 确认 UpperFilters: reg query ""HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E978-E325-11CE-BFC1-08002BE10318}"" /v UpperFilters" Write-Host "" Write-Host "如果串口显示 '访问设备失败',请重启计算机使 UpperFilters 生效" -ForegroundColor Yellow Write-Host " 重启命令: shutdown /r /t 0" -ForegroundColor Yellow