问题
System.Diagnostics.Process exposes a StreamWriter named StandardInput, which accepts only characters as far as I know.
But I need to send keystrokes as well, and some keystrokes don't map well to characters.
What should I do?
回答1:
You are mixing input streams with control signals. A console process has a default input stream which you can control with the StandardInput, as you already know. But Ctrl-C and Ctrl-Break are not characters sent to the process through this stream, but instead they are instead control signals that the process receives using the registered signal handlers, see CTRL+C and CTRL+BREAK Signals:
By default, when a console window has the keyboard focus, CTRL+C or CTRL+BREAK is treated as a signal (SIGINT or SIGBREAK) and not as keyboard input.
To send fake signals to a process you can use GenerateConsoleCtrlEvent and send either CTRL_C_EVENT
or CTRL_BREAK_EVENT
. This API has no .Net equivalent, so you have to PInvoke it.
To use it from .NET you simply need to include the function definition:
const int CTRL_C_EVENT = 0;
const int CTRL_BREAK_EVENT = 1;
[DllImport("kernel32.dll")]
static extern bool GenerateConsoleCtrlEvent(
uint dwCtrlEvent,
uint dwProcessGroupId);
回答2:
There's an input Simulator found here on Codeplex which may do just the job for you. I am working on a sample code and will post back here shortly, bear in mind the Input Simulator is similar to what was found in the link supplied by Remus...
Edit: I have found that there is a limitation with this, you can definitely get away with the typical System.Windows.Forms.SendKeys.Send
method, it does work effectively! , but, the process must have
- No redirections of the streams
- Cannot be hidden window (this is where it will fail, since the window's handle is nowhere to be seen, no way of bringing it to the foreground to make it active!)
- A window showing the process for this to be effective!
In your case, it's a matter of finding the window, set it active via pinvoke 'SetForegroundWindow', and send the sequences ^{BREAK}
which sends the Ctrl+Break signal to the process which does work very well (especially if the process is a command line program/batch file). Here's an article on CodeProject that does this exactly and mirrors the SendKeys...I have yet to paste some code into this to demonstrate ....
Edit#2: Actually I am quite surprised...as this code will show (proof of concept)...it is using:
- InputSimulator (as mentioned previously)
- A windows form that consists of a button, when the form is loaded it automatically runs the class. Upon clicking the button, it posts a ctrl-break to the hidden process
- The output stream is indeed redirected and is a hidden window.
- The weird thing, is the output is being captured but does not show the results in the debug window, in real-time that is, it is buffered (I guess) until the process terminates, the whole output is shown...
- I cheated a bit on the
FindWindow
API call, because I knew the window's title was and was somehow, able to bring it to the foreground, and using the InputSimulator to send the keystrokes to it...or use the traditional plain oldSendKeys
function...the reason I had theThread.Sleep
is to ensure that the keystrokes are sent in order to be 'pushed into the keyboard queue of the "active foreground window", which despite that, is hidden' - I used the 'netstat -e 5' command to loop forever, refreshing the results every 5 seconds until it receives a 'Ctrl+C' to break the infinite loop.
public partial class Form1 : Form
{
private TestNetStat netStat = new TestNetStat();
public Form1()
{
InitializeComponent();
using (BackgroundWorker bgWorker = new BackgroundWorker())
{
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
bgWorker.RunWorkerAsync();
}
}
void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("BGWORKER ENDED!");
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
netStat.Run();
}
void btnPost_Click(object sender, EventArgs e)
{
netStat.PostCtrlC();
System.Diagnostics.Debug.WriteLine(string.Format("[{0}] - {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), this.netStat.OutputData.Replace(Environment.NewLine, "")));
}
}
public class TestNetStat
{
private StringBuilder sbRedirectedOutput = new StringBuilder();
//
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32")]
public static extern int SetForegroundWindow(IntPtr hwnd);
public string OutputData
{
get { return this.sbRedirectedOutput.ToString(); }
}
public void PostCtrlC()
{
IntPtr ptr = FindWindow(null, @"C:\Windows\System32\netstat.exe");
if (ptr != null)
{
SetForegroundWindow(ptr);
Thread.Sleep(1000);
WindowsInput.InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.CANCEL);
// SendKeys.Send("^{BREAK}");
Thread.Sleep(1000);
}
}
public void Run()
{
System.Diagnostics.ProcessStartInfo ps = new System.Diagnostics.ProcessStartInfo();
ps.FileName = "netstat";
ps.ErrorDialog = false;
ps.Arguments = "-e 5";
ps.CreateNoWindow = true;
ps.UseShellExecute = false;
ps.RedirectStandardOutput = true;
ps.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
{
proc.StartInfo = ps;
proc.EnableRaisingEvents = true;
proc.Exited += new EventHandler(proc_Exited);
proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();
}
}
void proc_Exited(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("proc_Exited: Process Ended");
}
void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (e.Data != null)
{
this.sbRedirectedOutput.Append(e.Data + Environment.NewLine);
System.Diagnostics.Debug.WriteLine("proc_OutputDataReceived: Data: " + e.Data);
}
}
}
Nitpicky aside, I know that the netStat
is running off the 'BackgroundWorker' thread, and I directly invoked the 'PostCtrlC' method from the main GUI thread...this is pedantic as a proof-of-concept code, but it does show that it needs to implement 'ISynchronizeInvoke' to make it thread-safe, that aside...it does indeed work.
回答3:
Have you seen this great tool - AutoIt. This is a scripting tool. To send a backspace you would use Send("{BACKSPACE}")
This is a great tool and it can help in automating many manual clicks/double-clicks/etc.
Is this relevant to your question ?
回答4:
If you have a Windows Forms window that you can send the keys to, then SendKeys might be an appropriate solution.
For pressing backspace and Ctrl+C, that should be
SendKeys.Send("{BACKSPACE}^C");
来源:https://stackoverflow.com/questions/2274668/how-to-send-keys-instead-of-characters-to-a-process