C# 监视桌面应用程序的最佳方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11146381/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 16:43:09  来源:igfitidea点击:

What's the best way to watchdog a desktop application?

c#.netwindows-servicesdesktop-applicationwatchdog

提问by Axel Magagnini

I need some way to monitor a desktop application and restart it if it dies.

我需要某种方法来监视桌面应用程序并在它死机时重新启动它。

Initially I assumed the best way would be to monitor/restart the process from a Windows service, until I found out that since Vista Windows services should not interact with the desktop

最初我认为最好的方法是从 Windows 服务监视/重新启动进程,直到我发现由于 Vista Windows 服务不应该与桌面交互

I've seen several questions dealing with this issue, but every answer I've seen involved some kind of hack that is discouraged by Microsoft and will likely stop working in future OS updates.

我已经看到了几个处理这个问题的问题,但我看到的每个答案都涉及某种微软不鼓励的黑客行为,并且可能会在未来的操作系统更新中停止工作。

So, a Windows service is probably not an option anymore. I could probably just create a different desktop/console application to do this, but that kind of defeats its purpose.

因此,Windows 服务可能不再是一种选择。我可能只是创建一个不同的桌面/控制台应用程序来做到这一点,但这违背了它的目的。

Which would be the most elegant way to achieve this, in your opinion?

在您看来,哪种方式是实现这一目标的最优雅的方式?

EDIT: This is neither malware nor virus.The app that needs monitoring is a media player that will run on an embedded system, and even though I'm trying to cover all possible crash scenarios, I can't risk having it crash over an unexpected error (s**t happens). This watchdog would be just a safeguard in case everything else goes wrong. Also, since the player would be showing 3rd party flash content, an added plus would be for example to monitor for resource usage, and restart the player if say, some crappy flash movie starts leaking memory.

编辑:这既不是恶意软件也不是病毒。需要监控的应用程序是一个将在嵌入式系统上运行的媒体播放器,即使我试图涵盖所有可能的崩溃情况,我也不能冒险让它因意外错误而崩溃(s**t 发生) . 这个看门狗只是一个保护措施,以防其他一切都出错。此外,由于播放器将显示 3rd 方 Flash 内容,因此一个额外的好处是例如监视资源使用情况,并在说某些蹩脚的 Flash 电影开始泄漏内存时重新启动播放器。

EDIT 2: I forgot to mention, the application I would like to monitor/restart has absolutely no needto run on either the LocalSystem account nor with any administrative privileges at all. Actually, I'd preferit to run using the currently logged user credentials.

编辑 2:我忘了提及,我想监视/重新启动的应用程序绝对不需要在 LocalSystem 帐户上运行,也不需要任何管理权限。实际上,我更喜欢它使用当前登录的用户凭据运行。

采纳答案by A_nto2

Initially I assumed the best way would be to monitor/restart the process from a Windows service...

最初我认为最好的方法是从 Windows 服务监视/重新启动进程......

Sure you can! I did it some times ago. You can start learning how watching this:

你当然可以!我以前做过几次。您可以开始学习如何观看此内容:

http://msdn.microsoft.com/en-us/windows7trainingcourse_win7session0isolation_topic2#_Toc243675529

http://msdn.microsoft.com/en-us/windows7trainingcourse_win7session0isolation_topic2#_Toc243675529

and this:

和这个:

http://www.codeproject.com/Articles/18367/Launch-your-application-in-Vista-under-the-local-s

http://www.codeproject.com/Articles/18367/Launch-your-application-in-Vista-under-the-local-s

In substance, you have to run programs as SYSTEM, but with the SessionID of the current user.

实际上,您必须以 SYSTEM 身份运行程序,但使用当前用户的 SessionID。

If you're feeling lazy, I suppose there could be some good little Services which make the thing you're looking for. Try searching on www.codeproject.com.

如果您感到懒惰,我想可能有一些不错的小服务可以满足您的需求。尝试在www.codeproject.com 上搜索。

回答by oasten

The watchdog process could make use of System.Diagnostics.Processto launch the application, use the WaitForExitMethod()and check the ExitCodeproperty.

看门狗进程可以System.Diagnostics.Process用来启动应用程序、使用WaitForExitMethod()和检查ExitCode属性。

In response to the complaints over the question, I have had to use such a method when working with a legacy call center application over which I had no source control access.

为了回应对这个问题的抱怨,在使用我没有源控制访问权限的遗留呼叫中心应用程序时,我不得不使用这种方法。

EDIT:

编辑:

For the host application you could use a .NET application of output type "Windows Application" and simply not have a form at all. For example:

对于主机应用程序,您可以使用输出类型为“Windows 应用程序”的 .NET 应用程序,而根本没有表单。例如:

namespace WindowsFormsApplication1
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            var info = new ProcessStartInfo(@"calc.exe");
            var process = Process.Start(info);
            process.WaitForExit();
            MessageBox.Show("Hello World!");
        }
    }
}

回答by Axel Magagnini

I finally implemented a the solution suggested by @A_nto2 and it achieved exactly what I was looking for: I now have a Windows Service that monitors a list of processes and whenever they are down, they are launched again automatically using the active user's credentials and session, so the GUI is visible.

我最终实现了@A_nto2 建议的解决方案,它完全实现了我想要的:我现在有一个 Windows 服务来监视进程列表,每当它们关闭时,它们会使用活动用户的凭据和会话自动再次启动,所以 GUI 是可见的。

However, since the links he posted shown VC++ code, I'm sharing my C# implementation for anyone dealing with the same issue:

但是,由于他发布的链接显示了 VC++ 代码,因此我将与处理相同问题的任何人共享我的 C# 实现:

public static class ProcessExtensions
{
    public enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    }

    [StructLayout(LayoutKind.Sequential)]
    public class SECURITY_ATTRIBUTES
    {
        public int nLength;
        public IntPtr lpSecurityDescriptor;
        public int bInheritHandle;
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

    [Flags]
    public enum CREATE_PROCESS_FLAGS : uint
    {
        NONE = 0x00000000,
        DEBUG_PROCESS = 0x00000001,
        DEBUG_ONLY_THIS_PROCESS = 0x00000002,
        CREATE_SUSPENDED = 0x00000004,
        DETACHED_PROCESS = 0x00000008,
        CREATE_NEW_CONSOLE = 0x00000010,
        NORMAL_PRIORITY_CLASS = 0x00000020,
        IDLE_PRIORITY_CLASS = 0x00000040,
        HIGH_PRIORITY_CLASS = 0x00000080,
        REALTIME_PRIORITY_CLASS = 0x00000100,
        CREATE_NEW_PROCESS_GROUP = 0x00000200,
        CREATE_UNICODE_ENVIRONMENT = 0x00000400,
        CREATE_SEPARATE_WOW_VDM = 0x00000800,
        CREATE_SHARED_WOW_VDM = 0x00001000,
        CREATE_FORCEDOS = 0x00002000,
        BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
        ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
        INHERIT_PARENT_AFFINITY = 0x00010000,
        INHERIT_CALLER_PRIORITY = 0x00020000,
        CREATE_PROTECTED_PROCESS = 0x00040000,
        EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
        PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000,
        PROCESS_MODE_BACKGROUND_END = 0x00200000,
        CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
        CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
        CREATE_DEFAULT_ERROR_MODE = 0x04000000,
        CREATE_NO_WINDOW = 0x08000000,
        PROFILE_USER = 0x10000000,
        PROFILE_KERNEL = 0x20000000,
        PROFILE_SERVER = 0x40000000,
        CREATE_IGNORE_SYSTEM_DEFAULT = 0x80000000,
    }

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

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    public class Kernel32
    {
        [DllImport("kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
        public static extern uint WTSGetActiveConsoleSessionId();

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);
    }

    public class WtsApi32
    {
        [DllImport("Wtsapi32.dll", EntryPoint = "WTSQueryUserToken")]
        public static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr phToken);
    }

    public class AdvApi32
    {
        public const uint MAXIMUM_ALLOWED = 0x2000000;

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool DuplicateTokenEx
        (
            IntPtr hExistingToken,
            uint dwDesiredAccess,
            SECURITY_ATTRIBUTES lpTokenAttributes,
            SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
            TOKEN_TYPE TokenType,
            out IntPtr phNewToken
        );

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool CreateProcessAsUser
        (
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            SECURITY_ATTRIBUTES lpProcessAttributes,
            SECURITY_ATTRIBUTES lpThreadAttributes,
            bool bInheritHandles,
            CREATE_PROCESS_FLAGS dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation
        );
    }

    public class UserEnv
    {
        [DllImport("userenv.dll", SetLastError = true)]
        public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

        [DllImport("userenv.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
    }

    public static void StartAsActiveUser(this Process process)
    {
        // Sanity check.
        if (process.StartInfo == null)
        {
            throw new InvalidOperationException("The StartInfo property must be defined");
        }

        if (string.IsNullOrEmpty(process.StartInfo.FileName))
        {
            throw new InvalidOperationException("The StartInfo.FileName property must be defined");
        }

        // Retrieve the active session ID and its related user token.
        var sessionId = Kernel32.WTSGetActiveConsoleSessionId();
        var userTokenPtr = new IntPtr();
        if (!WtsApi32.WTSQueryUserToken(sessionId, out userTokenPtr))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // Duplicate the user token so that it can be used to create a process.
        var duplicateUserTokenPtr = new IntPtr();
        if (!AdvApi32.DuplicateTokenEx(userTokenPtr, AdvApi32.MAXIMUM_ALLOWED, null, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out duplicateUserTokenPtr))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // Create an environment block for the interactive process.
        var environmentPtr = new IntPtr();
        if (!UserEnv.CreateEnvironmentBlock(out environmentPtr, duplicateUserTokenPtr, false))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // Create the process under the target user's context.
        var processFlags = CREATE_PROCESS_FLAGS.NORMAL_PRIORITY_CLASS | CREATE_PROCESS_FLAGS.CREATE_NEW_CONSOLE | CREATE_PROCESS_FLAGS.CREATE_UNICODE_ENVIRONMENT;
        var processInfo = new PROCESS_INFORMATION();
        var startupInfo = new STARTUPINFO();
        startupInfo.cb = Marshal.SizeOf(startupInfo);
        if (!AdvApi32.CreateProcessAsUser
        (
            duplicateUserTokenPtr, 
            process.StartInfo.FileName, 
            process.StartInfo.Arguments, 
            null, 
            null, 
            false, 
            processFlags, 
            environmentPtr, 
            null, 
            ref startupInfo, 
            out processInfo
        ))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // Free used resources.
        Kernel32.CloseHandle(processInfo.hProcess);
        Kernel32.CloseHandle(processInfo.hThread);
        if (userTokenPtr != null)
        {
            Kernel32.CloseHandle(userTokenPtr);
        }

        if (duplicateUserTokenPtr != null)
        {
            Kernel32.CloseHandle(duplicateUserTokenPtr);
        }

        if (environmentPtr != null)
        {
            UserEnv.DestroyEnvironmentBlock(environmentPtr);
        }
    }
}

And here's how the code is invoked:

以下是代码的调用方式:

var process = new Process();
process.StartInfo = new ProcessStartInfo { FileName = @"C:\path-to\target.exe", Arguments = "-arg1 -arg2" };
process.StartAsActiveUser();

Hope it helps!

希望能帮助到你!

回答by Oded Ben Dov

Found this lib written up on Code Project: https://www.codeproject.com/Tips/1054098/Simple-Csharp-Watchdog

在 Code Project 上找到了这个库:https: //www.codeproject.com/Tips/1054098/Simple-Csharp-Watchdog

It was posted 3 years after the latest answer here, so adding it for record's sake.

它是在此处最新答案发布 3 年后发布的,因此为了记录起见添加它。

-- Addendum: Installed it in our app, and it works pretty well. Needed slight tweaking to support our use case, but the code is pretty solid and straight forward

-- 附录:在我们的应用程序中安装了它,并且运行良好。需要稍微调整以支持我们的用例,但代码非常可靠和直接