Running Command Line Process from c# code is Hanging / Locking and never Finishing

I created a WCF Service to allow remote machines to run command line scripts (in this case batch files) and then return the error and standard output as a string to the caller. The batch files worked fine when you ran them directly but whenever I ran them via the WCF service they kept hanging / locking and never completing. Mysterious.

I have since found out that this is because you have to redirect either or both of the standard out or error out to read in an asynchronous thread otherwise you get a deadlock when the buffer on one becomes full. Here is my code which does this and there is now no locking.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Diagnostics;
using System.Security;
using System.Security.Principal;
using System.Security.Permissions;
using System.IO;

namespace MyCompany.BatchScriptsService
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    /// <summary>
    ///
    /// </summary>
    [ServiceBehavior(Namespace = "http://MyCompany.LaunchBatchScript")]
    public class LaunchBatchScriptService : ILaunchBatchScriptService
    {

        private StringBuilder allStandardOutput;

        public StringBuilder AllStandardOutput
        {
            get { return allStandardOutput; }
            set { allStandardOutput = value; }
        }

        private static readonly log4net.ILog log = log4net.LogManager.GetLogger
    (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Launches the batch script.
        /// </summary>
        /// <param name="batchScriptFilename">The batch script filename.</param>
        /// <param name="runAsUsername">The run as username.</param>
        /// <param name="runAsPassword">The run as password.</param>
        /// <param name="arguments">The arguments.</param>
        /// <returns></returns>
        public string LaunchBatchScript(LaunchBatchScriptDataContract inputVariables)
        {

            try
            {
                // Initiate the logging
                log4net.Config.XmlConfigurator.Configure();
                log.Info(String.Format("Start LaunchBatchScriptService call"));

                // Create a new process object
                Process p = new Process();

                // Redirect the output stream of the child process.
                p.StartInfo.UseShellExecute = false;

                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardError = true;
                p.StartInfo.RedirectStandardInput = true;
                p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;

                if (inputVariables.BatchScriptFilename.Contains(" ") && !inputVariables.BatchScriptFilename.StartsWith("\""))
                {
                    // Ecapsulate in double quotes
                    inputVariables.BatchScriptFilename = String.Format("\"{0}\"", inputVariables.BatchScriptFilename);
                }

                p.StartInfo.FileName = inputVariables.BatchScriptFilename;
                log.Info(String.Format("Running script {0}", inputVariables.BatchScriptFilename));

                if (!String.IsNullOrEmpty(inputVariables.Arguments))
                {
                    p.StartInfo.Arguments = inputVariables.Arguments;
                }

                // This is where the output is redirected
                p.ErrorDataReceived += build_ErrorDataReceived;
                p.OutputDataReceived += build_ErrorDataReceived;

                // Initialise the StringBuilder Object
                AllStandardOutput = new StringBuilder();

                // Kick off the command
                bool isProcessStarted = p.Start();

                string output = "Process not started";

                if (isProcessStarted)
                {

                    log.Info(String.Format("Process Started Successfully"));
                    p.BeginOutputReadLine();
                    p.BeginErrorReadLine();

                    p.WaitForExit(); // wait for both threads to finish

                    output = AllStandardOutput.ToString();

                }

                log.Info(String.Format("Returning {0}", output));

                log.Info(String.Format("End LaunchBatchScriptService call"));
                return output;

            }
            catch (Exception ex)
            {
                log.Error(String.Format("There was a problem running the script: {0}",ex.Message));
                throw new FaultException(System.String.Format("There was a problem executing the script {0}: {1}", inputVariables.BatchScriptFilename, ex.Message));
            }
        }

        /// <summary>
        /// Handles the ErrorDataReceived event of the build control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Diagnostics.DataReceivedEventArgs"/> instance containing the event data.</param>
        public void build_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            string strMessage = e.Data;

            AllStandardOutput.Append(strMessage);
            AllStandardOutput.Append(System.Environment.NewLine);
        }
    }
}

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>