From the device manager, I have a USB device node. I extracted its "Physical Device Object name" (e.g. \Device\0000010f
).
Fighting for hours with NtOpenDirectoryObject
, NtQueryDirectoryObject
, NtOpenSymbolicLinkObject
, NtQuerySymbolicLinkObject
and QueryDosDevice
, I couldn't find a way to get from that "Physical Device Object name" to the actual drive-letter (C:
, D:
, ...).
I'm looking for any storage solution (USB/SATA/...). How do I do that?
(There are many similar questions, none of them answers e.g. how to get from Physical Device Object name to \Device\HarddiskVolumeXYZ
or to Volume{SOME_GUID}
)
what you view \Device\0000010f
this is PDO (Physical Device Object) created by some bus driver (it have flag DO_BUS_ENUMERATED_DEVICE
)
to it can be attached some FDO (Functional Device Object). if this is from storage stack (based on CompatibleIDs strings returned by bus device for this PDO ) typical FDO name have form \Device\Harddisk%d\DR%d
and well known symbolic link to it \Device\Harddisk%d\Partition0
disk driver FDO enumerate partitions on volume and for every partition create PDO device object ( with well known symbolic link \Device\Harddisk%d\Partition%d
where partition number always > 0, Partition0 is refer to whole disk FDO)
usual partition is same as volume but not always (Partitions and Volumes) also note IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
- it return array of DISK_EXTENT
structures - look here for DiskNumber -The number of the disk that contains this extent.
so volume can placed on several disks. but in 99%+ IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
return to you only one DISK_EXTENT
so what you can do if you have path to PDO in storage stack ?
- open the device - if use
ZwOpenFile
(of course this worked in
user mode) we can use \Device\0000010f
as is. if we want use win32
api we need use prefix \\?\GLOBALROOT
for all names. really we open by this bus PDO but because disk FDO is attached to PDO all our requests will be send via FDO and handled here. desired access ? SYNCHRONIZE
is enough (in case CreateFile
if we not set FILE_FLAG_OVERLAPPED
api implicity add this flag to DESIRED_ACCESS
)
- send
IOCTL_STORAGE_GET_DEVICE_NUMBER
to device. check that
DeviceType == FILE_DEVICE_DISK && sdn.PartitionNumber == 0
- send
IOCTL_DISK_GET_DRIVE_LAYOUT_EX
for get a variable-sized
array of PARTITION_INFORMATION_EX
structures
- based on
DeviceNumber
(which we get at step 2) and
PartitionNumber
(which we get at step 3) format symbolic link to
partition PDO - \\?\GLOBALROOT\Device\Harddisk%d\Partition%d
- open partition PDO with
SYNCHRONIZE
access (enough because all
IOCTL which we use have FILE_ANY_ACCESS
type
- send
IOCTL_MOUNTDEV_QUERY_DEVICE_NAME
to partition
- now we need handle to MountManager device (
\\.\MountPointManager
)
for send him IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH
this IOCTL defined in mountmgr.h
in input it require MOUNTDEV_NAME
which we get at step 6. on output we receive MOUNTMGR_VOLUME_PATHS
structure (also defined in mountmgr.h
) alternatively we can use
IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS
- if all ok we got list of
drive letters like C:
, D:
, etc..
- for enumerate disk drives in system we can use
CM_Get_Device_ID_ListW
with
{4d36e967-e325-11ce-bfc1-08002be10318}
filter, open every device
instance by CM_Locate_DevNodeW
and finally query for
DEVPKEY_Device_PDOName
by call
CM_Get_DevNode_Property
ok, here the code example correct which do all this:
#include <mountmgr.h>
// guz == 0 always, volatile for prevent CL "optimization" - it can drop alloca(0) call
static volatile UCHAR guz;
ULONG QueryPartitionW32(HANDLE hPartition, HANDLE hMountManager)
{
MOUNTDEV_STABLE_GUID guid;
ULONG dwBytesRet;
if (DeviceIoControl(hPartition, IOCTL_MOUNTDEV_QUERY_STABLE_GUID, 0, 0, &guid, sizeof(guid), &dwBytesRet, NULL))
{
DbgPrint("StableGuid = \\\\?\\Volume{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n",
guid.StableGuid.Data1, guid.StableGuid.Data2, guid.StableGuid.Data3,
guid.StableGuid.Data4[0],
guid.StableGuid.Data4[1],
guid.StableGuid.Data4[2],
guid.StableGuid.Data4[3],
guid.StableGuid.Data4[4],
guid.StableGuid.Data4[5],
guid.StableGuid.Data4[6],
guid.StableGuid.Data4[7]
);
}
// assume NumberOfDiskExtents == 1
VOLUME_DISK_EXTENTS vde;
if (DeviceIoControl(hPartition, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, 0, 0, &vde, sizeof(vde), &dwBytesRet, 0))
{
if (vde.NumberOfDiskExtents)
{
DbgPrint("ofs=%I64u, len=%I64u\n", vde.Extents->StartingOffset.QuadPart, vde.Extents->ExtentLength.QuadPart);
}
}
PVOID stack = alloca(guz);
union {
PVOID buf;
PMOUNTDEV_NAME pmdn;
};
ULONG err;
ULONG cb = 0, rcb = sizeof(MOUNTDEV_NAME) + 0x10, InputBufferLength;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (DeviceIoControl(hPartition, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, 0, 0, buf, cb, &dwBytesRet, NULL))
{
DbgPrint("%.*S\n", pmdn->NameLength >> 1, pmdn->Name);
union {
PVOID pv;
PMOUNTMGR_VOLUME_PATHS pmvp;
};
cb = 0, rcb = sizeof(MOUNTMGR_VOLUME_PATHS) + 0x10, InputBufferLength = sizeof(MOUNTDEV_NAME) + pmdn->NameLength;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(pv = alloca(rcb - cb), pmdn);
}
if (DeviceIoControl(hMountManager, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
pmdn, InputBufferLength, pv, cb, &dwBytesRet, NULL))
{
PWSTR sz = pmvp->MultiSz;
while(*sz)
{
DbgPrint("%S\n", sz);
sz += 1 + wcslen(sz);
}
return NOERROR;
}
rcb = sizeof(MOUNTMGR_VOLUME_PATHS) + pmvp->MultiSzLength;
} while ((err = GetLastError()) == ERROR_MORE_DATA);
break;
}
rcb = sizeof(MOUNTDEV_NAME) + pmdn->NameLength;
} while ((err = GetLastError()) == ERROR_MORE_DATA);
return err;
}
ULONG EnumDiskPartitionsW32(HANDLE hDisk, HANDLE hMountManager)
{
STORAGE_DEVICE_NUMBER sdn;
ULONG dwBytesRet;
if (!DeviceIoControl(hDisk, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesRet, NULL))
{
return GetLastError();
}
if (sdn.DeviceType != FILE_DEVICE_DISK || sdn.PartitionNumber != 0)
{
return ERROR_GEN_FAILURE;
}
WCHAR sz[128], *c = sz + swprintf(sz, L"\\\\?\\GLOBALROOT\\Device\\Harddisk%d\\Partition", sdn.DeviceNumber);
PVOID stack = alloca(guz);
union {
PVOID buf;
PDRIVE_LAYOUT_INFORMATION_EX pdli;
};
ULONG cb = 0, rcb, PartitionCount = 4;
for (;;)
{
if (cb < (rcb = FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[PartitionCount])))
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (DeviceIoControl(hDisk, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0, buf, cb, &dwBytesRet, NULL))
{
if (PartitionCount = pdli->PartitionCount)
{
PPARTITION_INFORMATION_EX PartitionEntry = pdli->PartitionEntry;
do
{
if (!PartitionEntry->PartitionNumber)
{
continue;
}
_itow(PartitionEntry->PartitionNumber, c, 10);
DbgPrint("%S\n", sz);
HANDLE hPartition = CreateFile(sz, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hPartition != INVALID_HANDLE_VALUE)
{
QueryPartitionW32(hPartition, hMountManager);
CloseHandle(hPartition);
}
} while (PartitionEntry++, --PartitionCount);
}
return NOERROR;
}
switch (ULONG err = GetLastError())
{
case ERROR_MORE_DATA:
PartitionCount = pdli->PartitionCount;
continue;
case ERROR_BAD_LENGTH:
case ERROR_INSUFFICIENT_BUFFER:
PartitionCount <<= 1;
continue;
default:
return err;
}
}
}
void DiskEnumW32(HANDLE hMountManager)
{
static const WCHAR DEVCLASS_DISK[] = L"{4d36e967-e325-11ce-bfc1-08002be10318}";
enum { flags = CM_GETIDLIST_FILTER_CLASS|CM_GETIDLIST_FILTER_PRESENT };
ULONG len;
if (!CM_Get_Device_ID_List_SizeW(&len, DEVCLASS_DISK, flags))
{
PWSTR buf = (PWSTR)alloca(len * sizeof(WCHAR));
if (!CM_Get_Device_ID_ListW(DEVCLASS_DISK, buf, len, flags))
{
PVOID stack = buf;
static const WCHAR prefix[] = L"\\\\?\\GLOBALROOT";
ULONG cb = 0, rcb = sizeof(prefix) + 0x20;
while (*buf)
{
DbgPrint("%S\n", buf);
DEVINST dnDevInst;
if (!CM_Locate_DevNodeW(&dnDevInst, buf, CM_LOCATE_DEVNODE_NORMAL))
{
DEVPROPTYPE PropertyType;
int err;
union {
PVOID pv;
PWSTR sz;
PBYTE pb;
};
do
{
if (cb < rcb)
{
rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
}
rcb -= sizeof(prefix) - sizeof(WCHAR);
if (!(err = CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_Device_PDOName, &PropertyType,
pb + sizeof(prefix) - sizeof(WCHAR), &rcb, 0)))
{
if (PropertyType == DEVPROP_TYPE_STRING)
{
memcpy(pv, prefix, sizeof(prefix) - sizeof(WCHAR));
DbgPrint("%S\n", sz);
HANDLE hDisk = CreateFile(sz, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hDisk != INVALID_HANDLE_VALUE)
{
EnumDiskPartitionsW32(hDisk, hMountManager);
CloseHandle(hDisk);
}
}
else
{
err = ERROR_GEN_FAILURE;
}
break;
}
rcb += sizeof(prefix) - sizeof(WCHAR);
} while (err == CR_BUFFER_SMALL);
}
buf += 1 + wcslen(buf);
}
}
}
}
void DiskEnumW32()
{
HANDLE hMountManager = CreateFile(MOUNTMGR_DOS_DEVICE_NAME, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hMountManager != INVALID_HANDLE_VALUE)
{
DiskEnumW32(hMountManager);
CloseHandle(hMountManager);
}
}
Another possible solution for you, based on PowerShell and WMI alone:
$PDO = "\Device\00000052"
$DiskDriverData = Get-WmiObject Win32_PNPSignedDriver | Where {$_.PDO -eq $PDO}
$DeviceID = """" + $DiskDriverData.DeviceID.Replace("\","\\") + """"
$ComputerInfo = Get-WmiObject Win32_Computersystem
$name = $ComputerInfo.Name
$FullString = "\\$name\root\cimv2:Win32_PnPEntity.DeviceID=$DeviceID"
$PNPDevice = Get-WmiObject Win32_PNPDevice | Where {$_.SystemElement -eq $FullString}
$DiskDriveToPartition = Get-WmiObject -Class Win32_DiskDriveToDiskPartition | where {$_.Antecedent -eq $PNPDevice.SameElement}
foreach ($i in $DiskDriveToPartition) {
$Partition = Get-WmiObject -Class Win32_LogicalDiskToPartition | Where {$_.Antecedent -eq $i.Dependent}
$PartitionLetter = $Partition.Dependent.split('"')[1]
Write-Host -ForegroundColor Green "Detected Partition for the given PDO: $PartitionLetter"
}