spark
spark

Reputation: 531

How do I write Powershell Wrapper in C#?

I'm trying to write general interactive shell wrapper class from c#/.Net using CreateProcess() for Dos, Powershell, Plink, etc, and I've found the article Why does StandardOutput.Read() block when StartInfo.RedirectStandardInput is set to true? to write wrapper classes which redirect child process's stdin/stdout for getting powershell command execution output. As he directed, I got his source code from http://sixfeetsix.blogspot.com/2012/08/interacting-with-sub-processed-shell-in.html. However, any command line execution such as plink.exe, cmd.exe works great for redirection of stdin/stdout but powershell.exe doesn't get me the input/output correctly. It looks like powershell.exe doesn't inherit from parant process and thread for pipe. Would you give me any feedbacks for getting retValue as of "get-help" powershell command successfully?

When I run "dir" command from DosShell, it returns the output of dos command "dir" successfully. However, Powershell doesn't return "get-help" command from powershell prompt.

using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Diagnostics;


namespace PowershellWrapperPOC
{
    class Program
    {
        static void Main(string[] args)
        {
            StringBuilder testOutput = new StringBuilder();
            string retValue = null;

            DosShell ds = new DosShell();
            retValue = ds.Start(@"C:\Windows\System32\cmd.exe /k", @"C:\Windows\System32\");
            testOutput.Append(retValue.ToString());

            retValue = ds.SendAndReceive("dir");
            testOutput.Append(retValue.ToString());

            ds.Terminate();
            Debug.Print(testOutput.ToString());

            testOutput = new StringBuilder();

            PowerShell ps = new PowerShell();
            retValue = ps.Start(@"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe", @"C:\Windows\System32\");
            testOutput.Append(retValue.ToString());

            retValue = ps.SendAndReceive("get-help");
            testOutput.Append(retValue.ToString());

            ps.Terminate();
            Debug.Print(testOutput.ToString());
        }
    }

    public class PowerShell : CommonShell
    {
        public PowerShell()
        {
            base.SetPrintSendCommand(false);
            base.SetEncoding("utf8");
            base.SetExitCommand("exit");
            base.SetPrompts("> ; ");
        }
    }

    public class DosShell : CommonShell
    {
        public DosShell()
        {
            base.SetPrintSendCommand(false);
            base.SetEncoding("utf8");
            base.SetExitCommand("exit");
            base.SetPrompts(">");
        }

    }

    public class CommonShell : ShellProcess
    {
        private StringBuilder _strOutput;

        private StringBuilder _strLastOutput;

        private static string _escapeCharsPattern = "[\\[|\\(][0-9;?]*[^0-9;]";

        private int _timeout;

        public CommonShell()
        {
            _strOutput = new StringBuilder();
            _strLastOutput = new StringBuilder();
        }

        public bool RemoveEscChars
        {
            get;
            set;
        }

        protected override string Prompt
        {
            get;
            set;
        }

        protected override string ExitCommand
        {
            get;
            set;
        }

        protected override Encoding Encoding
        {
            get;
            set;
        }

        public bool PrintSendCommand
        {
            get;
            set;
        }


        new public string Start(string applicationName, string workDirectory)
        {
            if (PrintSendCommand == true)
            {
                _strOutput.Append(applicationName + "\r\n");
                _strLastOutput.Append(applicationName + "\r\n");
            }

            var results = base.Start(applicationName, workDirectory);

            // if remove esc chars?
            if (RemoveEscChars == true)
            {
                string str = results.Item3;
                str = Regex.Replace(str, _escapeCharsPattern, "");
                _strOutput.Append(str);
                _strLastOutput.Append(str);
            }
            else
            {
                _strOutput.Append(results.Item3);
                _strLastOutput.Append(results.Item3);
            }

            return _strLastOutput.ToString();
        }

        new public string SendAndReceive(string toSend)
        {
            _strLastOutput = new StringBuilder();

            if (PrintSendCommand == true)
            {
                _strLastOutput.Append(toSend + "\r\n");
                _strOutput.Append(toSend + "\r\n");
            }

            // Wait forever till getting the expected prompt
            var results = base.SendAndReceive(toSend + "\r\n");

            // if remove esc chars?
            if (RemoveEscChars == true)
            {
                string str = results.Item3;
                str = Regex.Replace(str, _escapeCharsPattern, "");
                _strOutput.Append(str);
                _strLastOutput.Append(str);
            }
            else
            {
                _strOutput.Append(results.Item3);
                _strLastOutput.Append(results.Item3);
            }

            return _strLastOutput.ToString();
        }

        public void SetPrompts(string prompt)
        {
            var enc = this.Encoding;
            byte[] utfBytes = enc.GetBytes(prompt);
            Prompt = enc.GetString(utfBytes);
        }

        public void SetEncoding(string enc)
        {
            string l_enc = enc.ToLower();

            switch (l_enc)
            {
                case "utf8":
                    this.Encoding = Encoding.UTF8;
                    break;
                case "utf7":
                    this.Encoding = Encoding.UTF7;
                    break;
                case "utf32":
                    this.Encoding = Encoding.UTF32;
                    break;
                case "ascii":
                    this.Encoding = Encoding.ASCII;
                    break;
                default:
                    return;
            }
        }

        public void SetExitCommand(string command)
        {
            ExitCommand = command;
        }

        public void SetPrintSendCommand(bool bPrintSendCommand)
        {
            PrintSendCommand = bPrintSendCommand;
        }

        public void SetRemoveEscChars(bool bRemove)
        {
            RemoveEscChars = bRemove;
        }

        public void SetTimeout(int timeout)
        {
            this._timeout = timeout;
        }

        public void Flush()
        {
            _strOutput = new StringBuilder();
            _strLastOutput = new StringBuilder();

        }

        public string GetLastOutput()
        {
            return _strLastOutput.ToString();
        }

        public string GetOutput()
        {
            return _strLastOutput.ToString();
        }

    }

    /// <summary>
    /// Kernel32 Marshaling
    /// </summary>
    public static class Kernel32
    {
        /// <summary>
        /// HANDLE_FLAG_INHERIT
        /// 
        /// If this flag is set, a child process created with the bInheritHandles parameter of CreateProcess set to TRUE will inherit the object handle.
        /// 
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724935(v=vs.85).aspx
        /// </summary>
        public const int HANDLE_FLAG_INHERIT = 1;
        /// <summary>
        /// STARTF_USESTDHANDLES
        /// 
        /// The hStdInput, hStdOutput, and hStdError members contain additional information.
        /// If this flag is specified when calling one of the process creation functions, the handles must be inheritable and the function's 
        /// bInheritHandles parameter must be set to TRUE. For more information, see Handle Inheritance.
        /// 
        /// If this flag is specified when calling the GetStartupInfo function, these members are either the handle value specified during 
        /// process creation or INVALID_HANDLE_VALUE.
        /// 
        /// Handles must be closed with CloseHandle when they are no longer needed.
        /// This flag cannot be used with STARTF_USEHOTKEY.
        /// 
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx
        /// </summary>
        public const UInt32 STARTF_USESTDHANDLES = 0x00000100;
        /// <summary>
        /// STARTF_USESHOWWINDOW
        /// 
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx
        /// </summary>
        public const UInt32 STARTF_USESHOWWINDOW = 0x00000001;

        /// <summary>
        /// SECURITY_ATTRIBUTES
        /// </summary>
        public struct SECURITY_ATTRIBUTES
        {
            /// <summary>
            /// The size, in bytes, of this structure. Set this value to the size of the SECURITY_ATTRIBUTES structure.
            /// </summary>
            public int length;
            /// <summary>
            /// A pointer to a SECURITY_DESCRIPTOR structure that controls access to the object. 
            /// If the value of this member is NULL, the object is assigned the default security descriptor associated with the access token of the calling process. 
            /// This is not the same as granting access to everyone by assigning a NULL discretionary access control list (DACL). 
            /// By default, the default DACL in the access token of a process allows access only to the user represented by the access token.
            /// 
            /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa379560(v=vs.85).aspx
            /// </summary>
            public IntPtr lpSecurityDescriptor;
            /// <summary>
            /// A Boolean value that specifies whether the returned handle is inherited when a new process is created. 
            /// If this member is TRUE, the new process inherits the handle.
            /// </summary>
            [MarshalAs(UnmanagedType.Bool)]
            public bool bInheritHandle;
        }

        /// <summary>
        /// STARTUPINFO
        /// </summary>
        public struct STARTUPINFO
        {
            /// <summary>
            /// The size of the structure, in bytes.
            /// </summary>
            public uint cb;
            /// <summary>
            /// Reserved; must be NULL.
            /// </summary>
            public string lpReserved;
            /// <summary>
            /// The name of the desktop, or the name of both the desktop and window station for this process. 
            /// A backslash in the string indicates that the string includes both the desktop and window station names. 
            /// </summary>
            public string lpDesktop;
            /// <summary>
            /// For console processes, this is the title displayed in the title bar if a new console window is created. 
            /// If NULL, the name of the executable file is used as the window title instead. 
            /// This parameter must be NULL for GUI or console processes that do not create a new console window.
            /// </summary>
            public string lpTitle;
            /// <summary>
            /// If dwFlags specifies STARTF_USEPOSITION, this member is the x offset of the upper left corner of a window if a new window is created, in pixels. 
            /// Otherwise, this member is ignored.
            /// 
            /// The offset is from the upper left corner of the screen. For GUI processes, the specified position is used the first time the new process calls 
            /// CreateWindow to create an overlapped window if the x parameter of CreateWindow is CW_USEDEFAULT.
            /// </summary>
            public uint dwX;
            /// <summary>
            /// If dwFlags specifies STARTF_USEPOSITION, this member is the y offset of the upper left corner of a window if a new window is created, in pixels. 
            /// Otherwise, this member is ignored.
            /// 
            /// The offset is from the upper left corner of the screen. For GUI processes, the specified position is used the first time the new process calls 
            /// CreateWindow to create an overlapped window if the y parameter of CreateWindow is CW_USEDEFAULT.
            /// </summary>
            public uint dwY;
            /// <summary>
            /// If dwFlags specifies STARTF_USESIZE, this member is the width of the window if a new window is created, in pixels. 
            /// Otherwise, this member is ignored.
            /// 
            /// For GUI processes, this is used only the first time the new process calls CreateWindow to create an overlapped window 
            /// if the nWidth parameter of CreateWindow is CW_USEDEFAULT.
            /// </summary>
            public uint dwXSize;
            /// <summary>
            /// If dwFlags specifies STARTF_USESIZE, this member is the height of the window if a new window is created, in pixels. 
            /// Otherwise, this member is ignored.
            /// 
            /// For GUI processes, this is used only the first time the new process calls CreateWindow to create an overlapped window 
            /// if the nHeight parameter of CreateWindow is CW_USEDEFAULT.
            /// </summary>
            public uint dwYSize;
            /// <summary>
            /// If dwFlags specifies STARTF_USECOUNTCHARS, if a new console window is created in a console process, 
            /// this member specifies the screen buffer width, in character columns. Otherwise, this member is ignored.
            /// </summary>
            public uint dwXCountChars;
            /// <summary>
            /// If dwFlags specifies STARTF_USECOUNTCHARS, if a new console window is created in a console process, 
            /// this member specifies the screen buffer height, in character rows. Otherwise, this member is ignored.
            /// </summary>
            public uint dwYCountChars;
            /// <summary>
            /// If dwFlags specifies STARTF_USEFILLATTRIBUTE, this member is the initial text and background colors 
            /// if a new console window is created in a console application. Otherwise, this member is ignored.
            /// 
            /// This value can be any combination of the following values: 
            /// FOREGROUND_BLUE, FOREGROUND_GREEN, FOREGROUND_RED, FOREGROUND_INTENSITY, BACKGROUND_BLUE, BACKGROUND_GREEN, 
            /// BACKGROUND_RED, and BACKGROUND_INTENSITY. For example, the following combination of values produces red text on a white background:
            /// 
            /// FOREGROUND_RED| BACKGROUND_RED| BACKGROUND_GREEN| BACKGROUND_BLUE
            /// </summary>
            public uint dwFillAttribute;
            /// <summary>
            /// A bitfield that determines whether certain STARTUPINFO members are used when the process creates a window. 
            /// This member can be one or more of the following values.
            /// 
            /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx
            /// </summary>
            public uint dwFlags;
            /// <summary>
            /// If dwFlags specifies STARTF_USESHOWWINDOW, this member can be any of the values that can be specified in the nCmdShow parameter for the 
            /// ShowWindow function, except for SW_SHOWDEFAULT. Otherwise, this member is ignored.
            /// 
            /// For GUI processes, the first time ShowWindow is called, its nCmdShow parameter is ignored wShowWindow specifies the default value. 
            /// In subsequent calls to ShowWindow, the wShowWindow member is used if the nCmdShow parameter of ShowWindow is set to SW_SHOWDEFAULT.
            /// </summary>
            public short wShowWindow;
            /// <summary>
            /// Reserved for use by the C Run-time; must be zero.
            /// </summary>
            public short cbReserved2;
            /// <summary>
            /// Reserved for use by the C Run-time; must be NULL.
            /// </summary>
            public IntPtr lpReserved2;
            /// <summary>
            /// If dwFlags specifies STARTF_USESTDHANDLES, this member is the standard input handle for the process. 
            /// If STARTF_USESTDHANDLES is not specified, the default for standard input is the keyboard buffer.
            /// 
            /// If dwFlags specifies STARTF_USEHOTKEY, this member specifies a hotkey value that is sent as the wParam parameter of a 
            /// WM_SETHOTKEY message to the first eligible top-level window created by the application that owns the process. 
            /// If the window is created with the WS_POPUP window style, it is not eligible unless the WS_EX_APPWINDOW extended window style is also set. 
            /// 
            /// For more information, see CreateWindowEx.
            /// 
            /// Otherwise, this member is ignored.
            /// </summary>
            public IntPtr hStdInput;
            /// <summary>
            /// If dwFlags specifies STARTF_USESTDHANDLES, this member is the standard output handle for the process. 
            /// Otherwise, this member is ignored and the default for standard output is the console window's buffer.
            /// 
            /// If a process is launched from the taskbar or jump list, the system sets hStdOutput to a handle to the monitor 
            /// that contains the taskbar or jump list used to launch the process. For more information, see Remarks.
            /// 
            /// Windows 7, Windows Server 2008 R2, Windows Vista, Windows Server 2008, Windows XP, and Windows Server 2003:  
            /// This behavior was introduced in Windows 8 and Windows Server 2012.
            /// </summary>
            public IntPtr hStdOutput;
            /// <summary>
            /// If dwFlags specifies STARTF_USESTDHANDLES, this member is the standard error handle for the process. 
            /// Otherwise, this member is ignored and the default for standard error is the console window's buffer.
            /// </summary>
            public IntPtr hStdError;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms684873(v=vs.85).aspx
        /// </summary>
        public struct PROCESS_INFORMATION
        {
            /// <summary>
            /// A handle to the newly created process. The handle is used to specify the process in all functions that perform operations on the process object.
            /// </summary>
            public IntPtr hProcess;
            /// <summary>
            /// A handle to the primary thread of the newly created process. The handle is used to specify the thread in all functions that perform operations on the thread object.
            /// </summary>
            public IntPtr hThread;
            /// <summary>
            /// A value that can be used to identify a process. The value is valid from the time the process is created until all handles to the process are closed and the process object is freed; at this point, the identifier may be reused.
            /// </summary>
            public uint dwProcessId;
            /// <summary>
            /// A value that can be used to identify a thread. The value is valid from the time the thread is created until all handles to the thread are closed and the thread object is freed; at this point, the identifier may be reused.
            /// </summary>
            public uint dwThreadId;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
        /// </summary>
        /// <param name="lpApplicationName"></param>
        /// <param name="lpCommandLine"></param>
        /// <param name="lpProcessAttributes"></param>
        /// <param name="lpThreadAttributes"></param>
        /// <param name="bInheritHandles"></param>
        /// <param name="dwCreationFlags"></param>
        /// <param name="lpEnvironment"></param>
        /// <param name="lpCurrentDirectory"></param>
        /// <param name="lpStartupInfo"></param>
        /// <param name="lpProcessInformation"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CreateProcess(string lpApplicationName,
                                            string lpCommandLine,
                                            IntPtr lpProcessAttributes,
                                            IntPtr lpThreadAttributes,
                                            [MarshalAs(UnmanagedType.Bool)] bool bInheritHandles,
                                            uint dwCreationFlags,
                                            IntPtr lpEnvironment,
                                            string lpCurrentDirectory,
                                            ref STARTUPINFO lpStartupInfo,
                                            out PROCESS_INFORMATION lpProcessInformation);
        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx
        /// </summary>
        /// <param name="hObject"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa365152(v=vs.85).aspx
        /// </summary>
        /// <param name="hReadPipe"></param>
        /// <param name="hWritePipe"></param>
        /// <param name="lpPipeAttributes"></param>
        /// <param name="nSize"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CreatePipe(out IntPtr hReadPipe,
                                         out IntPtr hWritePipe,
                                         ref SECURITY_ATTRIBUTES lpPipeAttributes,
                                         uint nSize);
        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa365779(v=vs.85).aspx
        /// </summary>
        /// <param name="hNamedPipe"></param>
        /// <param name="pBuffer"></param>
        /// <param name="nBufferSize"></param>
        /// <param name="lpBytesRead"></param>
        /// <param name="lpTotalBytesAvail"></param>
        /// <param name="lpBytesLeftThisMessage"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern unsafe bool PeekNamedPipe(IntPtr hNamedPipe,
                                                   IntPtr pBuffer,
                                                   int nBufferSize,
                                                   IntPtr lpBytesRead,
                                                   int* lpTotalBytesAvail,
                                                   IntPtr lpBytesLeftThisMessage);

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa365467(v=vs.85).aspx
        /// </summary>
        /// <param name="hFile"></param>
        /// <param name="pBuffer"></param>
        /// <param name="nNumberOfBytesToRead"></param>
        /// <param name="lpNumberOfBytesRead"></param>
        /// <param name="lpOverlapped"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern unsafe bool ReadFile(IntPtr hFile,
                                              void* pBuffer,
                                              int nNumberOfBytesToRead,
                                              int* lpNumberOfBytesRead,
                                              IntPtr lpOverlapped);
        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa365747(v=vs.85).aspx
        /// </summary>
        /// <param name="hFile"></param>
        /// <param name="pBuffer"></param>
        /// <param name="nNumberOfBytesToWrite"></param>
        /// <param name="lpNumberOfBytesWritten"></param>
        /// <param name="lpOverlapped"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern unsafe bool WriteFile(IntPtr hFile,
                                               void* pBuffer,
                                               int nNumberOfBytesToWrite,
                                               int* lpNumberOfBytesWritten,
                                               IntPtr lpOverlapped);

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724935(v=vs.85).aspx
        /// </summary>
        /// <param name="hObject"></param>
        /// <param name="dwMask"></param>
        /// <param name="dwFlags"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetHandleInformation(IntPtr hObject, int dwMask, uint dwFlags);
    }

    public abstract class ShellProcess
    {
        IntPtr _hChildStdoutR, _hChildStdoutW, _hChildStderrR, _hChildStderrW, _hChildStdinR, _hChildStdinW;
        Kernel32.SECURITY_ATTRIBUTES _sa, _sa_process, _sa_thread;
        Kernel32.STARTUPINFO _si;
        Kernel32.PROCESS_INFORMATION _pi;
        string _applicationName;

        protected abstract string Prompt { get; set; }

        protected abstract string ExitCommand { get; set; }

        protected abstract Encoding Encoding { get; set; }

        static unsafe int Write(IntPtr h, byte[] buffer, int index, int count)
        {
            int n = 0;
            fixed (byte* p = buffer)
            {
                if (!Kernel32.WriteFile(h, p + index, count, &n, IntPtr.Zero))
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            return n;
        }

        static unsafe int Peek(IntPtr h)
        {
            int n = 0;
            if (!Kernel32.PeekNamedPipe(h, IntPtr.Zero, 0, IntPtr.Zero, &n, IntPtr.Zero))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            return n;
        }

        static unsafe int Read(IntPtr h, byte[] buffer, int index, int count)
        {
            int n = 0;
            fixed (byte* p = buffer)
            {
                if (!Kernel32.ReadFile(h, p + index, count, &n, IntPtr.Zero))
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            return n;
        }

        public virtual void SendCommand(string s)
        {
            byte[] bytesToWrite = Encoding.GetBytes(s);
            Write(_hChildStdinW, bytesToWrite, 0, bytesToWrite.Length);
        }

        Tuple<string, string, string> ReadToPrompt()
        {
            StringBuilder strOutput = new StringBuilder();

            const int bufferLength = 128;
            byte[] buffer = new byte[bufferLength];
            int bytesReadCount;
            var stdOut = new StringBuilder(4096);
            var stdErr = new StringBuilder();

            string[] prompts = Prompt.Split(';');
            bool foundPrompt = false;

            while (!foundPrompt)
            {
                while (Peek(_hChildStdoutR) > 0)
                {
                    bytesReadCount = Read(_hChildStdoutR, buffer, 0, bufferLength);
                    stdOut.Append(Encoding.GetString(buffer, 0, bytesReadCount));
                    strOutput.Append(Encoding.GetString(buffer, 0, bytesReadCount));
                }

                foreach (string prompt in prompts)
                {
                    if (stdOut.ToString().Contains(prompt))
                    {
                        foundPrompt = true;
                        break;
                    }
                }
                //strOutput.Append(stdOut);

                while (Peek(_hChildStderrR) > 0)
                {
                    bytesReadCount = Read(_hChildStderrR, buffer, 0, bufferLength);
                    stdErr.Append(Encoding.GetString(buffer, 0, bytesReadCount));
                    strOutput.Append(Encoding.GetString(buffer, 0, bytesReadCount));
                }

                foreach (string prompt in prompts)
                {
                    if (stdErr.ToString().Contains(prompt))
                    {
                        foundPrompt = true;
                        break;
                    }
                }
                //strOutput.Append(stdErr);

                Thread.Sleep(20);
            }

            while (Peek(_hChildStderrR) > 0)
            {
                bytesReadCount = Read(_hChildStderrR, buffer, 0, bufferLength);
                stdErr.Append(Encoding.GetString(buffer, 0, bytesReadCount));
                strOutput.Append(Encoding.GetString(buffer, 0, bytesReadCount));
            }

            return new Tuple<string, string, string>(stdOut.ToString(), stdErr.ToString(), strOutput.ToString());
        }

        public virtual Tuple<string, string, string> SendAndReceive(string toSend)
        {
            SendCommand(toSend);
            return ReadToPrompt();
        }

        public virtual Tuple<string, string, string> Start(string applicationName, string workDirectory)
        {
            _sa = new Kernel32.SECURITY_ATTRIBUTES
            {
                bInheritHandle = true,
                lpSecurityDescriptor = IntPtr.Zero,
                length = Marshal.SizeOf(typeof(Kernel32.SECURITY_ATTRIBUTES))
            };
            _sa.lpSecurityDescriptor = IntPtr.Zero;

            _sa_process = new Kernel32.SECURITY_ATTRIBUTES
            {
                bInheritHandle = true,
                lpSecurityDescriptor = IntPtr.Zero,
                length = Marshal.SizeOf(typeof(Kernel32.SECURITY_ATTRIBUTES))
            };
            _sa_process.lpSecurityDescriptor = IntPtr.Zero;

            _sa_thread = new Kernel32.SECURITY_ATTRIBUTES
            {
                bInheritHandle = true,
                lpSecurityDescriptor = IntPtr.Zero,
                length = Marshal.SizeOf(typeof(Kernel32.SECURITY_ATTRIBUTES))
            };
            _sa_thread.lpSecurityDescriptor = IntPtr.Zero;


            if (!Kernel32.CreatePipe(out _hChildStdoutR, out _hChildStdoutW, ref _sa, 0))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.CreatePipe(out _hChildStderrR, out _hChildStderrW, ref _sa, 0))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.CreatePipe(out _hChildStdinR, out _hChildStdinW, ref _sa, 0))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.SetHandleInformation(_hChildStdoutR, Kernel32.HANDLE_FLAG_INHERIT, 0))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.SetHandleInformation(_hChildStderrR, Kernel32.HANDLE_FLAG_INHERIT, 0))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.SetHandleInformation(_hChildStdinW, Kernel32.HANDLE_FLAG_INHERIT, 0))
                throw new Win32Exception(Marshal.GetLastWin32Error());

            _si = new Kernel32.STARTUPINFO
            {
                wShowWindow = 0,
                dwFlags = Kernel32.STARTF_USESTDHANDLES | Kernel32.STARTF_USESHOWWINDOW,
                hStdOutput = _hChildStdoutW,
                hStdError = _hChildStderrW,
                hStdInput = _hChildStdinR
            };

            _si.cb = (uint)Marshal.SizeOf(_si);
            _pi = new Kernel32.PROCESS_INFORMATION();

            if (!Kernel32.CreateProcess(null, applicationName, IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, workDirectory, ref _si, out _pi))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            _applicationName = applicationName;
            return ReadToPrompt();
        }

        public void Terminate()
        {
            SendCommand(ExitCommand);
            if (!Kernel32.CloseHandle(_hChildStderrW))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.CloseHandle(_hChildStdoutW))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.CloseHandle(_hChildStdinW))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.CloseHandle(_pi.hProcess))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!Kernel32.CloseHandle(_pi.hThread))
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }
}

Upvotes: 1

Views: 3693

Answers (2)

Lars Truijens
Lars Truijens

Reputation: 43595

It sounds like you don't want to just call powershell commands from C#, but to act as a powershell host. Being a powershell host allows you to receive the output from commands like Write-Host and Out-String and just have a stream of strings as input and output. Examples of hosts are powershell.exe and powershell_ise.exe, but it can be a lot of work and won't make processing the results of powershell commands easier, only harder. The power of powershell is that the input and output of commands are real objects, not just strings. You can read more about it here.

If you just want to execute some powershell commands and receive their output you would be better of with this here and work with the objects the powershell commands are returning instead of wanting them to be strings. This means you have to stop using commands like Write-Host, Out-String, Format-Table, etc. Those are for formatting, not for processing results.

Upvotes: 1

urlreader
urlreader

Reputation: 6605

powershell is different. I think you can not simple redirect its input and output. that's why we need to use runspace to get the output object. search "powershell c# example", lots code. here is a simple one:

Calling PowerShell From C#

depends on the powershell you use (exchange powers shell, sharepoint power shell..), you will need to add the correct snapin.

Upvotes: 3

Related Questions