Windows - How to redirect one console output to another

So I was trying to port a code from C# to Rust that can run a Windows command as another user. I got it working fine, but the problem is I want to redirect the output to current console instead of showing it on a new one.

This piece of C# code working as intended:

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;


public class RunasCsl
{
    private const Int32 StartfUseStdHandles = 0x00000100;
    private const int BUFFER_SIZE_PIPE = 1048576;
    private const uint CREATE_NO_WINDOW = 0x08000000;
    private const uint CREATE_UNICODE_ENVIRONMENT = 0x400;
    private const uint DUPLICATE_SAME_ACCESS = 0x00000002;
    private const UInt32 INFINITE = 4294967295;


   
    private WindowStationDACL stationDaclObj;
    private IntPtr hTokenPreviousImpersonatingThread;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct STARTUPINFO
    {
        public Int32 cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public Int32 dwX;
        public Int32 dwY;
        public Int32 dwXSize;
        public Int32 dwYSize;
        public Int32 dwXCountChars;
        public Int32 dwYCountChars;
        public Int32 dwFillAttribute;
        public Int32 dwFlags;
        public Int16 wShowWindow;
        public Int16 cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    private struct ProcessInformation
    {
        public IntPtr process;
        public IntPtr thread;
        public int processId;
        public int threadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct SECURITY_ATTRIBUTES
    {
        public int Length;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr handle);

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool CreateProcessWithLogonW(String userName, String domain, String password,
        UInt32 logonFlags, String applicationName, String commandLine, uint creationFlags, UInt32 environment,
        String currentDirectory, ref STARTUPINFO startupInfo, out ProcessInformation processInformation);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe,
        ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize);

    [DllImport("kernel32.dll")]
    private static extern bool SetNamedPipeHandleState(IntPtr hNamedPipe, ref UInt32 lpMode,
        IntPtr lpMaxCollectionCount, IntPtr lpCollectDataTimeout);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead,
        out uint lpNumberOfBytesRead, IntPtr lpOverlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle,
        IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess,
        [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);

    private bool CreateAnonymousPipeEveryoneAccess(ref IntPtr hReadPipe, ref IntPtr hWritePipe)
    {
        SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
        sa.Length = Marshal.SizeOf(sa);
        sa.lpSecurityDescriptor = IntPtr.Zero;
        sa.bInheritHandle = true;
        if (CreatePipe(out hReadPipe, out hWritePipe, ref sa, (uint)BUFFER_SIZE_PIPE))
            return true;
        return false;
    }

    private string ReadOutputFromPipe(IntPtr hReadPipe)
    {
        string output = "";
        uint dwBytesRead = 0;
        byte[] buffer = new byte[BUFFER_SIZE_PIPE];
        if (!ReadFile(hReadPipe, buffer, BUFFER_SIZE_PIPE, out dwBytesRead, IntPtr.Zero))
        {
            output += "No output received from the process.\r\n";
        }

        output += Encoding.Default.GetString(buffer, 0, (int)dwBytesRead);
        return output;
    }

    private void RunasSetupStdHandlesForProcess(ref STARTUPINFO startupInfo,
        out IntPtr hOutputWrite, out IntPtr hErrorWrite, out IntPtr hOutputRead)
    {
        IntPtr hOutputReadTmpLocal = IntPtr.Zero;
        IntPtr hOutputWriteLocal = IntPtr.Zero;
        IntPtr hErrorWriteLocal = IntPtr.Zero;
        IntPtr hOutputReadLocal = IntPtr.Zero;
 

        IntPtr hCurrentProcess = Process.GetCurrentProcess().Handle;
        if (!CreateAnonymousPipeEveryoneAccess(ref hOutputReadTmpLocal, ref hOutputWriteLocal))
            throw new RunasCsException("CreatePipe", true);
        if (!DuplicateHandle(hCurrentProcess, hOutputWriteLocal, hCurrentProcess, out hErrorWriteLocal, 0, true,
                DUPLICATE_SAME_ACCESS))
            throw new RunasCsException("DuplicateHandle stderr write pipe", true);
        if (!DuplicateHandle(hCurrentProcess, hOutputReadTmpLocal, hCurrentProcess, out hOutputReadLocal, 0,
                false,
                DUPLICATE_SAME_ACCESS))
            throw new RunasCsException("DuplicateHandle stdout read pipe", true);
        CloseHandle(hOutputReadTmpLocal);

        UInt32 PIPE_NOWAIT = 0x00000001;
        if (!SetNamedPipeHandleState(hOutputReadLocal, ref PIPE_NOWAIT, IntPtr.Zero, IntPtr.Zero))
            throw new RunasCsException("SetNamedPipeHandleState", true);
        startupInfo.dwFlags = StartfUseStdHandles;
        startupInfo.hStdOutput = hOutputWriteLocal;
        startupInfo.hStdError = hErrorWriteLocal;


        hOutputWrite = hOutputWriteLocal;
        hErrorWrite = hErrorWriteLocal;
        hOutputRead = hOutputReadLocal;
    }

    public string RunAs()

    {
        IntPtr hOutputWrite;
        IntPtr hErrorWrite;
        IntPtr hOutputRead;
        
        string username = "test";
        string password = "1";
        string domainName = ".";
        string commandLine = "C:\\WINDOWS\\system32\\cmd.exe /c whoami /all";
        string currentDirectory = "C:\\Windows\\system32";
        
        STARTUPINFO startupInfo = new STARTUPINFO();
        ProcessInformation processInfo = new ProcessInformation();
        uint logonFlags = 0;
        RunasSetupStdHandlesForProcess(ref startupInfo, out hOutputWrite, out hErrorWrite,
            out hOutputRead);

        if (!CreateProcessWithLogonW(username, domainName, password, logonFlags, null, commandLine,
                CREATE_UNICODE_ENVIRONMENT, (UInt32)0, currentDirectory, ref startupInfo, out processInfo))
            throw new RunasCsException("CreateProcessWithLogonW error", true);
        string output = "";
        WaitForSingleObject(processInfo.process, INFINITE);
        output += "\r\n" + ReadOutputFromPipe(hOutputRead);


        CloseHandle(processInfo.process);
        CloseHandle(processInfo.thread);

        return output;
    }
}

class MainClass
{
    static void Main(string[] args)
    {
        RunasCsl invoker = new RunasCsl();
        Console.Out.Write(invoker.RunAs());
        Console.Out.Flush();
    }
}

And here is what I got with Rust so far:


use std::mem::{size_of, MaybeUninit};
use std::process::{exit};
use core::ffi::c_void;
use std::ptr;
use std::str;
use std::ffi::CStr;
use std::os::raw::c_char;

use windows::core::{Error, PWSTR, PCWSTR};
use windows::imp::WaitForSingleObject;
use windows::w;
use windows::Win32::Security::SECURITY_ATTRIBUTES;
use windows::Win32::Foundation::{WAIT_OBJECT_0, HANDLE, BOOL, CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS};
use windows::Win32::System::Threading::{CreateProcessWithLogonW,GetCurrentProcess,
                                        STARTUPINFOW, PROCESS_INFORMATION, CREATE_PROCESS_LOGON_FLAGS,
                                        CREATE_UNICODE_ENVIRONMENT,CREATE_NO_WINDOW, STARTF_USESTDHANDLES,
                                        INFINITE};
use windows::Win32::System::Pipes::{SetNamedPipeHandleState, CreatePipe, NAMED_PIPE_MODE, PIPE_NOWAIT};
use windows::Win32::Storage::FileSystem::ReadFile;





fn main() {
    let lpusername: PCWSTR = w!("test").into();
    let lpdomain: PCWSTR = w!("").into();
    let lppassword: PCWSTR = w!("1").into();
    let lpapplicationname: PCWSTR = PCWSTR::null();

    let cmd: PCWSTR = w!("C:\\WINDOWS\\system32\\cmd.exe /c whoami /all").into();
    let lpcommandline: PWSTR = PWSTR::from_raw(cmd.as_ptr() as *mut u16);


    let dwcreationflags = CREATE_UNICODE_ENVIRONMENT;

    let lpcurrentdirectory: PCWSTR = w!("C:\\Windows\\system32").into();


    let handler_setup = runas_setup_std_handles_for_process().unwrap();
    let mut si = handler_setup.0;
    let output_read_handler = handler_setup.3;

    let mut pi: PROCESS_INFORMATION = Default::default();
    unsafe {
        if !CreateProcessWithLogonW(lpusername, lpdomain, lppassword, CREATE_PROCESS_LOGON_FLAGS::default(),
                                lpapplicationname, lpcommandline, dwcreationflags,
                                None, lpcurrentdirectory, &mut si, &mut pi).as_bool() {
            eprintln!("CreateProcessWithLogonW error");
        }
    };

    let r = unsafe { WaitForSingleObject(pi.hProcess.0, INFINITE) };
    if r != WAIT_OBJECT_0.0 {
        eprintln!("WaitForSingleObject: {:?}", Error::from_win32());
        exit(1);
    }

    read_output_from_pipe(output_read_handler)
}


fn read_output_from_pipe(h_read_pipe: HANDLE){

    const  BUFFER_SIZE_PIPE: u32 = 1048576_u32;
    let context: *mut c_void =ptr::null_mut();
    let mut bytes_read: u32 = 0;
    let bytes_read_ptr: *mut u32 = &mut bytes_read;

    unsafe {
        if !ReadFile(h_read_pipe, Some(context), BUFFER_SIZE_PIPE, Some(bytes_read_ptr), None).as_bool() {
            println!("No output received from the process.\r\n");
            return;
        }
    }
    let data = unsafe {
        CStr::from_ptr(context as *const c_char).to_string_lossy().into_owned()
    };
    let b = data.as_bytes();
    let s = match str::from_utf8(b) {
        Ok(v) => v,
        Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
    };
    println!("{}", s);


}


fn create_anonymous_pipe_everyone_access() -> Option<(HANDLE, HANDLE)> {
    const SA: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES{
        nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
        lpSecurityDescriptor: ptr::null_mut(),
        bInheritHandle: BOOL(1),
    };

    unsafe {
        const BUFFER_SIZE_PIPE: u32 = 1048576;
        let mut read_handle = MaybeUninit::uninit();
        let mut write_handle = MaybeUninit::uninit();
        if CreatePipe(read_handle.as_mut_ptr(), write_handle.as_mut_ptr(), Some(&SA), BUFFER_SIZE_PIPE).as_bool() {
            Some((read_handle.assume_init(), write_handle.assume_init()))
        } else {
            None
        }
    }
}

fn runas_setup_std_handles_for_process() -> Option<(STARTUPINFOW, HANDLE, HANDLE, HANDLE)> {
    let mut startup_info: STARTUPINFOW = Default::default();
    let h_current_process = unsafe { GetCurrentProcess() };

    let Some((output_read_tmp_local, output_write_local)) = create_anonymous_pipe_everyone_access() else {
        println!("Error Create pipe");
        return None;
    };

    let mut tmp_handle = MaybeUninit::uninit();
    let error_write_local: HANDLE;
    let output_read_local: HANDLE;
    unsafe {
        if !DuplicateHandle(h_current_process, output_write_local,
                            h_current_process, tmp_handle.as_mut_ptr(),
                            0, true, DUPLICATE_SAME_ACCESS).as_bool() {
            eprintln!("Error DuplicateHandle stderr write pipe");
            return None;
        }
        error_write_local = tmp_handle.clone().assume_init();

        if !DuplicateHandle(h_current_process, output_read_tmp_local,
                            h_current_process, tmp_handle.as_mut_ptr(),
                            0, true, DUPLICATE_SAME_ACCESS).as_bool() {
            eprintln!("Error DuplicateHandle stdout read pipe");
            return None;
        }
        output_read_local = tmp_handle.assume_init();

        CloseHandle(output_read_tmp_local);
        const LPMODE: NAMED_PIPE_MODE = PIPE_NOWAIT;

        if !SetNamedPipeHandleState(output_read_local, Some(&LPMODE), None, None ).as_bool(){
            eprintln!("Error SetNamedPipeHandleState");
            return None;
        }
    }
    startup_info.dwFlags = STARTF_USESTDHANDLES;
    startup_info.hStdOutput = output_write_local;
    startup_info.hStdError = error_write_local;

    return Some((startup_info, output_write_local, error_write_local, output_read_local))
}

Running the Rust code only gives me "No output received from the process". The comand did execute, a terminal did showup, but empty, so I guess the output was directed somewhere. I am not sure if I did something wrong at the read_output_from_pipe, or anywhere else.

Sorry for the long code, because I am not sure where is the problem, so I just included everything.
Would be great if somebody could have a look.
Thank you.

I got it. For any one looking to redirect console log with Windows API, check this out:

mod do_shell;

use std::mem::{size_of, MaybeUninit};
use std::process::{exit};

use std::ptr;
use std::str;

use std::iter::FromIterator;

use windows::core::{Error, PWSTR, PCWSTR};
use windows::imp::WaitForSingleObject;
use windows::w;
use windows::Win32::Security::SECURITY_ATTRIBUTES;
use windows::Win32::Foundation::{WAIT_OBJECT_0, HANDLE, BOOL, CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS};
use windows::Win32::System::Threading::{CreateProcessWithLogonW, GetCurrentProcess,
                                        STARTUPINFOW, PROCESS_INFORMATION, CREATE_PROCESS_LOGON_FLAGS,
                                        CREATE_UNICODE_ENVIRONMENT, CREATE_NO_WINDOW, STARTF_USESTDHANDLES,
                                        INFINITE};
use windows::Win32::System::Pipes::{SetNamedPipeHandleState, CreatePipe, NAMED_PIPE_MODE, PIPE_NOWAIT};
use windows::Win32::Storage::FileSystem::ReadFile;

const BUFFER_SIZE_PIPE: u32 = 48576;


fn main() {
    let lpusername: PCWSTR = w!("test").into();
    let lpdomain: PCWSTR = w!("").into();
    let lppassword: PCWSTR = w!("1").into();
    let lpapplicationname: PCWSTR = PCWSTR::null();

    let cmd: PCWSTR = w!("C:\\WINDOWS\\system32\\cmd.exe /c whoami /all").into();
    let lpcommandline: PWSTR = PWSTR::from_raw(cmd.as_ptr() as *mut u16);


    let lpcurrentdirectory: PCWSTR = w!("C:\\Windows\\system32").into();


    let (mut startup_info, h_output_write, h_error_write, output_read_handler) = runas_setup_std_handles_for_process().unwrap();


    let mut pi: PROCESS_INFORMATION = Default::default();
    unsafe {
        if !CreateProcessWithLogonW(lpusername, lpdomain, lppassword, CREATE_PROCESS_LOGON_FLAGS::default(),
                                    lpapplicationname, lpcommandline, CREATE_UNICODE_ENVIRONMENT,
                                    None, lpcurrentdirectory, &mut startup_info, &mut pi).as_bool() {
            eprintln!("CreateProcessWithLogonW error");
        }
    };

    let r = unsafe { WaitForSingleObject(pi.hProcess.0, INFINITE) };
    if r != WAIT_OBJECT_0.0 {
        eprintln!("WaitForSingleObject: {:?}", Error::from_win32());
        exit(1);
    }

    unsafe { CloseHandle(h_output_write); }
    unsafe { CloseHandle(h_error_write); }

    read_output_from_pipe(output_read_handler)
}


fn read_output_from_pipe(h_read_pipe: HANDLE) {
    let mut bytes_read: u32 = 0;


    let mut buffer = [0u8; BUFFER_SIZE_PIPE as usize];

    unsafe {
        if !ReadFile(h_read_pipe, Some(buffer.as_mut_ptr() as _), BUFFER_SIZE_PIPE, Some(&mut bytes_read), None).as_bool() {
            println!("No output received from the process.\r\n");
            return;
        } else {
            println!("ĐÃ ĐỌC")
        }
    }
    let part = Vec::from_iter(buffer[0..bytes_read as usize].iter().cloned());
    let s = match str::from_utf8(&part) {
        Ok(v) => v,
        Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
    };
    println!("{}", s);
}


fn create_anonymous_pipe_everyone_access() -> Option<(HANDLE, HANDLE)> {
    const SA: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES {
        nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
        lpSecurityDescriptor: ptr::null_mut(),
        bInheritHandle: BOOL(1),
    };

    unsafe {
        let mut read_handle = MaybeUninit::uninit();
        let mut write_handle = MaybeUninit::uninit();
        if CreatePipe(read_handle.as_mut_ptr(), write_handle.as_mut_ptr(), Some(&SA), BUFFER_SIZE_PIPE).as_bool() {
            Some((read_handle.assume_init(), write_handle.assume_init()))
        } else {
            None
        }
    }
}

fn runas_setup_std_handles_for_process() -> Option<(STARTUPINFOW, HANDLE, HANDLE, HANDLE)> {
    let mut startup_info: STARTUPINFOW = Default::default();
    let h_current_process = unsafe { GetCurrentProcess() };

    let Some((output_read_tmp_local, output_write_local)) = create_anonymous_pipe_everyone_access() else {
        println!("Error Create pipe");
        return None;
    };

    let mut tmp_handle_err_write = MaybeUninit::uninit();
    let error_write_local: HANDLE;

    let mut tmp_handle_out_read = MaybeUninit::uninit();
    let output_read_local: HANDLE;

    unsafe {
        if !DuplicateHandle(h_current_process, output_write_local,
                            h_current_process, tmp_handle_err_write.as_mut_ptr(),
                            0, true, DUPLICATE_SAME_ACCESS).as_bool() {
            eprintln!("Error DuplicateHandle stderr write pipe");
            return None;
        }
        error_write_local = tmp_handle_err_write.assume_init();

        if !DuplicateHandle(h_current_process, output_read_tmp_local,
                            h_current_process, tmp_handle_out_read.as_mut_ptr(),
                            0, true, DUPLICATE_SAME_ACCESS).as_bool() {
            eprintln!("Error DuplicateHandle stdout read pipe");
            return None;
        }
        output_read_local = tmp_handle_out_read.assume_init();

        CloseHandle(output_read_tmp_local);

        const LPMODE: NAMED_PIPE_MODE = PIPE_NOWAIT;

        if !SetNamedPipeHandleState(output_read_local, Some(&LPMODE), None, None).as_bool() {
            eprintln!("Error SetNamedPipeHandleState");
            return None;
        }
    }
    startup_info.dwFlags = STARTF_USESTDHANDLES;
    startup_info.hStdOutput = output_write_local;
    startup_info.hStdError = error_write_local;

    return Some((startup_info, output_write_local, error_write_local, output_read_local));
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.