/// <summary>Run the AccuRev \e command synchronously (blocks) on the current thread.</summary>
        /// <param name="command">The AccuRev command to run, e.g. <tt>hist -fx -p AcTools -t 453</tt></param>
        /// <param name="validator">Use to change the [default logic](@ref AcUtils#CmdValidate#isValid) for determining
        /// if an AcUtilsException should be thrown based on AccuRev's program return value for \e command.</param>
        /// <returns>On success an AcResult object with AcResult.RetVal set to the AccuRev program return value and the command
        /// result (usually XML) in AcResult.CmdResult. Otherwise, AcResult.RetVal is <em>minus one (-1)</em> (default) on error.
        /// </returns>
        /// <exception cref="AcUtilsException">thrown on AccuRev program invocation failure or <tt>merge/diff</tt>
        /// program error (return value <em>two (2)</em>).</exception>
        /// <exception cref="Win32Exception">caught and [logged](@ref AcUtils#AcDebug#initAcLogging)
        /// in <tt>\%LOCALAPPDATA\%\\AcTools\\Logs\\<prog_name\>-YYYY-MM-DD.log</tt> on error spawning the AccuRev process that runs the command.</exception>
        /// <exception cref="InvalidOperationException">caught and logged in same on failure to handle a range of exceptions.</exception>
        /*! \attention Do not use this function for the <tt>ismember</tt> command. Instead, use AcGroups#isMember. */
        public static AcResult run(string command, ICmdValidate validator = null)
        {
            AcResult      result = new AcResult();
            StringBuilder error  = new StringBuilder(512);

            try
            {
                using (Process process = new Process())
                {
                    process.StartInfo.FileName               = "accurev";
                    process.StartInfo.Arguments              = command;
                    process.StartInfo.UseShellExecute        = false;
                    process.StartInfo.CreateNoWindow         = true;
                    process.StartInfo.RedirectStandardInput  = true; // fix for AccuRev defect 29059
                    process.StartInfo.RedirectStandardOutput = true;
                    process.StartInfo.RedirectStandardError  = true;
                    process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
                    process.StartInfo.StandardErrorEncoding  = Encoding.UTF8;
                    process.ErrorDataReceived += (sender, e) =>
                    {
                        error.AppendLine(e.Data);
                    };
                    StringBuilder output = new StringBuilder();
                    output.Capacity             = 4096;
                    process.OutputDataReceived += (sender, e) =>
                    {
                        output.AppendLine(e.Data);
                    };
                    process.Start();
                    process.BeginErrorReadLine();
                    process.BeginOutputReadLine();
                    process.WaitForExit(); // blocks here. recommended to ensure output buffer has been flushed
                    if (process.HasExited)
                    {
                        string err = error.ToString().Trim();
                        if (!String.IsNullOrEmpty(err) &&
                            !(String.Equals("You are not in a directory associated with a workspace", err)))
                        {
                            AcDebug.Log(err, false);
                        }

                        ICmdValidate validate = validator ?? new CmdValidate();
                        if (validate.isValid(command, process.ExitCode))
                        {
                            result.RetVal    = process.ExitCode;
                            result.CmdResult = output.ToString();
                        }
                        else
                        {
                            throw new AcUtilsException($"AccuRev program return: {process.ExitCode}{Environment.NewLine}accurev {command}"); // let calling method handle
                        }
                    }
                }
            }

            catch (Win32Exception ecx)
            {
                string msg = String.Format(@"Win32Exception caught and logged in AcCommand.run{0}{1}{0}""accurev {2}""{0}errorcode: {3}{0}native errorcode: {4}{0}{5}{0}{6}{0}{7}{0}{8}",
                                           Environment.NewLine, ecx.Message, command, ecx.ErrorCode.ToString(), ecx.NativeErrorCode.ToString(), ecx.StackTrace, ecx.Source, ecx.GetBaseException().Message, error.ToString());
                AcDebug.Log(msg);
            }

            catch (InvalidOperationException ecx)
            {
                string msg = String.Format(@"InvalidOperationException caught and logged in AcCommand.run{0}{1}{0}""accurev {2}""{0}{3}",
                                           Environment.NewLine, ecx.Message, command, error.ToString());
                AcDebug.Log(msg);
            }

            return(result);
        }
        /// <summary>Run the AccuRev \e command asynchronously with non-blocking I/O.</summary>
        /// <remarks>To reduce the risk of the AccuRev server becoming unresponsive due to an excess of commands,
        /// the maximum number of commands that will run simultaneously for a client application is eight (8).
        /// Other commands [are queued](@ref System#Threading#Tasks#Schedulers#LimitedConcurrencyLevelTaskScheduler)
        /// until space is available. You can override this default value by creating the environment variable
        /// \b ACUTILS_MAXCONCURRENT and specifying a different number, e.g. \c ACUTILS_MAXCONCURRENT=12.</remarks>
        /// <param name="command">The AccuRev command to run, e.g. <tt>hist -fx -p AcTools -t 453</tt></param>
        /// <param name="validator">Use to change the [default logic](@ref AcUtils#CmdValidate#isValid) for determining
        /// if an AcUtilsException should be thrown based on AccuRev's program return value for \e command.</param>
        /// <returns>An AcResult object with AcResult.RetVal set to the AccuRev program return value and the command result
        /// (usually XML) in AcResult.CmdResult. Returns \e null if an exception occurs.
        /// </returns>
        /// <exception cref="AcUtilsException">thrown on AccuRev program invocation failure or <tt>merge/diff</tt>
        /// program error (return value <em>two (2)</em>).</exception>
        /// <exception cref="Win32Exception">caught and [logged](@ref AcUtils#AcDebug#initAcLogging)
        /// in <tt>\%LOCALAPPDATA\%\\AcTools\\Logs\\<prog_name\>-YYYY-MM-DD.log</tt> on error spawning the AccuRev process that runs the command.</exception>
        /// <exception cref="InvalidOperationException">caught and logged in same on failure to handle a range of exceptions.</exception>
        /*! \attention Do not use this function for the <tt>ismember</tt> command. Instead, use AcGroups#isMember. */
        public static async Task <AcResult> runAsync(string command, ICmdValidate validator = null)
        {
            TaskCompletionSource <AcResult> tcs = new TaskCompletionSource <AcResult>();
            StringBuilder error = new StringBuilder(512);

            try
            {
                await _taskFactory.StartNew(() =>
                {
                    using (Process process = new Process())
                    {
                        process.StartInfo.FileName               = "accurev";
                        process.StartInfo.Arguments              = command;
                        process.StartInfo.UseShellExecute        = false;
                        process.StartInfo.CreateNoWindow         = true;
                        process.StartInfo.RedirectStandardInput  = true; // fix for AccuRev defect 29059
                        process.StartInfo.RedirectStandardOutput = true;
                        process.StartInfo.RedirectStandardError  = true;
                        process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
                        process.StartInfo.StandardErrorEncoding  = Encoding.UTF8;
                        process.ErrorDataReceived += (sender, e) =>
                        {
                            error.AppendLine(e.Data);
                        };
                        process.Start();
                        process.BeginErrorReadLine();
                        Task <string> output = process.StandardOutput.ReadToEndAsync();
                        process.WaitForExit();
                        if (process.HasExited)
                        {
                            string err = error.ToString().Trim();
                            if (!String.IsNullOrEmpty(err) &&
                                !(String.Equals("You are not in a directory associated with a workspace", err)))
                            {
                                AcDebug.Log(err, false);
                            }

                            ICmdValidate validate = validator ?? new CmdValidate();
                            if (validate.isValid(command, process.ExitCode))
                            {
                                tcs.SetResult(new AcResult(process.ExitCode, output.Result));
                            }
                            else
                            {
                                tcs.SetException(new AcUtilsException($"AccuRev program return: {process.ExitCode}{Environment.NewLine}accurev {command}")); // let calling method handle
                            }
                        }
                    }
                }).ConfigureAwait(false);
            }

            catch (Win32Exception ecx)
            {
                string msg = String.Format(@"Win32Exception caught and logged in AcCommand.runAsync{0}{1}{0}""accurev {2}""{0}errorcode: {3}{0}native errorcode: {4}{0}{5}{0}{6}{0}{7}{0}{8}",
                                           Environment.NewLine, ecx.Message, command, ecx.ErrorCode.ToString(), ecx.NativeErrorCode.ToString(), ecx.StackTrace, ecx.Source, ecx.GetBaseException().Message, error.ToString());
                AcDebug.Log(msg);
                tcs.SetException(ecx);
            }

            catch (InvalidOperationException ecx)
            {
                string msg = String.Format(@"InvalidOperationException caught and logged in AcCommand.runAsync{0}{1}{0}""accurev {2}""{0}{3}",
                                           Environment.NewLine, ecx.Message, command, error.ToString());
                AcDebug.Log(msg);
                tcs.SetException(ecx);
            }

            return(await tcs.Task.ConfigureAwait(false));
        }