Запустите скрипт vb с помощью System.Diagnostics.Process, но только частично успешно выполните ввод входного текста в процесс 'stdin - PullRequest
8 голосов
/ 24 июня 2019

Редактировать: обо всем по порядку

Смысл vbscript в том, чтобы действовать как среда REPL или командной строки / bash, она упрощена просто для повторной печати пользовательского ввода

Таким образом, другими словами, процесс cscript должен оставаться в живых, и пользовательский ввод для каждого прохода должен отправляться только этому процессу.

А также это означает, что внутреннее состояние скрипта должно сохраняться для каждогоpass ( One pass = каждый раз, когда нажимается кнопка «Отправить» в winform C # , или в контексте vbscript, One pass = каждый раз, когда ^ Z вводится ).

Например, если скрипт vbscript необходимо изменить для демонстрации поведения с сохранением состояния, вы можете сделать следующие моды:

  1. В строке dim wsh,stmt,l... добавить его с помощью : dim passcnt : passcnt=1
  2. В строке wsh.Echo("Enter lines of strings, press ctrl-z..., заменить последнюю закрывающую скобку на & " (pass #" & passcnt & ")")
  3. В строке wsh.Echo("End output") добавить код : passcnt = passcnt + 1

Запустив vbscript, консоль покажет номер проходаmented на каждом проходе.

  1. Winform C # может быть изменен любым способом, пока вышеупомянутое условие все еще выполняется.
  2. Попробуйте посмотреть, что делает скриптcscript ask_SO.vbs, это должно прояснить ситуацию

Я думаю, это самое ясное, что я могу сделать.


Я хотел бы использовать перенаправление stdout / stdin System.Diagnostics.Process для подачи входных текстов в следующий VBScript.

Что делает vbscript, так это то, что он позволяет пользователювведите несколько строк строк в консоль, и когда будет введен символ ^ z, скрипт просто выведет все данные в консоль:

Пример вывода

Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.

Enter lines of strings, press ctrl-z when you are done (ctrl-c to quit):
I come with no wrapping or pretty pink bows.
got line
I am who I am, from my head to my toes.
got line
I tend to get loud when speaking my mind.
got line
Even a little crazy some of the time.
got line
I'm not a size 5 and don't care to be.
got line
You can be you and I can be me.
got line

got line
Source: https://www.familyfriendpoems.com/poem/be-proud-of-who-you-are
got line
^Z
=====================================
You have entered:
I come with no wrapping or pretty pink bows.
I am who I am, from my head to my toes.
I tend to get loud when speaking my mind.
Even a little crazy some of the time.
I'm not a size 5 and don't care to be.
You can be you and I can be me.

Source: https://www.familyfriendpoems.com/poem/be-proud-of-who-you-are

End output
Enter lines of strings, press ctrl-z when you are done (ctrl-c to quit):

После этогопользователь может ввести другой фрагмент текста и повторить процесс.

Это код сценария:

ask_SO.vbs


dim wsh,stmt,l : set wsh = WScript


do
    wsh.Echo("Enter lines of strings, press ctrl-z when you are done (ctrl-c to quit):")
    'stmt=wsh.StdIn.ReadAll()
    do
        l=wsh.StdIn.ReadLine()
        wsh.echo("got line")
        stmt = stmt & l & vbcrlf
    loop while (not wsh.StdIn.AtEndOfStream)
    wsh.Echo("=====================================")
    wsh.Echo("You have entered:")
    wsh.Echo(stmt)
    wsh.Echo("End output")
loop

Это способ вызова сценария.:

cscript ask_SO.vbs

Я получил следующий код C # (тип проекта установлен на Консольное приложение вместо Windows Forms ):

frmPostSample

public class frmPostSample : Form

{
    Process proc_cscr;
    StreamWriter sw;
    public frmPostSample()
    {
        InitializeComponent2();
    }

    #region Copied from generated code
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }
    private void InitializeComponent2()
    {
        this.txt_lines = new System.Windows.Forms.TextBox();
        this.Btn_Send = new System.Windows.Forms.Button();
        this.SuspendLayout();
        // 
        // txt_lines2
        // 
        this.txt_lines.Location = new System.Drawing.Point(41, 75);
        this.txt_lines.Multiline = true;
        this.txt_lines.Name = "txt_lines2";
        this.txt_lines.Size = new System.Drawing.Size(689, 298);
        this.txt_lines.TabIndex = 0;
        // 
        // Btn_Send2
        // 
        this.Btn_Send.Location = new System.Drawing.Point(695, 410);
        this.Btn_Send.Name = "Btn_Send2";
        this.Btn_Send.Size = new System.Drawing.Size(75, 23);
        this.Btn_Send.TabIndex = 1;
        this.Btn_Send.Text = "&Send";
        this.Btn_Send.UseVisualStyleBackColor = true;
        this.Btn_Send.Click += new System.EventHandler(this.Btn_Send_Click);
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(800, 450);
        this.Controls.Add(this.Btn_Send);
        this.Controls.Add(this.txt_lines);
        this.Name = "Form1";
        this.Text = "Form1";
        this.ResumeLayout(false);
        this.PerformLayout();

    }


    private System.Windows.Forms.TextBox txt_lines;
    private System.Windows.Forms.Button Btn_Send;

    #endregion
    private void Btn_Send_Click(object sender, EventArgs e)
    {
        if (proc_cscr == null)
        {
            if (!File.Exists("ask_SO.vbs"))
            {
                MessageBox.Show("Script file not exist");
                return;
            }
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = "cscript";
            startInfo.Arguments = "//nologo ask_SO.vbs";

            startInfo.RedirectStandardInput = true;
            startInfo.RedirectStandardOutput = true;
            startInfo.UseShellExecute = false;


            proc_cscr = new Process();

            proc_cscr.StartInfo = startInfo;

            proc_cscr.Start();
            sw = proc_cscr.StandardInput;
        }
        OutPrint();


        foreach (var vbsline in txt_lines.Lines)
        {
            sw.WriteLine(vbsline);     // <-------- SW WRITELINE
            sw.Flush();
            OutPrint();


        }
        //sw.Flush();
        sw.Close();
        while (true)
        {
            var s2 = proc_cscr.StandardOutput.ReadLineAsync();
            s2.Wait();
            Console.WriteLine(s2.Result);
            if (proc_cscr.StandardOutput.Peek() == -1) break;
        }

    }
    private void OutPrint()
    {
        string l;
        while (proc_cscr.StandardOutput.Peek() != -1)
        {
            l = proc_cscr.StandardOutput.ReadLine();
            Console.WriteLine(l);
        }
    }
}

Запустите программу, и если вы правильно установили тип проекта «Консольное приложение», должны появиться окно консоли и окно графического интерфейса пользователя.Вы просто вставляете текст в область ввода текста и нажимаете кнопку отправки, и наблюдаете результат в окне консоли.

Однако поведение формы C # отличается от прямого запуска сценария cscript ask_SO.vbs:

  1. Скрипт может принять только один проход ввода - второй проход выдает ошибку «Невозможно записать в закрытый TextWriter» в строке с комментарием SW WRITELINE - Я знаю, что это потому, что я закрылпоток stdin, но в противном случае я не могу заставить сценарий двигаться вперед
  2. Кроме того, у меня отображается сообщение об ошибке: ...\ask_SO.vbs(8, 9) Microsoft VBScript runtime error: Input past end of file.
  3. Эхо "got line" не отображаетсясразу после кода c # напишите строку ввода в stdin (опять же, в строке с комментарием SW WRITELINE).

Я искал в Интернете, чтобы найти решение, но большинство материалов толькопоказывает ввод без использования символа ^ z, или, другими словами, принимает только однопроходный ввод.

Вы можете загрузить решение C # visual studio здесь (vbscript включен - вы просто загружаетерешение в визуальномStudio 2019 и нажмите F5 для запуска).

Примечание

Кодировка, полученная из proc_cscr.StandardOutput.CurrentEncoding.BodyName и proc_cscr.StandardInput.Encoding.BodyName, равна big5, это DBCSCodePageEncoding, используемая для кодирования китайских символов.

Я осознал, что мне нужно упомянуть об этом, когда я попробовал предложение, упомянутое в ответе, написать (char) 26 в поток stdin.Поскольку Encoding.GetEncoding("big5").GetBytes(new char[]{(char)26}) возвращает только один байт (два байта для Unicode: {byte[2]} [0]: 26 [1]: 0), я сделал sw.Write((char)26); и добавил также sw.flush().Это все еще не сработало.

Ответы [ 2 ]

1 голос
/ 02 июля 2019

Ваша проблема в том, что когда вы закрываете поток, cscript также завершается, и вы пытаетесь прочитать из мертвого процесса.

Я изменил ваш пример для использования асинхронного чтения cscript, вызвав BeginOutputReadLine и прочитав выходные данные в событии OutputDataReceived. Я также добавил WaitForExit, который необходим для обеспечения возникновения асинхронных событий.

Кстати, вам действительно не нужно отправлять CTRL + Z, поскольку это всего лишь символ, а на самом деле это не маркер EOF. Обработчик консоли просто обрабатывает это нажатие клавиши как сигнал EOF. Закрытие StandardInput делает свое дело.

var psi = new ProcessStartInfo
          {
              FileName = "cscript",
              RedirectStandardError = true,
              RedirectStandardOutput = true,
              RedirectStandardInput = true,
              UseShellExecute = false,
              //CreateNoWindow = true,
              WindowStyle = ProcessWindowStyle.Normal,
              Arguments = "//nologo ask_SO.vbs"
          };

var process = Process.Start(psi);
process.BeginOutputReadLine();

var buffer = new StringBuilder();

process.OutputDataReceived += (s, args) =>
{
    buffer.AppendLine(args.Data);
};

foreach (var line in textBox1.Lines)
{
    buffer.AppendLine(line);
    process.StandardInput.WriteLine(line);

    Thread.Sleep(50);
}

process.StandardInput.Flush();
process.StandardInput.Close();

process.WaitForExit();

output.Text = buffer.ToString();

РЕДАКТИРОВАТЬ: Обновлено, чтобы сохранить процесс

private Process process;

private void EnsureProcessStarted()
{
    if (null != process)
        return;

    var psi = new ProcessStartInfo
              {
                  FileName = "cscript",
                  RedirectStandardError = true,
                  RedirectStandardOutput = true,
                  RedirectStandardInput = true,
                  UseShellExecute = false,
                  //CreateNoWindow = true,
                  WindowStyle = ProcessWindowStyle.Normal,
                  Arguments = "//nologo ask_SO.vbs"
              };

    process = Process.Start(psi);
    process.OutputDataReceived += (s, args) => AppendLineToTextBox(args.Data);

    process.BeginOutputReadLine();

    // time to warm up
    Thread.Sleep(500);
}

private void AppendLineToTextBox(string line)
{
    if (string.IsNullOrEmpty(line))
        return;

    if (output.InvokeRequired)
    {
        output.Invoke(new Action<string>(AppendLineToTextBox), line);
        return;
    }

    output.AppendText(line);
    output.AppendText(Environment.NewLine);
}

private void SendLineToProcess(string text)
{
    EnsureProcessStarted();

    if (string.IsNullOrWhiteSpace(text))
    {
        process.StandardInput.Flush();
        process.StandardInput.Close();

        //process.WaitForExit(); causes a deadlock
        process = null;
    }
    else
    {
        AppendLineToTextBox(text); // local echo
        process.StandardInput.WriteLine(text);
        process.StandardInput.Flush();

        // time to process
        Thread.Sleep(50);
    }
}
1 голос
/ 27 июня 2019

Не думаю, это возможно сделать.

Ваша точка 3:

Эхо "got line" не отображается сразу после того, как код c # записывает ввод строки в стандартный ввод

Это потому, что вы перенаправили вывод (startInfo.RedirectStandardOutput = true). Если вы перенаправите его, все, что вы напишете, попадет в поток StandardOutput, и вам придется читать его вручную. Так что просто не перенаправляйте вывод, и ваши got line сообщения будут немедленными. Если выходные данные не перенаправлены, вы не можете использовать свойство StandardOutput (но оно вам все равно не нужно).

Остальное сложнее. Дело в том, что, похоже, нет способа отправить конец потока , потому что это то, что останавливает ваш внутренний цикл в vbs. Поток заканчивается, когда вы заканчиваете с ним - технически, когда вы закрываете его или завершаете свой процесс. Символ значения 26 представляется как конец потока (Ctrl + Z) где-то . Но он здесь не работает (я пробовал sw.Write(Convert.ToChar(26)).

Я не знаю, возможно ли это (я не знаю vbs), но, возможно, вы можете изменить свою логику там, а не проверять конец потока. Возможно, он прочитан байтами (символами) и проверит наличие конкретного символа (например, char (26)) для выхода из внутреннего цикла.

...