【问题标题】:What's the best way to watchdog a desktop application?监视桌面应用程序的最佳方式是什么?
【发布时间】:2012-06-24 04:09:11
【问题描述】:

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

最初我认为最好的方法是从 Windows 服务监视/重新启动进程,直到我发现自从 Vista Windows services should not interact with the desktop

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

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

您认为哪种方式最优雅?

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

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

【问题讨论】:

  • 为什么这听起来像恶意软件病毒
  • 创建一个确保另一个程序始终运行的程序是恶意程序的标志。服务可以涵盖大多数合法用例。
  • 也许您在问如果应用程序死了如何重新启动它:stackoverflow.com/questions/779405/…
  • @SliverNinja:所以问一个编程问题是错误的,如果答案可以用于恶意软件/病毒目的?可能所以应该要求任何问题的证明写出该问题与恶意软件无关?这就是一直困扰我的问题,如果您稍微提出疑问,可能会唤起恶意软件的概念,您必须为自己辩护并说服您并不是要编写病毒。在被证明有罪之前是无辜的怎么办?看看你评论中的所有这些赞成票!
  • @Gabe:我是这个社区的一员,和你自己一样。我的观点是,如果你的道德或士气禁止你回答问题,那很好。但是,当您拥有的所有证据都是您的“直觉”时,暗示一个问题可能具有恶意意图,这对 OP 来说是不礼貌的。我不会在这里继续讨论这个问题,我很抱歉提出这个问题,这不是地方。如果您有兴趣进一步讨论,请随时在 meta 上提出问题并在此处链接。谢谢。

标签: c# .net windows-services desktop-application watchdog


【解决方案1】:

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

但是,由于他发布的链接显示了 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);
        }
    }
}

下面是代码的调用方式:

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

希望对你有帮助!

【讨论】:

  • 对于将来使用此代码的任何人:它运行良好,但服务必须作为 LocalSystem 运行。
  • 支持什么windows版本?
  • 在 Vista 和 7 下应该可以正常工作。还没有测试过其他的。如果您运行的是较旧的操作系统(例如 XP),则不需要此代码,因为当时 Windows 服务能够显示 GUI。
  • 您是否也在崩溃/退出时重新启动了应用程序?我正在尝试实现它,但似乎失败了。
  • 为什么我会在 WTSQueryUserToken() 处收到错误消息。错误:试图引用不存在的令牌。我的服务作为本地系统运行。
【解决方案2】:

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

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

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

还有这个:

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

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

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

【讨论】:

  • 抱歉,我已链接到 C++ 代码...但您可以轻松找到 c# 风格。
  • 我刚刚分享了我的 C# 实现作为替代答案,以防其他人面临同样的问题。
【解决方案3】:

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

为了回应对该问题的投诉,我在处理我没有源代码控制访问权限的旧呼叫中心应用程序时不得不使用这种方法。

编辑:

对于主机应用程序,您可以使用输出类型为“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!");
        }
    }
}

【讨论】:

  • 这或多或少正是我尝试过的,但是被监控的应用程序有一个 GUI,所以这个方法在 Vista 和更高版本中不起作用。
  • @AxelMagagnini - 为什么它不起作用。他不建议在 Windows 服务中这样做。
  • @ChrisDunaway 我以为他是这个意思,如果我误解了,抱歉。如果是这样,那你会从哪里做呢?
  • 是的,我忽略了 Windows 服务方面!
【解决方案4】:

发现这个库写在代码项目上: https://www.codeproject.com/Tips/1054098/Simple-Csharp-Watchdog

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

-- 附录: 将它安装在我们的应用程序中,效果很好。需要稍作调整以支持我们的用例,但代码非常可靠且直截了当

【讨论】:

  • 我没有尝试过,但从外观上看,它解决了一个稍微不同的问题。我将只允许您的应用程序启动和监视进程,但由于原始帖子中提到的限制,它不能作为 Windows 服务工作。它使用与@oasten 的答案相同的方法。
  • 尝试后更新了答案。无论如何,您是对的 - 它不是一项服务,从您最初的问题来看,自 Windows Vista 以来似乎一项服务不适合。此代码将创建另一个进程,让您的应用程序保持活动状态(并且它具有交叉检查以保持看门狗活动,以及心跳机制)。仅供参考。 @oasten 的答案肯定是相似的,这只是更“代码完整”
猜你喜欢
  • 1970-01-01
  • 2022-01-21
  • 1970-01-01
  • 1970-01-01
  • 2019-04-29
  • 1970-01-01
  • 1970-01-01
  • 2010-10-10
  • 1970-01-01
相关资源
最近更新 更多