Trigger a hardware detection scan from Delphi, InstallShield, C++, script or Run prompt

Ξ January 4th, 2007 | → | ∇ Batch, Delphi, RegEdit, WINDOWS, XP, installation |

In my deployment process, it had looked like I was going to need to detect some changes in hardware and then perform a reboot.
I researched how to do this but it turns ou that I don’t need this code. Into the cave it goes.

You can of course run the “Add New Hardware” wizard manually. Here’s the command line to do just that:
“C:\WINDOWS\system32\rundll32.exe” C:\WINDOWS\system32\shell32.dll,Control_RunDLL “C:\WINDOWS\system32\hdwwiz.cpl”,Detect Hardware

However, what if you want to automate the process.

The information for how to do this is relatively scarce even though there is a technet page about it. Strangely enough the first thing I found was an NSIS script for doing this through that open source instalation program. The strange thing about it is that it was on a WinAMP website (link).

Here’s that code:

CODE:
  1. Function ScanForNewHW
  2. SetPluginUnload alwaysoff
  3. StrCpy $1 “”
  4.  
  5. System::Call ’setupapi::CM_Locate_DevNodeA(*i .r0, t r1, i r2) i .r3′
  6. System::Call ’setupapi::CM_Reenumerate_DevNode(i r0, i r4) i .r5′
  7.  
  8. SetPluginUnload manual
  9. System::Free 0
  10. FunctionEnd

Armed with the DLL name, the second thing I found was an Install Shield script (link) that allowed it to be done:

CODE:
  1. function ScanForHardwareChanges()
  2.   NUMBER devInst, myreturn;
  3. begin
  4.   if(UseDLL(WINSYSDIR ^ “cfgmgr32.dll”) != 0)then
  5.     MessageBox(“Didn’t load Dll”, SEVERE);
  6.     return FALSE;
  7.   endif;
  8.   myreturn = CM_Locate_DevNodeA(&devInst, \0, 0);
  9.   myreturn = CM_Reenumerate_DevNode(devInst, 0);
  10.   UnUseDLL(WINSYSDIR ^ “cfgmgr32.dll”);
  11.   return TRUE;
  12. end;

Armed with the DLL name and a possible procedure name, I was able to track down the Microsoft support page about it (link). That page provided a C routine for calling the code. Here it is:

C:
  1. BOOL ScanForHardwareChanges()
  2. {
  3.     DEVINST     devInst;
  4.     CONFIGRET   status;
  5.    
  6.     //
  7.     // Get the root devnode.
  8.     //
  9.    
  10.     status = CM_Locate_DevNode(&devInst, NULL, CM_LOCATE_DEVNODE_NORMAL);
  11.    
  12.     if (status != CR_SUCCESS) {
  13.         printf(“CM_Locate_DevNode failed: %x\n, status);
  14.         return FALSE;
  15.  
  16.     }
  17.    
  18.     status = CM_Reenumerate_DevNode(devInst, 0);
  19.    
  20.     if (status != CR_SUCCESS) {
  21.         printf(“CM_Reenumerate_DevNode failed: %x\n, status));
  22.         return FALSE;
  23.     }
  24.  
  25.     return TRUE;
  26. }

However, I wanted to do this in Delphi. With the correct constant names, I was able to find two references to this routine. The Delphi JEDI project has a provides a routine for loading the DLL that allows these calls to be made and either someone (link) translated Microsoft’s code into a routine for scanning for the hardware or there was a, now gone, JEDI demo project that included this routine. Either way, the French site was the first one I’d found that scanned for new hardware with Delphi.

Here is that code:

DELPHI:
  1. procedure SomeProcedure;
  2.   // First you need to load the module. 
  3.   LoadConfigManagerApi; 
  4.   // Then call a translation of the MS routine
  5.   ScanForHardwareChanges;
  6. end;
  7.  
  8. //  Here’s the translation of the ScanForHardwareChanges
  9. function ScanForHardwareChanges: boolean;
  10. var
  11.   dev: DEVINST;
  12.   status: CONFIGRET;
  13. begin
  14.  
  15.   status := CM_Locate_DevNode(dev, , CM_LOCATE_DEVNODE_NORMAL);
  16.  
  17.   if (status <> CR_SUCCESS) then
  18.   begin
  19.     result := FALSE;
  20.     exit;
  21.   end;
  22.  
  23.   status := CM_Reenumerate_DevNode(dev, 0);
  24.  
  25.   if (status <> CR_SUCCESS) then
  26.   begin
  27.     result := FALSE;
  28.     exit;
  29.   end;
  30.   Result := TRUE;
  31. end;

That routine was picked up on a Russian site (link) and modified to be independent of the JEDI files. However, both of these routines include way more information than is needed.

The process is really simple.
1. Load the DLL
2. Get the location of the two methods you need.
3. Call them (using the appropriate constants
4. Unload everything.

I’ve written my own Delphi routine that does all that and has no extra baggage dragged (drug?) along for the ride..

My all-in-one solution:

DELPHI:
  1. {******************************************************************************
  2.   ScanForHardwareChanges
  3.   by Brian Layman at TheCodeCave.com
  4. ******************************************************************************}
  5. function ScanForHardwareChanges: Boolean;
  6. const
  7.   CFGMGR32_DLL                 = ‘cfgmgr32.dll’;
  8.   CM_LOCATE_DEVNODE_NAME       = ‘CM_Locate_DevNodeA’;
  9.   CM_REENUMERATE_DEVNODE_NAME  = ‘CM_Reenumerate_DevNode’;
  10.   CM_LOCATE_DEVNODE_NORMAL     = $00000000;
  11.   CR_SUCCESS                   = $00000000;
  12. var
  13.    DeviceNode: DWord;
  14.    HCfgMgr: THandle;
  15.    CM_Locate_DevNode: function(var dnDevInst: DWord; pDeviceID: PAnsiChar;
  16.                                ulFlags: ULONG): DWord; stdcall;
  17.    CM_Reenumerate_DevNode: function(dnDevInst: DWord; ulFlags: ULong): DWord; stdcall;
  18. begin // ScanForHardwareChanges
  19.   Result := FALSE;
  20.   HCfgMgr := LoadLibrary(CFGMGR32_DLL);
  21.   if (HCfgMgr <32)
  22.   then MessageDlg(‘Error: could not find Configuration Manager DLL’, mtError, [mbOk], 0)
  23.   else begin
  24.     try
  25.       CM_Locate_DevNode := GetProcAddress(HCfgMgr, CM_LOCATE_DEVNODE_NAME);
  26.       CM_Reenumerate_DevNode := GetProcAddress(HCfgMgr, CM_REENUMERATE_DEVNODE_NAME);
  27.       if (CM_Locate_DevNode(DeviceNode, NIL, CM_LOCATE_DEVNODE_NORMAL) = CR_SUCCESS)
  28.       then Result := (CM_Reenumerate_DevNode(DeviceNode, 0) = CR_SUCCESS);
  29.     finally // wrap up
  30.       FreeLibrary(HCfgMgr);
  31.     end;    // try/finally
  32.   end;
  33. end// ScanForHardwareChanges

As a bonus, here it is combined into a project that scans for new hardware and then reboots the computer.

To test this yourself, just create a basic project1 unit1 project, put a TButton on the form, paste this text over unit1’s existing code, and then double click the button. You’ll then have a compilable program. You can modify it to eliminate the form if you want and do all of this between begin and end in the project unit itself.

Here you go:

DELPHI:
  1. // ****************************************************************************
  2. //  Project1                                                                                                          04/Jan/2007
  3. //
  4. //  Written by Brian Layman (AKA Capt. Queeg AKA SilverPaladin)
  5. //  Visit him at http://www.TheCodeCave.com
  6. //
  7. //  This is a simple demonstration to show how a Delphi program can be used
  8. //  to detect new hardware and reboot.
  9. //
  10. //  Warning: I can’t think of any way that this routine could cause harm to .
  11. //  your computer, but it is a good best practice to understand every line
  12. //  of new code before you run it.  Who knows what could be lurking. Better
  13. //  yet, do not run this example at all. You should stop right now and erase
  14. //  the files.  For if it causes blue smoke to be emitted from your network
  15. //  card, if it erases all users from your computer, or if it makes your
  16. //  sister break up with her lawyer boyfriend and start dating a caver, it
  17. //  is not my fault.  (Actually that last one might be an improvement, but
  18. //  it is still not my fault.) But the fact of the matter is, computers
  19. //  have a mind of their own and we programmers live on the wild side.
  20. //
  21. //  Usage: Project1.exe
  22. //  Run it. Press the button.  WARNING YOU WILL REBOOT AND MAY LOOSE WORK.
  23. //
  24. //  History:
  25. //    04/Jan/2007 - BL - Created
  26. //
  27. // ****************************************************************************
  28. unit Unit1;
  29.  
  30. interface
  31.  
  32. uses
  33.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  34.   StdCtrls, ShellAPI;
  35.  
  36. type
  37.   TForm1 = class(TForm)
  38.     Button1: TButton;
  39.     procedure Button1Click(Sender: TObject);
  40.   private
  41.     { Private declarations }
  42.   public
  43.     { Public declarations }
  44.   end;
  45.  
  46. var
  47.   Form1: TForm1;
  48.  
  49. implementation
  50.  
  51. {$R *.DFM}
  52.  
  53. {******************************************************************************
  54.   SetTokenPrivilege
  55.   A helper function that enables or disables specific privileges on the
  56.   specified computer.  A NIL in SystemName means the privilege will be granted
  57.   for the current computer.  Any other value must match the name of a computer
  58.   on your network.
  59. ******************************************************************************}
  60. procedure SetTokenPrivilege(aSystemName: PChar; aPrivilegeName: PChar; aEnabled: Boolean);
  61. var
  62.   TTokenHd: THandle;
  63.   TTokenPvg: TTokenPrivileges;
  64.   cbtpPrevious: DWORD;
  65.   rTTokenPvg: TTokenPrivileges;
  66.   pcbtpPreviousRequired: DWORD;
  67.   TokenOpened, ValueFound: Boolean;
  68. const
  69.   // Custom Constants
  70.   // The SE_PRIVILEGE_DISABLED = 0 is an assumption that works.
  71.   // The default I’ve seen retrieved was 2012309862 I suspect that was just junk bits
  72.   SE_PRIVILEGE_DISABLED = 0;
  73. begin // SetTokenPrivilege
  74.   // The privilege system is only available on NT and beyond
  75.   if (Win32Platform = VER_PLATFORM_WIN32_NT)
  76.   then begin
  77.     // Retrieve the Token that represents this current application session
  78.     TokenOpened := OpenProcessToken(GetCurrentProcess(),
  79.                                     TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,
  80.                                     TTokenHd);
  81.  
  82.     // Check for failure
  83.     if (not TokenOpened)
  84.     then raise Exception.Create(‘The current user does not have the access ‘ +
  85.                                 ‘required to run this program.’)
  86.     else begin
  87.       // Get the name of the privilege (since Windows is multi-lingual, this must be done)
  88.       ValueFound := LookupPrivilegeValue(aSystemName, aPrivilegeName, TTokenPvg.Privileges[0].Luid) ;
  89.       TTokenPvg.PrivilegeCount := 1;
  90.  
  91.       // Enable or disable the flag according to the bool passed
  92.       if (aEnabled)
  93.       then TTokenPvg.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED
  94.       else TTokenPvg.Privileges[0].Attributes := SE_PRIVILEGE_DISABLED; // See declaration
  95.       cbtpPrevious := SizeOf(rTTokenPvg) ;
  96.       pcbtpPreviousRequired := 0;
  97.       if (not ValueFound)
  98.       then raise Exception.Create(‘This program is incompatible with the ‘ +
  99.                                  ‘operating system installed on this computer.’)
  100.       else begin
  101.         try
  102.           // Adjust the permissions as required.
  103.           Windows.AdjustTokenPrivileges(TTokenHd, False, TTokenPvg, cbtpPrevious,
  104.                                         rTTokenPvg, pcbtpPreviousRequired);
  105.         except
  106.           raise Exception.Create(‘The current user does not have the required ‘ +
  107.                                  ‘access to load a registry hive.’)
  108.         end;
  109.       end;
  110.     end
  111.   end;
  112. end// SetTokenPrivilege
  113.  
  114. {******************************************************************************
  115.   GrantPrivilege
  116.   This routine grants the privilege(s) needed to access the hidden system hive
  117.   and load it into memory.
  118. ******************************************************************************}
  119. procedure GrantPrivilege(aPrivilegeName: String);
  120. begin // GrantPrivilege
  121.   SetTokenPrivilege(NIL, PChar(aPrivilegeName), TRUE);
  122. end// GrantPrivilege
  123.  
  124. {******************************************************************************
  125.   RevokePrivilege
  126.   This routine revokes privilege(s) given in GrantPrivilege
  127. ******************************************************************************}
  128. procedure RevokePrivilege(aPrivilegeName: String);
  129. begin // RevokePrivilege
  130.   SetTokenPrivilege(NIL, PChar(aPrivilegeName), FALSE);
  131. end// RevokePrivilege
  132.  
  133. {******************************************************************************
  134.   RebootSystem
  135. ******************************************************************************}
  136. function RebootSystem(Message: String; Timeout: DWord;
  137.                         ForceClose: WordBool = FALSE): WordBool;
  138. begin // RebootSystem
  139.   if (Message = ) then Message := #0; //null terminate for next
  140.  
  141.   GrantPrivilege(‘SeShutdownPrivilege’);
  142.   try
  143.     Result := InitiateSystemShutdown(NIL, @Message[1], TimeOut, ForceClose, TRUE);
  144.   finally
  145.     RevokePrivilege(‘SeShutdownPrivilege’);
  146.   end;
  147. end// RebootSystem
  148.  
  149.  
  150. {******************************************************************************
  151.   ScanForHardwareChanges
  152. ******************************************************************************}
  153. function ScanForHardwareChanges: Boolean;
  154. const
  155.   CFGMGR32_DLL                 = ‘cfgmgr32.dll’;
  156.   CM_LOCATE_DEVNODE_NAME       = ‘CM_Locate_DevNodeA’;
  157.   CM_REENUMERATE_DEVNODE_NAME  = ‘CM_Reenumerate_DevNode’;
  158.   CM_LOCATE_DEVNODE_NORMAL     = $00000000;
  159.   CR_SUCCESS                   = $00000000;
  160. var
  161.    DeviceNode: DWord;
  162.    HCfgMgr: THandle;
  163.    CM_Locate_DevNode: function(var dnDevInst: DWord; pDeviceID: PAnsiChar;
  164.                                ulFlags: ULONG): DWord; stdcall;
  165.    CM_Reenumerate_DevNode: function(dnDevInst: DWord; ulFlags: ULong): DWord; stdcall;
  166. begin // ScanForHardwareChanges
  167.   Result := FALSE;
  168.   HCfgMgr := LoadLibrary(CFGMGR32_DLL);
  169.   if (HCfgMgr <32)
  170.   then MessageDlg(‘Error: could not find Configuration Manager DLL’, mtError, [mbOk], 0)
  171.   else begin
  172.     try
  173.       CM_Locate_DevNode := GetProcAddress(HCfgMgr, CM_LOCATE_DEVNODE_NAME);
  174.       CM_Reenumerate_DevNode := GetProcAddress(HCfgMgr, CM_REENUMERATE_DEVNODE_NAME);
  175.       if (CM_Locate_DevNode(DeviceNode, NIL, CM_LOCATE_DEVNODE_NORMAL) = CR_SUCCESS)
  176.       then Result := (CM_Reenumerate_DevNode(DeviceNode, 0) = CR_SUCCESS);
  177.     finally // wrap up
  178.       FreeLibrary(HCfgMgr);
  179.     end;    // try/finally
  180.   end;
  181. end// ScanForHardwareChanges
  182.  
  183.  
  184. {******************************************************************************
  185.   Button1Click
  186. ******************************************************************************}
  187. procedure TForm1.Button1Click(Sender: TObject);
  188. begin // Button1Click
  189.   if (ScanForHardwareChanges)
  190.   then RebootSystem(‘Hardware change Successful’, 15);
  191. end// Button1Click
  192.  
  193. end.

 

2 Responses to ' Trigger a hardware detection scan from Delphi, InstallShield, C++, script or Run prompt '

Subscribe to comments with RSS or TrackBack to ' Trigger a hardware detection scan from Delphi, InstallShield, C++, script or Run prompt '.

  1. someone said,

    on May 30th, 2008 at 1:08 pm

    Year and a half old but still it was usefull. Thanks man ;) Can't stand using huge libraries just for a few lines of code...

  2. Raven said,

    on November 27th, 2008 at 11:25 am

    Thanks for this article. It was very useful for me
    з.ы. Перевод статьи на русский посредством i18ln - жжот!

Leave a reply