Related
I'm trying to read the NTFS Change Journal but I've noticed that all the example code I can find fails on Windows 10, even though it works on Windows 7.
For example, Microsofts own example Walking a Buffer of Change Journal Records works on Windows 7 but when I run the same code on Windows 10 I get an error 87 (The parameter is incorrect) when I call DeviceIoControl with FSCTL_READ_USN_JOURNAL (Note that the earlier call to DeviceIoControl with FSCTL_QUERY_USN_JOURNAL completes successfully and return valid data.).
I've even taken the EXE compiled and working on Windows 7 and copied it to the Windows 10 machine and it still fails, so I believe that Windows 10 may be more strict on parameter validation or something like that?
I am running the code as Administrator, so that's not the issue.
I can't find any other references to this problem, but I get the same issue if I take other peoples example code and attempt to run it on Windows 10.
Edit:
The code itself:
#include <Windows.h>
#include <WinIoCtl.h>
#include <stdio.h>
#define BUF_LEN 4096
void main()
{
HANDLE hVol;
CHAR Buffer[BUF_LEN];
USN_JOURNAL_DATA JournalData;
READ_USN_JOURNAL_DATA ReadData = {0, 0xFFFFFFFF, FALSE, 0, 0};
PUSN_RECORD UsnRecord;
DWORD dwBytes;
DWORD dwRetBytes;
int I;
hVol = CreateFile( TEXT("\\\\.\\c:"),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if( hVol == INVALID_HANDLE_VALUE )
{
printf("CreateFile failed (%d)\n", GetLastError());
return;
}
if( !DeviceIoControl( hVol,
FSCTL_QUERY_USN_JOURNAL,
NULL,
0,
&JournalData,
sizeof(JournalData),
&dwBytes,
NULL) )
{
printf( "Query journal failed (%d)\n", GetLastError());
return;
}
ReadData.UsnJournalID = JournalData.UsnJournalID;
printf( "Journal ID: %I64x\n", JournalData.UsnJournalID );
printf( "FirstUsn: %I64x\n\n", JournalData.FirstUsn );
for(I=0; I<=10; I++)
{
memset( Buffer, 0, BUF_LEN );
if( !DeviceIoControl( hVol,
FSCTL_READ_USN_JOURNAL,
&ReadData,
sizeof(ReadData),
&Buffer,
BUF_LEN,
&dwBytes,
NULL) )
{
printf( "Read journal failed (%d)\n", GetLastError());
return;
}
dwRetBytes = dwBytes - sizeof(USN);
// Find the first record
UsnRecord = (PUSN_RECORD)(((PUCHAR)Buffer) + sizeof(USN));
printf( "****************************************\n");
// This loop could go on for a long time, given the current buffer size.
while( dwRetBytes > 0 )
{
printf( "USN: %I64x\n", UsnRecord->Usn );
printf("File name: %.*S\n",
UsnRecord->FileNameLength/2,
UsnRecord->FileName );
printf( "Reason: %x\n", UsnRecord->Reason );
printf( "\n" );
dwRetBytes -= UsnRecord->RecordLength;
// Find the next record
UsnRecord = (PUSN_RECORD)(((PCHAR)UsnRecord) +
UsnRecord->RecordLength);
}
// Update starting USN for next call
ReadData.StartUsn = *(USN *)&Buffer;
}
CloseHandle(hVol);
}
I've managed to work out what the problem is.
The example Microsoft code creates a local variable defined as READ_USN_JOURNAL_DATA, which is defined as:
#if (NTDDI_VERSION >= NTDDI_WIN8)
typedef READ_USN_JOURNAL_DATA_V1 READ_USN_JOURNAL_DATA, *PREAD_USN_JOURNAL_DATA;
#else
typedef READ_USN_JOURNAL_DATA_V0 READ_USN_JOURNAL_DATA, *PREAD_USN_JOURNAL_DATA;
#endif
On my systems (Both the Win10 and Win7 systems) this evaluates to READ_USN_JOURNAL_DATA_V1.
Looking at the difference betweem READ_USN_JOURNAL_DATA_V0 and READ_USN_JOURNAL_DATA_V1 we can see that V0 is defined as:
typedef struct {
USN StartUsn;
DWORD ReasonMask;
DWORD ReturnOnlyOnClose;
DWORDLONG Timeout;
DWORDLONG BytesToWaitFor;
DWORDLONG UsnJournalID;
} READ_USN_JOURNAL_DATA_V0, *PREAD_USN_JOURNAL_DATA_V0;
and the V1 version is defined as:
typedef struct {
USN StartUsn;
DWORD ReasonMask;
DWORD ReturnOnlyOnClose;
DWORDLONG Timeout;
DWORDLONG BytesToWaitFor;
DWORDLONG UsnJournalID;
WORD MinMajorVersion;
WORD MaxMajorVersion;
} READ_USN_JOURNAL_DATA_V1, *PREAD_USN_JOURNAL_DATA_V1;
Note the new Min and Max Major version members.
So, the Microsoft code is defining a local variable called ReadData that is actually a V1 structure, yet it appears to fill in the data assuming it's a V0 structure. i.e. it doesn't set the Min and Max elements.
It appears that Win7 is fine with this but Win10 rejects it and returns error 87 (The parameter is incorrect).
Sure enough if I explicitly define the ReadData variable to be a READ_USN_JOURNAL_DATA_V0 then the code works on Win7 and Win10, whereas if I explicitly define it as a READ_USN_JOURNAL_DATA_V1 then it continues to work on Win7 but not on Win10.
The strange thing is that the API documentation for READ_USN_JOURNAL_DATA_V1 structure states that it's only supported from Windows 8 on, so it's odd that it works on Windows 7 at all. I guess it's just interpreting it as a READ_USN_JOURNAL_DATA_V0 structure given that V1 version is an extension of the V0 structure. If so then it must be ignoring the size parameter that is passed into DeviceIOControl.
Anyway, all working now. I hope someone finds this a useful reference in the future.
I came across this exact same issue with the sample code as the OP. At the start of the sample code, you will see a partial initialization of the structure at the point of declaration. A little later in the code, right before the offending call, there is a line that assigns the UsnJournalID into the read data structure.
For Windows 10, though, the other two members of the V1 structure are not initialized. I initialized them right after the UsnJournalID's initialization with:
#if (NTDDI_VERSION >= NTDDI_WIN8)
ReadData.MinMajorVersion = JournalData.MinSupportedMajorVersion;
ReadData.MaxMajorVersion = JournalData.MaxSupportedMajorVersion;
#endif
When I ran my code after doing this, it worked correctly without the error code. I remember reading in the Volume Management API discussion that the Min and Max versions needed to be set. I forgot exactly where, because I've been reading and testing this stuff for a couple days.
Anyway, I hope that clarifies the issue for anyone coming after me.
Replace the READ_USN_JOURNAL_DATA structure with READ_USN_JOURNAL_DATA_V0 data structure and initialize it.
This worked for me
READ_USN_JOURNAL_DATA_V0 ReadData;
ZeroMemory(&ReadData, sizeof(ReadData));
ReadData.ReasonMask = 0xFFFFFFFF;
I am using windows WinVerifyTrust function on windows 10 pro, to verify dll signatures.
when I activate this function for the first time, it takes 4 seconds for the function to execute and return verification status for the first dll. for the other proceeding dlls, the function returns at fast rate.
Can anyone help me understand the possible reason for that latency?
the call that takes 4 sec is this call:
lStatus = WinVerifyTrust(
NULL,
&WVTPolicyGUID,
&WinTrustData);
The wraper function I'm using looks like this:
#define _UNICODE 1
#define UNICODE 1
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <Softpub.h>
#include <wincrypt.h>
#include <wintrust.h>
// Link with the Wintrust.lib file.
#pragma comment (lib, "wintrust")
BOOL VerifyEmbeddedSignature(LPCWSTR pwszSourceFile)
{
LONG lStatus;
DWORD dwLastError;
// Initialize the WINTRUST_FILE_INFO structure.
WINTRUST_FILE_INFO FileData;
memset(&FileData, 0, sizeof(FileData));
FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);
FileData.pcwszFilePath = pwszSourceFile;
FileData.hFile = NULL;
FileData.pgKnownSubject = NULL;
GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
WINTRUST_DATA WinTrustData;
// Initialize the WinVerifyTrust input data structure.
// Default all fields to 0.
memset(&WinTrustData, 0, sizeof(WinTrustData));
WinTrustData.cbStruct = sizeof(WinTrustData);
// Use default code signing EKU.
WinTrustData.pPolicyCallbackData = NULL;
// No data to pass to SIP.
WinTrustData.pSIPClientData = NULL;
// Disable WVT UI.
WinTrustData.dwUIChoice = WTD_UI_NONE;
// No revocation checking.
WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
// Verify an embedded signature on a file.
WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;
// Verify action.
WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
// Verification sets this value.
WinTrustData.hWVTStateData = NULL;
// Not used.
WinTrustData.pwszURLReference = NULL;
// This is not applicable if there is no UI because it changes
// the UI to accommodate running applications instead of
// installing applications.
WinTrustData.dwUIContext = 0;
// Set pFile.
WinTrustData.pFile = &FileData;
// WinVerifyTrust verifies signatures as specified by the GUID
// and Wintrust_Data.
lStatus = WinVerifyTrust(
NULL,
&WVTPolicyGUID,
&WinTrustData);
switch (lStatus)
{
case ERROR_SUCCESS:
/*
Signed file:
- Hash that represents the subject is trusted.
- Trusted publisher without any verification errors.
- UI was disabled in dwUIChoice. No publisher or
time stamp chain errors.
- UI was enabled in dwUIChoice and the user clicked
"Yes" when asked to install and run the signed
subject.
*/
wprintf_s(L"The file \"%s\" is signed and the signature "
L"was verified.\n",
pwszSourceFile);
break;
case TRUST_E_NOSIGNATURE:
// The file was not signed or had a signature
// that was not valid.
// Get the reason for no signature.
dwLastError = GetLastError();
if (TRUST_E_NOSIGNATURE == dwLastError ||
TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError ||
TRUST_E_PROVIDER_UNKNOWN == dwLastError)
{
// The file was not signed.
wprintf_s(L"The file \"%s\" is not signed.\n",
pwszSourceFile);
}
else
{
// The signature was not valid or there was an error
// opening the file.
wprintf_s(L"An unknown error occurred trying to "
L"verify the signature of the \"%s\" file.\n",
pwszSourceFile);
}
break;
case TRUST_E_EXPLICIT_DISTRUST:
// The hash that represents the subject or the publisher
// is not allowed by the admin or user.
wprintf_s(L"The signature is present, but specifically "
L"disallowed.\n");
break;
case TRUST_E_SUBJECT_NOT_TRUSTED:
// The user clicked "No" when asked to install and run.
wprintf_s(L"The signature is present, but not "
L"trusted.\n");
break;
case CRYPT_E_SECURITY_SETTINGS:
wprintf_s(L"CRYPT_E_SECURITY_SETTINGS - The hash "
L"representing the subject or the publisher wasn't "
L"explicitly trusted by the admin and admin policy "
L"has disabled user trust. No signature, publisher "
L"or timestamp errors.\n");
break;
default:
wprintf_s(L"Error is: 0x%x.\n",
lStatus);
break;
}
// Any hWVTStateData must be released by a call with close.
WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
lStatus = WinVerifyTrust(
NULL,
&WVTPolicyGUID,
&WinTrustData);
return true;
}
Please see MSDN documentation on WinVerifyTrust, it seems you will need to prevent retrieval of revocation lists as well:
// Use only the local cache for revocation checks. Prevents revocation checks over the network.
WinTrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
Scenario
I have a remote computer that I want to run installers (arbitrary executables) on programatically. These installers require two things:
They must run in Administrator mode.
They must run under a specific user context (Specifically, a local user who is a member of the Administrators group).
This has proven to be very challenging.
It appears as though there are a few external tools that exist that do this, but I am looking for a solution that comes with Windows.
What a valid solution to this problem would look like
From an elevated context (e.g. an elevated batch file or executable program) a valid solution should be able to programatically launch a process in Administrator mode under another user context. Assume that the other user's id and password are available, and that the other user is a member of the Administrators group. Additional restrictions:
A valid solution cannot rely on an external tool. Since newer versions of Windows come with .NET and PowerShell by default, these are valid tools to use.
A valid solution cannot require user interactions. This means that if a UAC window pops up, or if any user confirmation is required, the solution is invalid.
Please test your solution before posting it to make sure it works! If you are going to provide a link to another solution, please verify that the linked solution works before posting. Many people who claim to have working solutions to this problem in fact do not.
What I have tried
I have tried using Batch Scripts, PowerShell, and C#. As far as I can tell, none of these technologies will accomplish the task. They all suffer from the same fundamental problem - running a task as another user and in Administrator mode are mutually exclusive processes. Let me be more specific:
Why Not Batch
The command that one would use to run under a different user context is Runas, which does not launch the process elevated. There are several external tools that claim to get around this, but as stated earlier these are not permitted.
Why Not PowerShell
The command to start a new process, Start-Process, can elevate a new process and run it as a different user, but not at the same time. I have an open question here referring to this issue. Unfortunately no one has provided a solution, which leads me to believe that it is impossible.
Why Not C#
This also appears to be impossible, as the Process class does not appear to support launching a process in Administrator mode and under a different user's credentials.
Why not an external tool?
This forces me to rely on someone else's code to do the right thing, and I would rather code it up myself than do that. In fact I have a solution that is one step better than relying on someone else, but is rather hackish:
Create a task using the Task Scheduler to launch the executable in administrator mode on the specified account at some time in the very distant future.
Force the Task to run immediately.
Wait to see if the task has finished. Answered here.
Thanks in advance to anyone who tries to help! It is greatly appreciated and I hope that if nothing else, other people are able to find this for the Task Scheduler work around.
OK, so it turns out that CreateProcessWithLogonW function filters the user token, and so does LogonUser. This would seem to leave us stuck, since we don't have the right privileges to correct the problem (see footnote) but it turns out that LogonUser does not filter the token if you use LOGON32_LOGON_BATCH rather than LOGON32_LOGON_INTERACTIVE.
Here's some code that actually works. We use the CreateProcessAsTokenW function to launch the process, because this particular variant requires only SE_IMPERSONATE_NAME privilege, which is granted to administrator accounts by default.
This sample program launches a subprocess which creates a directory in c:\windows\system32, which would not be possible if the subprocess was not elevated.
#define _WIN32_WINNT 0x0501
#include <Windows.h>
#include <Sddl.h>
#include <conio.h>
#include <stdio.h>
wchar_t command[] = L"c:\\windows\\system32\\cmd.exe /c md c:\\windows\\system32\\proof-that-i-am-an-admin";
int main(int argc, char **argv)
{
HANDLE usertoken;
STARTUPINFO sinfo;
PROCESS_INFORMATION pinfo;
ZeroMemory(&sinfo, sizeof(sinfo));
sinfo.cb = sizeof(sinfo);
if (!LogonUser(L"username", L"domain", L"password", LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &usertoken))
{
printf("LogonUser: %u\n", GetLastError());
return 1;
}
if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\\windows\\system32\\cmd.exe", command, 0, NULL, NULL, &sinfo, &pinfo))
{
printf("CreateProcess: %u\n", GetLastError());
return 1;
}
return 0;
}
However, if the target process is a GUI process (including a process with a visible console) it won't display properly. Apparently CreateProcessWithTokenW only assigns the minimum desktop and window station permissions necessary for a process to run, which is not enough to actually display a GUI.
Even if you don't actually need to see the output, there's a risk that the broken GUI will cause functional problems with the program.
So, unless the target process runs in the background, we should probably assign permissions appropriately. In general, it is best to create a new window station and a new desktop, to isolate the target process; in this case, though, the target process is going to be running as admin anyway, so there's no point - we can make life easier by just changing the permissions on the existing window station and desktop.
Edit 24 November 2014: corrected access rights in window station ACE so they will work for non-administrative users. Note that doing this may allow the non-admin user in question to compromise processes in the target session.
#define _WIN32_WINNT 0x0501
#include <Windows.h>
#include <AccCtrl.h>
#include <Aclapi.h>
#include <stdio.h>
wchar_t command[] = L"c:\\windows\\system32\\notepad.exe";
int main(int argc, char **argv)
{
HANDLE usertoken;
STARTUPINFO sinfo;
PROCESS_INFORMATION pinfo;
HDESK desktop;
EXPLICIT_ACCESS explicit_access;
BYTE buffer_token_user[SECURITY_MAX_SID_SIZE];
PTOKEN_USER token_user = (PTOKEN_USER)buffer_token_user;
PSECURITY_DESCRIPTOR existing_sd;
SECURITY_DESCRIPTOR new_sd;
PACL existing_dacl, new_dacl;
BOOL dacl_present, dacl_defaulted;
SECURITY_INFORMATION sec_info_dacl = DACL_SECURITY_INFORMATION;
DWORD dw, size;
HWINSTA window_station;
if (!LogonUser(L"username", L"domain", L"password", LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &usertoken))
{
printf("LogonUser: %u\n", GetLastError());
return 1;
}
if (!GetTokenInformation(usertoken, TokenUser, buffer_token_user, sizeof(buffer_token_user), &dw))
{
printf("GetTokenInformation(TokenUser): %u\n", GetLastError());
return 1;
}
window_station = GetProcessWindowStation();
if (window_station == NULL)
{
printf("GetProcessWindowStation: %u\n", GetLastError());
return 1;
}
if (!GetUserObjectSecurity(window_station, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
printf("GetUserObjectSecurity(window_station) call 1: %u\n", GetLastError());
return 1;
}
existing_sd = malloc(size);
if (existing_sd == NULL)
{
printf("malloc failed\n");
return 1;
}
if (!GetUserObjectSecurity(window_station, &sec_info_dacl, existing_sd, size, &dw))
{
printf("GetUserObjectSecurity(window_station) call 2: %u\n", GetLastError());
return 1;
}
if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted))
{
printf("GetSecurityDescriptorDacl(window_station): %u\n", GetLastError());
return 1;
}
if (!dacl_present)
{
printf("no DACL present on window station\n");
return 1;
}
explicit_access.grfAccessMode = SET_ACCESS;
explicit_access.grfAccessPermissions = WINSTA_ALL_ACCESS | READ_CONTROL;
explicit_access.grfInheritance = NO_INHERITANCE;
explicit_access.Trustee.pMultipleTrustee = NULL;
explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER;
explicit_access.Trustee.ptstrName = (LPTSTR)token_user->User.Sid;
dw = SetEntriesInAcl(1, &explicit_access, existing_dacl, &new_dacl);
if (dw != ERROR_SUCCESS) {
printf("SetEntriesInAcl(window_station): %u\n", dw);
return 1;
}
if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION))
{
printf("InitializeSecurityDescriptor(window_station): %u\n", GetLastError());
return 1;
}
if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE))
{
printf("SetSecurityDescriptorDacl(window_station): %u\n", GetLastError());
return 1;
}
if (!SetUserObjectSecurity(window_station, &sec_info_dacl, &new_sd))
{
printf("SetUserObjectSecurity(window_station): %u\n", GetLastError());
return 1;
}
free(existing_sd);
LocalFree(new_dacl);
desktop = GetThreadDesktop(GetCurrentThreadId());
if (desktop == NULL)
{
printf("GetThreadDesktop: %u\n", GetLastError());
return 1;
}
if (!GetUserObjectSecurity(desktop, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
printf("GetUserObjectSecurity(desktop) call 1: %u\n", GetLastError());
return 1;
}
existing_sd = malloc(size);
if (existing_sd == NULL)
{
printf("malloc failed\n");
return 1;
}
if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, size, &dw))
{
printf("GetUserObjectSecurity(desktop) call 2: %u\n", GetLastError());
return 1;
}
if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, 4096, &dw))
{
printf("GetUserObjectSecurity: %u\n", GetLastError());
return 1;
}
if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted))
{
printf("GetSecurityDescriptorDacl: %u\n", GetLastError());
return 1;
}
if (!dacl_present)
{
printf("no DACL present\n");
return 1;
}
explicit_access.grfAccessMode = SET_ACCESS;
explicit_access.grfAccessPermissions = GENERIC_ALL;
explicit_access.grfInheritance = NO_INHERITANCE;
explicit_access.Trustee.pMultipleTrustee = NULL;
explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER;
explicit_access.Trustee.ptstrName = (LPTSTR)token_user->User.Sid;
dw = SetEntriesInAcl(1, &explicit_access, existing_dacl, &new_dacl);
if (dw != ERROR_SUCCESS) {
printf("SetEntriesInAcl: %u\n", dw);
return 1;
}
if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION))
{
printf("InitializeSecurityDescriptor: %u\n", GetLastError());
return 1;
}
if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE))
{
printf("SetSecurityDescriptorDacl: %u\n", GetLastError());
return 1;
}
if (!SetUserObjectSecurity(desktop, &sec_info_dacl, &new_sd))
{
printf("SetUserObjectSecurity(window_station): %u\n", GetLastError());
return 1;
}
free(existing_sd);
LocalFree(new_dacl);
ZeroMemory(&sinfo, sizeof(sinfo));
sinfo.cb = sizeof(sinfo);
if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\\windows\\system32\\notepad.exe", command, 0, NULL, NULL, &sinfo, &pinfo))
{
printf("CreateProcess: %u\n", GetLastError());
return 1;
}
return 0;
}
Note the use of LOGON_WITH_PROFILE. This is not necessary to display a GUI, and it slows down launching the process considerably, so remove it if you don't need it - but if you are an administrator, the most likely reason that you are launching a process as a different administrator is that you need something in that administrator's user profile. (Another scenario might be that you need to use a specific domain account in order to access resources on another machine.)
Footnote:
Specifically, you need SeTcbPrivilege in order to use GetTokenInformation and TokenLinkedToken to obtain a usable handle to the elevated token that LogonUser generates. Unfortunately, this privilege is usually only available if you are running as local system.
If you do not have SeTcbPrivilege you can still obtain a copy of the linked token, but in this case it is an impersonation token at SecurityIdentification level so is of no use when creating a new process. Thanks to RbMm for helping me clarify this.
I am writing a C++ (Windows) client console application which reads from an anonymous pipe on STDIN. I would like to be able to use my program as follows:
echo input text here | my_app.exe
and do something in the app with the text that is piped in
OR
my_app.exe
and then use some default text inside of the app instead of the input from the pipe.
I currently have code that successfully reads from the pipe on STDIN given the first situation:
#include <Windows.h>
#include <iostream>
#include <string>
#define BUFSIZE 4096
int main(int argc, const char *argv[]) {
char char_buffer[BUFSIZE];
DWORD bytes_read;
HANDLE stdin_handle;
BOOL continue_reading;
unsigned int required_size;
bool read_successful = true;
stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
if (stdin_handle == INVALID_HANDLE_VALUE) {
std::cout << "Error: invalid handle value!\n\n";
} else {
continue_reading = true;
while (continue_reading) {
continue_reading = ReadFile(stdin_handle, char_buffer, BUFSIZE,
&bytes_read, NULL);
if (continue_reading) {
if (bytes_read != 0) {
// Output what we have read so far
for (unsigned int i = 0; i < bytes_read; i++) {
std::cout << char_buffer[i];
}
} else {
continue_reading = false;
}
}
}
}
return 0;
}
I know that my only option with anonymous pipes is to do a blocking read with ReadFile. If I understand correctly, in regard to how I am invoking it, ReadFile will continue to read from the buffer on STDIN until it detects an end of write operation on the other end of the pipe (perhapse reads some sort of "end of write" token??). I would like to know if there is some sort of "beginning write" token that will be in the buffer if something is being piped in which I can check on STDIN BEFORE I call ReadFile. If this were the case I could just skip calling ReadFile and use some default text.
If there is not a way to do this, I can always pass in a command line argument that denotes that I should not check the pipe and just use the default text (or the other way around), but I would much prefer to do it the way that I specified.
Look at PeekNamedPipe(). Despite its name, it works for both named and anonymous pipes.
int main(int argc, const char *argv[])
{
char char_buffer[BUFSIZE];
DWORD bytes_read;
DWORD bytes_avail;
DWORD dw;
HANDLE stdin_handle;
bool is_pipe;
stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
is_pipe = !GetConsoleMode(stdin_handle, &dw);
if (stdin_handle == INVALID_HANDLE_VALUE) {
std::cout << "Error: invalid handle value!\n\n";
} else {
while (1) {
if (is_pipe) {
if (PeekNamedPipe(stdin_handle, NULL, 0, NULL, &bytes_avail, NULL)) {
if (bytes_avail == 0) {
Sleep(100);
continue;
}
}
}
if (!ReadFile(stdin_handle, char_buffer, min(bytes_avail, BUFSIZE), &bytes_read, NULL)) {
break;
}
if (bytes_read == 0) {
break;
}
// Output what we have read so far
for (unsigned int i = 0; i < bytes_read; i++) {
std::cout << char_buffer[i];
}
}
}
return 0;
}
It looks like what you're really trying to do here is to determine whether you've got console input (where you use default value) vs pipe input (where you use input from the pipe).
Suggest testing that directly instead of trying to check if there's input ready: the catch with trying to sniff whether there's data in the pipe is that if the source app is slow in generating output, your app might make an incorrect assumption just because there isn't input yet available. (It might also be possible that, due to typeahead, there's a user could have typed in characters that area ready to be read from console STDIN before your app gets around to checking if input is available.)
Also, keep in mind that it might be useful to allow your app to be used with file redirection, not just pipes - eg:
myapp.exe < some_input_file
The classic way to do this "interactive mode, vs used with redirected input" test on unix is using isatty(); and luckily there's an equivalent in the Windows CRT - see function _isatty(); or use GetFileType() checking for FILE_TYPE_CHAR on GetStdHandle(STD_INPUT_HANDLE) - or use say GetConsoleMode as Remy does, which will only succeed on a real console handle.
This also works without overlapped I/O while using a second thread, that does the synchronous ReadFile-call. Then the main thread waits an arbitrary amount of time and acts like above...
Hope this helps...
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