There was an deceptively simple question in the Embarcadero forums recently — how can you retrieve a drive’s friendly name? I wasn’t the only person to think the questioner meant either the volume label or ‘display name’ (as the shell API calls it), the latter being whatever Windows Explorer labels the drive, which is typically the volume label followed by ‘ [drive letter](:)’. Neither was what the poster wanted however — rather, he wanted the ‘friendly name’ as retrievable from Windows’ Device Manager.
Googling, it appears the only documented (or even semi-documented!) way of getting it is via the WMI API. I say ‘the’ WMI API — in fact, there seems to be several to the extent that C++, ‘classic’ VB and Windows Scripting (VBScript/JScript), .Net and PowerShell all have their own particular interface, though the available information seems the same. Anyhow, to get going in Delphi, we need the interface designed for VB — in D2007, go to Component|Import Component…; have ‘Import type library’ selected and click Next; find ‘Microsoft WMIScripting V1.2 Library’ (the version might be slightly different on your machine); don’t generate component wrappers (there’s no need); and let the IDE just create the unit. There should now be a WbemScripting_TLB.pas created, saved to the Imports directory and so ready to be added to any given uses clause.
The next problem is figuring out how to create an instance of the main WMI object. In VBScript you can use the slightly odd-looking ‘moniker’ version of the GetObject standard function:
Dim WMIServices Set WMIServices = GetObject("winmgmts:\\.\root\cimv2")
Unfortunately, Delphi’s CreateOleObject doesn’t support this syntax, and while there should be a way to write one that does, the following creates the needed object just as well —
uses WbemScripting_TLB; var WMIServices: ISWbemServices; begin WMIServices := CoSWbemLocator.Create.ConnectServer('.', 'root\cimv2', '', '', '', '', 0, nil);
With respect to actually using the object, the ExecQuery method may be called. This returns a further object that you can enumerate in Delphi using its Count and ItemIndex methods; to actually read any subclass-specific properties (see here for a list), cast a returned object to Variant first or assign it to a Variant variable, against which you can use late binding —
procedure ShowDriveInterfaceTypes; var WMIServices: ISWbemServices; Drives: ISWbemObjectSet; Drive: Variant; I: Integer; begin WMIServices := CoSWbemLocator.Create.ConnectServer('.', 'root\cimv2', '', '', '', '', 0, nil); Drives := WMIServices.ExecQuery('Select * FROM Win32_DiskDrive', 'WQL', 0, nil); for I := 0 to Drives.Count - 1 do begin Drive := Drives.ItemIndex(I); ShowMessage(Drive.DeviceID + ' = ' + Drive.InterfaceType); end; end;
So, how can we use this to retrieve a drive’s ‘friendly name’? An issue here is that the name concerns a physical drive whilst a drive letter concerns a logical drive — and WMI doesn’t seem to provide any way to directly get a physical drive from a logical one (this is not as ridiculous as it may seem, since a logical drive can span more than one physical drive). What we need to do, then, is to go the other way first, loading what drives we have, data that can then be accessed to map a drive letter to a physical drive. Here’s a simple class I came up with, together with a wrapper function for ‘one-off’ calls:
uses WbemScripting_TLB; type TFriendlyDriveNames = class private FNames: array['A'..'Z'] of string; FWMIServices: ISWbemServices; function GetFriendlyName(DriveLetter: Char): string; public constructor Create; procedure Refresh; property FriendlyNames[DriveLetter: Char]: string read GetFriendlyName; default; end; constructor TFriendlyDriveNames.Create; begin FWMIServices := CoSWbemLocator.Create.ConnectServer('.', 'root\cimv2', '', '', '', '', 0, nil); Refresh; end; function TFriendlyDriveNames.GetFriendlyName(DriveLetter: Char): string; begin DriveLetter := UpCase(DriveLetter); case DriveLetter of Low(FNames)..High(FNames): Result := FNames[DriveLetter]; else Result := ''; end; end; procedure TFriendlyDriveNames.Refresh; var Ch: Char; Drive: ISWbemObject; Drives, Partitions, LogicalDrives: ISWbemObjectSet; I, J, K: Integer; S: string; begin for Ch := Low(FNames) to High(FNames) do FNames[Ch] := ''; Drives := FWMIServices.ExecQuery('Select * FROM Win32_DiskDrive', 'WQL', 0, nil); for I := 0 to Drives.Count - 1 do begin Drive := Drives.ItemIndex(I); Partitions := Drive.Associators_('Win32_DiskDriveToDiskPartition', '', '', '', False, False, '', '', 0, nil); for J := 0 to Partitions.Count - 1 do begin LogicalDrives := Partitions.ItemIndex(J).Associators_( 'Win32_LogicalDiskToPartition', '', '', '', False, False, '', '', 0, nil); for K := 0 to LogicalDrives.Count - 1 do begin S := Variant(LogicalDrives.ItemIndex(K)).Name; if S <> '' then FNames[UpCase(S)] := Variant(Drive).Caption; end; end; end; end; function GetFriendlyDriveName(const DriveLetter: Char): string; begin with TFriendlyDriveNames.Create do try Result := FriendlyNames[DriveLetter]; finally Free; end; end;