0

I'm using:

DWORD d = GetLogicalDrives();
for (int i = 0; i < 26; i++)
{
    if ((1 << i) & d) // drive letter 'A' + i present on computer
    {
        wstring s = std::wstring(L"\\\\.\\") + wchar_t('A' + i) + L":";

        PARTITION_INFORMATION diskInfo;
        DWORD dwResult;
        HANDLE dev = CreateFile(LPWSTR(s.c_str()), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
        DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0, &diskInfo, sizeof(diskInfo), &dwResult, NULL);
        CloseHandle(dev);
        if (diskInfo.PartitionType == PARTITION_IFS) 
        {
            ...
        }
    }
}

to enumerate all NTFS partitions of a computer.

It works on my Windows 7, on a Windows 8.1 I tried it on, and on a Windows 10 computer.

But it fails on another Windows 10 computer: on this one, the volume C: has a diskInfo.PartitionType value equal to 0x00, instead of 0x07 (PARTITION_IFS).

This value is (see the doc here):

PARTITION_ENTRY_UNUSED : 0x00 : An unused entry partition.

This is strange, since, I can confirm, the partition is really NTFS.

Questions:

  • Is it well-known that IOCTL_DISK_GET_PARTITION_INFO is not 100% reliable to get the partition type?

  • What would be a more reliable way to enumerate all NTFS volumes?


Note: I also looked at using IOCTL_DISK_GET_PARTITION_INFO_EX instead of IOCTL_DISK_GET_PARTITION_INFO but then the structure PARTITION_INFORMATION_EX does not seem to give informations about PartitionType, whereas the structure PARTITION_INFORMATION does give access to PartitionType.

Basj
  • 41,386
  • 99
  • 383
  • 673
  • 1
    You are not doing any error handling. You are not checking to make sure that `CreateFile()` and `DeviceIoControl()` are actually successful before evaluating `diskInfo.PartitionType`. Another way to find NTFS partitions is to use `GetLogicalDrives/Strings()` and `GetVolumeInformation()`, the `lpFileSystemNameBuffer` parameter will return the name of the file system on each drive you query... – Remy Lebeau Mar 20 '19 at 00:41
  • ... At the very least, you should use `GetLogicalDrives/Strings()` anyway. This will reduce the number of drives you attempt to query, even if you stick with `CreateFile()`/`DeviceIoControl()`. Instead of blindly trying all 26 letters of the alphabet, you only have to query the ones that have actually been assigned to a drive. – Remy Lebeau Mar 20 '19 at 00:44
  • @RemyLebeau In fact in my original code, I did use `GetLogicalDrives`, I didn't include it here because I thought it was not relevant for the problem, but you're right: it's an important part, so I edited the question to include it. (Sorry, I should have included it before). With the current code, what would you change? If you have the possibility, could you post a sample code in an answer? Thank you in advance. – Basj Mar 20 '19 at 08:39
  • Use a [`wmi` query](https://www.codeproject.com/Articles/10539/Making-WMI-Queries-In-C) to enumerate **all partitions**, something like following PowerShell `Get-WmiObject -Class "Win32_Volume" -Namespace "ROOT\CIMV2" | Select-Object -Property DriveLetter, Label, FileSystem, DeviceId` (since Windows 8, use `-namespace "ROOT\Microsoft\Windows\Storage" -classname "MSFT_Volume"` and `-Property DriveLetter, FileSystemLabel, FileSystem, UniqueId`). – JosefZ Mar 20 '19 at 10:37
  • 1
    @JosefZ - wmi is very not efficent way, this is only remote call. for enumerate disks, volumes need enumerate interfaces via config api – RbMm Mar 20 '19 at 15:08
  • and you need use `IOCTL_DISK_GET_PARTITION_INFO_EX` for check partition type – RbMm Mar 20 '19 at 15:37
  • @RbMm If possible, could you post a full code sample? Because I see many different techniques: `GetLogicalDrives`, `DeviceIoControl`, `IOCTL_DISK_GET_PARTITION_INFO` vs. `IOCTL_DISK_GET_PARTITION_INFO_EX`, `Get-WMiObject`, `GetVolumeInformationW`, etc. so we could easily get confused. A good answer would be very helpful for people who want to enumerate NTFS volumes in the future. – Basj Mar 20 '19 at 15:41
  • @RemyLebeau Or maybe you have a sample code (see previous comment)? – Basj Mar 20 '19 at 15:42
  • here is the question in the formulation of the problem. what is given, what to get. whether to associate the information with something. and more specific question. *wmi* of course not solution at all. – RbMm Mar 20 '19 at 15:48
  • @Basj many different ways to tackle this. So try them all and see which one works best for your situation and skillset. – Remy Lebeau Mar 20 '19 at 16:18
  • @RemyLebeau I just inspected the return values. For `ret = DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0, &diskInfo, sizeof(diskInfo), &lpBytesReturned, NULL);`, on the computer where it failed, I once got `0` `0` for `lpBytesReturned` and `ret`. After reboot, I had `2005860068` `0`. In both cases, `0` for `ret` indicates a failure. Any idea how I can investigate further? – Basj Mar 20 '19 at 17:27
  • @Basj You need to check the return value of `CreateFile()` before calling `DeviceIoControl()`. `CreateFile()` returns `INVALID_HANDLE_VALUE` on failure. Also, you need to use `GetLastError()` to find out WHY each of these functions is failing. For example: `dev = CreateFile(...); if (dev == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); ... } else { ret = DeviceIoControl(dev, ...); if (ret == FALSE) { DWORD err = GetLastError(); ... } CloseHandle(dev); }` – Remy Lebeau Mar 20 '19 at 17:33
  • @RemyLebeau Thank you. I followed your advice, and investigation shows that `CreateFile` succeeds, and `DeviceIoControl` fails (returns `0` / `FALSE`). `GetLastError()` is `1`. I looked [here](https://msdn.microsoft.com/en-us/data/aa365179(v=vs.90)) but I don't find the reason for the error. – Basj Mar 20 '19 at 20:13
  • @RbMm I also looked at using `IOCTL_DISK_GET_PARTITION_INFO_EX` instead of `IOCTL_DISK_GET_PARTITION_INFO` but then the structure [`PARTITION_INFORMATION_EX `](https://docs.microsoft.com/en-us/windows/desktop/api/winioctl/ns-winioctl-_partition_information_ex) does not seem to give informations about `PartitionType`, whereas the structure [`PARTITION_INFORMATION `](https://docs.microsoft.com/en-us/windows/desktop/api/winioctl/ns-winioctl-_partition_information) does give access to [`PartitionType`](https://docs.microsoft.com/en-us/windows/desktop/FileIO/disk-partition-types).What do you think? – Basj Mar 20 '19 at 20:57
  • @RemyLebeau If you have a few minutes, do you think it's possible to chat (not very long). I spend the whole day on this, but I can't find the error ;) (see my previous message about GetLastError). – Basj Mar 20 '19 at 20:59
  • @Basj per [System Error Codes (0-499)](https://learn.microsoft.com/en-us/windows/desktop/debug/system-error-codes--0-499-), error code 1 is `ERROR_INVALID_FUNCTION`. That means `IOCTL_DISK_GET_PARTITION_INFO` is not supported by the device you passed to `DeviceIoControl()`. – Remy Lebeau Mar 20 '19 at 21:02
  • @Basj "*`PARTITION_INFORMATION_EX` does not seem to give informations about `PartitionType`*" - yes, it does. `PARTITION_INFORMATION_EX` has a `PartitionStyle` field that tells you whether to look at the `Mbr` or `Gpt` field for more information. Both of those structs have a `PartitionType` field. `Mbr.PartitionType` is a `BYTE`, `Gpt.PartitionType` is a `GUID`. – Remy Lebeau Mar 20 '19 at 21:06
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/190394/discussion-between-basj-and-remy-lebeau). – Basj Mar 20 '19 at 21:10
  • To the downvoter: it would be cool if you could leave a message about how to improve this question. I have done extensive research during hours, and I have now posted the result of the observations in an answer, and have tried to summarized comments together. It would be a shame that the question/answer gets roomba'd one day because of downvotes, thus loosing these reports of working+nonworking solutions. – Basj Mar 20 '19 at 22:27

2 Answers2

2

I did further investigation thanks to @RemyLebeau's comments with:

HANDLE dev = CreateFile(..., GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);

if (dev == INVALID_HANDLE_VALUE) 
{ 
    DWORD err = GetLastError();  // then MessageBox       
} 
else
{ 
    BOOL ret = DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0, &diskInfo, sizeof(diskInfo), &dwResult, NULL);

    if (ret == FALSE) 
    { 
        DWORD err = GetLastError();  // then MessageBox
    } 
    CloseHandle(dev); 
} 

on the computer where it failed (computer with Windows 10). I found that CreateFile succeeded but then DeviceIoControl failed with GetLastError being 1 i.e. ERROR_INVALID_FUNCTION (see System Error Codes (0-499)).

Conclusion (I quote Remy's comment):

That means IOCTL_DISK_GET_PARTITION_INFO is not supported by the device you passed to DeviceIoControl().

I then tried with IOCTL_DISK_GET_PARTITION_INFO_EX:

PARTITION_INFORMATION_EX diskInfo;
BOOL ret = DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0, &diskInfo, sizeof(diskInfo), &lpBytesReturned, NULL);

and then it worked. I could see that diskInfo.PartitionStyle was PARTITION_STYLE_GPT (=1), and this was the reason why IOCTL_DISK_GET_PARTITION_INFO failed. I quote Remy's comment again:

IOCTL_DISK_GET_PARTITION_INFO is not supported on GPT partitioned drives.

So here's the conclusion:

  • use IOCTL_DISK_GET_PARTITION_INFO_EX instead of IOCTL_DISK_GET_PARTITION_INFO

  • it's probably easier to use GetVolumeInformation() instead and just compare if the result is the "NTFS" string, as in the other answer

  • in my particular case, I initially wanted to test if a volume is NTFS or not before attempting an indexing with DeviceIoControl(hVol, FSCTL_ENUM_USN_DATA, ...) because I thought such MFT querying would be limited to NTFS volumes. In fact, an easier solution would be to NOT TEST if it's NTFS or not, and just do the FSCTL_ENUM_USN_DATA. The worst that can happen is that FSCTL_ENUM_USN_DATA fails with ERROR_INVALID_FUNCTION error, per the documentation:

    "ERROR_INVALID_FUNCTION The file system on the specified volume does not support this control code."

Basj
  • 41,386
  • 99
  • 383
  • 673
  • look for `PARTITION_BASIC_DATA_GUID` definition in *diskguid.h*. this (*EBD0A0A2-B9E5-4433-87C0-68B6B72699C7*) not mean *ntfs* partition. this is unrelated to file system – RbMm Mar 20 '19 at 22:27
  • @RbMm Ok I'll remove this part in my answer. Feel free to post a better answer, I'm still interested. – Basj Mar 20 '19 at 22:28
  • and you not need test ntfs this or not before send `FSCTL_ENUM_USN_DATA` – RbMm Mar 20 '19 at 22:28
  • @RbMm yes that's what I mentioned in my last paragraph, thanks for this precision. – Basj Mar 20 '19 at 22:32
1

As @RemyLebeau says, you are not checking the return value for each call.

PARTITION_ENTRY_UNUSED often means the DeviceIoControl() call failed. It depends on the permissions of your user. You should check your user's access rights to see if it has the FILE_READ_DATA permission (included in GENERIC_READ) on volume C:. In my test environment, if you have no access to open volume C: with GENERIC_READ, CreateFile() returns INVALID_HANDLE_VALUE, and then DeviceIoControl() fails as well.

EDIT:

I suggest using GetVolumeInformation(), for example:

wchar_t fs[MAX_PATH + 1] = { 0 };
GetVolumeInformationW(L"C:\\", NULL, 0, NULL, NULL, NULL, fs, MAX_PATH + 1);

And you will see the Type info in the fs buffer.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Drake Wu
  • 6,927
  • 1
  • 7
  • 30
  • Thank you for your answer @DrakeWu-MSFT. Which solution would you suggest to enumerate reliably the NTFS partitions? Note: The software I'm working on is *always* run as administrator (so user permission should not be a problem). – Basj Mar 20 '19 at 08:47
  • So, could you make sure the calls are successful. I suggest to use `GetVolumeInformation()`. See my edit. – Drake Wu Mar 20 '19 at 09:03
  • it works with `GetVolumeInformationW` @DrakeWu-MSFT, I found `"NTFS"` in `fs`. Is it reliable to test if `fs` is equal to `NTFS`? Or can it be `"ntfs"` as well on some versions of Windows? – Basj Mar 20 '19 at 17:29
  • @Basj does it really matter? Just use a case-insensitive comparison. – Remy Lebeau Mar 20 '19 at 21:09
  • @RemyLebeau I mean in general, is it documented to be always `"NTFS"` or are there versions of Windows for which it could be `NTFS-something` or `IFS` or anything slight variation meaning the same thing? (You gave a better solution in chat, but this question is interesting independently). – Basj Mar 20 '19 at 21:48
  • @Basj the names are not formally documented by MS, that I know of, but have a look at [What are possible values for the FileSystemName string that GetVolumeInformation returns?](https://stackoverflow.com/questions/7106679/). – Remy Lebeau Mar 20 '19 at 21:51