text: /*++
  
  Copyright (c) Microsoft Corporation.  All rights reserved.
  
  Module Name:
  
      devcon.cpp
  
  Abstract:
  
      Device Console
      command-line interface for managing devices
  
  --*/
  
  #include "devcon.h"
  
  struct IdEntry {
      LPCTSTR String;     // string looking for
      LPCTSTR Wild;       // first wild character if any
      BOOL    InstanceId;
  };
  
  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;
  }
  
  LPTSTR * CopyMultiSz(LPTSTR * Array)
  /*++
  
  Routine Description:
  
      Creates a new array from old
      old array need not have been allocated by GetMultiSzIndexArray
  
  Arguments:
  
      Array - array of strings, last entry is NULL
  
  Return Value:
  
      MultiSz array allocated by GetMultiSzIndexArray
  
  --*/
  {
      LPTSTR multiSz = NULL;
      int len = 0;
      int c;
      if(Array) {
          for(c=0;Array[c];c++) {
              len+=lstrlen(Array[c])+1;
          }
      }
      len+=1; // final Null
      multiSz = new TCHAR[len];
      if(!multiSz) {
          return NULL;
      }
      len = 0;
      if(Array) {
          for(c=0;Array[c];c++) {
              lstrcpy(multiSz+len,Array[c]);
              len+=lstrlen(multiSz+len)+1;
          }
      }
      multiSz[len] = TEXT('\0');
      LPTSTR * pRes = GetMultiSzIndexArray(multiSz);
      if(pRes) {
          return pRes;
      }
      delete [] multiSz;
      return NULL;
  }
  
  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[])
  
  {
      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) {
          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(_tcsicmp(cmd,DispatchTable[dispIndex].cmd)==0) {
              retval = DispatchTable[dispIndex].func(baseName,machine,argc-firstArg,argv+firstArg);
              switch(retval) {
                  case EXIT_USAGE:
                      break;
                  case EXIT_REBOOT:
                      if(autoReboot) {
                          Reboot();
                      }
                      break;
                  case EXIT_OK:
                      break;
                  default:
                      break;
              }
              return retval;
          }
      }
      return EXIT_USAGE;
  }
  