How to list physical disks? - c

How to list physical disks in Windows?
In order to obtain a list of "\\\\.\PhysicalDrive0" available.

#WMIC
wmic is a very complete tool
wmic diskdrive list
provide a (too much) detailed list, for instance
for less info
wmic diskdrive list brief
#C
Sebastian Godelet mentions in the comments:
In C:
system("wmic diskdrive list");
As commented, you can also call the WinAPI, but... as shown in "How to obtain data from WMI using a C Application?", this is quite complex (and generally done with C++, not C).
#PowerShell
Or with PowerShell:
Get-WmiObject Win32_DiskDrive
Update Feb. 2022, Microsoft announces in "Windows 10 features we're no longer developing"
The WMIC tool is deprecated in Windows 10, version 21H1 and the 21H1 General Availability Channel release of Windows Server.
This tool is superseded by Windows PowerShell for WMI.
Note: This deprecation only applies to the command-line management tool. WMI itself is not affected.

One way to do it:
Enumerate logical drives using GetLogicalDrives
For each logical drive, open a file named "\\.\X:" (without the quotes) where X is the logical drive letter.
Call DeviceIoControl passing the handle to the file opened in the previous step, and the dwIoControlCode parameter set to IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS:
HANDLE hHandle;
VOLUME_DISK_EXTENTS diskExtents;
DWORD dwSize;
[...]
iRes = DeviceIoControl(
hHandle,
IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
NULL,
0,
(LPVOID) &diskExtents,
(DWORD) sizeof(diskExtents),
(LPDWORD) &dwSize,
NULL);
This returns information of the physical location of a logical volume, as a VOLUME_DISK_EXTENTS structure.
In the simple case where the volume resides on a single physical drive, the physical drive number is available in diskExtents.Extents[0].DiskNumber

This might be 5 years too late :). But as I see no answer for this yet, adding this.
We can use Setup APIs to get the list of disks ie., devices in the system implementing GUID_DEVINTERFACE_DISK.
Once we have their device paths, we can issue IOCTL_STORAGE_GET_DEVICE_NUMBER to construct "\\.\PHYSICALDRIVE%d" with STORAGE_DEVICE_NUMBER.DeviceNumber
See also SetupDiGetClassDevs function
#include <Windows.h>
#include <Setupapi.h>
#include <Ntddstor.h>
#pragma comment( lib, "setupapi.lib" )
#include <iostream>
#include <string>
using namespace std;
#define START_ERROR_CHK() \
DWORD error = ERROR_SUCCESS; \
DWORD failedLine; \
string failedApi;
#define CHK( expr, api ) \
if ( !( expr ) ) { \
error = GetLastError( ); \
failedLine = __LINE__; \
failedApi = ( api ); \
goto Error_Exit; \
}
#define END_ERROR_CHK() \
error = ERROR_SUCCESS; \
Error_Exit: \
if ( ERROR_SUCCESS != error ) { \
cout << failedApi << " failed at " << failedLine << " : Error Code - " << error << endl; \
}
int main( int argc, char **argv ) {
HDEVINFO diskClassDevices;
GUID diskClassDeviceInterfaceGuid = GUID_DEVINTERFACE_DISK;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData;
DWORD requiredSize;
DWORD deviceIndex;
HANDLE disk = INVALID_HANDLE_VALUE;
STORAGE_DEVICE_NUMBER diskNumber;
DWORD bytesReturned;
START_ERROR_CHK();
//
// Get the handle to the device information set for installed
// disk class devices. Returns only devices that are currently
// present in the system and have an enabled disk device
// interface.
//
diskClassDevices = SetupDiGetClassDevs( &diskClassDeviceInterfaceGuid,
NULL,
NULL,
DIGCF_PRESENT |
DIGCF_DEVICEINTERFACE );
CHK( INVALID_HANDLE_VALUE != diskClassDevices,
"SetupDiGetClassDevs" );
ZeroMemory( &deviceInterfaceData, sizeof( SP_DEVICE_INTERFACE_DATA ) );
deviceInterfaceData.cbSize = sizeof( SP_DEVICE_INTERFACE_DATA );
deviceIndex = 0;
while ( SetupDiEnumDeviceInterfaces( diskClassDevices,
NULL,
&diskClassDeviceInterfaceGuid,
deviceIndex,
&deviceInterfaceData ) ) {
++deviceIndex;
SetupDiGetDeviceInterfaceDetail( diskClassDevices,
&deviceInterfaceData,
NULL,
0,
&requiredSize,
NULL );
CHK( ERROR_INSUFFICIENT_BUFFER == GetLastError( ),
"SetupDiGetDeviceInterfaceDetail - 1" );
deviceInterfaceDetailData = ( PSP_DEVICE_INTERFACE_DETAIL_DATA ) malloc( requiredSize );
CHK( NULL != deviceInterfaceDetailData,
"malloc" );
ZeroMemory( deviceInterfaceDetailData, requiredSize );
deviceInterfaceDetailData->cbSize = sizeof( SP_DEVICE_INTERFACE_DETAIL_DATA );
CHK( SetupDiGetDeviceInterfaceDetail( diskClassDevices,
&deviceInterfaceData,
deviceInterfaceDetailData,
requiredSize,
NULL,
NULL ),
"SetupDiGetDeviceInterfaceDetail - 2" );
disk = CreateFile( deviceInterfaceDetailData->DevicePath,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL );
CHK( INVALID_HANDLE_VALUE != disk,
"CreateFile" );
CHK( DeviceIoControl( disk,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL,
0,
&diskNumber,
sizeof( STORAGE_DEVICE_NUMBER ),
&bytesReturned,
NULL ),
"IOCTL_STORAGE_GET_DEVICE_NUMBER" );
CloseHandle( disk );
disk = INVALID_HANDLE_VALUE;
cout << deviceInterfaceDetailData->DevicePath << endl;
cout << "\\\\?\\PhysicalDrive" << diskNumber.DeviceNumber << endl;
cout << endl;
}
CHK( ERROR_NO_MORE_ITEMS == GetLastError( ),
"SetupDiEnumDeviceInterfaces" );
END_ERROR_CHK();
Exit:
if ( INVALID_HANDLE_VALUE != diskClassDevices ) {
SetupDiDestroyDeviceInfoList( diskClassDevices );
}
if ( INVALID_HANDLE_VALUE != disk ) {
CloseHandle( disk );
}
return error;
}

The answer is far simpler than all the above answers. The physical drive list is actually stored in a Registry key which also gives the device mapping.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\disk\Enum
Count is the number of PhysicalDrive# and each numbered Registry Value is the corresponding physical drive.
For example, Registry Value "0" is PhysicalDrive0. The value is the actual device PhysicalDrive0 is mapped to. The value contained here can be passed into CM_Locate_DevNode within parameter pDeviceID to use the plug and play services. This will allow you to gather a wealth of information on the device. Such as the properties from Device Manager like "Friendly Display Name" if you need a name for the drive, serial numbers and more.
There is no need for WMI services which may not be running on the system or other hackery and this functionality has been present in Windows since at least 2000 and continues to be the case in Windows 10.

I've modified an open-source program called "dskwipe" in order to pull this disk information out of it. Dskwipe is written in C, and you can pull this function out of it. The binary and source are available here: dskwipe 0.3 has been released
The returned information will look something like this:
Device Name Size Type Partition Type
------------------------------ --------- --------- --------------------
\\.\PhysicalDrive0 40.0 GB Fixed
\\.\PhysicalDrive1 80.0 GB Fixed
\Device\Harddisk0\Partition0 40.0 GB Fixed
\Device\Harddisk0\Partition1 40.0 GB Fixed NTFS
\Device\Harddisk1\Partition0 80.0 GB Fixed
\Device\Harddisk1\Partition1 80.0 GB Fixed NTFS
\\.\C: 80.0 GB Fixed NTFS
\\.\D: 2.1 GB Fixed FAT32
\\.\E: 40.0 GB Fixed NTFS

The only correct answer is the one by #Grodriguez, and here's a code that he was too lazy to write:
#include <windows.h>
#include <iostream>
#include <bitset>
#include <vector>
using namespace std;
typedef struct _DISK_EXTENT {
DWORD DiskNumber;
LARGE_INTEGER StartingOffset;
LARGE_INTEGER ExtentLength;
} DISK_EXTENT, *PDISK_EXTENT;
typedef struct _VOLUME_DISK_EXTENTS {
DWORD NumberOfDiskExtents;
DISK_EXTENT Extents[ANYSIZE_ARRAY];
} VOLUME_DISK_EXTENTS, *PVOLUME_DISK_EXTENTS;
#define CTL_CODE(DeviceType, Function, Method, Access) \
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
#define IOCTL_VOLUME_BASE ((DWORD)'V')
#define METHOD_BUFFERED 0
#define FILE_ANY_ACCESS 0x00000000
#define IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS CTL_CODE(IOCTL_VOLUME_BASE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS)
int main() {
bitset<32> drives(GetLogicalDrives());
vector<char> goodDrives;
for (char c = 'A'; c <= 'Z'; ++c) {
if (drives[c - 'A']) {
if (GetDriveType((c + string(":\\")).c_str()) == DRIVE_FIXED) {
goodDrives.push_back(c);
}
}
}
for (auto & drive : goodDrives) {
string s = string("\\\\.\\") + drive + ":";
HANDLE h = CreateFileA(
s.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS, NULL
);
if (h == INVALID_HANDLE_VALUE) {
cerr << "Drive " << drive << ":\\ cannot be opened";
continue;
}
DWORD bytesReturned;
VOLUME_DISK_EXTENTS vde;
if (!DeviceIoControl(
h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
NULL, 0, &vde, sizeof(vde), &bytesReturned, NULL
)) {
cerr << "Drive " << drive << ":\\ cannot be mapped into physical drive";
continue;
}
cout << "Drive " << drive << ":\\ is on the following physical drives: ";
for (int i = 0; i < vde.NumberOfDiskExtents; ++i) {
cout << vde.Extents[i].DiskNumber << ' ';
}
cout << endl;
}
}
I think that installation of Windows Driver Development Kit is quite a lengthy process, so I've included the declarations one needs to use DeviceIoControl for this task.

The only sure shot way to do this is to call CreateFile() on all \\.\Physicaldiskx where x is from 0 to 15 (16 is maximum number of disks allowed). Check the returned handle value. If invalid check GetLastError() for ERROR_FILE_NOT_FOUND. If it returns anything else then the disk exists but you cannot access it for some reason.

GetLogicalDrives() enumerates all mounted disk partitions, not physical drives.
You can enumerate the drive letters with (or without) GetLogicalDrives, then call QueryDosDevice() to find out which physical drive the letter is mapped to.
Alternatively, you can decode the information in the registry at HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices. The binary data encodings there are not obvious, however. If you have a copy of Russinovich and Solomon's book Microsoft Windows Internals, this registry hive is discussed in Chapter 10.

Thic WMIC command combination works fine:
wmic volume list brief

If you only need to look at the existing disks, this one will suffice:
powershell "get-physicaldisk"

Might want to include the old A: and B: drives as you never know who might be using them!
I got tired of USB drives bumping my two SDHC drives that are just for Readyboost.
I had been assigning them to High letters Z: Y: with a utility that will assign drive letters to devices as you wish. I wondered.... Can I make a Readyboost drive letter A: ? YES!
Can I put my second SDHC drive letter as B: ? YES!
I've used Floppy Drives back in the day, never thought that A: or B: would come in handy for
Readyboost.
My point is, don't assume A: & B: will not be used by anyone for anything
You might even find the old SUBST command being used!

Here is a new solution of doing it with doing WMI calls.
Then all you need to do is just to call :
queryAndPrintResult(L"SELECT * FROM Win32_DiskDrive", L"Name");

I just ran across this in my RSS Reader today. I've got a cleaner solution for you. This example is in Delphi, but can very easily be converted to C/C++ (It's all Win32).
Query all value names from the following registry location:
HKLM\SYSTEM\MountedDevices
One by one, pass them into the following function and you will be returned the device name. Pretty clean and simple! I found this code on a blog here.
function VolumeNameToDeviceName(const VolName: String): String;
var
s: String;
TargetPath: Array[0..MAX_PATH] of WideChar;
bSucceeded: Boolean;
begin
Result := ”;
// VolumeName has a format like this: \\?\Volume{c4ee0265-bada-11dd-9cd5-806e6f6e6963}\
// We need to strip this to Volume{c4ee0265-bada-11dd-9cd5-806e6f6e6963}
s := Copy(VolName, 5, Length(VolName) - 5);
bSucceeded := QueryDosDeviceW(PWideChar(WideString(s)), TargetPath, MAX_PATH) <> 0;
if bSucceeded then
begin
Result := TargetPath;
end
else begin
// raise exception
end;
end;

If you want "physical" access, we're developing this API that will eventually allows you to communicate with storage devices. It's open source and you can see the current code for some information. Check back for more features:
https://github.com/virtium/vtStor

Make a list of all letters in the US English Alphabet, skipping a & b. "CDEFGHIJKLMNOPQRSTUVWXYZ". Open each of those drives with CreateFile e.g. CreateFile("\\.\C:"). If it does not return INVALID_HANDLE_VALUE then you got a 'good' drive. Next take that handle and run it through DeviceIoControl to get the Disk #. See my related answer for more details.

Related

How to change disk serial numbers using WinAPI?

To get the disk serial number I would use function GetVolumeInformation but I couldn't find a way to change it using windows api like VolumeID program from Sysinternals does.
There is no Windows API to do this easily, you will have to manually overwrite the volume ID in the boot sector of the drive. This involves (at least) three different operations:
Get an handle to the volume.
Read the boot sector and parse it to recognize the filesystem type (FAT vs FAT32 vs NTFS, etc.).
Overwrite the Volume ID field and re-write the modified boot sector back to the disk.
It's not that simple of an operation, but this is how Sysinternals VolumeID does it. Note that boot sector fields and offsets are different for every filesystem.
The way SysInternals Volume ID does this. First open the volume with CreateFile API
char volumeName[8];
char const driveLetter = 'F';
char const volumeFormat[] = "\\\\.\\%c:";
sprintf_s(volumeName,8,volumeFormat, driveLetter);
HANDLE hVolume = CreateFileA(volumeName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
Next read 512 bytes (This is read into a struct, i'm not sure the exact structure)
DWORD dwBytesRead;
ReadFile(hVolume,&volumeHeader, 512, &dwBytesRead, NULL);
Using struct defined like this:
struct VOLUME_HEADER
{
char v1[3];
char v2[36];
int v3;
char v4[13];
int v5;
int v6;
char v7[430];
char v8[12];
};
The structure is checked if it is NTFS, FAT, FAT32 etc.
if (!strncmp(volumeHeader.v2, "NTFS", 4u))
{
printf("Volume is NTFS!\n");
}
else if (!strncmp(volumeHeader.v7, "FAT32", 5u))
{
printf("Volume is FAT32!\n");
}
else
{
if (strncmp(volumeHeader.v4, "FAT", 3u))
{
printf("\n\nUnrecognized drive type\n");
return 0;
}
printf(":Volume is FAT!\n");
}
Then a 32-bit integer is updated in this structure with the new volume ID. If you retrieve a specific volume like NTFS you can find the offset of the volume ID if you know the existing one. Then file pointer is set back to beginning of file and data is written back with WriteFile API.
SetFilePointer ( hVolume, 0, NULL, FILE_BEGIN );
WriteFile ( hVolume, &volumeHeader, 512, &dwBytesWritten, NULL );

Get list of USB devices in windows

I am attempting to get a list of removable/USB drives connected to a Windows computer. I have tried functions like GetLogicalDrives however I have had no luck. I am looking for an effective method of gathering the USB list
This will do what you asked. It returns all removable drives, meaning that it will returns all USB drives, but also everything tagged as "Removable" by Windows - excepted CD-ROM, they have their own type (see GetDriveType for details).
If you want exclusively USB drives, you'll need to call also SetupDiGetDeviceRegistryPropertyW with SPDRP_REMOVAL_POLICY property before accepting the drive, I've left a comment to where you should insert it if needed.
#include <Windows.h>
#include <fileapi.h>
#include <tchar.h>
// Return a list of removable devices like GetLogicalDrives
// Bit 0=drive A, bit 26=drive Z.
// 1=removable drive, 0=everything else.
DWORD getRemovableDrives()
{
DWORD result = 0u ;
DWORD curr = 0u ;
// Found all existing drives.
if ((curr=GetLogicalDrives())) {
TCHAR root[3] = TEXT("A:") ;
int idx = 1 ;
// Parse drives.
for (int i=0;i<26;i++, idx<<=1) {
// If drive present, check it..
if ( (curr & idx) && (GetDriveType(root)==DRIVE_REMOVABLE) )
// Drive is removable (can be USB, CompactFlash, SDCard, ...).
// Call SetupDiGetDeviceRegistryPropertyW here if needed.
result |= idx ;
root[0]++ ;
}
}
return result ;
}
int main()
{
DWORD removableDrives = getRemovableDrives() ;
int count = 0 ;
_tprintf(TEXT("Current removable drive(s):\n\t")) ;
for (int i=0 ; i<26 ; i++)
if (removableDrives & (1<<i)) {
_tprintf(TEXT("%c: "),_T('A')+i) ;
count++ ;
}
_tprintf(TEXT("\nFound %d removable drive(s).\n"),count) ;
return 0 ;
}
Sample output:
Current removable drive(s):
K: N:
Found 2 removable drive(s).
The main function, getRemovableDrives(), is obviously fully silent and does not produce any output. The main() only shows how to parse its results. The function is pretty fast, therefore you can call it without impacting too much performances - but it would be better to get notifications about drives connection/disconnection anyway.
Error checking is at minimal level, and my Unicode C is a bit rusted so they may be some space for some little more optimizations.

Suppressing "Insert diskette to drive X:"

I am trying to check does any disk is present in drive A: (after my program installs i need to ensure that computer won't boot from installation diskette). I've tried using _access method (undefined reference...), FILE* and making directory inside diskette and remove it after checking. Unfortunately DOS displays ugly piece of text about putting disk in drive (Destroying my TUI and making user think that diskette in drive is important). So how to suppress this message, or safely check does disk is present in drive?
Possibly BIOS INT 13H 16H: Detect Media Change - it has a status:
80H = diskette drive not ready or not installed
Which may solve your problem - I lack the antique hardware and software to test it personally.
#include <dos.h>
unsigned int DetectMediaChange()
{
union REGS regs;
regs.h.ah = 0x16; // Detect Media Change
regs.h.dl = 0; // Drive A
int86( 0x13, &regs, &regs ); // BIOS Disk I/O INT 13h
return regs.h.ah ; // Status : 00H = diskette change line not active
// 01H = invalid drive number
// 06H = either change line is not supported or
// disk change line is active (media was swapped)
// 80H = diskette drive not ready or not installed
// else= BIOS disk error code if CF is set to CY
}
Okay, I've figured it out:
char far * bufptr;
union REGS inregs, outregs;
struct SREGS segregs;
char buf [1024];
avaliable(){
redo:
segread(&segregs);
bufptr = (char far *) buf;
segregs.es = FP_SEG(bufptr);
inregs.x.bx = FP_OFF(bufptr);
inregs.h.ah = 2;
inregs.h.al = 1;
inregs.h.ch = 0;
inregs.h.cl = 1;
inregs.h.dh = 0;
inregs.h.dl = 0;
int86x(0x13, &inregs, &outregs, &segregs);
return outregs.x.cflag;
}
Returns true if disk is in drive.

QtCreator undefined reference using FTD2XX C++

So I've been playing with this one for a week now and I figured it was time to call in some backup. I am currently developing some software to communicate with some boards I am developing. The boards have ft230 chips from FTDI, so I am using their FTD2XX library to interface with them. This is where the issue starts.
For the longest time I have been getting "Undefined Reference" errors for some functions and not others. So I think the linker is correct, it just isn't working for all the functions. My environment is QtCreator 3.3.0 based on QT 5.4.0. The compiler is MinGW 4.9.1. The operating system is Windows 8.1. I have downloaded the latest drivers for 8.1 from ftdichips.com. Below is the function giving me the issue:
#include <windows.h>
#include "ftdiDriver/ftdi.h"
#include <ftd2xx.h>
#include <QDebug>
void ftdiConnect(void){
// Variables for FTDI communicaiton
FT_STATUS ftStatus;
FT_DEVICE_LIST_INFO_NODE *devInfo;
DWORD numDevs;
// create the device information list
ftStatus = FT_CreateDeviceInfoList(&numDevs);
if (ftStatus == FT_OK) {
qDebug() << "Number of devices is: " << numDevs;
}
if (numDevs > 0) {
// allocate storage for list based on numDevs
devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs);
// get the device information list
ftStatus = FT_GetDeviceInfoList(devInfo, &numDevs);
if (ftStatus == FT_OK) {
for (int i = 0; i < numDevs; i++) {
qDebug() << "Dev: " << i;
qDebug() << " Flags: " << devInfo[i].Flags;
qDebug() << " Type: " << devInfo[i].Type;
qDebug() << " ID: " << devInfo[i].ID;
qDebug() << " LocId: " << devInfo[i].LocId;
qDebug() << " SerialNumber: " << devInfo[i].SerialNumber;
qDebug() << " Description: " << devInfo[i].Description;
qDebug() << " ftHandle: " << devInfo[i].ftHandle;
}
}
}
}
I've searched a couple forums. It seems that most people are able to include the .lib file in their project file and things seem to work. I have tried every combination of this, and I have the same issue. Since some functions for (FT_ListDevices for example), I don't think that's the issue. It would not make sense for some functions to work and not others. I've called FTDIchips and they told me they would get back to me when they have had time to look at the issue, but had no idea of anything to try in the mean time.
If you think it would be helpful, I could include the project text in this question as well. I just didn't want it to become cluttered.
Linker's "undefined references" mean (i) either you didn't provide those functions referenced (no .lib file), or (ii) you provide wrong stuff (say, 32-bit instead of 64-bit), or (iii) you asked for wrong function (wrong function name or wrong modifiers).
Anyway, it's completely your fault. You should at last read something about linking executables with external libraries and dll's, and start using your head. It's a shame to ask tech support, just because you are too lazy to read some linker's guide.

How to identify the type of disc in a CD-ROM drive using WinAPI?

I'm writing an application that's going to work with audio CDs and mixed CDs. I would like to have a method of determining whether there is an audio or mixed-type (with at least one audio track) disc currently in the drive that the application uses.
So far, I was able to identify that the drive is a CD-ROM by GetDriveType. However, it turns out that identifying the media that is actually inside the drive is not that easy. This is what I've got so far :
int drive_has_audio_disc(const char *root_path)
{
char volume_name[MAX_PATH+1];
BOOL winapi_rv;
DWORD fs_flags;
int rv;
winapi_rv = GetVolumeInformation(root_path, volume_name, sizeof(volume_name),
NULL, NULL, &fs_flags, NULL, 0);
if(winapi_rv != 0)
{
rv = (strcmp(volume_name, "Audio CD") == 0 &&
(fs_flags & FILE_READ_ONLY_VOLUME));
}
else
{
rv = (GetLastError() == ERROR_INVALID_PARAMETER) ? 0 : -1;
}
return rv;
}
However, it relies on the fact that Windows assigns the name "Audio CD" to all discs that are recognized as audio. This doesn't feel right, and is going to fail miserably on mixed-mode CDs, since their name in Windows is determined by the volume name of the data track. Also, the else block is here because I've noticed that GetVolumeInformation returns an error with GetLastError equal to ERROR_INVALID_PARAMETER when there is no disc in the drive at all.
Ideally, I'm looking for something like the CDROM_DISC_STATUS ioctl present on Linux. It returns CDS_NO_INFO, CDS_AUDIO, CDS_MIXED, or some other values, depending on the contents of the disc.
Is there any other way of handling this? And what about mixed-mode discs?
You can use the CD-ROM I/O Control Codes, in particular the IOCTL_CDROM_READ_TOC. The structure it returns looks like this:
struct TRACK_DATA {
UCHAR Reserved;
UCHAR Control :4;
UCHAR Adr :4;
UCHAR TrackNumber;
UCHAR Reserved1;
UCHAR Address[4];
}
struct CDROM_TOC {
UCHAR Length[2];
UCHAR FirstTrack;
UCHAR LastTrack;
TRACK_DATA TrackData[MAXIMUM_NUMBER_TRACKS];
};
You can find an example of how to retrieve it on Larry Osterman's blog.
From this you should be able to determine the exact disc type. If not, check out other IOCTLs, I'm sure there should be one that gives you the necessary info.
I once had to do something similar for a project, but with DVDs instead. You can use DeviceIoControl(IOCTL_SCSI_PASS_THROUGH) to send a MultiMedia Commands (MMC) GET CONFIGURATION command directly to the drive to retrieve its currently active Profile, which is based on the specific type of disc that is currently loaded.
The CD profiles are:
0x0008 CD-ROM
0x0009 CD-R
0x000A CD-RW
0x0020 Double Density CD-ROM
0x0021 Double Density CD-R
0x0022 Double Density CD-RW

Resources