使用 .NET 核心检测并重启崩溃的 windows 应用程序
Detect and restart a crashed windows app using .NET core
我们很少有第三方供应商提供的应用程序经常崩溃,而且由于我们没有它的源代码,我们无法真正正确地修复它。所以我决定创建一个 .NET core 5 worker 服务 来监视这些应用程序并根据需要重新启动它们。
如何检测此应用程序是否已崩溃,因为该应用程序本身并未关闭但出现错误 window。该应用程序仍显示在任务管理器的“进程”选项卡上。它崩溃的迹象来自 window 对话框中的消息。
我只需要在错误 window 对话框中抓取错误消息进行记录,关闭错误 window 和应用程序,最后再次启动应用程序。
该应用程序是旧的;可能是 winforms
应用程序。
任何有关如何在 .NET core
中执行此操作的指导将不胜感激。
谢谢!
已更新为使用 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; }
}
我们很少有第三方供应商提供的应用程序经常崩溃,而且由于我们没有它的源代码,我们无法真正正确地修复它。所以我决定创建一个 .NET core 5 worker 服务 来监视这些应用程序并根据需要重新启动它们。
如何检测此应用程序是否已崩溃,因为该应用程序本身并未关闭但出现错误 window。该应用程序仍显示在任务管理器的“进程”选项卡上。它崩溃的迹象来自 window 对话框中的消息。
我只需要在错误 window 对话框中抓取错误消息进行记录,关闭错误 window 和应用程序,最后再次启动应用程序。
该应用程序是旧的;可能是 winforms
应用程序。
任何有关如何在 .NET core
中执行此操作的指导将不胜感激。
谢谢!
已更新为使用 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; }
}