代码改变世界

自动化实践(7.25):把 PsTools 接入 PowerShell / 批处理 / Ansible - 详解

2025-12-05 12:36  tlnshuju  阅读(0)  评论(0)    收藏  举报

自动化实践(7.25):把 PsTools 接入 PowerShell / 批处理 / Ansible

目标:把 PsExec / PsInfo / PsService / PsLogList 等常用 PsTools,系统化封装进 PowerShell 脚本、批处理(.bat/.cmd)与 Ansible 流水线,实现可复用、可审计、可回滚的批量运维与应急流程。


1)前置与安全基线

  • 网络:目标机放行 445/TCP(SMB)135/TCP(RPC)动态 RPC 端口
  • 权限:使用域受控账号或最小权限服务账号;工作组注意 UAC 远程限制。
  • 共享ADMIN$ / C$ 未禁用。
  • 签名/EDR:将 PsTools 目录与 PSEXESVC 行为加入白名单。
  • 时间/DNS:NTP 同步、DNS 正常解析(Kerberos/NTLM 影响大)。
  • 脚本规范:全流程 日志留痕 + 幂等检查 + 失败回滚

2)PowerShell 集成:模块化、并发与结构化日志

2.1 目录与凭据约定

C:\Ops\PsTools\       # 放置 PsTools
C:\Ops\Scripts\       # 自建脚本目录
C:\Ops\Logs\          # 结构化日志输出
C:\Ops\hosts.txt      # 主机清单(逐行一个主机)

凭据建议:用 Get-Credential 交互获取,或安全地从 Windows 凭据管理器/密管(Vault)读取。

# C:\Ops\Scripts\New-PsExecSession.ps1
param(
[Parameter(Mandatory)] [string]$Computer,
[Parameter()] [pscredential]$Credential,
[Parameter()] [string]$Command = 'hostname',
[switch]$System, [switch]$High, [int]$Timeout = 20,
[string]$WorkDir, [string[]]$CpuAffinity
)
$psi = @{
FilePath   = 'psexec.exe'
ArgumentList = @("\\$Computer")
NoNewWindow = $true
Wait       = $true
}
if ($Credential) { $psi.ArgumentList += @('-u', $Credential.UserName, '-p', $Credential.GetNetworkCredential().Password) }
if ($System)     { $psi.ArgumentList += '-s' }
if ($High)       { $psi.ArgumentList += '-h' }
if ($Timeout)    { $psi.ArgumentList += @('-n', $Timeout) }
if ($WorkDir)    { $psi.ArgumentList += @('-w', $WorkDir) }
if ($CpuAffinity){ $psi.ArgumentList += @('-a', ($CpuAffinity -join ',')) }
$psi.ArgumentList += @('-accepteula','-nobanner')
$psi.ArgumentList += $Command
try{
$sw = [Diagnostics.Stopwatch]::StartNew()
$p  = Start-Process @psi -PassThru -RedirectStandardOutput 'stdout.tmp' -RedirectStandardError 'stderr.tmp'
$code = $p.ExitCode
$out  = Get-Content .\stdout.tmp -Raw
$err  = Get-Content .\stderr.tmp -Raw
Remove-Item .\stdout.tmp, .\stderr.tmp -ErrorAction SilentlyContinue
[pscustomobject]@{
Computer = $Computer; Command = $Command; ExitCode = $code
DurationMs = $sw.ElapsedMilliseconds; StdOut = $out; StdErr = $err; Time = (Get-Date)
}
}
catch{
[pscustomobject]@{
Computer = $Computer; Command = $Command; ExitCode = -1
DurationMs = 0; StdOut = ''; StdErr = $_ | Out-String; Time = (Get-Date)
}
}

2.2 并发执行(PowerShell 7 推荐)

# C:\Ops\Scripts\Invoke-PsToolsBatch.ps1
$hosts = Get-Content 'C:\Ops\hosts.txt' | ? {$_ -and $_ -notmatch '^\s*#'}
$cred  = Get-Credential -Message '输入用于 PsExec 的域凭据(最小权限)'
$results = $hosts | ForEach-Object -Parallel {
Import-Module -Name 'C:\Ops\Scripts\New-PsExecSession.ps1' -Force
# 示例:查询磁盘信息(可以换成任意命令,如:ipconfig /all)
. 'C:\Ops\Scripts\New-PsExecSession.ps1' -Computer $_ -Credential $using:cred -High `
-Command 'cmd /c wmic logicaldisk get caption,freespace,size /format:table'
} -ThrottleLimit 20
# 结构化日志(JsonLines)
$log = 'C:\Ops\Logs\psexec-' + (Get-Date -UFormat %Y%m%d-%H%M%S) + '.jsonl'
$results | ForEach-Object { $_ | ConvertTo-Json -Compress | Out-File -FilePath $log -Append -Encoding utf8 }
# 失败告警粗筛
$fail = $results | ? { $_.ExitCode -ne 0 }
if($fail){ Write-Warning "失败 ${($fail.Count)} 台,详情:$log" } else { Write-Host '全部成功' -ForegroundColor Green }

2.3 封装常用动作(示例库)

  • 服务健康三连(查询→依赖→重启)
function Invoke-ServiceHealth {
param([string]$Computer, [string]$Service, [pscredential]$Credential)
. 'C:\Ops\Scripts\New-PsExecSession.ps1' -Computer $Computer -Credential $Credential -High -Command "psservice query $Service"
. 'C:\Ops\Scripts\New-PsExecSession.ps1' -Computer $Computer -Credential $Credential -High -Command "psservice depend $Service"
. 'C:\Ops\Scripts\New-PsExecSession.ps1' -Computer $Computer -Credential $Credential -High -Command "psservice restart $Service"
}
  • 冻结→抓转储→恢复(针对 CPU 飙高的 w3wp)
function Invoke-FreezeDumpResume {
param([string]$Computer,[pscredential]$Credential,[string]$Proc='w3wp',[string]$Dump='C:\Dumps\w3wp.dmp')
. 'C:\Ops\Scripts\New-PsExecSession.ps1' -Computer $Computer -Credential $Credential -High -Command "pssuspend $Proc"
. 'C:\Ops\Scripts\New-PsExecSession.ps1' -Computer $Computer -Credential $Credential -High -Command "procdump -accepteula -ma $Proc $Dump"
. 'C:\Ops\Scripts\New-PsExecSession.ps1' -Computer $Computer -Credential $Credential -High -Command "pssuspend -r $Proc"
}
  • 拉事件日志(近 2 天关键错误)
. 'C:\Ops\Scripts\New-PsExecSession.ps1' -Computer $Computer -Credential $cred -Command `
'psloglist -s -d 2 -i 41,1001 System'

幂等性建议

  • 起服务前先 query 判断状态;
  • 改配置前先 get 对比;
  • 大规模批量先试点(1~3 台)再并发放量。

3)批处理(.bat/.cmd):轻依赖、可落地

3.1 基础模板(带日志与退出码)

@echo off
setlocal enabledelayedexpansion
set P= C:\Ops\PsTools
set H= C:\Ops\hosts.txt
set LOG=C:\Ops\Logs\batch-%date:~0,10%_%time:~0,8%.log
for /f "usebackq delims=" %%h in ("%H%") do (
  if not "%%h"=="" (
    echo ===== %%h ===== >> "%LOG%"
    "%P%\psexec.exe" \\%%h -accepteula -nobanner ipconfig /all >> "%LOG%" 2>&1
    set EC=!errorlevel!
    echo ExitCode=!EC! >> "%LOG%"
    if not "!EC!"=="0" echo [WARN] %%h 失败(!EC!)
  )
)
echo 完成:%LOG%
endlocal

3.2 并发小技巧(慎用)

:: 同时对 3 台发起
start "" cmd /c "%P%\psexec.exe \\PC-001 hostname & echo EC=!errorlevel! >> %LOG%"
start "" cmd /c "%P%\psexec.exe \\PC-002 hostname & echo EC=!errorlevel! >> %LOG%"
start "" cmd /c "%P%\psexec.exe \\PC-003 hostname & echo EC=!errorlevel! >> %LOG%"

注意:批处理缺少原生结构化日志与健壮错误处理,建议用 PowerShell 主导,批处理当“引导器”。


4)Ansible 集成:把 PsTools 纳入编排

思路:Ansible 控制端为 Windows(或 WSL/代理到 Windows),PsTools 安装在控制端。Playbook 通过 delegate_to: localhost 在控制端循环调用 PsTools 远程连接各 Windows 主机。

4.1 示例清单

inventory.ini

[win]
PC-001
PC-002
SRV-APP

group_vars/win.yml

psexec_path: 'C:\\Ops\\PsTools\\psexec.exe'
ops_user: 'CORP\\opsuser'
ops_pass: '{{ vault_ops_pass }}'   # 建议用 Ansible Vault

playbook:批量查询磁盘信息

---
- name: PsTools orchestration from Windows control node
hosts: win
gather_facts: no
tasks:
- name: Run PsExec disk query
ansible.windows.win_command: >
"{{ psexec_path }}"
"\\{{ inventory_hostname }}"
-u {{ ops_user }} -p {{ ops_pass }}
-accepteula -nobanner
cmd /c wmic logicaldisk get caption,freespace,size /format:table
register: out
delegate_to: localhost
- name: Save raw output per host
ansible.builtin.copy:
dest: "C:/Ops/Logs/{{ inventory_hostname }}-disk.txt"
content: "{{ out.stdout }}"
delegate_to: localhost

更推荐:Ansible 访问 Windows 目标,首选 WinRM + ansible.windows.* 原生模块(如 win_servicewin_commandwin_eventlogwin_package)。
在必须跨越网络策略/EDR 限制时,再用 PsTools 作为“后备通道”。


5)流水线与计划任务:把脚本放进 CI/CD

5.1 Windows 计划任务(每日清点)

$act = New-ScheduledTaskAction -Execute 'pwsh.exe' -Argument '-File C:\Ops\Scripts\Invoke-PsToolsBatch.ps1'
$trg = New-ScheduledTaskTrigger -Daily -At 03:00
Register-ScheduledTask -Action $act -Trigger $trg -TaskName 'Nightly-PsTools-Inventory' -User 'CORP\svc_ops' -Password (Read-Host -AsSecureString)

5.2 Jenkins/GitLab CI(示意)

  • Jenkins:Windows Node 执行 pwsh -File C:\Ops\Scripts\Invoke-PsToolsBatch.ps1,归档 C:\Ops\Logs\*.jsonl
  • GitLab CI:Windows Runner 执行同上,产物落盘归档。

6)幂等与回滚策略

  • 服务/配置:变更前 psservice query 快照 → 变更 → 失败时按快照回退。
  • 关机/重启psloglist 抽取关键事件留痕;计划窗口+沟通提示。
  • 杀进程:优先 pssuspend(冻结)+ 取证(procdump)+ 再 pskill
  • 大批量:分批灰度(10% → 30% → 100%),提高成功率与可控性。

7)故障快修清单(执行层面)

现象快速定位处置
Access is denied权限/UAC/ADMIN$管理员凭据;启用 ADMIN$;必要时工作组放宽 UAC 远程限制
RPC/SMB 不通Test-NetConnection -ComputerName HOST -Port 445/135放行端口、防火墙策略核对
Logon failure凭据错误/锁定校验域名 + 口令;解锁策略
PSEXESVC 启动失败EDR/白名单协同安全放行 PsExec 行为
回显阻塞交互/超时-d 后台、或 -i 指定会话、-n 超时

8)推荐仓库结构(可直接落地)

Ops-Automation/
├─ PsTools/                      # 官方二进制
├─ Scripts/
│  ├─ New-PsExecSession.ps1      # 核心封装
│  ├─ Invoke-PsToolsBatch.ps1     # 并发批量执行
│  ├─ Recipes/                    # 业务动作库(服务健康、转储、日志收集…)
├─ Logs/                          # JsonLines/CSV/原始输出
├─ hosts.txt                      # 资产清单
└─ README.md                      # 使用说明、安全提示、回滚流程

结语

把 PsTools 接入 PowerShell/批处理/Ansible 后,你就拥有了一个 轻依赖、跨版本、可编排 的 Windows 批量运维“工具台”。建议从 PowerShell 封装 + 并发执行 + 结构化日志 起步,逐步沉淀“动作库”(服务治理、事件采集、取证流程),再上 CI/计划任务,实现 标准化、自动化与可审计 的闭环。

下一篇(7.26):安全与合规——最小权限、日志审计与取证要点,保证“能用更要安全可查”。