简体   繁体   English

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

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

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. 因此,Windows服务可能不再是一个选项。 I could probably just create a different desktop/console application to do this, but that kind of defeats its purpose. 我可能只是创建一个不同的桌面/控制台应用程序来做到这一点,但这种方式失败了。

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. 此外,由于播放器将显示第三方Flash内容,因此增加的优势将是监视资源使用情况,并重新启动播放器,如果说,一些蹩脚的Flash电影开始泄漏内存。

EDIT 2: I forgot to mention, the application I would like to monitor/restart has absolutely no need to run on either the LocalSystem account nor with any administrative privileges at all. 编辑2:我忘了提一下,我想要监视/重启的应用程序绝对不需要在LocalSystem帐户上运行,也不需要任何管理权限。 Actually, I'd prefer it to run using the currently logged user credentials. 实际上,我更喜欢它使用当前记录的用户凭据运行。

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! 希望能帮助到你!

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上搜索。

The watchdog process could make use of System.Diagnostics.Process to launch the application, use the WaitForExitMethod() and check the ExitCode property. 监视程序进程可以使用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. 对于宿主应用程序,您可以使用输出类型为“Windows Application”的.NET应用程序,根本就没有表单。 For example: 例如:

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!");
        }
    }
}

Found this lib written up on Code Project: https://www.codeproject.com/Tips/1054098/Simple-Csharp-Watchdog 在代码项目中发现了这个lib: 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 需要稍微调整以支持我们的用例,但代码非常可靠且直接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 数据库桌面应用程序权限管理的最佳方法是什么? - What is the Best way for database desktop application permission managing? 通过网络在android应用程序和WPF桌面应用程序之间传输数据的最佳方法是什么 - what is the best way to transfer data between android application and WPF desktop application over network 将.NET Core桌面应用程序连接到Azure SQL Server的最佳方法是什么 - What is the best way to connect a .NET Core desktop application to a Azure SQL Server 在数据库访问应用程序中管理并发的最佳方法是什么? - What's the best way to manage concurrency in a database access application? 在C#中设计全屏应用程序的最佳方法是什么? - What's the best way to design a full screen application in C#? 在应用程序启动时隐藏主窗体的最佳方法是什么 - What is the best way to hide the main form on application's startup .NET Windows窗体应用程序更新自身的最佳方法是什么? - What's the best way for a .NET windows forms application to update itself? 在不使用数据库的情况下,为桌面存储/检索数据的最佳方法是什么? - What's the best way to store/retrieve data for a desktop without using a database? 有没有办法将应用程序的GUI带到当前桌面? - Is there a way to bring an application's GUI to the current desktop? 什么是多线程的最佳方式? - What's the best way to multithread?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM