0

There's a cmd window.
I want to read the text in it, then, write to it.
The handle of the cmd window is known,
Use C#'s Process is a way, but RedirectStandardOutput won't work.
Because it's running Python and will move the cursor.

I tried to use SendMessage, but it only shows the title of that cmd:
C:\WINDOWS\system32\cmd
But I want it's content text.

Is there a way to read what's showing on a running external console?
(for example, cmd console / python console)

For example:
Hello.exe :

    static void Main(string[] args)
    {
        Console.Write("hello world");
        Console.CursorLeft -= 5;
        Console.Write("you  \n");
        Console.WriteLine("Hello.exe");
        Console.ReadLine();
    }

The output would be:

hello you
Hello.exe

Then in test.exe :

    static void Main(string[] args)
    {
        if (Process.GetProcesses().Where(x => x.ProcessName.Contains("Hello")).Count() == 0)
        {
            Process p = new Process();
            p.StartInfo.FileName = "Hello.exe";
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;

            p.OutputDataReceived += delegate (object obj, DataReceivedEventArgs drea)
            {
                Console.WriteLine(drea.Data);
            };
                
            p.Start();
            p.BeginOutputReadLine();

            p.WaitForExit();
        }
        Console.WriteLine("Test.exe");
        Console.ReadLine();
    }

The output will be something like:

Unhandled exception. System.ArgumentOutOfRangeException: The value must be greater than or equal to zero and less than the console's buffer size in that dimension. (Parameter 'left') Actual value was -5. at System.Console.SetCursorPosition(Int32 left, Int32 top) at System.Console.set_CursorLeft(Int32 value) at Hello.Program.Main(String[] args)
hello world
Test.exe

Is there any fix for it? -v-

NnWinter
  • 3
  • 1
  • 3
  • There's not much you can do to get the functionality you seem to want. The redirected output is a stream, so moving the cursor does not make sense in that context. What you _can_ do to avoid errors is test for `Console.IsOutputRedirected` and avoid moving the cursor if it is. You'll also notice that when output is redirected, the cursor position remains at (0, 0), even after writing, so moving it to (-5, 0) is going to fail. – sellotape Nov 13 '21 at 13:32
  • @sellotape sadly, I cannot change the python file, so the movement of the cursor is a must-have. =( – NnWinter Nov 13 '21 at 14:15
  • So, is the assertion that the python program crashes if its output is redirected? Or do positioning commands just become ignored in the stream reading .. the next question I have; if your c# program is reading in lines, are you certain the python program outputs lines? – Caius Jard Nov 13 '21 at 20:24
  • @CaiusJard I'm not 100% sure if the python crashes, but after the first line of output, the program stopped showing anything. ||| I ain't sure what is going on in that python program, I only know that it is 100% moving the cursor around to refresh content like a progress bar like "(60/100)" to "(90/100)" – NnWinter Nov 13 '21 at 20:39
  • 1
    Do you need the progress bar or is there other output you need? Also, curious what happens if you run it in a Command prompt like `mypyexe.exe >c:\temp\py.txt` - what if anything ends up in the file – Caius Jard Nov 13 '21 at 20:49
  • @CaiusJard The progress bar is from python, and I can't change what's in that main.py. If I run it without Redirect output, everything works fine except I can't get what's on it. – NnWinter Nov 14 '21 at 03:41
  • 1
    That comment didn't answer my question – Caius Jard Nov 14 '21 at 05:05

2 Answers2

0

I tried to write a simple python script which reproduces this.


def moveleft(y):
    print("\033[%dD" %(y), end="")

print("Hello World",end="")
moveleft(5)
print("you  ")
print("Hello.exe")

When executing it, the output looks like:

Hello you
Hello.exe

When doing python hello.py > hello.txt, and looking at the hello.txt file, it looks like this:

Hello World<esc>[5Dyou  
Hello.exe

Where <esc> is a representation of the Escape character.

When your python script uses the same way as above script to position the cursor, you should not have a problem.

When other means of cursor positioning are used, you are "out of luck". Maybe some help would be Read values already printed to the console, but I do not know how to do that on a remote console.

Luuk
  • 12,245
  • 5
  • 22
  • 33
  • Solved! Thanks for your answer, I looked at your related post, then I found https://stackoverflow.com/questions/12355378 – NnWinter Nov 15 '21 at 08:27
0

It is solved. Much thanks to @Luuk.
I found what I need while looking at the post he gave and related questions!

Check : Read from location on console C#

Use these (by @Glenn Slayden)

[DllImport("kernel32", SetLastError = true)]
static extern IntPtr GetStdHandle(int num);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ReadConsoleOutputCharacterA(
    IntPtr hStdout,   // result of 'GetStdHandle(-11)'
    out byte ch,      // A̲N̲S̲I̲ character result
    uint c_in,        // (set to '1')
    uint coord_XY,    // screen location to read, X:loword, Y:hiword
    out uint c_out);  // (unwanted, discard)

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ReadConsoleOutputCharacterW(
        IntPtr hStdout,   // result of 'GetStdHandle(-11)'
        out Char ch,      // U̲n̲i̲c̲o̲d̲e̲ character result
        uint c_in,        // (set to '1')
        uint coord_XY,    // screen location to read, X:loword, Y:hiword
        out uint c_out);  // (unwanted, discard)

I was able to read a printed char using code:

Example Code Image

Although in this example, only the first char h has been read.
But with this, you will find a way to read all other chars too.
Thus, that's it! Yeah!

OK, but wait, it should be "external console's IO".
Well for this part, the only way I know is that
you could run an independent Console App as the external console app's console.
(Use create Process like what's in the picture in your independent console, or any other better way)

This is a ConsoleReader I made, not pretty, but it works.

class ConsoleReader
{
    [DllImport("kernel32", SetLastError = true)]
    static extern IntPtr GetStdHandle(int num);

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool ReadConsoleOutputCharacterA(
        IntPtr hStdout,   // result of 'GetStdHandle(-11)'
        out byte ch,      // A̲N̲S̲I̲ character result
        uint c_in,        // (set to '1')
        uint coord_XY,    // screen location to read, X:loword, Y:hiword
        out uint c_out);  // (unwanted, discard)

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool ReadConsoleOutputCharacterW(
            IntPtr hStdout,   // result of 'GetStdHandle(-11)'
            out char ch,      // U̲n̲i̲c̲o̲d̲e̲ character result
            uint c_in,        // (set to '1')
            uint coord_XY,    // screen location to read, X:loword, Y:hiword
            out uint c_out);  // (unwanted, discard)

    public static readonly IntPtr stdout = GetStdHandle(-11);
    public static char? ConsoleUnicodeCharAt(uint x, uint y)
    {
        uint coord;
        coord = x;        // loword  <-- X coord to read
        coord |= y << 16;           // hiword  <-- Y coord to read

        var success = ReadConsoleOutputCharacterW(
                stdout,
                out char ch,    // U̲n̲i̲c̲o̲d̲e̲ character result
                1,                  // # of chars to read
                coord,              // (X,Y) screen location to read (see above)
                out _);             // result: actual # of chars (unwanted)

        if (success) { return ch; }
        return null;
    }
    public static char? ConsoleUnicodeCharAt(uint x, uint y,uint read_char_length)
    {
        uint coord;
        coord = x;        // loword  <-- X coord to read
        coord |= y << 16;           // hiword  <-- Y coord to read

        var success = ReadConsoleOutputCharacterW(
                stdout,
                out char ch,    // result: single ANSI char
                read_char_length,                  // # of chars to read
                coord,              // (X,Y) screen location to read (see above)
                out uint actual_char_len);             // result: actual # of chars (unwanted)

        if (success) { return ch; }
        return null;
    }
    public static byte? ConsoleAnsiCharAt(uint x, uint y)
    {
        uint coord;
        coord = x;        // loword  <-- X coord to read
        coord |= y << 16;           // hiword  <-- Y coord to read

        var success = ReadConsoleOutputCharacterA(
                stdout,
                out byte ch,    // result: single ANSI char
                1,                  // # of chars to read
                coord,              // (X,Y) screen location to read (see above)
                out _);             // result: actual # of chars (unwanted)

        if (success) { return ch; }
        return null;
    }
    public static string GetLineAt(uint y, Encoding encoding)
    {
        uint i = 0;
        var line_index = y;
        if (encoding == Encoding.Unicode)
        {
            List<char> line_chars = new List<char>();
            char? ch;
            while ((ch = ConsoleUnicodeCharAt(i++, line_index)) != null) { line_chars.Add((char)ch); if (ch == '\0') { break; } }
            return new string(line_chars.ToArray());
        }
        else if (encoding == Encoding.ASCII)
        {
            List<byte> line_bytes = new List<byte>();
            byte? b;
            while ((b = ConsoleAnsiCharAt(i++, line_index)) != null) { line_bytes.Add((byte)b); if (b == byte.MinValue) { break; } }
            return Encoding.ASCII.GetString(line_bytes.ToArray());
        }
        else
        {
            return null;
        }
    }
    public static string GetLineAt_Unicode(uint y, uint trylength) //trylength is for first char
    {
        uint i = 0;
        var line_index = y;
        List<char> line_chars = new List<char>();
        char? ch;
        while((ch = ConsoleUnicodeCharAt(i++, line_index, trylength) ).HasValue)
        {
            line_chars.Add((char)ch);

            var size = GetCharSize(ch.Value);
            i += size - 1;

            if ((char)ch == '\0') { break; }
        }
        return new string(line_chars.ToArray());
    }
    public static string GetCurrentLine(Encoding encoding)
    {
        return GetLineAt((uint)Console.GetCursorPosition().Top, 
             encoding);
    }
    public static string GetCurrentLine_Unicode(uint try_char_length)
    {
        return GetLineAt_Unicode((uint)Console.GetCursorPosition().Top, try_char_length);
    }
    public static uint GetCharSize(char c)
    {
        var b = BitConverter.GetBytes(c);
        uint size = (uint)b.Length;
        for (int i=b.Length-1;i>=0;i--)
        {
            if (b[i] != 0) { break; }
            size--;
        }
        return size;
    }
}
NnWinter
  • 3
  • 1
  • 3