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
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 { /* 忽略 */ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|