Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Hardware Helper Library for C#

0.00/5 (No votes)
30 Nov 2007 36  
How to monitor, enable, and disable hardware devices from C#
Screenshot - HardwareHelper.gif

Introduction

A few days back I ran into an issue with a network device that required a "restart" from my C# service. Seemed simple enough, and while I was aware that this type of operation could be performed by spawning a new process and running the infamous "DEVCON", I really couldn't see why I wouldn't be able to wrap those same calls from my C# application. Maybe I'm funny that way. But, I already had a C# library I created a while back to monitor devices being added or dropped from the Windows Device Manager, so the enumeration portion of the equation was already in place. Really how hard could it be?

Honestly I expected that within an hour or two of actually starting, I'd be kicked back in the chair patting myself on the back while my application happily enabled and disabled devices until it's little heart was content. That was not the case. After digging around in the DDK and the SDK for a couple hours, I was reminded once again how humbling a career field we software engineers have chosen. Not one to give up, I banged my head on the table, left a five and a quarter inch floppy under my pillow for the code fairy, and scoured the news groups most of the weekend.

Not surprisingly eventually I had a headache and I never did catch sight of the code fairy. But finally scouring the news groups turned up a couple clues that got my foot in the door. From there it was just a matter of brute force. The result is a helper library that can both monitor devices being added to or removed from the system, as well as a method that can successfully enable or disable any node on the device tree. After so much trouble for a seemingly simple task I figured why not share that library with you, my fellow code warriors. So without further ado, say hello to my little friend, the HardwareHelperLib.

Background

There isn't really a lot of background required for you to use the library I've provided. The heart of HardwareHelperLib is the unmanaged SetupAPI.DLL that is part of the platform SDK. Properly declaring and marshalling the API was the tough part and the whole reason I'm posting this code is so you don't have to spend your whole weekend banging your head against the table too. It's ALWAYS helpful to understand how an operating system interacts with the hardware and if you've never looked at the source code for DEVCON or some of the other drivers in the DDK it certainly wouldn't hurt you, (well it might hurt a little if you've strayed from C++ in favor of C# for a few years), but regardless whether you understand the inner-workings of the class or not you still should be able to use it. The example is very simplistic. The only prerequisite assumed is that you are at least somewhat familiar with Windows messages.

Using the Code

To use the HardwareHelperLib, just add the HardwareHelperLib.csproj file to your solution, and then reference that project in the studio. Add a using clause to the beginning of your source file and you will be ready to go.

using HardwareHelperLib;

The next step is to declare a single global instance of the class for your application. Yes, I could have made the class a singleton pattern but let's face it, I was writing it for something fairly specific to what I was doing and as such wasn't all that worried about how someone else might choose to use or abuse it as the case may be. If you absolutely can't stop yourself from declaring two instances of the HardwareHelperLib, feel free to implement a Singleton or any other design pattern of your choosing. In the mean time the following should suffice.

HH_Lib hwh = new HH_Lib();

Once we have our helper lib, there are basically only four exposed methods. The first is GetAll which returns an array of strings. The result set contains an inventory of all the hardware devices present on your system. These are devices that are physically PRESENT (or at least Windows believes are physically present in the case of software only or virtual devices). It does not matter if the devices are enabled or disabled in the local or global device profile. The syntax is straight-forward.

string[] HardwareList = hwh.GetAll();

It's now easy to loop through and display the same text description for each device that you would see if you opened the Windows Device Manager. In the example below, I'm doing just that, adding the text to a standard list box.

foreach (string s in HardwareList)
{
   listBox1.Items.Add(s);
}

Having an initial list of all the hardware installed on the machine, we will now begin monitoring the system for any hardware changes. This is just a matter of overriding the default window message handler, and then calling the appropriate method in the class. I should note here that while according to the SDK you should be able to hook a window handle or a service handle, I was unsuccessful in getting the latter to operate properly. I have no doubt it can be done but as luck would have it in my service I only needed to enable or disable the devices, not monitor the addition or removal of them, so at the time I chose not to stress over it.

protected override void WndProc(ref Message m)
{
  switch (m.Msg)
  {
    case HardwareHelperLib.Native.WM_DEVICECHANGE:
    {
      if (m.WParam.ToInt32() ==  HardwareHelperLib.Native.DBT_DEVNODES_CHANGED)
      {
        listBox1.Items.Clear();
        string[] HardwareList = hwh.GetAll();
        foreach (string s in HardwareList)
        {
           listBox1.Items.Add(s);
        }
        label1.Text = listBox1.Items.Count.ToString() + " Devices Attached";
      }
      break;
    }
  }
  base.WndProc(ref m);
}

As you can see, when the WM_DEVICECHANGE message arrives, we simply reload the entire list. There is no fancy method of determining which device was added or removed at this time, though I guess if reloading the entire list is a problem you could modify the library to keep up with the hardware list and simply pass the changed items as an event argument.

Once you override the default message handler, you still need to register for the WM_DEVICECHANGE message. I do this in my form load with a simple call to HookHardwareNotifications. The first parameter is a Windows handle (or service handle if I ever get that option working). The second parameter is a Boolean that specifies which of the two the former parameter represents. In other words, true if you are passing a window handle to the method and false if you are passing in a service handle.

hwh.HookHardwareNotifications(this.Handle, true);

That's it for hardware monitoring. If you want to see for yourself, run the sample application and try connecting and disconnecting a USB device. I work a lot in shops that design hardware devices as well as software and so keeping a log of what was added and removed from a particular system is invaluable when troubleshooting.

The other functionality the library provides is that of enabling and disabling devices. Getting access to that feature is just a matter of calling SetDeviceState. In my example, I do this with the selected item from a list box, which I previously populated using GetAll.

string[] devices = new string[1];
devices[0]= listBox1.SelectedItem.ToString();
hwh.SetDeviceState(devices, true);

The first parameter is an array of strings that contains the "match pattern" to let SetDeviceState know which devices are affected. The Boolean that follows is set to true for enabling a device, or false for disabling it.

There are a couple words of explanation required here to accurately describe how "match pattern" works. I realize perhaps it is not the most straight-forward way to select which devices are to be affected, but as I said when I started this I was doing the library for a fairly specific need I had. You are welcome to modify it to match the device criteria differently, or perhaps you'll even want to use the vendor and hardware ID numbers like DEVCON does. However for the purpose of this discussion, I will at least explain how my method works, and why I chose to implement it the way I did.

The array of strings that gets passed to SetDeviceState is a list of non-case sensitive exact string match conditions. And EVERY condition must return true for the device to be selected. The strings you pass in will be compared against an array of strings exactly like that returned by the GetAll() method. Let me give you some examples. Imagine what follows is a partial listing of the hardware tree currently in your device manager.

Network Adapters
Intel(r) 82566DM Gigabit Network Connection
Intel(r) 82566DP Mobile Network Connection
Broadcom Pro 1000 Network Card

Now let us consider that you have arranged an array of "match patterns" to contain the following:

match[0]="Intel"match[1]="Network"match[2]="Connection"

Calling SetDeviceState(match,false) would effectively disable all devices that contained all three match criteria in their description, or the first two adapters. Similarly an array containing:

match[0]="Intel(r) 82566DM Gigabit Network Connection"

will ONLY disable the first adapter in the list when calling SetDeviceState. So far so good. This is probably what you expected and you are wondering why the heck I am spending so much time pointing out what you already deduced. In that case, ponder the following "match pattern" array for a moment.

match[0]="Intel(r) 82566DM Gigabit Network Connection"
match[1]="Intel(r) 82566DP Mobile Network Connection"

At first glance, you might expect a call to SetDeviceState(match,false) to disable both the first two Ethernet adapters in the device manager's list. You would be wrong though. In fact NO devices will be affected because NONE of the devices contain exactly both of the above match criteria. Hopefully, this makes it clear why it is important to consider your match criteria carefully. Accidentally disabling a system-level device in your code or a network adapter you are currently using could have some pretty unpleasant side effects.

This might possibly leave you scratching your head asking why I just didn't use something concrete like the VEN and DEV (or VID and PID in the case of USB devices). The answer is again I was using the library for something I was doing, and that specific scenario required some flexibility. My service is running on a large number of servers, all of which have different networking hardware. I needed to disable many different models of a device within one particular family. What I didn't want to have to do was customize the list for each and every server the software was deployed to. So in that respect, albeit it is slightly confusing to explain to others, the "match pattern" array did just that.

With that said, I believe I have pretty much explained all there is to explain about how to use the library. Of course you'll need to turn off the hardware event monitoring before your application exits. I included the following method call in my form closing event.

hwh.CutLooseHardwareNotifications(this.Handle);

The call will not fail if you are not currently "hooking" hardware notifications, so be sure you call this method before exiting. I say better safe than sorry. Such methods make use of unmanaged resources and it is important to give them the opportunity to perform a proper memory clean up before exiting.

Points of Interest

One final point of interest here is that some devices will require a reboot after enabling or disabling them before the change will go into effect. The unmanaged SetupAPI.Dll has two special flags DI_NEEDRESTART | DI_NEEDREBOOT which get combined with a logical OR operation and passed to SetupDiGetDeviceInstallParams to determine whether or not a reboot will be required. I already knew my devices did not require a restart, in fact if they did, in my particular situation this solution would not have been viable. However, if you want to know whether or not your device state change requires a reboot, I would think you could easily insert this functionality just by taking a look at how it is done inside of the DDK (again see the DEVCON source files).

History

  • Hardware Helper Lib (v)1.0.0 WJF 11/26/07: Original implementation
  • Hardware Lib Test (v)1.0.0 WJF 11/26/07: Original implementation

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here