写在前面
在此之前先想想做这件事的原因,哦,我想起来了,上周自动化测试好像有关于网银盾的,业务方不接受手工辅助插拔
虽然他们的要求有点难搞,不过正所谓不查一下资料就不知道真的能搞,搜了两天的资料,终于拼凑出了这篇文章
主要功能是实现USB根集线器的自动启停,在这个过程中,USB设备会重新挂载(ps:鼠标也会短暂失灵)
主要思路
在查资料的时候,发现Windows提供了一系列SetUPDi函数,可以操作设备管理器
相关函数
FormatGUID
这个没什么好说的,主要是格式化输出设备类GUID(注意那个“类”字)
void FormatGUID(GUID guid)
{
cout.fill('0');
cout.width(8);
cout << setiosflags(ios::uppercase) << hex << guid.Data1
<< "-" << guid.Data2 << "-" << guid.Data3 << "-";
for (int j = 0; j < 8; j++)
{
cout.fill('0');
cout.width(2);
cout << hex << (int)guid.Data4[j];
if (j == 1)
{
cout << "-";
}
}
cout << endl;
}
ChangeStatus
启停设备的主要代码段
传入了三个参数,第一个是要修改为的状态,DICS_DISABLE(停用)或DICS_ENABLE(启用)
第二个是在枚举设备过程中得到的参数,用于快速获取设备信息
第三个参数类似设备句柄,有了它才能进行相应的操作
int ChangeStatus(DWORD NewStatus, DWORD SelectedItem, HDEVINFO hDevInfo)
{
//两个结构体,一个用于修改设备参数,一个用于存储设备信息
SP_PROPCHANGE_PARAMS PropChangeParams = { sizeof(SP_CLASSINSTALL_HEADER) };
SP_DEVINFO_DATA DeviceInfoData = { sizeof(SP_DEVINFO_DATA) };
// 通过SelectedItem重新获取hDevInfo.
if (!SetupDiEnumDeviceInfo(hDevInfo, SelectedItem, &DeviceInfoData))
{
printf("SetupDiEnumDeviceInfo errorcode = %d\n", GetLastError());
return 0;
}
// 修改设备的参数.
PropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
PropChangeParams.Scope = DICS_FLAG_GLOBAL;
// 启用或停用设备
PropChangeParams.StateChange = NewStatus;
// 修改后的参数添加到hDevInfo
if (!SetupDiSetClassInstallParams(hDevInfo, &DeviceInfoData, (SP_CLASSINSTALL_HEADER*)&PropChangeParams, sizeof(PropChangeParams)))
{
printf("SetupDiSetClassInstallParams errorcode = %d\n", GetLastError());
return 0;
}
// 调用API使更改生效.
if (!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, &DeviceInfoData))
{
printf("SetupDiCallClassInstaller errorcode = %d\n", GetLastError());
return -1;
}
return 1;
}
GetDevInfo
程序入口函数
根据GUID枚举设备信息,获取设备的SPDRP_SERVICE,即服务名
与根集线器作比较,如果一致,则执行启/停操作
引入三个参数,分别是设备类GUID(这里需要通用串行总线控制器的GUID)
要修改为的状态(0:停用/1:启用),以及根集线器服务名
int GetDevInfo(char* guidstring, int nStatus, char* Service_Name)
{
// 这行代码可以用于枚举所有设备
//HDEVINFO hDevinfo = SetupDiGetClassDevs(NULL, 0, 0, DIGCF_ALLCLASSES | DIGCF_PRESENT);
GUID devGUID;
ZeroMemory(&devGUID, sizeof(GUID));
TCHAR* GUIDString = NULL;
GUIDString = (TCHAR*)(guidstring);
BOOL FOUND = FALSE;
if (UuidFromString((RPC_CSTR)GUIDString, &devGUID))
{
return FALSE;
}
// 根据类GUID获取设备句柄
HDEVINFO hDevinfo = SetupDiGetClassDevs(&devGUID, 0, 0, DIGCF_PRESENT);
int res = 1;
if (hDevinfo == INVALID_HANDLE_VALUE)
{
return FALSE;
}
SP_DEVINFO_DATA data;
DWORD nindex;
LPTSTR szBuf = NULL;
DWORD DataT;
DWORD buffsize = 0;
memset(&data, 0, sizeof(SP_DEVINFO_DATA));
data.cbSize = sizeof(SP_DEVINFO_DATA);
// 枚举设备类下的所有设备
for (nindex = 0; SetupDiEnumDeviceInfo(hDevinfo, nindex, &data); ++nindex)
{
// 获取设备的信息
while (!SetupDiGetDeviceRegistryProperty(hDevinfo, &data, SPDRP_SERVICE, &DataT, (PBYTE)szBuf, buffsize, &buffsize))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
if (szBuf) {
LocalFree(szBuf);
}
szBuf = (LPTSTR)LocalAlloc(LPTR, buffsize);
}
else {
break;
}
}
char This_Service[sizeof(szBuf) * 4]; int n;
n = sprintf_s(This_Service, "%s", szBuf);
// 与预设的服务名进行比较
if (szBuf && (string)Service_Name == (string)This_Service)
{
FOUND = TRUE;
printf("已发现通用串行总线控制器,GUID:");
FormatGUID(data.ClassGuid);
SP_PROPCHANGE_PARAMS procChange = { sizeof(SP_CLASSINSTALL_HEADER) };
procChange.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
procChange.Scope = DICS_FLAG_GLOBAL;
procChange.StateChange = DICS_DISABLE;
if (nStatus == 0) {
printf("即将禁用USB根集线器,目标服务:%s\n", szBuf);
Sleep(1000);
// 禁用设备
res = ChangeStatus(DICS_DISABLE, nindex, hDevinfo);
}
else if (nStatus == 1)
{
Sleep(1000);
printf("即将启用USB根集线器,目标服务:%s\n", szBuf);
// 启用设备
res = ChangeStatus(DICS_ENABLE, nindex, hDevinfo);
printf("已启用USB根集线器\n");
}
else {
return -1;
}
switch (res) {
case -1:
printf("启停过程异常,请在设备管理器中查看集线器状态\n");
break;
case 0:
printf("操作失败!枚举或设置参数过程引发异常\n");
break;
case 1:
printf("当前改动已生效\n");
break;
}
}
if (!FOUND)
printf("通用串行总线控制器中未发现相关设备");
return res;
}
}
完整代码
setupditest.h
需要的头可能没有那么多,可以试着去掉几个2333
#pragma once
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <setupapi.h>
#include <iostream>
#include <Rpc.h>
#include <devguid.h>
#include <usbiodef.h>
#include <atlstr.h>
#include <iomanip>
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "Rpcrt4.lib")
main.cpp
#include "setupditest.h"
using namespace std;
int GetDevInfo(char* guidstring,int nStatus,char* Service_Name);
void FormatGUID(GUID guid);
int ChangeStatus(DWORD NewStatus, DWORD SelectedItem, HDEVINFO hDevInfo);
void FormatGUID(GUID guid)
{
cout.fill('0');
cout.width(8);
cout << setiosflags(ios::uppercase) << hex << guid.Data1
<< "-" << guid.Data2 << "-" << guid.Data3 << "-";
for (int j = 0; j < 8; j++)
{
cout.fill('0');
cout.width(2);
cout << hex << (int)guid.Data4[j];
if (j == 1)
{
cout << "-";
}
}
cout << endl;
}
int ChangeStatus(DWORD NewStatus, DWORD SelectedItem, HDEVINFO hDevInfo)
{
SP_PROPCHANGE_PARAMS PropChangeParams = { sizeof(SP_CLASSINSTALL_HEADER) };
SP_DEVINFO_DATA DeviceInfoData = { sizeof(SP_DEVINFO_DATA) };
// Get a handle to the Selected Item.
if (!SetupDiEnumDeviceInfo(hDevInfo, SelectedItem, &DeviceInfoData))
{
printf("SetupDiEnumDeviceInfo errorcode = %d\n", GetLastError());
return 0;
}
// Set the PropChangeParams structure.
PropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
PropChangeParams.Scope = DICS_FLAG_GLOBAL;
PropChangeParams.StateChange = NewStatus;
if (!SetupDiSetClassInstallParams(hDevInfo, &DeviceInfoData, (SP_CLASSINSTALL_HEADER*)&PropChangeParams, sizeof(PropChangeParams)))
{
printf("SetupDiSetClassInstallParams errorcode = %d\n", GetLastError());
return 0;
}
// Call the ClassInstaller and perform the change.
if (!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, &DeviceInfoData))
{
printf("SetupDiCallClassInstaller errorcode = %d\n", GetLastError());
return -1;
}
return 1;
}
int GetDevInfo(char* guidstring, int nStatus, char* Service_Name)
{
//HDEVINFO hDevinfo = SetupDiGetClassDevs(NULL, 0, 0, DIGCF_ALLCLASSES | DIGCF_PRESENT);
GUID devGUID;
ZeroMemory(&devGUID, sizeof(GUID));
TCHAR* GUIDString = NULL;
GUIDString = (TCHAR*)(guidstring);
if (UuidFromString((RPC_CSTR)GUIDString, &devGUID))
{
return FALSE;
}
HDEVINFO hDevinfo = SetupDiGetClassDevs(&devGUID, 0, 0, DIGCF_PRESENT);
int res = 1;
if (hDevinfo == INVALID_HANDLE_VALUE)
{
return FALSE;
}
SP_DEVINFO_DATA data;
DWORD nindex;
LPTSTR szBuf = NULL;
DWORD DataT;
DWORD buffsize = 0;
memset(&data, 0, sizeof(SP_DEVINFO_DATA));
data.cbSize = sizeof(SP_DEVINFO_DATA);
for (nindex = 0; SetupDiEnumDeviceInfo(hDevinfo, nindex, &data); ++nindex)
{
//printf("%d %Id\n", data.DevInst,data.Reserved);
while (!SetupDiGetDeviceRegistryProperty(hDevinfo, &data, SPDRP_SERVICE, &DataT, (PBYTE)szBuf, buffsize, &buffsize))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
if (szBuf) {
LocalFree(szBuf);
}
szBuf = (LPTSTR)LocalAlloc(LPTR, buffsize);
}
else {
break;
}
}
char This_Service[sizeof(szBuf) * 4]; int n;
n = sprintf_s(This_Service, "%s", szBuf);
if (szBuf && (string)Service_Name == (string)This_Service)
{
printf("已发现通用串行总线控制器,GUID:");
FormatGUID(data.ClassGuid);
SP_PROPCHANGE_PARAMS procChange = { sizeof(SP_CLASSINSTALL_HEADER) };
procChange.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
procChange.Scope = DICS_FLAG_GLOBAL;
procChange.StateChange = DICS_DISABLE;
if (nStatus == 0) {
printf("即将禁用USB根集线器,目标服务:%s\n", szBuf);
Sleep(1000);
res = ChangeStatus(DICS_DISABLE, nindex, hDevinfo);
}
else if (nStatus == 1)
{
Sleep(1000);
printf("即将启用USB根集线器,目标服务:%s\n", szBuf);
res = ChangeStatus(DICS_ENABLE, nindex, hDevinfo);
printf("已启用USB根集线器\n");
}
else {
return 2;
}
switch (res) {
case -1:
printf("启停过程异常,请在设备管理器中查看集线器状态\n");
break;
case 0:
printf("操作失败!枚举或设置参数过程引发异常\n");
break;
case 1:
printf("当前改动已生效\n");
break;
}
}
}
return res;
}
int main(void)
{
GetDevInfo((char*)"36FC9E60-C465-11CF-8056-444553540000",0, (char*)"iusb3hub");
Sleep(10);
GetDevInfo((char*)"36FC9E60-C465-11CF-8056-444553540000",1, (char*)"iusb3hub");
}
动态链接库
如果要生成动态链接库,需要修改函数的声明与定义方式
如下所示:
// 定义一个宏,直接使用也是可以的
#define DLLEXPORT extern "C" __declspec(dllexport)
// 声明
DLLEXPORT void FormatGUID(GUID guid);
// 定义
DLLEXPORT void FormatGUID(GUID guid)
{
cout.fill('0');
cout.width(8);
cout << setiosflags(ios::uppercase) << hex << guid.Data1
<< "-" << guid.Data2 << "-" << guid.Data3 << "-";
for (int j = 0; j < 8; j++)
{
cout.fill('0');
cout.width(2);
cout << hex << (int)guid.Data4[j];
if (j == 1)
{
cout << "-";
}
}
cout << endl;
}
调用DLL
生成动态链接库后可以在python中调用,以下是一个例子:
ps:在cmd以外的地方运行py时,无法看到C代码中printf的输出
import ctypes
import time
UsbControl = ctypes.cdll.LoadLibrary("DllUSB.DLL")
# 一定要注意参数类型,不能直接传递字符串
GUID = ctypes.c_char_p(b'36FC9E60-C465-11CF-8056-444553540000')
Service_Name = ctypes.c_char_p(b'iusb3hub')
UsbControl.GetDevInfo(GUID,0,Service_Name)
time.sleep(3)
UsbControl.GetDevInfo(GUID,1,Service_Name)
Q&A
如何获取GUID?
右击我的电脑,点击管理,点击设备管理器,找到目标设备,右键,点击属性,点击详细信息,选择设备类GUID即可看到
如果你懒得手打,可以使用WMIC或者RW工具,此外devguid.h和usbiodef.h两个头文件中也存储有设备的GUID