Extract Eventlog messages in a specific language - windows-7

I need some help regarding the extraction of eventlog data under Windows 7.
What I try to achieve:
A computer has Windows 7 German (or any other language) installed. I want to extract the eventlog messages in Englisch to transport them to another computer where I want to store and analyze the eventlog.
This should be done somehow programatically (C# or C++).
I have tried different ways. Write a C# programm to extract the messages result always in getting the messages not in englisch but the configured language of the computer. I also tried it in C++ but also with the same result.
The other approach was then to extract the eventlog in a evtx-File and transport it to another computer with an englisch operating system. But the problem with that solution is that I also need non Windows eventlog messages (e.g. from the installed programs) which cannot be viewed on the other computer where the program and the message dlls are not installed.
Does anybody have an idea how to extract eventlog messages in English independent from the language of the operating system?
Thanks a lot,
Ulli

Here is the complete code for C++ to extract special eventlog messages in a specific language (Thanks to "Apokal" and MSDN). You can change the definitions for
Provider Name (this is the key in the registry)
Resource dll (this is the path to the message dll referenced in the registry)
Message language (this is the language code - Note: Seems the complete code is needed "DE" is not working "DE-de" works ...)
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>
#define PROVIDER_NAME L"SceCli"
#define RESOURCE_DLL L"C:\\Windows\\System32\\scecli.dll"
#define MESSAGE_LANGUAGE 0x0409 // En-Us
#define MAX_TIMESTAMP_LEN 23 + 1 // mm/dd/yyyy hh:mm:ss.mmm
#define MAX_RECORD_BUFFER_SIZE 0x10000 // 64K
HANDLE GetMessageResources();
DWORD DumpRecordsInBuffer(PBYTE pBuffer, DWORD dwBytesRead);
DWORD GetEventTypeName(DWORD EventType);
LPWSTR GetMessageString(DWORD Id, DWORD argc, LPWSTR args);
void GetTimestamp(const DWORD Time, WCHAR DisplayString[]);
DWORD ApplyParameterStringsToMessage(CONST LPCWSTR pMessage, LPWSTR & pFinalMessage);
CONST LPWSTR pEventTypeNames[] = {L"Error", L"Warning", L"Informational", L"Audit Success", L"Audit Failure"};
HANDLE g_hResources = NULL;
void wmain(void)
{
HANDLE hEventLog = NULL;
DWORD status = ERROR_SUCCESS;
DWORD dwBytesToRead = 0;
DWORD dwBytesRead = 0;
DWORD dwMinimumBytesToRead = 0;
PBYTE pBuffer = NULL;
PBYTE pTemp = NULL;
// The source name (provider) must exist as a subkey of Application.
hEventLog = OpenEventLog(NULL, PROVIDER_NAME);
if (NULL == hEventLog)
{
wprintf(L"OpenEventLog failed with 0x%x.\n", GetLastError());
goto cleanup;
}
// Get the DLL that contains the string resources for the provider.
g_hResources = GetMessageResources();
if (NULL == g_hResources)
{
wprintf(L"GetMessageResources failed.\n");
goto cleanup;
}
// Allocate an initial block of memory used to read event records. The number
// of records read into the buffer will vary depending on the size of each event.
// The size of each event will vary based on the size of the user-defined
// data included with each event, the number and length of insertion
// strings, and other data appended to the end of the event record.
dwBytesToRead = MAX_RECORD_BUFFER_SIZE;
pBuffer = (PBYTE)malloc(dwBytesToRead);
if (NULL == pBuffer)
{
wprintf(L"Failed to allocate the initial memory for the record buffer.\n");
goto cleanup;
}
// Read blocks of records until you reach the end of the log or an
// error occurs. The records are read from newest to oldest. If the buffer
// is not big enough to hold a complete event record, reallocate the buffer.
while (ERROR_SUCCESS == status)
{
if (!ReadEventLog(hEventLog,
EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ,
0,
pBuffer,
dwBytesToRead,
&dwBytesRead,
&dwMinimumBytesToRead))
{
status = GetLastError();
if (ERROR_INSUFFICIENT_BUFFER == status)
{
status = ERROR_SUCCESS;
pTemp = (PBYTE)realloc(pBuffer, dwMinimumBytesToRead);
if (NULL == pTemp)
{
wprintf(L"Failed to reallocate the memory for the record buffer (%d bytes).\n", dwMinimumBytesToRead);
goto cleanup;
}
pBuffer = pTemp;
dwBytesToRead = dwMinimumBytesToRead;
}
else
{
if (ERROR_HANDLE_EOF != status)
{
wprintf(L"ReadEventLog failed with %lu.\n", status);
goto cleanup;
}
}
}
else
{
// Print the contents of each record in the buffer.
DumpRecordsInBuffer(pBuffer, dwBytesRead);
}
}
getchar();
cleanup:
if (hEventLog)
CloseEventLog(hEventLog);
if (pBuffer)
free(pBuffer);
}
// Get the provider DLL that contains the string resources for the
// category strings, event message strings, and parameter insert strings.
// For this example, the path to the DLL is hardcoded but typically,
// you would read the CategoryMessageFile, EventMessageFile, and
// ParameterMessageFile registry values under the source's registry key located
// under \SYSTEM\CurrentControlSet\Services\Eventlog\Application in
// the HKLM registry hive. In this example, all resources are included in
// the same resource-only DLL.
HANDLE GetMessageResources()
{
HANDLE hResources = NULL;
hResources = LoadLibraryEx(RESOURCE_DLL, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
if (NULL == hResources)
{
wprintf(L"LoadLibrary failed with %lu.\n", GetLastError());
}
return hResources;
}
// Loop through the buffer and print the contents of each record
// in the buffer.
DWORD DumpRecordsInBuffer(PBYTE pBuffer, DWORD dwBytesRead)
{
DWORD status = ERROR_SUCCESS;
PBYTE pRecord = pBuffer;
PBYTE pEndOfRecords = pBuffer + dwBytesRead;
LPWSTR pMessage = NULL;
LPWSTR pFinalMessage = NULL;
WCHAR TimeStamp[MAX_TIMESTAMP_LEN];
while (pRecord < pEndOfRecords)
{
// If the event was written by our provider, write the contents of the event.
if (0 == wcscmp(PROVIDER_NAME, (LPWSTR)(pRecord + sizeof(EVENTLOGRECORD))))
{
GetTimestamp(((PEVENTLOGRECORD)pRecord)->TimeGenerated, TimeStamp);
wprintf(L"Time stamp: %s\n", TimeStamp);
wprintf(L"record number: %lu\n", ((PEVENTLOGRECORD)pRecord)->RecordNumber);
wprintf(L"status code: %d\n", ((PEVENTLOGRECORD)pRecord)->EventID & 0xFFFF);
wprintf(L"event type: %s\n", pEventTypeNames[GetEventTypeName(((PEVENTLOGRECORD)pRecord)->EventType)]);
pMessage = GetMessageString(((PEVENTLOGRECORD)pRecord)->EventCategory, 0, NULL);
if (pMessage)
{
wprintf(L"event category: %s", pMessage);
LocalFree(pMessage);
pMessage = NULL;
}
pMessage = GetMessageString(((PEVENTLOGRECORD)pRecord)->EventID,
((PEVENTLOGRECORD)pRecord)->NumStrings, (LPWSTR)(pRecord + ((PEVENTLOGRECORD)pRecord)->StringOffset));
if (pMessage)
{
status = ApplyParameterStringsToMessage(pMessage, pFinalMessage);
wprintf(L"event message: %s", (pFinalMessage) ? pFinalMessage : pMessage);
LocalFree(pMessage);
pMessage = NULL;
if (pFinalMessage)
{
free(pFinalMessage);
pFinalMessage = NULL;
}
}
// To write the event data, you need to know the format of the data. In
// this example, we know that the event data is a null-terminated string.
if (((PEVENTLOGRECORD)pRecord)->DataLength > 0)
{
wprintf(L"event data: %s\n", (LPWSTR)(pRecord + ((PEVENTLOGRECORD)pRecord)->DataOffset));
}
wprintf(L"\n");
}
pRecord += ((PEVENTLOGRECORD)pRecord)->Length;
}
return status;
}
// Get an index value to the pEventTypeNames array based on
// the event type value.
DWORD GetEventTypeName(DWORD EventType)
{
DWORD index = 0;
switch (EventType)
{
case EVENTLOG_ERROR_TYPE:
index = 0;
break;
case EVENTLOG_WARNING_TYPE:
index = 1;
break;
case EVENTLOG_INFORMATION_TYPE:
index = 2;
break;
case EVENTLOG_AUDIT_SUCCESS:
index = 3;
break;
case EVENTLOG_AUDIT_FAILURE:
index = 4;
break;
}
return index;
}
// Formats the specified message. If the message uses inserts, build
// the argument list to pass to FormatMessage.
LPWSTR GetMessageString(DWORD MessageId, DWORD argc, LPWSTR argv)
{
LPWSTR pMessage = NULL;
DWORD dwFormatFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER;
DWORD_PTR* pArgs = NULL;
LPWSTR pString = argv;
// The insertion strings appended to the end of the event record
// are an array of strings; however, FormatMessage requires
// an array of addresses. Create an array of DWORD_PTRs based on
// the count of strings. Assign the address of each string
// to an element in the array (maintaining the same order).
if (argc > 0)
{
pArgs = (DWORD_PTR*)malloc(sizeof(DWORD_PTR) * argc);
if (pArgs)
{
dwFormatFlags |= FORMAT_MESSAGE_ARGUMENT_ARRAY;
for (DWORD i = 0; i < argc; i++)
{
pArgs[i] = (DWORD_PTR)pString;
pString += wcslen(pString) + 1;
}
}
else
{
dwFormatFlags |= FORMAT_MESSAGE_IGNORE_INSERTS;
wprintf(L"Failed to allocate memory for the insert string array.\n");
}
}
if (!FormatMessage(dwFormatFlags,
g_hResources,
MessageId,
MESSAGE_LANGUAGE,
(LPWSTR)&pMessage,
0,
(va_list*)pArgs))
{
wprintf(L"Format message failed with %lu\n", GetLastError());
}
if (pArgs)
free(pArgs);
return pMessage;
}
// If the message string contains parameter insertion strings (for example, %%4096),
// you must perform the parameter substitution yourself. To get the parameter message
// string, call FormatMessage with the message identifier found in the parameter insertion
// string (for example, 4096 is the message identifier if the parameter insertion string
// is %%4096). You then substitute the parameter insertion string in the message
// string with the actual parameter message string.
DWORD ApplyParameterStringsToMessage(CONST LPCWSTR pMessage, LPWSTR & pFinalMessage)
{
DWORD status = ERROR_SUCCESS;
DWORD dwParameterCount = 0; // Number of insertion strings found in pMessage
size_t cbBuffer = 0; // Size of the buffer in bytes
size_t cchBuffer = 0; // Size of the buffer in characters
size_t cchParameters = 0; // Number of characters in all the parameter strings
size_t cch = 0;
DWORD i = 0;
LPWSTR* pStartingAddresses = NULL; // Array of pointers to the beginning of each parameter string in pMessage
LPWSTR* pEndingAddresses = NULL; // Array of pointers to the end of each parameter string in pMessage
DWORD* pParameterIDs = NULL; // Array of parameter identifiers found in pMessage
LPWSTR* pParameters = NULL; // Array of the actual parameter strings
LPWSTR pTempMessage = (LPWSTR)pMessage;
LPWSTR pTempFinalMessage = NULL;
// Determine the number of parameter insertion strings in pMessage.
while (pTempMessage = wcschr(pTempMessage, L'%'))
{
dwParameterCount++;
pTempMessage++;
}
// If there are no parameter insertion strings in pMessage, return.
if (0 == dwParameterCount)
{
pFinalMessage = NULL;
goto cleanup;
}
// Allocate an array of pointers that will contain the beginning address
// of each parameter insertion string.
cbBuffer = sizeof(LPWSTR) * dwParameterCount;
pStartingAddresses = (LPWSTR*)malloc(cbBuffer);
if (NULL == pStartingAddresses)
{
wprintf(L"Failed to allocate memory for pStartingAddresses.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pStartingAddresses, cbBuffer);
// Allocate an array of pointers that will contain the ending address (one
// character past the of the identifier) of the each parameter insertion string.
pEndingAddresses = (LPWSTR*)malloc(cbBuffer);
if (NULL == pEndingAddresses)
{
wprintf(L"Failed to allocate memory for pEndingAddresses.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pEndingAddresses, cbBuffer);
// Allocate an array of pointers that will contain pointers to the actual
// parameter strings.
pParameters = (LPWSTR*)malloc(cbBuffer);
if (NULL == pParameters)
{
wprintf(L"Failed to allocate memory for pEndingAddresses.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pParameters, cbBuffer);
// Allocate an array of DWORDs that will contain the message identifier
// for each parameter.
pParameterIDs = (DWORD*)malloc(cbBuffer);
if (NULL == pParameterIDs)
{
wprintf(L"Failed to allocate memory for pParameterIDs.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pParameterIDs, cbBuffer);
// Find each parameter in pMessage and get the pointer to the
// beginning of the insertion string, the end of the insertion string,
// and the message identifier of the parameter.
pTempMessage = (LPWSTR)pMessage;
while (pTempMessage = wcschr(pTempMessage, L'%'))
{
if (isdigit(*(pTempMessage+1)))
{
pStartingAddresses[i] = pTempMessage;
pTempMessage++;
pParameterIDs[i] = (DWORD)_wtoi(pTempMessage);
while (isdigit(*++pTempMessage))
;
pEndingAddresses[i] = pTempMessage;
i++;
}
}
// For each parameter, use the message identifier to get the
// actual parameter string.
for (DWORD i = 0; i < dwParameterCount; i++)
{
pParameters[i] = GetMessageString(pParameterIDs[i], 0, NULL);
if (NULL == pParameters[i])
{
wprintf(L"GetMessageString could not find parameter string for insert %lu.\n", i);
status = ERROR_INVALID_PARAMETER;
goto cleanup;
}
cchParameters += wcslen(pParameters[i]);
}
// Allocate enough memory for pFinalMessage based on the length of pMessage
// and the length of each parameter string. The pFinalMessage buffer will contain
// the completed parameter substitution.
pTempMessage = (LPWSTR)pMessage;
cbBuffer = (wcslen(pMessage) + cchParameters + 1) * sizeof(WCHAR);
pFinalMessage = (LPWSTR)malloc(cbBuffer);
if (NULL == pFinalMessage)
{
wprintf(L"Failed to allocate memory for pFinalMessage.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pFinalMessage, cbBuffer);
cchBuffer = cbBuffer / sizeof(WCHAR);
pTempFinalMessage = pFinalMessage;
// Build the final message string.
for (DWORD i = 0; i < dwParameterCount; i++)
{
// Append the segment from pMessage. In the first iteration, this is "8 " and in the
// second iteration, this is " = 2 ".
wcsncpy_s(pTempFinalMessage, cchBuffer, pTempMessage, cch = (pStartingAddresses[i] - pTempMessage));
pTempMessage = pEndingAddresses[i];
cchBuffer -= cch;
// Append the parameter string. In the first iteration, this is "quarts" and in the
// second iteration, this is "gallons"
pTempFinalMessage += cch;
wcscpy_s(pTempFinalMessage, cchBuffer, pParameters[i]);
cchBuffer -= cch = wcslen(pParameters[i]);
pTempFinalMessage += cch;
}
// Append the last segment from pMessage, which is ".".
wcscpy_s(pTempFinalMessage, cchBuffer, pTempMessage);
cleanup:
if (ERROR_SUCCESS != status)
pFinalMessage = (LPWSTR)pMessage;
if (pStartingAddresses)
free(pStartingAddresses);
if (pEndingAddresses)
free(pEndingAddresses);
if (pParameterIDs)
free(pParameterIDs);
for (DWORD i = 0; i < dwParameterCount; i++)
{
if (pParameters[i])
LocalFree(pParameters[i]);
}
return status;
}
// Get a string that contains the time stamp of when the event
// was generated.
void GetTimestamp(const DWORD Time, WCHAR DisplayString[])
{
ULONGLONG ullTimeStamp = 0;
ULONGLONG SecsTo1970 = 116444736000000000;
SYSTEMTIME st;
FILETIME ft, ftLocal;
ullTimeStamp = Int32x32To64(Time, 10000000) + SecsTo1970;
ft.dwHighDateTime = (DWORD)((ullTimeStamp >> 32) & 0xFFFFFFFF);
ft.dwLowDateTime = (DWORD)(ullTimeStamp & 0xFFFFFFFF);
FileTimeToLocalFileTime(&ft, &ftLocal);
FileTimeToSystemTime(&ftLocal, &st);
StringCchPrintf(DisplayString, MAX_TIMESTAMP_LEN, L"%d/%d/%d %.2d:%.2d:%.2d",
st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond);
}

It's impossible to do in full way.
Here is why:
Each program that writes events to EventLog has an appropriate EventSource registered under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog. And an EventMessagFile value under EventSource key provides a path to a file that contain's event messages. So if, for example, some custom program provides only german event messages in that file, where do you get an english event messages from? The answer is from nowhere, because developers simply could not shipped event messages for other languages.
And for Windows, if you've got a german windows, but no english language pack (Microsoft's MUI) where does Windows have to get translations from? Nowhere.

Related

Unable to get all hard disks information with kernel mode driver on windows 11

I am fairly new to kernel programming and I have a little problem getting all disk drives information like name,serialnumber from kernel mode. I use below code to get all disks symbolic links which works perfectly fine.
static VOID DeviceInterfaceTest_Func() {
NTSTATUS Status;
PWSTR SymbolicLinkList;
PWSTR SymbolicLinkListPtr;
GUID Guid = {
0x53F5630D,
0xB6BF,
0x11D0,
{
0x94,
0xF2,
0x00,
0xA0,
0xC9,
0x1E,
0xFB,
0x8B
}
}; //Defined in mountmgr.h
Status = IoGetDeviceInterfaces( &
Guid,
NULL,
0, &
SymbolicLinkList);
if (!NT_SUCCESS(Status)) {
return;
}
KdPrint(("IoGetDeviceInterfaces results:\n"));
for (SymbolicLinkListPtr = SymbolicLinkList; SymbolicLinkListPtr[0] != 0 && SymbolicLinkListPtr[1] != 0; SymbolicLinkListPtr += wcslen(SymbolicLinkListPtr) + 1) {
KdPrint(("Symbolic Link: %S\n", SymbolicLinkListPtr));
PUNICODE_STRING PTarget {};
UNICODE_STRING Input;
NTSTATUS s = 0;
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.MaximumLength = 200 * sizeof(WCHAR);
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0);
s = SymbolicLinkTarget( & Input, PTarget);
if (s == STATUS_SUCCESS) {
//KdPrint(("%S\n", PTarget->Buffer));
KdPrint(("Finished!\n"));
}
}
ExFreePool(SymbolicLinkList);
}
However when i try to use InitializeObjectAttributes function to extract data of symbolic link inside for loop I checking their names with KdPrint and all them are null as a result i can't use ZwOpenSymbolicLinkObject, because when i use it i get BSOD. What am I doing wrong? Is my method valid to get disk information or I should use another method? Below is the code of SymbolicLinkTarget
NTSTATUS SymbolicLinkTarget(_In_ PUNICODE_STRING SymbolicLinkStr, _Out_ PUNICODE_STRING PTarget) {
OBJECT_ATTRIBUTES ObjectAtiribute {};
NTSTATUS Status = 0;
HANDLE Handle = nullptr;
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
KdPrint(("Object length:%u \n", ObjectAtiribute.Length));
KdPrint(("Object name:%s \n", ObjectAtiribute.ObjectName - > Buffer));
Status = ZwOpenSymbolicLinkObject(&Handle, GENERIC_READ, &ObjectAtiribute);
if (Status != STATUS_SUCCESS)
{
KdPrint(("ZwOpenSymbolicLinkObject failed (0x%08X)\n", Status));
return Status;
}
UNREFERENCED_PARAMETER(PTarget);
ULONG Tag1 = 'Tag1';
PTarget->MaximumLength = 200 * sizeof(WCHAR);
PTarget->Length = 0;
PTarget->Buffer = (PWCH)ExAllocatePool2(PagedPool, PTarget->MaximumLength, Tag1);
if (!PTarget->Buffer)
{
ZwClose(Handle);
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = ZwQuerySymbolicLinkObject(Handle, PTarget, NULL);
ZwClose(Handle);
if (Status != STATUS_SUCCESS)
{
KdPrint(("ZwQuerySymbolicLinkObject failed (0x%08X)\n", Status));
ExFreePool(PTarget->Buffer);
return Status;
}
return STATUS_SUCCESS;
}
Thank you very much for helping.
There are multiple problems in your functions. Let start with he main one:
In SymbolicLinkTarget():
OBJECT_ATTRIBUTES ObjectAtiribute {};
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
You are going to initialize ObjectAtiribute from SymbolicLinkStr (and the other parameters) but in DeviceInterfaceTest_Func() you actually never set Input to contain a string!
UNICODE_STRING Input;
NTSTATUS s = 0;
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.MaximumLength = 200 * sizeof(WCHAR);
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0);
s = SymbolicLinkTarget( & Input, PTarget);
Input.Length
This is wrong:
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.Length will be set to the size of a pointer. According to the UNICODE_STRING (ntdef.h; subauth.h) the length is:
Specifies the length, in bytes, of the string pointed to by the Buffer member, not including the terminating NULL character, if any.
So:
size_t str_len_no_null = wcslen(SymbolicLinkListPtr); // number of chars, not bytes!
Input.Length = str_len_no_null * sizeof(WCHAR);
Notice the wcslen() is already in the init-statement of the for loop, I would train to extract it to have it in the loop body.
Input.MaximumLength
Input.MaximumLength = 200 * sizeof(WCHAR);
What if the string is more lager than 200 characters?
MaximumLength is defined as such:
Specifies the total size, in bytes, of memory allocated for Buffer. Up to MaximumLength bytes may be written into the buffer without trampling memory.
Thus it's safe to just do:
size_t max_length_bytes = Input.Length + (1 * sizeof(WCHAR)); // add room for possible null.
Input.MaximumLength = max_length_bytes;
The allocation for the Buffer member can be kept in place. Now you need to copy the string into the buffer.
UNICODE_STRING init
size_t str_len_no_null = wcslen(SymbolicLinkListPtr); // number of chars, not bytes!
Input.Length = str_len_no_null * sizeof(WCHAR);
size_t max_length_bytes = Input.Length + (1 * sizeof(WCHAR)); // add room for possible null.
Input.MaximumLength = max_length_bytes;
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0); // note: you should define a Tag for your Driver.
if(Input.buffer == NULL) {
// not enough memory.
return;
}
status = RtlStringCbCopyW(Input.Buffer, max_length_bytes, SymbolicLinkListPtr);
// TODO: check status
Now that you know how to do it manually, throw your code and use RtlUnicodeStringInit
Other things & hints
Always checks the return status / value of the functions you use. In kernel mode, this is super important.
NTSTATUS check is always done using one of the status macros (usually NT_SUCCESS)
Use string safe functions.
nitpicking: A success return value of IoGetDeviceInterfaces may also indicate an empty buffer. Although you check that in the for loop init-statement, I would have checked that right after the function so the intent is clearer.
KdPrint(("Object name:%s \n", ObjectAtiribute.ObjectName - > Buffer));
It's %S (wide char) not %s (char); see format specification. you can pass a UNICODE_STRING and use the %Z formatter. Also be wary of - > which is strange (you probably meant ->).
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
Use OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE if the resulting handle is not meant to cross the kernel <-> user-mode boundary (in your case, it doesn't have to cross that boundary). Otherwise you leak a kernel handle to user-mode, which has security implications.
This is also required when you call ZwOpenSymbolicLinkObject and you are not running in a system thread:
If the caller is not running in a system thread context, it must set the OBJ_KERNEL_HANDLE attribute when it calls InitializeObjectAttributes.
You can define GUIDs with DEFINE_GUID; see Defining and Exporting New GUIDs and Including GUIDs in Driver Code. In your case you don't need to export it.
This is probably nitpicking, but use nullptr (c++) or NULL (c) instead of 0 to convey the idea that you are checking for a pointer and not just the integral value of 0.

Why might DeviceCapabilities() return 4294967295 for DC_BINS?

I'm fetching the selected printer tray from a WIN32 call to PrintDlgEx(). This seems to work successfully most of the time, but recently I added a new printer to my machine (a DYMO LabelWriter 450) and it caused my simple software to fail.
Upon investigation, the call to DeviceCapabilities() for DC_BINS is returning 4294967295, while all of the other printers I've tested so far return single digit bin counts.
My first inclination is to omit the bin name when the bin count is greater than a given threshold (say... 20?), but I don't love this solution.
Is there a known reason that a printer would return the max UNSIGNED INT value for this? Is it just poorly written drivers, or is there an alternate meaning? Or perhaps I totally misunderstand the intended value.
If I have to write an arbitrary cap I will, but I'd like to better understand why this situation exists. Clearly, this printer doesn't have billions of different printer trays.
Here's an MRE:
HINSTANCE hinst = GetModuleHandle(NULL);
HRESULT hResult;
PRINTDLGEX pdx = {0};
LPPRINTPAGERANGE pPageRanges = NULL;
HWND hWndOwner = GetForegroundWindow();
if(!hWndOwner){
hWndOwner = GetDesktopWindow();
}
// Allocate an array of PRINTPAGERANGE structures.
pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if(!pPageRanges){
return wprintf(L"{\"error\": \"%s\"}", GetLastError()); // "Your computer does not have enough memory to complete this operation:"
}
// Initialize the PRINTDLGEX structure.
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWndOwner;
pdx.hDevMode = NULL;
pdx.hDevNames = NULL;
pdx.hDC = NULL;
pdx.Flags = PD_RETURNDC | PD_COLLATE;
pdx.Flags2 = 0;
pdx.ExclusionFlags = 0;
pdx.nPageRanges = 0;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.hInstance = 0;
pdx.lpPrintTemplateName = NULL;
pdx.lpCallback = NULL;
pdx.nPropertyPages = 0;
pdx.lphPropertyPages = NULL;
pdx.nStartPage = START_PAGE_GENERAL;
pdx.dwResultAction = 0;
// Invoke the Print property sheet.
hResult = PrintDlgEx(&pdx);
DEVMODE * myDevMode = (DEVMODE *)GlobalLock(pdx.hDevMode);
DWORD binCount = DeviceCapabilities((CHAR*)myDevMode->dmDeviceName, nullptr, DC_BINS, nullptr, nullptr);
DWORD binNameCount = DeviceCapabilities((CHAR*)myDevMode->dmDeviceName, nullptr, DC_BINNAMES, nullptr, nullptr);
wprintf(L"\"binCount\":\"%lu\",", binCount);
wprintf(L"\"binNameCount\":\"%lu\",", binNameCount);
DeviceCapabilities() returns a signed int, not an unsigned DWORD.
The unsigned value 4294967295 is hex 0xFFFFFFFF, which is the same numeric value as a signed -1.
Per the DeviceCapabilities() documentation:
Return value
If the function succeeds, the return value depends on the setting of the fwCapability parameter. A return value of zero generally indicates that, while the function completed successfully, there was some type of failure, such as a capability that is not supported. For more details, see the descriptions for the fwCapability values.
If the function returns -1, this may mean either that the capability is not supported or there was a general function failure.
You are not accounting for the possibility of DeviceCapabilities() failing (or PrintDlgEx(), either).
Try this:
HWND hWndOwner = GetForegroundWindow();
if (!hWndOwner){
hWndOwner = GetDesktopWindow();
}
// Allocate an array of PRINTPAGERANGE structures.
LPPRINTPAGERANGE pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if (!pPageRanges){
// NOTE: GetLastError() returns DWORD, not TCHAR*! So, if you
// want to translate the error code in a human-readable string,
// use FormatMessage() instead...
return wprintf(L"{\"error\": %lu}", GetLastError());
}
// Initialize the PRINTDLGEX structure.
PRINTDLGEX pdx = {0};
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWndOwner;
pdx.Flags = PD_RETURNDC | PD_COLLATE;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.nStartPage = START_PAGE_GENERAL;
HRESULT hResult = PrintDlgEx(&pdx);
if (hResult != S_OK)
{
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return wprintf(L"{\"error\": %d}", hResult);
}
if (pdx.dwResultAction == PD_RESULT_CANCEL)
{
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return wprintf(L"{\"error\": \"cancelled\"}");
}
DEVMODE *myDevMode = (DEVMODE*) GlobalLock(pdx.hDevMode);
int binCount = DeviceCapabilities(reinterpret_cast<TCHAR*>(myDevMode->dmDeviceName), nullptr, DC_BINS, nullptr, nullptr);
wprintf(L"\"binCount\":%d,", binCount);
int binNameCount = DeviceCapabilities(reinterpret_cast<TCHAR*>(myDevMode->dmDeviceName),
nullptr, DC_BINNAMES, nullptr, nullptr);
wprintf(L"\"binNameCount\":%d,", binNameCount);
if (binCount == -1)
{
...
}
if (binNameCount == -1)
{
...
}
...
GlobalUnlock(pdx.hDevMode);
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return ...;

GetRawInputDeviceInfo indicates a buffer size of 1 character for RIDI_DEVICENAME

I'm getting ridiculous behavior from RIDI_DEVICENAME. According to the documentation,
Return value
Type: UINT
If successful, this function returns a non-negative number indicating the number of bytes copied to pData.
If pData is not large enough for the data, the function returns -1. If pData is NULL, the function returns a value of zero. In both of these cases, pcbSize is set to the minimum size required for the pData buffer.
Call GetLastError to identify any other errors.
Ignoring the obvious problem that -1 is not a representable value in the UINT return type, it seems that the function should tell me the required size of the buffer, and if I supply a buffer of this size, the function should either succeed or at least follow its own rules for failure.
However, I'm not seeing this at all. On Windows 10, the Unicode version of the function sets pcbSize to 1 when pData is null and leaves it alone otherwise, failing in all cases. The ANSI version of the function sets pcbSize to 2 when pData is null, and otherwise doubles whatever value was passed in, and still fails.
Headers used for either version of test code:
#define WIN32_EXTRA_LEAN 1
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include <windows.h>
ANSI test code:
std::string GetRawInputDeviceName( HANDLE hRaw )
{
UINT numChars = 0u;
INT validChars;
validChars = static_cast<INT>(::GetRawInputDeviceInfoA(hRaw, RIDI_DEVICENAME, nullptr, &numChars));
auto lasterror = ::GetLastError();
if (lasterror != ERROR_INSUFFICIENT_BUFFER) {
std::wcerr << L"Failed to get length of name of raw input device, retcode = " << validChars << L", last error = " << lasterror << L"\n";
return {};
}
std::string name;
name.resize(numChars);
validChars = static_cast<INT>(::GetRawInputDeviceInfoA(hRaw, RIDI_DEVICENAME, &name[0], &numChars));
lasterror = ::GetLastError();
if (validChars > 0) {
name.resize(validChars);
return name;
}
else {
std::wcerr << L"Failed to get name of raw input device, retcode = " << validChars << L", last error = " << lasterror << L"\n";
return {};
}
}
Unicode test code:
std::wstring GetRawInputDeviceName( HANDLE hRaw )
{
UINT numChars = 0u;
INT validChars;
validChars = static_cast<INT>(::GetRawInputDeviceInfoW(hRaw, RIDI_DEVICENAME, nullptr, &numChars));
auto lasterror = ::GetLastError();
if (lasterror != ERROR_INSUFFICIENT_BUFFER) {
std::wcerr << L"Failed to get length of name of raw input device, retcode = " << validChars << L", last error = " << lasterror << L"\n";
return {};
}
std::wstring name;
name.resize(numChars);
validChars = static_cast<INT>(::GetRawInputDeviceInfoW(hRaw, RIDI_DEVICENAME, &name[0], &numChars));
lasterror = ::GetLastError();
if (validChars > 0) {
name.resize(validChars);
return name;
}
else {
std::wcerr << L"Failed to get name of raw input device, retcode = " << validChars << L", last error = " << lasterror << L"\n";
return {};
}
}
On Windows 10 through RDP I'm getting ERROR_INSUFFICIENT_BUFFER consistently.
On Windows 8.1 running as a local user, I get ERROR_INSUFFICIENT_BUFFER if pData is null, and when I provide a buffer I get back failure ((UINT)-1) and GetLastError() returns zero.
I've also just tried proposing a likely-large-enough buffer size, and got failures as well.
What is going on, what is the right way to get the interface path name, and do I need administrative rights or to call some other APIs first? I don't seem to be having any problems calling GetRawInputDeviceList or using RIDI_DEVICEINFO mode of GetRawInputDeviceInfo... but I need the interface path in order to go further.
Windows HID Device Name Format
https://stackoverflow.com/a/64320052/103167
the GetRawInputDeviceName have several errors in declaration / implementation / documentation
by fact more correct declare return value as signed ( LONG or INT) but not UINT
exist 3 case:
1. function return negative value (or if want -1) : this is error
case, and by design - last error must be set. but really it not
always set (implementation error).
most common errors:
pcbSize or pData point to invalid or read only memory location. usual error in this case ERROR_NOACCESS (translated from
STATUS_ACCESS_VIOLATION)
hDevice not valid handle - ERROR_INVALID_HANDLE is returned
uiCommand not valid RIDI_XXX constant - ERROR_INVALID_PARAMETER
*pcbSize is not large enough for the data - in this case *pcbSize is set to the minimum size required for the pData buffer. ERROR_INSUFFICIENT_BUFFER
again - only in this case (-1) exist sense call GetLastError();
2. function return 0 this possible only in case when pData is NULL.
*pcbSize is set to the minimum size required for the pData buffer.
3. function return positive value ( > 0) this mean that this count of
bytes (in case RIDI_PREPARSEDDATA or RIDI_DEVICEINFO ) or
characters (in case RIDI_DEVICENAME) written to buffer
so documentation is wrong here:
pcbSize
[in, out]
Pointer to a variable that contains the size, in bytes, of the data in
pData.
in case RIDI_DEVICENAME in characters
so already visible very serious problems with design (type of return value - unsigned) and mixed bytes/characters. many different cases.
but then exist critical error in implementation. in begin of function handle hDevice converted to pointer.
PDEVICEINFO pDeviceInfo = HMValidateHandle(hDevice, TYPE_DEVICEINFO);
(if 0 returned - we got -1 on exit with ERROR_INVALID_HANDLE).
in DEVICEINFO exist UNICODE_STRING ustrName - this name and copied to user mode
switch (uiCommand) {
case RIDI_DEVICENAME:
/*
* N.b. UNICODE_STRING counts the length by the BYTE count, not by the character count.
* Our APIs always treat the strings by the character count. Thus, for RIDI_DEVICNAME
* only, cbOutSize holds the character count, not the byte count, in spite of its
* name. Confusing, but cch is the way to be consistent.
*/
cbOutSize = pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1; // for Null terminator
break;
//...
}
required cbOutSize compared with cbBufferSize = *pcbSize;
and if (cbBufferSize >= cbOutSize) api begin copy operation
exist next code
case RIDI_DEVICENAME:
if (cbOutSize <= 2) { // !!!! error !!!!
retval = -1;
goto leave;
}
RtlCopyMemory(pData, pDeviceInfo->ustrName.Buffer, pDeviceInfo->ustrName.Length);
((WCHAR*)pData)[1] = '\\'; // convert nt prefix ( \??\ ) to win32 ( \\?\ )
((WCHAR*)pData)[cbOutSize - 1] = 0; // make it null terminated
break;
cbOutSize here - is (len + 1) of device name (which we not control). so if name is zero length - always -1 is returned (error #1) but last error not set ( error #2 )
of course exist and error #3 - why is device name is 0 length ? this must not be. but in case terminal service devices - (virtual mouse/ keyboard device created on UMB bus ) - exist this result.
full code for api ( in kernel)
UINT NtUserGetRawInputDeviceInfo(
HANDLE hDevice,
UINT uiCommand,
LPVOID pData,
PUINT pcbSize)
{
UINT cbOutSize = 0;
UINT cbBufferSize;
int retval = 0;
EnterCrit(0, UserMode);
UserAtomicCheck uac;
try {
ProbeForRead(pcbSize, sizeof(UINT), sizeof(DWORD));
cbBufferSize = *pcbSize;
} except (EXCEPTION_EXECUTE_HANDLER) {
UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS
retval = -1;
goto leave1;
}
EnterDeviceInfoListCrit_();
PDEVICEINFO pDeviceInfo = HMValidateHandle(hDevice, TYPE_DEVICEINFO);
if (pDeviceInfo == NULL) {
UserSetLastError(ERROR_INVALID_HANDLE);
retval = -1;
goto leave;
}
/*
* Compute the size of the output and evaluate the uiCommand.
*/
switch (uiCommand) {
case RIDI_PREPARSEDDATA:
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
cbOutSize = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.DescriptorSize;
} else {
cbOutSize = 0;
}
break;
case RIDI_DEVICENAME:
/*
* N.b. UNICODE_STRING counts the length by the BYTE count, not by the character count.
* Our APIs always treat the strings by the character count. Thus, for RIDI_DEVICNAME
* only, cbOutSize holds the character count, not the byte count, in spite of its
* name. Confusing, but cch is the way to be consistent.
*/
cbOutSize = pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1; // for Null terminator
break;
case RIDI_DEVICEINFO:
cbOutSize = sizeof(RID_DEVICE_INFO);
break;
default:
UserSetLastError(ERROR_INVALID_PARAMETER);
retval = -1;
goto leave;
}
if (pData == NULL) {
/*
* The app wants to get the required size.
*/
try {
ProbeForWrite(pcbSize, sizeof(UINT), sizeof(DWORD));
*pcbSize = cbOutSize;
} except (EXCEPTION_EXECUTE_HANDLER) {
UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS
retval = -1;
goto leave;
}
retval = 0;
} else {
if (cbBufferSize >= cbOutSize) {
try {
ProbeForWrite(pData, cbBufferSize, sizeof(DWORD));
switch (uiCommand) {
case RIDI_PREPARSEDDATA:
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
RtlCopyMemory(pData, pDeviceInfo->hid.pHidDesc->pPreparsedData, cbOutSize);
}
break;
case RIDI_DEVICENAME:
if (cbOutSize <= 2) { // !!!!
retval = -1;
goto leave;
}
RtlCopyMemory(pData, pDeviceInfo->ustrName.Buffer, pDeviceInfo->ustrName.Length);
((WCHAR*)pData)[1] = '\\'; // make it null terminated
((WCHAR*)pData)[cbOutSize - 1] = 0; // make it null terminated
break;
case RIDI_DEVICEINFO:
{
PRID_DEVICE_INFO prdi = (PRID_DEVICE_INFO)pData;
ProbeForRead(prdi, sizeof(UINT), sizeof(DWORD));
if (prdi->cbSize != cbOutSize) {
MSGERRORCLEANUP(ERROR_INVALID_PARAMETER);
}
ProbeForWrite(prdi, sizeof(RID_DEVICE_INFO), sizeof(DWORD));
RtlZeroMemory(prdi, sizeof(RID_DEVICE_INFO));
prdi->cbSize = cbOutSize;
switch (pDeviceInfo->type) {
case DEVICE_TYPE_HID:
prdi->dwType = RIM_TYPEHID;
prdi->hid.dwVendorId = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.VendorID;
prdi->hid.dwProductId = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.ProductID;
prdi->hid.dwVersionNumber = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.VersionNumber;
prdi->hid.usUsagePage = pDeviceInfo->hid.pHidDesc->hidpCaps.UsagePage;
prdi->hid.usUsage = pDeviceInfo->hid.pHidDesc->hidpCaps.Usage;
break;
case DEVICE_TYPE_MOUSE:
prdi->dwType = RIM_TYPEMOUSE;
prdi->mouse.dwId = pDeviceInfo->mouse.Attr.MouseIdentifier;
prdi->mouse.dwNumberOfButtons = pDeviceInfo->mouse.Attr.NumberOfButtons;
prdi->mouse.dwSampleRate = pDeviceInfo->mouse.Attr.SampleRate;
break;
case DEVICE_TYPE_KEYBOARD:
prdi->dwType = RIM_TYPEKEYBOARD;
prdi->keyboard.dwType = GET_KEYBOARD_DEVINFO_TYPE(pDeviceInfo);
prdi->keyboard.dwSubType = GET_KEYBOARD_DEVINFO_SUBTYPE(pDeviceInfo);
prdi->keyboard.dwKeyboardMode = pDeviceInfo->keyboard.Attr.KeyboardMode;
prdi->keyboard.dwNumberOfFunctionKeys = pDeviceInfo->keyboard.Attr.NumberOfFunctionKeys;
prdi->keyboard.dwNumberOfIndicators = pDeviceInfo->keyboard.Attr.NumberOfIndicators;
prdi->keyboard.dwNumberOfKeysTotal = pDeviceInfo->keyboard.Attr.NumberOfKeysTotal;
break;
}
}
break;
default:
__assume(false);
}
} except (EXCEPTION_EXECUTE_HANDLER) {
UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS
retval = -1;
goto leave;
}
retval = cbOutSize;
} else {
/*
* The buffer size is too small.
* Returns error, storing the required size in *pcbSize.
*/
retval = -1;
try {
ProbeForWrite(pcbSize, sizeof(UINT), sizeof(DWORD));
*pcbSize = cbOutSize;
UserSetLastError(ERROR_INSUFFICIENT_BUFFER);
} except (EXCEPTION_EXECUTE_HANDLER) {
UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS
retval = -1;
goto leave;
}
}
}
leave:
LeaveDeviceInfoListCrit_();
leave1:
UserSessionSwitchLeaveCrit();
return retval;
}
then GetRawInputDeviceInfoA add additional errors compare GetRawInputDeviceInfoW - the value from *pcbSize by some reason multiple on 2. but again - this error in all case.
note that DeviceName (formatted from strings returned from driver on IRP_MN_QUERY_ID have very strict restrictions:
If a driver returns an ID with an illegal character, the system will
bug check. Characters with the following values are illegal in an ID
for this IRP:
Less than or equal to 0x20 (' ')
Greater than 0x7F
Equal to 0x2C (',')
so even after covert unicode to ansi - length of device name will be the same ( all symbols < 0x80 ). so not need *2 buffer size for Ansi version.
then i already view error in your code - you call ::GetLastError(); unconditionally after GetRawInputDeviceInfoW - but returned value have sense only in case api return -1
explain for observed behavior:
for local devices api in general work correct (if no mistakes in our code)
for terminal service devices - was 0 length ustrName. as result if we pass NULL in pData - return value will be
pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1;
because pDeviceInfo->ustrName.Length == 0 - 1 will be returned inside *pcbSize
in case A version - -by mistake - 2*1==2 will be returned.
but when e pass not NULL in pData - we trap in this
if (cbOutSize <= 2) { // !!!! error !!!!
retval = -1;
goto leave;
}
so you can pass any by size buffer, anyway, because (cbOutSize <= 2) - -1 will be returned and last error not set
possible solution - at first - never use ansi version - GetRawInputDeviceInfoA
use this wrapper function.
ULONG GetRawInputDeviceInfoExW(_In_opt_ HANDLE hDevice,
_In_ UINT uiCommand,
_Inout_updates_bytes_to_opt_(*pcbSize, *pcbSize) LPVOID pData,
_Inout_ PUINT pcbSize)
{
switch (int i = GetRawInputDeviceInfoW(hDevice, uiCommand, pData, pcbSize))
{
case 0:
return ERROR_INSUFFICIENT_BUFFER;
case 1:
return ERROR_INVALID_NAME;
default:
if (0 > i)
{
return GetLastError();
}
*pcbSize = i;
return NOERROR;
}
}
example of usage: (/RTCs must be disabled )
void Demo()
{
PRAWINPUTDEVICELIST pRawInputDeviceList = 0;
UINT uiNumDevices = 0;
UINT cch, cchAllocated = 0;
union {
PVOID buf;
PWSTR name;
};
buf = 0;
while (0 <= (int)GetRawInputDeviceList(pRawInputDeviceList, &uiNumDevices, sizeof(RAWINPUTDEVICELIST)))
{
if (pRawInputDeviceList)
{
do
{
HANDLE hDevice = pRawInputDeviceList->hDevice;
ULONG dwError;
while (ERROR_INSUFFICIENT_BUFFER == (dwError =
GetRawInputDeviceInfoExW(hDevice, RIDI_DEVICENAME, name, &(cch = cchAllocated))))
{
if (cch > cchAllocated)
{
cchAllocated = RtlPointerToOffset(buf = alloca((cch - cchAllocated) * sizeof(WCHAR)),
pRawInputDeviceList) / sizeof(WCHAR);
}
else
{
__debugbreak();
}
}
if (dwError == NOERROR)
{
DbgPrint("[%p, %x %S]\n", hDevice, pRawInputDeviceList->dwType, name);
}
else
{
DbgPrint("error = %u\n", dwError);
}
} while (pRawInputDeviceList++, --uiNumDevices);
break;
}
pRawInputDeviceList = (PRAWINPUTDEVICELIST)alloca(uiNumDevices * sizeof(RAWINPUTDEVICELIST));
}
}
This code is working fine on my PC. Not sure, but it indeed could be RDP issue.
UINT result = ::GetRawInputDeviceInfoW(m_Handle, RIDI_DEVICENAME, nullptr, &size);
if (result == static_cast<UINT>(-1))
{
//PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(0u, result);
std::wstring buffer(size, 0);
result = ::GetRawInputDeviceInfoW(m_Handle, RIDI_DEVICENAME, buffer.data(), &size);
if (result == static_cast<UINT>(-1))
{
//PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(size, result);

How to get string from pointer

GetWindowThreadProcessId(hwndFoundWindow, &dwTrayProcessID);
HANDLE hTrayProc = OpenProcess(PROCESS_ALL_ACCESS, 0, dwTrayProcessID);
int iButtonsCount = SendMessage(hwndFoundWindow, TB_BUTTONCOUNT, 0, 0);
LPVOID lpData = VirtualAllocEx(hTrayProc, NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);
int iButton;
DWORD dwBytesRead;
TBBUTTON buttonData;
dwBytesRead = -1;
int chk_data = (int)SendMessage(hwndFoundWindow, TB_GETBUTTON, iButton, (LPARAM)lpData);
ReadProcessMemory(hTrayProc, lpData, &buttonData, sizeof(TBBUTTON), &dwBytesRead);
int len_text = (int)SendMessage(hwndFoundWindow, TB_GETBUTTONTEXTW, buttonData.idCommand, (LPARAM)lpData);
till now, i know the length of button's text but i also need to get the text to display on console.
my problem is i do not really know how to get that text from the button. please kindly help.
what i am trying is ... trying to access to lpData to get the string inside, but could not do that.
My first comment is that you need to add error checking to your code. As far as I can see, you perform no checking of return values. Any of the API functions you call could fail. If you don't check return values for errors then you have no way of diagnosing where you went wrong.
For instance, starting with GetWindowThreadProcessId, you need to write it like this:
if (GetWindowThreadProcessId(hwndFoundWindow, &dwTrayProcessID) == 0)
{
// handle error
}
And so on for all the other functions. Consult MSDN carefully to understand how each function signals failure.
Now to the main part of the question. I believe that it is the TB_GETBUTTONTEXTW message that is giving you trouble. You need to write it like this:
LRESULT len = SendMessage(hwndFoundWindow, TB_GETBUTTONTEXTW,
buttonData.idCommand, NULL);
if (len == -1)
{
// handle error
}
size_t size = sizeof(wchar_t)*(len+1);
LPVOID lpData = VirtualAllocEx(hTrayProc, NULL, size, MEM_COMMIT, PAGE_READWRITE);
if (lpData == NULL)
{
// handle error
}
len = SendMessage(hwndFoundWindow, TB_GETBUTTONTEXTW,
buttonData.idCommand, (LPARAM)lpData);
if (len == -1)
{
// handle error
}
wchar_t* str = new wchar_t[len+1];
if (!ReadProcessMemory(hTrayProc, lpData, (LPVOID)str, size, NULL))
{
// handle error
}
// the text is now in str, as a null-terminated UTF-16 string
delete[] str;
You need this: (see documentation of TB_GETBUTTONTEXTW).
WCHAR *buffer ;
int len_text = (int)SendMessage(hwndFoundWindow, TB_GETBUTTONTEXTW,
buttonData.idCommand, (LPARAM)NULL);
buffer = (WCHAR*)malloc(sizeof(WCHAR) * (len_text + 1)) ;
SendMessage(hwndFoundWindow, TB_GETBUTTONTEXTW,
buttonData.idCommand, (LPARAM)buffer);
....
free(buffer) ;

RegQueryValueEx return ERROR_MORE_DATA when the buffer is already large enough

Sorry for my pure english.
I have two processes which can Read and Write data to the same value(my tests do it).
Sometimes(one per ten times) the read method is fail with error ERROR_MORE_DATA and Value is 12.
But I call Read method from the tests with 32 bytes.
By chance I looked to #err,hr in watch (GetLastError()) and saw ERROR_NOT_OWNER error code.I understand that second process is block the key and I must try again.
Can anybody approve my conclusions (MSDN is not say anything about this)?
Can anybody will tell me other strange effects?
Thank you.
Update:
I have UAC Virtualization. All changes are stored to the
[HKEY_CLASSES_ROOT\VirtualStore\MACHINE\SOFTWARE]
May be it is effect virtualization???
{
...
char name[32] = "";
grandchild.OpenValue("name").Read(name, _countof(name));
...
}
bool RegisteryStorageValue::Read(void* Buffer, size_t Size) throw (IOException)
{
DWORD Value = DWORD(Size);
DWORD rez = ::RegQueryValueEx(mKey, mName.c_str(), NULL, NULL, (BYTE*)Buffer, &Value);
if (rez != ERROR_SUCCESS) // here I have 'rez = ERROR_MORE_DATA' and 'Value = 12'
throw IOException(rez);
return true;
}
bool RegisteryStorageValue::Write(Type type, const void* Buffer, size_t Size) throw (IOException)
{
DWORD rez = ::RegSetValueEx(mKey, mName.c_str(), NULL, getRegType(type), (const BYTE*)Buffer, (DWORD)Size);
if (rez != ERROR_SUCCESS)
throw IOException(rez);
return true;
}
Registry functions do not use GetLastError() to report errors. They return error codes directly. So the ERROR_NOT_OWNER is misleading, it is from an earlier Win32 API call, not the Registry calls.
There is no possible way you can pass in a Size value of 32 to RegQueryValueEx() and get back an ERROR_MORE_DATA error saying the data is actually 12 in size. RegQueryValueEx() does not work that way. Make sure your Size value is actually set to 32 upon entry to the Read() function and is not set to some other value.
Update: it is, however, possible for RegQueryValueEx() to report ERROR_MORE_DATA and return a data size that is twice as larger as what you requested, even if RegSetValueEx() is not actually passed that much data. When I ran your test code, I was able to get RegQueryValueEx() to sometimes (not every time) report a data size of 64 even though 32 was being requested. The reason is because RegSetValueExA(), which your code is actually calling, performs a data conversion from Ansi to Unicode for string types (REG_SZ, REG_MULTI_SZ and REG_EXPAND_SZ), and RegQueryValueExA(), which your code is actually calling, queries the raw bytes and performs a Unicode to Ansi conversion for string types. So while your writing code may be saving 32 char values, thus 32 bytes, the Registry is actually storing 32 wchar_t values instead, thus 64 bytes (it would be more if your input strings had non-ASCII characters in them). Chances are, RegQueryValueEx() is returning the raw bytes as-is instead of converting them, such as if RegSetValueEx() is saving the raw bytes first and then saving the data type afterwards, but RegQueryValueEx() is reading the raw bytes before the data type has been saved and thus does not know the data is a string type that needs converting.
Either way, this is a race condition between one thread/process reading while another thread/process is writing, issues with reading while the writing is caching data internally before flushing it, etc. Nothing you can do about this unless you synchronize the reads and writes, since the Registry API does not synchronize for you.
I write sample for my question. I have repeate it issue on the third start.
If sample is complite you can see "Query complite" and "SetComplite" messages
On err you should saw: "error more data: ??"
#include <string>
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
bool start(char* path, char* args)
{
std::string cmd = path;
cmd.push_back(' ');
cmd.append(args);
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
BOOL res = ::CreateProcess(NULL, (LPSTR)cmd.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (res == FALSE)
return false;
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hEvent = ::CreateEvent(NULL, TRUE, FALSE, "Local/blah");
if (argc == 1)
{
HKEY hKey;
if (::RegCreateKey(HKEY_CURRENT_USER, "Software\\TestRegistry", &hKey) != ERROR_SUCCESS)
return -1;
char buffer[] = "Hello, Stack!";
::RegSetValueEx(hKey, "Value", 0, REG_SZ, (BYTE*)buffer, _countof(buffer));
::RegCloseKey(hKey);
if (start(argv[0], "r") == false ||
start(argv[0], "w") == false)
return -2;
::Sleep(1000);
::SetEvent(hEvent);
}
else
{
if (argv[1][0] == 'r')
{
HKEY hKey;
if (::RegOpenKey(HKEY_CURRENT_USER, "Software\\TestRegistry", &hKey) != ERROR_SUCCESS)
return -1;
char buffer[1024] = {0};
if (::WaitForSingleObject(hEvent, 10000) == WAIT_TIMEOUT)
return -3;
for (size_t index = 0; index < 1000000; ++index)
{
DWORD dwType;
DWORD dwSize = _countof(buffer);
DWORD result = ::RegQueryValueEx(hKey, "Value", 0, &dwType, (LPBYTE)buffer, &dwSize);
if (result == ERROR_SUCCESS)
continue;
if (result == ERROR_MORE_DATA)
{
::printf_s("\nError more data: %d\n", dwSize);
return 1;
}
}
::RegCloseKey(hKey);
::printf_s("\nQuery completed\n");
}
else
{
::srand(::GetTickCount());
HKEY hKey;
if (::RegOpenKey(HKEY_CURRENT_USER, "Software\\TestRegistry", &hKey) != ERROR_SUCCESS)
return -1;
const size_t word_size = 32;
char dict[][word_size] =
{
"aaaaaaaa",
"help me",
"rape me",
"in the pines",
"argh",
};
char buffer[1024] = {0};
if (::WaitForSingleObject(hEvent, 10000) == WAIT_TIMEOUT)
return -3;
for (size_t index = 0; index < 1000000; ++index)
{
DWORD dwType = REG_SZ;
DWORD dwSize = word_size;
DWORD result = ::RegSetValueEx(hKey, "Value", 0, dwType, (LPBYTE)dict[rand() % _countof(dict)], dwSize);
if (result == ERROR_SUCCESS)
continue;
}
::RegCloseKey(hKey);
::printf_s("\nSet completed\n");
}
}
return 0;
}

Resources