C# console application stdin/stdout redirection - c#

I have an interesting (read: frustrating) problem kicking off a console application from a C# WPF app and redirecting its stdin and stdout.
It is mostly up and working but I seem to end up not getting some data from stdout as soon as I start redirecting stdin.
I'll clarify with an example. If I don't set hStdInput in the STARTUPINFO structure, when I start the child process I receive the following:
MongoDB shell version: 2.2.0
connecting to: test
local:PRIMARY>
Once I set hStdInput however, I just get this:
MongoDB shell version: 2.2.0
connecting to: test
I know that the BackgroundWorker processing stdout is still functioning because if I send something to the process on stdin, it responds accordingly.
use TestDB
switched to db TestDB
So, this is how I create the process:
_processInfo = new ProcessInfo();
bool ok = false;
SECURITY_ATTRIBUTES sattr = new SECURITY_ATTRIBUTES();
sattr.bInheritHandle = 1;
unsafe
{
sattr.lpSecurityDescriptor = null;
}
sattr.nLength = Marshal.SizeOf(sattr);
IntPtr hWrite;
ok = CreatePipe(out _hReadStdOut, out hWrite, ref sattr, 0);
ok = SetHandleInformation(_hReadStdOut, HANDLE_FLAGS.INHERIT, 0);
IntPtr hRead;
ok = CreatePipe(out hRead, out _hWriteStdIn, ref sattr, 0);
ok = SetHandleInformation(_hWriteStdIn, HANDLE_FLAGS.INHERIT, 0);
var startInfo = new StartupInfo
{
dwFlags = 0x0001 | 0x0100,
wShowWindow = 0,
hStdOutput = hWrite,
hStdError = hWrite,
hStdInput = hRead // If this is IntPtr.Zero, I get everything from stdout
};
SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES();
tSec.nLength = Marshal.SizeOf(tSec);
unsafe
{
ok = CreateProcess(
null,
pathToExeAndArgs,
ref pSec,
ref tSec,
true,
0,
IntPtr.Zero,
null,
ref startInfo,
out _processInfo);
}
I have a BackgroundWorker processing stdout on DoWork which reads the pipe like so:
success = ReadFile(
_hReadStdOut,
bufPtr,
1024,
&read,
IntPtr.Zero);
I'm not using the .Net Process class because it didn't obtain data from stdout until the console application sent a newline, so I didn't get the prompt back in that case either.
Any help with this greatly appreciated.
Cheers.

I suspect the following explains what you have observed:
When you don't define hStdInput the child process uses the standard input device attached to the console. The child process detects that standard input is an interactive console device and writes a prompt.
When you do define hStdInput the child process detects that the standard input is a pipe and so neglects to write a prompt. After all, what's the point of prompting a non-interactive input device?
The child process will use GetFileType(GetStdHandle(STD_INPUT_HANDLE)) to detect what type of device is attached to the standard input. A value of FILE_TYPE_CHAR indicates a console. When you attach a pipe to the standard input then the standard input file type will be FILE_TYPE_PIPE.
My conclusion is that everything is working as designed and intended.

Related

Why does CreateProcess ignore STARTUPINFO window size values?

I am trying to use CreateProcess to start Notepad at a specific window size to avoid the flashing that occurs when it appears somewhere for a split second before I can resize it and move it.
CreateProcess starts Notepad but CreateProcess and Notepad both ignore the window dimensions in the STARTUPINFO structure. Notepad appears in the normal place at the normal size where it was last closed.
No combination of dwFlags that I tried worked. Notepad either does not appear at all or ignores my settings and appears in a place and size determined by the operating system.
Why is CreateProcess ignoring the values I set in STARTUPINFO? Am I missing something?
[TestMethod()]
public void CreateProcessTest() {
const uint NORMAL_PRIORITY_CLASS = 0x0020;
const uint STARTF_USESHOWWINDOW = 0x0001;
// create structures needed by CreateProcess
var pInfo = new Kernel32.PROCESS_INFORMATION();
var pSec = new Kernel32.SECURITY_ATTRIBUTES();
var tSec = new Kernel32.SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
tSec.nLength = Marshal.SizeOf(tSec);
// set the app and a file to open
var app = Environment.GetEnvironmentVariable("windir") + #"\notepad.exe";
var arguments = #" C:\somefile.txt";
// the started app window does not use these values
var sInfo = new Kernel32.STARTUPINFO();
sInfo.dwX = 800; // desired x-y position of the window
sInfo.dwY = 400;
sInfo.dwXSize = 200; // desired size of the window
sInfo.dwYSize = 400;
// no combination of these flags that I tried makes any difference
// Notepad always appears as normal and ignores the size settings above
sInfo.dwFlags = STARTF_USESHOWWINDOW;
sInfo.wShowWindow = (short) Win32.SW_SHOW;
// create the process
var result = Kernel32.CreateProcess(app, arguments,
ref pSec, ref tSec, false, NORMAL_PRIORITY_CLASS,
IntPtr.Zero, null, ref sInfo, out pInfo);
}
Your entire STARTUPINFO structure is being ignored, since you did not set the cb member correctly. I am a bit surprised that CreateProcess is not failing outright with ERROR_INVALID_PARAMETER.
According to the MSDN:
the dwFlags member of STARTUPINFO needs to add STARTF_USEPOSITION and STARTF_USESIZE.
Edited,
Store in registry directory:Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Notepad.
iWindowPosDX
iWindowPosDY
iWindowPosX
iWindowPosY

Send key from C# to interrupt Fortran process

I need to communicate with a command line Fortran app using a c# wrapper. The Fortran process is started using the following c# code.
var process = new Process();
process.StartInfo = new ProcessStartInfo(pathToFortranExe)
{
WorkingDirectory = directory,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
};
...
//listen for prompts from the Fortran program
//and send replies using standardInput as follows
process.StandardInput.WriteLine(data);
When the Fortran program is waiting for user input on the command line I can successfully send messages using the above code.
Now here is the problem. The Fortran program uses long running analysis loops which can be interrupted by sending keys such as Esq or Q. I've been told this interrupt feature is implemented in the Fortran code using the Intel Fortran command PEEKCHARQQ. When I try and trigger these keys from c# using StandardInput they are ignored by the Fortran program. To send these interrupt signals I use:
char key = 'q'
process.StandardInput.Write(key);
//Note that StandardInput.AutoFlush==true
I've also tried SendMessage via pinvoke, but again no luck so far:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private const UInt32 WM_KEYDOWN = 0x0100;
private const UInt32 WM_KEYUP = 0x0101;
public static void SendKey(Process process, char key)
{
var keyCode = (IntPtr)key;
var hWnd = process.Handle;
SendMessage(hWnd, WM_KEYDOWN, keyCode, IntPtr.Zero);
SendMessage(hWnd, WM_KEYUP, keyCode, IntPtr.Zero);
}
So the question is: Are there any other ways to place keys into the keyboard buffer such that they might be picked up PEEKCHARQQ in the Fortran process? Or anything else that I might be missing here?
Update 1:
I've also tried WriteConsoleInput, but I don't think I have the right handle:
var keyCode = (short)key;
var hWnd = process.Handle;
INPUT_RECORD[] lpBuffer = new INPUT_RECORD[1];
lpBuffer[0].KeyEvent.wVirtualKeyCode = keyCode;
int nLength = lpBuffer.Length;
int lpNumberOfEventsWritten;
if (!WriteConsoleInput(
hWnd,
lpBuffer,
nLength,
out lpNumberOfEventsWritten))
{
//this results error code 6: Invalid handle
Console.WriteLine("Error: {0}", GetLastError());
}
You can write to the console input buffer for the child process using a handle to the CONIN$ device with the WriteConsoleInput API.
Two Fortran programs below to demonstrate. The program for the child process:
PROGRAM peek_a_boo
USE IFCORE
IMPLICIT NONE
CHARACTER(*), PARAMETER :: fmt = "('Press any key!')"
PRINT fmt
DO WHILE (.NOT. PEEKCHARQQ())
CALL SLEEPQQ(2000)
PRINT fmt
END DO
END PROGRAM peek_a_boo
The program that shows the Windows API calls. You can translate this to whichever language floats your boat.
PROGRAM peek_parent
USE IFWIN
IMPLICIT NONE
INTEGER(BOOL) :: api_result
INTEGER(HANDLE) :: console_input
TYPE(T_STARTUPINFO) :: startup_info
TYPE(T_PROCESS_INFORMATION) :: process_info
TYPE(T_INPUT_RECORD) :: input_record
INTEGER(DWORD) :: events_written
INTEGER(DWORD) :: wait_event
! Create the child process.
CALL ZeroMemory(LOC(startup_info), SIZEOF(startup_info))
startup_info%cb = SIZEOF(startup_info)
api_result = CreateProcess( &
'peek_a_boo.exe', &
NULL, & ! command line.
NULL, & ! process security attributes.
NULL, & ! thread security attributes.
.FALSE., & ! inherit handles.
0_DWORD, &
NULL, & ! environment.
NULL, & ! current directory.
startup_info, &
process_info )
IF (api_result == 0) THEN
ERROR STOP 'Couldn''t start it :('
END IF
api_result = CloseHandle(process_info%hThread)
! Let the child run for a bit.
CALL SLEEPQQ(5000)
! Get a handle to our console input buffer.
console_input = CreateFile( &
'CONIN$', &
GENERIC_WRITE, &
IANY([FILE_SHARE_READ, FILE_SHARE_WRITE]), &
NULL, & ! security attrs
OPEN_EXISTING, &
0_DWORD, & ! attributes
NULL ) ! template file
IF (console_input == 0) THEN
ERROR STOP 'Couldn''t open it :('
END IF
! Poke something into the buffer.
input_record%EventType = KEY_EVENT
input_record%KeyEvent%bKeyDown = .TRUE.
input_record%KeyEvent%wRepeatCount = 1
input_record%KeyEvent%wVirtualKeyCode = INT(Z'51', WORD)
input_record%KeyEvent%wVirtualScanCode = INT(Z'51', WORD)
input_record%KeyEvent%AsciiChar = 'Q'
input_record%KeyEvent%dwControlKeyState = 0
api_result = WriteConsoleInput( &
console_input, &
input_record, &
1, &
LOC(events_written) )
! Wait for the child to terminate.
wait_event = WaitForSingleObject(process_info%hProcess, INFINITE)
api_result = CloseHandle(console_input)
api_result = CloseHandle(process_info%hProcess)
END PROGRAM peek_parent

Get elevated excel process

A few days ago, I had to run Excel with current user rights using ProcessStartInfo in order to user some addins that require admin or current user rights.
new Process{
StartInfo = new ProcessStartInfo{
Verb = "runas",
FileName = #"path/to/app.exe",
Arguments = "-officeexcel",
Domain = domain,
UserName = login,
Password = pa ss,
UseShellExecute = false,
}
}.Start();
Thus, i can't use anymore :
Marshal.GetActiveObject("Excel.Application")
to get excel instance. So i tried to use late binding with AccessibleObjectFromWindow() :
int hwnd = (int)FindWindow("XLMAIN", null);
if (hwnd != 0)
{
int hwndChild = 0;
EnumChildCallback cb = new EnumChildCallback(EnumChildProc);
EnumChildWindows(hwnd, cb, ref hwndChild);
if (hwndChild != 0)
{
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
ExcelWindow ptr;
int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out ptr);
if (hr >= 0)
{
using (UILanguageHelper fix = new UILanguageHelper())
{
return (Excel.Application)ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
}
}
}
}
return null;
It worked but the Excel Application I get has zero property and I can't invoke any methods !
I need to run some macros after getting elevated Excel instance !
What did i do wrong ?
You cannot interact with an application launched in elevated mode from a non-elevated one for security reasons.
In your case, I think the only way to achieve what you ask is to elevate your app (or another small app dedicated to this task) first, then, your elevated app, will, on its turn, run excel and perform the tasks you require. Excel will run in elevated mode.
The design pattern in this way is to run your application with the minimum rights required. And then elevate, specifying in the command line a "token" (whatsoever you can use to connect to your application via IPC/sockets).

C# STARTUPINFO Flags to show UI for a process started from a Service in XP

I'm launching a process from a windows service in XP, I'm just launching the process not trying to interact with it. The process starts but the UI does not show. I believe I need to set some flags in STARTUPINFO to make process visible, and hoping someone could show how and what flags to set.
sPath = #"C:\Windows\notepad.exe";
string Message = string.Empty;
// Variables
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
STARTUPINFO startInfo = new STARTUPINFO();
Boolean bResult = false;
IntPtr hToken = IntPtr.Zero;
try
{
// Logon user
bResult = LogonUser(
"Test",
"VirtualXP-23639",
"test",
LogonType.LOGON32_LOGON_INTERACTIVE,
LogonProvider.LOGON32_PROVIDER_DEFAULT,
out hToken
);
if (!bResult) { throw new Exception("Logon error #" + Marshal.GetLastWin32Error()); }
// Create process
startInfo.cb = Marshal.SizeOf(startInfo);
startInfo.lpDesktop = "winsta0\\default";
bResult = CreateProcessAsUser(
hToken,
null,
sPath,
IntPtr.Zero,
IntPtr.Zero,
false,
0,
IntPtr.Zero,
null,
ref startInfo,
out processInfo
);
if (!bResult)
{
Message = "Failed to Create Process on Desktop/Console. Code=" + Marshal.GetLastWin32Error().ToString();
Logging.LogError(Ascension.CM.Common.Enums.ApplicationModuleEnums.Service, Message, "Ascension.CM.ServiceWorker.ProcessLauncher.XpLaunchDesktopProcess", null);
}
}
finally
{
// Close all handles
CloseHandle(hToken);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
}
You'll at least need to allow the service to interact with the desktop, so in services.msc, click on your serivce an go to properties, then logon and select allow to interact with desktop..
I would suggest that you use the Process class in the .net framework.
Process.Start("notepad.exe")
This should have your desired effect.
Thanks guys, but I've found a solution.
I ended up using WTSQueryUserToken to get the current logged in user and then used DuplicateTokenEx to get a token that I used with CreateProcessAsUser to start the process.
For XP use session id 0 and for win7 use WTSGetActiveConsoleSessionId to get the current session Id.
This works fine with out having to use the "Allow to interact with Desktop" property.
Thanks

SHGetKnownFolderPath / Environment.GetFolderPath() returning wrong value for public documents

I got a somewhat strange error when trying to resolve the CommonDocuments directory.
It keeps resolving to the wrong directory, after the CommonDocuments directory has been redirected / moved to a new location using Windows Explorer (Properties->Path from the context menu).
a minimal working piece of code would be:
namespace CommonDocumentsTest
{
class Program
{
private static readonly Guid CommonDocumentsGuid = new Guid("ED4824AF-DCE4-45A8-81E2-FC7965083634");
[Flags]
public enum KnownFolderFlag : uint
{
None = 0x0,
CREATE = 0x8000,
DONT_VERFIY = 0x4000,
DONT_UNEXPAND= 0x2000,
NO_ALIAS = 0x1000,
INIT = 0x800,
DEFAULT_PATH = 0x400,
NOT_PARENT_RELATIVE = 0x200,
SIMPLE_IDLIST = 0x100,
ALIAS_ONLY = 0x80000000
}
[DllImport("shell32.dll")]
static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr pszPath);
static void Main(string[] args)
{
KnownFolderFlag[] flags = new KnownFolderFlag[] {
KnownFolderFlag.None,
KnownFolderFlag.ALIAS_ONLY | KnownFolderFlag.DONT_VERFIY,
KnownFolderFlag.DEFAULT_PATH | KnownFolderFlag.NOT_PARENT_RELATIVE,
};
foreach (var flag in flags)
{
Console.WriteLine(string.Format("{0}; P/Invoke==>{1}", flag, pinvokePath(flag)));
}
Console.ReadLine();
}
private static string pinvokePath(KnownFolderFlag flags)
{
IntPtr pPath;
SHGetKnownFolderPath(CommonDocumentsGuid, (uint)flags, IntPtr.Zero, out pPath); // public documents
string path = System.Runtime.InteropServices.Marshal.PtrToStringUni(pPath);
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(pPath);
return path;
}
}
}
Expected behaviour:
Output is D:\TestDocuments
Actual behaviour:
Output is C:\Users\Public\Documents
None; P/Invoke==>C:\Users\Public\Documents
DONT_VERFIY, ALIAS_ONLY; P/Invoke==>
NOT_PARENT_RELATIVE, DEFAULT_PATH; P/Invoke==>C:\Users\Public\Documents
The correct value is stored in the Windows Registry (HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Documents), but it is not returned by SHGetKnownFolderPath (or Environment.GetFolderPath)
OS: Windows 7 Professional x64
.NET Framework v4.0.30319
Application is compiled for x86 CPU
What I tried so far:
restarting my application
restarting the computer
calling Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments);
direct calls to Win32-API SHGetKnownFolderPath
EDIT 2
Steps to reproduce:
deactivate UAC on your computer [and restart!]
go to C:\Users\Public\
right click on "Public Documents" folder and select
Properties
select the 'Path' tab
click 'Move...' and select a (new) folder on drive D: called TestDocuments
click 'Apply'
accept to move all files to the new location start the minimal
application above
tl;dr: Behaviour is by design and only appears when you're running an assembly that was compiled for x86 CPUs on a x64 OS
Longer version:
Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments) accesses the 32-bit hive of the Windows Registry.
The actual path to the folder is stored in the 64-bit hive.
The issue has been forwarded to the Windows team and may be fixed in a future version of Windows.
For a bit more information see the Microsoft connect report
Workaround
create a console application with the following code and compile it for ANY CPU
static void Main(string[] args)
{
Console.WriteLine("{0}", Environment.GetFolderPath(System.Environment.SpecialFolder.CommonDocuments));
}
then call it from your main application:
Process proc = new Process();
ProcessStartInfo info = new ProcessStartInfo("myConsoleApp.exe");
// allow output to be read
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.UseShellExecute = false;
proc.StartInfo = info;
proc.Start();
proc.WaitForExit();
string path = proc.StandardOutput.ReadToEnd();
this will launch the ANY CPU executable, which only prints out the desired path to the standard output. The output then is read in the main application and you get the real path.

Categories