用 Python 远程控制 Windows 服务器,太好用了!
以前我对“远程控制 Windows 服务器”这件事有点刻板印象,总觉得要么靠人肉点远程桌面,要么写一堆 PowerShell 脚本丢到机器上跑。后来真把 Python 接进来之后,很多运维动作就顺手多了。
尤其是那种重复性的活:批量查服务状态、重启 IIS、拉日志、执行补丁脚本、检查磁盘空间、远程创建目录。以前一台一台登服务器,做完一轮人都麻了。现在 Python 连上去,几台、十几台机器一起跑,省事很多。
我这边平时用得比较顺的是 WinRM + Python 这条路。Windows 原生就支持 WinRM,Python 侧直接用 pywinrm,上手不算重,也比较适合做一些后台自动化。
先装包:
pip install pywinrm
服务端先把 WinRM 开起来。测试环境里我一般先这样配,先把链路打通再谈收敛权限:
winrm quickconfig
Enable-PSRemoting -Force
Set-Item WSMan:\localhost\Service\Auth\Basic -Value $true
Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value $true
生产环境别这么裸跑,后面最好切到 HTTPS,认证方式也尽量别偷懒。这个后面再说,先看 Python 这边怎么用。
最基础的一段,先远程执行一条命令:
import winrm
session = winrm.Session(
'192.168.10.23:5985',
auth=('administrator', 'YourPassword123')
)
r = session.run_cmd('ipconfig', ['/all'])
print(r.status_code)
print(r.std_out.decode('gbk', errors='ignore'))
print(r.std_err.decode('gbk', errors='ignore'))
这里有个小细节,Windows 返回内容很多时候是 gbk,直接按 utf-8 解码,中文日志经常是乱码。我第一次跑的时候,结果是拿到了输出,但满屏问号,后面才反应过来不是命令有问题,是解码方式不对。
如果你要执行稍微复杂一点的逻辑,直接跑 PowerShell 会更顺手。比如查某个服务状态:
import winrm
ps = """
$svc = Get-Service -Name W32Time
if ($svc.Status -eq 'Running') {
Write-Output "W32Time is running"
} else {
Write-Output "W32Time is $($svc.Status)"
}
"""
session = winrm.Session('192.168.10.23:5985', auth=('administrator', 'YourPassword123'))
r = session.run_ps(ps)
print(r.std_out.decode('gbk', errors='ignore'))
这个方式比你用 run_cmd 拼一长串命令舒服不少。尤其是涉及条件判断、变量、异常处理时,PowerShell 脚本块可读性会好很多。
我自己在线上最常见的一个场景,是批量检查多台 Windows 服务器的磁盘空间。这个动作非常适合 Python 来收口:
import winrm
hosts = [
'192.168.10.21:5985',
'192.168.10.22:5985',
'192.168.10.23:5985',
]
ps = """
Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" |
Select-Object DeviceID,
@{Name="FreeGB";Expression={[math]::Round($_.FreeSpace / 1GB, 2)}},
@{Name="TotalGB";Expression={[math]::Round($_.Size / 1GB, 2)}}
"""
for host in hosts:
session = winrm.Session(host, auth=('administrator', 'YourPassword123'))
r = session.run_ps(ps)
print(f'===== {host} =====')
print(r.std_out.decode('gbk', errors='ignore'))
这类代码不复杂,但很实用。比如你一早收到告警,说某几个应用节点日志盘快满了,就不用一个个 RDP 上去看。脚本一跑,哪台机器 C 盘、D 盘还剩多少,基本一眼就能看出来。
再比如重启应用池或者 IIS,这个也挺常见。有人喜欢远程桌面进去点,我现在更愿意直接发命令:
import winrm
ps = """
Import-Module WebAdministration
Restart-WebAppPool -Name "DefaultAppPool"
Write-Output "AppPool restarted"
"""
session = winrm.Session('192.168.10.23:5985', auth=('administrator', 'YourPassword123'))
r = session.run_ps(ps)
print(r.status_code)
print(r.std_out.decode('gbk', errors='ignore'))
如果你要做发布前检查,也可以顺手把几个动作串起来。比如先停服务,再备份目录,再覆盖文件,最后启动服务。真正好用的点,不是“Python 能连 Windows”,而是它能把这些原本零散的动作串成一个完整流程。
我之前写过一个很小的发布辅助脚本,核心逻辑大概就是这样:
import winrm
session = winrm.Session('192.168.10.25:5985', auth=('administrator', 'YourPassword123'))
steps = [
'Stop-Service -Name "ReportService"',
'if (!(Test-Path "D:\\backup")) { New-Item -Path "D:\\backup" -ItemType Directory }',
'Copy-Item "D:\\app\\report\\*" "D:\\backup\\" -Recurse -Force',
'Copy-Item "\\\\10.0.0.8\\pkg\\report\\*" "D:\\app\\report\\" -Recurse -Force',
'Start-Service -Name "ReportService"',
]
for step in steps:
r = session.run_ps(step)
print("CMD:", step)
print("CODE:", r.status_code)
print(r.std_out.decode('gbk', errors='ignore'))
err = r.std_err.decode('gbk', errors='ignore')
if r.status_code != 0:
print("ERR:", err)
break
这种脚本的价值就在于:动作固定、步骤固定、机器固定的时候,别再手工重复了。手工操作最容易出错的不是不会点,是点到第七八步开始疲劳,然后漏掉一步。
当然,远程控制 Windows 服务器也不是把代码写出来就完事了,几个坑还是得提前避一下。
一个是 认证和加密。开发测试环境用 HTTP 5985 跑 Basic 认证,图省事没问题;但到了正式环境,最好还是上 HTTPS 5986。要不然账号密码和执行内容都在链路上裸奔,看着总归不踏实。
另一个是 权限边界。别一上来就拿域管理员或者本地管理员跑所有自动化。很多操作其实给到服务级别权限就够了。脚本方便归方便,权限给大了,出事也放大得快。
还有一个是 幂等性。比如“创建目录”“部署文件”“启动服务”这类动作,最好都写成重复执行也不容易出问题的样子。否则第一次成功,第二次因为目录已存在、服务已启动又报错,自动化脚本跑起来就不稳。
最后我自己的感觉是,Python 做这类事,最舒服的地方不是替代 PowerShell,而是把 PowerShell、批量控制、结果汇总、失败重试都串起来。你完全可以把它当成一个调度层:下面还是 Windows 原生命令,上面由 Python 负责组织流程。
真到机器一多,这套东西就挺顺手了。尤其是那些每天都有人在做、但做法又很机械的动作,交给 Python 去跑,省下来的不只是时间,主要是少出低级错。