Updating ShowMessageAsync Progress Value From "External" Method - c#

I have a WPF C# app using the MahApps.Metro UI resources. I'm trying to tie the progress dialog control to a method call from another class and manually increment the progress of the progress bar as the method is stepped through.
I'm having trouble figuring out how I would program "We're at checkpoint 1 of method X - report back 25% complete. Now we're at checkpoint 2 of method X - report back 50% complete."
The method in MainWindow.xaml.cs that initiates the ShowMessageAsync progress bar is shown here:
private async void installThirdPartyUpdatesButton_Click(object sender, RoutedEventArgs e)
{
var app = ((FrameworkElement)sender).DataContext as Tuple<ThirdPartyUpdate.InstalledApplicationFromRegistryScan, ThirdPartyUpdate.ManifestRequiredApplication, ThirdPartyUpdate.RequiredApplicationState>;
Button ThirdPartyInstallButton = sender as Button;
ThirdPartyInstallButton.IsEnabled = false;
var controller = await this.ShowProgressAsync("Please wait...", "Preparing update installation...");
await Task.Delay(2000);
controller.SetCancelable(false);
if (app != null)
{
ThirdPartyUpdate.thirdPartyApplicationInstallWorkflow(app);
}
controller.SetProgress(.25);
controller.SetMessage("Closing dependant processes...");
await Task.Delay(2000);
controller.SetProgress(.50);
controller.SetMessage("Uninstalling legacy version...");
await Task.Delay(2000);
controller.SetProgress(.75);
controller.SetMessage("Installing new version...");
await Task.Delay(2000);
controller.SetProgress(.100);
await controller.CloseAsync();
await this.ShowMessageAsync("Success!", "Update installed successfully.");
}
The controller.SetProgress entries I have there presently are for illustrative purposes only to show my desired outcome. They render the following:
What I really need is for this method:
ThirdPartyUpdate.thirdPartyApplicationInstallWorkflow(app);
to report back its progress as it hits certain sections of code. Here are the contents of that method in case it helps:
public static int thirdPartyApplicationInstallWorkflow(Tuple<ThirdPartyUpdate.InstalledApplicationFromRegistryScan, ThirdPartyUpdate.ManifestRequiredApplication, ThirdPartyUpdate.RequiredApplicationState> thirdPartyApp)
{
// 1. Close listed process
// 2. Execute explicit uninstall
// 3. Execute WMI uninstall
// 4. Execute install/command
writeEvent(EventLogEntryType.Information, "Beginning execution of: " + thirdPartyApp.Item2.Name + " job.", "Third Party Update");
//Close processes prior to upgrade if needed
closeProcesses(thirdPartyApp);
//Execute explicit uninstall if needed
uninstallExplicit(thirdPartyApp);
//Execute WMI uninstall if needed
uninstallWMI(thirdPartyApp);
//Execute install
if (!string.IsNullOrEmpty(thirdPartyApp.Item2.InstallString))
{
//String cleanup on comma placement so that a few different input styles are valid
string cleanedstrInstall = thirdPartyApp.Item2.InstallString.Replace(", ", ",").Replace(" ,", ",").Replace(" , ", ",");
List<string> strInstall = cleanedstrInstall.Split(',').ToList<string>();
int installExitCode = 0;
DateTime timeLaunched;
DateTime timeCompleted;
foreach (var install in strInstall)
{
ProcessStartInfo procStartInfo = new ProcessStartInfo("cmd.exe", "/c " + thirdPartyApp.Item2.InstallString);
// The following commands are needed to redirect the standard output.
// This means that it will be redirected to the Process.StandardOutput StreamReader.
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.UseShellExecute = false;
// Do not create the black window.
procStartInfo.CreateNoWindow = true;
// Now we create a process, assign its ProcessStartInfo and start it
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
writeEvent(EventLogEntryType.Information, "Attempting to launch upgrade: " + thirdPartyApp.Item2.InstallString, "Third Party Update");
timeLaunched = DateTime.UtcNow;
proc.Start();
string stderror = proc.StandardError.ReadToEnd();
proc.WaitForExit();
timeCompleted = DateTime.UtcNow;
if (proc.ExitCode == 0 || proc.ExitCode == 3010)
{
writeEvent(EventLogEntryType.Information, "Successfully completed upgrade from: " + thirdPartyApp.Item2.InstallString, "Third Party Update");
}
}
}
return 0;
}

Just make a delegate to the SetProgress function on the controller.
var controller = await this.ShowProgressAsync("Please wait...", "Progress message");
Define the following delegate somewhere:
public delegate void UpdateProgressFunc(double value);
Change your method thirdPartyApplicationInstallWorkflow to add this delegate as a method parameter:
public static void thirdPartyApplicationInstallWorkflow(App app, UpdateProgressFunc UpdateProgress);
And then from within this function, at the different stages, just call:
UpdateProgress(PERCENT);

Related

Can not access controls in a Windows Form inside a Process using Invoke if required

I need to execute a Process (CMD executing a script) called from within a Windows Forms app, and update some controls when getting Output from the process.
I am using Windows Forms in .NET Core 5.0.
I have been researching a bit and found the example Extension method InvokeIfRequired,
public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
if (control.InvokeRequired) control.Invoke(action);
else action();
}
Though at first I tried just using that same structure but for each control I needed to update.
This is the code of the function I am calling the process from, and trying to update the controls from:
private int StartGeneration()
{
int result = 0;
using (Process cmd = new Process())
{
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.WorkingDirectory = #"C:\git\pub";
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.CreateNoWindow = true;
cmd.Start();
cmd.Exited += (sender, e) =>
{
Console.WriteLine("Exiting from CMD process");
};
cmd.OutputDataReceived += (sender, e) =>
{
lbResult.InvokeIfRequired(new MethodInvoker(delegate
{
lbResult.Text = e.Data;
}));
if (e.Data.ToLower().Contains("exiting"))
{
// The script has ended
lbResult.InvokeIfRequired(new MethodInvoker(delegate
{
lbResult.ForeColor = System.Drawing.Color.Aqua;
lbResult.Text = "Done";
}));
rtbResult.InvokeIfRequired(new MethodInvoker(delegate
{
rtbResult.Visible = true;
rtbResult.Text = cmd.StandardOutput.ReadToEnd();
Size = new System.Drawing.Size(Width, Height + 460);
}));
cmd.StandardInput.Flush();
cmd.StandardInput.Close();
cmd.Close();
}
};
cmd.BeginOutputReadLine();
cmd.StandardInput.WriteLine(BuildPublishingScriptCommand());
cmd.WaitForExit();
}
return result;
}
And the BuildPublishingScriptCommand() function is just a simple function that returns a string, containing the script I need to run in the CMD.
The problem comes when the code gets in the OutputDataReceived event of the process and I try to use the InvokeIfRequired method (or any Invoke method in general).
The application just keeps running infinitely but nothing updates. Though the script completes successfully because I can confirm the work is done (creates some DLLs).
If I debug, I get until the call to InvokeIfRequired and then I lose control to debugging.
It does not give any exception.
I have not tried anything else.

What is breaking my Process.StartInfo.OutputDataReceived callbacks prematurely?

The following problem occurs on .NET Framework v3.5. Don't know if it applies to v4*.
To capture stdout for some processes I've successfully used p.StartInfo.UseShellExecute = false; and p.StartInfo.RedirectStandardOutput = true; and an event handler hooked to p.StartInfo.OutputDataReceived+=...;. Then I call p.Start(); then p.BeginOutputReadLine(); and then p.WaitForExit();
All is well so far. I get all stdout on the event handler, line by line, as expected.
I had to introduce a timeout instead of WaitForExit() because some processes unpredictably trigger requests for input at stdin (e.g. are you sure? [y/n]) leading to a deadlock where I wait forever and so do they.
The first thing I tried is changing to while (!p.HasExited && DateTime.Now < timeoutmom) p.WaitForExit(200); where timeoutmoment is 2 minutes after proc.Start(). This is when I ran into problems. Very consistently, the code works for calls that produce up to a few hundred lines of stdout but it breaks for one call that produces about 7500 lines. What happens is the proc.WaitForExit(200); thread exits the while when my OutputDataReceived event handler was called for only ~ 7300 lines (this number is again very consistent it varies by only +/- 1 between tests) and the handler is not called anymore for the rest of the stdout lines so I lose them.
Strangely, the problem doesn't appear if I avoid WaitForExit(200) and instead use while (!p.HasExited && DateTime.Now < timeoutmom) System.Threading.Thread.Sleep(1000); (not shown in the code below). When I posted the question I was pretty sure the problem was avoided using Sleep(1000) but I was wrong. It worked a few dozen times like that and then it didn't, it started behaving just like when I checked WaitForExit(200).
I now speculate that the reasons for this problem are (1) I take too long to process each OutputDataReceived callback. I noticed the problem was aggravated when I added a conditional breakpoint in the event handler which lengthened the method execution by a lot. I can now reproduce the problem by simply adding 3x Debug.WriteLines without the conditional breakpoint; PLUS (2) my context is somehow corrupted by me accessing HasExited / WaitForExit(200) before the system had a chance to perform all the callbacks on my event handler. I now do a blind System.Threading.Thread.Sleep(30000) just after p.Start() and before accessing any p.* method and I get all the callbacks. When I used WaitForExit() it seemed I can take however much time I want to process every callback and I would still get them all.
Can someone make more sense of this?
Code:
private int _execOsProc(
ProcessStartInfo Psi
, string SecInsensArgs
, TextWriter ExtraStdOutAndErrTgt
, bool OutputToExtraStdOutOnly
)
{
var pr = new Process();
pr.StartInfo = Psi;
pr.StartInfo.UseShellExecute = false;
pr.StartInfo.RedirectStandardOutput = pr.StartInfo.RedirectStandardError = true;
pr.StartInfo.CreateNoWindow = true;
var ol = new DataReceivedEventHandler(this._stdOutDataReceived);
var el = new DataReceivedEventHandler(this._stdErrDataReceived);
pr.OutputDataReceived += ol;
pr.ErrorDataReceived += el;
try
{
__logger.Debug("Executing: \"" + pr.StartInfo.FileName + "\" " + SecInsensArgs);
if (ExtraStdOutAndErrTgt == null)
{
this.__outputToExtraStdOutOnly = false;
}
else
{
this.__extraStdOutAndErrTgt = ExtraStdOutAndErrTgt;
this.__outputToExtraStdOutOnly = OutputToExtraStdOutOnly;
}
pr.Start();
pr.BeginOutputReadLine();
pr.BeginErrorReadLine();
var startmom = DateTime.Now;
var timeoutmom = startmom.AddMinutes(2);
while (!pr.HasExited && DateTime.Now < timeoutmom) pr.WaitForExit(200);
pr.CancelOutputRead();
pr.CancelErrorRead();
if (pr.HasExited)
{
__logger.Debug("Execution finished with exit status code: " + pr.ExitCode);
return pr.ExitCode;
}
else
{
__logger.Debug("Timeout while waiting for execution to finish");
pr.Kill();
return -100;
}
}
finally
{
pr.OutputDataReceived -= ol;
pr.ErrorDataReceived -= el;
if (this.__extraStdOutAndErrTgt != null)
{
this.__extraStdOutAndErrTgt = null;
this.__outputToExtraStdOutOnly = false;
}
}
}
private void _stdOutDataReceived(
object sender
, DataReceivedEventArgs e
)
{
string rdata = string.IsNullOrEmpty(e.Data) ? "" : e.Data.Trim();
if (!this.__outputToExtraStdOutOnly) __logger.Debug("SO: " + rdata);
if (this.__extraStdOutAndErrTgt != null)
{
lock (this.__extraStdOutAndErrTgt)
{
try
{
this.__extraStdOutAndErrTgt.WriteLine(rdata);
this.__extraStdOutAndErrTgt.Flush();
}
catch (Exception exc)
{
__logger.Warn(
"WARNING: Error detected but ignored during extra stream write"
+ " on SODR. Details: " + exc.Message
, exc
);
}
}
}
}
private void _stdErrDataReceived(
object sender
, DataReceivedEventArgs e
)
{
string rdata = string.IsNullOrEmpty(e.Data) ? "" : e.Data.Trim();
if (!__outputToExtraStdOutOnly) __logger.Debug("SE: " + rdata);
if (this.__extraStdOutAndErrTgt != null)
{
lock (this.__extraStdOutAndErrTgt)
{
try
{
this.__extraStdOutAndErrTgt.WriteLine(rdata);
this.__extraStdOutAndErrTgt.Flush();
}
catch (Exception exc)
{
__logger.Warn(
"WARNING: Error detected but ignored during extra stream write"
+ " on SEDR. Details: " + exc.Message
, exc
);
}
}
}
}
I'm not sure if it will solve the problem, but it is too long to post it in the comment.
MSDN says about Process.HasExited:
When standard output has been redirected to asynchronous event
handlers, it is possible that output processing will not have
completed when this property returns true. To ensure that asynchronous
event handling has been completed, call the WaitForExit() overload
that takes no parameter before checking HasExited.
and about WaitForExit():
This overload ensures that all processing has been completed,
including the handling of asynchronous events for redirected standard
output. You should use this overload after a call to the
WaitForExit(Int32) overload when standard output has been redirected
to asynchronous event handlers.
It indicates, that call to WaitForExit() with no parameters should solve the problem. Something like:
var startmom = DateTime.Now;
var timeoutmom = startmom.AddMinutes(2);
while (!pr.HasExited && DateTime.Now < timeoutmom)
pr.WaitForExit(200);
if (pr.HasExited)
{
WaitForExit();//Ensure that redirected output buffers are flushed
pr.CancelOutputRead();
pr.CancelErrorRead();
__logger.Debug("Execution finished with exit status code: " + pr.ExitCode);
return pr.ExitCode;
}
else
{
pr.CancelOutputRead();
pr.CancelErrorRead();
__logger.Debug("Timeout while waiting for execution to finish");
pr.Kill();
return -100;
}

cant get process error output using process.ErrorDataReceived c#

I've built Form App that I use for some time , Now I want to Catch the StandardError of my process as well as its standartOutput
I've looked at answers in SO and MSDN and yet and cant get it right
My code :
public void RunProcess(string FileName, string Arguments,, bool IsPrintOutput = true)
{
process = new Process();
process.ErrorDataReceived += new DataReceivedEventHandler(OnDataReceivedEvent);
if (IsPrintOutput) process.OutputDataReceived += new DataReceivedEventHandler(OnDataReceivedEvent);
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = FileName;
process.StartInfo.Arguments = Arguments;
if (EventWhenExit)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(myprocess_Exited);
}
process.Start();
process.BeginOutputReadLine();
//run polling on stored logs to print them to screen
PollingService();
}
I've check it with Iperf and I see that when I run it with correct argument I get correct output
but when I just send it with out any argumnet I see that with cmd I get
C:\>iperf.exe
Usage: iperf [-s|-c host] [options]
Try `iperf --help' for more information.
And my App I get Nothing !
what am I missing here ?
Thanks
You can stop reading here ! If you want to see the details of inner method continue below :
private void OnDataReceivedEvent(object sender, DataReceivedEventArgs e)
{
string ProcessOutput = e.Data;
ProcessLog.Add(e.Data);
}
private void PollingService()
{
var T = new Thread (()=>
{
while (true /* ProcessRunning*/)
{
if (ProcessLogIndex < ProcessLog.Count)
{
lock (this)
{
var tempList = ProcessLog.GetRange(ProcessLogIndex, ProcessLog.Count - ProcessLogIndex);
ProcessLogIndex = ProcessLog.Count;
foreach (var ToSend in tempList)
{
onDataOutputFromProcess(this, ToSend, sProcessNameID.ToString());
}
}
}
Thread.Sleep(400);
}
});
T.IsBackground = true;
T.Start();
}
I don't see a call to BeginErrorReadLine() anywhere in the code you posted. If you don't call that method, then the Process class won't actually redirect the stderr to your event handler.
I believe the above is the issue, but if you are actually calling that somewhere (and just didn't show it), then it is worth considering that there are some strange console programs out there that don't actually used stderr (or stdout) for error output. Instead, they write directly to the console window or some other non-standard mechanism. In those cases, you won't be able to receive the error output by redirecting stderr.
You can identify those programs by redirecting their output at the command like with e.g. iperf.exe 2> foo.txt. The stderr file handle is 2, and so that syntax redirects that file handle to a file named foo.txt. If the file is empty and you see errors on the screen, then the program is one of those strange programs.
But really, I think you probably just forgot to call BeginErrorReadLine(). :)

C# process start firefox.exe fires the exited immediately after launch

Similar to
Process.Start("IEXPLORE.EXE") immediately fires the Exited event after launch.. why?
The -nomerge option does not seem to work for firefox.
Updated:
Here's a preview of the C# code inside a console app
static bool exitCalled = false;
static string baseUrl = <some url to display in the browser>;
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "Firefox.exe"
Arguments = " -url " + baseUrl + " -no-remote -P MyProfile "
}
}
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(delegate(Object o, EventArgs e)
{
// process has exited
Console.WriteLine("Exited event called");
Console.ReadLine();
exitCalled = true;
}
process.Start();
while (!exitCalled)
{
Thread.Sleep(100);
}
Running this piece of code displays the "Exited event called" message before the browser is invoked.
That because -nomerge is an IE specific program argument, for firefox you need to use -no-remote. You will also need to pass it the -P program argument as well since it is not recommend to start another firefox process with the default profile. Refer to the following link on starting a new FireFox instance:
http://kb.mozillazine.org/Opening_a_new_instance_of_Firefox_with_another_profile

handle exit event of child process

I have a console application and in the Main method. I start a process like the code below, when process exists, the Exist event of the process is fired but it closed my console application too, I just want to start a process and then in exit event of that process start another process.
It is also wired that process output is reflecting in my main console application.
Process newCrawler = new Process();
newCrawler.StartInfo = new ProcessStartInfo();
newCrawler.StartInfo.FileName = configSection.CrawlerPath;
newCrawler.EnableRaisingEvents = true;
newCrawler.Exited += new EventHandler(newCrawler_Exited);
newCrawler.StartInfo.Arguments = "someArg";
newCrawler.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
newCrawler.StartInfo.UseShellExecute = false;
newCrawler.Start();
You have to call newCrawler.WaitForExit() in order to stay until the child process finish. Then you can use newCrawler.ExitCode to have the exit value.
Seems like the Process exit handling could have caused the application error. So the application could have terminated. Can you put a proper try..catch block and debugg to see what is going wrong. Or comment the
line
newCrawler.Exited += new EventHandler(newCrawler_Exited);
and see what happens.
Please try following code (This is from MSDN) , also don't forget to pass one argument (FileName)
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Threading;
using Microsoft.VisualBasic;
class PrintProcessClass
{
private Process myProcess = new Process();
private int elapsedTime;
private bool eventHandled;
// Print a file with any known extension.
public void PrintDoc(string fileName)
{
elapsedTime = 0;
eventHandled = false;
try
{
// Start a process to print a file and raise an event when done.
myProcess.StartInfo.FileName = fileName;
myProcess.StartInfo.Verb = "Print";
myProcess.StartInfo.CreateNoWindow = true;
myProcess.EnableRaisingEvents = true;
myProcess.Exited += new EventHandler(myProcess_Exited);
myProcess.Start();
}
catch (Exception ex)
{
Console.WriteLine("An error occurred trying to print \"{0}\":" + "\n" + ex.Message, fileName);
return;
}
// Wait for Exited event, but not more than 30 seconds.
const int SLEEP_AMOUNT = 100;
while (!eventHandled)
{
elapsedTime += SLEEP_AMOUNT;
if (elapsedTime > 30000)
{
break;
}
Thread.Sleep(SLEEP_AMOUNT);
}
}
// Handle Exited event and display process information.
private void myProcess_Exited(object sender, System.EventArgs e)
{
eventHandled = true;
Console.WriteLine("Exit time: {0}\r\n" +
"Exit code: {1}\r\nElapsed time: {2}", myProcess.ExitTime, myProcess.ExitCode, elapsedTime);
}
public static void Main(string[] args)
{
// Verify that an argument has been entered.
if (args.Length <= 0)
{
Console.WriteLine("Enter a file name.");
return;
}
// Create the process and print the document.
PrintProcessClass myPrintProcess = new PrintProcessClass();
myPrintProcess.PrintDoc(args[0]);
}
}
One thing I noticed is, if you are not passing the filename as parameter, that will lead the process to crash, but still the application is intact (Since the exception is handled inside the process).
If you are not passing the filename the above code will crash beacuse the
myPrintProcess.PrintDoc(args[0]);
will throw exception from main process itself.
I tried to create an exceptin inside the Exit handler, at that time the application (main process) also crashed.
can you try commenting the code inside Exit handler?

Categories