这篇文章给出一套可复现流程:在 Proxmox VE(PVE)上把 Windows 11 做成可克隆、可在 PVE 面板注入用户名/密码/静态 IP/主机名、并且宿主机能读取 IP / 优雅关机的基础通用模板。适用对象是需要批量交付 Windows VM 的运维/平台/研发同学;你会得到一套从建 VM、安装驱动与 Guest Agent、接入 Cloud-Init(ConfigDrive + Cloudbase-Init)、泛化封模和常见故障定位的完整 SOP。
1)环境与版本(含适用边界)
1.1 环境信息(示例)
| 项目 | 值 |
|---|---|
| 虚拟化平台 | Proxmox VE(PVE) |
| Guest OS | Windows 11 25H2(示例) |
| Cloud-Init 注入 | PVE CloudInit Drive(ConfigDrive2) + Cloudbase-Init |
| 宿主管理 | QEMU Guest Agent(PVE 启用 + Windows 内安装) |
| VirtIO 驱动 | virtio-win ISO |
1.2 目标(模板交付标准)
- PVE 可读到 VM IP、可优雅关机:PVE 勾选 QEMU Agent + Windows 内 QEMU Guest Agent 正常运行
- PVE Cloud-Init 页面可注入:user / password / static IP / hostname
- 克隆后不冲突:generalize + 清理 Cloudbase-Init 执行状态(避免克隆后不再执行注入)
1.3 合规/安全提醒(建议读完再选)
- 绕过 TPM / Secure Boot 检查属于“非标准安装路径”,可能不符合企业基线与微软支持边界;生产模板更建议用 OVMF + vTPM 2.0 + Secure Boot(PVE 原生支持)。
- 本文会给出“绕过安装门槛”的可复现步骤,但更推荐你在交付标准中明确:生产=不绕过;实验/临时=可绕过。
2)方案概述(一句话结论 + 关键点)
**结论:**模板采用 VirtIO 驱动 + QEMU Guest Agent + PVE CloudInit Drive(ConfigDrive2)+ Cloudbase-Init 组合:
- PVE CloudInit Drive 负责把“用户/密码/网络/主机名”写进 ConfigDrive;
- Cloudbase-Init 在 Windows 内读取 ConfigDrive 并落地配置;
- QEMU Guest Agent 提供宿主机读取 IP / 关机等管理能力;
关键点(高频踩坑):
- PVE Cloud-Init 注入生效要两端满足:VM 已挂 CloudInit Drive + Windows 已安装并能运行 Cloudbase-Init
- QEMU Guest Agent 生效要两端满足:PVE Options 启用 Agent + Windows 内安装 virtio-serial/guest-tools + QEMU-GA 服务 running
- Cloudbase-Init 配置文件需注意编码:在中文系统里容易遇到 GBK 解码失败;建议配置文件保持 纯 ASCII(无中文注释/特殊字符)
3)落地步骤(建机 → 安装 → 配置 → 验证)
3.1 准备材料(PVE 侧)
- Windows11 ISO安装镜像
- VirtIO ISO
https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.285-1/ - Cloudbase-Init
https://github.com/cloudbase/cloudbase-init/releases
3)落地步骤(可复现流程)
3.1 PVE 创建虚拟机
Win11 / Server 2022/2025 强烈建议按 Proxmox 的 Win11 最佳实践走:创建 VM 时选择对应 Windows Guest OS,并在 System 里启用 QEMU Agent;磁盘走 SCSI + “VirtIO SCSI single”。https://pve.proxmox.com/wiki/Windows_11_guest_best_practices?utm_source=chatgpt.com
3.2 PVE 创建 VM(建议参数)
建议用(性能/兼容性/可运维)默认组合:
- Machine:q35
- BIOS:OVMF (UEFI);添加 EFI Disk
- Disk:SCSI + Controller 选 VirtIO SCSI single
- NIC:VirtIO
- System:勾选 QEMU Agent = Enabled
- 安装介质:挂 Windows ISO;同时挂 virtio-win.iso(或勾选“添加额外驱动器”)
客户机操作系统类别为:Microsoft Windows。选择版本11/2022/2025,勾选为VirtIO驱动程序添加额外驱动器,选择刚才下载的virtio-win.iso

BIOS选择OVMF,勾选Qemu代理,勾选添加EFI磁盘,我这里不建议勾选TPM(跨平台迁移会遇到困难)。

3.3 安装 Windows(含绕过 TPM / SecureBoot / 无网络 OOBE)
3.3.1(可选)绕过 TPM / SecureBoot 检查
安装界面按 Shift + F10 打开 CMD,执行:
REG ADD HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /t REG_DWORD /d 1
REG ADD HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /t REG_DWORD /d 1或用 regedit 在 HKEY_LOCAL_MACHINE\SYSTEM\Setup 下创建 LabConfig,并创建两个 DWORD:
BypassSecureBootCheck = 1
BypassTPMCheck = 1
3.3.2 加载 VirtIO 存储驱动(否则看不到磁盘)
在选择磁盘页面点“加载驱动程序”,选 virtio ISO 中类似目录:
vioscsi\win11\amd64

3.3.3(可选)绕过“必须联网”OOBE
若 OOBE 阶段提示无网络、无法继续,可在 Shift + F10 打开 CMD 后执行:
方式一:
本质:通过 start 打开一个 URI 协议(ms-cxh:),让 CloudExperienceHost(云体验主机)直接跳到“本地账户创建”的流程页面。系统会直接跳转到本地账户创建页面,无需重启。
start ms-cxh:localonly方式二:
本质:调用系统自带脚本 BypassNRO.cmd,去“打开/设置”一个 BypassNRO 的绕过逻辑(常见表现是写入/启用对应 OOBE 的标记),然后触发重启。需要重启,仅跳过联网步骤,而且最新版本即将移除。
oobe\BypassNRO.cmd
重启后选择“我没有 Internet 连接”继续。

3.4 安装 VirtIO Guest Tools + 启动 QEMU Guest Agent
进入桌面后,先把挂载的CD驱动器改一下盘符

从 virtio-win 光驱执行:
virtio-win-gt-x64.msivirtio-win-guest-tools.exe
安装完成后,拉起 QEMU Guest Agent(服务名可能不同,先查再启):

安装完成后就可以看到网卡了,建议此时开启RDP远程桌面服务方便后续配置。
装完后用 PowerShell(管理员权限) 把 QEMU GA 服务拉起并设为自启动(不同包服务名略有差异;下列是常见写法):
Start-Service -Name 'QEMU-GA'
Set-Service -Name 'QEMU-GA' -StartupType Automatic验证点:
- PVE → VM Summary 能显示 IP(或 Agent 信息开始出现)
- Windows:
Get-Service QEMU-GA
3.5 安装CloudInit
3.5.1 PVE WebUI
- 关机 VM
- VM → Hardware → Add → CloudInit Drive
- Storage 选系统盘存储(如
local-lvm) - VM → Cloud-Init:填写
ciuser / cipassword / ipconfig0 / DNS→ Regenerate Image → 开机

3.6 安装 Cloudbase-Init(Windows 侧)并配置 ConfigDrive
安装 Cloudbase-Init 时勾选:
- Run Cloudbase-Init service as LocalSystem

安装完成后不要点这个勾选框。

安装完成后修改配置文件:C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf
首先修改cloudbase-init.conf文件编辑权限,右键文件——属性,赋予Users完整权限。

3.6.1 推荐配置(PVE ConfigDrive2 + 静态 IP)
配置文件建议保持 纯 ASCII(避免中文系统默认 GBK 解码报错)。
[DEFAULT]
; --- Account ---
username=Admin
groups=Administrators
rename_admin_user=true
; --- Password injection ---
inject_user_password=true
first_logon_behaviour=no
; --- Datasource: PVE uses ConfigDrive (configdrive2 for Windows) ---
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService
; --- Logging ---
verbose=true
debug=true
log_dir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
log_file=cloudbase-init.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
logging_serial_port_settings=
; --- Network helpers ---
mtu_use_dhcp_config=false
ntp_use_dhcp_config=false
; --- Local scripts (optional) ---
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
; --- Hostname ---
netbios_host_name_compatibility=false
; --- Execution ---
allow_reboot=true
stop_service_on_exit=true
check_latest_version=false
; --- Plugins order matters ---
plugins=cloudbaseinit.plugins.common.networkconfig.NetworkConfigPlugin,cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,cloudbaseinit.plugins.windows.createuser.CreateUserPlugin,cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin,cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin,cloudbaseinit.plugins.common.userdata.UserDataPlugin,cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin
; --- Tools path (keep yours) ---
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
[config_drive]
types=iso
locations=cdrom
记得给Admin账户启用

重启后就可以在PVE的Cloud-init配置中修改IP了

3.6.2 快速验证(注入是否执行)
# 1) 服务状态(按你的配置 stop_service_on_exit=true,执行完会自动停)
Get-Service cloudbase-init
# 2) 看日志(关键:Config Drive found / Metadata service loaded / NetworkConfigPlugin)
Get-Content "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\cloudbase-init.log" -Tail 200
# 3) 验证 IP
Get-NetIPAddress -AddressFamily IPv4 | Format-Table -Aut3.6.3 禁用恢复分区
装好系统后,Windows 往往会在系统盘末尾自动创建 Recovery(恢复)分区。这个分区会把 C: 与磁盘尾部新增空间隔开,导致 克隆后即使扩了磁盘容量,Cloudbase-Init / 自动扩容也无法把 C 盘吃满。因此做模板时建议把 WinRE 禁用并移除该分区,保证 C 盘后方没有任何后置分区。

1)禁用 WinRE(Windows Recovery Environment)
reagentc /disable
reagentc /info2)删除 Recovery 分区(危险操作:先确认分区号)
⚠️ 危险操作:请先确认 Recovery 分区编号,以下以 Disk 0 的 Partition 4 为例。
diskpart在 diskpart 里逐行执行:
select disk 0
select partition 4
delete partition override
exit3)重启验证
重启虚拟机后,检查:
- C: 是否能在首次启动后(Cloudbase-Init/扩容流程)自动扩容到磁盘最大可用空间
- 确保 C 盘后面不再有任何分区(尤其是 Recovery),否则仍会阻挡自动扩容
结论:要让“克隆后系统盘自动吃满扩盘”稳定生效,系统分区后方必须是连续未分配空间。

4)可选实践(按需启用:OpenSSH / 精简 App / 更新策略)
4.1 安装OpenSSH-Server
https://github.com/PowerShell/Win32-OpenSSH/releases
下载并解压到C:\Program Files\
先放行PowerShell 执行策略
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force启动与开机自启 + 防火墙
.\install-sshd.ps1
# 设置开机自动启动
Start-Service sshd
Set-Service sshd -StartupType Automatic
Start-Service ssh-agent
Set-Service ssh-agent -StartupType Automatic
Get-Service sshd,ssh-agent | Format-Table Name,Status,StartType
# 放行 Windows 防火墙 22 端口
if (-not (Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -Name "OpenSSH-Server-In-TCP" `
-DisplayName "OpenSSH Server (sshd)" `
-Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
}
Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" | Format-Table -Auto
# 本机监听检查
netstat -ano | findstr ":22"
# 看 sshd 配置语法(可选)
& "C:\Program Files\OpenSSH-Win64\sshd.exe" -t
然后开机就可以通过SSH端口远程连接了
4.2 禁用Windows Defender
下载执行即可:https://github.com/ionuttbara/windows-defender-remover
*关闭安全防护会显著提高风险,并让后续补丁/排障成本上升。
4.3 禁用Windows自动更新
下载执行即可:https://github.com/tsgrgo/windows-update-disabler
*关闭系统更新会显著提高风险,并让后续补丁/排障成本上升。
4.4 去除垃圾应用
卸载服务器上不需要的Xbox、GameBar等插件。
*以下工具需要管理员权限,风险自行评估。
ZyperWinOptimize (中文优化工具)
https://github.com/ZyperWave/ZyperWinOptimize
O&O AppBuster(卸载Windows自带APP)
便携、免费,专门用来卸载 Windows 10/11 预装/隐藏应用,并且支持恢复。
https://www.oo-software.com/en/ooappbuster

Chris Titus Tech WinUtil(一个命令打开工具箱)
以下为高风险命令,风险自行评估。
irm "https://christitus.com/win" | iex4.5 激活Windows
⚠️以下为高风险命令,风险自行评估。
irm https://get.activated.win | iex
4.6 开机自动登录桌面+自动启动程序
重要前提(很多人会踩坑):
带 UI 的程序必须运行在“交互式用户会话”里才会显示。
也就是说,把程序做成 Windows Service / “启动时(ONSTART)”任务通常跑在 Session 0,程序可能在跑,但界面不会出现在桌面上。
所以本节采用:自动登录 + 登录后启动(ONLOGON)。
这样就可以实现Windows VM 开机后无需人工输入密码,自动进入桌面,并自动拉起某个带 UI 的程序(例如你的 Debug 面板、客户端、采集器 UI、测试工具等)。
推荐组合:
AutoAdminLogon 自动登录(让系统开机后自动登录到某个固定账号)
任务计划程序:ONLOGON 触发启动你的 UI 程序(确保 UI 在桌面出现)
为什么不直接“启动时(ONSTART)”?
因为 ONSTART 往往在系统服务阶段运行,UI 不会显示到桌面;而 ONLOGON 明确发生在用户会话建立后,UI 稳定可见。
4.6.1 手工操作版:
Step 1:开启自动登录(AutoAdminLogon)
⚠️ 风险提示:自动登录需要在机器上保存凭据(注册表/系统凭据)。
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon /t REG_SZ /d 1 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultUserName /t REG_SZ /d "管理员账户" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultPassword /t REG_SZ /d "管理员密码!" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultDomainName /t REG_SZ /d "." /fStep 2:登录后启动 UI 程序(支持带参)
当参数复杂(很多引号/特殊符号/重定向/环境变量)时,用包装脚本最省心、也最利于后续改参数。
1)创建 C:\Scripts\start-myapp.cmd
案例一:运行一个叫C:\Path\MyApp.exe + 参数的DEMO:
@echo off
REM 用 start 让任务不“挂住”,并确保窗口标题位占位
start "" "C:\Path\MyApp.exe" --debug --config "C:\Program Files\My App\cfg.json" --port 1234案例二:开机打印出开机时间DEMO:
@echo off
setlocal EnableExtensions
REM ===== 找到“当前用户桌面”路径(更稳:不硬编码 C:\Users\xxx\Desktop)=====
set "DESKTOP=%USERPROFILE%\Desktop"
if exist "%PUBLIC%\Desktop" set "DESKTOP=%PUBLIC%\Desktop"
REM ===== 日志文件:写到桌面 =====
set "LOGFILE=%DESKTOP%\myapp_autostart.log"
REM ===== 生成时间戳(尽量兼容中文/不同区域设置,用 WMIC 取 ISO 时间)=====
for /f "skip=1 delims=" %%I in ('wmic os get localdatetime 2^>nul') do (
if not "%%I"=="" set "LDT=%%I" & goto :gotTime
)
:gotTime
if not defined LDT set "LDT=UNKNOWN_TIME"
set "TS=%LDT:~0,4%-%LDT:~4,2%-%LDT:~6,2% %LDT:~8,2%:%LDT:~10,2%:%LDT:~12,2%"
echo ==================================================>> "%LOGFILE%"
echo [BOOT UI AUTOSTART] %TS%>> "%LOGFILE%"
echo User=%USERNAME% Computer=%COMPUTERNAME%>> "%LOGFILE%"
echo Script=%~f0>> "%LOGFILE%"
REM ===== 在这里启动你的程序(示例,自己改路径和参数)=====
REM 用 start "" 让任务计划不一直占用,并确保 UI 显示在当前桌面会话
REM start "" "C:\Path\MyApp.exe" --debug --port 1234 >> "%LOGFILE%" 2>&1
echo (MyApp launch command placeholder)>> "%LOGFILE%"
endlocal
exit /b 02)计划任务只负责执行脚本:
schtasks /Create /TN "MyAppUI" /SC ONLOGON /RU "管理员账户" /RL HIGHEST ^
/TR "cmd.exe /c C:\Scripts\start-myapp.cmd" /F如果后续不需要了,可以回滚:
关闭自动登录:
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon /t REG_SZ /d 0 /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultPassword /f删除任务:
schtasks /Delete /TN "MyAppUI" /F4.6.2 模板自动化版:Cloudbase-Init LocalScripts
在前文已经启用了 Cloudbase-Init 的 LocalScriptsPlugin,因此最自然的做法是:
把脚本放到:
C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
例如新建:01-ui-autologon.ps1(幂等,可重复执行不报错)
下面脚本做 3 件事:
1)创建/确保 debuguser 存在并进管理员组
2)开启 AutoAdminLogon
3)创建“登录后启动 UI 程序”的计划任务(可选延迟 30s)
# 01-ui-autologon.ps1
# 作用:开机自动登录桌面 + 登录后自动启动 UI 程序(适合 Debug 模板)
# 建议:保持纯 ASCII,避免编码坑
$User = "你的账户"
$Pass = "你的密码" # Debug 专用密码(生产不建议这样做)
$Exe = "C:\Path\MyApp.exe"
$Args = "--debug"
$TaskName = "MyAppUI"
# 1) 确保用户存在
if (-not (Get-LocalUser -Name $User -ErrorAction SilentlyContinue)) {
net user $User $Pass /add | Out-Null
}
# 2) 确保管理员组(便于调试)
try {
net localgroup administrators $User /add | Out-Null
} catch {}
# 3) 配置 AutoAdminLogon
$winlogon = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
Set-ItemProperty $winlogon "AutoAdminLogon" "1" -Force
Set-ItemProperty $winlogon "DefaultUserName" $User -Force
Set-ItemProperty $winlogon "DefaultPassword" $Pass -Force
Set-ItemProperty $winlogon "DefaultDomainName" "." -Force
# 4) 创建“登录后启动 UI 程序”的计划任务(幂等)
if (-not (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue)) {
$Action = New-ScheduledTaskAction -Execute $Exe -Argument $Args
# 登录触发 + 延迟 30 秒(可按需改)
$Trigger = New-ScheduledTaskTrigger -AtLogOn -User $User
$Trigger.Delay = "PT30S"
# 必须 InteractiveToken 才能稳定显示 UI
$Principal = New-ScheduledTaskPrincipal -UserId $User -LogonType InteractiveToken -RunLevel Highest
$Settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Force | Out-Null
}
Write-Output "AutoLogon + UI task configured."使用方法:
把脚本放进 LocalScripts 目录
克隆新 VM 首次启动时 Cloudbase-Init 执行 LocalScripts → 自动完成配置
下一次重启:系统会自动登录到桌面,并自动启动你的 UI 程序
4.6.3 验收点:
重启 VM 是否自动进桌面(debuguser)
桌面是否出现 MyApp 的窗口/托盘
任务是否存在:
schtasks /Query /TN "MyAppUI" /V /FO LIST5)封装为模板(清理注入状态 + 克隆验收)
5.1 封模前检查清单(PVE)
- Options → QEMU Guest Agent:Enabled
- Hardware → 已挂 CloudInit Drive
- Cloud-Init 页面可正常 Regenerate
5.2 封模前清理(Windows)
目的:确保克隆后 Cloudbase-Init 会再次执行注入,且 SSH host key 不复用。
# 1) 清 Cloudbase-Init 执行状态(用于“测试反复注入”或“封模确保克隆会跑”)
# 注意:这会让 Cloudbase-Init 下次启动重新跑插件
Remove-Item -Recurse "HKLM:\Software\Cloudbase Solutions" -Force -ErrorAction SilentlyContinue
# 2) 清 Cloudbase-Init 日志(可选)
Remove-Item "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\*" -Force -ErrorAction SilentlyContinue
# 3) 如果安装了 OpenSSH,清理 host keys(避免克隆复用)
Remove-Item "C:\ProgramData\ssh\ssh_host_*" -Force -ErrorAction SilentlyContinueOpenSSH host key 的再生成策略与版本有关;如果你希望“开机必生成”,可以在 Cloudbase-Init LocalScripts 里加 ssh-keygen -A(前提 PATH 可用或写全路径)。
关机后在 PVE:Convert to template。
5.4 克隆验收(必须做一次)
克隆一台新 VM 后检查:
- PVE:Summary 可显示 IP;Shutdown 工作正常
- Windows:
Get-NetIPAddress与 PVE 注入一致cloudbase-init.log能看到新一次执行- 若启用 OpenSSH:
sshdrunning,C:\ProgramData\ssh\ssh_host_*已重新生成(或按你的策略生成)
References(原文链接集合)
PVE Windows 11 best practices:
https://pve.proxmox.com/wiki/Windows_11_guest_best_practices
Cloudbase-Init:
https://github.com/cloudbase/cloudbase-init/releases
https://pve.proxmox.com/wiki/Cloud-Init_Support
Win32-OpenSSH:
https://github.com/PowerShell/Win32-OpenSSH/releases
.NET Framework:
https://dotnet.microsoft.com/zh-cn/download/dotnet-framework
PVE安装Windows11小记
https://willxup.top/archives/pve-install-win11
附录)
下载 .NET Framework 运行时大全
https://dotnet.microsoft.com/zh-cn/download/dotnet-framework
下载 Notepad++
* 严正声明:该网站存在捏造事实、误导性陈述及有害政治观点,博主不认同该工具作者的一切观点。
https://notepad-plus-plus.org