Get the immediate target path from symlink/reparse point - windows

I am aware that GetFinalPathNameByHandle can be used to obtain the target of a symbolic link or a reparse point, but there are situations where its use is not desirable:
If the target is not available, doesn't exist or cannot be opened, CreateFile on a symlink fails, and thus the path cannot be obtained.
If I point a symlink "a" to file "b" and create a symlink "b" to file "c", the function follows the whole chain, returning "c".
The function is not useful much when I already have the handle to the actual symlink at hand.
It seems that DeviceIoControl can be used together with FSCTL_GET_REPARSE_POINT to obtain the actual reparse data of the file, but that gets me the REPARSE_DATA_BUFFER, and I would have to parse that.
I don't know how the system actually processes reparse points, but I think that the target location is a piece of information that should be available at some point. The dir command, for example, can display the target path correctly for any reparse point... well I have already seen it handle just symlinks and mount points (junctions).

how the system actually processes reparse points
this is done inside file system and file system filter drivers. result depend from are FILE_FLAG_OPEN_REPARSE_POINT option used in call CreateFile (or FILE_OPEN_REPARSE_POINT in NT calls).
when FILE_FLAG_OPEN_REPARSE_POINT is specified - file system bypass normal reparse point processing for the file and attempts to directly open the reparse point file as is.
If the FILE_OPEN_REPARSE_POINT flag is not specified - file-system attempts to open a file to which reparse point is point (if fs understand format of reparse point - primary only Microsoft reparse points)
the data format saved in reparse point is REPARSE_DATA_BUFFER (Microsoft reparse point format) or REPARSE_GUID_DATA_BUFFER - need look for ReparseTag at begin.
to determine whether a reparse point tag corresponds to a tag owned by Microsoft we use IsReparseTagMicrosoft macro.
code for test/print reparse point data:
volatile UCHAR guz;
ULONG TestReparsePoint(PCWSTR FileName)
{
HANDLE hFile = CreateFile(FileName, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
union {
PVOID pv;
PULONG ReparseTag;
PREPARSE_DATA_BUFFER prdb;
PREPARSE_GUID_DATA_BUFFER prgdb;
};
PVOID stack = alloca(guz);
ULONG cb = 0, rcb = sizeof(REPARSE_DATA_BUFFER) + 0x100, BytesReturned;
ULONG dwError;
do
{
if (cb < rcb) cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
if (DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, 0, 0, pv, cb, &BytesReturned, 0))
{
dwError = NOERROR;
if (IsReparseTagMicrosoft(*ReparseTag))
{
char cc[16];
LPCSTR name;
switch (*ReparseTag)
{
case IO_REPARSE_TAG_SYMLINK:
name = " SYMLINK";
stack = prdb->SymbolicLinkReparseBuffer.PathBuffer;
break;
case IO_REPARSE_TAG_MOUNT_POINT:
name = " MOUNT_POINT";
stack = prdb->MountPointReparseBuffer.PathBuffer;
break;
default:
sprintf(cc, " %08x", prdb->ReparseTag);
name = cc;
}
DbgPrint(" %s->%.*S <%.*S>\n", name,
prdb->MountPointReparseBuffer.SubstituteNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.SubstituteNameOffset),
prdb->MountPointReparseBuffer.PrintNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.PrintNameOffset)
);
}
else
{
PGUID g = &prgdb->ReparseGuid;
DbgPrint(" tag=%x {%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} size=%x\n", *ReparseTag,
g->Data1, g->Data2, g->Data3,
g->Data4[0],g->Data4[1],g->Data4[2],g->Data4[3],g->Data4[4],g->Data4[5],g->Data4[6],g->Data4[7],
prgdb->ReparseDataLength);
}
break;
}
rcb = IsReparseTagMicrosoft(*ReparseTag)
? REPARSE_DATA_BUFFER_HEADER_SIZE + prdb->ReparseDataLength
: REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + prgdb->ReparseDataLength;
} while((dwError = GetLastError()) == ERROR_MORE_DATA);
CloseHandle(hFile);
return dwError;
}

Microsoft reparse points can be read with REPARSE_DATA_BUFFER instead. The MS open protocol specification might also be useful.
Parsing other GUID based tags can only be done if you know the format.

Related

GetModuleFileNameEx on 32bit process from 64bit process on windows 10

I'm trying to enumerate 32bit process modules names from 64bit application using the following code:
if (EnumProcessModulesEx(hProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL))
{
for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
{
TCHAR szModName[MAX_PATH] = { 0 };
if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
sizeof(szModName) / sizeof(TCHAR)))
{
printf("module name is: %S", szModName);
}
}
}
The code works as expected in Windows 7, as part of the results are:
...
C:\Windows\**SysWOW64**\ntdll.dll
...
In Windows 10 the above code returns the full path but with System32 instead of SysWOW64. e.g,
...
C:\Windows\**System32**\ntdll.dll
...
Looking deeper for the cause, I notice that GetModuleFileNameEx reads the remote process PEB and LDR_TABLE_ENTRY, and starting from Windows 10 the LDR_TABLE_ENTRY contains the full path with System32 and not SysWOW64 - also for 32bit applications.
I also tried to use GetMappedFileName but it isn't straight forward and efficient to translate the path from dos path (\device\harddiskvolume) to standard (c:\) path.
I wonder if there are any other easy way to extract the full syswow64 path.
for get valid win32 file path from file nt-path - simplest way - add L"\\\\?\\globalroot" (\\?\globalroot) prefix. this is because CreateFileW looked from \??\ directory and globalroot is symbolic link in \??\ which let as to jump to root of nt namespace.
for example - \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll is nt absolute path. and \\?\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll is valid win32 path for CreateFileW - this api convert well known prefix \\?\ to nt prefix \??\ and pass name \??\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll to kernel. when parsing this name - after process symbolic link globalroot which point to root of namespace - we again got \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll - correct nt path.
so if we need valid win32 path for use in CreateFileW - simply append this prefix to nt path. however some shell32 api not accept this form path. also it not nice looked in UI. if we want got DOS drive letter form path (this is subset of valid win32 paths) - we can use IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH which convert device name to drive letter. this ioctl take as input MOUNTDEV_NAME (declared in mountmgr.h) and output buffer is MOUNTMGR_VOLUME_PATHS. in MOUNTDEV_NAME buffer must be exactly device name, without file path. so we need break returned nt path to 2 components. for example in \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll :
\Device\HarddiskVolume9 - device path
\Windows\SysWOW64\ntdll.dll - file system path
correct way here first open file and call GetFileInformationByHandleEx with FileNameInfo - we got file system path in output. with this we can use wcsstr for separate device path. also if we open file handle - we can use it in call GetFinalPathNameByHandleW with VOLUME_NAME_DOS. this api do exactly which we will be do - query file path, separate device path and call IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH. + open/close mount manager.
but usual nt file path begin from \Device\HarddiskVolumeX. this allow first try fast way - avoid open file and query it path.
so first we need open mount manager:
#include <mountmgr.h>
HANDLE hMountManager = CreateFile(MOUNTMGR_DOS_DEVICE_NAME,
0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
then we can run next code:
void dumpModules(HANDLE hMountManager, HANDLE hProcess)
{
ULONG cb = 0, cbNeeded = 16;
volatile static UCHAR guz;
PVOID stack = alloca(guz);
HMODULE *hMods, hmod;
__continue:
// cumulative allocate memory in stack, not need free it
cb = RtlPointerToOffset(hMods = (HMODULE*)alloca(cbNeeded - cb), stack);
if (EnumProcessModulesEx(hProcess, hMods, cb, &cbNeeded, LIST_MODULES_32BIT))
{
if (cb < cbNeeded)
{
goto __continue;
}
if (cbNeeded /= sizeof(HMODULE))
{
//i use hard coded size buffers, for reduce code and show main idea
#define FILE_NAME_INFO_buffer_size FIELD_OFFSET(FILE_NAME_INFO, FileName[MAX_PATH])
#define MOUNTDEV_NAME_buffer_size FIELD_OFFSET(MOUNTDEV_NAME, Name[MAX_PATH])
#define MOUNTMGR_VOLUME_PATHS_buffer_size FIELD_OFFSET(MOUNTMGR_VOLUME_PATHS, MultiSz[64])
// + space for 0 at the end
PFILE_NAME_INFO pfni = (PFILE_NAME_INFO)alloca(FILE_NAME_INFO_buffer_size + sizeof(WCHAR));
PMOUNTMGR_VOLUME_PATHS pmvp = (PMOUNTMGR_VOLUME_PATHS)alloca(MOUNTMGR_VOLUME_PATHS_buffer_size);
PMOUNTDEV_NAME pmdn = (PMOUNTDEV_NAME)alloca(MOUNTDEV_NAME_buffer_size);
static WCHAR globalroot[] = L"\\\\.\\globalroot";
alloca(sizeof(globalroot));
PWSTR win32Path = pmdn->Name - RTL_NUMBER_OF(globalroot) + 1;
memcpy(win32Path, globalroot, sizeof(globalroot));
USHORT NameLength = pmdn->NameLength;
do
{
hmod = *hMods++;
if (GetMappedFileNameW(hProcess, hmod, pmdn->Name, MAX_PATH))
{
DbgPrint("%p %S\n",hmod, pmdn->Name);
PWSTR c = 0;
static const WCHAR HarddiskVolume[] = L"\\Device\\HarddiskVolume";
// fast way
if (!memcmp(pmdn->Name, HarddiskVolume, sizeof(HarddiskVolume) - sizeof(WCHAR)))
{
c = wcschr(pmdn->Name + RTL_NUMBER_OF(HarddiskVolume) - 1, '\\');
}
// else - for demo
{
pmdn->NameLength = NameLength;
HANDLE hFile = CreateFile(win32Path, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
//++ just for demo
WCHAR DosPath[MAX_PATH];
if (GetFinalPathNameByHandleW(hFile, DosPath, RTL_NUMBER_OF(DosPath), VOLUME_NAME_DOS))
{
DbgPrint("%S\n", DosPath);
}
RtlGetLastNtStatus();
//-- just for demo
BOOL fOk = GetFileInformationByHandleEx(hFile, FileNameInfo, pfni, FILE_NAME_INFO_buffer_size);
CloseHandle(hFile);
if (fOk)
{
// FileName not 0 terminated
pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] = 0;
c = wcsstr(pmdn->Name, pfni->FileName);
}
}
}
if (c)
{
pmdn->NameLength = (USHORT)RtlPointerToOffset(pmdn->Name, c);
if (DeviceIoControl(hMountManager, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
pmdn, MOUNTDEV_NAME_buffer_size,
pmvp, MOUNTMGR_VOLUME_PATHS_buffer_size, &cb, NULL))
{
DbgPrint("%S%S\n", pmvp->MultiSz, c);
}
}
}
} while (--cbNeeded);
}
}
}
and demo output for notepad:
0000000000170000 \Device\HarddiskVolume9\Windows\SysWOW64\notepad.exe
\\?\C:\Windows\SysWOW64\notepad.exe
C:\Windows\SysWOW64\notepad.exe
0000000077A90000 \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll
\\?\C:\Windows\SysWOW64\ntdll.dll
0000000075460000 \Device\HarddiskVolume9\Windows\SysWOW64\kernel32.dll
\\?\C:\Windows\SysWOW64\kernel32.dll
C:\Windows\SysWOW64\kernel32.dll
0000000074A30000 \Device\HarddiskVolume9\Windows\SysWOW64\KernelBase.dll
\\?\C:\Windows\SysWOW64\KernelBase.dll
C:\Windows\SysWOW64\KernelBase.dll
00000000749B0000 \Device\HarddiskVolume9\Windows\SysWOW64\advapi32.dll
\\?\C:\Windows\SysWOW64\advapi32.dll

How can I invalidate the file system cache?

I want to measure/optimize the "cold boot" startup performance of an application, and it's difficult to do this without an actual reboot, which is obviously not an ideal solution.
Is there a way I could invalidate entire system's file cache, so that mapped page accesses actually cause a disk access, so that I can measure the time my program takes to start up?
Information:
I pretty much need FSCTL_DISMOUNT_VOLUME's functionality, but for the system volume.
At least on Windows 7, it seems that attempting to open a volume handle without FILE_SHARE_WRITE sharing permissions causes the file system cache to be invalidated, even if the creation fails.
Thus I made a program that simply calls CreateFile to this end.
Download the program* from its Base64 version here:
<!-- Click "Run Snippet", then Right-Click -> Save As... -->
FlushFileSystemCache.exe
Source
// Usage: ClearCache C: D:
#include <tchar.h>
#include <stdio.h>
#include <windows.h>
int _tmain(int argc, LPTSTR argv[]) {
LPCTSTR DOS_PREFIX = _T("\\\\.\\");
for (int i = 1; i < argc; i++) {
LPTSTR arg = argv[i];
LPTSTR path = (LPTSTR)calloc(
_tcslen(arg) + _tcslen(DOS_PREFIX) + 1, sizeof(*arg));
__try {
if (_istalpha(arg[0]) && arg[1] == _T(':') &&
(arg[2] == _T('\0') ||
arg[2] == _T('\\') && arg[3] == _T('\0')))
{ _tcscat(path, DOS_PREFIX); }
_tcscat(path, arg);
HANDLE hFile = CreateFile(path,
FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); }
else {
DWORD le = GetLastError();
if (le != ERROR_SHARING_VIOLATION && le != ERROR_ACCESS_DENIED)
{
_ftprintf(stderr, _T("Error %d clearing %s\n"), le, argv[i]);
return le;
}
}
} __finally { free(path); }
}
return 0;
}
* Just for fun, see if you can figure out what the executable does by disassembling it. It's not your typical executable. :)
I've written a simple command-line utility to do that: FlushFileCache
It relies on the undocumented NtSetSystemInformation functions, and can flush the various other memory pools as well.
This solution worked great: Clear file cache to repeat performance testing
More specifically, I'm doing this:
// Open with FILE_FLAG_NO_BUFFERING
auto hFile = CreateFile(path.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING,
nullptr);
/// Check
if (hFile == INVALID_HANDLE_VALUE){
//_tprintf(TEXT("Terminal failure: unable to open file \"%s\" for read.\n"), argv[1]);
cout << "error" << endl;
return;
}
// Close
CloseHandle(hFile);
// Now open file with regular C++ API, and caching disabled
ifstream file(path, ios::binary | ios::ate);
What David said. Create a large file, however many GB you need, and each time you want to reset your file cache, make a copy of the file. Then make sure you delete the old file.
So, create BIGFILE1.DAT, copy it to BIGFILE2.DAT, and then delete BIGFILE1.DAT (which removes it from the disk and the cache). Next time, just reverse the process.
Addenda:
Well, the other option is to take the files that are mapped, and copy them to new files, delete the old ones, and rename the new files back to the old ones. The cache is backed by a file. If the file "goes away" so does the cache.
If you can identify these files, and they're not shared by the system/other running programs, this should be simple to script and, ideally, run faster than copy 6 GB of files around.
You can use a VM and take a snapshot right after the VM boots. Resuming from a snapshot will be faster than a reboot.

How to get the length of signing segment of a signed windows executable

My program is combined with some additional data at the end of the original exe. The program would extract the additional data to disk when running the program.
However my program can't get the right offset of the appended data after signing the combined executable program.
I compared the signed exe and the original exe, the signing information is appended at the end of the exe. So I'm looking for a Win32 API to get the length of signing segment from the signed program. After that, my program could find the right offset of combined data, then extract them correctly.
Could anyone give me a hint?
I find a tool named PEDump(written by Matt Pietrek for his book) with source code to demonstrate how to get the size of signing information.
Below is the code extracted from PEDump for my purpose,
// MakePtr is a macro that allows you to easily add to values (including
// pointers) together without dealing with C's pointer arithmetic. It
// essentially treats the last two parameters as DWORDs. The first
// parameter is used to typecast the result to the appropriate pointer type.
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr) + (DWORD)(addValue))
// Names of the data directory elements that are defined
const char *ImageDirectoryNames[] = {
"EXPORT", "IMPORT", "RESOURCE", "EXCEPTION", "SECURITY", "BASERELOC",
"DEBUG", "COPYRIGHT", "GLOBALPTR", "TLS", "LOAD_CONFIG",
"BOUND_IMPORT", "IAT", // These two entries added for NT 3.51
"DELAY_IMPORT" }; // This entry added in NT 5
#define NUMBER_IMAGE_DIRECTORY_ENTRYS \
(sizeof(ImageDirectoryNames)/sizeof(char *))
HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(getProgramFile()));
HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if ( hFileMapping == 0 )
{
printf("%s", "Couldn't open file mapping with CreateFileMapping()\n");
} else {
LPVOID lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
if ( lpFileBase == 0 )
{
printf("%s", "Couldn't map view of file with MapViewOfFile()\n");
} else {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)lpFileBase;
PIMAGE_FILE_HEADER pImgFileHdr = (PIMAGE_FILE_HEADER)lpFileBase;
// it's EXE file
if ( dosHeader->e_magic == IMAGE_DOS_SIGNATURE )
{
PIMAGE_NT_HEADERS pNTHeader;
DWORD base = (DWORD)dosHeader;
pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader, dosHeader->e_lfanew );
PIMAGE_OPTIONAL_HEADER optionalHeader = (PIMAGE_OPTIONAL_HEADER)&pNTHeader->OptionalHeader;
for ( int i=0; i < optionalHeader->NumberOfRvaAndSizes; i++)
{
// DataDirectory[4] represents security directory
if ( 4 == i ) {
signingLength = optionalHeader->DataDirectory[i].Size;
break;
}
}
}
UnmapViewOfFile(lpFileBase);
}
CloseHandle(hFileMapping);
}
Put a long signature line before and after your data, and then just search for those lines at the expected offsets.

Drive Letter to Device Instance ID

How do I get from a drive letter to a device instance ID?
My process starts with a device arrival message. I have been successful in getting the drive letter from the arrival message and in opening the dvd tray.
I have searched the various Setup API items; but I haven't found anything that gets me from a drive letter to a device instance ID.
A solution in C# or VB.NET would be ideal, but I'm willing to figure it out from any other language as long as I can see the API calls.
Thanks in advance...
You cannot do it directly.
The link is to use STORAGE_DEVICE_NUMBER. You can use DeviceIoControl with IOCTL_STORAGE_GET_DEVICE_NUMBER on your device name to populate this structure. Put this value to one side.
You then need to get device infomation on your system using SetupDiGetClassDevs setting the GUIDS as approriate, indicicating the drives your are insterested in. Then enumerate through the devices using SetupDiEnumDeviceInfo. Then enumerate the interfaces using SetupDiEnumDeviceInterfaces and finally get the information using SetupDiGetDeviceInterfaceDetail. In this structure returned you can get a DevicePath you can use to get the STORAGE_DEVICE_NUMBER as above. Match this with the STORAGE_DEVICE_NUMBER from your drive letter, and you have now linked a driver letter to your structure. Phew! Inside this structure is a DevInst.
i know it's late for you now but not for everybody ^^
I had the same need and this is main line of how I did it:
-You need a window to receive device arrival and removal (as you said)
-Then you create a DeviceNotificationFilter initiated to dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE
-Then in the message loop of your window you look for VM_DEVICECHANGE
-When u receive it if wParam == DBT_DEVICEARRIVAL, use the lParam to check if it is a DBT_DEVTYPE_VOLUME (i was getting the letter and the type of the drive here) or a DBT_DEVTYPE_DEVICEINTERFACE ( there you can use your wellcasted lParam to get the InstanceId from the input structure).
When you connect a drive your receive DEVINTERFACE first then the other.
I only give the main line beacause i did this long time ago and i don't have the code here, and also I had found a lot of code pieces on the net (long time ago so there should be more now ^^^) maybe msdn give a full code example to do that now.
If you read this and need more informations, i'll reply or make a full documented answer if many need it.
Hope it will help some of you.
I know it's years later but I had to do this and searching brought me here and #DanDan 's answer worked. In order to save future people a lot of work, I thought I'd give back a little and present the technique a bit more explicitly. You'll still have to write a bit of code, but the part I found difficult is below as code:
As DanDan mentioned, the idea is to use CreateFile and DeviceIoControl to get the Windows STORAGE_DEVICE_NUMBER for the disk associated with a file path, and then use the Setup API to enumerate disk devices until we find one whose device instance equals the SDN.
First, here's a summary of how you get the STORAGE_DEVICE_NUMBER from the path (e.g. c:\\users\\bob);
Strip the path to the root (e.g down to C:) and prepend it with \\\\.\\ so you have \\\\.\\C:
Open that path up using CreateFileW with to get metadata
Use DeviceIoControl with IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS to get the extents
Get the DiskNumber member from the first extent returned.
Close the file
Open up \\\\.\\PhysicalDrive<n> where <n> is the that DiskNumber from the first extent
Use DeviceIoControl with code IOCTL_STORAGE_GET_DEVICE_NUMBER to get make it fill out a STORAGE_DEVICE_NUMBER struct as output
Use SetupDiGetClassDevs with arguments &GUID_DEVCLASS_DISKDRIVE and DICGF_PRESENT to get all disks on the system
In a loop, use SetupDiEnumDeviceInfo to get a SP_DEVINFO_DATA repeatedly (on the device list returned by step #8 above) and a call the function below to determine which one, if any, matches the STORAGE_DEVICE_NUMBER for the give path.
(This is edited to remove custom utility classes of mine right on the SO web page so I might have introduced errors/typos)
bool DoesDeviceInstanceEqualStorageDeviceNumber(
const std::string& devInstance,
STORAGE_DEVICE_NUMBER sdn)
{
// Open up this device instance, specifying that we want the *interfaces*.
// The interfaces are key key because examining them will let us get a
// string we can use the Win32 CreateFile function.
const auto hDevInfo = SetupDiGetClassDevsA(
nullptr,
devInstance.c_str(),
nullptr,
DIGCF_DEVICEINTERFACE | DIGCF_ALLCLASSES);
if (hDevInfo == INVALID_HANDLE_VALUE)
throws std::runtime_error("Unable to get disk devices");
DWORD dwSize = 0;
SP_DEVINFO_DATA did;
WCHAR buffer[4096];
did.cbSize = sizeof (did);
bool foundValidMatch = false;
int deviceNumber = 0;
// Iterate through all such devices, looking for one that has a storage device number that matches the given one.
while ( !foundValidMatch && SetupDiEnumDeviceInfo(hDevInfo, deviceNumber, &did))
{
deviceNumber++;
DEVPROPTYPE devPropType;
// We'll only bother comparing this one if it is fixed. Determine that.
const auto getPropResult = SetupDiGetDevicePropertyW (
hDevInfo,
&did,
&DEVPKEY_Device_RemovalPolicy, // Ask for the "removal policy"
&devPropType,
(BYTE*)buffer,
sizeof(buffer),
&dwSize,
0);
if (!getPropResult)
{
std::cerr << "Unable to to get removal policy for disk device: " << ::GetLastError() << std::endl;
continue;
}
/* This bit *would* skip removable disks, you wanted...
else if (buffer[0] != 1)
{
std::cerr << "Skipping removable disk device " << devInstance << std::endl;
continue;
}
*/
// OK this is a fixed disk so it might be the one we'll compare against
// 1. Get the very first disk interface from this particular disk device
// 2. Open a file on it
// 3. Query the resulting file for its device number.
// 4. Compare the device number to the one we determined above
// 5. If it matches ours, then we succeed. If not, continue
SP_DEVICE_INTERFACE_DATA devIntData;
devIntData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
// Get the disk interfaces
const auto result = SetupDiEnumDeviceInterfaces(
hDevInfo,
&did, //&did,
&GUID_DEVINTERFACE_DISK, // Get Disk Device Interface (from winioctl.h)
0, // We only need the very FIRST one. I think...
&devIntData);
if (!result)
continue;
DWORD dwRequiredSize = 0;
// Want to get the detail but don't yet know how much space we'll need
// Do a dummy call to find out
SetupDiGetDeviceInterfaceDetail(
hDevInfo,
&devIntData,
nullptr,
0,
&dwRequiredSize,
nullptr);
if (ERROR_INSUFFICIENT_BUFFER != ::GetLastError())
{
std::cerr << "Unable to get device interface Detail: " << ::GetLastError() << std::endl;;
}
else
{
// Get the detail data so we can get the device path and open a file.
std::vector<TCHAR> buf(dwRequiredSize);
auto pDidd = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(buf.data());
// WARNING: HARD CODED HACK
// ------------------------
// https://stackoverflow.com/questions/10405193/vb-net-hid-setupdigetdeviceinterfacedetail-getlasterror-shows-1784-error-inv
//
// Don't ask. Just do what they tell you.
// -----------------------------------------------------------------
#ifdef BUILD_64
pDidd->cbSize = 8;
#else
pDidd->cbSize = 6;
#endif
// -----------------------------------------------------------------
if (!SetupDiGetDeviceInterfaceDetail(
hDevInfo,
&devIntData,
pDidd,
dwRequiredSize,
&dwRequiredSize,
nullptr))
{
std::cerr << "Cannot get interface detail: " << ::GetLastError());
}
else
{
// FINALLY: We now have a DevicePath that we can use to open up
// in a Win32 CreateFile() call. That will let us get the
// STORAGE_DEVICE_NUMBER and compare it to the one we were given.
const auto hFile = ::CreateFileW(pDidd->DevicePath, 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
std::cerr << "Unable to open logical volume: " + devicePath << std::endl;
continue;
}
STORAGE_DEVICE_NUMBER sdnTest;
ZeroMemory(&sdnTest, sizeof(STORAGE_DEVICE_NUMBER));
if (0 == DeviceIoControl(
hDevInfo
IOCTL_STORAGE_GET_DEVICE_NUMBER,
nullptr, // output only so not needed
0, // output only so not needed
&sdnTest,
sizeof(STORAGE_DEVICE_NUMBER),
nullptr,
nullptr))
{
std::cerr << "Unable to determine storage device number: " << ::GetLastError() << std::endl;);
}
else
{
// All this for a one-line test...
foundValidMatch = sdnTest.DeviceNumber == sdn.DeviceNumber;
}
}
}
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return foundValidMatch;
}
I hope this saves someone a headache

Win32 API to tell whether a given binary (EXE or DLL) is x86, x64, or ia64

I am trying to find a programmatic way to tell if a binary is x86, x64, or ia64.
Platform: Windows.
Language: c/c++.
Background: Before trying to load a third-party dll, I need to find out its bitness.
Appreciate any pointers.
For EXEs
use GetBinaryType(...)
Here is same question for manged exe.
For DLLs (and EXEs)
Use the ImageNtHeader(...) to get the PE data of the file and then check the IMAGE_FILE_HEADER.Machine field.
Here is some code I found using Google Code Search
No Cleanup and NO error checking
// map the file to our address space
// first, create a file mapping object
hMap = CreateFileMapping(
hFile,
NULL, // security attrs
PAGE_READONLY, // protection flags
0, // max size - high DWORD
0, // max size - low DWORD
NULL ); // mapping name - not used
// next, map the file to our address space
void* mapAddr = MapViewOfFileEx(
hMap, // mapping object
FILE_MAP_READ, // desired access
0, // loc to map - hi DWORD
0, // loc to map - lo DWORD
0, // #bytes to map - 0=all
NULL ); // suggested map addr
peHdr = ImageNtHeader( mapAddr );
I open-sourced a project on Github that checks for VC++ redistributable DLLs specifically and there's a code snippet I created based off of the function in Shay's answer that successfully finds, loads, and inspects DLLs for x86 / x64 compatibility.
Full code snippet below:
/******************************************************************
Function Name: CheckProductUsingCurrentDirectory
Description: Queries the current working directory for a given binary.
Inputs: pszProductFolderToCheck - the product name to look up.
pBinaryArchitecture - the desired processor architecture
of the binary (x86, x64, etc..).
Results: true if the requested product is installed
false otherwise
******************************************************************/
bool CheckProductUsingCurrentDirectory(const LPCTSTR pszProductBinaryToCheck, Architecture pBinaryArchitecture){
bool bFoundRequestedProduct = false;
//Get the length of the buffer first
TCHAR currentDirectory[MAX_PATH];
DWORD currentDirectoryChars = GetCurrentDirectory(MAX_PATH, currentDirectory);
//exit if couldn't get current directory
if (currentDirectoryChars <= 0) return bFoundRequestedProduct;
TCHAR searchPath[MAX_PATH];
//exit if we couldn't combine the path to the requested binary
if (PathCombine(searchPath, currentDirectory, pszProductBinaryToCheck) == NULL) return bFoundRequestedProduct;
WIN32_FIND_DATA FindFileData;
HANDLE hFind= FindFirstFile(searchPath, &FindFileData);
//exit if the binary was not found
if (hFind == INVALID_HANDLE_VALUE) return bFoundRequestedProduct;
HANDLE hFile = CreateFile(searchPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (hFile == INVALID_HANDLE_VALUE) goto cleanup;
HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, pszProductBinaryToCheck);
if (hMapping == INVALID_HANDLE_VALUE) goto cleanup;
LPVOID addrHeader = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
if (addrHeader == NULL) goto cleanup; //couldn't memory map the file
PIMAGE_NT_HEADERS peHdr = ImageNtHeader(addrHeader);
if (peHdr == NULL) goto cleanup; //couldn't read the header
//Found the binary, AND its architecture matches. Success!
if (peHdr->FileHeader.Machine == pBinaryArchitecture){
bFoundRequestedProduct = true;
}
cleanup: //release all of our handles
FindClose(hFind);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
if (hMapping != INVALID_HANDLE_VALUE)
CloseHandle(hMapping);
return bFoundRequestedProduct;
}
This question and Shay's answer were helpful to me while I was creating this, so I thought I'd post the project here.
You can check the PE header yourself to read the IMAGE_FILE_MACHINE field. Here's a C# implementation that shouldn't be too hard to adapt to C++.

Resources