src/programFrames/default.cs

// 由 Ingo Karstein 创建的简单 PowerShell 宿主 (http://blog.karstein-consulting.com)
// 由 Markus Scholtes 重构并添加 GUI 支持
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.IO;
#if !Pwsh20
    using System.Management.Automation.Language;
#endif
using System.Globalization;
using System.Management.Automation.Host;
using System.Security;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
#if noConsole
    using System.Windows.Forms;
    using System.Drawing;
#endif
using System.Runtime.Versioning;
 
// 不显示在属性对话框的详细信息选项卡中,但会嵌入到文件里
#if Resources
    [assembly: AssemblyDescription("$description")]
    [assembly: AssemblyCompany("$company")]
    [assembly: AssemblyTitle("$title")]
    [assembly: AssemblyProduct("$product")]
    [assembly: AssemblyCopyright("$copyright")]
    [assembly: AssemblyTrademark("$trademark")]
#endif
#if version
    [assembly: AssemblyVersion("$version")]
    [assembly: AssemblyFileVersion("$version")]
#endif
#if winFormsDPIAware
    [assembly: TargetFrameworkAttribute("$TargetFramework,Profile=Client")]
#endif
 
namespace PSRunnerNS {
    #if noConsole || credentialGUI
    internal class Credential_Form {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct CREDUI_INFO {
            public int cbSize;
            public IntPtr hwndParent;
            public string pszMessageText;
            public string pszCaptionText;
            public IntPtr hbmBanner;
        }
 
        [Flags]
        enum CREDUI_FLAGS {
            INCORRECT_PASSWORD = 0x1,
            DO_NOT_PERSIST = 0x2,
            REQUEST_ADMINISTRATOR = 0x4,
            EXCLUDE_CERTIFICATES = 0x8,
            REQUIRE_CERTIFICATE = 0x10,
            SHOW_SAVE_CHECK_BOX = 0x40,
            ALWAYS_SHOW_UI = 0x80,
            REQUIRE_SMARTCARD = 0x100,
            PASSWORD_ONLY_OK = 0x200,
            VALIDATE_USERNAME = 0x400,
            COMPLETE_USERNAME = 0x800,
            PERSIST = 0x1000,
            SERVER_CREDENTIAL = 0x4000,
            EXPECT_CONFIRMATION = 0x20000,
            GENERIC_CREDENTIALS = 0x40000,
            USERNAME_TARGET_CREDENTIALS = 0x80000,
            KEEP_USERNAME = 0x100000,
        }
 
        public enum CredUI_ReturnCodes {
            NO_ERROR = 0,
            ERROR_CANCELLED = 1223,
            ERROR_NO_SUCH_LOGON_SESSION = 1312,
            ERROR_NOT_FOUND = 1168,
            ERROR_INVALID_ACCOUNT_NAME = 1315,
            ERROR_INSUFFICIENT_BUFFER = 122,
            ERROR_INVALID_PARAMETER = 87,
            ERROR_INVALID_FLAGS = 1004,
        }
 
        [DllImport("credui", CharSet = CharSet.Unicode)]
        private static extern CredUI_ReturnCodes CredUIPromptForCredentials(ref CREDUI_INFO credinfo,
            string targetName,
            IntPtr reserved1,
            int iError,
            StringBuilder userName,
            int maxUserName,
            StringBuilder password,
            int maxPassword,
            [MarshalAs(UnmanagedType.Bool)] ref bool pfSave,
            CREDUI_FLAGS flags);
 
        public class User_Pwd {
            public string User = string.Empty;
            public string Password = string.Empty;
            public string Domain = string.Empty;
        }
 
        internal static User_Pwd PromptForPassword(string caption, string message, string target, string user, PSCredentialTypes credTypes, PSCredentialUIOptions options) {
            // 初始化标志和变量
            StringBuilder userPassword = new StringBuilder("", 128), userID = new StringBuilder(user, 128);
            CREDUI_INFO credUI = new CREDUI_INFO();
            if (!string.IsNullOrEmpty(message)) credUI.pszMessageText = message;
            if (!string.IsNullOrEmpty(caption)) credUI.pszCaptionText = caption;
            credUI.cbSize = Marshal.SizeOf(credUI);
            bool save = false;
 
            CREDUI_FLAGS flags = CREDUI_FLAGS.DO_NOT_PERSIST;
            if ((credTypes & PSCredentialTypes.Generic) == PSCredentialTypes.Generic) {
                flags |= CREDUI_FLAGS.GENERIC_CREDENTIALS;
                if ((options & PSCredentialUIOptions.AlwaysPrompt) == PSCredentialUIOptions.AlwaysPrompt) {
                    flags |= CREDUI_FLAGS.ALWAYS_SHOW_UI;
                }
            }
 
            // 以图形提示向用户询问密码
            CredUI_ReturnCodes returnCode = CredUIPromptForCredentials(ref credUI, target, IntPtr.Zero, 0, userID, 128, userPassword, 128, ref save, flags);
 
            if (returnCode == CredUI_ReturnCodes.NO_ERROR) {
                User_Pwd ret = new User_Pwd();
                ret.User = userID.ToString();
                ret.Password = userPassword.ToString();
                ret.Domain = "";
                return ret;
            }
 
            return null;
        }
    }
    #endif
 
    internal class PSRunnerRawUI: PSHostRawUserInterface {
        #if noConsole
            // GUI 输出时的控制台颜色会被读取和设置,但目前尚未使用(供将来使用)
            private ConsoleColor _GUIBackgroundColor = ConsoleColor.White;
            private ConsoleColor _GUIForegroundColor = ConsoleColor.Black;
 
            #if noConsole
            private string _windowTitleData;
            public PSRunnerRawUI() {
                // 加载 assembly:AssemblyTitle
                AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute) Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyTitleAttribute));
                if (titleAttribute != null)
                    _windowTitleData = titleAttribute.Title;
                else
                    _windowTitleData = System.AppDomain.CurrentDomain.FriendlyName;
            }
            #endif
        #else
            const int STD_OUTPUT_HANDLE = -11;
 
            //CHAR_INFO 结构体早年是一个 union,因此我们用 LayoutKind.Explicit 尽量贴近它
            [StructLayout(LayoutKind.Explicit)]
            public struct CHAR_INFO {
                [FieldOffset(0)]
                internal char UnicodeChar;
                [FieldOffset(0)]
                internal char AsciiChar;
                [FieldOffset(2)] //2 字节似乎能正常工作
                internal UInt16 Attributes;
            }
 
            //COORD 结构体
            [StructLayout(LayoutKind.Sequential)]
            public struct COORD {
                public short X;
                public short Y;
            }
 
            //SMALL_RECT 结构体
            [StructLayout(LayoutKind.Sequential)]
            public struct SMALL_RECT {
                public short Left;
                public short Top;
                public short Right;
                public short Bottom;
            }
 
            /* 从控制台屏幕缓冲区的矩形字符单元格块读取字符与颜色属性数据,并把数据写入目标缓冲区指定位置的矩形块。 */
            [DllImport("Kernel32.dll", EntryPoint = "ReadConsoleOutputW", CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern bool ReadConsoleOutput(
                IntPtr hConsoleOutput,
                /* 该指针被视为 CHAR_INFO 结构二维数组的原点,数组大小由 dwBufferSize 参数指定。*/
                [MarshalAs(UnmanagedType.LPArray), Out] CHAR_INFO[, ] lpBuffer,
                COORD dwBufferSize,
                COORD dwBufferCoord,
                ref SMALL_RECT lpReadRegion);
 
            /* 把字符与颜色属性数据写入控制台屏幕缓冲区中指定的矩形字符单元格块。要写入的数据取自源缓冲区指定位置的相应大小矩形块。 */
            [DllImport("Kernel32.dll", EntryPoint = "WriteConsoleOutputW", CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern bool WriteConsoleOutput(
                IntPtr hConsoleOutput,
                /* 该指针被视为 CHAR_INFO 结构二维数组的原点,数组大小由 dwBufferSize 参数指定。*/
                [MarshalAs(UnmanagedType.LPArray), In] CHAR_INFO[, ] lpBuffer,
                COORD dwBufferSize,
                COORD dwBufferCoord,
                ref SMALL_RECT lpWriteRegion);
 
            /* 在屏幕缓冲区中移动一块数据。移动效果可用裁剪矩形加以限制,裁剪矩形之外的屏幕缓冲区内容保持不变。 */
            [DllImport("Kernel32.dll", SetLastError = true)]
            static extern bool ScrollConsoleScreenBuffer(
                IntPtr hConsoleOutput,
                [In] ref SMALL_RECT lpScrollRectangle,
                [In] ref SMALL_RECT lpClipRectangle,
                COORD dwDestinationOrigin,
                [In] ref CHAR_INFO lpFill);
 
            [DllImport("Kernel32.dll", SetLastError = true)]
            static extern IntPtr GetStdHandle(int nStdHandle);
        #endif
 
        public override ConsoleColor BackgroundColor {
            #if !noConsole
                get { return Console.BackgroundColor; }
                set { Console.BackgroundColor = value; }
            #else
                get { return _GUIBackgroundColor; }
                set { _GUIBackgroundColor = value; }
            #endif
        }
 
        public override System.Management.Automation.Host.Size BufferSize {
            get {
                #if !noConsole
                    if (!Console_Info.IsOutputRedirected())
                        return new System.Management.Automation.Host.Size(Console.BufferWidth, Console.BufferHeight);
                #endif
                // 返回默认值。如果没有返回有效值,WriteLine 将不会被调用
                return new System.Management.Automation.Host.Size(120, 50);
            }
            set {
                #if !noConsole
                    Console.BufferWidth = value.Width;
                    Console.BufferHeight = value.Height;
                #endif
            }
        }
 
        public override Coordinates CursorPosition {
            get {
                return new Coordinates(
                #if !noConsole
                    Console.CursorLeft, Console.CursorTop
                #else
                    // 为 WinForms 返回一个虚拟值。
                    0, 0
                #endif
                );
            }
            set {
                #if !noConsole
                    Console.CursorTop = value.Y;
                    Console.CursorLeft = value.X;
                #endif
            }
        }
 
        public override int CursorSize {
            get {
                return
                    #if !noConsole
                        Console.CursorSize
                    #else
                        // 为 WinForms 返回一个虚拟值。
                        25
                    #endif
                ;
            }
            set {
                #if !noConsole
                    Console.CursorSize = value;
                #endif
            }
        }
 
        #if noConsole
            private Form Invisible_Form;
        #endif
 
        public override void FlushInputBuffer() {
            #if !noConsole
            if (!Console_Info.IsInputRedirected()) {
                while (Console.KeyAvailable)
                    Console.ReadKey(true);
            }
            #else
            if (Invisible_Form != null) {
                Invisible_Form.Close();
                Invisible_Form = null;
            } else {
                Invisible_Form = new Form();
                Invisible_Form.Opacity = 0;
                Invisible_Form.ShowInTaskbar = false;
                Invisible_Form.Visible = true;
            }
            #endif
        }
 
        public override ConsoleColor ForegroundColor {
            #if !noConsole
                get { return Console.ForegroundColor; }
                set { Console.ForegroundColor = value; }
            #else
                get { return _GUIForegroundColor; }
                set { _GUIForegroundColor = value; }
            #endif
        }
 
        public override BufferCell[, ] GetBufferContents(System.Management.Automation.Host.Rectangle rectangle) {
            #if !noConsole
            IntPtr hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
            CHAR_INFO[, ] buffer = new CHAR_INFO[rectangle.Bottom - rectangle.Top + 1, rectangle.Right - rectangle.Left + 1];
            COORD buffer_size = new COORD {
                X = (short)(rectangle.Right - rectangle.Left + 1), Y = (short)(rectangle.Bottom - rectangle.Top + 1)
            };
            COORD buffer_index = new COORD {
                X = 0, Y = 0
            };
            SMALL_RECT screen_rect = new SMALL_RECT {
                Left = (short) rectangle.Left, Top = (short) rectangle.Top, Right = (short) rectangle.Right, Bottom = (short) rectangle.Bottom
            };
 
            ReadConsoleOutput(hStdOut, buffer, buffer_size, buffer_index, ref screen_rect);
 
            System.Management.Automation.Host.BufferCell[, ] ScreenBuffer = new System.Management.Automation.Host.BufferCell[rectangle.Bottom - rectangle.Top + 1, rectangle.Right - rectangle.Left + 1];
            for (int y = 0; y <= rectangle.Bottom - rectangle.Top; y++)
                for (int x = 0; x <= rectangle.Right - rectangle.Left; x++) {
                    ScreenBuffer[y, x] = new System.Management.Automation.Host.BufferCell(buffer[y, x].AsciiChar, (System.ConsoleColor)(buffer[y, x].Attributes & 0xF), (System.ConsoleColor)((buffer[y, x].Attributes & 0xF0) / 0x10), System.Management.Automation.Host.BufferCellType.Complete);
                }
 
            return ScreenBuffer;
            #else
            System.Management.Automation.Host.BufferCell[, ] ScreenBuffer = new System.Management.Automation.Host.BufferCell[rectangle.Bottom - rectangle.Top + 1, rectangle.Right - rectangle.Left + 1];
 
            for (int y = 0; y <= rectangle.Bottom - rectangle.Top; y++)
                for (int x = 0; x <= rectangle.Right - rectangle.Left; x++) {
                    ScreenBuffer[y, x] = new System.Management.Automation.Host.BufferCell(' ', _GUIForegroundColor, _GUIBackgroundColor, System.Management.Automation.Host.BufferCellType.Complete);
                }
 
            return ScreenBuffer;
            #endif
        }
 
        public override bool KeyAvailable {
            get {
                return
                    #if !noConsole
                        Console.KeyAvailable
                    #else
                        true
                    #endif
                ;
            }
        }
 
        public override System.Management.Automation.Host.Size MaxPhysicalWindowSize {
            get {
                return new System.Management.Automation.Host.Size(
                    #if !noConsole
                        Console.LargestWindowWidth, Console.LargestWindowHeight
                    #else
                        // WinForms 的虚拟值
                        240, 84
                    #endif
                );
            }
        }
 
        public override System.Management.Automation.Host.Size MaxWindowSize {
            get {
                return new System.Management.Automation.Host.Size(
                    #if !noConsole
                        Console.BufferWidth, Console.BufferWidth
                    #else
                        // WinForms 的虚拟值
                        120, 84
                    #endif
                );
            }
        }
 
        public override KeyInfo ReadKey(ReadKeyOptions options) {
            #if !noConsole
            ConsoleKeyInfo info = Console.ReadKey((options & ReadKeyOptions.NoEcho) != 0);
 
            ControlKeyStates state = 0;
            if ((info.Modifiers & ConsoleModifiers.Alt) != 0)
                state |= ControlKeyStates.LeftAltPressed | ControlKeyStates.RightAltPressed;
            if ((info.Modifiers & ConsoleModifiers.Control) != 0)
                state |= ControlKeyStates.LeftCtrlPressed | ControlKeyStates.RightCtrlPressed;
            if ((info.Modifiers & ConsoleModifiers.Shift) != 0)
                state |= ControlKeyStates.ShiftPressed;
            if (Console.CapsLock)
                state |= ControlKeyStates.CapsLockOn;
            if (Console.NumberLock)
                state |= ControlKeyStates.NumLockOn;
 
            return new KeyInfo((int) info.Key, info.KeyChar, state, (options & ReadKeyOptions.IncludeKeyDown) != 0);
            #else
            if ((options & ReadKeyOptions.IncludeKeyDown) != 0)
                return ReadKey_Box.Show(_windowTitleData, "", true);
            else
                return ReadKey_Box.Show(_windowTitleData, "", false);
            #endif
        }
 
        public override void ScrollBufferContents(System.Management.Automation.Host.Rectangle source, Coordinates destination, System.Management.Automation.Host.Rectangle clip, BufferCell fill) { // 未实现目标块裁剪
            #if !noConsole
            // 裁剪区域超出源范围?
            if ((source.Left > clip.Right) || (source.Right < clip.Left) || (source.Top > clip.Bottom) || (source.Bottom < clip.Top)) { // 裁剪超出范围 -> 无需处理
                return;
            }
 
            IntPtr hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
            SMALL_RECT lpScrollRectangle = new SMALL_RECT {
                Left = (short) source.Left, Top = (short) source.Top, Right = (short)(source.Right), Bottom = (short)(source.Bottom)
            };
            SMALL_RECT lpClipRectangle;
            if (clip != null) {
                lpClipRectangle = new SMALL_RECT {
                    Left = (short) clip.Left, Top = (short) clip.Top, Right = (short)(clip.Right), Bottom = (short)(clip.Bottom)
                };
            } else {
                lpClipRectangle = new SMALL_RECT {
                    Left = (short) 0, Top = (short) 0, Right = (short)(Console.WindowWidth - 1), Bottom = (short)(Console.WindowHeight - 1)
                };
            }
            COORD dwDestinationOrigin = new COORD {
                X = (short)(destination.X), Y = (short)(destination.Y)
            };
            CHAR_INFO lpFill = new CHAR_INFO {
                AsciiChar = fill.Character, Attributes = (ushort)((int)(fill.ForegroundColor) + ((int)(fill.BackgroundColor) * 16))
            };
 
            ScrollConsoleScreenBuffer(hStdOut, ref lpScrollRectangle, ref lpClipRectangle, dwDestinationOrigin, ref lpFill);
            #endif
        }
 
        public override void SetBufferContents(System.Management.Automation.Host.Rectangle rectangle, BufferCell fill) {
            #if !noConsole
            // 用一个小技巧:把缓冲区移出屏幕,源区域便会被 fill.Character 字符填充
            if (rectangle.Left >= 0)
                Console.MoveBufferArea(rectangle.Left, rectangle.Top, rectangle.Right - rectangle.Left + 1, rectangle.Bottom - rectangle.Top + 1, BufferSize.Width, BufferSize.Height, fill.Character, fill.ForegroundColor, fill.BackgroundColor);
            else { // Clear-Host:把所有内容移出屏幕
                Console.MoveBufferArea(0, 0, BufferSize.Width, BufferSize.Height, BufferSize.Width, BufferSize.Height, fill.Character, fill.ForegroundColor, fill.BackgroundColor);
            }
            #endif
        }
 
        public override void SetBufferContents(Coordinates origin, BufferCell[, ] contents) {
            #if !noConsole
            IntPtr hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
            CHAR_INFO[, ] buffer = new CHAR_INFO[contents.GetLength(0), contents.GetLength(1)];
            COORD buffer_size = new COORD {
                X = (short)(contents.GetLength(1)), Y = (short)(contents.GetLength(0))
            };
            COORD buffer_index = new COORD {
                X = 0, Y = 0
            };
            SMALL_RECT screen_rect = new SMALL_RECT {
                Left = (short) origin.X, Top = (short) origin.Y, Right = (short)(origin.X + contents.GetLength(1) - 1), Bottom = (short)(origin.Y + contents.GetLength(0) - 1)
            };
 
            for (int y = 0; y < contents.GetLength(0); y++)
                for (int x = 0; x < contents.GetLength(1); x++) {
                    buffer[y, x] = new CHAR_INFO {
                        AsciiChar = contents[y, x].Character, Attributes = (ushort)((int)(contents[y, x].ForegroundColor) + ((int)(contents[y, x].BackgroundColor) * 16))
                    };
                }
 
            WriteConsoleOutput(hStdOut, buffer, buffer_size, buffer_index, ref screen_rect);
            #endif
        }
 
        public override Coordinates WindowPosition {
            get {
                return new Coordinates(
                    #if !noConsole
                        Console.WindowLeft, Console.WindowTop
                    #else
                        // WinForms 的虚拟值
                        0, 0
                    #endif
                );
            }
            set {
                #if !noConsole
                Console.WindowLeft = value.X;
                Console.WindowTop = value.Y;
                #endif
            }
        }
 
        public override System.Management.Automation.Host.Size WindowSize {
            get {
                return new System.Management.Automation.Host.Size(
                #if !noConsole
                    Console.WindowWidth, Console.WindowHeight
                #else
                    // WinForms 的虚拟值
                    120, 50
                #endif
                );
            }
            set {
                #if !noConsole
                Console.WindowWidth = value.Width;
                Console.WindowHeight = value.Height;
                #endif
            }
        }
 
        public override string WindowTitle {
            get {
                return
                #if !noConsole
                    Console.Title
                #else
                    _windowTitleData
                #endif
                ;
            }
            set {
                #if !noConsole
                    Console.Title
                #else
                    _windowTitleData
                #endif
                = value;
            }
        }
    }
 
    #if noConsole
    public class Input_Box {
        [DllImport("user32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr MB_GetString(uint strId);
 
        public static DialogResult Show(string strTitle, string strPrompt, ref string strVal, bool blSecure) {
            // 生成控件
            Form form = new Form();
            form.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            form.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            Label label = new Label();
            TextBox textBox = new TextBox();
            Button buttonOk = new Button();
            Button buttonCancel = new Button();
 
            // 尺寸和位置根据标签确定,必须先完成这个控件
            if (string.IsNullOrEmpty(strPrompt)) {
                if (blSecure)
                    strPrompt = "Secure input:";
                else
                    strPrompt = "Input:";
            }
            label.Text = strPrompt.PadRight(16);
            label.Location = new Point(9, 19);
            label.MaximumSize = new System.Drawing.Size(System.Windows.Forms.Screen.FromControl(form).Bounds.Width * 5 / 8 - 18, 0);
            label.AutoSize = true;
            // 标签的尺寸要到 Add() 之后才能确定
            form.Controls.Add(label);
 
            // 生成文本框
            if (blSecure) textBox.UseSystemPasswordChar = true;
            textBox.Text = strVal;
            textBox.SetBounds(12, label.Bottom, label.Right - 12, 20);
 
            // 生成按钮,并获取本地化的 "OK" 字符串
            string sTextOK = Marshal.PtrToStringUni(MB_GetString(0));
            if (string.IsNullOrEmpty(sTextOK))
                buttonOk.Text = "OK";
            else
                buttonOk.Text = sTextOK;
 
            // 获取本地化的 "Cancel" 字符串
            string sTextCancel = Marshal.PtrToStringUni(MB_GetString(1));
            if (string.IsNullOrEmpty(sTextCancel))
                buttonCancel.Text = "Cancel";
            else
                buttonCancel.Text = sTextCancel;
 
            buttonOk.DialogResult = DialogResult.OK;
            buttonCancel.DialogResult = DialogResult.Cancel;
            buttonOk.SetBounds(System.Math.Max(12, label.Right - 158), label.Bottom + 36, 75, 23);
            buttonCancel.SetBounds(System.Math.Max(93, label.Right - 77), label.Bottom + 36, 75, 23);
 
            // 配置窗体
            form.Text = strTitle;
            form.ClientSize = new System.Drawing.Size(System.Math.Max(178, label.Right + 10), label.Bottom + 71);
            form.Controls.AddRange(new Control[] {
                textBox,
                buttonOk,
                buttonCancel
            });
            form.FormBorderStyle = FormBorderStyle.FixedDialog;
            form.StartPosition = FormStartPosition.CenterScreen;
            try {
                form.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);
            } catch {}
            form.MinimizeBox = false;
            form.MaximizeBox = false;
            form.AcceptButton = buttonOk;
            form.CancelButton = buttonCancel;
 
            // 显示窗体并计算结果
            DialogResult dialogResult = form.ShowDialog();
            strVal = textBox.Text;
            return dialogResult;
        }
 
        public static DialogResult Show(string strTitle, string strPrompt, ref string strVal) {
            return Show(strTitle, strPrompt, ref strVal, false);
        }
    }
 
    public class Choice_Box {
        public static int Show(System.Collections.ObjectModel.Collection<ChoiceDescription> arrChoice, int intDefault, string strTitle, string strPrompt) {
            // 数组为空则取消
            if (arrChoice == null) return -1;
            if (arrChoice.Count < 1) return -1;
 
            // 生成控件
            Form form = new Form();
            form.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            form.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            RadioButton[] aradioButton = new RadioButton[arrChoice.Count];
            ToolTip toolTip = new ToolTip();
            Button buttonOk = new Button();
 
            // 尺寸和位置根据标签确定,有提示时必须先完成这个控件
            int iPosY = 19, iMaxX = 0;
            if (!string.IsNullOrEmpty(strPrompt)) {
                Label label = new Label();
                label.Text = strPrompt;
                label.Location = new Point(9, 19);
                label.MaximumSize = new System.Drawing.Size(System.Windows.Forms.Screen.FromControl(form).Bounds.Width * 5 / 8 - 18, 0);
                label.AutoSize = true;
                // 标签的尺寸要到 Add() 之后才能确定
                form.Controls.Add(label);
                iPosY = label.Bottom;
                iMaxX = label.Right;
            }
 
            // 其余尺寸和位置以单选按钮为基准,因此现在就完成这些控件
            int Counter = 0;
            int tempWidth = System.Windows.Forms.Screen.FromControl(form).Bounds.Width * 5 / 8 - 18;
            foreach(ChoiceDescription sAuswahl in arrChoice) {
                aradioButton[Counter] = new RadioButton();
                aradioButton[Counter].Text = Regex.Replace(sAuswahl.Label, ".\b", "");
                if (Counter == intDefault)
                    aradioButton[Counter].Checked = true;
                aradioButton[Counter].Location = new Point(9, iPosY);
                aradioButton[Counter].AutoSize = true;
                // 标签的尺寸要到 Add() 之后才能确定
                form.Controls.Add(aradioButton[Counter]);
                if (aradioButton[Counter].Width > tempWidth) { // 单选按钮对屏幕来说太宽 -> 换成两行
                    int tempHeight = aradioButton[Counter].Height;
                    aradioButton[Counter].Height = tempHeight * (1 + (aradioButton[Counter].Width - 1) / tempWidth);
                    aradioButton[Counter].Width = tempWidth;
                    aradioButton[Counter].AutoSize = false;
                }
                iPosY = aradioButton[Counter].Bottom;
                if (aradioButton[Counter].Right > iMaxX) {
                    iMaxX = aradioButton[Counter].Right;
                }
                if (!string.IsNullOrEmpty(sAuswahl.HelpMessage))
                    toolTip.SetToolTip(aradioButton[Counter], sAuswahl.HelpMessage);
                Counter++;
            }
 
            // 父窗口不活动时也显示工具提示
            toolTip.ShowAlways = true;
 
            // 创建按钮
            buttonOk.Text = "OK";
            buttonOk.DialogResult = DialogResult.OK;
            buttonOk.SetBounds(System.Math.Max(12, iMaxX - 77), iPosY + 36, 75, 23);
 
            // 配置窗体
            form.Text = strTitle;
            form.ClientSize = new System.Drawing.Size(System.Math.Max(178, iMaxX + 10), iPosY + 71);
            form.Controls.Add(buttonOk);
            form.FormBorderStyle = FormBorderStyle.FixedDialog;
            form.StartPosition = FormStartPosition.CenterScreen;
            try {
                form.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);
            } catch {}
            form.MinimizeBox = false;
            form.MaximizeBox = false;
            form.AcceptButton = buttonOk;
 
            // 显示并计算窗体
            if (form.ShowDialog() != DialogResult.OK)
                return -1;
            int iRueck = -1;
            for (Counter = 0; Counter < arrChoice.Count; Counter++) {
                if (aradioButton[Counter].Checked == true) {
                    iRueck = Counter;
                }
            }
            return iRueck;
        }
    }
 
    public class ReadKey_Box {
        [DllImport("user32.dll")]
        public static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState,
            [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] System.Text.StringBuilder pwszBuff,
            int cchBuff, uint wFlags);
 
        static string GetCharFromKeys(Keys keys, bool blShift, bool blAltGr) {
            System.Text.StringBuilder buffer = new System.Text.StringBuilder(64);
            byte[] keyboardState = new byte[256];
            if (blShift)
                keyboardState[(int) Keys.ShiftKey] = 0xff;
            if (blAltGr) {
                keyboardState[(int) Keys.ControlKey] = 0xff;
                keyboardState[(int) Keys.Menu] = 0xff;
            }
            if (ToUnicode((uint) keys, 0, keyboardState, buffer, 64, 0) >= 1)
                return buffer.ToString();
            else
                return "\0";
        }
 
        class Keyboard_Form: Form {
            public Keyboard_Form() {
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.KeyDown += new KeyEventHandler(Keyboard_Form_KeyDown);
                this.KeyUp += new KeyEventHandler(Keyboard_Form_KeyUp);
            }
 
            // 检查 KeyDown 还是 KeyUp?
            public bool checkKeyDown = true;
            // 按键的键码
            public KeyInfo keyinfo;
 
            void Keyboard_Form_KeyDown(object sender, KeyEventArgs kevent) {
                if (checkKeyDown) { // 存储按键信息
                    keyinfo.VirtualKeyCode = kevent.KeyValue;
                    keyinfo.Character = GetCharFromKeys(kevent.KeyCode, kevent.Shift, kevent.Alt & kevent.Control)[0];
                    keyinfo.KeyDown = false;
                    keyinfo.ControlKeyState = 0;
                    if (kevent.Alt) {
                        keyinfo.ControlKeyState = ControlKeyStates.LeftAltPressed | ControlKeyStates.RightAltPressed;
                    }
                    if (kevent.Control) {
                        keyinfo.ControlKeyState |= ControlKeyStates.LeftCtrlPressed | ControlKeyStates.RightCtrlPressed;
                        if (!kevent.Alt)
                            if (kevent.KeyValue > 64 && kevent.KeyValue < 96)
                                keyinfo.Character = (char)(kevent.KeyValue - 64);
                    }
                    if (kevent.Shift) {
                        keyinfo.ControlKeyState |= ControlKeyStates.ShiftPressed;
                    }
                    if ((kevent.Modifiers & System.Windows.Forms.Keys.CapsLock) > 0) {
                        keyinfo.ControlKeyState |= ControlKeyStates.CapsLockOn;
                    }
                    if ((kevent.Modifiers & System.Windows.Forms.Keys.NumLock) > 0) {
                        keyinfo.ControlKeyState |= ControlKeyStates.NumLockOn;
                    }
                    // 然后关闭窗体
                    this.Close();
                }
            }
 
            void Keyboard_Form_KeyUp(object sender, KeyEventArgs kevent) {
                if (!checkKeyDown) { // 存储按键信息
                    keyinfo.VirtualKeyCode = kevent.KeyValue;
                    keyinfo.Character = GetCharFromKeys(kevent.KeyCode, kevent.Shift, kevent.Alt & kevent.Control)[0];
                    keyinfo.KeyDown = true;
                    keyinfo.ControlKeyState = 0;
                    if (kevent.Alt) {
                        keyinfo.ControlKeyState = ControlKeyStates.LeftAltPressed | ControlKeyStates.RightAltPressed;
                    }
                    if (kevent.Control) {
                        keyinfo.ControlKeyState |= ControlKeyStates.LeftCtrlPressed | ControlKeyStates.RightCtrlPressed;
                        if (!kevent.Alt)
                            if (kevent.KeyValue > 64 && kevent.KeyValue < 96)
                                keyinfo.Character = (char)(kevent.KeyValue - 64);
                    }
                    if (kevent.Shift) {
                        keyinfo.ControlKeyState |= ControlKeyStates.ShiftPressed;
                    }
                    if ((kevent.Modifiers & System.Windows.Forms.Keys.CapsLock) > 0) {
                        keyinfo.ControlKeyState |= ControlKeyStates.CapsLockOn;
                    }
                    if ((kevent.Modifiers & System.Windows.Forms.Keys.NumLock) > 0) {
                        keyinfo.ControlKeyState |= ControlKeyStates.NumLockOn;
                    }
                    // 然后关闭窗体
                    this.Close();
                }
            }
        }
 
        public static KeyInfo Show(string strTitle, string strPrompt, bool blIncludeKeyDown) {
            // 创建控件
            Keyboard_Form form = new Keyboard_Form();
            Label label = new Label();
 
            // 尺寸和位置以标签为基准,因此先完成这个控件
            if (string.IsNullOrEmpty(strPrompt))
                label.Text = "Press a key";
            else
                label.Text = strPrompt;
            label.Location = new Point(9, 19);
            label.MaximumSize = new System.Drawing.Size(System.Windows.Forms.Screen.FromControl(form).Bounds.Width * 5 / 8 - 18, 0);
            label.AutoSize = true;
            // 标签的尺寸要到 Add() 之后才能确定
            form.Controls.Add(label);
 
            // 配置窗体
            form.Text = strTitle;
            form.ClientSize = new System.Drawing.Size(System.Math.Max(178, label.Right + 10), label.Bottom + 55);
            form.FormBorderStyle = FormBorderStyle.FixedDialog;
            form.StartPosition = FormStartPosition.CenterScreen;
            try {
                form.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);
            } catch {}
            form.MinimizeBox = false;
            form.MaximizeBox = false;
 
            // 显示并计算窗体
            form.checkKeyDown = blIncludeKeyDown;
            form.ShowDialog();
            return form.keyinfo;
        }
    }
 
    public class Progress_Form: Form {
        private ConsoleColor ProgressBarColor = ConsoleColor.DarkCyan;
        private string WindowTitle = "";
 
        #if !noVisualStyles
        private System.Timers.Timer _timer = new System.Timers.Timer();
        private int _barNumber = -1;
        private int _barValue = -1;
        private bool _inTick = false;
        #endif
 
        struct Progress_Data {
            internal Label lbActivity;
            internal Label lbStatus;
            internal ProgressBar objProgressBar;
            internal Label lbRemainingTime;
            internal Label lbOperation;
            internal int ActivityId;
            internal int ParentActivityId;
            internal int Depth;
        };
 
        private List<Progress_Data> progressDataList = new List<Progress_Data> ();
 
        public Progress_Form(string Title, ConsoleColor BarColor) {
            WindowTitle = Title;
            ProgressBarColor = BarColor;
            InitializeComponent();
        }
        private void InitializeComponent() {
            this.SuspendLayout();
 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 
            this.AutoScroll = true;
            this.Text = WindowTitle;
            this.Height = 147;
            this.Width = 800;
            this.BackColor = Color.White;
            this.FormBorderStyle = FormBorderStyle.FixedSingle;
            this.MinimizeBox = false;
            this.MaximizeBox = false;
            this.ControlBox = false;
            this.StartPosition = FormStartPosition.CenterScreen;
 
            this.ResumeLayout();
            #if !noVisualStyles
            _timer.Elapsed += new System.Timers.ElapsedEventHandler(TimeTick);
            _timer.Interval = 50; // 毫秒
            _timer.AutoReset = true;
            _timer.Start();
            #endif
        }
 
        private Color DrawingColor(ConsoleColor color) { // 把 ConsoleColor 转换为 System.Drawing.Color
            switch (color) {
            case ConsoleColor.DarkYellow:
                return ColorTranslator.FromOle(35723);//#8B8B00
            default:
                return Color.FromName(color.ToString());
            }
        }
 
        #if !noVisualStyles
        private void TimeTick(object source, System.Timers.ElapsedEventArgs eventargs) { // 由 _timer 事件调用的工作函数
            if (_inTick) return;
            _inTick = true;
            if (_barNumber >= 0) {
                if (_barValue >= 0) {
                    progressDataList[_barNumber].objProgressBar.Value = _barValue;
                    _barValue = -1;
                }
                progressDataList[_barNumber].objProgressBar.Refresh();
            }
            _inTick = false;
        }
        #endif
 
        private void AddBar(ref Progress_Data pd, int position) {
            // 创建标签
            pd.lbActivity = new Label();
            pd.lbActivity.Left = 5;
            pd.lbActivity.Top = 104 * position + 10;
            pd.lbActivity.Width = 800 - 20;
            pd.lbActivity.Height = 16;
            pd.lbActivity.Font = new Font(pd.lbActivity.Font, FontStyle.Bold);
            pd.lbActivity.Text = "";
            // 把标签添加到窗体
            this.Controls.Add(pd.lbActivity);
 
            // 创建标签
            pd.lbStatus = new Label();
            pd.lbStatus.Left = 25;
            pd.lbStatus.Top = 104 * position + 26;
            pd.lbStatus.Width = 800 - 40;
            pd.lbStatus.Height = 16;
            pd.lbStatus.Text = "";
            // 把标签添加到窗体
            this.Controls.Add(pd.lbStatus);
 
            // 创建进度条
            pd.objProgressBar = new ProgressBar();
            pd.objProgressBar.Value = 0;
            pd.objProgressBar.Style =
                #if noVisualStyles
                    ProgressBarStyle.Continuous
                #else
                    ProgressBarStyle.Blocks
                #endif
            ;
            pd.objProgressBar.ForeColor = DrawingColor(ProgressBarColor);
            if (pd.Depth < 15) {
                pd.objProgressBar.Size = new System.Drawing.Size(800 - 60 - 30 * pd.Depth, 20);
                pd.objProgressBar.Left = 25 + 30 * pd.Depth;
            } else {
                pd.objProgressBar.Size = new System.Drawing.Size(800 - 60 - 450, 20);
                pd.objProgressBar.Left = 25 + 450;
            }
            pd.objProgressBar.Top = 104 * position + 47;
            // 把进度条添加到窗体
            this.Controls.Add(pd.objProgressBar);
 
            // 创建标签
            pd.lbRemainingTime = new Label();
            pd.lbRemainingTime.Left = 5;
            pd.lbRemainingTime.Top = 104 * position + 72;
            pd.lbRemainingTime.Width = 800 - 20;
            pd.lbRemainingTime.Height = 16;
            pd.lbRemainingTime.Text = "";
            // 把标签添加到窗体
            this.Controls.Add(pd.lbRemainingTime);
 
            // 创建标签
            pd.lbOperation = new Label();
            pd.lbOperation.Left = 25;
            pd.lbOperation.Top = 104 * position + 88;
            pd.lbOperation.Width = 800 - 40;
            pd.lbOperation.Height = 16;
            pd.lbOperation.Text = "";
            // 把标签添加到窗体
            this.Controls.Add(pd.lbOperation);
        }
 
        public int GetCount() {
            return progressDataList.Count;
        }
 
        public void Update(ProgressRecord objRecord) {
            if (objRecord == null)
                return;
 
            int currentProgress = -1;
            for (int i = 0; i < progressDataList.Count; i++) {
                if (progressDataList[i].ActivityId == objRecord.ActivityId) {
                    currentProgress = i;
                    break;
                }
            }
 
            if (objRecord.RecordType == ProgressRecordType.Completed) {
                if (currentProgress >= 0) {
                    #if !noVisualStyles
                    if (_barNumber == currentProgress) _barNumber = -1;
                    #endif
                    this.Controls.Remove(progressDataList[currentProgress].lbActivity);
                    this.Controls.Remove(progressDataList[currentProgress].lbStatus);
                    this.Controls.Remove(progressDataList[currentProgress].objProgressBar);
                    this.Controls.Remove(progressDataList[currentProgress].lbRemainingTime);
                    this.Controls.Remove(progressDataList[currentProgress].lbOperation);
 
                    progressDataList[currentProgress].lbActivity.Dispose();
                    progressDataList[currentProgress].lbStatus.Dispose();
                    progressDataList[currentProgress].objProgressBar.Dispose();
                    progressDataList[currentProgress].lbRemainingTime.Dispose();
                    progressDataList[currentProgress].lbOperation.Dispose();
 
                    progressDataList.RemoveAt(currentProgress);
                }
 
                if (progressDataList.Count == 0) {
                    #if !noVisualStyles
                    _timer.Stop();
                    _timer.Dispose();
                    #endif
                    this.Close();
                    return;
                }
 
                if (currentProgress < 0) return;
 
                for (int i = currentProgress; i < progressDataList.Count; i++) {
                    progressDataList[i].lbActivity.Top = 104 * i + 10;
                    progressDataList[i].lbStatus.Top = 104 * i + 26;
                    progressDataList[i].objProgressBar.Top = 104 * i + 47;
                    progressDataList[i].lbRemainingTime.Top = 104 * i + 72;
                    progressDataList[i].lbOperation.Top = 104 * i + 88;
                }
 
                if (104 * progressDataList.Count + 43 <= System.Windows.Forms.Screen.FromControl(this).Bounds.Height) {
                    this.Height = 104 * progressDataList.Count + 43;
                    this.Location = new Point((System.Windows.Forms.Screen.FromControl(this).Bounds.Width - this.Width) / 2, (System.Windows.Forms.Screen.FromControl(this).Bounds.Height - this.Height) / 2);
                } else {
                    this.Height = System.Windows.Forms.Screen.FromControl(this).Bounds.Height;
                    this.Location = new Point((System.Windows.Forms.Screen.FromControl(this).Bounds.Width - this.Width) / 2, 0);
                }
 
                return;
            }
 
            if (currentProgress < 0) {
                Progress_Data pd = new Progress_Data();
                pd.ActivityId = objRecord.ActivityId;
                pd.ParentActivityId = objRecord.ParentActivityId;
                pd.Depth = 0;
 
                int nextid = -1;
                int parentid = -1;
                if (pd.ParentActivityId >= 0) {
                    for (int i = 0; i < progressDataList.Count; i++) {
                        if (progressDataList[i].ActivityId == pd.ParentActivityId) {
                            parentid = i;
                            break;
                        }
                    }
                }
 
                if (parentid >= 0) {
                    pd.Depth = progressDataList[parentid].Depth + 1;
 
                    for (int i = parentid + 1; i < progressDataList.Count; i++) {
                        if ((progressDataList[i].Depth < pd.Depth) || ((progressDataList[i].Depth == pd.Depth) && (progressDataList[i].ParentActivityId != pd.ParentActivityId))) {
                            nextid = i;
                            break;
                        }
                    }
                }
 
                if (nextid == -1) {
                    AddBar(ref pd, progressDataList.Count);
                    currentProgress = progressDataList.Count;
                    progressDataList.Add(pd);
                } else {
                    AddBar(ref pd, nextid);
                    currentProgress = nextid;
                    progressDataList.Insert(nextid, pd);
 
                    for (int i = currentProgress + 1; i < progressDataList.Count; i++) {
                        progressDataList[i].lbActivity.Top = 104 * i + 10;
                        progressDataList[i].lbStatus.Top = 104 * i + 26;
                        progressDataList[i].objProgressBar.Top = 104 * i + 47;
                        progressDataList[i].lbRemainingTime.Top = 104 * i + 72;
                        progressDataList[i].lbOperation.Top = 104 * i + 88;
                    }
                }
                if (104 * progressDataList.Count + 43 <= System.Windows.Forms.Screen.FromControl(this).Bounds.Height) {
                    this.Height = 104 * progressDataList.Count + 43;
                    this.Location = new Point((System.Windows.Forms.Screen.FromControl(this).Bounds.Width - this.Width) / 2, (System.Windows.Forms.Screen.FromControl(this).Bounds.Height - this.Height) / 2);
                } else {
                    this.Height = System.Windows.Forms.Screen.FromControl(this).Bounds.Height;
                    this.Location = new Point((System.Windows.Forms.Screen.FromControl(this).Bounds.Width - this.Width) / 2, 0);
                }
            }
 
            if (!string.IsNullOrEmpty(objRecord.Activity))
                progressDataList[currentProgress].lbActivity.Text = objRecord.Activity;
            else
                progressDataList[currentProgress].lbActivity.Text = "";
 
            if (!string.IsNullOrEmpty(objRecord.StatusDescription))
                progressDataList[currentProgress].lbStatus.Text = objRecord.StatusDescription;
            else
                progressDataList[currentProgress].lbStatus.Text = "";
 
            if ((objRecord.PercentComplete >= 0) && (objRecord.PercentComplete <= 100)) {
                #if !noVisualStyles
                if (objRecord.PercentComplete < 100)
                    progressDataList[currentProgress].objProgressBar.Value = objRecord.PercentComplete + 1;
                else
                    progressDataList[currentProgress].objProgressBar.Value = 99;
                progressDataList[currentProgress].objProgressBar.Visible = true;
                _barNumber = currentProgress;
                _barValue = objRecord.PercentComplete;
                #else
                progressDataList[currentProgress].objProgressBar.Value = objRecord.PercentComplete;
                progressDataList[currentProgress].objProgressBar.Visible = true;
                #endif
            } else {
                if (objRecord.PercentComplete > 100) {
                    progressDataList[currentProgress].objProgressBar.Value = 0;
                    progressDataList[currentProgress].objProgressBar.Visible = true;
                    #if !noVisualStyles
                    _barNumber = currentProgress;
                    _barValue = 0;
                    #endif
                } else {
                    progressDataList[currentProgress].objProgressBar.Visible = false;
                    #if !noVisualStyles
                    if (_barNumber == currentProgress) _barNumber = -1;
                    #endif
                }
            }
 
            if (objRecord.SecondsRemaining >= 0) {
                System.TimeSpan objTimeSpan = new System.TimeSpan(0, 0, objRecord.SecondsRemaining);
                progressDataList[currentProgress].lbRemainingTime.Text = string.Format("Remaining time: {0:00}:{1:00}:{2:00}", (int) objTimeSpan.TotalHours, objTimeSpan.Minutes, objTimeSpan.Seconds);
            } else
                progressDataList[currentProgress].lbRemainingTime.Text = "";
 
            if (!string.IsNullOrEmpty(objRecord.CurrentOperation))
                progressDataList[currentProgress].lbOperation.Text = objRecord.CurrentOperation;
            else
                progressDataList[currentProgress].lbOperation.Text = "";
 
            Application.DoEvents();
        }
    }
    #endif
 
    // 在这里定义 IsInputRedirected()、IsOutputRedirected() 和 IsErrorRedirected(),因为它们最早是在 .NET 4.5 中引入的
    public class Console_Info {
        private enum FileType: uint {
            FILE_TYPE_UNKNOWN = 0x0000,
            FILE_TYPE_DISK = 0x0001,
            FILE_TYPE_CHAR = 0x0002,
            FILE_TYPE_PIPE = 0x0003,
            FILE_TYPE_REMOTE = 0x8000
        }
 
        private enum STDHandle: uint {
            STD_INPUT_HANDLE = unchecked((uint) - 10),
            STD_OUTPUT_HANDLE = unchecked((uint) - 11),
            STD_ERROR_HANDLE = unchecked((uint) - 12)
        }
        private enum ConsoleMode: uint {
            ENABLE_ECHO_INPUT = 0x0004,
            ENABLE_INSERT_MODE = 0x0020,
            ENABLE_LINE_INPUT = 0x0002,
            ENABLE_MOUSE_INPUT = 0x0010,
            ENABLE_PROCESSED_INPUT = 0x0001,
            ENABLE_QUICK_EDIT_MODE = 0x0040,
            ENABLE_WINDOW_INPUT = 0x0008,
            ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200,
 
            ENABLE_PROCESSED_OUTPUT = 0x0001,
            ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002,
            ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004,
            DISABLE_NEWLINE_AUTO_RETURN = 0x0008,
            ENABLE_LVB_GRID_WORLDWIDE = 0x0010
        }
 
        [DllImport("Kernel32.dll")]
        static private extern UIntPtr GetStdHandle(STDHandle stdHandle);
 
        [DllImport("Kernel32.dll")]
        static private extern FileType GetFileType(UIntPtr hFile);
        [DllImport("Kernel32.dll")]
        static private extern bool GetConsoleMode(UIntPtr hConsoleHandle, out ConsoleMode lpConsoleMode);
        [DllImport("Kernel32.dll")]
        static private extern bool SetConsoleMode(UIntPtr hConsoleHandle, ConsoleMode dwMode);
 
        static public bool IsInputRedirected() {
            UIntPtr hInput = GetStdHandle(STDHandle.STD_INPUT_HANDLE);
            FileType fileType = GetFileType(hInput);
            if ((fileType == FileType.FILE_TYPE_CHAR) || (fileType == FileType.FILE_TYPE_UNKNOWN))
                return false;
            return true;
        }
 
        static public bool IsOutputRedirected() {
            UIntPtr hOutput = GetStdHandle(STDHandle.STD_OUTPUT_HANDLE);
            FileType fileType = GetFileType(hOutput);
            if ((fileType == FileType.FILE_TYPE_CHAR) || (fileType == FileType.FILE_TYPE_UNKNOWN))
                return false;
            return true;
        }
 
        static public bool IsErrorRedirected() {
            UIntPtr hError = GetStdHandle(STDHandle.STD_ERROR_HANDLE);
            FileType fileType = GetFileType(hError);
            if ((fileType == FileType.FILE_TYPE_CHAR) || (fileType == FileType.FILE_TYPE_UNKNOWN))
                return false;
            return true;
        }
        static public bool IsVirtualTerminalSupported() {
            UIntPtr hOutput = GetStdHandle(STDHandle.STD_OUTPUT_HANDLE);
            ConsoleMode consoleMode;
            if(!GetConsoleMode(hOutput, out consoleMode))
                return false;
            return (consoleMode & ConsoleMode.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
        }
    }
 
    internal class PSRunnerUI: PSHostUserInterface {
        public PSRunnerRawUI rawUI;
 
        public ConsoleColor ErrorForegroundColor = ConsoleColor.Red;
        public ConsoleColor ErrorBackgroundColor = ConsoleColor.Black;
 
        public ConsoleColor WarningForegroundColor = ConsoleColor.Yellow;
        public ConsoleColor WarningBackgroundColor = ConsoleColor.Black;
 
        public ConsoleColor DebugForegroundColor = ConsoleColor.Yellow;
        public ConsoleColor DebugBackgroundColor = ConsoleColor.Black;
 
        public ConsoleColor VerboseForegroundColor = ConsoleColor.Yellow;
        public ConsoleColor VerboseBackgroundColor = ConsoleColor.Black;
 
        public ConsoleColor ProgressForegroundColor =
        #if !noConsole
            ConsoleColor.Yellow
        #else
            ConsoleColor.DarkCyan
        #endif
        ;
        public ConsoleColor ProgressBackgroundColor = ConsoleColor.DarkCyan;
 
        public PSRunnerUI() {
            rawUI = new PSRunnerRawUI();
            #if !noConsole
                rawUI.ForegroundColor = Console.ForegroundColor;
                rawUI.BackgroundColor = Console.BackgroundColor;
            #endif
        }
 
        #if !Pwsh20
            public override bool SupportsVirtualTerminal { get { return Console_Info.IsVirtualTerminalSupported(); } }
        #endif
 
        public override Dictionary<string, PSObject> Prompt(string caption, string message, System.Collections.ObjectModel.Collection<FieldDescription> descriptions) {
            #if !noConsole
                if (!string.IsNullOrEmpty(caption)) WriteLine(caption);
                if (!string.IsNullOrEmpty(message)) WriteLine(message);
            #else
                if ((!string.IsNullOrEmpty(caption)) || (!string.IsNullOrEmpty(message))) {
                    string sTitle = rawUI.WindowTitle, sMeldung = "";
 
                    if (!string.IsNullOrEmpty(caption)) sTitle = caption;
                    if (!string.IsNullOrEmpty(message)) sMeldung = message;
                    MessageBox.Show(sMeldung, sTitle);
                }
 
                // 重置 Input_Box 的标签文本
                _ib_message = "";
            #endif
            Dictionary<string, PSObject> ret = new Dictionary<string, PSObject> ();
            foreach(FieldDescription cd in descriptions) {
                Type type;
                if (string.IsNullOrEmpty(cd.ParameterAssemblyFullName))
                    type = typeof(string);
                else
                    type = Type.GetType(cd.ParameterAssemblyFullName);
 
                if (type.IsArray) {
                    Type elementType = type.GetElementType();
                    Type genericListType = Type.GetType("System.Collections.Generic.List\x60\x31");
                    genericListType = genericListType.MakeGenericType(new [] {
                        elementType
                    });
                    ConstructorInfo constructor = genericListType.GetConstructor(BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null);
                    object resultList = constructor.Invoke(null);
 
                    int index = 0;
                    string data;
                    do {
                        if (!string.IsNullOrEmpty(cd.Name))
                            #if !noConsole
                                Write(string.Format("{0}[{1}]: ", cd.Name, index));
                            #else
                                _ib_message = string.Format("{0}[{1}]: ", cd.Name, index);
                            #endif
                        data = ReadLine();
                        if (string.IsNullOrEmpty(data))
                            break;
                        object obj = System.Convert.ChangeType(data, elementType);
                        genericListType.InvokeMember("Add", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, resultList, new [] {
                            obj
                        });
                        index++;
                    } while (true);
 
                    System.Array retArray = (System.Array) genericListType.InvokeMember("ToArray", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, resultList, null);
                    ret.Add(cd.Name, new PSObject(retArray));
                } else {
                    object obj=null;
                    string line;
                    if (type != typeof(System.Security.SecureString)) {
                        if (type != typeof(System.Management.Automation.PSCredential)) {
                            #if !noConsole
                            if (!string.IsNullOrEmpty(cd.Name)) Write(cd.Name);
                            if (!string.IsNullOrEmpty(cd.HelpMessage)) Write(" (Type !? for help.)");
                            if ((!string.IsNullOrEmpty(cd.Name)) || (!string.IsNullOrEmpty(cd.HelpMessage))) Write(": ");
                            #else
                            if (!string.IsNullOrEmpty(cd.Name)) _ib_message = string.Format("{0}: ", cd.Name);
                            if (!string.IsNullOrEmpty(cd.HelpMessage)) _ib_message += "\n(Type !? for help.)";
                            #endif
                            do {
                                line = ReadLine();
                                if (line == "!?")
                                    WriteLine(cd.HelpMessage);
                                else {
                                    if (string.IsNullOrEmpty(line)) obj = cd.DefaultValue;
                                    if (obj == null) {
                                        try {
                                            obj = System.Convert.ChangeType(line, type);
                                        } catch {
                                            Write("Wrong format, please repeat input: ");
                                            line = "!?";
                                        }
                                    }
                                }
                            } while (line == "!?");
                        } else
                            obj = PromptForCredential("", "", "", "");
                    } else {
                        if (!string.IsNullOrEmpty(cd.Name))
                            #if !noConsole
                                Write(string.Format("{0}: ", cd.Name));
                            #else
                                _ib_message = string.Format("{0}: ", cd.Name);
                            #endif
 
                        obj = ReadLineAsSecureString();
                    }
 
                    ret.Add(cd.Name, new PSObject(obj));
                }
            }
            #if noConsole
            // 重置 Input_Box 的标签文本
            _ib_message = "";
            #endif
            return ret;
        }
 
        public override int PromptForChoice(string caption, string message, System.Collections.ObjectModel.Collection<ChoiceDescription> choices, int defaultChoice) {
            #if noConsole
            if (string.IsNullOrEmpty(caption)) caption = rawUI.WindowTitle;
            int iReturn = Choice_Box.Show(choices, defaultChoice, caption, message);
            if (iReturn == -1)
                iReturn = defaultChoice;
            return iReturn;
            #else
            if (!string.IsNullOrEmpty(caption)) WriteLine(caption);
            WriteLine(message);
            do {
                int idx = 0;
                SortedList<string, int> res = new SortedList<string, int> ();
                string defkey = "";
                foreach(ChoiceDescription cd in choices) {
                    string lkey = cd.Label.Substring(0, 1), ltext = cd.Label;
                    int pos = cd.Label.IndexOf('&');
                    if (pos > -1) {
                        lkey = cd.Label.Substring(pos + 1, 1).ToUpper();
                        if (pos > 0)
                            ltext = cd.Label.Substring(0, pos) + cd.Label.Substring(pos + 1);
                        else
                            ltext = cd.Label.Substring(1);
                    }
                    res.Add(lkey.ToLower(), idx);
 
                    if (idx > 0) Write(" ");
                    ConsoleColor fg = rawUI.ForegroundColor, bg = rawUI.BackgroundColor;
                    if (idx == defaultChoice) {
                        fg = VerboseForegroundColor;
                        defkey = lkey;
                    }
                    Write(fg, bg, string.Format("[{0}] {1}", lkey, ltext));
                    idx++;
                }
                Write(rawUI.ForegroundColor, rawUI.BackgroundColor, string.Format(" [?] Help (default is \"{0}\"): ", defkey));
 
                string inpkey = "";
                try {
                    inpkey = Console.ReadLine().ToLower();
                    if (res.ContainsKey(inpkey)) return res[inpkey];
                    if (string.IsNullOrEmpty(inpkey)) return defaultChoice;
                } catch {/* 忽略部分读取错误 */}
                if (inpkey == "?") {
                    foreach(ChoiceDescription cd in choices) {
                        string lkey = cd.Label.Substring(0, 1);
                        int pos = cd.Label.IndexOf('&');
                        if (pos > -1) lkey = cd.Label.Substring(pos + 1, 1).ToUpper();
                        if (!string.IsNullOrEmpty(cd.HelpMessage))
                            WriteLine(rawUI.ForegroundColor, rawUI.BackgroundColor, string.Format("{0} - {1}", lkey, cd.HelpMessage));
                        else
                            WriteLine(rawUI.ForegroundColor, rawUI.BackgroundColor, string.Format("{0} -", lkey));
                    }
                }
            } while (true);
            #endif
        }
 
        public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) {
            #if !(noConsole || credentialGUI)
            if (!string.IsNullOrEmpty(caption)) WriteLine(caption);
            WriteLine(message);
 
            string UserName;
            Write("User name: ");
            if ((string.IsNullOrEmpty(userName)) || ((options & PSCredentialUIOptions.ReadOnlyUserName) == 0))
                UserName = ReadLine();
            else {
                if (!string.IsNullOrEmpty(targetName)) Write(targetName + "\\");
                WriteLine(userName);
                UserName = userName;
            }
            Write("Password: ");
            SecureString password = ReadLineAsSecureString();
 
            if (string.IsNullOrEmpty(UserName)) UserName = "<NOUSER>";
            if (!string.IsNullOrEmpty(targetName))
                if (UserName.IndexOf('\\') < 0)
                    UserName = targetName + "\\" + UserName;
 
            return new PSCredential(UserName, password);
            #else
            Credential_Form.User_Pwd cred = Credential_Form.PromptForPassword(caption, message, targetName, userName, allowedCredentialTypes, options);
            if (cred != null) {
                System.Security.SecureString x = new System.Security.SecureString();
                foreach(char c in cred.Password.ToCharArray())
                    x.AppendChar(c);
 
                return new PSCredential(cred.User, x);
            }
            return null;
            #endif
        }
 
        public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) {
            #if !(noConsole || credentialGUI)
                if (!string.IsNullOrEmpty(caption)) WriteLine(caption);
                WriteLine(message);
 
                string un;
                Write("User name: ");
                if (string.IsNullOrEmpty(userName))
                    un = ReadLine();
                else {
                    if (!string.IsNullOrEmpty(targetName)) Write(targetName + "\\");
                    WriteLine(userName);
                    un = userName;
                }
                SecureString pwd;
                Write("Password: ");
                pwd = ReadLineAsSecureString();
 
                if (string.IsNullOrEmpty(un)) un = "<NOUSER>";
                if (!string.IsNullOrEmpty(targetName)) {
                    if (un.IndexOf('\\') < 0)
                        un = targetName + "\\" + un;
                }
 
                PSCredential c2 = new PSCredential(un, pwd);
                return c2;
            #else
                Credential_Form.User_Pwd cred = Credential_Form.PromptForPassword(caption, message, targetName, userName, PSCredentialTypes.Default, PSCredentialUIOptions.Default);
                if (cred != null) {
                    System.Security.SecureString x = new System.Security.SecureString();
                    foreach(char c in cred.Password.ToCharArray())
                    x.AppendChar(c);
 
                    return new PSCredential(cred.User, x);
                }
                return null;
            #endif
        }
 
        public override PSHostRawUserInterface RawUI {
            get { return rawUI; }
        }
 
        #if noConsole
        private string _ib_message;
        #endif
 
        public override string ReadLine() {
            #if !noConsole
                return Console.ReadLine();
            #else
                string sWert = "";
                if (Input_Box.Show(rawUI.WindowTitle, _ib_message, ref sWert) == DialogResult.OK)
                    return sWert;
                #if exitOnCancel
                    Environment.Exit(1);
                #endif
                return "";
            #endif
        }
 
        private System.Security.SecureString getPassword() {
            System.Security.SecureString pwd = new System.Security.SecureString();
            while (true) {
                ConsoleKeyInfo i = Console.ReadKey(true);
                if (i.Key == ConsoleKey.Enter) {
                    Console.WriteLine();
                    break;
                } else if (i.Key == ConsoleKey.Backspace) {
                    if (pwd.Length > 0) {
                        pwd.RemoveAt(pwd.Length - 1);
                        Console.Write("\b \b");
                    }
                } else if (i.KeyChar != '\u0000') {
                    pwd.AppendChar(i.KeyChar);
                    Console.Write("*");
                }
            }
            return pwd;
        }
 
        public override System.Security.SecureString ReadLineAsSecureString() {
            System.Security.SecureString secstr;
            #if !noConsole
                secstr = getPassword();
            #else
                secstr = new System.Security.SecureString();
                string sWert = "";
 
                if (Input_Box.Show(rawUI.WindowTitle, _ib_message, ref sWert, true) == DialogResult.OK) {
                    foreach(char ch in sWert)
                    secstr.AppendChar(ch);
                }
                #if exitOnCancel
                else
                    Environment.Exit(1);
                #endif
            #endif
            return secstr;
        }
 
        // 由 Write-Host 调用
        public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) {
            #if !noOutput
            #if !noConsole
                ConsoleColor fgc = Console.ForegroundColor, bgc = Console.BackgroundColor;
                Console.ForegroundColor = foregroundColor;
                Console.BackgroundColor = backgroundColor;
                Console.Write(value);
                Console.ForegroundColor = fgc;
                Console.BackgroundColor = bgc;
            #else
                if ((!string.IsNullOrEmpty(value)) && (value != "\n"))
                    MessageBox.Show(value, rawUI.WindowTitle);
            #endif
            #endif
        }
 
        public override void Write(string value) {
            #if !noOutput
            #if !noConsole
                Console.Write(value);
            #else
                if ((!string.IsNullOrEmpty(value)) && (value != "\n"))
                    MessageBox.Show(value, rawUI.WindowTitle);
            #endif
            #endif
        }
 
        // 由 Write-Debug 调用
        public override void WriteDebugLine(string message) {
            #if !noDebug
            #if !noConsole
                WriteLineInternal(DebugForegroundColor, DebugBackgroundColor, string.Format("DEBUG: {0}", message));
            #else
                MessageBox.Show(message, rawUI.WindowTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
            #endif
            #endif
        }
 
        // 由 Write-Error 调用
        public override void WriteErrorLine(string value) {
            #if !noError
            #if !noConsole
                if (Console_Info.IsErrorRedirected())
                    Console.Error.WriteLine(string.Format("ERROR: {0}", value));
                else
                    WriteLineInternal(ErrorForegroundColor, ErrorBackgroundColor, string.Format("ERROR: {0}", value));
            #else
                MessageBox.Show(value, rawUI.WindowTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
            #endif
            #endif
        }
 
        internal void WriteErrorRecord(ErrorRecord errorItem) {
            // 特殊处理原生stderr导致的异常
            if (errorItem.Exception is System.Management.Automation.RemoteException) {
                var RemoteException = errorItem.Exception as System.Management.Automation.RemoteException;
                if (RemoteException.SerializedRemoteException == null)
                    Console.Error.WriteLine(errorItem.Exception.Message);
                else
                    WriteErrorLine(errorItem.ToString());
            }
            else
                WriteErrorLine(errorItem.ToString());
        }
 
        public override void WriteLine() {
            #if !noOutput
            #if !noConsole
                Console.WriteLine();
            #else
                MessageBox.Show("", rawUI.WindowTitle);
            #endif
            #endif
        }
 
        public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) {
            #if !noOutput
            #if !noConsole
                // 上色本身可能因宿主控制台状态异常而抛错(比如句柄暂时无效);上色失败也不能让这行内容干脆不出现。
                try {
                    ConsoleColor fgc = Console.ForegroundColor, bgc = Console.BackgroundColor;
                    Console.ForegroundColor = foregroundColor;
                    Console.BackgroundColor = backgroundColor;
                    Console.WriteLine(value);
                    Console.ForegroundColor = fgc;
                    Console.BackgroundColor = bgc;
                }
                catch {
                    Console.WriteLine(value);
                }
            #else
                if ((!string.IsNullOrEmpty(value)) && (value != "\n"))
                    MessageBox.Show(value, rawUI.WindowTitle);
            #endif
            #endif
        }
 
        #if !noConsole
        private void WriteLineInternal(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) {
            // 同上:ERROR/WARNING/DEBUG 走这条路,上色失败绝不能让失败原因本身消失(issue 60)。
            try {
                ConsoleColor fgc = Console.ForegroundColor, bgc = Console.BackgroundColor;
                Console.ForegroundColor = foregroundColor;
                Console.BackgroundColor = backgroundColor;
                Console.WriteLine(value);
                Console.ForegroundColor = fgc;
                Console.BackgroundColor = bgc;
            }
            catch {
                Console.WriteLine(value);
            }
        }
        #endif
 
        // 由 Write-Output 调用
        public override void WriteLine(string value) {
            #if !noOutput
            #if !noConsole
                Console.WriteLine(value);
            #else
                if ((!string.IsNullOrEmpty(value)) && (value != "\n"))
                    MessageBox.Show(value, rawUI.WindowTitle);
            #endif
            #endif
        }
 
        #if noConsole
        public Progress_Form pf;
        #endif
        public override void WriteProgress(long sourceId, ProgressRecord record) {
            #if noConsole
            if (pf == null) {
                if (record.RecordType == ProgressRecordType.Completed) return;
                pf = new Progress_Form(rawUI.WindowTitle, ProgressForegroundColor);
                pf.Show();
            }
            pf.Update(record);
            if (record.RecordType == ProgressRecordType.Completed) {
                if (pf.GetCount() == 0) pf = null;
            }
            #else
            if (!Console_Info.IsOutputRedirected()) {// 标准输出被重定向时不写进度条。
                // 用于开启进度指示器的 OSC 序列
                // https://github.com/microsoft/terminal/issues/6700
                if(Console_Info.IsVirtualTerminalSupported()){
                    if (record.RecordType == ProgressRecordType.Completed)//结束进度指示器
                        Console.Write("\x1b]9;4;0\x1b\\");
                    else {
                        int percentComplete = record.PercentComplete;
                        // Write-Progress 允许负数完成百分比,但不得大于 100,而 OSC 序列限制在 0 到 100。
                        if (percentComplete < 0)
                            percentComplete = 0;
                        Console.Write(string.Format("\x1b]9;4;1;{0}\x1b\\", percentComplete));
                    }
                }
            }
            #endif
        }
 
        // 由 Write-Verbose 调用
        public override void WriteVerboseLine(string message) {
            #if !noVerbose
            #if !noConsole
            WriteLineInternal(VerboseForegroundColor, VerboseBackgroundColor, string.Format("VERBOSE: {0}", message));
            #else
            MessageBox.Show(message, rawUI.WindowTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
            #endif
            #endif
        }
 
        // 由 Write-Warning 调用
        public override void WriteWarningLine(string message) {
            #if !noWarning
                #if !noConsole
                    WriteLineInternal(WarningForegroundColor, WarningBackgroundColor, string.Format("WARNING: {0}", message));
                #else
                    MessageBox.Show(message, rawUI.WindowTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning);
                #endif
            #endif
        }
    }
 
    internal class PSRunnerHost: PSHost {
        private readonly PSRunnerInterface parent;
        private readonly PSRunnerUI _ui;
 
        private readonly CultureInfo originalCultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture;
 
        private readonly CultureInfo originalUICultureInfo = System.Threading.Thread.CurrentThread.CurrentUICulture;
 
        private Guid _myId = Guid.NewGuid();
 
        public PSRunnerHost(PSRunnerInterface app, PSRunnerUI ui) {
            this.parent = app;
            this._ui = ui;
        }
 
        public class ConsoleColorProxy {
            private readonly PSRunnerUI _ui;
 
            public ConsoleColorProxy(PSRunnerUI ui) {
                if (ui == null) throw new ArgumentNullException("ui");
                _ui = ui;
            }
 
            public ConsoleColor ErrorForegroundColor {
                get { return _ui.ErrorForegroundColor; }
                set { _ui.ErrorForegroundColor = value; }
            }
 
            public ConsoleColor ErrorBackgroundColor {
                get { return _ui.ErrorBackgroundColor; }
                set { _ui.ErrorBackgroundColor = value; }
            }
 
            public ConsoleColor WarningForegroundColor {
                get { return _ui.WarningForegroundColor; }
                set { _ui.WarningForegroundColor = value; }
            }
 
            public ConsoleColor WarningBackgroundColor {
                get { return _ui.WarningBackgroundColor; }
                set { _ui.WarningBackgroundColor = value; }
            }
 
            public ConsoleColor DebugForegroundColor {
                get { return _ui.DebugForegroundColor; }
                set { _ui.DebugForegroundColor = value; }
            }
 
            public ConsoleColor DebugBackgroundColor {
                get { return _ui.DebugBackgroundColor; }
                set { _ui.DebugBackgroundColor = value; }
            }
 
            public ConsoleColor VerboseForegroundColor {
                get { return _ui.VerboseForegroundColor; }
                set { _ui.VerboseForegroundColor = value; }
            }
 
            public ConsoleColor VerboseBackgroundColor {
                get { return _ui.VerboseBackgroundColor; }
                set { _ui.VerboseBackgroundColor = value; }
            }
 
            public ConsoleColor ProgressForegroundColor {
                get { return _ui.ProgressForegroundColor; }
                set { _ui.ProgressForegroundColor = value; }
            }
 
            public ConsoleColor ProgressBackgroundColor {
                get { return _ui.ProgressBackgroundColor; }
                set { _ui.ProgressBackgroundColor = value; }
            }
        }
 
        public override PSObject PrivateData {
            get {
                if (_ui == null) return null;
                return _consoleColorProxy ?? (_consoleColorProxy = PSObject.AsPSObject(new ConsoleColorProxy(_ui)));
            }
        }
 
        private PSObject _consoleColorProxy;
 
        public override System.Globalization.CultureInfo CurrentCulture {
            get { return this.originalCultureInfo; }
        }
 
        public override System.Globalization.CultureInfo CurrentUICulture {
            get { return this.originalUICultureInfo; }
        }
 
        public override Guid InstanceId {
            get { return this._myId; }
        }
 
        public override string Name {
            get { return "PSEXE"; }
        }
 
        public override PSHostUserInterface UI {
            get { return _ui; }
        }
 
        public override Version Version {
            get { return new Version(0, 0, 0, 0); }
        }
 
        public override void EnterNestedPrompt() {}
 
        public override void ExitNestedPrompt() {}
 
        public override void NotifyBeginApplication() {}
 
        public override void NotifyEndApplication() {}
 
        public override void SetShouldExit(int exitCode) {
            this.parent.ShouldExit = true;
            this.parent.ExitCode = exitCode;
        }
    }
 
    internal interface PSRunnerInterface {
        bool ShouldExit {get;set;}
        int ExitCode {get;set;}
    }
 
    internal class PSRunner: PSRunnerInterface {
        // 启动计时。需要计时时用 ps12exe -StartupTiming 编译,计时输出走 stderr。
        #if StartupTiming
        internal static System.Diagnostics.Stopwatch TimerSw = System.Diagnostics.Stopwatch.StartNew();
        #endif
        [System.Diagnostics.Conditional("StartupTiming")]
        internal static void TimerMark(string s) {
            #if StartupTiming
                System.Console.Error.WriteLine("[timing] " + s + ": " + TimerSw.Elapsed.TotalMilliseconds.ToString("F1") + " ms");
            #endif
        }
 
        private bool shouldExit;
 
        private int exitCode;
        public bool Inited;
 
        public bool ShouldExit {
            get { return this.shouldExit; }
            set { this.shouldExit = value; }
        }
 
        public int ExitCode {
            get { return this.exitCode; }
            set { this.exitCode = value; }
        }
 
        public PSRunnerUI ui;
        public PSRunnerHost host;
        public Runspace PSRunSpace;
        public PowerShell pwsh;
 
        public PSRunner() {
            TimerMark("ctor:enter");
            this.shouldExit = false;
            this.exitCode = 0;
            this.ui = new PSRunnerUI();
            TimerMark("ctor:ui");
            this.host = new PSRunnerHost(this, ui);
            #if Pwsh20
                this.PSRunSpace = RunspaceFactory.CreateRunspace(host);
            #else
                // 完整默认 ISS(含 Utility/Management 等内置管理单元):自身创建稍慢,但首个 cmdlet 调用不必再走模块自动发现。CreateDefault2 的轻量 ISS 会把这份开销推迟到第一管道命令,对 hello world 实测反而慢约 70ms(见 tools/Benchmark)。
                InitialSessionState iss = InitialSessionState.CreateDefault();
                this.PSRunSpace = RunspaceFactory.CreateRunspace(host, iss);
            #endif
            TimerMark("ctor:runspace-create");
            this.PSRunSpace.ApartmentState = System.Threading.ApartmentState.$threadingModel;
            this.PSRunSpace.Open();
            TimerMark("ctor:runspace-open");
            this.pwsh = PowerShell.Create();
            this.pwsh.Runspace = PSRunSpace;
            TimerMark("ctor:pwsh-create");
            #if CoreHost
                string exepath = System.Environment.ProcessPath;
            #else
                string exepath = Assembly.GetEntryAssembly().Location;
            #endif
            Assembly executingAssembly = Assembly.GetExecutingAssembly();
            string script;
            // 脚本以 main.ps1 资源内嵌在负载程序集里;负载本身在打包时会被整体压缩。
            using (Stream scriptstream = executingAssembly.GetManifestResourceStream("main.ps1")) {
                using (var scriptreader = new StreamReader(scriptstream, Encoding.UTF8)) {
                    script = scriptreader.ReadToEnd();
                    this.PSRunSpace.SessionStateProxy.SetVariable("PSEXEscript", script);
                }
            }
            TimerMark("ctor:read-script");
            script = "function PSEXEMainFunction{"+script+"}";
            #if Pwsh20
                this.pwsh.AddScript(script);
            #else
            {
                Token[] tokens;
                ParseError[] errors;
                ScriptBlockAst AST = Parser.ParseInput(script, exepath, out tokens, out errors);
                TimerMark("ctor:parse");
                this.PSRunSpace.SessionStateProxy.SetVariable("PSEXEIniter", AST.GetScriptBlock());
                TimerMark("ctor:getscriptblock");
                if(errors.Length > 0)
                    throw new System.InvalidProgramException(errors[0].Message);
                this.pwsh.AddScript(".$PSEXEIniter");
            }
            #endif
            TimerMark("ctor:done");
        }
        public void Dispose() {
            if (pwsh != null) pwsh.Dispose();
            if (PSRunSpace != null) {
                PSRunSpace.Close();
                PSRunSpace.Dispose();
            }
            host = null;
            ui = null;
 
            GC.SuppressFinalize(this);
        }
        //基础初始化
        public static void BaseInit() {
            #if UNICODEEncoding && !noConsole
            System.Console.OutputEncoding = new System.Text.UnicodeEncoding();
            #endif
            #if UTF8Encoding && !noConsole
            System.Console.OutputEncoding = new System.Text.UTF8Encoding();
            #endif
 
            #if culture
            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("$lcid");
            System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("$lcid");
            #endif
 
            #if !noVisualStyles && noConsole
            Application.EnableVisualStyles();
            #endif
 
            FixModulePath();
        }
 
        // 把自己的模块目录前置;这里做同样的事,保证轻量 ISS 下命令仍能正确自动加载(issue 61)。
        static void FixModulePath() {
            try {
                string docs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
                string systemRoot = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
                string[] preferred = new string[] {
                    string.IsNullOrEmpty(docs) ? null : Path.Combine(Path.Combine(docs, "WindowsPowerShell"), "Modules"),
                    string.IsNullOrEmpty(programFiles) ? null : Path.Combine(Path.Combine(programFiles, "WindowsPowerShell"), "Modules"),
                    string.IsNullOrEmpty(systemRoot) ? null : Path.Combine(Path.Combine(systemRoot, Path.Combine("System32", Path.Combine("WindowsPowerShell", Path.Combine("v1.0", "Modules"))))),
                };
                List<string> parts = new List<string>();
                foreach (string path in preferred) {
                    if (!string.IsNullOrEmpty(path) && !parts.Contains(path)) parts.Add(path);
                }
                string existing = Environment.GetEnvironmentVariable("PSModulePath");
                if (!string.IsNullOrEmpty(existing)) {
                    foreach (string path in existing.Split(';')) {
                        if (!string.IsNullOrEmpty(path) && !parts.Contains(path)) parts.Add(path);
                    }
                }
                Environment.SetEnvironmentVariable("PSModulePath", string.Join(";", parts.ToArray()));
            } catch {
                // 模块路径修正失败不应影响启动
            }
        }
    }
    static class PSRunnerEntry {
        static PSRunner me;
 
        #if ScriptHasParam
        // 把命令行参数当 PowerShell 数据(PSD)解析:只接受字面量(字符串/数字/bool/null/数组/哈希表),
        // 任何表达式或命令都视为普通字符串。解析出的对象直接通过变量传入,不再作为文本进入命令行,
        // 因此参数内容不会被当作 PowerShell 脚本求值。
        // 例外:允许到安全类型的安全转换(如 [int]'5'、[hashtable]@{}、[ordered]@{})。
        static readonly Dictionary<string, Type> PsdSafeCastTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase) {
            { "int", typeof(int) }, { "int32", typeof(int) }, { "system.int32", typeof(int) },
            { "long", typeof(long) }, { "int64", typeof(long) }, { "system.int64", typeof(long) },
            { "short", typeof(short) }, { "int16", typeof(short) },
            { "byte", typeof(byte) }, { "sbyte", typeof(sbyte) },
            { "uint", typeof(uint) }, { "uint32", typeof(uint) },
            { "ulong", typeof(ulong) }, { "uint64", typeof(ulong) }, { "ushort", typeof(ushort) },
            { "single", typeof(float) }, { "float", typeof(float) },
            { "double", typeof(double) }, { "system.double", typeof(double) },
            { "decimal", typeof(decimal) },
            { "string", typeof(string) }, { "system.string", typeof(string) }, { "char", typeof(char) },
            { "bool", typeof(bool) }, { "boolean", typeof(bool) },
            { "hashtable", typeof(System.Collections.Hashtable) }, { "system.collections.hashtable", typeof(System.Collections.Hashtable) },
            { "array", typeof(object[]) }, { "object[]", typeof(object[]) }
        };
        static bool TryParsePsdValue(ExpressionAst expression, out object value) {
            value = null;
            if (expression is StringConstantExpressionAst) {
                value = ((StringConstantExpressionAst)expression).Value;
                return true;
            }
            if (expression is ConstantExpressionAst) {
                value = ((ConstantExpressionAst)expression).Value;
                return true;
            }
            ConvertExpressionAst convert = expression as ConvertExpressionAst;
            if (convert != null) {
                if (convert.Child == null || convert.Type == null || convert.Type.TypeName == null) return false;
                string castName = convert.Type.TypeName.Name;
                if (castName == null) return false;
                // [ordered]@{}:与 PowerShell 一样保留键顺序
                if (castName.Equals("ordered", StringComparison.OrdinalIgnoreCase) ||
                    castName.Equals("ordereddictionary", StringComparison.OrdinalIgnoreCase) ||
                    castName.Equals("System.Collections.Specialized.OrderedDictionary", StringComparison.OrdinalIgnoreCase)) {
                    HashtableAst orderedSource = convert.Child as HashtableAst;
                    if (orderedSource == null) return false;
                    System.Collections.Specialized.OrderedDictionary ordered = new System.Collections.Specialized.OrderedDictionary(StringComparer.OrdinalIgnoreCase);
                    foreach (var pair in orderedSource.KeyValuePairs) {
                        object key, item;
                        string keyText;
                        if (!TryParsePsdValue(pair.Item1, out key) || (keyText = key as string) == null) return false;
                        if (!TryParsePsdStatement(pair.Item2, out item)) return false;
                        ordered[keyText] = item;
                    }
                    value = ordered;
                    return true;
                }
                Type castType;
                if (!PsdSafeCastTypes.TryGetValue(castName, out castType)) return false;
                object converted;
                if (!TryParsePsdValue(convert.Child, out converted)) return false;
                try {
                    value = LanguagePrimitives.ConvertTo(converted, castType, CultureInfo.InvariantCulture);
                    return true;
                }
                catch {
                    return false;
                }
            }
            VariableExpressionAst variable = expression as VariableExpressionAst;
            if (variable != null) {
                switch (variable.VariablePath.UserPath) {
                    case "true": value = true; return true;
                    case "false": value = false; return true;
                    case "null": return true;
                }
                return false;
            }
            UnaryExpressionAst unary = expression as UnaryExpressionAst;
            if (unary != null && unary.TokenKind == TokenKind.Minus) {
                object inner;
                if (!TryParsePsdValue(unary.Child, out inner)) return false;
                if (inner is int) { value = -(int)inner; return true; }
                if (inner is long) { value = -(long)inner; return true; }
                if (inner is double) { value = -(double)inner; return true; }
                if (inner is decimal) { value = -(decimal)inner; return true; }
                return false;
            }
            ArrayLiteralAst arrayLiteral = expression as ArrayLiteralAst;
            if (arrayLiteral != null) {
                List<object> items = new List<object>();
                foreach (ExpressionAst element in arrayLiteral.Elements) {
                    object item;
                    if (!TryParsePsdValue(element, out item)) return false;
                    items.Add(item);
                }
                value = items.ToArray();
                return true;
            }
            ArrayExpressionAst arrayExpression = expression as ArrayExpressionAst;
            if (arrayExpression != null) {
                if (arrayExpression.SubExpression == null || arrayExpression.SubExpression.Statements.Count != 1) return false;
                return TryParsePsdStatement(arrayExpression.SubExpression.Statements[0], out value);
            }
            HashtableAst hashtable = expression as HashtableAst;
            if (hashtable != null) {
                System.Collections.Hashtable result = new System.Collections.Hashtable(StringComparer.OrdinalIgnoreCase);
                foreach (var pair in hashtable.KeyValuePairs) {
                    object key, item;
                    string keyText;
                    if (!TryParsePsdValue(pair.Item1, out key) || (keyText = key as string) == null) return false;
                    if (!TryParsePsdStatement(pair.Item2, out item)) return false;
                    result[keyText] = item;
                }
                value = result;
                return true;
            }
            return false;
        }
        // 哈希表的值在 AST 里是语句(PipelineAst),取其中的表达式再按 PSD 解析
        static bool TryParsePsdStatement(StatementAst statement, out object value) {
            value = null;
            PipelineAst pipeline = statement as PipelineAst;
            if (pipeline == null || pipeline.PipelineElements.Count != 1)
                return false;
            CommandExpressionAst expression = pipeline.PipelineElements[0] as CommandExpressionAst;
            if (expression == null)
                return false;
            return TryParsePsdValue(expression.Expression, out value);
        }
        static bool TryParsePsd(string text, out object value, out bool explicitCast) {
            value = null;
            explicitCast = false;
            Token[] tokens;
            ParseError[] errors;
            ScriptBlockAst ast = Parser.ParseInput(text, out tokens, out errors);
            if (errors.Length > 0 || ast.EndBlock == null || ast.EndBlock.Statements.Count != 1)
                return false;
            PipelineAst pipeline = ast.EndBlock.Statements[0] as PipelineAst;
            if (pipeline == null || pipeline.PipelineElements.Count != 1)
                return false;
            CommandExpressionAst commandExpression = pipeline.PipelineElements[0] as CommandExpressionAst;
            if (commandExpression == null)
                return false;
            explicitCast = commandExpression.Expression is ConvertExpressionAst;
            return TryParsePsdValue(commandExpression.Expression, out value);
        }
        #endif
 
        // EXE 主入口
        [$threadingModelThread]
        private static int Main(string[] args) {
            #if StartupTiming
                PSRunner.TimerSw.Restart();
            #endif
            PSRunner.TimerMark("main:enter");
            PSRunner.BaseInit();
            PSRunner.TimerMark("main:baseinit");
            me = new PSRunner();
            PSRunner.TimerMark("main:ctor-done");
            System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false);
 
            try {
                #if !noConsole
                Console.CancelKeyPress += (object sender, ConsoleCancelEventArgs eventargs) => {
                    try {
                        me.pwsh.BeginStop((_) => {
                            mre.Set();
                            eventargs.Cancel = true;
                        }, null);
                    } catch {
                        // 忽略,因为正在关闭
                    }
                };
                #endif
 
                #if ScriptHasParam
                int psdIndex = 0;
                #endif
                for(int i = 0; i < args.Length; i++) {
                    if (Regex.IsMatch(args[i], @"^(-|\$)\w*$"))
                        continue;
                    #if ScriptHasParam
                    // 脚本有 param 块时,显式安全转换或表/数组类参数值按 PSD 数据解析成对象,再用变量传入,
                    // 避免作为文本被求值;其余值仍按字符串传递,保持数字/字符串的原有绑定行为。
                    object psdValue;
                    bool explicitCast;
                    if (TryParsePsd(args[i], out psdValue, out explicitCast) &&
                        (explicitCast || psdValue is System.Collections.IDictionary || psdValue is System.Array)) {
                        string psdVar = "PSEXEArg" + (psdIndex++);
                        me.pwsh.Runspace.SessionStateProxy.SetVariable(psdVar, psdValue);
                        args[i] = "$" + psdVar;
                        continue;
                    }
                    #endif
                    args[i] = "\'"+args[i].Replace("'", "''")+"\'";
                }
 
                #if ReadInput
                    // 仅当编译期检测到脚本顶层使用 $input 时(ReadInput)才读取重定向的标准输入:否则保留原始 stdin,且不在启动时等待 stdin(issue 62)。
                    PSDataCollection<string> colInput = new PSDataCollection<string> ();
                    if (Console_Info.IsInputRedirected()) { // 读取标准输入
                        string sItem;
                        while ((sItem = Console.ReadLine()) != null) { // 添加到 powershell 管道
                            colInput.Add(sItem);
                        }
                    }
                    colInput.Complete();
 
                    me.pwsh.Runspace.SessionStateProxy.SetVariable("PSEXEInput", colInput);
                    me.pwsh.AddScript("$PSEXEInput|PSEXEMainFunction "+String.Join(" ", args));
                #else
                    // 脚本顶层不用 $input:完全不带管道输入,也不设置 $PSEXEInput
                    me.pwsh.AddScript("PSEXEMainFunction "+String.Join(" ", args));
                #endif
                // Out-Default 走 host UI;勿用 Out-String/输出收集,否则 native 子进程 stdout 会变成管道(非 TTY)
                me.pwsh.AddCommand("Out-Default");
                me.pwsh.Streams.Error.DataAdded += (sender, eventargs) => {
                    me.ui.WriteErrorRecord(((PSDataCollection<ErrorRecord>)sender)[eventargs.Index]);
                };
                IAsyncResult asyncResult = me.pwsh.BeginInvoke();
                PSRunner.TimerMark("main:begininvoke");
 
                System.Threading.WaitHandle[] waitHandles = new System.Threading.WaitHandle[] { mre, asyncResult.AsyncWaitHandle };
                while (System.Threading.WaitHandle.WaitAny(waitHandles, 10) == System.Threading.WaitHandle.WaitTimeout) {
                    if (me.ShouldExit) break;
                }
 
                PSRunner.TimerMark("main:pipeline-completed");
                me.Inited = true;
                me.pwsh.EndInvoke(asyncResult);
                PSRunner.TimerMark("main:endinvoke");
                me.pwsh.Stop();
                PSRunner.TimerMark("main:stop");
 
                if (me.pwsh.InvocationStateInfo.State == PSInvocationState.Failed)
                    me.ui.WriteErrorLine(me.pwsh.InvocationStateInfo.Reason.Message);
            }
            catch (Exception ex) {
                #if !noError
                    me.ui.WriteErrorLine(ex.Message);
                #endif
                me.ExitCode = 1;
            }
            finally {
                #if !Pwsh20 // bro wtf
                    mre.Dispose();
                #endif
                me.pwsh.Dispose();
            }
 
            return me.ExitCode;
        }
    }
}