我正在编写一个测试应用程序,以监视另一个Windows应用程序并在允许用户使用它们之前执行操作。
背景
我们有可以访问计算机并启动应用程序的用户。对于其中一些应用程序,我们希望用户填写一些表格,然后才允许他们使用该应用程序。同时,我们要跟踪应用程序的总运行时间(即用户使用该应用程序的时间)。 用户可以运行的应用程序并非全部是第三方应用程序,因此我们无法控制其“质量”。
当前解决方案
使用此Code Project article和WMI,我创建了一个监视应用程序,该应用程序跟踪应用程序的打开和关闭并显示要填写的表单。
问题
我正在以Calculator.exe为例测试监视应用程序。监视可以正确检测到可执行文件的启动和关闭,如果用户取消弹出的表单,我们可以杀死该应用程序。我们还可以使用表格中的数据以及开始和结束时间编写日志。 不幸的是,可执行文件未以任何方式“绑定”到应用程序,并且我们不能阻止用户简单地忽略监视应用程序表单并使用他们启动的应用程序。
可能的解决方案
杀死启动的应用程序,如果用户提交了所有信息,则显示表单并重新启动应用程序。 此解决方案可行,但是某些应用程序可能不希望被突然杀死。
使用solution described in this answer挂起已启动应用程序的线程。 我的疑问是关于暂停线程。如上所述,我们不知道第三方申请的编写情况如何。有死锁的风险吗? 同样在这种情况下,终止进程可能会导致某些第三方应用程序出现问题
更改策略:而不是监视应用程序的启动,而是创建启动器,然后edit the registry key for the application启动启动器而不是应用程序。这种策略是我所追求的,但是如果更改注册表项,我仍然不知道如何从启动器启动应用程序。
有没有我们没有想到的更好的解决方案? 如果没有,那么“三个”中的哪一个是“去”?
谢谢!
答案 0 :(得分:0)
我错过了什么吗?最简单的解决方案似乎是在填写表单后才启动应用程序。
答案 1 :(得分:-1)
您最好的选择是使用Windows hooks。
使用挂钩可以监视系统中某些类型的事件。例如正在执行的应用程序或拦截点击的次数更多。
您也可以使用event tracing来监视Windows中应用程序的执行和终止。
这是我刚刚给出的链接中的一个示例:
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;
}
}
}