I need to automate an command line application. It asks the user to enter a password. All my approches to send the password via STDIN failed. Now I am trying to do this with an wrapper-programm using .NET.
I am starting the application creating a new process, setting the StartInfo
-properties and then start the process:
Dim app_path As String
Dim app_args As String
Dim myProcess As Process = New Process()
myProcess.StartInfo.FileName = app_path
myProcess.StartInfo.Arguments = app_args
myProcess.StartInfo.UseShellExecute = False
myProcess.Start()
I did try to use the StartInfo.RedirectStandardInput
Property but with no success.
Now I came accross the WriteConsoleInput
function from the kernel32.dll
that I included like this:
Declare Function WriteConsoleInput Lib "kernel32.dll" Alias "WriteConsoleInputA" (ByVal hConsoleInput As Integer, ByVal lpBuffer As String, ByVal nNumberOfCharsToWrite As Integer, ByRef lpNumberOfCharsWritten As Integer) As Boolean
I can get the handle of the process via the myProcess.Handle
property. But sending input to the input-buffer using this way was also not possible.
I found those questions but they did not help:
How do I write ‘PAGE DOWN’ into the console input buffer? (1475353)
Java - passing input into external C/C++ application (1421273)
Controlling a Windows Console App w/ stdin pipe (723424)
Using StraceNtX.exe I got this output for the moment the app is waiting for input:
[T4024] GetConsoleMode(f, 12d35c, 12d3af, 77bff894, ...) = 1
[T4024] SetConsoleMode(f, 0, 12d3af, 77bff894, ...) = 1
[T4024] ReadConsoleInputA(f, 12d348, 1, 12d360, ...) = 1
Can anyone tell me, what else to try or how to do the above the right way?
Thanks!
Based on Tim Robinsons answere I've got this code now, but it does not work:
myProcess = New Process()
myProcess.StartInfo.FileName = app_path
myProcess.StartInfo.Arguments = app_args
myProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal
myProcess.StartInfo.UseShellExecute = False
myProcess.Start()
' Wait for process requesting passwort input
System.Threading.Thread.Sleep(3000)
Dim len As Integer
len = 0
Dim handle As Integer
handle = GetStdHandle(STD_INPUT_HANDLE)
WriteConsoleInput(handle, "Test", 4, len)
My programm is an commandline application that should act as an wrapper.
The input is send but in a way that it is not typed into the password field but that below the password field a new promt is shown (without even showing the input).
Tim, can you give me an example?
Sorry for my late reply, been very busy.
Here is an full code example, stripped down from my code. I hope I did not delete something crucial.
Private Sub runWaitInput(ByVal exe As String, ByVal parameter As String, ByVal trigger As String, ByVal input As String)
' runs an process, waits for a certain string in stdout and then enters text '
Dim proc As Process
Dim stdOut As StreamReader
Dim ch As Int32
Dim buffer As String = ""
Dim InputRecords(0) As KeyEventStruct
' create process '
proc = New Process()
proc.StartInfo.FileName = exe
proc.StartInfo.Arguments = parameter
proc.StartInfo.UseShellExecute = False
proc.StartInfo.RedirectStandardOutput = True
proc.Start()
' redirect stdOut '
stdOut = proc.StandardOutput
While Not proc.HasExited
' read character '
ch = stdOut.Read()
Console.Write(Convert.ToChar(ch))
buffer = buffer & Convert.ToChar(ch)
' read output and check for trigger-text '
If buffer.LastIndexOf(trigger) <> -1 Then
buffer = ""
InputRecords(0) = New KeyEventStruct
InputRecords = generateInputRecord(input & Convert.ToChar(13))
WriteConsoleInput(STD_INPUT_HANDLE, InputRecords, InputRecords.Count(), New Integer)
End If
End While
Console.Write(stdOut.ReadToEnd())
End Sub
Function generateInputRecord(ByVal str As String) As KeyEventStruct()
Dim ret(str.Count() - 1) As KeyEventStruct
For i = 0 To str.Count - 1 Step 1
With ret(i)
.EventType = 1
.bKeyDown = True
.uChar.AsciiChar = Convert.ToInt32(str(i))
.dwControlKeyState = 0
.wRepeatCount = 1
.wVirtualKeyCode = 0
.wVirtualScanCode = 0
End With
Next
Return ret
End Function
It uses the following module:
Imports System.Runtime.InteropServices
Module ConsoleUtils
<Flags()> Public Enum ControlKeyState As Integer
RightAltPressed = &H1
LeftAltPressed = &H2
RightCtrlPressed = &H4
LeftCtrlPressed = &H8
ShiftPressed = &H10
NumLockOn = &H20
ScrollLockOn = &H40
CapsLockOn = &H80
EnhancedKey = &H100
End Enum
<StructLayout(LayoutKind.Explicit)> Public Structure CHAR_UNION
<FieldOffset(0)> Public UnicodeChar As Short
<FieldOffset(0)> Public AsciiChar As Byte
End Structure
<DllImport("kernel32", EntryPoint:="WriteConsoleInputA", CharSet:=CharSet.Auto, SetLastError:=True, ThrowOnUnmappablechar:=True)> _
Public Function WriteConsoleInput( _
ByVal hConsoleInput As IntPtr, _
ByVal lpBuffer() As KeyEventStruct, _
ByVal nLength As Integer, _
ByRef lpNumberOfEventsWritten As Integer _
) As Boolean
End Function
<DllImport("KERNEL32.DLL", EntryPoint:="GetStdHandle", SetLastError:=False, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
Public Function GetStdHandle( _
ByVal nStdHandle As Integer _
) As Integer
End Function
<StructLayout(LayoutKind.Sequential)> Public Structure KeyEventStruct
Public EventType As Short
<MarshalAs(UnmanagedType.Bool)> Public bKeyDown As Boolean
Public wRepeatCount As Short
Public wVirtualKeyCode As Short
Public wVirtualScanCode As Short
Public uChar As CHAR_UNION
Public dwControlKeyState As ControlKeyState
End Structure
Public ReadOnly STD_OUTPUT_HANDLE As IntPtr = New IntPtr(GetStdHandle(-11))
Public ReadOnly STD_INPUT_HANDLE As IntPtr = New IntPtr(GetStdHandle(-10))
Public ReadOnly STD_ERROR_HANDLE As IntPtr = New IntPtr(GetStdHandle(-12))
End Module
With this function you can start the process and wait for a line of output (i.e. "password:"). Then the function will enter the provided text followed by an return.
I hope this helps!
Regards
sc911
WriteConsoleInput
expects a console handle, not a process handle. Your problem is how to get hold of this console handle.
If your process is a console app, and you don't redirect anything when starting the child process, then the child process will be attached to your own process's console. In which case, you may be able to:
- Start the child process with redirection turned off
- Call
GetStdHandle(STD_INPUT_HANDLE)
to get hold of your own console handle
- Pass that console handle to
WriteConsoleInput
If you have a GUI app you can assign yourself a console using AllocConsole
.
Edit: I didn't notice at first, but that's not the right definition for WriteConsoleInput
. It takes an array of INPUT_RECORD
, not a string. My own experiment with runas.exe works OK once I copied the function prototype from pinvoke.net.