Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run a vb script using System.Diagnostics.Process but only partially successful in feeding in input text to the process' stdin

Edit: first things first

The point of the vbscript is to act like a REPL or command prompt/bash environment, it is simplified to just reprinting the user input

So in other words the cscript process should stay alive and the user input for each pass should be sent to this process only.

And also it means that the internal state of the script should be kept for each pass (One pass = each time the "Send" button in the C# winform is clicked, or in the context of the vbscript, One pass = each time ^Z is input).

For example, if the vbscript is to be modified to demonstrate the state-keeping behavior, you can make the following mods:

  1. At line dim wsh,stmt,l... append it with : dim passcnt : passcnt=1
  2. At line wsh.Echo("Enter lines of strings, press ctrl-z..., replace the last closing bracket with & " (pass #" & passcnt & ")")
  3. At line wsh.Echo("End output") append the code : passcnt = passcnt + 1

Running the vbscript the console will show the pass number incremented on each pass.

  1. The C# winform can be modified in any way, as long as the above condition still holds.
  2. Try to observe what the script does by cscript ask_SO.vbs, it should make things clear enough

I think this is the most clear I am able to made it.


I would like to use stdout/stdin redirection of System.Diagnostics.Process to feed input texts to the following VBScript.

What the vbscript does is that it allows the user to input multiple lines of strings to the console, and when the ^z character is input, the script will just output everything ver batim to the console:

Sample Output

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):

After that, the user can input another chunk of text and repeat the process.

This is the script code:

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

This is how to invoke the script:

cscript ask_SO.vbs

I came out with the following C# code (project type set to Console Application instead of 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);
        }
    }
}

Run the program, and if you have correctly set the project type to "Console Application", a console window and a GUI Window should be shown. You just paste the text to the text input area and press send, and observe the result in the console window.

However, what the C# form behaves is not the same as directly running the script cscript ask_SO.vbs:

  1. The script can only accept one pass of input - the second pass throws the error "Cannot write to a closed TextWriter" at the line with comment SW WRITELINE - I know it is because I've closed the stdin stream, but otherwise I can't make the script go forward
  2. Also, I've got the error shown: ...\ask_SO.vbs(8, 9) Microsoft VBScript runtime error: Input past end of file.
  3. The "got line" echo is not shown immediately after the c# code write a line input to the stdin (again, at the line with comment SW WRITELINE).

I've searched online to find a solution, but most of the materials only shows input without using the ^z character, or in other words, only accepts one-pass input.

You can download the C# visual studio solution here (vbscript included - you just load the solution in visual studio 2019 and press F5 to run).

Note

The encoding I got from proc_cscr.StandardOutput.CurrentEncoding.BodyName and proc_cscr.StandardInput.Encoding.BodyName is big5, it is a DBCSCodePageEncoding, used for encoding Chinese characters.

I recognized that I need to mention this, when I tried the suggestion mentioned in an answer to write (char)26 to the stdin stream. As Encoding.GetEncoding("big5").GetBytes(new char[]{(char)26}) returns only one byte (two bytes for unicode: {byte[2]} [0]: 26 [1]: 0), I did a sw.Write((char)26);, and add a sw.flush() also. It still didn't work.

like image 651
im_chc Avatar asked Jun 24 '19 06:06

im_chc


1 Answers

I do not think, this is possible to do.

Your point 3:

The "got line" echo is not shown immediately after the c# code write a line input to the stdin

This is because you have redirected output (startInfo.RedirectStandardOutput = true). If you redirect it, everything you write goes to the StandardOutput stream and you have to read it manually. So just do not redirect output and your got line messages will be immediate. If the output is not redirected, you can not use StandardOutput property (but you do not need it anyway).

The rest is more difficult. The thing is, it seems there is not a way how to send end of stream, because this is what stops your inner loop in vbs. The stream ends when you finish with it - technically when you close it, or finish your process. The character of value 26 is represented as end of stream (Ctrl + Z) somewhere. But it is not working here (I tried sw.Write(Convert.ToChar(26)).

I do not know if it is possible (I do not know vbs), but maybe you can change your logic there and not check for end of stream. Insted of it maybe read by bytes (characters) and check for specific char (for example that char(26)) to step out of the inner loop.

like image 158
Stano Peťko Avatar answered Sep 22 '22 15:09

Stano Peťko