Creating console pipe instead of file pipe - c

I am trying to capture output from cmd.exe when running commands. I am trying to capture Unicode output to support other languages. My code works right now for getting unicode back from built in commands to cmd.exe. Such as Dir. But if I try to run external commands such as "net user" or "ipconfig" it does not output unicode.
I did reading and found out it is due to my pipes being file pipes. Ipconfig.exe determines if it is printing to the console or not. If it is printing to console, it uses WriteFileW and outputs unicode. Else it just prints ANSI and I get ???????? for anything Unicode.
#include <winsock.h>
#include <Windows.h>
#include <stdio.h>
void ExecCmd() {
HANDLE hPipeRead;
HANDLE hPipeWrite;
SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) };
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
BOOL fSuccess;
BOOL bProcessEnded = FALSE;
size_t total = 0;
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) {
return;
}
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput = hPipeWrite;
si.hStdError = hPipeWrite;
si.wShowWindow = SW_HIDE;
WCHAR k[] = L"cmd.exe /u /c net user";
fSuccess = CreateProcessW(NULL, k, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (!fSuccess) {
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
return;
}
for (; !bProcessEnded;) {
bProcessEnded = WaitForSingleObject(pi.hProcess, 50) == WAIT_OBJECT_0;
while (TRUE) {
DWORD dwRead = 0;
DWORD dwAvail = 0;
WCHAR *buffer = NULL;
if (!PeekNamedPipe(hPipeRead, NULL, NULL, NULL, &dwAvail, NULL)) {
break;
}
if (!dwAvail) {
break;
}
buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (dwAvail + 2));
if (buffer == NULL) {
break;
}
if (!ReadFile(hPipeRead, buffer, dwAvail, &dwRead, NULL) || !dwRead) {
break;
}
total += dwRead;
}
}
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return;
}
int main() {
ExecCmd();
return 0;
}
My question is how can I change the way my pipes are getting information back from external commands like ipconfig so that it will support unicode.

Related

C: Windows not finding HARDWARE\DEVICEMAP\SERIALCOMM even though it can be found using regedit

I want to list the COM ports (like one can see in 'Device Manager'), but at runtime.
I shamelessly copied the code from this site (code shown at end of post), and I think I understand it. However, the program fails at line 36, giving me the error:
Failed to open key 'HARDWARE\DEVICEMAP\SERIALCOMM'
Windows reports error: (0x00000002): The system cannot find the file specified.
If I open the registry editor I clearly see the folder, and when I click it, I see the device I have connected to my computer.
I've compiled the code using MinGW and the command gcc filename.c
Any ideas on how to fix this? Might it be a permission issue? Is there some compiler flag that gcc needs for me to be able to use it?
The code I am running is this:
#define WIN32_LEAN_AND_MEAN // excludes stuff frokm windows.h that we won't need here.
#include <Windows.h>
#include <string.h>
#include <tchar.h>
#include <malloc.h>
void ShowErrorFromLStatus(LSTATUS lResult)
{
LPTSTR psz;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
lResult,
0,
(LPTSTR)&psz,
1024,
NULL);
_tprintf(_T("Windows reports error: (0x%08X): %s\n"), lResult, (psz) ? psz : _T("(null)"));
if (psz)
{
LocalFree(psz);
}
}
int main()
{
DWORD nValues, nMaxValueNameLen, nMaxValueLen;
HKEY hKey = NULL;
LPTSTR szDeviceName = NULL;
LPTSTR szFriendlyName = NULL;
DWORD dwType = 0;
DWORD nValueNameLen = 0;
DWORD nValueLen = 0;
DWORD dwIndex = 0;
LSTATUS lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey);
if (ERROR_SUCCESS != lResult)
{
printf("Failed to open key \'HARDWARE\\DEVICEMAP\\SERIALCOMM\' \n");
ShowErrorFromLStatus(lResult);
return 1;
}
lResult = RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL,
&nValues, &nMaxValueNameLen, &nMaxValueLen, NULL, NULL);
if (ERROR_SUCCESS != lResult)
{
_tprintf(_T("Failed to RegQueryInfoKey()\n"));
ShowErrorFromLStatus(lResult);
RegCloseKey(hKey);
return 2;
}
szDeviceName = (LPTSTR)malloc(nMaxValueNameLen + sizeof(TCHAR));
if (!szDeviceName)
{
_tprintf(_T("malloc() fail\n"));
RegCloseKey(hKey);
return 3;
}
szFriendlyName = (LPTSTR)malloc(nMaxValueLen + sizeof(TCHAR));
if (!szFriendlyName)
{
free(szDeviceName);
_tprintf(_T("malloc() fail\n"));
RegCloseKey(hKey);
return 3;
}
_tprintf(_T("Found %d serial device(s) registered with PnP and active or available at the moment.\n"), nValues);
for (DWORD dwIndex = 0; dwIndex < nValues; ++dwIndex)
{
dwType = 0;
nValueNameLen = nMaxValueNameLen + sizeof(TCHAR);
nValueLen = nMaxValueLen + sizeof(TCHAR);
lResult = RegEnumValueW(hKey, dwIndex,
szDeviceName, &nValueNameLen,
NULL, &dwType,
(LPBYTE)szFriendlyName, &nValueLen);
if (ERROR_SUCCESS != lResult || REG_SZ != dwType)
{
_tprintf(_T("SerialPortEnumerator::Init() : can't process registry value, index: %d\n"), dwIndex);
ShowErrorFromLStatus(lResult);
continue;
}
_tprintf(_T("Found port \'%s\': Device name for CreateFile(): \'\\.%s\'\n"), szFriendlyName, szDeviceName);
}
free(szDeviceName);
free(szFriendlyName);
RegCloseKey(hKey);
return 0;
}
EDIT
I'm now calling RegOpenKeyEx with _T("HARDWARE\\DEVICEMAP\\SERIALCOMM"), and in the call to RegEnumValueW on line 81, I casted szDeviceName to a LPWSTR. gcc no longer gives any errors or warnings. However, when running the program, all that comes out is
Found 1 serial device(s) registered with PnP and active or available at the moment.
Found port 'C': Device name for CreateFile(): '\.\'
And the port I have connected (COM3) does not show up.
Any other advice?
Thank you to Simon Mourier and Ian Abbott!
As stated in the edit, calling RegOpenKeyEx with _T("HARDWARE\\DEVICEMAP\\SERIALCOMM") and casting szDeviceName to a LPWSTR fixed all errors.
Further, using the identifier %ls in the call to _tprintf() on line 92 made the names print correctly.
The final, working code looks like this:
#define WIN32_LEAN_AND_MEAN // excludes stuff frokm windows.h that we won't need here.
#include <Windows.h>
#include <string.h>
#include <tchar.h>
#include <malloc.h>
#include <stdio.h>
void ShowErrorFromLStatus(LSTATUS lResult)
{
LPTSTR psz;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
lResult,
0,
(LPTSTR)&psz,
1024,
NULL);
_tprintf(_T("Windows reports error: (0x%08X): %s\n"), lResult, (psz) ? psz : _T("(null)"));
if (psz)
{
LocalFree(psz);
}
}
int main()
{
DWORD nValues, nMaxValueNameLen, nMaxValueLen;
HKEY hKey = NULL;
LPTSTR szDeviceName = NULL;
LPTSTR szFriendlyName = NULL;
DWORD dwType = 0;
DWORD nValueNameLen = 0;
DWORD nValueLen = 0;
DWORD dwIndex = 0;
LSTATUS lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &hKey);
if (ERROR_SUCCESS != lResult)
{
printf("Failed to open key \'HARDWARE\\DEVICEMAP\\SERIALCOMM\' \n");
ShowErrorFromLStatus(lResult);
return 1;
}
lResult = RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL,
&nValues, &nMaxValueNameLen, &nMaxValueLen, NULL, NULL);
if (ERROR_SUCCESS != lResult)
{
_tprintf(_T("Failed to RegQueryInfoKey()\n"));
ShowErrorFromLStatus(lResult);
RegCloseKey(hKey);
return 2;
}
szDeviceName = (LPTSTR)malloc(nMaxValueNameLen + sizeof(TCHAR));
if (!szDeviceName)
{
_tprintf(_T("malloc() fail\n"));
RegCloseKey(hKey);
return 3;
}
szFriendlyName = (LPTSTR)malloc(nMaxValueLen + sizeof(TCHAR));
if (!szFriendlyName)
{
free(szDeviceName);
_tprintf(_T("malloc() fail\n"));
RegCloseKey(hKey);
return 3;
}
_tprintf(_T("Found %d serial device(s) registered with PnP and active or available at the moment.\n"), nValues);
for (DWORD dwIndex = 0; dwIndex < nValues; ++dwIndex)
{
dwType = 0;
nValueNameLen = nMaxValueNameLen + sizeof(TCHAR);
nValueLen = nMaxValueLen + sizeof(TCHAR);
lResult = RegEnumValueW(hKey, dwIndex,
(LPWSTR)szDeviceName, &nValueNameLen,
NULL, &dwType,
(LPBYTE)szFriendlyName, &nValueLen);
if (ERROR_SUCCESS != lResult || REG_SZ != dwType)
{
_tprintf(_T("SerialPortEnumerator::Init() : can't process registry value, index: %d\n"), dwIndex);
ShowErrorFromLStatus(lResult);
continue;
}
_tprintf(_T("Found port \'%ls\': Device name for CreateFile(): \'\\.%ls\'\n"), szFriendlyName, szDeviceName);
}
free(szDeviceName);
free(szFriendlyName);
RegCloseKey(hKey);
return 0;
}

Launching an APP from volume ID

I'm trying to launch an .exe file in windows using and a volume ID. The volume do not have a letter. Here is what I've tryed.
ShellExecute
When I call ShellExecute with the volume ID, Windows' explorer open the correct directory. Same thing if I try to open a folder inside the volume. However, if I try to open an exe file, nothing happen.
This is the way I call ShellExecute, which is part of windows.h :
char path[MAX_PATH] = R"(\\?\Volume{0dc7f9cc-d3ea-11e4-824b-806e6f6e6963}\App.exe)";
LONG_PTR returnValue = (LONG_PTR) ShellExecute(GetDesktopWindow(),NULL, path,NULL,NULL,SW_SHOW);
The error code returned is 2 :
The system cannot find the file specified.
CreateProcessW
After following the comments, I am now using . My new code look like this :
char path[MAX_PATH] = R"(\\?\Volume{0dc7f9cc-d3ea-11e4-824b-806e6f6e6963}\launcher.exe)";
STARTUPINFO info = {sizeof(info)};
PROCESS_INFORMATION processInfo;
if (CreateProcessW(path, NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &info, &processInfo))
{
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
else {
DWORD dw = GetLastError();
printf("ERROR CODE: %d", dw);
}
But it still doesn't work. Error is the same (2).
Changing the path notation
From "\\?\Volume{ID}" to "\\.\Volume{ID}".
The strange thing is that I can list the drive's files or start a console process but I cannot launch any GUI EXE files. Error code remains 2.
Is the anythink I am missing here ? Any help are welcome. Thank you.
Answer
I've got confuse with the comments, using "\\.\Volume{ID}\myApp.exe" worked well if used with CreateProcess, not CreateProcessW.
There is the fully functional code (Don't forget to include windows.h).
char path[MAX_PATH] = R"(\\.\Volume{0dc7f9cc-d3ea-11e4-824b-806e6f6e6963}\App.exe)";
STARTUPINFO info = {sizeof(info)};
PROCESS_INFORMATION processInfo;
if (CreateProcess(path, NULL, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &info, &processInfo))
{
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
else
{
DWORD dw = GetLastError();
printf("ERROR CODE WHILE STARTING: %d", dw);
}
problem in win32 subsystem - it not use native for windows NT Paths, but Win32 Paths which first must be converted to NT Paths before passing to kernel.
also some functions, like ShellExecuteEx or CreateProcessW accept not any valid win32 path, but only restricted subset - so called Drive Letter form. if pass path in form \\?\Volume{guid}\* - ShellExecuteEx and CreateProcessW always fail with this path (so called volume path) even if path correct (CreateFileW open this path). funny that CreateProcessW will be work with path \\.\Volume{guid}\* (if replace ? to . at [2] position) but ShellExecuteEx not worked with this path too.
only one reliable solution here - convert this volume form path to Drive Letter form. this can be done with help IOCTL_MOUNTMGR_QUERY_POINTS - need get list of all MOUNTMGR_MOUNT_POINT in system and do 2 loops by this list - first found device name by existing volume symlink. then in loop #2 - by already known device name found - dos-device name and got it drive letter
#include <mountmgr.h>
ULONG NtVolumePathToDosPath(PUNICODE_STRING VolumePath)
{
if (!MOUNTMGR_IS_NT_VOLUME_NAME(VolumePath))
{
return ERROR_PATH_NOT_FOUND;
}
static volatile UCHAR guz;
PVOID stack = alloca(guz);
ULONG cb = 0, rcb = 0x400;
union {
PVOID buf;
PMOUNTMGR_MOUNT_POINTS pmmp;
};
HANDLE hFile = CreateFile(MOUNTMGR_DOS_DEVICE_NAME, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
static MOUNTMGR_MOUNT_POINT mmp;
ULONG dwError = NOERROR;
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (!DeviceIoControl(hFile, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(mmp), buf, cb, &rcb, 0))
{
dwError = GetLastError();
rcb = pmmp->Size;
continue;
}
dwError = ERROR_PATH_NOT_FOUND;
if (ULONG NumberOfMountPoints = pmmp->NumberOfMountPoints)
{
PMOUNTMGR_MOUNT_POINT MountPoints = pmmp->MountPoints;
//loop #1: search for DeviceName linked to VolumePath
do
{
UNICODE_STRING SymbolicLinkName = {
MountPoints->SymbolicLinkNameLength,
SymbolicLinkName.Length,
(PWSTR)RtlOffsetToPointer(pmmp, MountPoints->SymbolicLinkNameOffset)
};
if (MOUNTMGR_IS_VOLUME_NAME(&SymbolicLinkName))
{
if (RtlEqualUnicodeString(&SymbolicLinkName, VolumePath, TRUE))
{
// found DeviceName
UNICODE_STRING _DeviceName = {
MountPoints->DeviceNameLength,
_DeviceName.Length,
(PWSTR)RtlOffsetToPointer(pmmp, MountPoints->DeviceNameOffset)
};
NumberOfMountPoints = pmmp->NumberOfMountPoints;
MountPoints = pmmp->MountPoints;
// loop #2: search for "drive letter" linked to DeviceName
do
{
UNICODE_STRING DeviceName = {
MountPoints->DeviceNameLength,
DeviceName.Length,
(PWSTR)RtlOffsetToPointer(pmmp, MountPoints->DeviceNameOffset)
};
if (RtlEqualUnicodeString(&_DeviceName, &DeviceName, FALSE))
{
SymbolicLinkName.MaximumLength = SymbolicLinkName.Length = MountPoints->SymbolicLinkNameLength;
SymbolicLinkName.Buffer = (PWSTR)RtlOffsetToPointer(pmmp, MountPoints->SymbolicLinkNameOffset);
if (MOUNTMGR_IS_DRIVE_LETTER(&SymbolicLinkName))
{
PWSTR szVolumePath = VolumePath->Buffer + 48;
*--szVolumePath = ':';
*--szVolumePath = SymbolicLinkName.Buffer[12];
*--szVolumePath = '\\';
*--szVolumePath = '?';
*--szVolumePath = '\\';
*--szVolumePath = '\\';
VolumePath->Buffer = szVolumePath;
dwError = NOERROR;
break;
}
}
} while (MountPoints++, --NumberOfMountPoints);
break;
}
}
} while (MountPoints++, --NumberOfMountPoints);
}
break;
} while (dwError == ERROR_MORE_DATA);
CloseHandle(hFile);
return dwError;
}
ULONG TestExecByVolumePath(PCWSTR szVolumePath)
{
size_t size = wcslen(szVolumePath) * sizeof(WCHAR);
if (size >= MAXUSHORT || size < 98)
{
return ERROR_PATH_NOT_FOUND;
}
UNICODE_STRING VolumePath;
VolumePath.Length = 96;
VolumePath.MaximumLength = (USHORT)size + sizeof(WCHAR);
memcpy(VolumePath.Buffer = (PWSTR)alloca(VolumePath.MaximumLength), szVolumePath, VolumePath.MaximumLength);
if (!MOUNTMGR_IS_DOS_VOLUME_NAME(&VolumePath))
{
return ERROR_PATH_NOT_FOUND;
}
VolumePath.Buffer[1] = '?';
ULONG dwErr = NtVolumePathToDosPath(&VolumePath);
if (dwErr == NOERROR)
{
SHELLEXECUTEINFOW sei = {sizeof(sei), 0, 0, L"open", VolumePath.Buffer, 0, 0, SW_SHOWDEFAULT };
if (!ShellExecuteExW(&sei))
{
dwErr = GetLastError();
}
}
return dwErr;
}
TestExecByVolumePath(L"\\\\?\\Volume{***}\\Windows\\System32\\notepad.exe");

Windows CreateProcess and output redirection

I'm working in a C project, windows environment using WinAPI.
My function execs a command (e.g. : "dir C:\Users") it received as a param, and returns its output.
It is ran from a DLL so it must not spawn a cmd.exe windows, which means I can't use _popen. So I use CreateProcessA, and a pipe to catch stdout.
Here is the code :
char *runExec(char *command, int *contentLen) {
char *ret=NULL,*tmp=NULL;
DWORD readBytes;
int size=0;
char buffer[128],cmdBuf[4096];
HANDLE StdOutHandles[2];
CreatePipe(&StdOutHandles[0], &StdOutHandles[1], NULL, 4096);
STARTUPINFOA si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdOutput = StdOutHandles[1];
si.hStdError = StdOutHandles[1];
PROCESS_INFORMATION pi;
snprintf(cmdBuf,4096,"cmd /C %s", command);
if (!CreateProcessA(NULL, cmdBuf, NULL, NULL, FALSE, CREATE_NO_WINDOW | DETACHED_PROCESS, NULL, NULL, &si, &pi)) {
printf("Error createProcess : %d\n",GetLastError());
return NULL;
}
CloseHandle(StdOutHandles[1]);
printf("Before read\n");
while (ReadFile(StdOutHandles[0], buffer, 127, &readBytes, NULL)){
printf("IN WHILE\n");
buffer[readBytes] = 0;
printf("read %d bytes\n", readBytes);
size += readBytes;
tmp = (char *)realloc(ret, size + 1);
if (tmp == NULL) {
free(ret);
return NULL;
}
ret = tmp;
strncpy(ret + (size - readBytes), buffer, readBytes);
ret[size] = 0;
}
printf("Readfile returned with %d, read %d\n", GetLastError(),readBytes);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(StdOutHandles[0]);
printf("#%s#\n", ret);
return ret;
}
And the output is :
Before read
Readfile returned with 109, read 0
#(null)#
So from that I understand that :
- My loop isn't even executed once
- ReadFile reads nothing and returns Broken Pipe immediately
I have read various stackoverflow and MSDN pages, but I can't seem to make it work.
As pointed out in the comment, handles must be inheritable, which means replacing the CreatePipe like this :
SECURITY_ATTRIBUTES pipeAttrib;
memset(&pipeAttrib, 0, sizeof(pipeAttrib));
pipeAttrib.nLength = sizeof(SECURITY_ATTRIBUTES);
pipeAttrib.bInheritHandle = NULL;
pipeAttrib.bInheritHandle = TRUE;
if (!CreatePipe(&StdOutHandles[0], &StdOutHandles[1],&pipeAttrib, 4096)) {
printf("Create pipe error %d\n", GetLastError());
return NULL;
}
And setting CreateProcess 5th parameter (bInheritHandles) to TRUE instead of FALSE.

Redirecting CMD.exe output and input in c through pipes

Im trying to print the output and input of cmd commands recieved to stdout just like a normal cmd.exe would do.
I could use the function _popen but when i start a program like Python or Powershell whith it, it doesnt work. So i Need the Output of the child process and be able to send commands to the child process.
there is a simmilar question here
So i modified the code from this link to look like this:
void WriteToPipe(char* command){
DWORD dwRead, dwWritten;
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, command, strlen(command), &dwWritten, NULL);
// Close the pipe handle so the child process stops reading.
if (!CloseHandle(g_hChildStd_IN_Wr))
ErrorExit(TEXT("StdInWr CloseHandle"));
}
void ReadFromPipe(void){
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
//bSuccess = PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, BUFSIZE, &dwRead, &dwTotalAvailBytes, &dwBytesLeft);
}
the main loop looks like this:
CreateChildProcess("C:\\Python27\\python.exe");
char input_buffer[100] = { 0 };
while (1){
fgets(input_buffer, 99, stdin);
WriteToPipe(input_buffer);
ReadFromPipe();
}
everything else (from the code) stayed the same.
Now my Problem is, i want to enter multiple commands to the same process, but there is a CloseHandle fucntion in WriteToPipe, after the handle is closed i cant enter more commands.
How to get a valid HANDLE to write more than 1 command to the process ?
int WriteToPipe(char* command){
DWORD dwRead, dwWritten;
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, command, strlen(command), &dwWritten, NULL);
return bSuccess ;
}
close the handle in main() function since g_hChildStd_IN_Wr is global. also, return bSuccess; notify you if write to pipe has succeeded
put ReadFromPipe() in a separate Thread;
my code here is a garbage but it is working code, it is for test purpose only.
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#define BUFSIZE 4096
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
HANDLE g_hInputFile = NULL;
int CreateChildProcess(TCHAR *szCmdline);
void PrintError(char *text,int err);
int InitPipes();
int WriteToPipe(char* command){
DWORD dwRead, dwWritten;
BOOL bSuccess = FALSE;
SetLastError(0);
WriteFile(g_hChildStd_IN_Wr, command, strlen(command), &dwWritten, NULL);
bSuccess=GetLastError();
PrintError("WriteToPipe",bSuccess);
return (bSuccess==0)||(bSuccess==ERROR_IO_PENDING);
}
int ReadFromPipe(void){
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetLastError(0);
while(
ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL)
&&
WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL)
);
bSuccess=GetLastError();
PrintError("ReadFromPipe",bSuccess);
return (bSuccess==0)||(bSuccess==ERROR_IO_PENDING);
}
HANDLE hThread;
int __stdcall ThreadProc(int arg){
while(ReadFromPipe())
;
return 0;
}
int main(){
char input_buffer[100] = { 0 };
if(!InitPipes()){
printf("Failed to CreatePipes\n");
return -1;
}
if(!CreateChildProcess("cmd.exe")){
printf("Failed to create child process\n");
return -2;
}
hThread=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
while (1){
fgets(input_buffer, 99, stdin);
if(!WriteToPipe(input_buffer)) break;
}
printf("Program terminated\n");
return 0;
}
int CreateChildProcess(TCHAR *szCmdline){
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(NULL,
szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if ( bSuccess ){
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
}
return bSuccess;
}
int InitPipes(){
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) )
return 0;
if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) )
return 0;
if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
return 0;
if ( ! SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) )
return 0;
return 1;
}
void PrintError(char *text,int err){
DWORD retSize;
LPTSTR pTemp=NULL;
if(!err) return;
retSize=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
NULL,
err,
LANG_NEUTRAL,
(LPTSTR)&pTemp,
0,
NULL );
if(pTemp) printf("%s: %s\n",text,pTemp);
LocalFree((HLOCAL)pTemp);
return ;
}

Given I have PID and Process handle, can I write to stdin of existing process using C in WINAPI

I am trying to send a message to stdin of an existing process. This process writes to stdout, and therefore a command prompt is visible when the process is running.
I have looked here, but it is specific to .net. I would like to use C with the winapi.
Here is what I have tried:
I launch an exe that runs in a command prompt. When launched, the Process ID and Process Handle for the exe are captured by calling GetHandleOfProcessByExeName() (below). My understanding , through reading the MSDN page on WriteFile(), that I should be able to pass the handle of the process along with some text to the process pointed to by the handle. Say "prg.exe", is running on Windows 7. I get its process handle, then pass it along with a message to WriteToProcess().
I expect to see text appear on the command prompt, but this has not happened yet.
Relevant code:
int main(void)
{
HANDLE h = GetHandleOfProcessByExeName("prg.exe");
//this continually fails (returns FALSE)
BOOL status = WriteToProcess("test message", sizeof("test message"));
return 0;
}
HANDLE GetHandleOfProcessByExeName(char *exe)
{
PROCESSENTRY32 entry;
HANDLE hProcess=0;
entry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Process32First(snapshot, &entry) == TRUE)
{
while (Process32Next(snapshot, &entry) == TRUE)
{
if (stricmp(entry.szExeFile, exe) == 0)
{
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
CloseHandle(hProcess);
}
}
}
CloseHandle(snapshot);
return hProcess;
}
BOOL WriteToProcess(char *msg, size_t size)
{
BOOL status = FALSE;
status = WriteFile(gStdinWrite, msg, size, NULL, NULL);
return status; //TRUE for success
}
Edit to response to comments:
defined as file globals:
HANDLE gStdinRead = NULL;
HANDLE gStdinWrite = NULL;
int SystemX(char *cmd, int index)
{
STARTUPINFO sj;
PROCESS_INFORMATION pj;
SECURITY_ATTRIBUTES saAttr;
HANDLE h = 0;
int exit;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
ZeroMemory( &sj, sizeof(sj) );
sj.cb = sizeof(sj);
ZeroMemory( &pj, sizeof(pj) );
//create pipe and pass read end to CreateProcess
CreatePipe(&gStdinRead, &gStdinWrite, &saAttr, 0);
sj.hStdInput = gStdinRead;
sj.hStdOutput = gStdinWrite;
if(!CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &sj, &pj))
{
return h;
}
//Wait until child processes exit.
WaitForSingleObject( pj.hProcess, IGNORE ); //ingnore signal
//Get exit code
GetExitCodeProcess(pj.hProcess, (LPDWORD)(&exit));
return exit;
}

Resources