【问题标题】:Detect and restart a crashed windows app using .NET core使用 .NET 核心检测并重新启动崩溃的 Windows 应用程序
【发布时间】:2021-06-25 18:17:42
【问题描述】:

我们很少有第三方供应商提供的经常崩溃的应用程序,而且由于我们没有它的源代码,我们无法真正正确地修复它。所以我决定创建一个.NET core 5 worker 服务来监控这些应用程序并根据需要重新启动它们。

我如何检测这个应用程序是否因为应用程序本身没有关闭但出现错误窗口而崩溃。该应用程序仍显示在任务管理器的“进程”选项卡上。它崩溃的迹象来自窗口对话框中的消息。

我只需要抓取错误窗口对话框中的错误消息进行日志记录,关闭错误窗口和应用程序,最后再次启动应用程序。 该应用程序已旧;可能是winforms 应用程序。

任何有关如何在.NET core 中执行此操作的指导将不胜感激。

谢谢!

【问题讨论】:

标签: c# .net-core ui-automation .net-5 microsoft-ui-automation


【解决方案1】:

更新为使用 UIAutomation

正如 @Simon Mourier 和 @Ben Voigt 在 cmets 中所建议的那样。 非常感谢!

这就是我让它工作的方式。如果可以做得更好,请随时提出建议。

确保将其添加到.NET core .csproj 文件中,以便能够使用using System.Windows.Automation; 命名空间:

  <ItemGroup>
    <FrameworkReference Include="Microsoft.WindowsDesktop.App" />
  </ItemGroup>

现主Program.cs

class Program
{
    private const int ThreadDelay = 5000;
    public static async Task Main(string[] args)
    {
        var appsFromAppSettings = new List<WatchedApp>
        {
            new WatchedApp()
            {
                AppName = "TMWMPoll",
                NumberOfInstances = 1,
                AppWindowName = "(4650) Test PNET Poller (3) ELogs",
                ErrorWindowName = "TMW MobileComm Xfc",
                AppLocation = @"C:\Users\source\repos\TMWMPoll\publish\setup.exe"
            }
        };

        // I'm using Hashset, because I do not want to add duplicate items to the list.
        var appsToRestart = new HashSet<WatchedApp>();

        while (true)
        {
            var processArray = Process.GetProcesses();

            //Step 1: Handle the errored out apps
            foreach (var app in appsFromAppSettings)
            {
                var process = processArray.FirstOrDefault(p => p.ProcessName == app.AppName);

                // See if the app is even running:
                if (process == null)
                {
                    Console.WriteLine($"Couldn't find the app: '{app.AppName}' to be running. A new instance will be opened for it.");
                    appsToRestart.Add(app);
                    continue;
                }

                // Get the main window of the process we're interested in:
                AutomationElement appMainWindow = AutomationElement.FromHandle(process.MainWindowHandle);
                if (appMainWindow == null)
                {
                    Console.WriteLine($"Couldn't find the app window for: {app.AppName}.");
                    continue;
                }

                // Check if it is being opened as a Window. If it is, then it should implement the Window pattern.
                object pattern;
                if (!appMainWindow.TryGetCurrentPattern(WindowPattern.Pattern, out pattern))
                {
                    continue;
                }

                // Cast the pattern object to WindowPattern
                var window = (WindowPattern)pattern;

                // Get all the child windows.
                // Because if there is a child window, the app could have errored out so we'll be restarting the app to be safe.
                var childElements = appMainWindow.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
                if (childElements.Count > 0)
                {
                    foreach (AutomationElement childElement in childElements)
                    {
                        // Check if it is being opened as a Window. If it is, then it should implement the Window pattern.
                        if (!childElement.TryGetCurrentPattern(WindowPattern.Pattern, out pattern))
                        {
                            continue;
                        }

                        // // Cast the pattern object to WindowPattern
                        var childWindow = (WindowPattern)pattern;

                        // Now read the error message in there:
                        var errorMessage = childElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Text))?.Current.Name;
                        Console.WriteLine($"This is the error to log: {errorMessage}");
                        childWindow.Close();
                    }

                    // This app will need to be restarted, so make note of it.
                    appsToRestart.Add(app);
                    // Finally kill the process after all that logging from those child windows.
                    process.Kill();
                }
            }

            //Step 2: Handle the apps that didn't start or were crashed (by comparing with the processArray)
            var notRunningApps = appsFromAppSettings
                                .Where(aps => !processArray
                                                .Select(pa => pa.ProcessName)
                                                .Contains(aps.AppName))
                                .ToList();

            // Now create the final list of apps for us to open:
            appsToRestart.UnionWith(notRunningApps);

            // Now open all those apps.
            if (appsToRestart.Any())
            {
                Console.WriteLine("Some required apps either crashed or were not running, so starting them now.");
                foreach (var notRunningApp in appsToRestart)
                {
                    //Start the app now
                    for (int i = 1; i <= notRunningApp.NumberOfInstances; i++)
                    {
                        Process.Start(notRunningApp.AppLocation);
                    }
                }
            }

            // Now clear the hashset for appsToRestart before the next run
            appsToRestart.Clear();

            // Poll every ThreadDelay microseconds.
            await Task.Delay(ThreadDelay);
        };
    }
}

WatchedApp 记录:

//In a record type, you can't change the value of value-type properties or the reference of reference-type properties. 
public record WatchedApp
{
    public string AppName { get; init; }
    public sbyte NumberOfInstances { get; init; }
    public string AppWindowName { get; init; }
    public string ErrorWindowName { get; init; }
    public string AppLocation { get; init; }
}

【讨论】:

  • 这将适用于使用例如传统 Win32 应用程序。 MessageBox() 显示错误。我建议改用 System.Windows.Automation 命名空间...它不需要 p/invoke 并且应该与使用更新的 UI 框架(如 WPF)的应用程序一起使用。这里有一个例子:docs.microsoft.com/en-us/archive/msdn-magazine/2008/february/…
  • @BenVoigt 非常感谢您提供的链接。你能看看我更新的答案,看看它是否看起来不错。任何建议将不胜感激。
  • 这看起来是正确的想法。您可能希望遍历错误弹出窗口中的所有文本元素,以防有多个。您可能还想检查弹出窗口的标题,以记录它或将错误与正常对话框区分开来。但我会说你做得对。
  • @BenVoigt:我对此有一个后续问题。你能看看吗? stackoverflow.com/questions/68323725/…
猜你喜欢
  • 1970-01-01
  • 2010-09-09
  • 2017-03-26
  • 2016-02-23
  • 1970-01-01
  • 2011-01-05
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
相关资源
最近更新 更多