1、实现服务进程启动窗体进程
2、代码如下:
namespace AppMonitor.Model
{
/// <summary>
/// 应用信息
/// </summary>
public class AppInfo
{
/// <summary>
/// sesssion名称
/// </summary>
[XmlElement()]
public string WinStationName { get; set; }
[XmlElementAttribute()]
public List<AppInfoDetail> Item;
}
public class AppInfoDetail
{
/// <summary>
/// 会话Id,小于0时,由系统确定活动会话
/// </summary>
[XmlAttribute()]
public int SessionId { get; set; }
/// <summary>
/// 应用名称
/// </summary>
[XmlAttribute()]
public string ExeName { get; set; }
/// <summary>
/// 应用路径
/// </summary>
[XmlAttribute()]
public string Path { get; set; }
/// <summary>
/// 心跳文件
/// </summary>
[XmlAttribute()]
public string HeartFile { get; set; }
/// <summary>
/// 崩溃间隔(分钟为单位)
/// </summary>
[XmlAttribute()]
public int CrashInterval { get; set; }
/// <summary>
/// 是否启用
/// </summary>
[XmlAttribute()]
public bool IsEnabled { get; set; }
/// <summary>
/// 描述
/// </summary>
[XmlAttribute()]
public string Description { get; set; }
/// <summary>
/// 异常窗口标题
/// </summary>
[XmlAttribute()]
public string FatalWindowTitle { get; set; }
/// <summary>
/// 关闭按钮文本
/// </summary>
[XmlAttribute()]
public string CloseButtonTitle { get; set; }
}
}
2、用到了Cjwdev.WindowsApi.dll中的方法,开机时,获取Console的SessionId需要做特殊处理,否则服务进程启用窗体进程,会把窗体进程作为服务启动,从而不会在任务栏出现图标
public partial class AppMonitor : ServiceBase
{
/// <summary>
/// 是否运行
/// </summary>
private bool IsRunning = false;
/// <summary>
/// 监视线程
/// </summary>
private System.Threading.Thread m_Monitor;
/// <summary>
/// 子窗口回调函数代理
/// </summary>
public EnumWindowsProc callBackEnumChildWindows;
/// <summary>
/// 枚举主窗口
/// </summary>
public EnumWindowsProc callBackEnumWindows;
public AppMonitor()
{
InitializeComponent();
callBackEnumChildWindows = new EnumWindowsProc(ChildWindowProcess);
callBackEnumWindows = new EnumWindowsProc(ChildWindowProcess);
}
protected override void OnStart(string[] args)
{
try
{
IsRunning = true;
m_Monitor = new System.Threading.Thread(new System.Threading.ThreadStart(this.Monitor));
m_Monitor.Start();
}
catch(Exception ex)
{
Log.WriteLog("OnStart" + ex.Message);
}
}
protected override void OnStop()
{
IsRunning = false;
}
protected void Monitor()
{
System.Threading.Thread.Sleep(1000);
//System.Threading.Thread.Sleep(10 * 1000);
int sessionId = -1;
while (IsRunning)
{
// 加载xml
string xmlFileName = SystemConst.AppInfoFile;
try
{
string xml = XmlHelper.LoadXmlFromFile(xmlFileName);
var appInfo = XmlSerializer.Deserialize<AppInfo>(xml);
if (appInfo != null)
{
if (sessionId < 0)
{
sessionId = GetActiveSessionId(IntPtr.Zero, appInfo);
}
if (sessionId < 0)
{
// 暂停1分钟
System.Threading.Thread.Sleep(1 * 60 * 1000);
continue;
}
foreach (var item in appInfo.Item)
{
if (!item.IsEnabled)
continue;
// 检查进程
bool restartApp = false;
var processList = GetProcess(item.ExeName);
if (processList == null || processList.Length < 1)
{
restartApp = true;
}
else
{
var process = processList.Where(x => x.MainModule.FileName == item.Path).FirstOrDefault();
if (process == null)
{
restartApp = true;
}
else
{
//https://www.cnblogs.com/kuangke/p/9524310.html
//NtQuerySystemInformation
//if (process.Responding)
// 如果发生致命错误
//FindFatalCloseButton(item);
// 检查进程是否正常
if (AppIsCrash(item, process))
{
// 杀掉进程
process.Kill();
restartApp = true;
// 暂停10秒
System.Threading.Thread.Sleep(10 * 1000);
}
}
}
if (restartApp)
{
//System.Diagnostics.Process.Start(item.Path);
// 启动进程
uint curSessionId = (uint)item.SessionId;
if (item.SessionId < 0)
{
curSessionId = (uint)sessionId;
}
StartApp(item.Path, curSessionId);
Log.WriteLog(string.Format("SessionId: {0}, 启动应用: {1}, 应用路径: {2}", curSessionId, item.ExeName, item.Path));
}
}
// 暂停1分钟
System.Threading.Thread.Sleep(1 * 60 * 1000);
}
else
{
Log.WriteLog("Monitor" + "未配置应用参数");
System.Threading.Thread.Sleep(10 * 60 * 1000);
}
}
catch(Exception ex)
{
Log.WriteLog("Monitor" + ex.Message);
System.Threading.Thread.Sleep(10 * 60 * 1000);
}
}
}
/// <summary>
/// 子窗口回调处理函数
/// </summary>
/// <param name="hwnd"></param>
/// <param name="lParam"></param>
/// <returns></returns>
public bool ChildWindowProcess(IntPtr hwnd, IntPtr lParam)
{
int len;
StringBuilder title = new StringBuilder(200);
len = GetWindowText(hwnd, title, 200);
string winTitle = title.ToString();
if (!string.IsNullOrEmpty(winTitle))
{
string key = Marshal.PtrToStringBSTR(lParam);
if (key == winTitle)
{
SendMessage(hwnd, WM_SETFOCUS, 0, 0);
SendMessage(hwnd, BM_CLICK, 0, 0);
Log.WriteLog("Monitor:发生了异常,并自动关闭了程序");
}
}
return true;
}
/// <summary>
/// 单击事件
/// </summary>
public const int BM_CLICK = 0x00F5;
/// <summary>
/// 获取焦点事件
/// </summary>
public const int WM_SETFOCUS = 0x0007;
/// <summary>
/// 查找异常关闭按钮
/// </summary>
/// <param name="detail"></param>
private void FindFatalCloseButton(AppInfoDetail detail)
{
if (detail == null ||
string.IsNullOrEmpty(detail.FatalWindowTitle) ||
string.IsNullOrEmpty(detail.CloseButtonTitle))
return;
IntPtr keyPtr = Marshal.StringToBSTR(detail.FatalWindowTitle);
EnumWindows(callBackEnumWindows, keyPtr);
//测试警告框
//主窗口标题
IntPtr maindHwnd = FindWindowW(null, detail.FatalWindowTitle);
if (maindHwnd != IntPtr.Zero)
{
//按钮控件标题
IntPtr childHwnd = FindWindowExW(maindHwnd, IntPtr.Zero, null, detail.CloseButtonTitle);
if (childHwnd != IntPtr.Zero)
{
SendMessage(childHwnd, WM_SETFOCUS, 0, 0);
SendMessage(childHwnd, BM_CLICK, 0, 0);
Log.WriteLog("Monitor:发生了异常,并自动关闭了" + detail.ExeName);
return;
}
else
{
keyPtr = Marshal.StringToBSTR(detail.CloseButtonTitle);
EnumChildWindows(maindHwnd, callBackEnumChildWindows, keyPtr);
}
}
else
{
IntPtr hwd = FindDesktopWindow(detail.FatalWindowTitle);
if (hwd != IntPtr.Zero)
{
keyPtr = Marshal.StringToBSTR(detail.CloseButtonTitle);
EnumChildWindows(maindHwnd, callBackEnumChildWindows, keyPtr);
}
}
}
enum GetWindow_Cmd : uint
{
GW_HWNDFIRST = 0,
GW_HWNDLAST = 1,
GW_HWNDNEXT = 2,
GW_HWNDPREV = 3,
GW_OWNER = 4,
GW_CHILD = 5,
GW_ENABLEDPOPUP = 6
}
[DllImport("user32.dll", EntryPoint = "GetDesktopWindow", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", EntryPoint = "GetWindow", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr GetWindow(IntPtr hd, GetWindow_Cmd uCmd);
/// <summary>
/// 查找桌面窗口
/// </summary>
/// <param name="catpion"></param>
/// <returns></returns>
public IntPtr FindDesktopWindow(string catpion)
{
IntPtr hd = GetDesktopWindow();
//得到屏幕上第一个子窗口
hd = GetWindow(hd, GetWindow_Cmd.GW_CHILD);
StringBuilder title = new StringBuilder(200);
//循环得到所有的子窗口
while (hd != IntPtr.Zero)
{
title.Clear();
int len = GetWindowText(hd, title, 200);
string winTitle = title.ToString();
if (!string.IsNullOrEmpty(winTitle))
{
if (winTitle.ToLower() == catpion.ToLower())
return hd;
}
hd = GetWindow(hd, GetWindow_Cmd.GW_HWNDNEXT);
}
return IntPtr.Zero;
}
/// <summary>
/// 进程是否运行
/// </summary>
/// <param name="appName"></param>
/// <returns></returns>
protected Process[] GetProcess(string appName)
{
System.Diagnostics.Process[] processList = System.Diagnostics.Process.GetProcessesByName(appName);
return processList;
}
/// <summary>
/// 进程是否崩溃
/// </summary>
/// <param name="detail"></param>
/// <param name="p"></param>
/// <returns></returns>
protected bool AppIsCrash(AppInfoDetail detail, Process p)
{
// 如果是文件夹,则获取文件夹中文件的最大日期
if (Directory.Exists(detail.HeartFile))
{
DateTime tnow = DateTime.Now.AddDays(-1);
DateTime LastWriteTime = GetLastFile(detail.HeartFile, tnow);
var totalMinutes = (DateTime.Now - LastWriteTime).TotalMinutes;
if (totalMinutes > detail.CrashInterval)
{
Log.WriteLog(string.Format("应用:{0},目录{1},最后修改日期{2},离现在{3}分钟", detail.Path, detail.HeartFile, LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss"), totalMinutes));
return true;
}
else
{
return false;
}
}
// 如果心跳文件不存在,则检查进程响应状态
if (File.Exists(detail.HeartFile))
{
return !p.Responding;
}
// 检查心跳文件
var lastWriteTime = File.GetLastWriteTime(detail.HeartFile);
if ((DateTime.Now - lastWriteTime).TotalMinutes > detail.CrashInterval)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// 获取最近的文件的时间
/// </summary>
/// <param name="dir"></param>
/// <param name="tnow"></param>
private DateTime GetLastFile(string dir, DateTime tnow)
{
DirectoryInfo di = new DirectoryInfo(dir);
FileInfo[] fis = di.GetFiles($"*.*");//文件类型
foreach (FileInfo fi in fis)
{
if (fi.LastWriteTime > tnow)
tnow = fi.LastWriteTime;
}
DirectoryInfo[] dis = di.GetDirectories();//目录下的子目录
foreach (DirectoryInfo item in dis)
{
if (item.LastWriteTime > tnow)
tnow = item.LastWriteTime;
var tLastTime = GetLastFile(item.FullName, tnow);
if (tLastTime > tnow)
tLastTime = tnow;
}
return tnow;
}
/// <summary>
/// 启动应用
/// </summary>
/// <param name="filePath"></param>
/// https://blog.csdn.net/weixin_30399821/article/details/97821077
protected void StartApp(string filePath, uint sessionId)
{
try
{
string appStartPath = filePath;
IntPtr userTokenHandle = IntPtr.Zero;
// 在服务器上,获取的ConsoleSessionId使用后,窗体程序不显示
// 所以需要在配置文件中配置SessionId,配置为能显示窗体程序的SessionId
//uint csId = ApiDefinitions.WTSGetActiveConsoleSessionId();
ApiDefinitions.WTSQueryUserToken(sessionId, ref userTokenHandle);
ApiDefinitions.PROCESS_INFORMATION procInfo = new ApiDefinitions.PROCESS_INFORMATION();
ApiDefinitions.STARTUPINFO startInfo = new ApiDefinitions.STARTUPINFO();
startInfo.cb = (uint)Marshal.SizeOf(startInfo);
ApiDefinitions.CreateProcessAsUser(
userTokenHandle,
appStartPath,
"",
IntPtr.Zero,
IntPtr.Zero,
false,
0,
IntPtr.Zero,
null,
ref startInfo,
out procInfo);
//Log.WriteLog("userTokenHandle: " + userTokenHandle.ToString());
if (userTokenHandle != IntPtr.Zero)
ApiDefinitions.CloseHandle(userTokenHandle);
//int pid = (int)procInfo.dwProcessId;
//var windowHandler = CurrentProcess.GetProcessWindowHandle(pid);
//Log.WriteLog("currentAquariusProcessId: " + _currentAquariusProcessId.ToString());
}
catch (Exception ex)
{
Log.WriteLog("StartApp" + ex.Message);
}
}
/// <summary>
/// 获取活动会话的SessionId
/// </summary>
/// <param name="hServer"></param>
/// <returns></returns>
public int GetActiveSessionId(IntPtr hServer, AppInfo appInfo)
{
IntPtr ppSessionInfo = IntPtr.Zero;
uint count = 0;
try
{
// 默认为控制台
string winStationName = "console";
if (appInfo != null && !string.IsNullOrEmpty(appInfo.WinStationName))
winStationName = appInfo.WinStationName;
winStationName = winStationName.ToLower();
if (WTSEnumerateSessions(hServer, 0, 1, ref ppSessionInfo, ref count))
{
Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
Int32 current = (Int32)ppSessionInfo;
for (int ix = 0; ix < count; ++ix)
{
WTS_SESSION_INFO session = new WTS_SESSION_INFO();
session = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
current += dataSize;
Log.WriteLog("SessionId: " + session.SessionId + "; pWinStationName: " + session.pWinStationName);
// 在阿里云服务器中,因为有RDP-TCP#xx的原因,
// 即WinStationName名称可能会有变化,所以此处用StartsWith
// 并且会有一个65536, WinStationName为"RDP-Tcp"的名称
//if (session.State == WTS_CONNECTSTATE_CLASS.WTSActive &&
// session.pWinStationName.ToLower().StartsWith(winStationName))
if (session.pWinStationName.ToLower().StartsWith(winStationName))
{
Log.WriteLog(string.Format("获取到{0}的SessionId:{1} ", session.pWinStationName, session.SessionId));
return (int)session.SessionId;
}
}
}
//return ApiDefinitions.WTSGetActiveConsoleSessionId();
}
finally
{
int error = Marshal.GetLastWin32Error();
WTSFreeMemory(ppSessionInfo);
}
return -1;
}
}