Dwarfsoft [GPA]

Windows Disk LUN WWID

by on Aug.28, 2014, under Scripting, Storage, Tweet, Work


Print This Post Print This Post

One of my main issues with attempting to identify storage on Windows Systems is that the Bus/LUN Id can be stored in multiple places, and trying to track that back to a Volume is pretty hard. So after doing some fairly deep investigation into DeviceIOControl calls to query Page 80 (Device information such as Vendor and type) and Page 83 (which returns the WWID object) I have managed to wrangle the C# into a Type usable from a PowerShell Script.

What is a WWID?

Many things fall under the category of WWID. This includes standard WWNs (including category 1, 2 and 5 style WWNs) as well as extended information style WWNs (category 6). Example of different forms of WWIDs:
NAA
Name
Use
Example
1
IEEE 803.2 standard 48 bit ID
Standard WWN Addressing
10:00:00:00:c9:22:fc:01
2
IEEE 803.2 extended 48-bit ID
Allows some Vendor defined sections to allow better identification of Ports or extend the serial number
20:00:00:e0:8b:05:05:04
5
IEEE Registered Name
WWN FC address for a named device. Allows vendors to create unique Identifiers without having to maintain a database of Serial Number codes.
50:06:04:81:D6:F3:45:42
6
IEEE Extended Registered Name
Identifying objects such as disks
60:06:0e:80:05:34:2a:00:00:00:34:2a:00:00:10:00

More information about NAA 1, 2 and 5 can be obtained from http://howto.techworld.com/storage/156/how-to-interpret-worldwide-names/, which was my first point of call when starting my deep dive into WWIDs. http://www.computerworld.com.au/article/54436/how_interpret_san_worldwide_names/ also provides much the same information, and http://storagemeat.blogspot.com.au/2012/08/decoding-wwids-or-how-to-tell-whats-what.html talks more directly about NAA 6 WWIDs and how to decode information from them for HDS arrays.

Scripts for dealing with Disk WWID (NAA=6)

The below script queries the Physical Disks for their WWIDs (eg: \\.\PHYSICALDISK1) under the function SAN-GetDiskWWIDs. I mentioned that it is sometimes quite hard to match the Volume back to a physical disk, so fortunately this method will work for that purpose also (using SAN-GetVolumeWWID). There is quite a long bit of setup for the StorageDevice type, and some could probably be trimmed, but I have cut it down only as far as I am willing in order to get the script to function. It is an amalgamation of multiple scripts in languages from C#, C++, VB.Net, and pure C, and has taken quite some time to refine down to a working query. In order to query against the device instance in memory we are mapping from PowerShell back to Managed .NET code in C#, which is in turn referencing kernel32.dll in unmanaged codespace and executing queries to access memory directly in the kernel memory space. This is certainly not pretty, but the results are worth the effort when easily identifying Disks on a given system.
Function SAN-DefineStorageDevice()
{
  return @'
// [StorageDevice.Program]::
using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
 
namespace StorageDevice
{
    public class Program
    {
        // For CreateFile to get handle to drive
        private const uint FILE_SHARE_READ = 0x00000001;
        private const uint FILE_SHARE_WRITE = 0x00000002;
        private const uint OPEN_EXISTING = 3;
        private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
        
        private const Int32 IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400;
        private const Int32 PropertyStandardQuery = 0;
        private const Int32 StorageDeviceProperty = 0;
        private const Int32 StorageAdapterProperty = 1;
        private const Int32 StorageDeviceIdProperty = 2;
        private const Int32 StorageDeviceUniqueIdProperty = 3;
 
    public enum StorageIdCodeSet 
    { 
        Reserved = 0, 
        Binary = 1, 
        Ascii = 2, 
        Utf8 = 3 
    } 
 
    public enum StorageIdType 
    { 
        VendorSpecific = 0, 
        VendorId = 1, 
        EUI64 = 2, 
        FCPHName = 3, 
        PortRelative = 4, 
        TargetPortGroup = 5, 
        LogicalUnitGroup = 6, 
        MD5LogicalUnitIdentifier = 7, 
        ScsiNameString = 8 
    } 
 
    public enum StorageIdAssoc 
    { 
        Device = 0, 
        Port = 1, 
        Target = 2 
    } 
    
        // CreateFile to get handle to drive
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern SafeFileHandle CreateFileW(
            [MarshalAs(UnmanagedType.LPWStr)]
            string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile);
 
        // For control codes
        private const uint IOCTL_VOLUME_BASE = 0x00000056;
        private const uint METHOD_BUFFERED = 0;
        private const uint FILE_ANY_ACCESS = 0;
 
        private static uint CTL_CODE(uint DeviceType, uint Function,
                                     uint Method, uint Access)
        {
            return ((DeviceType << 16) | (Access << 14) |
                    (Function << 2) | Method);
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct STORAGE_PROPERTY_QUERY
        {
            public Int32 PropertyId;
            public Int32 QueryType;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
            public byte[] AdditionalParameters;
        }
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
        public struct STORAGE_IDENTIFIER 
        { 
            public StorageDevice.Program.StorageIdCodeSet CodeSet; 
            public StorageDevice.Program.StorageIdType Type; 
            public UInt16 IdentifierSize; 
            public UInt16 NextOffset; 
            public StorageDevice.Program.StorageIdAssoc Association; 
         
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)] 
            public string Identifiers; 
        } 
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
        public struct STORAGE_DEVICE_ID_DESCRIPTOR 
        { 
            public UInt32 Version; 
            public UInt32 Size; 
            public UInt32 NumberOfIdentifiers; 
            
            //[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)] 
            //public String Identifiers; 
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10240)]
            public System.Byte[] Identifiers;
            //public UChar Identifiers;
        } 
 
        [StructLayout(LayoutKind.Sequential)]
        public struct STORAGE_DEVICE_DESCRIPTOR
        {
            public Int32 Version;
            public Int32 Size;
            public System.Byte DeviceType;
            public System.Byte DeviceTypeModifier;
            public System.Byte RemovableMedia;
            public System.Byte CommandQueueing;
            public Int32 VendorIdOffset;
            public Int32 ProductIdOffset;
            public Int32 ProductRevisionOffset;
            public Int32 SerialNumberOffset;
            public System.Byte BusType;
            public Int32 RawPropertiesLength;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10240)]
            public System.Byte[] RawDeviceProperties;
        }
        
        // For DeviceIoControl to get disk extents
        [StructLayout(LayoutKind.Sequential)]
        private struct DISK_EXTENT
        {
            public uint DiskNumber;
            public long StartingOffset;
            public long ExtentLength;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct VOLUME_DISK_EXTENTS
        {
            public uint NumberOfDiskExtents;
            [MarshalAs(UnmanagedType.ByValArray)]
            public DISK_EXTENT[] Extents;
        }
 
        // DeviceIoControl to get disk extents
        [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl",
                   SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hDevice,
            uint dwIoControlCode,
            IntPtr lpInBuffer,
            uint nInBufferSize,
            ref VOLUME_DISK_EXTENTS lpOutBuffer,
            uint nOutBufferSize,
            out uint lpBytesReturned,
            IntPtr lpOverlapped);
         // DeviceIoControl to get disk extents
         
        [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl",
                   SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hDevice,
            uint dwIoControlCode,
            ref STORAGE_PROPERTY_QUERY lpInBuffer,
            uint nInBufferSize,
            ref STORAGE_DEVICE_DESCRIPTOR lpOutBuffer,
            uint nOutBufferSize,
            out uint lpBytesReturned,
            IntPtr lpOverlapped);
            
        [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl",
                   SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hDevice,
            uint dwIoControlCode,
            ref STORAGE_PROPERTY_QUERY lpInBuffer,
            uint nInBufferSize,
            ref STORAGE_DEVICE_ID_DESCRIPTOR lpOutBuffer,
            uint nOutBufferSize,
            out uint lpBytesReturned,
            IntPtr lpOverlapped);
  
        [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl",
                   SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hDevice,
            uint dwIoControlCode,
            ref STORAGE_PROPERTY_QUERY lpInBuffer,
            uint nInBufferSize,
            //[MarshalAs(UnmanagedType.LPArray, SizeConst = 10240)]byte[] lpOutBuffer,
            
            IntPtr lpOutBuffer,
            uint nOutBufferSize,
            out uint lpBytesReturned,
            IntPtr lpOverlapped);            
   
       // For error message
        private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
 
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern uint FormatMessage(
            uint dwFlags,
            IntPtr lpSource,
            uint dwMessageId,
            uint dwLanguageId,
            StringBuilder lpBuffer,
            uint nSize,
            IntPtr Arguments);
 
        public static void Main(string[] args)
        {
            Console.WriteLine("Input logical drive letter:");
            char cDrive = Console.ReadLine()[0];
            if (char.IsLetter(cDrive))
            {
                GetDiskExtents(cDrive);
            }
 
            Console.ReadLine();
        }
 
         // Method for disk extents
        private static void GetDiskExtents(char cDrive)
        {
            DriveInfo di = new DriveInfo(cDrive.ToString());
            if (di.DriveType != DriveType.Fixed)
            {
                Console.WriteLine("This drive is not fixed drive.");
            }
 
            string sDrive = "\\\\.\\" + cDrive.ToString() + ":";
 
            SafeFileHandle hDrive = CreateFileW(
                sDrive,
                0, // No access to drive
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                IntPtr.Zero);
 
            if (hDrive == null || hDrive.IsInvalid)
            {
                string message = GetErrorMessage(Marshal.GetLastWin32Error());
                Console.WriteLine("CreateFile failed. " + message);
            }
 
            uint IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = CTL_CODE(
                IOCTL_VOLUME_BASE, 0,
                METHOD_BUFFERED, FILE_ANY_ACCESS); // From winioctl.h
 
            VOLUME_DISK_EXTENTS query_disk_extents =
                new VOLUME_DISK_EXTENTS();
 
            uint returned_query_disk_extents_size;
 
            bool query_disk_extents_result = DeviceIoControl(
                hDrive,
                IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
                IntPtr.Zero,
                0,
                ref query_disk_extents,
                (uint)Marshal.SizeOf(query_disk_extents),
                out returned_query_disk_extents_size,
                IntPtr.Zero);
 
            hDrive.Close();
 
            if (query_disk_extents_result == false ||
                query_disk_extents.Extents.Length != 1)
            {
                string message = GetErrorMessage(Marshal.GetLastWin32Error());
                Console.WriteLine("DeviceIoControl failed. " + message);
            }
            else
            {
                Console.WriteLine("The physical drive number is: " +
                                  query_disk_extents.Extents[0].DiskNumber);
            }
        }
 
        // Method for error message
        private static string GetErrorMessage(int code)
        {
            StringBuilder message = new StringBuilder(255);
 
            FormatMessage(
              FORMAT_MESSAGE_FROM_SYSTEM,
              IntPtr.Zero,
              (uint)code,
              0,
              message,
              (uint)message.Capacity,
              IntPtr.Zero);
 
            return message.ToString();
        }
        
        public static string GetStorageWWID(string sDrive)
        {
          STORAGE_DEVICE_DESCRIPTOR sdd = QueryPage80(sDrive);
          if (sdd.Size == 0)
          {
            return null;
          }
          byte[] bySDI = QueryPage83(sDrive);
          STORAGE_DEVICE_ID_DESCRIPTOR devId = DiskUniqueIdstoStorageDeviceIdDescriptor(bySDI);
 
          if (devId.NumberOfIdentifiers > 0)
          {
            STORAGE_IDENTIFIER[] sdi = DiskUniqueIdstoStorageId(bySDI);
            for (int i = 0; i<devId.NumberOfIdentifiers;++i)
            {
              if (sdi[i].Type == StorageDevice.Program.StorageIdType.FCPHName)
              {
                string rs = "";
                string[] parts = sdi[i].Identifiers.Trim().Split(' ');
                foreach (string s in parts)
                {
                  UInt32 u = Convert.ToUInt32(s,16);
                  if (rs != "")
                    rs += ":";
                    
                  rs += u.ToString("x2");
                }
                return rs;
              }
            }
              
 
          }
          return "";
 
        }
        
        public static STORAGE_IDENTIFIER[] GetStorageIdentifiers(string sDrive)
        {
          STORAGE_DEVICE_DESCRIPTOR sdd = QueryPage80(sDrive);
          if (sdd.Size == 0)
          {
            return null;
          }
          byte[] bySDI = QueryPage83(sDrive);
          STORAGE_DEVICE_ID_DESCRIPTOR devId = DiskUniqueIdstoStorageDeviceIdDescriptor(bySDI);
          //Console.WriteLine("Not Null");
          if (devId.NumberOfIdentifiers > 0)
          {
            STORAGE_IDENTIFIER[] sdi = DiskUniqueIdstoStorageId(bySDI);
            return sdi;
          }
          return null;
        }
        
        // Method for Query Page 83
        public static byte[] QueryPage83(string sDrive)
        {
          //unsafe {
          // The input parameter 
          STORAGE_PROPERTY_QUERY spq = new STORAGE_PROPERTY_QUERY();
          spq.PropertyId = StorageDeviceIdProperty;
          spq.QueryType = PropertyStandardQuery;
    
          //uint iBytesReturned = 0;
          // Prepare a large output buffer (good enough in our sample code)
          //bOutputBuffer;
          //STORAGE_DEVICE_DESCRIPTOR sdd = new STORAGE_DEVICE_DESCRIPTOR();
          
          byte[] sdi;// = new byte[10240];
 
          uint returned_query_disk_page83_size;
          
          //uint returnedLength;
          //string sDrive = "\\\\.\\PhysicalDrive1";
          SafeFileHandle hDrive = CreateFileW(
                sDrive,
                0,         // No access to drive
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                IntPtr.Zero);
           
           IntPtr result = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(byte))*10240);     
           bool query_disk_page83_result = DeviceIoControl(
                hDrive,
               IOCTL_STORAGE_QUERY_PROPERTY,
                ref spq,
                (uint)Marshal.SizeOf(spq),
                (result),
                (uint)10240,
                out returned_query_disk_page83_size,
                IntPtr.Zero); 
            
            if (query_disk_page83_result)
            {    
                //Console.WriteLine ("83 Size: {0}",returned_query_disk_page83_size);
                sdi = new byte[returned_query_disk_page83_size];
 
                Marshal.Copy(result,sdi,0,(Marshal.SizeOf(typeof(byte))*(int)returned_query_disk_page83_size));
                Marshal.FreeHGlobal(result);
                
                return sdi;
            } else
            {
              Marshal.FreeHGlobal(result);
              return null;
            }
          //}
        }
 
        
        // Method for Query Page 80
        public static STORAGE_DEVICE_DESCRIPTOR QueryPage80(string sDrive)
        {
          // The input parameter 
          STORAGE_PROPERTY_QUERY spq = new STORAGE_PROPERTY_QUERY();
          spq.PropertyId = StorageDeviceProperty;
          spq.QueryType = PropertyStandardQuery;
    
          //uint iBytesReturned = 0;
          // Prepare a large output buffer (good enough in our sample code)
          //bOutputBuffer;
          STORAGE_DEVICE_DESCRIPTOR sdd = new STORAGE_DEVICE_DESCRIPTOR();
          //STORAGE_DEVICE_ID_DESCRIPTOR sdi = new STORAGE_DEVICE_ID_DESCRIPTOR();
          
          //uint returnedLength;
          //string sDrive = "\\\\.\\PhysicalDrive1";
          SafeFileHandle hDrive = CreateFileW(
                sDrive,
                0,         // No access to drive
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                IntPtr.Zero);
 
           uint returned_query_disk_page80_size,returned_query_disk_page83_size;
           bool query_disk_page80_result = DeviceIoControl(
                hDrive,
                IOCTL_STORAGE_QUERY_PROPERTY,
                ref spq,
                (uint)Marshal.SizeOf(spq),
                ref sdd,
                (uint)Marshal.SizeOf(sdd),
                out returned_query_disk_page80_size,
                IntPtr.Zero);
                
          //Console.WriteLine ( returned_query_disk_page80_size);
          if (query_disk_page80_result)
          {
             unsafe
             {
               //byte[] b = sdd.RawDeviceProperties;
               //Byte* b = &(sdd.RawDeviceProperties);
               string s = System.Text.Encoding.Default.GetString(sdd.RawDeviceProperties);
               
               return sdd;
               //char*p = &(sdd.RawDeviceProperties);
               //STORAGE_DEVICE_DESCRIPTOR* p = &sdd;
               
               if (sdd.VendorIdOffset != 0)
                 //Console.WriteLine("- Page80.VendorId: {0}\n", *(p+sdd.VendorIdOffset));
                 //Console.WriteLine("- Page80.VendorId: {0}\n",(string)sdd.RawDeviceProperties);
                 Console.WriteLine("- Page80.VendorId: {0}\n",s);
                 Console.Write("\"");
                 int row = 0;
                 Console.Write("{0:d3}: \"",row);
                 for (int i = 0; i < (sdd.VendorIdOffset+80); i++)
                 {
                   if (sdd.RawDeviceProperties[i] == 0)
                   {
                     ++row;
                     Console.Write("\"\n");
                     Console.Write("{0:d3}: \"",row);
                   } else
                   {
                     Console.Write("{0}",(char)sdd.RawDeviceProperties[i]);
                   }
                 }
                 Console.Write("\"\n");
             }
             
          }
 
          return sdd;
        }
        
    public static STORAGE_DEVICE_ID_DESCRIPTOR DiskUniqueIdstoStorageDeviceIdDescriptor(byte[] DiskUniqueIds) 
    {
        IntPtr ArrayAddress = Marshal.UnsafeAddrOfPinnedArrayElement(DiskUniqueIds, 0); 
 
        STORAGE_DEVICE_ID_DESCRIPTOR devId =  
            (STORAGE_DEVICE_ID_DESCRIPTOR)Marshal.PtrToStructure( 
            ArrayAddress, typeof(STORAGE_DEVICE_ID_DESCRIPTOR)); 
            
        return devId;      
    }
    
    public static STORAGE_IDENTIFIER[] DiskUniqueIdstoStorageId(byte[] DiskUniqueIds) 
    {
        STORAGE_DEVICE_ID_DESCRIPTOR devId = DiskUniqueIdstoStorageDeviceIdDescriptor(DiskUniqueIds);
        
        IntPtr ArrayAddress = Marshal.UnsafeAddrOfPinnedArrayElement(DiskUniqueIds, 0); 
        IntPtr AddressOfIdentifiers = (IntPtr) 
            ((long)ArrayAddress +  
            (long)(Marshal.OffsetOf( 
            typeof(STORAGE_DEVICE_ID_DESCRIPTOR), "Identifiers"))); 
 
        IntPtr OffsetOfIdentifiersStrings = Marshal.OffsetOf( 
            typeof(STORAGE_IDENTIFIER), "Identifiers"); 
             
        STORAGE_IDENTIFIER[] ArrayofSTORAGE_IDENTIFIER =  
            new STORAGE_IDENTIFIER[devId.NumberOfIdentifiers]; 
             
        IntPtr CurrentAddressOfIdentifiers=AddressOfIdentifiers; 
         
        for (int i = 0; i < devId.NumberOfIdentifiers; i++) 
        { 
            ArrayofSTORAGE_IDENTIFIER[i] =  
                (STORAGE_IDENTIFIER)Marshal.PtrToStructure( 
                CurrentAddressOfIdentifiers, typeof(STORAGE_IDENTIFIER)); 
 
            IntPtr AddressOfIdentifiersString =  
                (IntPtr)((long)CurrentAddressOfIdentifiers +  
                (long)OffsetOfIdentifiersStrings); 
                 
            switch (ArrayofSTORAGE_IDENTIFIER[i].CodeSet) 
            { 
                case StorageIdCodeSet.Ascii: 
                    ArrayofSTORAGE_IDENTIFIER[i].Identifiers =  
                        Marshal.PtrToStringAnsi(AddressOfIdentifiersString, 
                        (Int32)ArrayofSTORAGE_IDENTIFIER[i].IdentifierSize); 
                         
                    break; 
                     
                case StorageIdCodeSet.Utf8: 
                    ArrayofSTORAGE_IDENTIFIER[i].Identifiers =  
                        Marshal.PtrToStringUni(AddressOfIdentifiersString, 
                        (Int32)ArrayofSTORAGE_IDENTIFIER[i].IdentifierSize); 
                         
                    break; 
                     
                default: 
                    for (int x=0; x < ArrayofSTORAGE_IDENTIFIER[i].IdentifierSize; x++) 
                    { 
                        ArrayofSTORAGE_IDENTIFIER[i].Identifiers =  
                            String.Format("{0} {1:X}",  
                            ArrayofSTORAGE_IDENTIFIER[i].Identifiers, 
                            Marshal.ReadByte(AddressOfIdentifiersString, x)); 
                    } 
                    break; 
            } 
            CurrentAddressOfIdentifiers = (IntPtr) 
                ((long)CurrentAddressOfIdentifiers +  
                (long)ArrayofSTORAGE_IDENTIFIER[i].NextOffset); 
        } 
        return ArrayofSTORAGE_IDENTIFIER; 
    } 
    
    public static string GetWWIDOfStorageIdentifiers(STORAGE_IDENTIFIER[] sdi)
    {
      
      return "";
    }
 
    }
}
'@
}
 
# Return a list of WWIDs for each Physical Disk 
Function SAN-GetDiskWWIDs
{
  [CmdletBinding()]
  Param (
   
  )
  $cp = new-object codedom.compiler.compilerparameters
  $cp.CompilerOptions = "/unsafe"
  $cp.WarningLevel = 0
  Add-Type -TypeDefinition (SAN-DefineStorageDevice) -CompilerParameters $cp #-IgnoreWarnings
  $dds = gwmi win32_diskdrive|Sort DeviceId
  $list = @()
  foreach ($d in $dds)
  {
    $obj = ""|Select Disk,WWID
    $obj.Disk = $d.DeviceId
    $wwid = ""
    Write-Debug $d.DeviceId
    try {
      $wwid = [StorageDevice.Program]::GetStorageWWID($d.DeviceId);
    } catch
    {
      # do nothing as $wwid is already ""
    }
    if ($wwid -ne "0")
    {
      $obj.WWID = $wwid
    }
    $list += $obj
  }
  return $list
}
 
Function SAN-GetVolumeWWID
{
  [CmdletBinding()]
  Param ( 
    $Volume = "\\.\C:"  
  )
  if ($Volume[-1] -eq "\")
  {
    # Remove trailing \ as that breaks the StorageDevice Query
    $Volume = $Volume.Substring(0,$Volume.Length-1)
  }
  if ($Volume -match ".:[\\]?")
  {
    $Volume = "\\.\$Volume"
  }
  if ($Volume -notlike "\\?\*")
  {
    return $null
  }
  $obj = ""|Select Volume,WWID
  $obj.Volume = $Volume
  $wwid = ""
  Write-Debug $Volume
  try {
    $wwid = [StorageDevice.Program]::GetStorageWWID($Volume);
  } catch
  {
    # do nothing as $wwid is already ""
  }
  if ($wwid -ne "0")
  {
    $obj.WWID = $wwid
  }
  return $obj
}

Outputs

The output that can be expected from these functions are as follows.

SAN-GetVolumeWWID

Get a list of Volumes to work with

PS W:\> GWMI Win32_Volume |Select Name
Name
----
\\?\Volume{a0312498-c120-11e3-b9e6-806e6f6e6963}\
E:\
\\?\Volume{daa5adfe-c53b-11e3-b8b4-001b21ac3d80}\
\\?\Volume{daa5ae01-c53b-11e3-b8b4-001b21ac3d80}\
\\?\Volume{daa5ae04-c53b-11e3-b8b4-001b21ac3d80}\
\\?\Volume{daa5ae07-c53b-11e3-b8b4-001b21ac3d80}\
C:\
D:\
F:\
G:\
Z:\
Y:\

Get WWID Information for some of these Volumes

PS W:\> "C:","D:","E:","F:","G:"|%{ SAN-GetVolumeWWID   $_ }
Volume          WWID
------          ----
\\.\C:          67:82:bc:b0:27:47:1d:00:15:72:d9:2f:06:54:e5:aa
\\.\D:          67:82:bc:b0:27:47:1d:00:15:72:d9:2f:06:54:e5:aa
\\.\E:          67:82:bc:b0:27:47:1d:00:1a:df:d8:3c:5d:50:da:53
\\.\F:
\\.\G:

The C: and D: drives are Volumes created on two different partitions on the same Physical Disk. The E: drive is on a separate Physical Disk. The WWIDs reflect this.

Query the WWID of one of the \\?\Volume* devices

PS W:\> GWMI Win32_Volume |Select -Expand Name|?{$_ -like "*Volume*"}| %{ SAN-GetVolumeWWID $_ }
Volume                                           WWID
------                                           ----
\\?\Volume{a0312498-c120-11e3-b9e6-806e6f6e6963} 67:82:bc:b0:27:47:1d:00:15:72:d9:2f:06:54:e5:aa
\\?\Volume{daa5adfe-c53b-11e3-b8b4-001b21ac3d80} 60:06:0e:80:05:34:2a:00:00:00:34:2a:00:00:10:00
\\?\Volume{daa5ae01-c53b-11e3-b8b4-001b21ac3d80} 60:06:0e:80:05:34:2a:00:00:00:34:2a:00:00:10:d0
\\?\Volume{daa5ae04-c53b-11e3-b8b4-001b21ac3d80} 60:06:0e:80:05:5f:a3:00:00:00:5f:a3:00:00:10:ef
\\?\Volume{daa5ae07-c53b-11e3-b8b4-001b21ac3d80} 60:06:0e:80:05:5f:a3:00:00:00:5f:a3:00:00:11:00

Query more information about a WWID

PS W:\> SAN-GetVolumeWWID "\\?\Volume{daa5adfe-c53b-11e3-b8b4-001b21ac3d80}\"|%{ SAN-WhatsThatWWID -wwid $_.WWID }
Format    : 6
WWID      : 60:06:0e:80:05:34:2a:00:00:00:34:2a:00:00:10:00
CleanWWID : {6, 0, 0, 6...}
OUI       : 0060E8
Vendor    : HDS
Model     : USP-V
Serial    : 13354
Type      : Enterprise
Family    : USP-V
LDEV      : 00:10:00

Here I have run the Volume through another of my scripts to determine some information about the WWID that was returned. It gives me a surprising amount of information about the disk including the Serial Number of the Array and the LDEV ID. This is capable for HDS storage, whereas EMC storage has obfuscated the Array ID through some form of Hashing algorithm.

SAN-GetDiskWWIDs

PS W:\> SAN-GetDiskWWIDs
Disk                  WWID
----                  ----
\\.\PHYSICALDRIVE0    67:82:bc:b0:27:47:1d:00:15:72:d9:2f:06:54:e5:aa
\\.\PHYSICALDRIVE1    67:82:bc:b0:27:47:1d:00:1a:df:d8:3c:5d:50:da:53
\\.\PHYSICALDRIVE2    60:06:0e:80:05:34:2a:00:00:00:34:2a:00:00:10:00
\\.\PHYSICALDRIVE3    60:06:0e:80:05:34:2a:00:00:00:34:2a:00:00:10:d0
\\.\PHYSICALDRIVE4    60:06:0e:80:05:5f:a3:00:00:00:5f:a3:00:00:10:ef
\\.\PHYSICALDRIVE5    60:06:0e:80:05:5f:a3:00:00:00:5f:a3:00:00:11:00
\\.\PHYSICALDRIVE6
\\.\PHYSICALDRIVE7

Not all drives return WWIDs. Disk 6 and 7 are the Dell DRAC Virtual Floppy and Virtual CD devices. They are unmounted which may be causing the lack of WWID, but WWID of local disk is not really relevant to this discussion.

I hope this has been somewhat informative, and useful. Now it is far more easy to identify which specific disk will need to be removed or extended on a Windows Host the same way that a disk can be identified on Solaris/Linux/AIX/ESXi for these purposes.

Cheers, Chris.

:, , , , , , , , , , , , , , , ,

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!