| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- # 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
- # 重新枚举设备使 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
|