将PowerShell安全事件监控脚本转化为常驻系统进程并优化资源占用
作为PowerShell新手,你遇到的这个"脚本关了ISE就停"的问题太常见了,我给你两个靠谱的解决方案,还有脚本优化的小建议,帮你把脚本改成开机自启、后台稳跑的服务。
一、原生方案:不用额外工具,直接注册成Windows服务
Windows自带的工具就能把你的脚本做成系统服务,开机自动启动,后台默默运行,完全不用依赖控制台窗口。
操作步骤:
- 先把你的脚本存成固定路径的
.ps1文件,比如E:\apps\me\SecurityEventMonitor.ps1,注意所有路径都用绝对路径,别用相对路径或者环境变量,避免服务运行时找不到文件。 - 写个简单的批处理文件(比如
StartMonitor.bat),用来调用PowerShell运行脚本:
@echo off powershell.exe -NoProfile -ExecutionPolicy Bypass -File "E:\apps\me\SecurityEventMonitor.ps1"
解释下参数:-NoProfile跳过加载用户配置文件(服务用的是系统账户,没用户配置),-ExecutionPolicy Bypass确保脚本能跑(如果服务器的PowerShell执行策略设得比较严的话)。
3. 用管理员权限打开PowerShell,注册服务:
New-Service -Name "SecurityEventMonitor" -BinaryPathName "C:\path\to\StartMonitor.bat" -DisplayName "安全事件日志监控服务" -StartupType Automatic
要是习惯用sc命令也可以:
sc create SecurityEventMonitor binPath= "C:\path\to\StartMonitor.bat" start= auto displayname= "安全事件日志监控服务"
⚠️ 注意:binPath=和start=后面必须加空格,这是sc命令的奇葩语法要求,别漏了!
4. 启动服务:
Start-Service SecurityEventMonitor
之后你可以在「服务」管理器里看到它的状态,确认是不是在运行。
踩坑提醒:
- 服务默认用Local System账户运行,如果你的脚本里
Get-ADUser需要访问AD的权限,得去服务属性里把登录账户改成有AD读取权限的域账户。 - 脚本里写CSV的路径(
E:\apps\me\testScript.csv)要给服务账户加写入权限,不然会报错写不进去。 - 要是想调试脚本,把批处理里的命令改成这样,把输出日志存下来:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "E:\apps\me\SecurityEventMonitor.ps1" >> "E:\apps\me\monitor_debug.log" 2>&1
二、NSSM:非原生但更省心的工具,绝对可靠
你提到的NSSM(Non-Sucking Service Manager)真的是个宝藏工具,完全不像它名字里的"Non-Sucking"那么调侃,它是开源的,已经维护很多年了,生产环境用的人特别多,把普通程序包装成服务比原生方案稳定太多——比如脚本崩溃了它能自动重启,还能配置各种恢复策略。
而且它根本不用"安装",解压出来一个nssm.exe就能用,完全符合你"尽量不用额外工具"的需求,顶多就是复制个文件到服务器而已。
使用步骤:
- 下载对应你服务器位数的NSSM版本,解压后把
nssm.exe放到C:\Windows\System32(这样全局都能调用),或者直接记住它的路径。 - 管理员打开命令提示符/PowerShell,运行:
nssm install SecurityEventMonitor
- 弹出图形配置窗口,照着填:
- Path:选
powershell.exe的路径,一般是C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe - Arguments:填
-NoProfile -ExecutionPolicy Bypass -File "E:\apps\me\SecurityEventMonitor.ps1" - Working Directory:选脚本所在的文件夹,比如
E:\apps\me - 切到「Log On」标签,要是需要AD权限,就设置成有对应权限的域账户
- 切到「Recovery」标签,设置失败后的重启策略(比如第一次失败重启,第二次也重启,第三次重启服务,这样容错性拉满)
- Path:选
- 点「Install Service」完成安装,然后启动服务:
nssm start SecurityEventMonitor
三、给你的脚本提个关键优化建议
我看你的脚本里有个$lineNo变量,但从头到尾没定义过!这肯定会导致提取用户名失败的,得赶紧改。而且用分割事件消息的方式提取属性太不靠谱了——如果服务器是中文系统还好,要是换成英文系统,事件消息的行号完全不一样,脚本直接就废了。
推荐用解析事件XML的方式提取属性,不管系统语言是什么都能用:
# 替换你原来分割Message的代码,用XML解析事件属性 $eventXml = [xml]$Evlog.ToXml() if ($eventID -eq 4724) { $SubjectUserName1 = $eventXml.Event.EventData.Data | Where-Object { $_.Name -eq "SubjectUserName" } | Select-Object -ExpandProperty "#text" $TargetUserName1 = $eventXml.Event.EventData.Data | Where-Object { $_.Name -eq "TargetUserName" } | Select-Object -ExpandProperty "#text" } if ($eventID -eq 4723) { $SubjectUserName1 = $eventXml.Event.EventData.Data | Where-Object { $_.Name -eq "SubjectUserName" } | Select-Object -ExpandProperty "#text" $TargetUserName1 = $eventXml.Event.EventData.Data | Where-Object { $_.Name -eq "TargetUserName" } | Select-Object -ExpandProperty "#text" }
另外,脚本里的Get-Event | Remove-Event也可以删掉,服务环境里事件订阅的处理是独立的,这个命令可能会误删其他系统事件,没必要留着。
最后总结
- 要是不想碰任何第三方工具,原生服务方案完全够用,就是要注意权限和路径的配置。
- 追求稳定省心的话,NSSM绝对是首选,配置灵活,还能自动恢复故障,生产环境放心用。
- 赶紧把脚本里的
$lineNo问题解决,换成XML解析的方式,不然脚本跑起来也抓不到正确的用户名。
内容的提问来源于stack exchange,提问作者sting_array




