Осмысленное использование консольных приложений в C#

Когда что-то уже написано, оттестировано и достойно справляется со своей работой, то лучше использовать это средство, нежели изобретать велосипед. Например, есть консольная утилита cpctest.exe, которая позволяет выполнять все те-же действия что и графическая оболочка, и масса других утилит из стандартного набора Windows. На разработку, отладку и покрытие тестами аналогичной функциональности уйдет драгоценное время. Так зачем его прожигать? Приступим.

Для начала нам нужно залезть на MSDN и посмотреть синтаксис стандартных команд. В результате мы увидим, что запуск любого консольного приложения состоит из имени приложения и его параметров. Приложение инстанцируется системным классом Process. Прототип нашей функции для запуска консольного приложения будет выглядеть следующим образом:

	bool Execute(string commandName, IEnumerable<string> paramsList)

Если мы захотим получать результат выполнения запущенного приложения и обрабатывать его, нам потребуется соответствующий метод:

string GetResult (string commandName, IEnumerable<string> paramsList)

Далее идем снова на MSDN и смотрим ProcessStartInfo Arguments, UseShellExecute, RedirectStandardOutput и RedirectStandardError, если Вы будете строго обрабатывать исключения как в Java. В итоге для инициализации процесса нам потребуется свойство, которое будет определять режим запуска консольного приложения и метод для инициации процесса. Для своего класса я использовал паттерн Facade.

public  class CommandHelpers
    {
        public CommandHelpers()
        {
            Invisible = true;
        }

public bool Invisible { get; set; }

private Process CreateProcess(string commandName, IEnumerable<string> paramsList, bool output = false)
        {
            string paramString = paramsList.Aggregate<string, string>(null,
                (current, param) => current + " " + param);
            return new Process
            {
                StartInfo =
                {
                    FileName = commandName,
                    Arguments = paramString,
                    UseShellExecute = output ? !output : !Invisible, 
                    RedirectStandardOutput = output
                }
            };
        }

Необходимо учесть, что запускаемое приложение может работать бесконечно долго, например, ping –t youwebsite.org. Для его запуска нам потребуется соответствующий метод:

public Task<bool> ExecuteAsync(string commandName, IEnumerable<string> paramsList)

Исходный код:

Пример использования:
public class CspHelpers
    {
        private readonly CommandHelpers _cryptoconsole;
        private readonly string _command = @"c:\Program Files\Crypto Pro\CSP\csptest";

        public CspHelpers()
        {
            _cryptoconsole = new CommandHelpers();
        }
        /// <summary>
        /// Импорт сертификата в контейнер
        /// </summary>
        /// <param name="driveName">Имя сменного носителя</param>
        /// <param name="containerName">Имя контейнера</param>
        /// <param name="type">Тип импортируемого серификата exchange или signature</param>
        /// <param name="certPath">Полный путь до сертификата</param>
        /// <param name="password">Пароль на контейнер</param>
        /// <returns>Сведения о выполнении</returns>
        public string ImportToContainer(string driveName, int containerName, KeyType type, string certPath,
            string password)
        {
            var Params = new List<string>();
            Params.Add("-keyset");
            Params.Add("-container");
            Params.Add(string.Format(@"\\.\FAT12_{0}\{1}", driveName[0], containerName));
            Params.Add("-password");
            Params.Add(password);
            Params.Add("-keytype");
            Params.Add(type.ToString());
            Params.Add("-impcert");
            Params.Add(certPath);
            return _cryptoconsole.GetResult(_command, Params);
        }
    }



CommandHelpers.cs
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Common.Security
{
    /// <summary>
    ///     See for correct use https://technet.microsoft.com/en-us/library/bb491070.aspx
    /// </summary>
    public class CommandHelpers
    {
        public CommandHelpers()
        {
            Invisible = true;
        }

        /// <summary>
        ///     Not show CMD window
        /// </summary>
        public bool Invisible { get; set; }

        /// <summary>
        ///     Execete CMD command
        /// </summary>
        /// <param name="commandName">Command name only</param>
        /// <param name="paramsList">Params and keys for command</param>
        public bool Execute(string commandName, IEnumerable<string> paramsList)
        {
            return CreateProcess(commandName, paramsList).Start();
        }

        /// <summary>
        ///     Async execete CMD command
        /// </summary>
        /// <param name="commandName">Command name only</param>
        /// <param name="paramsList">Params and keys for command</param>
        public Task<bool> ExecuteAsync(string commandName, IEnumerable<string> paramsList)
        {
            return Task<bool>.Factory.StartNew(() => CreateProcess(commandName, paramsList).Start());
        }

        /// <summary>
        ///     Returns result of command execution
        /// </summary>
        /// <param name="commandName">Command name only</param>
        /// <param name="paramsList">Params and keys for command</param>
        /// <returns></returns>
        public string GetResult(string commandName, IEnumerable<string> paramsList)
        {
            var bufer = new StringBuilder();
            using (var proc = CreateProcess(commandName, paramsList, true))
            {
                proc.Start();
                while (!proc.StandardOutput.EndOfStream)
                {
                    bufer.AppendLine(proc.StandardOutput.ReadLine());
                }
            }
            return bufer.ToString();
        }

        /// <summary>
        ///     Returns result of command execution
        ///     Experemental. Not Tested.
        /// </summary>
        /// <param name="commandName">Command name only</param>
        /// <param name="paramsList">Params and keys for command</param>
        /// <returns></returns>
        public string GetResultAsync(string commandName, IEnumerable<string> paramsList)
        {
            var bufer = new StringBuilder();
            using (var proc = CreateProcess(commandName, paramsList, true))
            {
                proc.OutputDataReceived += (sender, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data))
                    {
                        bufer.AppendLine(e.Data);
                    }
                };
                proc.BeginOutputReadLine();
                proc.EnableRaisingEvents = true;
                proc.WaitForExit();
            }
            return bufer.ToString();
        }


        private Process CreateProcess(string commandName, IEnumerable<string> paramsList, bool output = false)
        {
            var paramString = paramsList.Aggregate<string, string>(null,
                (current, param) => current + " " + param);
            return new Process
            {
                StartInfo =
                {
                    FileName = commandName,
                    Arguments = paramString,
                    UseShellExecute = output ? !output : !Invisible,
                    RedirectStandardOutput = output
                }
            };
        }
    }
}


UPD:

Дополнительные материалы:

Источник вдохновения для статьи:
От пользователя vedmaka: toster.ru/q/7644

GUI обертка для консольного приложения: ru.jakeroid.com/gui-obertka-dlya-konsolnogo-prilozheniya-na-csharp.html

От пользователя evgenyl: habrahabr.ru/post/136766

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 10

    0
    Тут надо заметить, что иногда перехват вывода влияет на работоспособность консольной утилиты. Если не ошибаюсь, столкнулся с этой проблемой при работе с одной из утилит Марка Руссиновича: запуск через консоль отрабатывал, запуск без перехвата отрабатывал, запуск с перехватом не работал.
      +1
      Есть разные потоки вывода консольных приложений, а не только стандартный, на который ориентирован CommandHelpers.

      p.s Ну и название… 'CommandHelpers'
        0
        Ещё не хватает возврата возвращаемого значения ExitCode.
        bool Execute(string commandName, IEnumerable<string> paramsList);
        bool Execute(string commandName, IEnumerable<string> paramsList, out int exitCode);
        

          0
          В публикации его нет, как нет и других методов, которые реализованы в рабочих версиях этого класса. Они выходят за рамки этой статьи.
          0
          Вообще не понял, что это и о чем это…
            0
            У Вас имеется консольное приложение. Есть задача что-то автоматизировать или протестировать сборку. Вся логика действий описана некоторым сценарием. Возможно, вам придется производить обработку данных из других источников, например от тех-же консольных приложений. Для этих задач это простое решение.
              +1
              А не лучше было бы просто создать экстеншн для процесса? Это же фактически класс запуска процесса с аргументами. Тут ведь из своего кода разве что вызов Aggregate (польза которого для складывания строк сомнительна).
                0
                А почему польза сомнительна? Быстродействие?
                  +1
                  Ну учитывая, что вряд ли будет аргументов > 10 на это можно забить. Но вообще когда я вижу складывание строк в цикле у меня случается расстройство :)

                  Вкратце на картинке:

                  image
                    0
                    В моих граничных условиях было 256 символов.

          Only users with full accounts can post comments. Log in, please.