windows-nt/Source/XPSP1/NT/base/pnp/tools/devcon/devcon.cpp
2020-09-26 16:20:57 +08:00

1007 lines
22 KiB
C++

/*++
Copyright (c) Microsoft Corporation. All rights reserved.
Module Name:
devcon.cpp
Abstract:
Device Console
command-line interface for managing devices
@@BEGIN_DDKSPLIT
Author:
Jamie Hunter (JamieHun) Nov-30-2000
Revision History:
@@END_DDKSPLIT
--*/
#include "devcon.h"
struct IdEntry {
LPCTSTR String; // string looking for
LPCTSTR Wild; // first wild character if any
BOOL InstanceId;
};
void FormatToStream(FILE * stream,DWORD fmt,...)
/*++
Routine Description:
Format text to stream using a particular msg-id fmt
Used for displaying localizable messages
Arguments:
stream - file stream to output to, stdout or stderr
fmt - message id
... - parameters %1...
Return Value:
none
--*/
{
va_list arglist;
LPTSTR locbuffer = NULL;
DWORD count;
va_start(arglist, fmt);
count = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE|FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,
fmt,
0, // LANGID
(LPTSTR) &locbuffer,
0, // minimum size of buffer
&arglist);
if(locbuffer) {
if(count) {
_fputts(locbuffer,stream);
}
LocalFree(locbuffer);
}
}
void Padding(int pad)
/*++
Routine Description:
Insert padding into line before text
Arguments:
pad - number of padding tabs to insert
Return Value:
none
--*/
{
int c;
for(c=0;c<pad;c++) {
fputs(" ",stdout);
}
}
void Usage(LPCTSTR BaseName)
/*++
Routine Description:
Display simple usage text
Arguments:
BaseName - name of executable
Return Value:
none
--*/
{
FormatToStream(stderr,MSG_USAGE,BaseName);
}
void CommandUsage(LPCTSTR BaseName,LPCTSTR Cmd)
/*++
Routine Description:
Invalid command usage
Display how to get help on command
Arguments:
BaseName - name of executable
Return Value:
none
--*/
{
FormatToStream(stderr,MSG_COMMAND_USAGE,BaseName,Cmd);
}
void Failure(LPCTSTR BaseName,LPCTSTR Cmd)
/*++
Routine Description:
Display simple error text for general failure
Arguments:
BaseName - name of executable
Return Value:
none
--*/
{
FormatToStream(stderr,MSG_FAILURE,BaseName,Cmd);
}
BOOL Reboot()
/*++
Routine Description:
Attempt to reboot computer
Arguments:
none
Return Value:
TRUE if API suceeded
--*/
{
HANDLE Token;
BOOL b;
TOKEN_PRIVILEGES NewPrivileges;
LUID Luid;
//
// we need to "turn on" reboot privilege
// if any of this fails, try reboot anyway
//
if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&Token)) {
goto final;
}
if(!LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME,&Luid)) {
CloseHandle(Token);
goto final;
}
NewPrivileges.PrivilegeCount = 1;
NewPrivileges.Privileges[0].Luid = Luid;
NewPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(
Token,
FALSE,
&NewPrivileges,
0,
NULL,
NULL
);
CloseHandle(Token);
final:
//
// attempt reboot - inform system that this is planned hardware install
//
return ExitWindowsEx(EWX_REBOOT, REASON_PLANNED_FLAG|REASON_HWINSTALL);
}
LPTSTR GetDeviceStringProperty(HDEVINFO Devs,PSP_DEVINFO_DATA DevInfo,DWORD Prop)
/*++
Routine Description:
Return a string property for a device, otherwise NULL
Arguments:
Devs )_ uniquely identify device
DevInfo )
Prop - string property to obtain
Return Value:
string containing description
--*/
{
LPTSTR buffer;
DWORD size;
DWORD reqSize;
DWORD dataType;
DWORD szChars;
size = 1024; // initial guess
buffer = new TCHAR[(size/sizeof(TCHAR))+1];
if(!buffer) {
return NULL;
}
while(!SetupDiGetDeviceRegistryProperty(Devs,DevInfo,Prop,&dataType,(LPBYTE)buffer,size,&reqSize)) {
if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
goto failed;
}
if(dataType != REG_SZ) {
goto failed;
}
size = reqSize;
delete [] buffer;
buffer = new TCHAR[(size/sizeof(TCHAR))+1];
if(!buffer) {
goto failed;
}
}
szChars = reqSize/sizeof(TCHAR);
buffer[szChars] = TEXT('\0');
return buffer;
failed:
if(buffer) {
delete [] buffer;
}
return NULL;
}
LPTSTR GetDeviceDescription(HDEVINFO Devs,PSP_DEVINFO_DATA DevInfo)
/*++
Routine Description:
Return a string containing a description of the device, otherwise NULL
Always try friendly name first
Arguments:
Devs )_ uniquely identify device
DevInfo )
Return Value:
string containing description
--*/
{
LPTSTR desc;
desc = GetDeviceStringProperty(Devs,DevInfo,SPDRP_FRIENDLYNAME);
if(!desc) {
desc = GetDeviceStringProperty(Devs,DevInfo,SPDRP_DEVICEDESC);
}
return desc;
}
IdEntry GetIdType(LPCTSTR Id)
/*++
Routine Description:
Determine if this is instance id or hardware id and if there's any wildcards
instance ID is prefixed by '@'
wildcards are '*'
Arguments:
Id - ptr to string to check
Return Value:
IdEntry
--*/
{
IdEntry Entry;
Entry.InstanceId = FALSE;
Entry.Wild = NULL;
Entry.String = Id;
if(Entry.String[0] == INSTANCEID_PREFIX_CHAR) {
Entry.InstanceId = TRUE;
Entry.String = CharNext(Entry.String);
}
if(Entry.String[0] == QUOTE_PREFIX_CHAR) {
//
// prefix to treat rest of string literally
//
Entry.String = CharNext(Entry.String);
} else {
//
// see if any wild characters exist
//
Entry.Wild = _tcschr(Entry.String,WILD_CHAR);
}
return Entry;
}
LPTSTR * GetMultiSzIndexArray(LPTSTR MultiSz)
/*++
Routine Description:
Get an index array pointing to the MultiSz passed in
Arguments:
MultiSz - well formed multi-sz string
Return Value:
array of strings. last entry+1 of array contains NULL
returns NULL on failure
--*/
{
LPTSTR scan;
LPTSTR * array;
int elements;
for(scan = MultiSz, elements = 0; scan[0] ;elements++) {
scan += lstrlen(scan)+1;
}
array = new LPTSTR[elements+2];
if(!array) {
return NULL;
}
array[0] = MultiSz;
array++;
if(elements) {
for(scan = MultiSz, elements = 0; scan[0]; elements++) {
array[elements] = scan;
scan += lstrlen(scan)+1;
}
}
array[elements] = NULL;
return array;
}
void DelMultiSz(LPTSTR * Array)
/*++
Routine Description:
Deletes the string array allocated by GetDevMultiSz/GetRegMultiSz/GetMultiSzIndexArray
Arguments:
Array - pointer returned by GetMultiSzIndexArray
Return Value:
None
--*/
{
if(Array) {
Array--;
if(Array[0]) {
delete [] Array[0];
}
delete [] Array;
}
}
LPTSTR * GetDevMultiSz(HDEVINFO Devs,PSP_DEVINFO_DATA DevInfo,DWORD Prop)
/*++
Routine Description:
Get a multi-sz device property
and return as an array of strings
Arguments:
Devs - HDEVINFO containing DevInfo
DevInfo - Specific device
Prop - SPDRP_HARDWAREID or SPDRP_COMPATIBLEIDS
Return Value:
array of strings. last entry+1 of array contains NULL
returns NULL on failure
--*/
{
LPTSTR buffer;
DWORD size;
DWORD reqSize;
DWORD dataType;
LPTSTR * array;
DWORD szChars;
size = 8192; // initial guess, nothing magic about this
buffer = new TCHAR[(size/sizeof(TCHAR))+2];
if(!buffer) {
return NULL;
}
while(!SetupDiGetDeviceRegistryProperty(Devs,DevInfo,Prop,&dataType,(LPBYTE)buffer,size,&reqSize)) {
if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
goto failed;
}
if(dataType != REG_MULTI_SZ) {
goto failed;
}
size = reqSize;
delete [] buffer;
buffer = new TCHAR[(size/sizeof(TCHAR))+2];
if(!buffer) {
goto failed;
}
}
szChars = reqSize/sizeof(TCHAR);
buffer[szChars] = TEXT('\0');
buffer[szChars+1] = TEXT('\0');
array = GetMultiSzIndexArray(buffer);
if(array) {
return array;
}
failed:
if(buffer) {
delete [] buffer;
}
return NULL;
}
LPTSTR * GetRegMultiSz(HKEY hKey,LPCTSTR Val)
/*++
Routine Description:
Get a multi-sz from registry
and return as an array of strings
Arguments:
hKey - Registry Key
Val - Value to query
Return Value:
array of strings. last entry+1 of array contains NULL
returns NULL on failure
--*/
{
LPTSTR buffer;
DWORD size;
DWORD reqSize;
DWORD dataType;
LPTSTR * array;
DWORD szChars;
LONG regErr;
size = 8192; // initial guess, nothing magic about this
buffer = new TCHAR[(size/sizeof(TCHAR))+2];
if(!buffer) {
return NULL;
}
reqSize = size;
while((regErr = RegQueryValueEx(hKey,Val,NULL,&dataType,(PBYTE)buffer,&reqSize) != NO_ERROR)) {
if(GetLastError() != ERROR_MORE_DATA) {
goto failed;
}
if(dataType != REG_MULTI_SZ) {
goto failed;
}
size = reqSize;
delete [] buffer;
buffer = new TCHAR[(size/sizeof(TCHAR))+2];
if(!buffer) {
goto failed;
}
}
szChars = reqSize/sizeof(TCHAR);
buffer[szChars] = TEXT('\0');
buffer[szChars+1] = TEXT('\0');
array = GetMultiSzIndexArray(buffer);
if(array) {
return array;
}
failed:
if(buffer) {
delete [] buffer;
}
return NULL;
}
BOOL WildCardMatch(LPCTSTR Item,const IdEntry & MatchEntry)
/*++
Routine Description:
Compare a single item against wildcard
I'm sure there's better ways of implementing this
Other than a command-line management tools
it's a bad idea to use wildcards as it implies
assumptions about the hardware/instance ID
eg, it might be tempting to enumerate root\* to
find all root devices, however there is a CfgMgr
API to query status and determine if a device is
root enumerated, which doesn't rely on implementation
details.
Arguments:
Item - item to find match for eg a\abcd\c
MatchEntry - eg *\*bc*\*
Return Value:
TRUE if any match, otherwise FALSE
--*/
{
LPCTSTR scanItem;
LPCTSTR wildMark;
LPCTSTR nextWild;
size_t matchlen;
//
// before attempting anything else
// try and compare everything up to first wild
//
if(!MatchEntry.Wild) {
return _tcsicmp(Item,MatchEntry.String) ? FALSE : TRUE;
}
if(_tcsnicmp(Item,MatchEntry.String,MatchEntry.Wild-MatchEntry.String) != 0) {
return FALSE;
}
wildMark = MatchEntry.Wild;
scanItem = Item + (MatchEntry.Wild-MatchEntry.String);
for(;wildMark[0];) {
//
// if we get here, we're either at or past a wildcard
//
if(wildMark[0] == WILD_CHAR) {
//
// so skip wild chars
//
wildMark = CharNext(wildMark);
continue;
}
//
// find next wild-card
//
nextWild = _tcschr(wildMark,WILD_CHAR);
if(nextWild) {
//
// substring
//
matchlen = nextWild-wildMark;
} else {
//
// last portion of match
//
size_t scanlen = lstrlen(scanItem);
matchlen = lstrlen(wildMark);
if(scanlen < matchlen) {
return FALSE;
}
return _tcsicmp(scanItem+scanlen-matchlen,wildMark) ? FALSE : TRUE;
}
if(_istalpha(wildMark[0])) {
//
// scan for either lower or uppercase version of first character
//
TCHAR u = _totupper(wildMark[0]);
TCHAR l = _totlower(wildMark[0]);
while(scanItem[0] && scanItem[0]!=u && scanItem[0]!=l) {
scanItem = CharNext(scanItem);
}
if(!scanItem[0]) {
//
// ran out of string
//
return FALSE;
}
} else {
//
// scan for first character (no case)
//
scanItem = _tcschr(scanItem,wildMark[0]);
if(!scanItem) {
//
// ran out of string
//
return FALSE;
}
}
//
// try and match the sub-string at wildMark against scanItem
//
if(_tcsnicmp(scanItem,wildMark,matchlen)!=0) {
//
// nope, try again
//
scanItem = CharNext(scanItem);
continue;
}
//
// substring matched
//
scanItem += matchlen;
wildMark += matchlen;
}
return (wildMark[0] ? FALSE : TRUE);
}
BOOL WildCompareHwIds(LPTSTR * Array,const IdEntry & MatchEntry)
/*++
Routine Description:
Compares all strings in Array against Id
Use WildCardMatch to do real compare
Arguments:
Array - pointer returned by GetDevMultiSz
MatchEntry - string to compare against
Return Value:
TRUE if any match, otherwise FALSE
--*/
{
if(Array) {
while(Array[0]) {
if(WildCardMatch(Array[0],MatchEntry)) {
return TRUE;
}
Array++;
}
}
return FALSE;
}
int EnumerateDevices(LPCTSTR BaseName,LPCTSTR Machine,DWORD Flags,int argc,LPTSTR argv[],CallbackFunc Callback,LPVOID Context)
/*++
Routine Description:
Generic enumerator for devices that will be passed the following arguments:
<id> [<id>...]
=<class> [<id>...]
where <id> can either be @instance-id, or hardware-id and may contain wildcards
<class> is a class name
Arguments:
BaseName - name of executable
Machine - name of machine to enumerate
Flags - extra enumeration flags (eg DIGCF_PRESENT)
argc/argv - remaining arguments on command line
Callback - function to call for each hit
Context - data to pass function for each hit
Return Value:
EXIT_xxxx
--*/
{
HDEVINFO devs = INVALID_HANDLE_VALUE;
IdEntry * templ = NULL;
DWORD err;
int failcode = EXIT_FAIL;
int retcode;
int argIndex;
DWORD devIndex;
SP_DEVINFO_DATA devInfo;
SP_DEVINFO_LIST_DETAIL_DATA devInfoListDetail;
BOOL doSearch = FALSE;
BOOL match;
BOOL all = FALSE;
GUID cls;
DWORD numClass = 0;
int skip = 0;
if(!argc) {
return EXIT_USAGE;
}
templ = new IdEntry[argc];
if(!templ) {
goto final;
}
//
// determine if a class is specified
//
if(argc>skip && argv[skip][0]==CLASS_PREFIX_CHAR && argv[skip][1]) {
if(!SetupDiClassGuidsFromNameEx(argv[skip]+1,&cls,1,&numClass,Machine,NULL) &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
goto final;
}
if(!numClass) {
failcode = EXIT_OK;
goto final;
}
skip++;
}
if(argc>skip && argv[skip][0]==WILD_CHAR && !argv[skip][1]) {
//
// catch convinient case of specifying a single argument '*'
//
all = TRUE;
skip++;
} else if(argc<=skip) {
//
// at least one parameter, but no <id>'s
//
all = TRUE;
}
//
// determine if any instance id's were specified
//
// note, if =<class> was specified with no id's
// we'll mark it as not doSearch
// but will go ahead and add them all
//
for(argIndex=skip;argIndex<argc;argIndex++) {
templ[argIndex] = GetIdType(argv[argIndex]);
if(templ[argIndex].Wild || !templ[argIndex].InstanceId) {
//
// anything other than simple InstanceId's require a search
//
doSearch = TRUE;
}
}
if(doSearch || all) {
//
// add all id's to list
// if there's a class, filter on specified class
//
devs = SetupDiGetClassDevsEx(numClass ? &cls : NULL,
NULL,
NULL,
(numClass ? 0 : DIGCF_ALLCLASSES) | Flags,
NULL,
Machine,
NULL);
} else {
//
// blank list, we'll add instance id's by hand
//
devs = SetupDiCreateDeviceInfoListEx(numClass ? &cls : NULL,
NULL,
Machine,
NULL);
}
if(devs == INVALID_HANDLE_VALUE) {
goto final;
}
for(argIndex=skip;argIndex<argc;argIndex++) {
//
// add explicit instances to list (even if enumerated all,
// this gets around DIGCF_PRESENT)
// do this even if wildcards appear to be detected since they
// might actually be part of the instance ID of a non-present device
//
if(templ[argIndex].InstanceId) {
SetupDiOpenDeviceInfo(devs,templ[argIndex].String,NULL,0,NULL);
}
}
devInfoListDetail.cbSize = sizeof(devInfoListDetail);
if(!SetupDiGetDeviceInfoListDetail(devs,&devInfoListDetail)) {
goto final;
}
//
// now enumerate them
//
if(all) {
doSearch = FALSE;
}
devInfo.cbSize = sizeof(devInfo);
for(devIndex=0;SetupDiEnumDeviceInfo(devs,devIndex,&devInfo);devIndex++) {
if(doSearch) {
for(argIndex=skip,match=FALSE;(argIndex<argc) && !match;argIndex++) {
TCHAR devID[MAX_DEVICE_ID_LEN];
LPTSTR *hwIds = NULL;
LPTSTR *compatIds = NULL;
//
// determine instance ID
//
if(CM_Get_Device_ID_Ex(devInfo.DevInst,devID,MAX_DEVICE_ID_LEN,0,devInfoListDetail.RemoteMachineHandle)!=CR_SUCCESS) {
devID[0] = TEXT('\0');
}
if(templ[argIndex].InstanceId) {
//
// match on the instance ID
//
if(WildCardMatch(devID,templ[argIndex])) {
match = TRUE;
}
} else {
//
// determine hardware ID's
// and search for matches
//
hwIds = GetDevMultiSz(devs,&devInfo,SPDRP_HARDWAREID);
compatIds = GetDevMultiSz(devs,&devInfo,SPDRP_COMPATIBLEIDS);
if(WildCompareHwIds(hwIds,templ[argIndex]) ||
WildCompareHwIds(compatIds,templ[argIndex])) {
match = TRUE;
}
}
DelMultiSz(hwIds);
DelMultiSz(compatIds);
}
} else {
match = TRUE;
}
if(match) {
retcode = Callback(devs,&devInfo,devIndex,Context);
if(retcode) {
failcode = retcode;
goto final;
}
}
}
failcode = EXIT_OK;
final:
if(templ) {
delete [] templ;
}
if(devs != INVALID_HANDLE_VALUE) {
SetupDiDestroyDeviceInfoList(devs);
}
return failcode;
}
int
__cdecl
_tmain(int argc, LPTSTR argv[])
/*++
Routine Description:
Main entry point
interpret -m:<machine>
and hand off execution to command
Arguments:
argc/argv - parameters passed to executable
Return Value:
EXIT_xxxx
--*/
{
LPCTSTR cmd;
LPCTSTR baseName;
LPCTSTR machine = NULL;
int dispIndex;
int firstArg = 1;
int retval = EXIT_USAGE;
BOOL autoReboot = FALSE;
//
// syntax:
//
// [options] [-]command [<arg> [<arg>]]
//
// options:
// -m:<machine> - remote
// -r - auto reboot
//
baseName = _tcsrchr(argv[0],TEXT('\\'));
if(!baseName) {
baseName = argv[0];
} else {
baseName = CharNext(baseName);
}
while((argc > firstArg) && ((argv[firstArg][0] == TEXT('-')) || (argv[firstArg][0] == TEXT('/')))) {
if((argv[firstArg][1]==TEXT('m')) || (argv[firstArg][1]==TEXT('M'))) {
if((argv[firstArg][2]!=TEXT(':')) || (argv[firstArg][3]==TEXT('\0'))) {
//
// don't recognize this switch
//
break;
}
machine = argv[firstArg]+3;
} else if((argv[firstArg][1]==TEXT('r')) || (argv[firstArg][1]==TEXT('R'))) {
if((argv[firstArg][2]!=TEXT('\0')) ) {
//
// don't recognize this switch
//
break;
} else {
autoReboot = TRUE;
}
} else {
//
// don't recognize this switch
//
break;
}
firstArg++;
}
if((argc-firstArg) < 1) {
//
// after switches, must at least be command
//
Usage(baseName);
return EXIT_USAGE;
}
cmd = argv[firstArg];
if((cmd[0]==TEXT('-')) || (cmd[0]==TEXT('/'))) {
//
// command may begin '-' or '/'
// eg, people might do devcon -help
//
cmd = CharNext(cmd);
}
firstArg++;
for(dispIndex = 0;DispatchTable[dispIndex].cmd;dispIndex++) {
if(lstrcmpi(cmd,DispatchTable[dispIndex].cmd)==0) {
retval = DispatchTable[dispIndex].func(baseName,machine,argc-firstArg,argv+firstArg);
switch(retval) {
case EXIT_USAGE:
CommandUsage(baseName,DispatchTable[dispIndex].cmd);
break;
case EXIT_REBOOT:
if(autoReboot) {
Reboot();
}
break;
case EXIT_OK:
break;
default:
Failure(baseName,DispatchTable[dispIndex].cmd);
break;
}
return retval;
}
}
Usage(baseName);
return EXIT_USAGE;
}