You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过C#获取USB设备插拔时间?解决注册表权限不足问题

解决USB设备最后插拔时间的权限问题(C#实现)

你遇到的问题是管理员权限无法访问注册表中USB设备的Properties子项,需要SYSTEM权限才能读取。以下是几种可行的解决方案:

一、优先方案:用SetupAPI获取插拔时间(无需SYSTEM权限)

不用纠结注册表权限问题,直接通过Windows的SetupAPI获取设备的最后安装/插拔时间,管理员权限即可访问,代码更简洁可靠。

代码示例

using System;
using System.Runtime.InteropServices;
using System.Text;

public static class USBDeviceHelper
{
    [DllImport("setupapi.dll", SetLastError = true)]
    private static extern IntPtr SetupDiGetClassDevs(ref Guid classGuid, string enumerator, IntPtr hwndParent, uint flags);

    [DllImport("setupapi.dll", SetLastError = true)]
    private static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet, uint memberIndex, ref SP_DEVINFO_DATA deviceInfoData);

    [DllImport("setupapi.dll", SetLastError = true)]
    private static extern bool SetupDiGetDeviceRegistryProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint property, out uint propertyRegDataType, StringBuilder propertyBuffer, uint propertyBufferSize, out uint requiredSize);

    [DllImport("setupapi.dll", SetLastError = true)]
    private static extern bool SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);

    [StructLayout(LayoutKind.Sequential)]
    private struct SP_DEVINFO_DATA
    {
        public int cbSize;
        public Guid classGuid;
        public uint devInst;
        public IntPtr reserved;
    }

    // USB设备类GUID
    private static readonly Guid USB_CLASS_GUID = new Guid("{36FC9E60-C465-11CF-8056-444553540000}");
    // 最后安装/插拔时间属性ID
    private const uint SPDRP_LAST_INSTALL_DATE = 0x19;
    // 枚举标志:所有类+已连接设备
    private const uint DIGCF_FLAGS = 0x4 | 0x2;

    public static void FetchUSBDeviceLastPlugTime()
    {
        var deviceInfoSet = SetupDiGetClassDevs(ref USB_CLASS_GUID, null, IntPtr.Zero, DIGCF_FLAGS);
        if (deviceInfoSet == IntPtr.Zero)
        {
            Console.WriteLine($"获取设备列表失败: {Marshal.GetLastWin32Error()}");
            return;
        }

        var devInfoData = new SP_DEVINFO_DATA { cbSize = Marshal.SizeOf<SP_DEVINFO_DATA>() };
        uint index = 0;

        while (SetupDiEnumDeviceInfo(deviceInfoSet, index, ref devInfoData))
        {
            var buffer = new StringBuilder(256);
            uint regType = 0;
            uint reqSize = 0;

            if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref devInfoData, SPDRP_LAST_INSTALL_DATE, out regType, buffer, (uint)buffer.Capacity, out reqSize))
            {
                // 可添加读取SPDRP_FRIENDLYNAME属性获取设备名称的逻辑
                Console.WriteLine($"最后插拔时间: {buffer}");
            }
            else
            {
                Console.WriteLine($"读取时间失败: {Marshal.GetLastWin32Error()}");
            }

            index++;
        }

        SetupDiDestroyDeviceInfoList(deviceInfoSet);
    }
}

说明

  • SPDRP_LAST_INSTALL_DATE属性返回的是设备最后被安装(包括插拔)的时间字符串,格式为yyyyMMddHHmmss.ffffff+UUU。
  • 该方法仅需要管理员权限,无需SYSTEM身份,兼容性更好。

二、通过SYSTEM身份访问注册表

如果必须读取注册表的Properties子项,需要让代码以SYSTEM身份运行,常见的实现方式有两种:

方式1:将程序注册为Windows服务

Windows服务默认以SYSTEM账户运行,直接在服务中编写注册表读取逻辑即可:

  1. 创建Windows服务项目,在OnStart方法中添加注册表读取代码。
  2. 安装服务后启动,即可直接访问受限的注册表项。

方式2:临时创建SYSTEM权限进程(适合桌面应用)

通过API获取SYSTEM令牌,创建子进程执行注册表读取任务,主进程通过临时文件、命名管道等方式获取结果。

代码示例(创建SYSTEM进程)

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public static class SystemPrivilegeRunner
{
    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool OpenProcessToken(IntPtr hProcess, uint dwDesiredAccess, out IntPtr hToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, int impersonationLevel, int tokenType, out IntPtr hNewToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

    [StructLayout(LayoutKind.Sequential)]
    private struct STARTUPINFO
    {
        public int cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public int dwX;
        public int dwY;
        public int dwXSize;
        public int dwYSize;
        public int dwXCountChars;
        public int dwYCountChars;
        public int dwFillAttribute;
        public int dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    private const uint TOKEN_QUERY = 0x8;
    private const uint TOKEN_DUPLICATE = 0x2;
    private const uint TOKEN_ASSIGN_PRIMARY = 0x1;
    private const int SECURITY_IMPERSONATION_LEVEL = 2;
    private const int TOKEN_TYPE = 1;
    private const uint CREATE_NO_WINDOW = 0x8000000;

    public static bool RunAsSystem(string exePath, string args)
    {
        // 获取winlogon进程的SYSTEM令牌(winlogon默认以SYSTEM运行)
        var winlogon = Process.GetProcessesByName("winlogon")[0];
        if (!OpenProcessToken(winlogon.Handle, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, out var systemToken))
        {
            Console.WriteLine($"获取SYSTEM令牌失败: {Marshal.GetLastWin32Error()}");
            return false;
        }

        if (!DuplicateTokenEx(systemToken, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL, TOKEN_TYPE, out var primaryToken))
        {
            Console.WriteLine($"复制令牌失败: {Marshal.GetLastWin32Error()}");
            Marshal.CloseHandle(systemToken);
            return false;
        }

        var startupInfo = new STARTUPINFO
        {
            cb = Marshal.SizeOf<STARTUPINFO>(),
            lpDesktop = "WinSta0\\Default"
        };

        if (!CreateProcessAsUser(primaryToken, exePath, $"{exePath} {args}", IntPtr.Zero, IntPtr.Zero, false, CREATE_NO_WINDOW, IntPtr.Zero, null, ref startupInfo, out var procInfo))
        {
            Console.WriteLine($"创建SYSTEM进程失败: {Marshal.GetLastWin32Error()}");
            Marshal.CloseHandle(primaryToken);
            Marshal.CloseHandle(systemToken);
            return false;
        }

        // 释放资源
        Marshal.CloseHandle(primaryToken);
        Marshal.CloseHandle(systemToken);
        Marshal.CloseHandle(procInfo.hProcess);
        Marshal.CloseHandle(procInfo.hThread);

        return true;
    }
}

说明

  • 主程序需要以管理员权限运行,才能获取winlogon进程的令牌。
  • 子进程(如单独的控制台程序)负责读取注册表Properties子项,将结果写入临时文件,主进程读取该文件获取数据。

三、权限相关说明

  • 管理员权限并非Windows最高权限,SYSTEM账户拥有系统核心资源的访问权限,主要用于系统服务和核心进程。
  • 不要随意让桌面程序以SYSTEM身份运行,会带来安全风险,优先选择SetupAPI的方案。

内容的提问来源于stack exchange,提问作者Antonio Argentieri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 01:45:34