Обнаружение и перезапуск аварийного приложения Windows с помощью ядра .NET.

У нас есть несколько приложений, поставляемых сторонними поставщиками, которые довольно часто аварийно завершают работу, и, поскольку у нас нет для них исходного кода, мы не можем исправить это должным образом. Поэтому я решил создать рабочую службу .NET core 5, которая будет отслеживать эти приложения и перезапускать их по мере необходимости.

Как определить, произошло ли сбой этого приложения, потому что само приложение не закрывается, но появляется окно с ошибкой. Приложение по-прежнему отображается на вкладке «Процессы» в диспетчере задач. Индикация того, что он разбился, исходит из сообщения в диалоговом окне.

Мне просто нужно получить сообщение об ошибке в диалоговом окне окна ошибки для регистрации, закрыть окно ошибки и приложение и, наконец, снова запустить приложение. Приложение старое; возможно winforms приложение.

Будем очень признательны за любые рекомендации о том, как это сделать в .NET core.

Спасибо!

введите здесь описание изображения


person Ash K    schedule 25.06.2021    source источник
comment
Может быть, опросить окно с таким названием. Кроме того, если это приложение .net, и ваш поставщик совершенно не согласен (и вам это разрешено законом), есть варианты дизассемблирования и перекомпиляции.   -  person TheGeneral    schedule 26.06.2021
comment
@TheGeneral У вас есть пример фрагмента того, как опросить окно и прочитать сообщения в нем в .NET? Спасибо!   -  person Ash K    schedule 28.06.2021
comment
Вы можете использовать автоматизацию пользовательского интерфейса docs.microsoft.com. /en-us/dotnet/framework/ui-automation/, например аналогичный вопрос: stackoverflow.com/questions/24480596/   -  person Simon Mourier    schedule 01.07.2021
comment
Также stackoverflow.com/questions/7926107/   -  person Simon Mourier    schedule 01.07.2021
comment
@SimonMourier Большое спасибо за эти замечательные примеры. Можете ли вы взглянуть на мой обновленный ответ, чтобы увидеть, хорошо ли он выглядит. Любые предложения будут ценны.   -  person Ash K    schedule 01.07.2021


Ответы (1)


Обновлено для использования UIAutomation

как предложили @Simon Mourier и @Ben Voigt в комментариях. Большое спасибо!

Вот как я заставил это работать. Пожалуйста, не стесняйтесь вносить предложения, если это можно сделать лучше.

Не забудьте добавить это в файл .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; }
}
person Ash K    schedule 30.06.2021
comment
Это будет работать для традиционных приложений Win32, например. MessageBox() для отображения ошибки. Вместо этого я бы предложил использовать пространство имен System.Windows.Automation... оно не требует p/invoke и должно работать с приложениями, использующими более новые фреймворки пользовательского интерфейса, такие как WPF. Вот пример: docs.microsoft.com/en-us/archive/msdn-magazine/2008/february/ - person Ben Voigt; 30.06.2021
comment
@BenVoigt Большое спасибо за ссылку. Можете ли вы взглянуть на мой обновленный ответ, чтобы увидеть, хорошо ли он выглядит. Любые предложения будут ценны. - person Ash K; 01.07.2021
comment
Это похоже на правильную идею. Вы можете зациклиться на всех текстовых элементах во всплывающем окне ошибки, если их больше одного. И вы, вероятно, также захотите проверить заголовок всплывающего окна, либо для его регистрации, либо для того, чтобы отличить ошибки от обычных диалоговых окон. Но я бы сказал, что вы все делаете правильно. - person Ben Voigt; 01.07.2021
comment
@BenVoigt: у меня есть дополнительный вопрос к этому. Не могли бы вы взглянуть? stackoverflow.com/questions/68323725/ - person Ash K; 13.07.2021