获取不支持重定向的Console程序的输出【原创】

本文详细介绍了如何解决Dism命令行工具不支持输出重定向的问题,并提供了C#代码示例。通过设计Console窗口显示Dism程序的输出结果,实现了在控制台窗口中跟踪和读取Dism操作的实时进度。文章还讨论了如何将Dism操作的输出显示在Windows窗口、文件或共享内存中,以便于在不同场景下获取和处理Dism的运行状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

要做一个系统备份、恢复系统,之前用ImageX,但ImageX有一个大问题,就是它直接恢复系统时,会有很多checksum error,所幸dism解决了这个问题。因此换成调用dism。但发现了又一个问题:ImageX是支持输出重定向的,只要打开开头/scroll,可以很方便地获取当前的进度。



而dism不支持输出重定向,它的输出全部在同一行上:



为这个很伤脑筋,资料几乎没有。经过无数翻贴子和实验,今天终于解决了,分享一下。希望给有同样需要的朋友些帮助。

先说说这两种在Console中显示的区别:

如果是用printf、cout、Console.Write之类的函数输出的,那么就是支持输出重定向的,通常是按顺序输出,当然也可以用backspace之类的方法往回删除。而如果是用WriteConsole之类的API直接写入Console缓冲区,那就不能重新定向,但这种输出可以很好地控制格式。


既然知道它直接写入了Console缓冲区,那么从缓冲区读出来就OK了,不过实现起来也挺不容易,以下给出C#代码,关键处写上注释:

第一步:需要用API - ReadConsoleOutput从缓冲区读出信息

这里要用C#语法包装一下API:(这里借鉴了一篇贴子,地址忘了,好像叫《从Console屏幕截图........》)

internal class DismWrapper
    {
//x,y - 要读取的Console窗口的矩形区域的起点位置X,Y坐标,以字符为单位,而非像素
	//width,height - 要读取的Console窗口的矩形区域的宽和高,以字符为单位,而非像素
        public static IEnumerable<string> ReadFromBuffer(short x, short y, short width, short height)
        {
            IntPtr buffer = Marshal.AllocHGlobal(width * height * Marshal.SizeOf(typeof(CHAR_INFO)));
            if (buffer == null)
                throw new OutOfMemoryException();

            try
            {
                COORD coord = new COORD();
                SMALL_RECT rc = new SMALL_RECT();
                rc.Left = x;
                rc.Top = y;
                rc.Right = (short)(x + width - 1);
                rc.Bottom = (short)(y + height - 1);

                COORD size = new COORD();
                size.X = width;
                size.Y = height;

                const int STD_OUTPUT_HANDLE = -11;
                if (!ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), buffer, size, coord, ref rc))
                {
                    // 'Not enough storage is available to process this command' may be raised for buffer size > 64K (see ReadConsoleOutput doc.)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                IntPtr ptr = buffer;
                for (int h = 0; h < height; h++)
                {
                    StringBuilder sb = new StringBuilder();
                    for (int w = 0; w < width; w++)
                    {
                        CHAR_INFO ci = (CHAR_INFO)Marshal.PtrToStructure(ptr, typeof(CHAR_INFO));
                        char[] chars = Console.OutputEncoding.GetChars(ci.charData);
                        sb.Append(chars[0]);
                        ptr += Marshal.SizeOf(typeof(CHAR_INFO));
                    }
                    yield return sb.ToString();
                }
            }
            finally
            {
                Marshal.FreeHGlobal(buffer);
            }
        }


        [StructLayout(LayoutKind.Sequential)]
        private struct CHAR_INFO
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public byte[] charData;
            public short attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct COORD
        {
            public short X;
            public short Y;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct SMALL_RECT
        {
            public short Left;
            public short Top;
            public short Right;
            public short Bottom;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CONSOLE_SCREEN_BUFFER_INFO
        {
            public COORD dwSize;
            public COORD dwCursorPosition;
            public short wAttributes;
            public SMALL_RECT srWindow;
            public COORD dwMaximumWindowSize;
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool ReadConsoleOutput(IntPtr hConsoleOutput, IntPtr lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, ref SMALL_RECT lpReadRegion);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr GetStdHandle(int nStdHandle);

    }

第二步:在Console中调用API
这里的关键是需要在本Console窗口中显示另一个Console程序的输出结果,通过设计Process的StartInfo参数实现:
public class CommandCaller
    {
        private string m_Command;
        private Process m_Process;
        private StringBuilder m_Result = new StringBuilder();
        public string LastResult { get; private set; }
        private StringBuilder m_ErrorMsg = new StringBuilder();
        public string LastErrorMsg { get; private set; }
        public event RunWorkerCompletedEventHandler Exited;

        public CommandCaller(string command)
        {
            m_Command = command;
            m_Process = new Process();
            m_Process.StartInfo.FileName = command;
            m_Process.StartInfo.UseShellExecute = false;    //关键!
            m_Process.StartInfo.CreateNoWindow = false;    //关键!
            m_Process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
            m_Process.StartInfo.RedirectStandardError = true;
    //同时注意不要设置m_Process.StartInfo.RedirectStandardError 为 true!
            m_Process.StartInfo.UserName = null;
            m_Process.StartInfo.Password = null;
            m_Process.EnableRaisingEvents = true;
            m_Process.ErrorDataReceived += new DataReceivedEventHandler(OnErrorDataReceived);
            m_Process.Exited += new EventHandler(OnExited);
        }

        private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                m_ErrorMsg.Append(e.Data);
                LastErrorMsg = e.Data;
            }
        }

        private void OnExited(object sender, EventArgs e)
        {
            RunWorkerCompletedEventHandler _evtHandler = Exited;
            if (_evtHandler != null)
            {
                _evtHandler(sender, new RunWorkerCompletedEventArgs(null, null, false));
            }
        }

        public void Call(string parameter)
        {
            Cancel();
            try
            {
                m_Process.StartInfo.Arguments = parameter;
                m_Process.Start();

                m_Process.WaitForExit();
                m_Process.Close();
            }
            catch (Exception e)
            {
            }
        }

        public void Cancel()
        {
            try
            {
                if (!m_Process.HasExited)
                {
                    m_Process.Kill();
                }
            }
            catch (Exception e)
            {
            }
        }
    }

第三步:因为本身是Console程序,因此不能随便在Console中输出结果,可以输出到文件中,这里我显示在Console窗口的标题上。

static void Main(string[] args)
        {
            if (args.Length < 2 || !args[0].ToLower().Contains("dism"))
            {
                Console.WriteLine("Please enter valid path of dism.exe and its parameters.");
                return;
            }
            StringBuilder _sb = new StringBuilder();
            for (int i = 1; i < args.Length; i++)
            {
                _sb.Append(args[i]);
                _sb.Append(" ");
            }
            //示例
            //RunDism(@"d:\pe\dism.exe", @"/capture-image /ImageFile:e:\test.wim /CaptureDir:d:\bom /Name:test");
            RunDism(args[0], _sb.ToString());
            Console.ReadKey();
        }

        private static void RunDism(string command, string parameter)
        {
            Console.Clear();
            string readtext = string.Empty;
            double percentage = 0;
            Regex reg = new Regex(@"\[\=*\s*(\d{1,3}\.\d)%=*\s*\]");
            Task task = new Task(() =>
            {
                while (percentage < 99.9)
                {
                    foreach (string line in DismWrapper.ReadFromBuffer(0, 5, (short)Console.BufferWidth, 1))
                    {
                        readtext = line;
                    }
                    //Console.Title = readtext;
                    if (reg.IsMatch(readtext))
                    {
                        percentage = double.Parse(reg.Match(readtext).Groups[1].Value);
                        Console.Title = reg.Match(readtext).Groups[1].Value;
                    }
                }
                Console.WriteLine("Exit");
            });
            task.Start();

            CommandCaller _dismCaller = new CommandCaller(command);
            _dismCaller.Call(parameter);
        }

到了这里,剩下就简单了,要想在Windows窗体上显示这些数据,简单点的可以用获取进程窗体标题的方法,复杂点可以用SendMessage、使用内存映射文件、通过共享内存DLL共享内存,当然C#用IO命名管道更方便。


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值