C# 监视可执行文件的启动并在用户使用它之前执行操作

C# Monitor launch of an executable and do operation before the user an use it

我正在编写一个测试应用程序来监视另一个 windows 应用程序并在允许用户使用它们之前执行操作。

背景

我们有可以访问计算机和启动应用程序的用户。对于其中一些应用程序,我们希望用户填写一个小表格,然后他们将被允许使用该应用程序。同时,我们要跟踪应用程序的总 运行 时间(即用户使用该应用程序的时长)。 用户可以 运行 的应用程序并不都是第 3 方应用程序,我们无法控制他们的 "quality".

当前解

使用此 Code Project article 和 WMI,我创建了一个监控应用程序,用于跟踪应用程序的打开和关闭,显示要填写的表单。

问题

我正在使用 Calculator.exe 作为示例测试监控应用程序。监控正确检测到可执行文件的启动和关闭,如果用户取消弹出的表单,我们可以终止应用程序。我们还可以使用表单中的数据以及开始和结束时间来编写日志。 不幸的是,可执行文件在任何方面都不是 "bound" 应用程序,我们无法阻止用户简单地忽略监控应用程序表单并使用他们启动的应用程序。

可能的解决方案

  1. 终止已启动的应用程序,如果用户提交了所有信息,则显示表单并重新启动应用程序。 此解决方案可行,但某些应用程序可能不乐意被突然杀死。

  2. 使用 solution described in this answer 挂起启动的应用程序 的线程。 我在这里怀疑是关于暂停线程。如上所述,我们不知道第 3 方应用程序编写得如何。有死锁的风险吗? 同样在这种情况下,终止进程可能会导致某些第 3 方应用程序出现问题

  3. 更改策略:不监视应用程序的启动,而是制作启动器和edit the registry key for the application启动启动器而不是应用程序。这个策略是我倾向于的,但我仍然不知道如果我更改注册表项如何从启动器启动应用程序。

有没有我们没有考虑的更好的解决方案? 如果不是,那么 3 个中的哪个是 "go-to"?

谢谢!

您最好的选择是使用 windows hooks.
使用钩子,您可以监视系统的某些类型的事件。例如正在执行或拦截点击的应用程序更多。
您还可以使用 event tracing 来监控 windows.
中应用程序的执行和终止 这是我刚刚给出的 link 中的一个示例:

using Diagnostics.Tracing;
using Diagnostics.Tracing.Parsers;
using System;
using System.Diagnostics;
using System.IO;

namespace ProcessMonitor
{

    /// <summary>
    /// The main program monitors processes (and image loads) using ETW.  
    /// </summary>
    class Program
    {
        /// <summary>
        /// This is a demo of using TraceEvent to activate a 'real time' provider that is listening to 
        /// the MyEventSource above.   Normally this event source would be in a differnet process,  but 
        /// it also works if this process generate the evnets and I do that here for simplicity.  
        /// </summary>
        static int Main(string[] args)
        {
            // Today you have to be Admin to turn on ETW events (anyone can write ETW events).   
            if (!(TraceEventSession.IsElevated() ?? false))
            {
                Console.WriteLine("To turn on ETW events you need to be Administrator, please run from an Admin process.");
                return -1;
            }

            // As mentioned below, sessions can outlive the process that created them.  Thus you need a way of 
            // naming the session so that you can 'reconnect' to it from another process.   This is what the name
            // is for.  It can be anything, but it should be descriptive and unique.   If you expect mulitple versions
            // of your program to run simultaneously, you need to generate unique names (e.g. add a process ID suffix) 
            var sessionName = "ProessMonitorSession";
            using (var session = new TraceEventSession(sessionName, null))  // the null second parameter means 'real time session'
            {
                // Note that sessions create a OS object (a session) that lives beyond the lifetime of the process
                // that created it (like Filles), thus you have to be more careful about always cleaning them up. 
                // An importanty way you can do this is to set the 'StopOnDispose' property which will cause the session to 
                // stop (and thus the OS object will die) when the TraceEventSession dies.   Because we used a 'using'
                // statement, this means that any exception in the code below will clean up the OS object.   
                session.StopOnDispose = true;

                // By default, if you hit Ctrl-C your .NET objects may not be disposed, so force it to.  It is OK if dispose is called twice.
                Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { session.Dispose(); };

                // prepare to read from the session, connect the ETWTraceEventSource to the session
                using (var source = new ETWTraceEventSource(sessionName, TraceEventSourceType.Session))
                {
                    Action<TraceEvent> action = delegate(TraceEvent data)
                    {
                        // Console.WriteLine("GOT EVENT: " + data.ToString());
                        var taskName = data.TaskName;
                        if (taskName == "ProcessStart" || taskName == "ProcessStop") 
                        {
                            string exe = (string) data.PayloadByName("ImageName");
                            string exeName = Path.GetFileNameWithoutExtension(exe);

                            int processId = (int) data.PayloadByName("ProcessID");
                            if (taskName == "ProcessStart")
                            {
                                int parentProcessId = (int)data.PayloadByName("ParentProcessID");
                                Console.WriteLine("{0:HH:mm:ss.fff}: {1,-12}: {2} ID: {3} ParentID: {4}", 
                                    data.TimeStamp, taskName, exeName, processId, parentProcessId);
                            }
                            else
                            {
                                int exitCode = (int) data.PayloadByName("ExitCode");
                                long cpuCycles = (long) data.PayloadByName("CPUCycleCount");
                                Console.WriteLine("{0:HH:mm:ss.fff}: {1,-12}: {2} ID: {3} EXIT: {4} CPU Cycles: {5:n0}",
                                    data.TimeStamp, taskName, exeName, processId, exitCode, cpuCycles);
                            }
                        }
                    };

                    // Hook up the parser that knows about Any EventSources regsitered with windows.  (e.g. the OS ones. 
                    var registeredParser = new RegisteredTraceEventParser(source);
                    registeredParser.All += action;

                    // You can also simply use 'logman query providers' to find out the GUID yourself and wire it in. 
                    var processProviderGuid = TraceEventSession.GetProviderByName("Microsoft-Windows-Kernel-Process");
                    if (processProviderGuid == Guid.Empty)
                    {
                        Console.WriteLine("Error could not find Microsoft-Windows-Kernel-Process etw provider.");
                        return -1;
                    }

                    // Using logman query providers Microsoft-Windows-Kernel-Process I get 
                    //     0x0000000000000010  WINEVENT_KEYWORD_PROCESS
                    //     0x0000000000000020  WINEVENT_KEYWORD_THREAD
                    //     0x0000000000000040  WINEVENT_KEYWORD_IMAGE
                    //     0x0000000000000080  WINEVENT_KEYWORD_CPU_PRIORITY
                    //     0x0000000000000100  WINEVENT_KEYWORD_OTHER_PRIORITY
                    //     0x0000000000000200  WINEVENT_KEYWORD_PROCESS_FREEZE
                    //     0x8000000000000000  Microsoft-Windows-Kernel-Process/Analytic
                    // So 0x10 is WINEVENT_KEYWORD_PROCESS
                    session.EnableProvider(processProviderGuid, TraceEventLevel.Informational, 0x10);

                    Console.WriteLine("Starting Listening for events");
                    // go into a loop processing events can calling the callbacks.  Because this is live data (not from a file)
                    // processing never completes by itself, but only because someone called 'source.Close()'.  
                    source.Process();
                    Console.WriteLine();
                    Console.WriteLine("Stopping Listening for events");
                }
            }
            return 0;
        }
    }

}

我错过了什么吗? 最简单的解决方案似乎是在他们填写您的表格之前不要启动应用程序。