Run Powershell script files step by step in WPF (C#) - c#

I am a novice in C# and have a GUI in WPF where at some point it should automatically start executing Powershell scripts, but preferably one by one. As I see all methods run at once without waiting previous to finish, so my question is: what is better to use some kind of threads or async methods?
If I try to use task.WaitForExit(); then it freezes GUI, which is not acceptable. I have tried to use timer as well, but it looks like it doesn't see this it at all. Besides I have more ps1 files and several bat files, which need to be run one by one.
Could you tell please which method is better to use and how to combine it with active GUI in this case?
public partial class Start_deployment : Window
{
public Start_deployment()
{
InitializeComponent();
Run_scripts();
System.Windows.Application.Current.Shutdown();
}
public void Run_scripts()
{
var ps1File = #"C:\test\Install.ps1";
var startInfo = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -file \"{ps1File}\"",
UseShellExecute = false
};
var task = Process.Start(startInfo);
//task.WaitForExit();
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
}

Process.Start() returns Process instance, which has Exited event. Subscribe to that event to receive notification when it finished:
public partial class Start_deployment : Window
{
public Start_deployment()
{
InitializeComponent();
Run_scripts();
}
public void Run_scripts()
{
var ps1File = #"C:\test\Install.ps1";
var startInfo = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -file \"{ps1File}\"",
UseShellExecute = false
};
var proc = Process.Start(startInfo);
proc.Exited += OnProcessExited;
}
private void OnProcessExited(object sender, EventArgs eventArgs)
{
// todo, e.g.
// System.Windows.Application.Current.Shutdown();
}
}

Related

How to determine when the cmd.exe is finished

I have some code that I found here. It traps the output from cmd.exe and also sends input to it. I am using it (heavily modified) on a form in our application. It works well so we can hide cmd.exe from the users but allow this form to administrators. Other benefits include a favorites list, ect.
Here is some code. Note: I deleted 70% of the code to simplify it so there may be issues. If anyone wishes to run this code and they are having problems let me know and I will edit it and make sure it works. All it needs is a form with two textboxes, txtConsoleIn and txtConsoleOut plus a timer; tmrSelectedCommand.
public partial class frmCommandPrompt : Form
{
private bool CancelOutput;
private bool FirstTime = true;
private bool InternalCommand;
private string InternalCommandResponse;
private ProcessStartInfo psi;
private Process p;
private string SelectedCommand;
private bool SelectedCommandExecuteNow;
private delegate void InvokeWithString(string text);
public frmCommandPrompt()
{
InitializeComponent();
}
private void frmCommandPrompt_Shown(object sender, EventArgs e)
{
this.txtConsoleIn.Select();
}
private void tmrSelectedCommand_Tick(object sender, EventArgs e)
{
tmrSelectedCommand.Enabled = false;
if (SelectedCommand != string.Empty)
{
if (SelectedCommandExecuteNow)
{
ExecuteCommand(SelectedCommand);
}
else
{
txtConsoleIn.Text = SelectedCommand;
txtConsoleIn.SelectionStart = txtConsoleIn.Text.Length;
txtConsoleIn.Select();
}
SelectedCommand = string.Empty;
SelectedCommandExecuteNow = false;
}
}
// Sending console commands
private void txtConsoleIn_KeyUp(object sender, KeyEventArgs e)
{
string Command;
if ((int)e.KeyCode == (int)Keys.Return)
{
InternalCommand = false;
Command = txtConsoleIn.Text;
ExecuteCommand(Command);
txtConsoleIn.Clear();
}
}
private void Async_Data_Received(Object sender, DataReceivedEventArgs e)
{
if (this.IsHandleCreated)
{
this.Invoke(new InvokeWithString(Sync_Output), e.Data);
}
}
private void Sync_Output(string text)
{
if (!InternalCommand)
{
if (!CancelOutput)
{
OutputText(text);
}
}
else
{
if (text != String.Empty)
{
InternalCommandResponse = text;
InternalCommand = false;
}
}
}
private void ExecuteRecentCommand(string Command, bool ExecuteNow)
{
SelectedCommand = Command;
SelectedCommandExecuteNow = ExecuteNow;
tmrSelectedCommand.Enabled = true;
}
private void ExecuteCommand(string Command)
{
CancelOutput = false;
p.StandardInput.WriteLine(Command);
}
private void OutputText(string text)
{
txtConsoleOut.AppendText(text + Environment.NewLine);
txtConsoleOut.ScrollToCaret();
}
}
I would like to display the path, like prompt=$P$G. I can issue the command echo %cd% and trap the output while suppressing both that command and output. I
can then output this and add a ">." So far so good. Now the problem.
Sometimes commands can change the path. So I need to check the path again, but when? What if someone enters a long running command? How do I know when it is done? I would prefer to make this work as closely to the original cmd.exe as possible for ease of use.
is cd the only command that changes the path (besides a poorly written batch file)? If so I can check the path after that command. But I will have to use a label since a command like dir *.txt /s issued from the root can take some time and it will not only look stupid to throw the path in the middle of that output but will make the form less useable than not even displaying the path at all.
Or maybe have a timer starting another instance of cmd.exe running every 20 seconds or so just to get the path and display it in a label. Sounds like a lot of wasted cycles for little gain.
At this point the best bet seems to force the users to check the path themselves. I can add a button to get the path to make it easier but I would prefer a nicer solution.

Executing a method simultaneously to program window and stop it anytime

I need a functionality that will allow to execute method in a background and leave window responsive, but I need to have a possibility to stop or suspend it anytime. I know that threads are partly answer to my question, but unfortunately there is no way to stop thread from executing a time-absorbing block of code just like that. I had thoughts about process communication, but is it a good idea? Or maybe there is a way to terminate a thread unconditionally?
The only option that you have, if it's important that you can always stop the code at any point in it's execution, and when you can't have cooperative cancellation on the part of the worker, then you need to have a separate process. It is the most reliable way of stopping the execution of code in the manor you've described. It cannot be reliably done using Threads.
It seems that you're looking for BackgroundWorker .
You'll have to check in the second thread if the main thread is asking it to stop, and do so if needed.
Simple (tested) example:
public partial class Form1 : Form
{
BackgroundWorker w = new BackgroundWorker();
public Form1()
{
InitializeComponent();
w.WorkerSupportsCancellation = true;
w.DoWork += new DoWorkEventHandler(w_DoWork);
w.RunWorkerAsync();
}
void w_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10000000000; i++)
{
if (w.CancellationPending)
{
MessageBox.Show("Cancelled");
break;
}
//Do things...
}
}
private void button1_Click(object sender, EventArgs e)
{
w.CancelAsync();
}
}
EDIT
If you're speaking of an HTTP request, perhaps: HttpWebRequest.Abort? (Though see this answer.)
As stated in comments thread.Abort() is an option, but not advised. Rather you should be using thread.Interrupt() and the reasons for that are well detailed here.
The reason you should not kill a thread instantly is because it could cause a lock being set, but never unset due to the fact that the code was suddenly stopped with no path out. So if it locks code that you will need to reuse, there would be no way to unlock it from the previous call. Obviously, you could build around this, but I'm assuming you are using blocking code that isn't built from the ground up by you.
You could do it in a separate process and kill the process with much less risk, but then passing the data back and forth and the added mess becomes complicated.
Here is an example of using an external process to do this chuck and being able to kill the process will less risk.
public class Main
{
public Main()
{
//In use
DoCalculation calc = new DoCalculation();
calc.StartCalculation(123, 188, ReceivedResults);
//Cancel after 1sec
Thread.Sleep(1000);
calc.CancelProcess();
}
public void ReceivedResults(string result)
{
Console.WriteLine(result);
}
}
public class DoCalculation
{
private System.Diagnostics.Process process = new System.Diagnostics.Process();
private Action<string> callbackEvent;
public void StartCalculation(int var1, int var2, Action<string> CallbackMethod)
{
callbackEvent = CallbackMethod;
string argument = "-v1 " + var1 + " -v2 " + var2;
//this is going to run a separate process that you'll have to make and
//you'll need to pass in the argument via command line args.
RunProcess("calcProc.exe", argument);
}
public void RunProcess(string FileName, string Arguments)
{
SecurityPermission SP = new SecurityPermission(SecurityPermissionFlag.Execution);
SP.Assert();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = FileName;
process.StartInfo.Arguments = Arguments;
process.StartInfo.WorkingDirectory = "";
process.OutputDataReceived += ProcessCompleted;
process.Start();
}
public void CancelProcess()
{
if (process != null)
process.Kill();
}
private void ProcessCompleted(object sender, DataReceivedEventArgs e)
{
string result = e.Data;
if (callbackEvent != null)
{
callbackEvent.Invoke(result);
}
}
}
Can you give us any more details on exactly what you are doing? Perhaps there are better alternatives to this problem.

How do you start a batch file in the background and redirect its output?

I am trying to call a batch file from an application, but I want the command window to be hidden and the standard output to be redirected to one or more locations (as it is produced by the batch file).
My issue is that when the batch file is running the console is up and nothing displays; it's just up. When the task completes, the console closes. I want to get rid of the console (perhaps have it run in the back ground).
The other issue is that I am redirecting the output to a richtext box. If i redirect it to the console or text box, it just spits out all the results at once. I would like it to spit out line by line as it happens. Make sense?
The code is below:
//Declare and instantiate a new process component.
System.Diagnostics.Process process1;
process1 = new System.Diagnostics.Process();
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.FileName = "cmd.exe";
process1.StartInfo.Arguments = "<BATCHfILE>";
process1.Start();
string output = process1.StandardOutput.ReadToEnd();
rchsdtOut.Text = output;
Console.WriteLine(process1.StandardOutput.ReadToEnd());
process1.WaitForExit();
process1.Close();
This is how I would have done it. Hope I understood your question correctly:
Add:
process1.CreateNoWindow = true,
process1.OutputDataReceived += (s, e) => myMethod(e);
process1.BeginOutputReadLine();
And then a method
private void myMethod(DataReceivedEventArgs e)
{
//Do something with e.Data
}
To solve the Cross-thread operation issue mentioned in the comments. You will need to add this to your form class (before the functions begin):
private delegate void updateText(string str);
Then you need to add this:
private void update_richTextBox1(string value)
{
richTextBox1.Text += value;
}
And then in the myMethod function add:
richTextBox1.Invoke(new updateText(update_richTextBox1), new object[] { e.Data.ToString() });

Waiting for commands to be complete

I am working with a winform that runs a cmd in the background, redirecting input and output asynchronously.
Currently, the winform iterating through an array of commands, writing each to the cmd via the StreamWriter the StandardInput is redirected to. How can I force the loop to wait until the present command is complete in the cmd before writing the next line in?
EDIT: I took out all of my actual project code, and replaced it with this, a stripped down version of what I'm trying to do, only including components of my project relevant to my question.
public partial class Form1 : Form
{
public delegate void WriteToConsoleMethod(string text);
Process _process;
string[] _commands =
{
"echo hello world",
"echo my name is T.K.",
"echo Here is a list of commands"
};
public Form1()
{
InitializeComponent();
ProcessStartInfo processStartInfo = new ProcessStartInfo("cmd")
{
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
_process = Process.Start(processStartInfo);
_process.OutputDataReceived += new DataReceivedEventHandler(new DataReceivedEventHandler(DataReceived_EventHandler));
_process.ErrorDataReceived += new DataReceivedEventHandler(new DataReceivedEventHandler(DataReceived_EventHandler));
_process.BeginErrorReadLine();
_process.BeginOutputReadLine();
}
private void DataReceived_EventHandler(object sender, DataReceivedEventArgs e)
{
IAsyncResult result = this.BeginInvoke(new WriteToConsoleMethod(writeToConsole), new object[] { e.Data + Environment.NewLine });
this.EndInvoke(result);
}
private void writeToConsole(string output)
{
txtbxConsole.AppendText(output);
}
private void btnBegin_Click(object sender, EventArgs e)
{
foreach (string command in _commands)
{
_process.StandardInput.WriteLine(command);
// I want a way to pause here until the cmd has finished processing the command.
}
}
}
I don't think there is anything built-in that will support that. However you could send your own special command and then wait until you see this in the output for example ,
something like :
const string Separator= "---Command Completed--\xE3\xE2\xE1\xE0\xE3";
// Has to be something that won't occur in normal output.
volatile bool finished = false;
private void button1_Click(object sender, EventArgs e)
{
foreach (string command in _commands)
Run(command);
}
private void writeToConsole(string output)
{
if (output.IndexOf(Separator) >= 0)
finished = true;
else
richTextBox1.AppendText(output);
}
private void Run(string command)
{
finished = false;
_process.StandardInput.WriteLine(command);
_process.StandardInput.WriteLine("#echo " + Seperator);
while (!finished)
{
Application.DoEvents();
System.Threading.Thread.Sleep(100);
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
finished = true;
}
Assuming you are using System.Diagnostics.Process, then you probably need something like
ProcessStartInfo pi = new ProcessStartInfo(cmd);
pi.Arguments = ...
pi.WorkingDirectory = ...
Process myProcess = Process.Start(pi);
myProcess.WaitForExit();
I ended up solving this problem by wrapping my interaction with the command prompt into a separate class and instead of maintaining one prompt for all of the actions, I started up another prompt for each call. Then I take advantage of WaitForExit() to synchronize my threads.
After each command, I write in an exit command to close the process. I scan the output for exit calls, and when I find one, I use the context of that line to save the workspace so that the prompt for the next command will be made from the same working directory. I also had to hook up a DataRecievedEventHandler to parse out the header and exit calls before forwarding the EventHandlers to the winform.
The thing that's nagging me about this solution is that if the output of whatever process I'm running prints out exit, output scanner will behave as though it found the original exit. I employed the same solution sgmoore had in his answer - I write in exit [UNIQUE STRING] to the prompt, and scan the output for that, but I'm sure that's far from best practice.

cmd.exe style application written in C#

I am trying to get the results from any DOS-based application, effectively letting C# operate it as if it were a user.
I can get it to execute a command, and then show the output. The problem is knowing when the output has ended! For example, if I go start/run "cmd.exe", type "D:", then "cd D:\", and then "tree", it outputs my folder structure on the D drive, and then allows me to type my next command (only after it's finished printing the list).
However I can't find a way in code to get it to realise it's finished, and should allow the next command (basically when cmd.exe starts blinking your cursor).
public Process p = null;
private void Form1_Load(object sender, System.EventArgs e)
{
ProcessStartInfo procStarter = new ProcessStartInfo("cmd.exe");
procStarter.RedirectStandardOutput = true;
procStarter.RedirectStandardInput = true;
procStarter.UseShellExecute = false;
procStarter.CreateNoWindow = true;
p = Process.Start(procStarter);
}
private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
p.Close();
}
private void btnSend_Click(object sender, System.EventArgs e)
{
p.StandardInput.WriteLine("D:");
p.StandardInput.WriteLine(#"cd D:\");
txtOutput.Text = SendCommand(txtInput.Text);
}
private string SendCommand(string cmd)
{
p.StandardInput.WriteLine(cmd);
return p.StandardOutput.ReadToEnd();
}
In SendCommand(string cmd), if I run p.StandardOutput.ReadToEnd(), as per the code above, it hangs forever, presumably waiting for the application to close?
If I loop through p.StandardOutput.ReadLine(), it shows all the text (including the "D:\>" just before where the blinking cursor would then be, however it doesn't realise it's the end, calls ReadLine again, and hangs in a smiliar fashion to ReadToEnd. A dirty workaround would be to treat it as the end of the response if the current line ends with ">", however that falls apart if a line ends like that anywhere in the response.
I've tried looping through the stream character by character, and there's no special character sent at the end.
Any ideas?
Note: My ultimate goal is to provide a light library I can use for executing any DOS executable (which may require several typed commands passed to it, not just the one off arguments passed via command line on opening), parsing the results returned with a regex template, and returning the extracted result. I figured if I can effectively re-implement the cmd.exe in a windows application, then such a library will be possible.
Thanks,
Lee
I suspect that your approach doesn’t work. cmd.exe is not going to communicate to you via StandardOutput when or whether the command you ran has finished or not. (I should point out though that this doesn’t stop you from running multiple commands. You can probably just send the command lines and don’t actually need to wait for it to finish.)
Perhaps a more suitable approach might be not to use cmd.exe at all. Instead, use Process.Start() to run each individual command. Then you can use StandardOutput.ReadToEnd() and it will finish when the process is finished, and you can run the next one.
I agree with Timwi, But see if something like below helps
ProcessStartInfo procStarter = new ProcessStartInfo("cmd.exe");
procStarter.RedirectStandardOutput = true;
procStarter.RedirectStandardInput = true;
procStarter.UseShellExecute = false;
procStarter.CreateNoWindow = true;
procStarter.WorkingDirectory = #"D:\";
procStarter.Arguments = "/C dir";
Process p = Process.Start(procStarter);
string output = p.StandardOutput.ReadToEnd();
/C command line to cmd.exe will terminate cmd.exe once the work is done. You can also use p.Exited (exited event) to know when it happens.
However it will not keep the cmd.exe always running. But do you really need to keep it running?
If you're looking for 'how to wait till the spawned process terminates', Process.WaitForExit is what should do the trick.
You could spawn a new shell for each "command".
About a year ago I wrote a telnet server for windows that allowed the remote user to issue commands against cmd.exe. Maybe you can use it as a starting point for your own project.
Get the code on my blog
By reading the output asynchronous I have gotten this to work (aleast almost) like you described:
public Process p = null;
private void Send_Click(object sender, System.EventArgs e)
{
p.StandardInput.WriteLine("D:");
p.StandardInput.WriteLine(#"cd D:\");
p.StandardInput.WriteLine(txtInput.Text);
}
private void Form1_Load_1(object sender, EventArgs e)
{
ProcessStartInfo procStarter = new ProcessStartInfo("cmd.exe");
procStarter.RedirectStandardOutput = true;
procStarter.RedirectStandardInput = true;
procStarter.UseShellExecute = false;
procStarter.CreateNoWindow = true;
p = Process.Start(procStarter);
p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
p.BeginOutputReadLine();
}
void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
addTextToOutput(e.Data);
}
private void addTextToOutput(string text)
{
if (txtOutput.InvokeRequired)
{
addTextCallback cb = new addTextCallback(addTextToOutput);
this.Invoke(cb, new Object[] { text });
}
else
{
txtOutput.Text += text+ System.Environment.NewLine;
}
}
delegate void addTextCallback(String text);
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
p.Close();
}

Categories