A Simple Device ID Class

I've received a number of questions regarding how best to retrieve the Device ID. As a result, I've decided to provide the following class for your coding pleasure:

--- jeh
This posting is provided “AS IS“, without warranties, and confers no rights.


using System;
using System.Runtime.InteropServices;

namespace DeviceID
{
 public sealed class DeviceIDException : Exception
{
  public DeviceIDException() : base() {}
  public DeviceIDException(string message) : base(message) {}
  public DeviceIDException(string message, Exception innerException) : base(message, innerException) {}
}

 /// <summary>
 /// Summary description for DeviceID.
 /// </summary>
 public sealed class DeviceID
{
  private const int GuidLength = 16;
  private const Int32 ERROR_NOT_SUPPORTED = 0x32;
  private const Int32 ERROR_INSUFFICIENT_BUFFER = 0x7A;
  private const Int32 IOCTL_HAL_GET_DEVICEID = 0x01010054;

  private byte[] idBytes = null;
  private Guid idGuid = Guid.Empty;
  private string idString = String.Empty;

  public DeviceID()
{
   byte[] buffer = new byte[20];
   bool idLoaded = false;
   uint bytesReturned;

   while(!idLoaded)
{
Array.Copy(BitConverter.GetBytes(buffer.Length), 0, buffer, 0, 4);

    try
{
idLoaded = KernelIoControl(IOCTL_HAL_GET_DEVICEID, 0, 0, buffer, (uint)buffer.Length, out bytesReturned);
}
    catch (Exception e)
{
     throw new DeviceIDException("This platform may not support DeviceIDs", e);
}

    if(!idLoaded)
{
     int error = Marshal.GetLastWin32Error();

     if(error == ERROR_INSUFFICIENT_BUFFER)
{
buffer = new byte[BitConverter.ToUInt32(buffer, 0)];
}
     else
{
      // Some older PPC devices only return the ID if the buffer
      // is exactly the size of a GUID, so attempt to retrieve
      // the ID this way before throwing an exception
      buffer = new byte[GuidLength];
idLoaded = KernelIoControl(IOCTL_HAL_GET_DEVICEID, 0, 0, buffer, GuidLength, out bytesReturned);

      if(idLoaded)
{
InitializeFromBytes(buffer);
       return;
}
      else
{
       if(error == ERROR_NOT_SUPPORTED)
{
        throw new DeviceIDException("This platform does not support DeviceIDs");
}
       else
{
        throw new DeviceIDException(String.Format(
         "Error Encountered Retrieving ID (0x{0})", error.ToString("X8")));
}
}
}
}
}

   int dwPresetIDOffset = BitConverter.ToInt32(buffer, 4);
   int dwPresetIDBytes = BitConverter.ToInt32(buffer, 8);
   int dwPlatformIDOffset = BitConverter.ToInt32(buffer, 12);
   int dwPlatformIDBytes = BitConverter.ToInt32(buffer, 16);

idBytes = new byte[dwPresetIDBytes + dwPlatformIDBytes];

Array.Copy(buffer, dwPresetIDOffset, idBytes, 0, dwPresetIDBytes);
Array.Copy(buffer, dwPlatformIDOffset, idBytes, dwPresetIDBytes, dwPlatformIDBytes);
}

  public DeviceID(Guid g)
{
idBytes = g.ToByteArray();
idGuid = g;
}

  public DeviceID(byte[] bytes)
{
InitializeFromBytes(bytes);
}

  private void InitializeFromBytes(byte[] bytes)
{
idBytes = new byte[bytes.Length];
Array.Copy(bytes, 0, idBytes, 0, bytes.Length);
}

  /// <summary>
  /// DeviceIDs are only guaranteed to be Guids on PPC. On generic Windows CE, they can be
  /// any unspecified length
  /// </summary>
  public bool IsGuid
{
get
{
    return (idBytes.Length == GuidLength);
}
}

  public Guid Guid
{
get
{
    if(IsGuid)
{
     if(idGuid == Guid.Empty)
{
idGuid = new Guid(idBytes);
}

     return idGuid;
}
    else
{
     throw new DeviceIDException(String.Format("The DeviceID {0} is not a Guid", ToString()));
}
}
}

  public override bool Equals(object obj)
{
   if(obj is DeviceID)
{
DeviceID rhs = (DeviceID)obj;

    if(idBytes.Length == rhs.idBytes.Length)
{
     for(int i = 0; i < idBytes.Length; i++)
{
      if(idBytes[i] != rhs.idBytes[i])
{
       return false;
}
}

     return true;
}
}

   return false;
}

  public override int GetHashCode()
{
   if(IsGuid)
{
    return this.Guid.GetHashCode();
}
   else
{
    // The default GetHashCode for object is guaranteed to
    // always be the same for a given object, but not for
    // multiple objects with the same value. We want a HashCode
    // that will always be the same for a given ID
    byte[] tempbytes = new byte[16];
    if(idBytes.Length > 16)
{
Array.Copy(idBytes, 0, tempbytes, 0, 16);
}
    else
{
Array.Clear(tempbytes, 0, 16); // Should be unneccessary
     Array.Copy(idBytes, 0, tempbytes, 0, idBytes.Length);
}

    return (new Guid(tempbytes)).GetHashCode();
}
}

  public override string ToString()
{
   if(idString == String.Empty)
{
    if(IsGuid)
{
idString = this.Guid.ToString();
}

    else
{
idString = "";

     for(int i = 0; i < idBytes.Length; i++)
{
      if(i == 4 || i == 6 || i == 8 || i == 10)
{
idString = String.Format("{0}-{1}", idString, idBytes[i].ToString("x2"));
}
      else
{
idString = String.Format("{0}{1}", idString, idBytes[i].ToString("x2"));
}
}
}
}

   return idString;
}

  public byte[] ToByteArray()
{
   byte[] cpyBytes = new byte[idBytes.Length];
Array.Copy(idBytes, 0, cpyBytes, 0, idBytes.Length);
   return cpyBytes;
}

[DllImport("coredll.dll", SetLastError=true)]
  private static extern bool KernelIoControl(
             uint dwIoControlCode,
             uint lpInBuf /* set to 0 */,
             uint nInBufSize /* set to 0 */,
[In, Out] byte[] lpOutBuf,
             uint nOutBufSize,
   out     uint lpBytesReturned);
}
}