兼容win7版应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
5.6 KiB

using System;
using System.Diagnostics;
using System.IO;
using System.Security.Principal;
using System.Threading.Tasks;
namespace AIK.Service.Service
{
/// <summary>
/// devcon.exe 封装:用于 Win7 上解决 USB 设备热切换(HID ↔ U 盘模式)时枚举失败(Code 10)的问题。
/// devcon 是 Windows Driver Kit(WDK)自带工具,**不是** Windows 系统自带,需要随程序分发:
/// 将 devcon.exe(x86/x64 与程序位数一致)放到程序运行目录,或放在 LocateDevcon 指定的路径。
/// 注意:devcon remove / restart / rescan 需要**管理员权限**,建议程序以管理员身份运行。
/// 调用失败时全部降级返回 false,不抛异常,不影响原流程。
/// </summary>
public static class UsbDevconHelper
{
/// <summary>
/// 查找 devcon.exe。优先按当前进程位数查找运行目录下的 x64 或 x86 子目录,
/// 其次回退到运行目录根下的 devcon.exe;找不到返回 null。
/// </summary>
public static string LocateDevcon()
{
var candidates = new[]
{
AppDomain.CurrentDomain.BaseDirectory,
AppDomain.CurrentDomain.RelativeSearchPath ?? string.Empty
};
// 优先位数匹配的子目录(x64/x86),程序 AnyCPU 编译时按运行进程位数选择
string archDir = IntPtr.Size == 8 ? "x64" : "x86";
foreach (var dir in candidates)
{
if (string.IsNullOrEmpty(dir)) continue;
var archExe = Path.Combine(dir, archDir, "devcon.exe");
if (File.Exists(archExe)) return archExe;
}
// 回退:运行目录根下的 devcon.exe(兼容手动放置的场景)
foreach (var dir in candidates)
{
if (string.IsNullOrEmpty(dir)) continue;
var exe = Path.Combine(dir, "devcon.exe");
if (File.Exists(exe)) return exe;
}
return null;
}
/// <summary>是否以管理员权限运行(devcon remove/restart/rescan 需要)。</summary>
public static bool IsAdministrator()
{
try
{
using var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
catch
{
return false;
}
}
/// <summary>执行 devcon rescan(重新扫描硬件)。</summary>
public static Task<bool> RescanAsync(int timeoutMs = 30000)
{
return RunAsync("rescan", timeoutMs);
}
/// <summary>执行 devcon restart(重启设备:disable + enable,强制重新加载驱动)。</summary>
public static Task<bool> RestartDeviceAsync(int vid, int pid, int timeoutMs = 30000)
{
// 用通配符匹配设备实例 ID(形如 USB\VID_28E9&PID_FFF0\xxxxx)
var hwid = $"USB\\VID_{vid:X4}&PID_{pid:X4}";
return RunAsync($"restart \"{hwid}\"", timeoutMs);
}
/// <summary>
/// 执行 devcon remove + rescan(删除设备实例后重新扫描),等价于"软件拔插",
/// 比 restart(disable+enable)更彻底,对 Win7 热切换 Code 10 最有效。
/// </summary>
public static async Task<bool> RemoveAndRescanAsync(int vid, int pid, int timeoutMs = 30000)
{
var hwid = $"USB\\VID_{vid:X4}&PID_{pid:X4}";
bool removed = await RunAsync($"remove \"{hwid}\"", timeoutMs).ConfigureAwait(false);
// 无论 remove 是否成功都执行 rescan(设备可能已不在,rescan 无副作用)
bool rescanned = await RunAsync("rescan", timeoutMs).ConfigureAwait(false);
return removed || rescanned;
}
/// <summary>执行任意 devcon 命令;找不到 exe / 非管理员 / 超时 / 非零退出码均返回 false。</summary>
public static async Task<bool> RunAsync(string arguments, int timeoutMs = 30000)
{
var devcon = LocateDevcon();
if (devcon == null)
{
return false; // 未分发 devcon.exe,静默降级
}
if (!IsAdministrator())
{
return false; // 非管理员时 devcon 无法操作设备,静默降级
}
Process process = null;
try
{
var psi = new ProcessStartInfo
{
FileName = devcon,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
process = Process.Start(psi);
// .NET Framework 4.8 无 WaitForExitAsync,用 Task.Run 包装 + 超时控制
var exitTask = Task.Run(() => process.WaitForExit(timeoutMs));
bool exited = await exitTask.ConfigureAwait(false);
if (!exited)
{
try { process.Kill(); } catch { /* 忽略 */ }
return false;
}
return process.ExitCode == 0;
}
catch
{
return false;
}
finally
{
try { process?.Dispose(); } catch { /* 忽略 */ }
}
}
}
}