Query metadata for NTFS special files as unprivileged user? - c++

From an unprivileged user context, how can I query the size of the NTFS special files?
The size is the most important piece of metadata for me, but if I could get everything that is typically found in WIN32_FIND_DATA I would not mind it.
The NTFS special files I mean are (among others): $Mft, $MftMirr, $LogFile, $BadClus et cetera.
In order to open the MFT, I'd have to acquire certain privileges, open the volume and then parse the MFT. So that's out.
Also it seems to be impossible to open these files by name (for the majority of them), which precludes NtQueryInformationFile() and GetFileInformationByHandle(). Or maybe there is a combination of flags I haven't tried and it is possible to open them somehow for querying the file information?
Last but not least I don't get these files returned when using the respective Win32 APIs (FindFirstFile() et. al.), nor with NtQueryDirectoryFile() nor by using IRP_MN_QUERY_DIRECTORY directly.
Yes, I understand that I can effectively get the size of the MFT using FSCTL_GET_NTFS_VOLUME_DATA, but that's just one of these special files.

on ntfs volume we can enumerate all file records with FSCTL_GET_NTFS_FILE_RECORD. unfortunatelly format of FileRecordBuffer is undocumented/undeclared in windows headers. but this is common ntfs structs. buffer begin with NTFS_RECORD_HEADER (base class) after which will be several NTFS_ATTRIBUTE records. partial and custom definitions:
union NTFS_FILE_ID
{
LONGLONG IndexNumber;
struct
{
LONGLONG MftRecordIndex : 48;
LONGLONG SequenceNumber : 16;
};
};
struct NTFS_RECORD_HEADER
{
enum {
FILE = 'ELIF',
INDX = 'XDNI',
BAAD = 'DAAB',
HOLE = 'ELOH',
CHKD = 'DKHC'
} Type;
USHORT UsaOffset;
USHORT UsaCount;
USN Usn;
};
struct NTFS_FILE_RECORD_HEADER : public NTFS_RECORD_HEADER
{
USHORT SequenceNumber;
USHORT LinkCount;
USHORT AttributesOffset;
USHORT Flags;
ULONG BytesInUse;
ULONG BytesAllocated;
ULONGLONG BaseFileRecord;
USHORT NextAttributeNumber;
enum{
flgInUse = 1, flgDirectory = 2
};
};
struct NTFS_ATTRIBUTE
{
enum ATTRIBUTE_TYPE {
StandardInformation = 0x10,
AttributeList = 0x20,
FileName = 0x30,
ObjectId = 0x40,
SecurityDescriptor = 0x50,
VolumeName = 0x60,
VolumeInformation = 0x70,
Data = 0x80,
IndexRoot = 0x90,
IndexAllocation = 0xa0,
Bitmap = 0xb0,
ReparsePoint = 0xc0,
EAInformation = 0xd0,
EA = 0xe0,
PropertySet = 0xf0,
LoggedUtilityStream = 0x100,
StopTag = MAXDWORD
} Type;
ULONG Length;
BOOLEAN Nonresident;
UCHAR NameLength;
USHORT NameOffset;
USHORT Flags;// 1 = Compresed
USHORT AttributeNumber;
};
struct NTFS_RESIDENT_ATTRIBUTE : public NTFS_ATTRIBUTE
{
ULONG ValueLength;
USHORT ValueOffset;
USHORT Flags;
};
struct NTFS_NONRESIDENT_ATTRIBUTE : public NTFS_ATTRIBUTE
{
LONGLONG LowVcn;
LONGLONG HighVcn;
USHORT RunArrayOffset;
UCHAR CompressionUnit;
UCHAR Unknown[5];
LONGLONG AllocationSize;
LONGLONG DataSize;
LONGLONG InitializedSize;
LONGLONG CompressedSize;
};
struct NTFS_ATTRIBUTE_LIST
{
NTFS_ATTRIBUTE::ATTRIBUTE_TYPE Type;
USHORT Length;
UCHAR NameLength;
UCHAR NameOffset;
LONGLONG LowVcn;
LONGLONG FileReferenceNumber : 48;
LONGLONG FileReferenceNumber2 : 16;
USHORT AttributeNumber;
USHORT Unknown[3];
};
struct NTFS_STANDARD_ATTRIBUTE
{
LONGLONG CreationTime;
LONGLONG ChangeTime;
LONGLONG LastWriteTime;
LONGLONG LastAccessTime;
ULONG FileAttributes;
ULONG Unknown[3];
ULONG QuotaId;
ULONG SecurityId;
ULONGLONG QuotaChange;
USN Usn;
};
struct NTFS_FILENAME_ATTRIBUTE
{
NTFS_FILE_ID DirectoryId;
LONGLONG CreationTime;
LONGLONG ChangeTime;
LONGLONG LastWriteTime;
LONGLONG LastAccessTime;
LONGLONG AllocationSize;
LONGLONG DataSize;
ULONG FileAttributes;
ULONG EaSize;
UCHAR FileNameLength;// in symbols !!
UCHAR NameType;
WCHAR FileName[];
enum {
systemName , longName, shortName, systemName2
};
};
the code of enumeration all files can look like:
inline ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
ULONG QFMD(PCWSTR szVolumeName)
{
HANDLE hVolume = CreateFile(szVolumeName, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hVolume == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
ULONG cb, BytesReturned;
NTFS_VOLUME_DATA_BUFFER nvdb;
ULONG err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_VOLUME_DATA, 0, 0, &nvdb, sizeof(nvdb), &BytesReturned, 0));
if (err == NOERROR)
{
NTFS_FILE_RECORD_INPUT_BUFFER nfrib;
cb = FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer[nvdb.BytesPerFileRecordSegment]);
PNTFS_FILE_RECORD_OUTPUT_BUFFER pnfrob = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)alloca(cb);
// search for maximum valid FileReferenceNumber
LONG a = 0, b = MAXLONG, o;
do
{
nfrib.FileReferenceNumber.QuadPart = o = (a + b) >> 1;
err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD,
&nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0));
err ? b = o : a = o + 1;
} while(a < b);
nfrib.FileReferenceNumber.QuadPart--;
DbgPrint("MftRecordCount=%u\n", nfrib.FileReferenceNumber.LowPart);
union {
PVOID FileRecordBuffer;
PBYTE pb;
NTFS_RECORD_HEADER* pnrh;
NTFS_FILE_RECORD_HEADER* pnfrh;
NTFS_ATTRIBUTE* pna;
NTFS_RESIDENT_ATTRIBUTE* pnra;
NTFS_NONRESIDENT_ATTRIBUTE* pnaa;
};
NTFS_FILE_ID nfi;
UNICODE_STRING us = { sizeof (nfi), sizeof (nfi), (PWSTR)&nfi };
OBJECT_ATTRIBUTES oa = { sizeof(oa), hVolume, &us };
do
{
FileRecordBuffer = pnfrob->FileRecordBuffer;
if (err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD,
&nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0)))
{
break;
}
// are really file
if (
pnrh->Type != NTFS_RECORD_HEADER::FILE ||
!(pnfrh->Flags & NTFS_FILE_RECORD_HEADER::flgInUse) ||
pnfrh->BaseFileRecord
)
{
continue;
}
ULONG FileAttributes = INVALID_FILE_ATTRIBUTES;
ULONGLONG FileSize = 0;
nfi.MftRecordIndex = pnfrob->FileReferenceNumber.QuadPart;
nfi.SequenceNumber = pnfrh->SequenceNumber;
pb += pnfrh->AttributesOffset;
for( ; ; )
{
NTFS_FILENAME_ATTRIBUTE* pnfa;
NTFS_STANDARD_ATTRIBUTE* pnsa;
switch (pna->Type)
{
case NTFS_ATTRIBUTE::StopTag:
goto __end;
case NTFS_ATTRIBUTE::FileName:
pnfa = (NTFS_FILENAME_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
if (pnfa->NameType == NTFS_FILENAME_ATTRIBUTE::longName)
{
//DbgPrint("<< %.*S\n", pnfa->FileNameLength, pnfa->FileName);
}
break;
case NTFS_ATTRIBUTE::StandardInformation:
pnsa = (NTFS_STANDARD_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
FileAttributes = pnsa->FileAttributes;
break;
case NTFS_ATTRIBUTE::Data:
FileSize += pna->Nonresident ? pnaa->DataSize : pnra->ValueLength;
break;
}
pb += pna->Length;
}
__end:;
//HANDLE hFile;
//IO_STATUS_BLOCK iosb;
//NTSTATUS status = NtOpenFile(&hFile, FILE_READ_ATTRIBUTES, &oa, &iosb, FILE_SHARE_VALID_FLAGS,
// FILE_OPEN_REPARSE_POINT| FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT);
//if (0 <= status)
//{
// NtClose(hFile);
//}
} while (0 <= (nfrib.FileReferenceNumber.QuadPart = pnfrob->FileReferenceNumber.QuadPart - 1));
}
CloseHandle(hVolume);
return err;
}
some NTFS System Files, but this list already old, exist more system files. if want concrete system file query need assign it number to NTFS_FILE_RECORD_INPUT_BUFFER. little changed code for query sys files only:
ULONG QFMD(PCWSTR szVolumeName)
{
HANDLE hVolume = CreateFile(szVolumeName, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hVolume == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
ULONG cb, BytesReturned;
NTFS_VOLUME_DATA_BUFFER nvdb;
ULONG err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_VOLUME_DATA, 0, 0, &nvdb, sizeof(nvdb), &BytesReturned, 0));
if (err == NOERROR)
{
NTFS_FILE_RECORD_INPUT_BUFFER nfrib;
nfrib.FileReferenceNumber.QuadPart = 0x30;
cb = FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer[nvdb.BytesPerFileRecordSegment]);
PNTFS_FILE_RECORD_OUTPUT_BUFFER pnfrob = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)alloca(cb);
union {
PVOID FileRecordBuffer;
PBYTE pb;
NTFS_RECORD_HEADER* pnrh;
NTFS_FILE_RECORD_HEADER* pnfrh;
NTFS_ATTRIBUTE* pna;
NTFS_RESIDENT_ATTRIBUTE* pnra;
NTFS_NONRESIDENT_ATTRIBUTE* pnaa;
};
NTFS_FILE_ID nfi;
UNICODE_STRING us = { sizeof (nfi), sizeof (nfi), (PWSTR)&nfi };
OBJECT_ATTRIBUTES oa = { sizeof(oa), hVolume, &us };
do
{
FileRecordBuffer = pnfrob->FileRecordBuffer;
if (err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD,
&nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0)))
{
break;
}
// are really file
if (
pnrh->Type != NTFS_RECORD_HEADER::FILE ||
!(pnfrh->Flags & NTFS_FILE_RECORD_HEADER::flgInUse) ||
pnfrh->BaseFileRecord
)
{
continue;
}
ULONG FileAttributes = INVALID_FILE_ATTRIBUTES;
ULONGLONG FileSize = 0;
PCWSTR ShortName = 0, LongName = 0, SystemName = 0;
UCHAR ShortNameLength = 0, LongNameLength = 0, SystemNameLength = 0;
nfi.MftRecordIndex = pnfrob->FileReferenceNumber.QuadPart;
nfi.SequenceNumber = pnfrh->SequenceNumber;
pb += pnfrh->AttributesOffset;
BOOL bSysFile = FALSE;
for( ; ; )
{
union {
NTFS_FILENAME_ATTRIBUTE* pnfa;
NTFS_STANDARD_ATTRIBUTE* pnsa;
};
switch (pna->Type)
{
case NTFS_ATTRIBUTE::StopTag:
goto __end;
case NTFS_ATTRIBUTE::FileName:
pnfa = (NTFS_FILENAME_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
switch (pnfa->NameType)
{
case NTFS_FILENAME_ATTRIBUTE::systemName:
case NTFS_FILENAME_ATTRIBUTE::systemName2:
bSysFile = TRUE;
SystemName = pnfa->FileName, SystemNameLength = pnfa->FileNameLength;
break;
case NTFS_FILENAME_ATTRIBUTE::longName:
LongName = pnfa->FileName, LongNameLength = pnfa->FileNameLength;
break;
case NTFS_FILENAME_ATTRIBUTE::shortName:
ShortName = pnfa->FileName, ShortNameLength = pnfa->FileNameLength;
break;
}
break;
case NTFS_ATTRIBUTE::StandardInformation:
pnsa = (NTFS_STANDARD_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
FileAttributes = pnsa->FileAttributes;
break;
case NTFS_ATTRIBUTE::Data:
FileSize += pna->Nonresident ? pnaa->DataSize : pnra->ValueLength;
break;
}
pb += pna->Length;
}
__end:;
if (bSysFile)
{
HANDLE hFile;
IO_STATUS_BLOCK iosb;
NTSTATUS status = NtOpenFile(&hFile, FILE_READ_ATTRIBUTES, &oa, &iosb, FILE_SHARE_VALID_FLAGS,
FILE_OPEN_REPARSE_POINT| FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT);
if (0 <= status)
{
NtClose(hFile);
}
char sz[32];
StrFormatByteSize64A(FileSize, sz, RTL_NUMBER_OF(sz));
DbgPrint("%I64u: %08x %s [%x] %.*S\n", pnfrob->FileReferenceNumber.QuadPart,
FileAttributes, sz, status, SystemNameLength, SystemName);
}
} while (0 <= (nfrib.FileReferenceNumber.QuadPart = pnfrob->FileReferenceNumber.QuadPart - 1));
}
CloseHandle(hVolume);
return err;
}
with it i got next result:
38: 10000006 0 bytes [0] $Deleted
34: 00000020 10.0 MB [0] $TxfLogContainer00000000000000000002
33: 00000020 10.0 MB [0] $TxfLogContainer00000000000000000001
32: 00000020 64.0 KB [0] $TxfLog.blf
31: 00000026 1.00 MB [0] $Tops
30: 80000006 0 bytes [0] $Txf
29: 00000006 0 bytes [0] $TxfLog
28: 00000026 27.0 MB [0] $Repair
27: 00000006 0 bytes [0] $RmMetadata
26: 20000026 0 bytes [c0000034] $Reparse
25: 20000026 0 bytes [c0000034] $ObjId
24: 20000026 0 bytes [c0000034] $Quota
11: 00000006 0 bytes [0] $Extend
10: 00000006 128 KB [0] $UpCase
9: 20000006 0 bytes [c0000034] $Secure
8: 00000006 237 GB [c0000022] $BadClus
7: 00000006 8.00 KB [c0000022] $Boot
6: 00000006 7.42 MB [c0000022] $Bitmap
5: 00000806 0 bytes [0] .
4: 00000006 2.50 KB [0] $AttrDef
3: 00000006 0 bytes [0] $Volume
2: 00000006 64.0 MB [c0000022] $LogFile
1: 00000006 4.00 KB [0] $MFTMirr
0: 00000006 212 MB [0] $MFT

Yes, it is possible to use DeviceIoControl / FSCTL_GET_NTFS_FILE_RECORD to read the $MFT without elevation. With the help of this page, I have worked out the minimum settings. Note the last few paragraphs at the bottom of that page.
in Group Policy gpedit.msc, add the (non-elevated) user account you'll be running under to the following to the following policies:
Windows Settings/Security Settings/Local Policies/User Rights Assignment/...Perform Volume Maintenance tasks (definitely needed)
Windows Settings/Security Settings/Local Policies/User Rights Assignment/...Back up files and directories (not sure if this is essential)
So far, I haven't needed the following, but make a note of it in case you need to come back to it:
Windows Settings/Security Settings/Local Policies/User Rights Assignment/...Restore files and directories
Run gpupdate.exe from a Windows command prompt, or wait about 15 minutes for the group policy changes to take effect.
Those changes allow your user account to acquire the privileges. As a one-time step every time your app starts, you'll have to explicitly adjust your token. Here's a standalone version of the Win32 API AdjustTokenPrivileges:
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.ComponentModel;
[SuppressUnmanagedCodeSecurity]
public static class AdjPriv
{
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool OpenProcessToken(IntPtr h, int acc, out IntPtr phtok);
[DllImport("advapi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool LookupPrivilegeValueW(IntPtr host, [MarshalAs(UnmanagedType.LPWStr)] String name, out long pluid);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, in TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
const int
SE_PRIVILEGE_ENABLED /**/ = 0x00000002,
TOKEN_QUERY /**/ = 0x00000008,
TOKEN_ADJUST_PRIVILEGES /**/ = 0x00000020,
ERROR_NOT_ALL_ASSIGNED /**/ = 0x00000514;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
};
public static bool SetPrivilege(String szSe)
{
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out IntPtr htok))
goto _error;
var tp = new TokPriv1Luid { Count = 1, Attr = SE_PRIVILEGE_ENABLED };
if (!LookupPrivilegeValueW(IntPtr.Zero, szSe, out tp.Luid))
goto _error;
if (!AdjustTokenPrivileges(htok, false, in tp, 0, IntPtr.Zero, IntPtr.Zero))
goto _error;
return Marshal.GetLastWin32Error() != ERROR_NOT_ALL_ASSIGNED;
_error:
throw new Win32Exception();
}
};
Give the current user account the "SeManageVolumePrivilege" privilege when the app starts up by calling the AdjPriv.SetPrivilege utility function shown above. Call it once for each additional privilege you may also want to add.
static MyProgram()
{
if (!AdjPriv.SetPrivilege("SeManageVolumePrivilege"))
throw new SecurityException();
/// etc...
}
And now for the code. I won't go into detail with the p/Invoke since everyone has their own way of doing it. I'll just show the exact flags and constants values passed into the two critical two API calls in order to work without prompting for elevation.
IntPtr h = CreateFileW(#"\\?\Volume{c2655473-adc2-4fe3-99a0-77d5bb1b809f}\",
FILE_ACCESS_READ_CONTROL, // 0x00020000
FILE_SHARE_ANY, // 7
IntPtr.Zero,
CREATE_MODE_OPEN_EXISTING, // 3
FILE_FLAG_BACKUP_SEMANTICS, // 0x02000000
IntPtr.Zero);
And then finally...
/// <summary>
/// Given a 48-bit MFT index 'frn', recover the current "sequence number" of the file, which
/// can be used as the upper 16-bits to complete a usable FILE_REFERENCE (NTFS FileId):
/// </summary>
var frn = (FILE_REFERENCE)0x_0000_000000000218;
// ^^^^----- ???
// lookup proceeds downwards, so set the sought-after upper 16 bits to max. seq. value
frn.Seq = 0xFFFF;
if (!DeviceIoControl(h, FSCTL_GET_NTFS_FILE_RECORD, in frn, out FILE_RECORD_OUTPUT_BUFFER rec) ||
frn.Index != rec.file_ref.Index)
throw new Win32Exception();
frn.Seq = rec.frh.SequenceNumber;
// vvvv--- !!!
Console.WriteLine($"0x{frn:X16}"); // 0x_0092_000000000218
Notes:
It works! Well at least for me on Windows 10, Version 10.0.18362.387. Please notice the backslash ...\ at the end of the Volume Guid path in CreateFileW. The code will not work without it.

Related

How to get all (non-disabled) user SIDs via Windows API?

I'm looking for a way to retrieve all user SIDs on a system via the Windows API.
Retrieving all user SIDs can be done with via wmic useraccount get sid. Is there a way of getting this information via the Windows API instead?
Additionally, the wmic command returns the SIDs of all accounts, including disabled accounts - wmic useraccount get disabled,sid will show which accounts are disabled. It would be a bonus if a solution could advise on how to retrieve the SIDs of accounts that are not disabled, but this is not crucial.
You could use the function:
NET_API_STATUS NET_API_FUNCTION NetUserEnum(
LPCWSTR servername,
DWORD level,
DWORD filter,
LPBYTE *bufptr,
DWORD prefmaxlen,
LPDWORD entriesread,
LPDWORD totalentries,
PDWORD resume_handle
);
with servername = NULL to enumerate local computer accounts, then use:
BOOL LookupAccountNameW(
LPCWSTR lpSystemName,
LPCWSTR lpAccountName,
PSID Sid,
LPDWORD cbSid,
LPWSTR ReferencedDomainName,
LPDWORD cchReferencedDomainName,
PSID_NAME_USE peUse
);
to retrieve SID's.
Refer to https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuserenum and https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamew for details and examples.
In function NetUserEnum, setting the parameter level=1 will return detailed information about user accounts, and the bufptr parameter will point to an array of USER_INFO_1 structures.
Examining the member usri1_flags of structure USER_INFO_1 with mask UF_ACCOUNTDISABLE give the status of account.
Following RbMm comment, note that specifying in function NetUserEnum the parameter level=3, the bufptr parameter will point to an array of USER_INFO_3 structures, that contains user RID's.
The member usri3_user_id contains the relative ID (RID) of the user, and the member usri3_primary_group_id contains the RID of the Primary Global Group for the user. Using these values you don't need to call LookupAccountNameW.
The efficiency is boosted using suggestions from RbMm in the comments below.
There are several ways.
A simple one is with NetQueryDisplayInformation
Test sample (Windows 10, VS 2015) =>
NET_API_STATUS NetStatus;
DWORD dwIndex = 0;
DWORD dwEntriesRequested = 0xFFFFFFFF;
DWORD dwPreferredMaximumLength = 0xFFFFFFFF;
DWORD dwReturnedEntryCount;
PVOID pNDU = NULL;
do {
NetStatus = NetQueryDisplayInformation(NULL, 1, dwIndex, dwEntriesRequested, dwPreferredMaximumLength, &dwReturnedEntryCount, &pNDU);
if (NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA)
break;
for (int i = 0; i < dwReturnedEntryCount; i++)
{
PNET_DISPLAY_USER NetDisplayUser = (PNET_DISPLAY_USER)(((LPBYTE)pNDU) + sizeof(NET_DISPLAY_USER) * i);
PSID pSID = ConvertNameToSID(NetDisplayUser->usri1_name);
LPWSTR pszSid = NULL;
ConvertSidToStringSid(pSID, &pszSid);
BOOL bIsAccountDisabled = ((NetDisplayUser->usri1_flags & UF_ACCOUNTDISABLE) != 0) ? TRUE : FALSE;
WCHAR wsBuffer[MAX_PATH];
wsprintf(wsBuffer, L"%4.4ld %-20.20ws SID : %ws - Disabled : %ws - Comment : %ws\n",
NetDisplayUser->usri1_next_index,
NetDisplayUser->usri1_name,
pszSid,
(bIsAccountDisabled ? L"True" : L"False"),
NetDisplayUser->usri1_comment
);
LocalFree(pSID);
OutputDebugString(wsBuffer);
dwIndex = NetDisplayUser->usri1_next_index;
}
NetApiBufferFree(pNDU);
} while (NetStatus == ERROR_MORE_DATA);
PSID ConvertNameToSID(LPTSTR lpszName)
{
WCHAR wszDomainName[256];
DWORD dwSizeDomain = sizeof(wszDomainName) / sizeof(TCHAR);
DWORD dwSizeSid = 0;
SID_NAME_USE sidName;
LookupAccountName(NULL, lpszName, NULL, &dwSizeSid, wszDomainName, &dwSizeDomain, &sidName);
PSID pSid;
pSid = (PSID)LocalAlloc(LPTR, dwSizeSid);
LookupAccountName(NULL, lpszName, pSid, &dwSizeSid, wszDomainName, &dwSizeDomain, &sidName);
return pSid;
}
for enumerate user accounts in SAM (Security Account Manager) database we can use or NetQueryDisplayInformation (more fast) or NetUserEnum (if we need more detail user information). or SAM api (fastest, include ntsam.h and link with samlib.lib )
note that if we have user (RID) we not need use LookupAccountName - this is very not efficient in this case (many heavy remote calls internal - LsaOpenPolicy, LsaLookupNames2, LsaClose . internal LsaLookupNames2 use anyway SAM api SamLookupNamesInDomain).
really all what we need - first get domain SID and than append user RID to it. get domain SID we can by LsaQueryInformationPolicy with PolicyAccountDomainInformation for SID of the account domain (computer) - always exist and with PolicyDnsDomainInformation or PolicyPrimaryDomainInformation for get SID of the primary domain (exist only if computer part of Domain)
void PrintUsersInDomain(PUNICODE_STRING ServerName, PSID DomainSid)
{
PWSTR szServerName = 0;
if (ServerName)
{
if (ULONG Length = ServerName->Length)
{
szServerName = ServerName->Buffer;
// if not null terminated
if (Length + sizeof(WCHAR) < ServerName->MaximumLength || *(PWSTR)((PBYTE)szServerName + Length))
{
szServerName = (PWSTR)alloca(Length + sizeof(WCHAR));
memcpy(szServerName, ServerName->Buffer, Length);
*(PWSTR)((PBYTE)szServerName + Length) = 0;
}
}
}
UCHAR SubAuthorityCount = *GetSidSubAuthorityCount(DomainSid);
ULONG DestinationSidLength = GetSidLengthRequired(SubAuthorityCount + 1);
PSID UserSid = alloca(DestinationSidLength);
CopySid(DestinationSidLength, UserSid, DomainSid);
++*GetSidSubAuthorityCount(UserSid);
PULONG pRid = GetSidSubAuthority(UserSid, SubAuthorityCount);
PVOID Buffer;
ULONG Index = 0, ReturnedEntryCount;
NET_API_STATUS status;
do
{
switch (status = NetQueryDisplayInformation(szServerName, 1, Index,
64, MAX_PREFERRED_LENGTH, &ReturnedEntryCount, &Buffer))
{
case NOERROR:
case ERROR_MORE_DATA:
if (ReturnedEntryCount)
{
PNET_DISPLAY_USER pndu = (PNET_DISPLAY_USER)Buffer;
do
{
//if (!(pndu->usri1_flags & UF_ACCOUNTDISABLE))
{
*pRid = pndu->usri1_user_id;
PWSTR szSid;
if (ConvertSidToStringSidW(UserSid, &szSid))
{
DbgPrint("\t[%08x] %S %S\n", pndu->usri1_flags, pndu->usri1_name, szSid);
LocalFree(szSid);
}
}
Index = pndu->usri1_next_index;
} while (pndu++, --ReturnedEntryCount);
}
NetApiBufferFree(Buffer);
}
} while (status == ERROR_MORE_DATA);
}
void PrintUsersInDomain_fast(PUNICODE_STRING ServerName, PSID DomainSid)
{
SAM_HANDLE ServerHandle, DomainHandle = 0;
//SAM_SERVER_ENUMERATE_DOMAINS|SAM_SERVER_LOOKUP_DOMAIN
NTSTATUS status = SamConnect(ServerName, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, 0);
DbgPrint("SamConnect(%wZ) = %x\n", ServerName, status);
if (0 <= status)
{
status = SamOpenDomain(ServerHandle, DOMAIN_READ|DOMAIN_EXECUTE, DomainSid, &DomainHandle);
SamCloseHandle(ServerHandle);
}
if (0 <= status)
{
UCHAR SubAuthorityCount = *GetSidSubAuthorityCount(DomainSid);
ULONG DestinationSidLength = GetSidLengthRequired(SubAuthorityCount + 1);
PSID UserSid = alloca(DestinationSidLength);
CopySid(DestinationSidLength, UserSid, DomainSid);
++*GetSidSubAuthorityCount(UserSid);
PULONG pRid = GetSidSubAuthority(UserSid, SubAuthorityCount);
PVOID Buffer;
ULONG Index = 0, TotalAvailable, TotalReturned, ReturnedEntryCount;
do
{
if (0 <= (status = SamQueryDisplayInformation(DomainHandle,
DomainDisplayUser,
Index,
2,
0x10000,
&TotalAvailable,
&TotalReturned,
&ReturnedEntryCount,
&Buffer)))
{
if (ReturnedEntryCount)
{
PSAM_DISPLAY_USER psdu = (PSAM_DISPLAY_USER)Buffer;
do
{
//if (!(psdu->AccountControl & USER_ACCOUNT_DISABLED))
{
*pRid = psdu->Rid;
PWSTR szSid;
if (ConvertSidToStringSidW(UserSid, &szSid))
{
DbgPrint("\t[%08x] %wZ %S\n", psdu->AccountControl, &psdu->AccountName, szSid);
LocalFree(szSid);
}
}
Index = psdu->Index;
} while (psdu++, --ReturnedEntryCount);
}
SamFreeMemory(Buffer);
}
} while (status == STATUS_MORE_ENTRIES);
SamCloseHandle(DomainHandle);
}
}
void PrintUsers()
{
LSA_HANDLE PolicyHandle;
LSA_OBJECT_ATTRIBUTES ObjectAttributes = { sizeof(ObjectAttributes) };
NTSTATUS status;
if (0 <= (status = LsaOpenPolicy(0, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &PolicyHandle)))
{
union {
PVOID buf;
PPOLICY_DNS_DOMAIN_INFO pddi;
PPOLICY_ACCOUNT_DOMAIN_INFO padi;
};
if (0 <= LsaQueryInformationPolicy(PolicyHandle, PolicyAccountDomainInformation, &buf))
{
DbgPrint("DomainName=<%wZ>\n", &padi->DomainName);
if (padi->DomainSid)
{
PrintUsersInDomain_fast(&padi->DomainName, padi->DomainSid);
PrintUsersInDomain(&padi->DomainName, padi->DomainSid);
}
LsaFreeMemory(buf);
}
if (0 <= LsaQueryInformationPolicy(PolicyHandle, PolicyDnsDomainInformation, &buf))
{
DbgPrint("DomainName=<%wZ>\n", &pddi->Name);
if (pddi->Sid)
{
PrintUsersInDomain_fast(&pddi->Name, pddi->Sid);
PrintUsersInDomain(&pddi->Name, pddi->Sid);
}
LsaFreeMemory(buf);
}
LsaClose(PolicyHandle);
}
}
typedef struct SAM_DISPLAY_USER {
ULONG Index;
ULONG Rid;
ULONG AccountControl; /* User account control bits */
UNICODE_STRING AccountName;
UNICODE_STRING AdminComment;
UNICODE_STRING FullName;
} *PSAM_DISPLAY_USER;

Reliably know if a volume is removable or not with WinApi

I was using
DeviceIoControl(dev, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &driveInfo, sizeof(driveInfo), &dwResult, NULL)
to check if driveInfo.MediaType is RemovableMedia or FixedMedia, but it seems that all my volumes are "seen" as fixed:
\\.\C: NTFS Fixed, this is ok - internal hard drive
\\.\D: NTFS Fixed, this is ok - internal hard drive
\\.\E: NTFS Fixed, this is ok - internal hard drive
\\.\F: NTFS Fixed, this is NOT ok, this is a USB external 2.5" hard drive
Thus my question:
Is there a reliable way to know if a volume is removable or not?
There should be a way, because Windows does distinguish the removable ones (they have an icon "Safely remove hardward and eject media" near the clock).
The problem is that you're asking the wrong question. As they use the term, "removable" means that the media and the drive for the media are separate (like a floppy drive or CD-ROM). Anything that doesn't allow a single drive to hold different media at different times is a "fixed" drive.
Based on what you seem to want, I believe you want to use SetupDiGetDeviceRegistryProperty with the SPDRP_CAPABILITIES flag. This will tell you whether a drive can eject its media (pretty much equivalent to the "removable" you've already found), but also whether the device itself is removable (CM_DEVCAP_REMOVABLE).
Unfortunately, Microsoft's SetupDi* functions are kind of a mess to use (to put it as nicely as I know how). They have some demo code that uses the right functions and retrieves fairly similar information, but the code is also somewhat ugly, so it will probably take a little bit of study and experimentation to modify it to get what you want.
Jerry Coffin is almost right.
However, you have to check the SPDRP_REMOVAL_POLICY property instead.
Getting there is quite some hassle. Here is a C# implementation. It returns a list of PNPDeviceIDs or the device path. This can then be used to correlate with WMI data or the result of PowerShell's Get-Disk.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace checkIfDriveIsRemovable
{
class Program
{
// some relevant sources:
// https://www.pinvoke.net/default.aspx/setupapi.setupdigetclassdevs
// https://www.pinvoke.net/default.aspx/setupapi.setupdigetdeviceregistryproperty
// https://stackoverflow.com/questions/15000196/reading-device-managers-property-fields-in-windows-7-8
// https://stackoverflow.com/questions/14621211/determine-if-drive-is-removable-flash-or-hdd-knowing-only-the-drive-letter
private static string GUID_DEVINTERFACE_DISK = "53F56307-B6BF-11D0-94F2-00A0C91EFB8B";
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr( -1 );
const int BUFFER_SIZE = 1024;
enum RemovalPolicy : uint
{
CM_REMOVAL_POLICY_EXPECT_NO_REMOVAL = 1,
CM_REMOVAL_POLICY_EXPECT_ORDERLY_REMOVAL = 2,
CM_REMOVAL_POLICY_EXPECT_SURPRISE_REMOVAL = 3
}
enum SetupDiGetDeviceRegistryPropertyEnum : uint
{
SPDRP_DEVICEDESC = 0x00000000, // DeviceDesc (R/W)
SPDRP_HARDWAREID = 0x00000001, // HardwareID (R/W)
SPDRP_COMPATIBLEIDS = 0x00000002, // CompatibleIDs (R/W)
SPDRP_UNUSED0 = 0x00000003, // unused
SPDRP_SERVICE = 0x00000004, // Service (R/W)
SPDRP_UNUSED1 = 0x00000005, // unused
SPDRP_UNUSED2 = 0x00000006, // unused
SPDRP_CLASS = 0x00000007, // Class (R--tied to ClassGUID)
SPDRP_CLASSGUID = 0x00000008, // ClassGUID (R/W)
SPDRP_DRIVER = 0x00000009, // Driver (R/W)
SPDRP_CONFIGFLAGS = 0x0000000A, // ConfigFlags (R/W)
SPDRP_MFG = 0x0000000B, // Mfg (R/W)
SPDRP_FRIENDLYNAME = 0x0000000C, // FriendlyName (R/W)
SPDRP_LOCATION_INFORMATION = 0x0000000D, // LocationInformation (R/W)
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E, // PhysicalDeviceObjectName (R)
SPDRP_CAPABILITIES = 0x0000000F, // Capabilities (R)
SPDRP_UI_NUMBER = 0x00000010, // UiNumber (R)
SPDRP_UPPERFILTERS = 0x00000011, // UpperFilters (R/W)
SPDRP_LOWERFILTERS = 0x00000012, // LowerFilters (R/W)
SPDRP_BUSTYPEGUID = 0x00000013, // BusTypeGUID (R)
SPDRP_LEGACYBUSTYPE = 0x00000014, // LegacyBusType (R)
SPDRP_BUSNUMBER = 0x00000015, // BusNumber (R)
SPDRP_ENUMERATOR_NAME = 0x00000016, // Enumerator Name (R)
SPDRP_SECURITY = 0x00000017, // Security (R/W, binary form)
SPDRP_SECURITY_SDS = 0x00000018, // Security (W, SDS form)
SPDRP_DEVTYPE = 0x00000019, // Device Type (R/W)
SPDRP_EXCLUSIVE = 0x0000001A, // Device is exclusive-access (R/W)
SPDRP_CHARACTERISTICS = 0x0000001B, // Device Characteristics (R/W)
SPDRP_ADDRESS = 0x0000001C, // Device Address (R)
SPDRP_UI_NUMBER_DESC_FORMAT = 0X0000001D, // UiNumberDescFormat (R/W)
SPDRP_DEVICE_POWER_DATA = 0x0000001E, // Device Power Data (R)
SPDRP_REMOVAL_POLICY = 0x0000001F, // Removal Policy (R)
SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x00000020, // Hardware Removal Policy (R)
SPDRP_REMOVAL_POLICY_OVERRIDE = 0x00000021, // Removal Policy Override (RW)
SPDRP_INSTALL_STATE = 0x00000022, // Device Install State (R)
SPDRP_LOCATION_PATHS = 0x00000023, // Device Location Paths (R)
SPDRP_BASE_CONTAINERID = 0x00000024 // Base ContainerID (R)
}
[Flags]
public enum DiGetClassFlags : uint
{
DIGCF_DEFAULT = 0x00000001, // only valid with DIGCF_DEVICEINTERFACE
DIGCF_PRESENT = 0x00000002,
DIGCF_ALLCLASSES = 0x00000004,
DIGCF_PROFILE = 0x00000008,
DIGCF_DEVICEINTERFACE = 0x00000010,
}
public enum RegType : uint
{
REG_BINARY = 3,
REG_DWORD = 4,
REG_EXPAND_SZ = 2,
REG_MULTI_SZ = 7,
REG_SZ = 1
}
[StructLayout( LayoutKind.Sequential )]
struct SP_DEVICE_INTERFACE_DATA
{
public Int32 cbSize;
public Guid interfaceClassGuid;
public Int32 flags;
private UIntPtr reserved;
}
[StructLayout( LayoutKind.Sequential )]
struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid ClassGuid;
public UInt32 DevInst;
public IntPtr Reserved;
}
[StructLayout( LayoutKind.Sequential, CharSet = CharSet.Auto )]
struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
public int cbSize;
[MarshalAs( UnmanagedType.ByValArray, SizeConst = BUFFER_SIZE )]
public byte[] DevicePath;
}
[DllImport( "setupapi.dll", CharSet = CharSet.Auto )]
static extern IntPtr SetupDiGetClassDevs(
ref Guid ClassGuid,
IntPtr Enumerator,
IntPtr hwndParent,
uint Flags
);
[DllImport( #"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
static extern Boolean SetupDiEnumDeviceInterfaces( IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData );
[DllImport( #"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
static extern Boolean SetupDiGetDeviceInterfaceDetail( IntPtr hDevInfo, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, UInt32 deviceInterfaceDetailDataSize, ref UInt32 requiredSize, ref SP_DEVINFO_DATA deviceInfoData );
[DllImport( "setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
static extern bool SetupDiGetDeviceRegistryProperty( IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint property, out UInt32 propertyRegDataType, byte[] propertyBuffer, uint propertyBufferSize, out UInt32 requiredSize );
[DllImport( "setupapi.dll" )]
static extern bool SetupDiGetDeviceInstanceIdA(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, byte[] DeviceInstanceId, Int32 DeviceInstanceIdSize, out UInt32 RequiredSize);
[DllImport( "setupapi.dll" )]
static extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
static string getStringProp(IntPtr h, SP_DEVINFO_DATA da, SetupDiGetDeviceRegistryPropertyEnum prop)
{
UInt32 requiredSize;
UInt32 regType;
byte[] ptrBuf = new byte[BUFFER_SIZE];
if( !SetupDiGetDeviceRegistryProperty( h, ref da, ( uint ) prop, out regType, ptrBuf, BUFFER_SIZE, out requiredSize ) )
throw new InvalidOperationException( "Error getting string property" );
if( regType != (uint) RegType.REG_SZ || ( requiredSize & 1 ) != 0 )
throw new InvalidOperationException( "Property is not a REG_SZ" );
if( requiredSize == 0 )
return "";
return Encoding.Unicode.GetString( ptrBuf, 0, (int) requiredSize - 2 );
}
static uint getDWORDProp(IntPtr h, SP_DEVINFO_DATA da, SetupDiGetDeviceRegistryPropertyEnum prop)
{
UInt32 requiredSize;
UInt32 regType;
byte[] ptrBuf = new byte[4];
if( !SetupDiGetDeviceRegistryProperty( h, ref da, ( uint ) prop, out regType, ptrBuf, 4, out requiredSize ) )
throw new InvalidOperationException( "Error getting DWORD property" );
if( regType != ( uint ) RegType.REG_DWORD || requiredSize != 4 )
throw new InvalidOperationException( "Property is not a REG_DWORD" );
return BitConverter.ToUInt32( ptrBuf, 0 );
}
public static string[] getRemovableDisks( bool getPNPDeviceID = false )
{
List<String> result = new List<string>();
Guid DiskGUID = new Guid( GUID_DEVINTERFACE_DISK );
IntPtr h = SetupDiGetClassDevs( ref DiskGUID, IntPtr.Zero, IntPtr.Zero, ( uint ) ( DiGetClassFlags.DIGCF_PRESENT | DiGetClassFlags.DIGCF_DEVICEINTERFACE ) );
if( h == INVALID_HANDLE_VALUE )
return null;
IntPtr x = new IntPtr(); ;
int y = Marshal.SizeOf( x );
try {
for( uint i = 0; ; i++ ) {
SP_DEVICE_INTERFACE_DATA dia = new SP_DEVICE_INTERFACE_DATA();
dia.cbSize = Marshal.SizeOf( dia );
if( !SetupDiEnumDeviceInterfaces( h, IntPtr.Zero, ref DiskGUID, i, ref dia ) )
break;
SP_DEVINFO_DATA da = new SP_DEVINFO_DATA();
da.cbSize = ( uint ) Marshal.SizeOf( da );
// build a Device Interface Detail Data structure
SP_DEVICE_INTERFACE_DETAIL_DATA didd = new SP_DEVICE_INTERFACE_DETAIL_DATA();
// I honestly don't know, why this works. The if-part can be found on the net a few times, the else part is from me
if( IntPtr.Size == 4 )
didd.cbSize = 4 + Marshal.SystemDefaultCharSize; // trust me :)
else
didd.cbSize = 8;
// now we can get some more detailed information
uint nRequiredSize = 0;
uint nBytes = BUFFER_SIZE;
if( SetupDiGetDeviceInterfaceDetail( h, ref dia, ref didd, nBytes, ref nRequiredSize, ref da ) ) {
string devicePath = Encoding.Unicode.GetString( didd.DevicePath, 0, ( int ) nRequiredSize - 6 ); // remove 6 bytes: 2 bytes zero termination and another 4 bytes, because nRequiredSize also counts SP_DEVICE_INTERFACE_DETAIL_DATA.cbSize (as it seems...)
UInt32 RequiredSize;
byte[] ptrBuf = new byte[BUFFER_SIZE];
string PNPDeviceID = "";
if( SetupDiGetDeviceInstanceIdA( h, ref da, ptrBuf, BUFFER_SIZE, out RequiredSize ) ) {
if( RequiredSize >= 1 )
PNPDeviceID = Encoding.ASCII.GetString( ptrBuf, 0, ( int ) RequiredSize - 1 );
}
// you can get the properties, which are shown in "device manager -> properties of the drive -> details"
/*
string desc = getStringProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_PHYSICAL_DEVICE_OBJECT_NAME ); // SPDRP_DEVICEDESC );
string driver = getStringProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_DRIVER );
string friendlyname = getStringProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_FRIENDLYNAME );
// no, the removable flag in the capabalities is of no use! Use removalPolicy!
uint capabilities = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_CAPABILITIES );
uint removalPolicy = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_REMOVAL_POLICY );
uint removalPolicyHW = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_REMOVAL_POLICY_HW_DEFAULT );
//uint removalPolicyOVR = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_REMOVAL_POLICY_OVERRIDE );
Console.WriteLine( "{0,-40} {1,-60} {2,-20} {3,-20} {4,5} {5} {6}", friendlyname, PNPDeviceID, desc, driver, capabilities, removalPolicy, removalPolicyHW );
*/
try {
uint removalPolicy = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_REMOVAL_POLICY );
if( removalPolicy == ( uint ) RemovalPolicy.CM_REMOVAL_POLICY_EXPECT_ORDERLY_REMOVAL || removalPolicy == ( uint ) RemovalPolicy.CM_REMOVAL_POLICY_EXPECT_SURPRISE_REMOVAL )
result.Add( getPNPDeviceID ? PNPDeviceID : devicePath );
} catch( InvalidOperationException ) {
continue;
}
}
}
} finally {
SetupDiDestroyDeviceInfoList( h );
}
return result.ToArray();
}
static void Main(string[] args)
{
string[] removableDisks = getRemovableDisks();
}
}
}
most simply and reliable way use IOCTL_STORAGE_QUERY_PROPERTY with StorageDeviceProperty. on return we got STORAGE_DEVICE_DESCRIPTOR - and look for
RemovableMedia
Indicates when TRUE that the device's media (if any) is removable.
If the device has no media, this member should be ignored. When
FALSE the device's media is not removable.
so we need disk handle with any access (because IOCTL_STORAGE_QUERY_PROPERTY defined as CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) (in every IOCtl encoded access(read, write, both or any), in this case FILE_ANY_ACCESS. with this handle query can look like next
ULONG IsRemovable(HANDLE hDisk, BOOLEAN& RemovableMedia)
{
STORAGE_PROPERTY_QUERY spq = { StorageDeviceProperty, PropertyStandardQuery };
STORAGE_DEVICE_DESCRIPTOR sdd;
ULONG rcb;
if (DeviceIoControl(hDisk, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(spq), &sdd, sizeof(sdd), &rcb, 0))
{
RemovableMedia = sdd.RemovableMedia;
return NOERROR;
}
return GetLastError();
}
for enumerate all disk drives we can use for example next code:
void EnumDisks()
{
ULONG len;
if (!CM_Get_Device_Interface_List_SizeW(&len, const_cast<GUID*>(&GUID_DEVINTERFACE_DISK), 0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
{
PWSTR buf = (PWSTR)alloca(len << 1);
if (!CM_Get_Device_Interface_ListW(const_cast<GUID*>(&GUID_DEVINTERFACE_DISK), 0, buf, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
{
while (*buf)
{
HANDLE hDisk = CreateFile(buf, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hDisk != INVALID_HANDLE_VALUE)
{
BOOLEAN RemovableMedia;
if (!IsRemovable(hDisk, RemovableMedia))
{
DbgPrint("%u %S\n", RemovableMedia, buf);
}
CloseHandle(hDisk);
}
buf += wcslen(buf) + 1;
}
}
}
}
but for test you can and open disk as L"\\\\?\\X:" for example

FindFirstVolume does not return EFI system partition

I am using FindFirstVolume/FindNextVolume to get a list of all volumes on a machine. That works well enough, but strangely the EFI system partition is skipped for some reason. Diskpart, on the other hand, returns the EFI system partition and the Disk Management UI shows it, too.
Here is diskpart's output:
DISKPART> list volume
Volume ### Ltr Label Fs Type Size Status Info
---------- --- ----------- ----- ---------- ------- --------- --------
Volume 0 C NTFS Partition 476 GB Healthy Boot
Volume 1 Recovery NTFS Partition 450 MB Healthy Hidden
Volume 2 FAT32 Partition 100 MB Healthy System
Volume 3 D Data NTFS Partition 953 GB Healthy
Volume 4 E ESD-USB FAT32 Removable 14 GB Healthy
In that list the EFI system partition is Volume 2.
FindFirstVolume/FindNextVolume give me the other four volumes, but omit the EFI system partition.
Any ideas how I can get a volume GUID path for the EFI system parition or some other way to programmatically access it?
volume GUID path (i.e. \??\Volume{..}) not exist if not returned by FindFirstVolume/FindNextVolume but we can enumerate all volumes in system by different ways. we say can use Virtual Disk Service com api . DISKPART> list volume use exactly this. another way(more 'low' and fast) - use CM_Get_Device_ID_ListW with pszFilter = "{71a27cdd-812a-11d0-bec7-08002be2092f}" (look GUID_DEVCLASS_VOLUME in devguid.h) than obtain a device instance handle by CM_Locate_DevNodeW and query for DEVPKEY_Device_PDOName with CM_Get_DevNode_PropertyW - we got string (like \Device\HarddiskVolume<X> ) which we can use in ZwOpenFile and then use some ioctls (IOCTL_DISK_GET_PARTITION_INFO_EX and other) for get volume properties. code example:
#include <initguid.h>
#include <cfgmgr32.h>
#include <devguid.h>
#include <devpkey.h>
#include <diskguid.h>
void DumpVolume(HANDLE hFile);
void VolEnum()
{
STATIC_WSTRING(DEVCLASS_VOLUME, "{71a27cdd-812a-11d0-bec7-08002be2092f}");
enum { flags = CM_GETIDLIST_FILTER_CLASS|CM_GETIDLIST_FILTER_PRESENT };
ULONG len;
ULONG cb = 0, rcb = 64;
HANDLE hFile;
IO_STATUS_BLOCK iosb;
UNICODE_STRING ObjectName;
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };
if (!CM_Get_Device_ID_List_SizeW(&len, DEVCLASS_VOLUME, flags))
{
PWSTR buf = (PWSTR)alloca(len << 1);
if (!CM_Get_Device_ID_ListW(DEVCLASS_VOLUME, buf, len, flags))
{
PVOID stack = buf;
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);
}
if (!(err = CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_Device_PDOName, &PropertyType, pb, &rcb, 0)))
{
if (PropertyType == DEVPROP_TYPE_STRING)
{
DbgPrint("%S\n", sz);
RtlInitUnicodeString(&ObjectName, sz);
if (0 <= ZwOpenFile(&hFile, FILE_GENERIC_READ, &oa, &iosb, FILE_SHARE_VALID_FLAGS, 0))
{
DumpVolume(hFile);
ZwClose(hFile);
}
}
}
} while (err == CR_BUFFER_SMALL);
}
buf += 1 + wcslen(buf);
}
}
}
}
void DumpVolume(HANDLE hFile)
{
NTSTATUS status;
PARTITION_INFORMATION_EX pi;
IO_STATUS_BLOCK iosb;
if (0 <= (status = ZwDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_DISK_GET_PARTITION_INFO_EX, 0, 0, &pi, sizeof(pi))))
{
CHAR PartitionName[40], *szPartitionName;
PCSTR szps = "??";
switch (pi.PartitionStyle)
{
case PARTITION_STYLE_MBR: szps = "MBR";
break;
case PARTITION_STYLE_GPT: szps = "GPT";
break;
}
DbgPrint("%u %s %I64u(%I64x) %I64u ",
pi.PartitionNumber,
szps,
pi.StartingOffset.QuadPart, pi.StartingOffset.QuadPart,
pi.PartitionLength.QuadPart);
switch (pi.PartitionStyle)
{
case PARTITION_STYLE_MBR:
DbgPrint("type=%x boot=%x", pi.Mbr.PartitionType, pi.Mbr.BootIndicator);
break;
case PARTITION_STYLE_GPT:
if (IsEqualGUID(pi.Gpt.PartitionType, PARTITION_ENTRY_UNUSED_GUID))
{
szPartitionName = "UNUSED";
}
else if (IsEqualGUID(pi.Gpt.PartitionType, PARTITION_SYSTEM_GUID))
{
szPartitionName = "SYSTEM";
}
else if (IsEqualGUID(pi.Gpt.PartitionType, PARTITION_MSFT_RESERVED_GUID))
{
szPartitionName = "RESERVED";
}
else if (IsEqualGUID(pi.Gpt.PartitionType, PARTITION_BASIC_DATA_GUID))
{
szPartitionName = "DATA";
}
else if (IsEqualGUID(pi.Gpt.PartitionType, PARTITION_MSFT_RECOVERY_GUID))
{
szPartitionName = "RECOVERY";
}
else if (IsEqualGUID(pi.Gpt.PartitionType, PARTITION_MSFT_SNAPSHOT_GUID))
{
szPartitionName = "SNAPSHOT";
}
else
{
sprintf(szPartitionName = PartitionName, "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
pi.Gpt.PartitionType.Data1,
pi.Gpt.PartitionType.Data2,
pi.Gpt.PartitionType.Data3,
pi.Gpt.PartitionType.Data4[0],
pi.Gpt.PartitionType.Data4[1],
pi.Gpt.PartitionType.Data4[2],
pi.Gpt.PartitionType.Data4[3],
pi.Gpt.PartitionType.Data4[4],
pi.Gpt.PartitionType.Data4[5],
pi.Gpt.PartitionType.Data4[6],
pi.Gpt.PartitionType.Data4[7]);
}
DbgPrint("[%s] %I64x \"%S\"",
szPartitionName,
pi.Gpt.Attributes,
pi.Gpt.Name);
break;
}
ULONG cb = FIELD_OFFSET(FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName[32]);
PFILE_FS_ATTRIBUTE_INFORMATION pffai = (PFILE_FS_ATTRIBUTE_INFORMATION)alloca(cb);
switch (ZwQueryVolumeInformationFile(hFile, &iosb, pffai, cb, FileFsAttributeInformation))
{
case STATUS_SUCCESS:
case STATUS_BUFFER_OVERFLOW:
DbgPrint(" \"%.*S\"", pffai->FileSystemNameLength >> 1 , pffai->FileSystemName);
break;
}
DbgPrint("\n");
}
else
{
DbgPrint("status=%x\n", status);
}
}
and debug output
STORAGE\Volume\{d2bfdb30-4d04-11e5-824e-806e6f6e6963}#0000000012D00000
\Device\HarddiskVolume2
2 GPT 315621376(12d00000) 104857600 [SYSTEM] 8000000000000000 "EFI system partition" "FAT32"
STORAGE\Volume\{d2bfdb30-4d04-11e5-824e-806e6f6e6963}#0000000000100000
\Device\HarddiskVolume1
1 GPT 1048576(100000) 314572800 [RECOVERY] 8000000000000001 "Basic data partition" "NTFS"
STORAGE\Volume\{d2bfdb30-4d04-11e5-824e-806e6f6e6963}#0000000021100000
\Device\HarddiskVolume4
4 GPT 554696704(21100000) 255506513920 [DATA] 0 "Basic data partition" "NTFS"
STORAGE\Volume\{d2bfdb30-4d04-11e5-824e-806e6f6e6963}#0000000019100000
\Device\HarddiskVolume3
3 GPT 420478976(19100000) 134217728 [RESERVED] 8000000000000000 "Microsoft reserved partition" "RAW"
STORAGE\Volume\{a4d55aa5-4d7f-11e5-8256-5cc5d4ea6270}#0000000000007E00
\Device\HarddiskVolume5
1 MBR 32256(7e00) 32017013248 type=7 boot=1 "NTFS"
for attributes look - https://msdn.microsoft.com/en-us/library/windows/desktop/aa365449(v=vs.85).aspx

STATUS_ACCESS_DENIED on a call to NtQueryMutant

Disclaimer:
The only reason for the question and the code below to exist is an external component used in my application, which cannot be replaced, at least in the near future. This component's logic intercepts WinAPI calls from the application and performs various tasks based on these calls.
One of the things the component does, it creates mutex for each thread initialized inside the application. However, it doesn't close the mutexes, which results in handles leak.
Therefore, in order to prevent the leak and because I don't have access to the component's source code, I have to invent ugly workarounds and use esoteric API's.
End of disclaimer
I am trying to check state of mutexes in my application. In order to do this without changing the state of each of the objects I check, I have to use the NtQueryMutant method from ntdll.dll.
Based on examples here and here I wrote the following code to achieve this:
enum MUTANT_INFORMATION_CLASS
{
MutantBasicInformation
};
struct MUTANT_BASIC_INFORMATION {
LONG CurrentCount;
BOOLEAN OwnedByCaller;
BOOLEAN AbandonedState;
};
typedef NTSTATUS(WINAPI*QueryMutexHandler)(HANDLE, MUTANT_INFORMATION_CLASS, PVOID, ULONG, PULONG);
//somewhere in the code:
QueryMutexHandler queryMutex = reinterpret_cast<QueryMutexHandler>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryMutant"));
MUTANT_BASIC_INFORMATION mutantInfo;
NTSTATUS status = queryMutex(objectHandleCopy, MutantBasicInformation, &mutantInfo, sizeof(MUTANT_BASIC_INFORMATION), nullptr);
if (NT_SUCCESS(status))
{
//never arriving here
}
The status I receive here is always -1073741790 (0xFFFF FFFF C000 0022) which is, except being negative number, looks exactly like STATUS_ACCESS_DENIED.
That is very strange, because previously in code I use both NtQuerySystemInformation and NtQueryObject without any problem.
Additional details: my OS is Windows 7 SP1, the mutexes I try to query belong to the process I am performing the query from.
for effective test Mutant you need it handle and it access mask. you can got it from SYSTEM_HANDLE_INFORMATION_EX structure. if we already have MUTANT_QUERY_STATE - can direct query, if no - need reopen handle with MUTANT_QUERY_STATE
NTSTATUS QueryMutant(HANDLE hMutant, ULONG GrantedAccess, MUTANT_BASIC_INFORMATION* pmbi)
{
if (GrantedAccess & MUTANT_QUERY_STATE)
{
return ZwQueryMutant(hMutant, MutantBasicInformation, pmbi, sizeof(MUTANT_BASIC_INFORMATION), 0);
}
NTSTATUS status = ZwDuplicateObject(NtCurrentProcess(), hMutant, NtCurrentProcess(),&hMutant,
MUTANT_QUERY_STATE, 0, 0);
if (0 <= status)
{
status = ZwQueryMutant(hMutant, MutantBasicInformation, pmbi, sizeof(MUTANT_BASIC_INFORMATION), 0);
ZwClose(hMutant);
}
return status;
}
and you not need all time use NtQueryObject for determinate type of handle. you can use SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex . for get OBJECT_TYPE_INFORMATION by this index. for this you need only once call ZwQueryObject(0, ObjectAllTypeInformation, ) at start, but exist problem how convert SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex to array index (zero bassed). begin from win8.1 'OBJECT_TYPE_INFORMATION.TypeIndex' is valid and match to SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex, but for early version - you need once get SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex for some known object type and calc delta
static volatile UCHAR guz;
NTSTATUS getProcessIndex(USHORT& ObjectTypeIndex)
{
HANDLE hProcess;
NTSTATUS status = ZwDuplicateObject(NtCurrentProcess(), NtCurrentProcess(), NtCurrentProcess(), &hProcess, 0, 0, DUPLICATE_SAME_ACCESS);
if (0 <= status)
{
PVOID stack = alloca(guz);
DWORD cb = 0, rcb = 0x10000;
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, cb, &rcb)))
{
if (ULONG NumberOfHandles = (ULONG)pshti->NumberOfHandles)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles = pshti->Handles;
ULONG_PTR UniqueProcessId = GetCurrentProcessId();
do
{
if (Handles->UniqueProcessId == UniqueProcessId && (HANDLE)Handles->HandleValue == hProcess)
{
ObjectTypeIndex = Handles->ObjectTypeIndex;
goto __break;
}
} while (Handles++, --NumberOfHandles);
}
}
} while (STATUS_INFO_LENGTH_MISMATCH == status);
__break:
ZwClose(hProcess);
}
return status;
}
class ZOBJECT_ALL_TYPES_INFORMATION
{
OBJECT_TYPE_INFORMATION* _TypeInformation;
DWORD _NumberOfTypes, _TypeIndexDelta;
public:
operator DWORD()
{
return _NumberOfTypes;
}
operator OBJECT_TYPE_INFORMATION*()
{
return _TypeInformation;
}
DWORD operator[](OBJECT_TYPE_INFORMATION* TypeInformation)
{
return (DWORD)(TypeInformation - _TypeInformation) + _TypeIndexDelta;
}
OBJECT_TYPE_INFORMATION* operator[](DWORD Index)
{
return Index < _NumberOfTypes ? _TypeInformation + Index : 0;
}
ULONG TypeIndexToIndex(DWORD ObjectTypeIndex)
{
return ObjectTypeIndex -= _TypeIndexDelta;
}
OBJECT_TYPE_INFORMATION* operator[](PCUNICODE_STRING TypeName);
ZOBJECT_ALL_TYPES_INFORMATION();
~ZOBJECT_ALL_TYPES_INFORMATION();
};
ZOBJECT_ALL_TYPES_INFORMATION g_AOTI;
OBJECT_TYPE_INFORMATION* ZOBJECT_ALL_TYPES_INFORMATION::operator[](PCUNICODE_STRING TypeName)
{
if (DWORD NumberOfTypes = _NumberOfTypes)
{
OBJECT_TYPE_INFORMATION* TypeInformation = _TypeInformation;
do
{
if (RtlEqualUnicodeString(TypeName, &TypeInformation->TypeName, TRUE))
{
return TypeInformation;
}
} while (TypeInformation++, -- NumberOfTypes);
}
return 0;
}
ZOBJECT_ALL_TYPES_INFORMATION::ZOBJECT_ALL_TYPES_INFORMATION()
{
_TypeInformation = 0, _NumberOfTypes = 0;
USHORT ProcessTypeIndex;
if (0 > getProcessIndex(ProcessTypeIndex))
{
return ;
}
NTSTATUS status;
PVOID stack = alloca(guz);
union {
PVOID pv;
OBJECT_TYPES_INFORMATION* poati;
};
DWORD cb = 0, rcb = 0x2000;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQueryObject(0, ObjectAllTypeInformation, poati, cb, &rcb)))
{
if (DWORD NumberOfTypes = poati->NumberOfTypes)
{
if (OBJECT_TYPE_INFORMATION* TypeInformation = (OBJECT_TYPE_INFORMATION*)LocalAlloc(0, rcb))
{
_NumberOfTypes = NumberOfTypes;
_TypeInformation = TypeInformation;
ULONG Index = 0;
union {
ULONG_PTR uptr;
OBJECT_TYPE_INFORMATION* pti;
};
union {
PWSTR buf;
PBYTE pb;
PVOID pv;
};
pti = poati->TypeInformation;
pv = TypeInformation + NumberOfTypes;
do
{
STATIC_UNICODE_STRING_(Process);
if (RtlEqualUnicodeString(&Process, &pti->TypeName, TRUE))
{
_TypeIndexDelta = ProcessTypeIndex - Index;
}
ULONG Length = pti->TypeName.Length, MaximumLength = pti->TypeName.MaximumLength;
memcpy(buf, pti->TypeName.Buffer, Length);
*TypeInformation = *pti;
TypeInformation++->TypeName.Buffer = buf;
pb += Length;
uptr += (sizeof(OBJECT_TYPE_INFORMATION) + MaximumLength + sizeof(PVOID)-1) & ~ (sizeof(PVOID)-1);
} while (Index++, --NumberOfTypes);
}
}
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
}
ZOBJECT_ALL_TYPES_INFORMATION::~ZOBJECT_ALL_TYPES_INFORMATION()
{
if (_TypeInformation)
{
LocalFree(_TypeInformation);
}
}
and finally use next code, without NtQueryObject:
void TestMutant()
{
NTSTATUS status;
PVOID stack = alloca(guz);
DWORD cb = 0, rcb = 0x10000;
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, cb, &rcb)))
{
if (ULONG NumberOfHandles = (ULONG)pshti->NumberOfHandles)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles = pshti->Handles;
ULONG_PTR UniqueProcessId = GetCurrentProcessId();
do
{
if (Handles->UniqueProcessId == UniqueProcessId)
{
if (OBJECT_TYPE_INFORMATION* poti = g_AOTI[g_AOTI.TypeIndexToIndex(Handles->ObjectTypeIndex)])
{
STATIC_UNICODE_STRING_(Mutant);
if (RtlEqualUnicodeString(&Mutant, &poti->TypeName, TRUE))
{
MUTANT_BASIC_INFORMATION mbi;
QueryMutant((HANDLE)Handles->HandleValue, Handles->GrantedAccess, &mbi);
}
}
}
} while (Handles++, --NumberOfHandles);
}
}
} while (STATUS_INFO_LENGTH_MISMATCH == status);
}
can test with
void Az()
{
HANDLE hMutant;
if (0 <= ZwCreateMutant(&hMutant, SYNCHRONIZE, 0, TRUE))
{
TestMutant();
ZwClose(hMutant);
}
}

Working with transparent PNG in Windows mobile 6.x using C++

I have been trying to add to resource and use some semi transparent PNG files in my windows mobile 6.x application. After days of looking around and experimenting with different methods, I decided to dynamically load and use gdiplus.dll and use the flat APIs. everything works except function GdipCreateHBITMAPFromBitmap returns NotImplemented (6). any idea how to fix this problem? I have 3 files that I am attaching here.
GdiPlusDynamic.cpp:
#include "GdiPlusDynamic.h"
#include "GdiPlusDynamicTools.h"
#include <string>
TAutoStream::TAutoStream(HGLOBAL m_hBuffer)
{
pStream = NULL;
switch(::CreateStreamOnHGlobal(m_hBuffer, TRUE, &pStream))
{
case E_NOINTERFACE:
throw std::wstring(L"The specified interface is not supported");
break;
case E_OUTOFMEMORY:
throw std::wstring(L"Not enough memory");
break;
default:
break;
}
}
TAutoStream::~TAutoStream(void)
{
if(NULL != pStream)
pStream->Release();
}
IStream* TAutoStream::get(void)
{
return pStream;
}
AutoGlobal::AutoGlobal(UINT uFlags, SIZE_T dwBytes) : len(0)
{
m_hBuffer = ::GlobalAlloc(uFlags, dwBytes);
if(IsOk())
{
memset(m_hBuffer, 0, dwBytes);
len = dwBytes;
}
}
AutoGlobal::~AutoGlobal(void)
{
if(IsOk())
::GlobalFree(m_hBuffer);
}
bool AutoGlobal::IsOk(void)
{
return (NULL != m_hBuffer);
}
HGLOBAL AutoGlobal::get(void)
{
return m_hBuffer;
}
TAutoLockedBuff::TAutoLockedBuff(UINT uFlags, SIZE_T dwBytes) : AutoGlobal(uFlags, dwBytes)
{
pBuffer = NULL;
if(AutoGlobal::IsOk())
pBuffer = GlobalLock(m_hBuffer);
}
TAutoLockedBuff::~TAutoLockedBuff(void)
{
if(IsOk())
GlobalUnlock(m_hBuffer);
}
bool TAutoLockedBuff::IsOk(void)
{
return (AutoGlobal::IsOk() && (NULL != pBuffer));
}
void TAutoLockedBuff::CopyFrom(const void* pSrcData, SIZE_T srcLen)
{
if(IsOk())
CopyMemory(pBuffer, pSrcData, min(srcLen, len));
}
TDynamicGdiPlus::TDynamicGdiPlus(void) : everythigOK(false)
{
GdiplusStartupInput dpStartupInfo;
token = 0;
pGdiplusStartup = NULL;
pGdiplusShutdown = NULL;
pGdipCreateBitmapFromStream = NULL;
pGdipCreateHBITMAPFromBitmap = NULL;
pGdipFree = NULL;
hGdiPlus = ::LoadLibrary(L"gdiplus.dll");
if(NULL == hGdiPlus)
throw std::wstring(L"Unable to load 'gdiplus.dll'");
pGdiplusStartup = (TGdiplusStartup)GetProcAddress(hGdiPlus, L"GdiplusStartup");
if(NULL == pGdiplusStartup)
throw std::wstring(L"Unable to get the address of 'GdiplusStartup'");
pGdiplusShutdown = (TGdiplusShutdown)GetProcAddress(hGdiPlus, L"GdiplusShutdown");
if(NULL == pGdiplusShutdown)
throw std::wstring(L"Unable to get the address of 'GdiplusShutdown'");
pGdipCreateBitmapFromStream = (TGdipCreateBitmapFromStream)GetProcAddress(hGdiPlus, L"GdipCreateBitmapFromStreamICM");
if(NULL == pGdipCreateBitmapFromStream)
throw std::wstring(L"Unable to get the address of 'GdipCreateBitmapFromStreamICM'");
pGdipCreateHBITMAPFromBitmap = (TGdipCreateHBITMAPFromBitmap)GetProcAddress(hGdiPlus, L"GdipCreateHBITMAPFromBitmap");
if(NULL == pGdipCreateHBITMAPFromBitmap)
throw std::wstring(L"Unable to get the address of 'GdipCreateHBITMAPFromBitmap'");
pGdipFree = (TGdipFree)GetProcAddress(hGdiPlus, L"GdipFree");
if(NULL == pGdipFree)
throw std::wstring(L"Unable to get the address of 'GdipFree'");
if(Status::Ok != pGdiplusStartup(&token, &dpStartupInfo, NULL))
throw std::wstring(L"Unable to start 'GDI Plus'");
else
everythigOK = true;
}
TDynamicGdiPlus::~TDynamicGdiPlus(void)
{
if((0 != token) && (NULL != pGdiplusShutdown))
pGdiplusShutdown(token);
if(NULL != hGdiPlus)
FreeLibrary(hGdiPlus);
}
HBITMAP TDynamicGdiPlus::LoadImageFromResource(HINSTANCE hInst, LPCTSTR lpName, LPCTSTR lpType)
{
HBITMAP retVal = NULL;
if(everythigOK)
{
HRSRC hResource = ::FindResource(hInst, lpName, lpType);
if (NULL != hResource)
{
DWORD imageSize = ::SizeofResource(hInst, hResource);
if (0 < imageSize)
{
const void* pResourceData = ::LockResource(::LoadResource(hInst, hResource));
if (NULL != pResourceData)
{
TAutoLockedBuff m_Buffer(GMEM_MOVEABLE, imageSize + 1);
void* pBmp;
m_Buffer.CopyFrom(pResourceData, imageSize);
TAutoStream m_Stream(m_Buffer.get());
pGdipCreateBitmapFromStream(m_Stream.get(), &pBmp);
if (NULL != pBmp)
{
pGdipCreateHBITMAPFromBitmap(pBmp, &retVal, 0x00FFFFFF); // this returns NotImplemented
pGdipFree(pBmp);
if(NULL == retVal)
throw std::wstring(L"Unable to extract HBITMAP from Gdiplus::Bitmap");
}
else
throw std::wstring(L"Unable to extract Gdiplus::Bitmap from stream");
}
else
throw std::wstring(L"Unable to lock resource");
}
else
throw std::wstring(L"Size of resource is 0");
}
else
throw std::wstring(L"Unable to find resource");
}
return retVal;
}
GdiPlusDynamic.h
#pragma once
typedef enum {
Ok = 0,
GenericError = 1,
InvalidParameter = 2,
OutOfMemory = 3,
ObjectBusy = 4,
InsufficientBuffer = 5,
NotImplemented = 6,
Win32Error = 7,
WrongState = 8,
Aborted = 9,
FileNotFound = 10,
ValueOverflow = 11,
AccessDenied = 12,
UnknownImageFormat = 13,
FontFamilyNotFound = 14,
FontStyleNotFound = 15,
NotTrueTypeFont = 16,
UnsupportedGdiplusVersion = 17,
GdiplusNotInitialized = 18,
PropertyNotFound = 19,
PropertyNotSupported = 20,
ProfileNotFound = 21
} Status;
enum DebugEventLevel
{
DebugEventLevelFatal,
DebugEventLevelWarning
};
// Callback function that GDI+ can call, on debug builds, for assertions
// and warnings.
typedef VOID (WINAPI *DebugEventProc)(DebugEventLevel level, CHAR *message);
// Notification functions which the user must call appropriately if
// "SuppressBackgroundThread" (below) is set.
typedef Status (WINAPI *NotificationHookProc)(OUT ULONG_PTR *token);
typedef VOID (WINAPI *NotificationUnhookProc)(ULONG_PTR token);
struct GdiplusStartupInput
{
UINT32 GdiplusVersion; // Must be 1 (or 2 for the Ex version)
DebugEventProc DebugEventCallback; // Ignored on free builds
BOOL SuppressBackgroundThread; // FALSE unless you're prepared to call
// the hook/unhook functions properly
BOOL SuppressExternalCodecs; // FALSE unless you want GDI+ only to use
// its internal image codecs.
GdiplusStartupInput(
DebugEventProc debugEventCallback = NULL,
BOOL suppressBackgroundThread = FALSE,
BOOL suppressExternalCodecs = FALSE)
{
GdiplusVersion = 1;
DebugEventCallback = debugEventCallback;
SuppressBackgroundThread = suppressBackgroundThread;
SuppressExternalCodecs = suppressExternalCodecs;
}
};
struct GdiplusStartupOutput
{
// The following 2 fields are NULL if SuppressBackgroundThread is FALSE.
// Otherwise, they are functions which must be called appropriately to
// replace the background thread.
//
// These should be called on the application's main message loop - i.e.
// a message loop which is active for the lifetime of GDI+.
// "NotificationHook" should be called before starting the loop,
// and "NotificationUnhook" should be called after the loop ends.
NotificationHookProc NotificationHook;
NotificationUnhookProc NotificationUnhook;
};
typedef Status (WINAPI *TGdiplusStartup)(ULONG_PTR* token, const GdiplusStartupInput *input, GdiplusStartupOutput *output);
typedef void (WINAPI *TGdiplusShutdown)(ULONG_PTR token);
typedef Status (WINAPI *TGdipCreateBitmapFromStream)(IStream* stream, void **bitmap);
typedef Status (WINAPI *TGdipCreateHBITMAPFromBitmap)(void* bitmap, HBITMAP* hbmReturn, DWORD background);
typedef void (WINAPI *TGdipFree)(void* ptr);
class TDynamicGdiPlus
{
private:
bool everythigOK;
protected:
HMODULE hGdiPlus;
TGdiplusStartup pGdiplusStartup;
TGdiplusShutdown pGdiplusShutdown;
TGdipCreateBitmapFromStream pGdipCreateBitmapFromStream;
TGdipCreateHBITMAPFromBitmap pGdipCreateHBITMAPFromBitmap;
TGdipFree pGdipFree;
ULONG_PTR token;
public:
TDynamicGdiPlus(void);
virtual ~TDynamicGdiPlus(void);
HBITMAP LoadImageFromResource(HINSTANCE hInst, LPCTSTR lpName, LPCTSTR lpType);
};
and GdiPlusDynamicTools.h
#pragma once
class TAutoStream
{
protected:
IStream* pStream;
public:
TAutoStream(HGLOBAL m_hBuffer);
virtual ~TAutoStream(void);
IStream* get(void);
};
class AutoGlobal
{
protected:
HGLOBAL m_hBuffer;
SIZE_T len;
public:
AutoGlobal(UINT uFlags, SIZE_T dwBytes);
virtual ~AutoGlobal(void);
bool IsOk(void);
HGLOBAL get(void);
};
class TAutoLockedBuff : public AutoGlobal
{
protected:
void* pBuffer;
public:
TAutoLockedBuff(UINT uFlags, SIZE_T dwBytes);
virtual ~TAutoLockedBuff(void);
bool IsOk(void);
void CopyFrom(const void* pSrcData, SIZE_T srcLen);
};
You can use IImagingFactory service instead GDI+
This service can render png, bmp with alpha channel.
We use this service for real project under Windows Mobile 6.x
This code mockup:
CComPtr<IImagingFactory> spImageFactory;
spImageFactory.CoCreateInstance(__uuidof(ImagingFactory);
HRSRC hRes = FindResource(hInstance, MAKEINTRESOURCE(resID), _T("PNG"));
DWORD imageSize = SizeOfResource(hInstance, hRes);
HGLOBAL hGlobal = LoadResource(hInstance, hRes);
CComPtr<IImage> spImage;
spImageFactory->CreateImageFromBuffer(LockResource(hGlobal, imageSize), BufferDisposalFlagNone, &spImage);
ImageInfo info = {0};
spImage->GetImageInfo(&info);
CRect rect(x, y, x+info.Width, y+info.Height);
spImage->Draw(hDc, &rect, NULL);