Saturday, July 9, 2011

Uninstalling devices and hardware reenumeration in Windows - C# in action

Recently I came across the following problem: how to re-install some device in MS Windows in the easiest possible way (assuming a target user has no knowledge of the system, whatsoever, maybe with exception of an Internet browser). The solution is to write a simple app, which does everything automatically. The idea was quite simple: uninstall the device and then rescan all hardware for changes, letting the PnP mechanism do the rest. Language: C# and SetupAPI (Windows Driver Kit) hidden behind fancy Windows Forms GUI. Here's the code.

Declarations:
using System.Runtime.InteropServices;
// ...
public const int CR_SUCCESS = 0x00000000;
public const int CM_LOCATE_DEVNODE_NORMAL = 0x00000000;
public const int DIGCF_PRESENT = 0x00000002;
public const int DIF_REMOVE = 0x00000005;

// devnode info struct
[StructLayout(LayoutKind.Sequential)]
public class SP_DEVINFO_DATA
{
    public int cbSize;
    public Guid ClassGuid;
    public int DevInst;

    public ulong Reserved;
}

// importing external SetupAPI methods
[DllImport("cfgmgr32.dll")]
public static extern UInt32 CM_Locate_DevNode(ref UInt32 DevInst, string pDeviceID, UInt32 Flags);

[DllImport("cfgmgr32.dll", SetLastError = true)]
public static extern UInt32 CM_Reenumerate_DevNode(UInt32 DevInst, UInt32 Flags);

[DllImport("setupapi.dll")]
public static extern Boolean SetupDiClassGuidsFromNameA(string ClassName, ref Guid Guids, UInt32 ClassNameSize, ref UInt32 RequiredSize);

[DllImport("setupapi.dll")]
public static extern IntPtr SetupDiGetClassDevsA(ref Guid ClassGuid, UInt32 Enumerator, IntPtr hwndPtr, UInt32 Flags);

[DllImport("setupapi.dll")]
public static extern Boolean SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, UInt32 DeviceIndex, SP_DEVINFO_DATA DeviceInfoData);

[DllImport("setupapi.dll", SetLastError = true)]
public static extern Boolean SetupDiCallClassInstaller(UInt32 InstallFunction, IntPtr DeviceInfoSet, SP_DEVINFO_DATA DeviceInfoData);

[DllImport("setupapi.dll")]
public static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
Uninstalling all devices in class:
private Int32 RemoveAllDevicesInClassName(string ClassName)
    {
        IntPtr NewDeviceInfoSet;
        UInt32 RequiredSize = 0;
        SP_DEVINFO_DATA DeviceInfoData = new SP_DEVINFO_DATA();
        Guid[] Guids = new Guid[1];

  // getting required size to store Guids for given class name
        Boolean result = SetupDiClassGuidsFromNameA(
                                ClassName, ref Guids[0], RequiredSize, ref RequiredSize);
        if (RequiredSize == 0)
        {
            // incorrect class name
            return -4;
        }
        if (!result)
        {
            Guids = new Guid[RequiredSize];
   // getting the actual Guids
            result = SetupDiClassGuidsFromNameA(
                                ClassName, ref Guids[0], RequiredSize, ref RequiredSize);
        }
        if (!result)
        {
            // incorrect class name
            return -4;
        }
  // getting all present devnodes for given Guid
        NewDeviceInfoSet = SetupDiGetClassDevsA(ref Guids[0], 0, IntPtr.Zero, DIGCF_PRESENT);

        if (NewDeviceInfoSet.ToInt32() == -1)
        {
            // unavailable
            return -8;
        }

  // preparing device info struct
        DeviceInfoData.cbSize = 28;
        DeviceInfoData.ClassGuid = Guid.Empty;
        DeviceInfoData.DevInst = 0;
        DeviceInfoData.Reserved = 0;

        UInt32 i;
  // for each device in class (set of devnodes pointed by NewDeviceInfoSet)
        for (i = 0; SetupDiEnumDeviceInfo(NewDeviceInfoSet, i, DeviceInfoData); i++)
        {
   // invoke class installer method with DIF_REMOVE flag
            if (!SetupDiCallClassInstaller(DIF_REMOVE, NewDeviceInfoSet, DeviceInfoData))
            {
                // failed to uninstall device
    SetupDiDestroyDeviceInfoList(NewDeviceInfoSet);
                return -16;
            }
        }
  // perform cleanup
        SetupDiDestroyDeviceInfoList(NewDeviceInfoSet);
        return CR_SUCCESS;
    }
Forcing hardware reenumeration:
private Int32 ScanForHardwareChanges()
    {
        UInt32 DevInst = 0;
        UInt32 result = CM_Locate_DevNode(ref DevInst, null, CM_LOCATE_DEVNODE_NORMAL);
        if (result != CR_SUCCESS)
        {
            // failed to get devnode
            return -1;
        }
        result = CM_Reenumerate_DevNode(DevInst, 0);

        if (result != CR_SUCCESS)
        {
            // reenumeration failed
            return -2;
        }
        return CR_SUCCESS;
    }

Method invocation:
private void btnFixme_Click(object sender, EventArgs e)
    {
        // remove all devices of class "Image" (image capturing devices)
        Int32 status = RemoveAllDevicesInClassName("Image");
        if (status == CR_SUCCESS)
        {
            status = ScanForHardwareChanges();
        }
        lblStatus.Text = status==CR_SUCCESS ? "Done!" : "Error: " + status.ToString();
    }
I hope the code is commented well enough, and doesn't require further explanation.

1 comment:

  1. Thanks! Was looking for something like this for a long time? BTW, Is there a flag opposite to DIGCF_PRESENT, so I just get hidden devices?

    ReplyDelete