开发一款IIS自动检测修复工具

article/2025/8/7 7:54:04

         

目录

实现的功能

技术栈与依赖 DLL

实现细节

变量

初始化操作

自定义cpu阈值

检测IIS应用程序池

获取自定义阈值

获取某个应用程序池的占用率

获取性能计数器实例名

Kill 并重新启动应用池

写入日志到 Log 目录,并显示在文本框中

实际运行效果


        此工具可放在服务器中,每隔3秒检测一次iis中所有应用程序池中异常的应用程序。

实现的功能

  • 每3秒检测所有应用程序池中占用cpu过高的程序,cpu阈值可自行设置,超过阈值自动kill进程并且进行重启
  • 每3秒检测iis应用程序池中有崩溃、异常停止的应用程序,且进行自动3次的重启,3次以后不会自动重启,且过滤掉手动停止的iis应用程序池。

此界面左边会显示所有在iis中的应用程序池的信息,右边则是textBox,持续滚动显示日志,图片出现异常是我的本地电脑没有部署IIS。

技术栈与依赖 DLL

类型技术 / DLL用途路径说明
平台WinForms (.NET Framework 4.5.2)主程序框架
核心 DLLMicrosoft.Web.Administration.dll操作 IIS 应用池状态与回收C:\Windows\System32\inetsrv\
系统库System.Diagnostics获取进程信息与 CPU 使用率.NET 自带
日志System.IO记录操作日志到本地文件.NET 自带

⚠️ 注意:程序必须以 管理员权限运行 才能访问 IIS 的相关管理权限。

实现细节

  • 变量

 private System.Threading.Timer MonitorTimer; //定义计时器private const int MonitorInterval = 3000; // 检测间隔:5秒一检测private string LogDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log");//日志目录private string CpuConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cpu_threshold.txt");//cpu阈值文件private Dictionary<string, int> RestartDict = new Dictionary<string, int>(); //应用程序池重启次数                                                                                              // 定义文件路径(可放在程序目录)private string RestartCountPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "restart_counts.json");//应用程序池重启次数文件private volatile bool mIsChecking = false;//标志位避免重入,防止资源竞争。(检测 IIS 应用池状态 CheckAppPools)private bool mIsClose = false;
  • 初始化操作

/// <summary>
/// 初始化列表控件
/// </summary>
private void InitListView()
{listView1.View = View.Details;listView1.Columns.Add("应用程序池", 100);listView1.Columns.Add("进程ID", 100);listView1.Columns.Add("状态", 120);listView1.Columns.Add("CPU 使用率(%)", 120);// 启用 OwnerDrawlistView1.OwnerDraw = true;listView1.DrawColumnHeader += listView1_DrawColumnHeader;listView1.DrawSubItem += listView1_DrawSubItem;
}private void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{e.DrawDefault = true; // 默认绘制列头
}private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{if (e.ColumnIndex == 3) // 第四列是 cpuUsage{float cpu;int mCpuValue = GetCpuThreshold();if (float.TryParse(e.SubItem.Text, out cpu) && cpu >=mCpuValue) // 大于阈值时标红{e.Graphics.DrawString(e.SubItem.Text, listView1.Font, Brushes.Red, e.Bounds);}else{e.Graphics.DrawString(e.SubItem.Text, listView1.Font, Brushes.Black, e.Bounds);}}else{e.DrawDefault = true;}
}/// <summary>
/// 启动定时器监控
/// </summary>
private void StartMonitoring()
{MonitorTimer = new System.Threading.Timer(CheckAppPools, null, 0, MonitorInterval);
}
  • 自定义cpu阈值

 if (button1.Text == "设置 CPU 阈值"){txtCpuThreshold.Visible = true;txtCpuThreshold.Text = GetCpuThreshold().ToString();button1.Text = "保存";label1.Location = new System.Drawing.Point(258, 12);}else{label1.Location = new System.Drawing.Point(129, 11);string input = txtCpuThreshold.Text.Trim();if (int.TryParse(input, out int threshold)){string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cpu_threshold.txt");File.WriteAllText(path, threshold.ToString());txtCpuThreshold.Visible = false;button1.Text = "设置 CPU 阈值";label1.Text = "当前CPU阈值为:" + input + "%";MessageBox.Show($"CPU阈值已设置为 {threshold}%", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);}else{MessageBox.Show("请输入有效的整数值作为阈值。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}}
  • 检测IIS应用程序池

private void CheckAppPools(object state)
{if (mIsChecking) return;mIsChecking = true;try{using (ServerManager serverManager = new ServerManager()){var mCurrentNames = new HashSet<string>();foreach (var pool in serverManager.ApplicationPools){string mProcessID = "";//进程IDstring name = pool.Name;string statusEn = pool.State.ToString(); //应用程序池状态string statusCn = GetStatusDescription(pool.State);//应用程序池状态中文描述double cpuUsage = GetCpuUsageByAppPool(name);//获取某个应用程序池的cpu使用率var wp = pool.WorkerProcesses.FirstOrDefault();if (wp != null){var process = Process.GetProcessById(wp.ProcessId);mProcessID=process.Id.ToString();}else{mProcessID = "NULL";}int threshold = GetCpuThreshold();//我们设置的阈值if (cpuUsage >= threshold){KillAndRestart(pool);//超过了阈值以后直接杀掉然后重启Log($"[{name}](进程ID:{mProcessID}) CPU 使用率过高 {cpuUsage}%,已杀掉该进程并且重启。状态:{statusCn}",true);}//如果是已停止的状态我们通过日志去查是不是崩溃导致的,避免是手动停止的一直重启造成崩溃if (statusEn == "Stopped"){string logSource = "WAS"; // 只筛选管理应用程序池(AppPool)的生命周期,包括启动、停止、崩溃等日志。string logName = "System"; //系统日志DateTime checkTime = DateTime.Now.AddMinutes(-5); // 查最近5分钟bool isCrash = false;try{EventLog eventLog = new EventLog(logName);foreach (EventLogEntry entry in eventLog.Entries){if (entry.TimeGenerated >= checkTime &&entry.Source == logSource &&entry.EntryType == EventLogEntryType.Error &&entry.Message.Contains(name)) // 判断是否提到目标AppPool{isCrash = true;Log($"[{name}](进程ID:{mProcessID})发现相关崩溃日志,准备尝试自动重启。奔溃日志:" +entry.Message,true);break;}}}catch (Exception ex){Log($"[{name}] 检查WAS事件日志失败:{ex.Message}",false);}// 重启次数限制(防止死循环)string restartKey = $"RestartCount_{name}";int restartCount = RestartDict.ContainsKey(restartKey) ? RestartDict[restartKey] : 0;int restartLimit = 3;if (isCrash){if (restartCount < restartLimit){KillAndRestart(pool);restartCount++;RestartDict[restartKey] = restartCount;File.WriteAllText(RestartCountPath, JsonConvert.SerializeObject(RestartDict, Formatting.Indented));Log($"[{name}] (进程ID:{mProcessID})状态异常,尝试 Kill 并重启。(当前次数:{restartCount})",true);}else{Log($"[{name}](进程ID:{mProcessID}) 已达重启上限({restartLimit}次),不再重启,防止死循环。",true);}}else{Log($"[{name}] (进程ID:{mProcessID})状态为 Stopped,但无崩溃迹象,疑似人工操作,跳过自动重启。", true);}}mCurrentNames.Add(name);if (!name.Contains("DefaultAppPool") && !name.Contains(".NET v4.5 Classic") && !name.Contains(".NET v4.5")){Invoke(new Action(() =>{var existingItem = listView1.Items.Cast<ListViewItem>().FirstOrDefault(i => i.Text == name);if (existingItem != null){existingItem.SubItems[1].Text = mProcessID;existingItem.SubItems[2].Text = statusEn + "(" + statusCn + ")";existingItem.SubItems[3].Text = cpuUsage.ToString("F2");}else{var item = new ListViewItem(new[]{name,mProcessID,statusEn + "(" + statusCn + ")",cpuUsage.ToString("F2")});listView1.Items.Add(item);}}));}}// 移除已不存在的项Invoke(new Action(() =>{for (int i = listView1.Items.Count - 1; i >= 0; i--){string name = listView1.Items[i].Text;if (!mCurrentNames.Contains(name)){listView1.Items.RemoveAt(i);}}}));}}catch (Exception ex){Log("检测IIS应用池出现异常: " + ex.Message,true);}finally{mIsChecking = false;}
}
  • 获取自定义阈值

  private int GetCpuThreshold(){try{if (File.Exists(CpuConfigPath)){string text = File.ReadAllText(CpuConfigPath).Trim();if (int.TryParse(text, out int value))return value;}}catch { }return 70; // 默认值}
  • 获取某个应用程序池的占用率

 private double GetCpuUsageByAppPool(string appPoolName){// 排除三个应用程序池名称if (appPoolName.Contains("DefaultAppPool") ||appPoolName.Contains(".NET v4.5 Classic") ||appPoolName.Contains(".NET v4.5")){//Log($"[{appPoolName}] 属于排除监控列表,跳过 CPU 监测。");return 0;}using (ServerManager manager = new ServerManager()){var pool = manager.ApplicationPools[appPoolName];if (pool == null){Log($"[{appPoolName}]该应用池对象为 null,跳过处理。",false);return 0;}var wp = pool.WorkerProcesses.FirstOrDefault();if (wp == null){Log($"[{appPoolName}]的WorkerProcesses(进程总和)count为0,可能尚未启动,跳过处理。",false);return 0;}try{var process = Process.GetProcessById(wp.ProcessId);// 获取唯一的性能计数器实例名(如 w3wp#1)进程名都是w3wp,所以要取一下IDstring instanceName = GetProcessInstanceName(appPoolName, process.Id);//Log($"[{appPoolName}] 对应进程名称:{process.ProcessName},PID:{process.Id},性能计数器实例名:{instanceName}");var counter = new PerformanceCounter("Process", "% Processor Time", instanceName, true);counter.NextValue(); // 第一次调用为初始化,不可信Thread.Sleep(2000); // 等待间隔至少 1 秒以上float rawValue = counter.NextValue();int cpuCount = Environment.ProcessorCount;float usage = rawValue / cpuCount;//Windows 的 "% Processor Time" 是所有 CPU 核心总和,例如 4 核 CPU,一个进程满载可能是 400 %//所以:如果rawValue = 200(代表该进程用掉了 200 %)//cpuCount = 4 假设cpu核数是4核//usage = 200 / 4 =50//这样才能和设置的阈值(例如80 %)做准确对比。Log($"[{appPoolName}]CPU使用率:{usage:F2}%(原始值:{rawValue:F2}%,核数:{cpuCount})",false);return usage;}catch (Exception ex){Log($"[{appPoolName}] 获取 CPU 使用率失败:{ex.Message}",false);return 0;}}}
  • 获取性能计数器实例名

private string GetProcessInstanceName(string appPoolName,int pid)
{try{PerformanceCounterCategory category = new PerformanceCounterCategory("Process");string[] instances = category.GetInstanceNames();foreach (string instance in instances){using (PerformanceCounter counter = new PerformanceCounter("Process", "ID Process", instance, true)){if ((int)counter.RawValue == pid){return instance;}}}}catch (Exception ex){Log("["+appPoolName+"]未找到 PID 为 "+pid+" 的性能计数器实例名",false);}return "";
}
  • Kill 并重新启动应用池

private void KillAndRestart(ApplicationPool pool)
{string poolName = pool.Name;try{foreach (var wp in pool.WorkerProcesses){try { Process.GetProcessById(wp.ProcessId).Kill(); } catch { Log("kill并重新启动[" + poolName + "] 池时,没有找到"+ wp.ProcessId,true); }}pool.Stop();Thread.Sleep(1000);pool.Start();Log("[" + poolName + "]重启成功!",true);}catch (Exception ex){Log("["+poolName+"]重启失败: " + ex.Message,true);}
}
  • 写入日志到 Log 目录,并显示在文本框中

private void Log(string message,bool mIsWriteTxt)
{try{if (mIsWriteTxt){if (!Directory.Exists(LogDirectory))Directory.CreateDirectory(LogDirectory);string mFileName = DateTime.Now.ToString("yyyyMMdd") + "Log.txt";string mFilePath = Path.Combine(LogDirectory, mFileName);string mLine = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}";using (StreamWriter writer = new StreamWriter(mFilePath, true)){writer.WriteLine(mLine + Environment.NewLine);}}Invoke(new Action(() =>{// 判断当前文本框行数是否超过 200 行int currentLineCount = txtLog.Lines.Length;if (currentLineCount >= 200){txtLog.Clear(); // 超过行数则清空}string mLine = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}";txtLog.AppendText(mLine + Environment.NewLine);}));}catch { }
}

实际运行效果

  • 工具部署在多台服务器的桌面系统上,稳定运行数月。

  • 显著减少了手工处理 IIS 挂死的情况,节省了近 80% 的运维时间

  • 在 CPU 高频波动的高峰期,应用池自动回收机制保证了服务稳定不崩溃。

  • 日志功能也为问题回溯与性能分析提供了有力支持。


    http://www.hkcw.cn/article/arsVZCHBMa.shtml

    相关文章

    网络编程4-epoll

    select底层原理 fd_set底层使用位图标记每个文件标识符有没有被使用&#xff0c;位图在c语言里靠数组实现。 select 流程 在用户态空间里&#xff08;栈、堆、数据段&#xff09;申请一个fd_set将fd_set从用户态拷贝到内核态&#xff08;在后面操作系统轮询会使用到&#xff09…

    SOC-ESP32S3部分:19-ADC模数转换

    飞书文档https://x509p6c8to.feishu.cn/wiki/XycAwmO6Niitdtka1RAcclYfnvf ESP32-S3 集成了两个 12 位 SAR ADC&#xff0c;共支持 20 个模拟通道输入。 SAR ADC 管脚通过 IO MUX 与 GPIO1 ~ GPIO20、RTC_GPIO1 ~ RTC_GPIO20、触摸传感器接口、UART 接口、SPI 接口、以及 USB…

    默克微生物培养基选择指南

    微生物学研究需要能在实验室提供各种不同种类的细菌、酵母或病毒。诸如发酵、蛋白质和疫苗生产的大规模过程需要大量处于生理活性状态的细菌。因此&#xff0c;针对各种应用需要有能提供适当的生化环境并保持微生物所有特征的合适的营养培养基。 任何微生物培养基都应包括营养…

    移动安全Android——解决APP抓包证书无效问题

    问题 通过Burpsuite和ProxyPin进行代理抓包Android APP的时候发现虽然已经正确添加了用户证书&#xff0c;但是还是会出现SSL握手错误&#xff0c;证书无效问题。这是因为Android 7 以上版本APP默认不信任用户证书&#xff0c;只信任系统证书&#xff0c;所以需要将用户证书移动…

    【数据库】数据库恢复技术

    数据库恢复技术 实现恢复的核心是使用冗余&#xff0c;也就是根据冗余数据重建不正确数据。 事务 事务是一个数据库操作序列&#xff0c;是一个不可分割的工作单位&#xff0c;是恢复和并发的基本单位。 在关系数据库中&#xff0c;一个事务是一条或多条SQL语句&#xff0c…

    【学习笔记】深度学习-梯度概念

    一、定义 梯度向量不仅表示函数变化的速度&#xff0c;还表示函数增长最快的方向 二、【问】为什么说它表示方向&#xff1f; 三、【问】那在深度学习梯度下降的时候&#xff0c;还要判断梯度是正是负来更新参数吗&#xff1f; 假设某个参数是 w&#xff0c;损失函数对它的…

    【ROS2实体机械臂驱动】rokae xCoreSDK Python测试使用

    【ROS2实体机械臂驱动】rokae xCoreSDK Python测试使用 文章目录 前言正文配置环境下载源码配置环境变量测试运行修改点说明实际运行情况 参考 前言 本文用来记录 xCoreSDK-Python的调用使用1。 正文 配置环境 配置开发环境&#xff0c;这里使用conda做python环境管理&…

    深入浅出网络分析与故障检测工具

    目录 网络故障检测工具&#xff1a;别只靠“Ping 不通” 实战组合拳&#xff1a;分析 检测 问题闭环 四、选择工具的几个建议 五、总结&#xff1a;工具是手段&#xff0c;思维才是核心 在如今这个“数据就是生命线”的时代&#xff0c;网络的稳定性和性能直接决定着企业…

    使用Haproxy搭建Web群集

    目录 1&#xff0c;Haproxy简介 1&#xff0c;核心功能与特点 二&#xff0c;搭建haproxy群集 1&#xff0c;准备工作 2&#xff0c;修改haproxy的配置文件 3&#xff0c;准备网站 4&#xff0c;配置日志 5&#xff0c;验证 1&#xff0c;Haproxy简介 HAProxy 是一款高…

    Elasticsearch的写入流程介绍

    Elasticsearch 的写入流程是一个涉及 分布式协调、分片路由、数据同步和副本更新 的复杂过程,其设计目标是确保数据一致性、可靠性和高性能。以下是写入流程的详细解析: 一、写入流程总览 二、详细步骤解析 1. 客户端请求路由 请求入口:客户端(如 Java 客户端、REST API)…

    记录一次apisix上cros配置跨域失败的问题

    安全要求不允许跨域请求&#xff0c;但是业务侧由于涉及多个域名&#xff0c;并且需要共享cookie&#xff0c;所以需要配置跨域。 在apisix上配置了cors如下。 结果安全漏扫还是识别到了跨域请求的漏洞。 调试了cors.lua的插件脚本&#xff0c;发现apisix上是如果不在allowOri…

    VSCode无法转到定义python源码(ctrl加单击不跳转)

    已经尝试的方案&#xff1a; 1.确保对应python环境正确激活 在 VSCode 中&#xff0c;打开命令面板&#xff08;CtrlShiftP&#xff09;&#xff0c;输入并选择 Python: Select Interpreter&#xff0c;然后从列表中选择正确的 Python 解释器。 2.重新卸载Python插件再重新安装…

    会议室钥匙总丢失?换预约功能的智能门锁更安全

    在企业日常运营中&#xff0c;会议室作为重要的沟通与协作场所&#xff0c;其管理效率与安全性直接影响着企业的运作顺畅度。然而&#xff0c;传统会议室管理方式中钥匙丢失、管理不便等问题频发&#xff0c;给企业带来了不少困扰。近期&#xff0c;某企业引入了启辰智慧预约系…

    漫画Android:事件分发的过程是怎样的?

    当用户触摸屏幕时&#xff0c;硬件层会捕获触摸信号&#xff0c;并将其转化为内核事件。 Android系统会通过InputManagerService和WindowManagerService等服务将这些事件包装成MotionEvent对象&#xff0c;并将其传递给Activity的dispatchTouchEvent()方法中&#xff0c;Activi…

    【算法提升】分组 day_tow

    1.分组 1.1 解析 个人认为这题最难的点在于如何想到使用二分的算法来解题。 正向求解&#xff1a;就是去看每一组中需要分多少个人&#xff0c;但是这样求解代码我根本写不出来。 所以根据正难则反的思想&#xff0c;我们可以从最终结果去倒推。 枚举最终的分配结果中&#xff…

    【笔记】Suna 部署之 Supabase 数据库 schema 暴露操作

    #工作记录 一、前置信息 在 Suna 部署过程中&#xff0c;Supabase 数据库设置已完成&#xff08;✅ Supabase database setup completed &#xff09;&#xff0c;但需要手动在 Supabase 平台暴露basejump模式&#xff08;schema&#xff09;。 Suna 部署过程中&#xff0c;S…

    【Linux 学习计划】-- 进程状态 | 进程运行、阻塞和挂起的本质 | 并行、并发与进程切换 | 进程优先级

    目录 进程状态 五状态进程模型 运行、就绪状态的本质 阻塞状态的本质 挂起状态 并行与并发 进程切换 进程优先级 结语 进程状态 进程状态的本质是什么&#xff1f; 首先我们知道&#xff0c;在操作系统中&#xff0c;进程是需要被管理起来的&#xff0c;具体则是用一…

    自证式推理训练:大模型告别第三方打分的新纪元

    1. 传统验证体系的困境与技术跃迁的必然性 1.1 传统验证器的局限性 现有强化学习框架依赖显式验证器对答案进行二值化判定&#xff0c;这种模式在数学、代码等可验证领域表现优异。某厂内部数据显示&#xff0c;传统R1-Zero方法在代码生成任务中准确率达92%&#xff0c;但切换…

    《操作系统真相还原》——加载器

    显存 将上一章的中断输出&#xff0c;变为显存输出 加载器 使用mbr引导程序从磁盘中加载loader程序。 MBR %include "boot.inc" SECTION MBR vstart0x7c00 mov ax,cs mov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00mov ax,0xb800mov gs,ax;cl…

    Spring Boot 应用中实现配置文件敏感信息加密解密方案

    Spring Boot 应用中实现配置文件敏感信息加密解密方案 背景与挑战 &#x1f6a9;一、设计目标 &#x1f3af;二、整体启动流程 &#x1f504;三、方案实现详解 ⚙️3.1 配置解密入口&#xff1a;EnvironmentPostProcessor3.2 通用解密工具类&#xff1a;EncryptionTool 四、快速…