前言
在日常运维中,Windows 主机上往往存在大量的 bat、ps1 脚本,每台机器跑的脚本还不一样,有些甚至需要多台机器联动。手动维护既麻烦,又容易出错。
我的场景里,Windows 系统非常关键,不允许开发人员或普通用户随意访问,因此需要一个安全、统一的方式来管理这些脚本。
最开始考虑的方案是 Jenkins 或 Ansible:
- Jenkins 的优势在于能直观看到节点是否在线,但权限粒度不足:用户可以新建任意任务并选择指定节点执行,风险太大(比如误操作删除文件)。 目前开发同事工作需要登录 Jenkins 并创建 Job。
- Ansible 没有内置的节点在线监控,且 Windows 作为节点无需额外安装 agent 和开启 SSH 服务器 ,只要启用系统自带的 WinRM 功能即可。 Ansible 目前完全由 IT 管理,不用担心权限泄露问题。
综合考虑后,还是选择了 Ansible 来统一管理 Windows 脚本。下面记录下从原理到完整落地的全过程。
一、工作原理
Windows 作为 Ansible 节点的核心机制,可以用一句话概括:
控制端发送 PowerShell/命令 → Windows 执行 → 返回 JSON 结果 → 控制端解析
具体分为几个部分:
-
三要素
- 控制节点(Linux 上的 Ansible)
- 被管理节点(Windows,不需要安装 Ansible)
- 通信协议(WinRM,默认端口 5985/5986)
-
连接与认证
- 控制端通过
pywinrm
库和 Windows WinRM 服务通信 - 常见认证:basic、NTLM、Kerberos
- 内网环境可以用 basic+HTTP,生产推荐 Kerberos/HTTPS
- 控制端通过
-
模块执行流程
- 控制端选择目标主机
- 建立 WinRM 会话
- 传输 PowerShell 模块/命令到 Windows
- Windows 执行并产出 JSON 结果
- 结果返回控制端解析
-
文件与脚本管理
win_copy
:复制文件win_command
/win_shell
:执行 bat/命令win_powershell
:执行 PowerShell 脚本
-
幂等性
- 尽量使用模块(如
win_feature
、win_package
),而不是裸命令 - 自写脚本需处理"检查-执行"逻辑
- 尽量使用模块(如
-
权限
- 通常用管理员账号或
become_method: runas
- 通常用管理员账号或
-
离线环境
- Windows 开启 WinRM 不需要联网
- 控制端 Linux 如需安装
pywinrm
,可提前下载 wheel 包离线安装
二、Ansible 版本要求
-
2.8 及以上版本原生支持 Windows 节点
-
推荐使用最新稳定版,兼容性更好
-
查看版本:
bashansible --version
-
检查支持的模块:
bashansible-doc -l | grep win
如果版本过旧,可以通过 pip install --upgrade ansible
升级(离线环境提前下载好包)。
三、环境部署
这里给一个完整的部署流程。
Ansible 部署(Docker)
- 1、Ansible 官方 镜像不更新了
- 2、用 Python 镜像部署 Ansible 更加轻量级,更方便自定义
所以下面使用 Python 镜像进行 Ansible 部署。
Ansible 容器镜像
bash
docker pull python:3.9.23-trixie
拉取 python:3.9.23-trixie 容器镜像
编写 Dockerfile 并构建镜像
bash
vim Dockerfile
FROM python:3.9.23-trixie
ARG http_proxy="http://IP:PORT"
ARG https_proxy="http://IP:PORT" # 代理,用不上可以不加
RUN apt update && apt install -y sshpass git
RUN pip install ansible pywinrm
docker build -t python:3.9.23-trixie-ansible .
测试
bash
docker run --rm python:3.9.23-trixie /bin/bash -c "ansible --version && ansible-doc -l | grep win"
显示 Ansible 版本号和 win 支持模块即容器镜像完成构建。
启动 Ansible
bash
docker run -it --name ansible -v /root/ansible/ansible:/etc/ansible python:3.9.23-trixie /bin/bash
把本地 /root/ansible/ansible 目录映射到容器的 /etc/ansible 目录
不用 -p 映射端口出来,Ansible 能访问 Win 节点端口就行,Win 节点不用访问 Ansible。
三、Windows 节点配置
为方便测试,后续连接 Wiundows 均使用以下账号密码:
- 账号:lian
- 密码:lian
且 Windows 的 IP 为 192.168.62.148
开启 WinRM
- Ansible 官方在 Github 上有个开启 WinRM 脚本的 Powershell 脚本
https://github.com/ansible/ansible-documentation/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1
但官方不建议使用这个脚本(安全问题),建议最好自己敲 PowerShell 命令去开启服务,如果非要用的话建议删除并只保留自己需要的命令。
感兴趣的可以试一下上面 ps1 脚本,开启 WinRM 总共就两条命令,这里我就手敲执行好了。
管理员 PowerShell 执行:
powershell
# 启用 WinRM(管理员)
winrm quickconfig -q
# 允许 Basic 认证 & 明文传输(建议仅限内网/测试用)
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
# 执行这一句的时候会要求网络类型改为'域'或'专用'(默认'公共')
# 在'网络设置'→'以太网'→'网络'→点击勾选'专用',再执行命令即可生效
# 放行防火墙
New-NetFirewallRule -Name "WinRM" -DisplayName "WinRM" -Protocol TCP -LocalPort 5985 -Action Allow
# 授权用户可通过 WinRM 远程执行命令(Remote Management Users 是 Windows 用户组)
net localgroup "Remote Management Users" ansible /add
# 检查监听器
winrm e winrm/config/listener
四、配置 Ansible Inventory
在 Ansible 的 /etc/ansible/hosts
内写入:
ini
[windows]
192.168.62.148
[windows:vars]
ansible_user=lian
ansible_password=lian
ansible_connection=winrm
ansible_winrm_transport=basic
ansible_port=5985
⚠ 注意:如果用 Docker 容器跑的 Ansible,
192.168.62.148
必须能从宿主机/容器访问。
五、测试连通性
在 Ansible 容器里执行:
bash
ansible windows -m win_ping
成功输出:
json
win01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
六、执行脚本示例
把 Windows 上的脚本都放到 Ansible 控制端 ,然后在执行时传输到 Windows 节点再运行,这样 更好管理 脚本。
先准备个测试用的 BAT 批处理脚本(test.bat):
bat
@echo off
chcp 65001 >nul
echo ================================
echo 当前路径信息
echo ================================
echo 当前路径是:%cd%
echo.
echo 当前目录下的文件和文件夹:
dir
echo.
echo ================================
echo 当前时间和时区信息
echo ================================
echo 当前日期和时间:%date% %time%
echo.
echo 当前时区:
where tzutil >nul 2>&1 && tzutil /g
echo.
pause
PowerShell 脚本(test.ps1):
powershell
# 获取当前时间
$time = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# 获取计算机名
$computer = $env:COMPUTERNAME
# 获取当前用户名
$user = $env:USERNAME
# 输出结果
Write-Output "当前时间: $time"
Write-Output "计算机名: $computer"
Write-Output "当前用户名: $user"
这两个脚本都放到
Ansible
的 /etc/ansible/file 目录下。
在 Ansible 上调用 Windows bat 脚本,有三种解决方法:
方法 1:win_copy
+ 执行
先把脚本从控制节点复制到 Windows,再执行。
bash
vim test1.yml
yaml
- name: Run scripts on Windows
hosts: windows
tasks:
- name: Copy bat file to Windows
ansible.windows.win_copy:
src: /etc/ansible/file/test.bat
dest: C:\Users\lian\Desktop\script\test.bat
- name: Run bat file
ansible.windows.win_command: C:\Users\lian\Desktop\script\test.bat
- name: Copy ps1 script to Windows
ansible.windows.win_copy:
src: /etc/ansible/file/test.ps1
dest: C:\Users\lian\Desktop\script\test.ps1
- name: Run ps1 file
ansible.windows.win_powershell:
script: |
& "C:\Users\lian\Desktop\script\test.ps1"
bash
ansible-playbook test1.yml
这样 Windows 脚本统一放在 Ansible 项目里(比如
/etc/ansible/file/
目录),方便版本管理。
如果要打印脚本输出的,playbook 脚本改为如下:
yaml
- name: Run scripts on Windows
hosts: windows
tasks:
- name: Copy bat file to Windows
ansible.windows.win_copy:
src: /etc/ansible/file/test.bat
dest: C:\Users\lian\Desktop\script\test.bat
- name: Run bat file and show output
ansible.windows.win_command: C:\Users\lian\Desktop\script\test.bat
register: bat_result
- name: Display bat output
debug:
var: bat_result.stdout_lines
- name: Copy ps1 script to Windows
ansible.windows.win_copy:
src: /etc/ansible/file/test.ps1
dest: C:\Users\lian\Desktop\script\test.ps1
- name: Run ps1 file and show output
ansible.windows.win_shell: powershell.exe -ExecutionPolicy Bypass -File C:\Users\lian\Desktop\script\test.ps1
register: ps1_result
- name: Display ps1 output
debug:
var: ps1_result.stdout_lines
另外会发现,Windows 执行这些脚本只有返回到 Ansilble 只有执行和打印输出信息 ,如果脚本本身就有问题,Ansible 执行过程是不会中断、也不会有脚本以外的报错等信息 。
这样可以结合输出,追加脚本打印输出,跟据 Windows 执行脚本时返回的信息,判断输出结果符不符合预期(如:Success),符合的话就是正确执行。
方法 2:直接传脚本内容(小脚本用)
如果脚本不大,可以直接嵌到 Playbook,用 win_powershell
/ win_shell
的 inline script 方式:
yaml
- name: Run scripts on Windows (inline)
hosts: windows
tasks:
- name: Run bat inline
ansible.windows.win_shell: |
if not exist "C:\Users\lian\Desktop\script\test2.bat" echo. > "C:\Users\lian\Desktop\script\test2.bat"
echo echo Hello from BAT > "C:\Users\lian\Desktop\script\test2.bat"
"C:\Users\lian\Desktop\script\test2.bat"
- name: Run ps1 inline
ansible.windows.win_powershell:
script: |
Write-Output "Hello from PowerShell"
缺点:脚本内容写在 YAML 里,不好维护,适合临时命令。
方法 3:Ansible roles + 模板化(更适合大规模)
和 方法 1 差不多。
把 bat/ps1 脚本放到 /etc/ansible/file/
目录下,用 win_copy
统一分发并执行,方便团队协作和版本控制。
bash
vim test3.yml
yaml
- name: Copy and run BAT
hosts: windows
tasks:
- block:
- name: Copy BAT file
win_copy:
src: /etc/ansible/file/test.bat
dest: C:\Users\lian\Desktop\script\test.bat
- name: Run BAT file
win_command: C:\Users\lian\Desktop\script\test.bat
tags:
- bat
- name: Copy and run PS1
hosts: windows
tasks:
- block:
- name: Copy PS1 file
win_copy:
src: /etc/ansible/file/test.ps1
dest: C:\Users\lian\Desktop\script\test.ps1
- name: Run PS1 file
win_powershell:
script: C:\Users\lian\Desktop\script\test.ps1
tags:
- ps1
执行:
bash
ansible-playbook -i windows test3.yml -t bat
注意事项
测试过程中,发现 Windows LTSC 版阉 割太多,好多事情都做不了,一个简单的 CMD
、简单的 bat 脚本
,都会把变量里的内容打印并识别成执行语句。
Ansible 里的 win_xx
等命令就更不用说了,Windows LTSC 上很多依赖调用不了,执行会报错。
当然,用 Windows Home 版 作为 Ansbile 节点也不行,因为它无法开启 WinRM 服务。
所以实际上,只能用 Server 版 和 专业/企业版 作为 Ansible 节点。
好在我实际使用场景都是 Window Server 版,所以不用担心功能严格问题。
总结
- Ansible 控制端 (Docker 内)通过 Python 的
pywinrm
库发送 WinRM 请求。 - Windows 节点 WinRM 服务接收请求 → 验证用户身份 → 调用 PowerShell 引擎执行命令。
- 返回结果 通过 WinRM 协议传回 Ansible。
- Ansible 汇总输出,显示在控制端。
虽说 Windows 的 bat/ps1 脚本也可以存放到 Windows 节点上,但不好管理。
Windows 的脚本都放到 Ansible 上,并且 Playbook 也在 Ansible 上,再加上 Git 仓库,这样便于对整套 Playbook 和 Windows 脚本进行版本管理。可实现,一个平台(Ansible)上就可以完成脚本编写 + 下发了。