
Ouvre n’importe quel tuto Packer sur Windows et tu tomberas sur la même promesse : « une image parfaite en une commande, sans effort ». Tu copies le template, tu lances packer build, et tu déploies ta machine idéale.
Sauf que c’est faux. Enfin, pas tout à fait. La commande existe, le résultat est réel, mais entre les deux il y a une dizaine de pièges que personne ne te montre. Contrôleur disque invisible, balise obsolète qui fait tout planter, VM qui s’endort en plein build. J’ai tout traversé, en direct, sur ma machine. Cet article, c’est le tuto que j’aurais aimé lire avant de commencer.
L’objectif est simple : produire une golden image Windows 11 automatisée. Windows s’installe seul, le système passe en français, les VMware Tools et cinq applis se posent tout seuls, et à la fin tu récupères une image prête à cloner. Le tout sur VMware Workstation, en local, sans toucher la souris une seule fois une fois le build lancé.
Je te préviens tout de suite : ce n’est pas plug and play. Mais une fois les pièges désamorcés, ça tourne d’un bout à l’autre en 24 minutes. Et je te donne les vrais chiffres, y compris ceux qui font mal.
Packer, Chocolatey, Boxstarter : qui fait quoi
Avant de coder, il faut comprendre les trois briques, parce qu’elles n’ont rien à voir entre elles. On les empile, chacune a un rôle précis.
Packer, c’est l’orchestrateur du build. C’est lui qui crée la VM, monte l’ISO, la démarre, attend que Windows soit debout, se connecte, pousse les scripts, gère les reboots, puis exporte l’image. Il ne fait rien d’autre. Il fabrique le moule, une fois, et s’arrête.
Chocolatey, c’est le gestionnaire de paquets Windows. L’équivalent d’apt sur Debian ou de dnf sur Fedora, mais pour Windows. Une ligne, choco install firefox, et le soft est là, sans installeur à cliquer, sans « suivant, suivant, terminer ». C’est lui qui remplit le moule.
Boxstarter, c’est une surcouche de Chocolatey. Son intérêt unique : la résilience aux reboots. Il pose un autologon et reprend le script exactement là où il s’était arrêté après un redémarrage. Utile quand tu enchaînes des installs qui rebootent. On y reviendra, parce que dans un build Packer, il fait doublon, et c’est une nuance importante.
Packer contre Terraform : la confusion classique
Petite mise au point, parce que les deux sont des outils HashiCorp, écrits en HCL, et qu’on les confond tout le temps. Ce ne sont pas des concurrents. Ils font deux choses différentes, à deux moments différents.
Packer fabrique des images. Il produit un artefact figé, immuable : une image de VM, un template, une AMI sur AWS. Il construit, il exporte, il s’arrête. Il ne sait pas ce que devient l’image après, et il s’en moque.
Terraform déploie et gère l’infrastructure. Il est déclaratif, il tient un état (le fameux state), il crée, modifie et détruit des ressources vivantes : des VM, des réseaux, des ressources cloud. Il gère tout le cycle de vie.
En clair : Packer fabrique le moule, Terraform coule le béton dedans et gère les bâtiments. Le combo classique en prod, c’est Packer qui produit la golden image, puis Terraform qui déploie cinquante instances de cette image. L’un ne remplace pas l’autre, ils se passent le relais.
Pour cette démo, on reste sur Packer seul. On fabrique le moule.
I. Installer Packer sur l’hôte, pas dans la VM
Première confusion à évacuer : Packer s’installe sur la machine hôte, celle qui fait tourner VMware Workstation. Pas dans la VM. La VM n’existe même pas encore quand tu lances le build, c’est Packer qui la crée.
Sur mon poste, winget fait le job en trente secondes.
[CODE : install-packer.ps1]
powershell
winget install HashiCorp.Packer
Attention au piège le plus bête du monde : après l’install, le PATH n’est pas rechargé dans ta session en cours. Soit tu fermes et rouvres ton terminal, soit tu recharges à la volée.
[CODE : reload-path.ps1]
powershell
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
packer version

Chez moi, Packer v1.15.4. Note bien la version, parce que le plugin qui va avec est devenu beaucoup plus strict que les anciens, et ça va nous jouer des tours.
II. L’ISO Windows 11 : LTSC ou pas, la nuance qui compte
Pour la source, on part d’une ISO d’évaluation, gratuite et sans clé, depuis l’Evaluation Center Microsoft. Attention, le paysage a changé. La Windows 10 Enterprise classique a été retirée depuis la fin de support d’octobre 2025. Sur Windows 11, tu as le choix entre la Enterprise LTSC 2024 (build 26100) et la version standard.
Petite parenthèse vécue : j’ai cru télécharger la LTSC, et une fois la VM montée, j’ai vu le Microsoft Store et Outlook dans le menu Démarrer. Or la LTSC n’a ni l’un ni l’autre. J’avais en fait la Enterprise Evaluation standard, pas la LTSC. Pour le pipeline ça ne change rien, choco et Packer s’en fichent, mais c’est le genre de détail à vérifier avant de croire qu’on a la bonne édition.
L’eval demande un formulaire, donc pas de téléchargement direct. Packer ne peut pas la récupérer seul, on pointe un fichier local. Et on calcule son empreinte pour la vérification d’intégrité.
[CODE : hash-iso.ps1]
powershell
Get-FileHash "C:\demo\ISO\win11ent.iso" -Algorithm SHA256
III. Le template Packer, et le plugin qui ne pardonne rien
Le coeur du build, c’est le fichier .pkr.hcl. Il déclare la source (la VM à créer) et le build (les étapes de provisioning). Je te donne la version finale, celle qui marche, après avoir mangé toutes les erreurs.
Premier piège, et il est de taille. Le plugin github.com/vmware/vmware en version 2.1.3 est beaucoup plus strict que les versions d’avant. Il ne devine plus rien. Deux paramètres que les anciens plugins déduisaient tout seuls sont désormais obligatoires : network_adapter_type et disk_adapter_type. Il y en a un troisième, tools_mode, mais celui-là ne devient obligatoire que si tu touches à la config d’upload des VMware Tools, ce qu’on évite ici en les installant par choco (j’y reviens en partie VI). Si tu copies un template d’un vieux tuto, il casse à la validation. Tu vas te manger des X is required en série. On met donc les deux paramètres requis d’entrée.
[CODE : windows11.pkr.hcl (extrait source)]
hcl
source "vmware-iso" "win11" {
vm_name = "win11-ltsc-demo"
guest_os_type = "windows11-64"
iso_url = var.iso_path
iso_checksum = var.iso_checksum
cpus = 4
memory = 8192
disk_size = 40960
disk_adapter_type = "sata"
cdrom_adapter_type = "sata"
network_adapter_type = "e1000e"
vmx_data = {
"firmware" = "efi"
"uefi.secureBoot.enabled" = "FALSE"
"vtpm.present" = "FALSE"
}
cd_files = ["./autounattend.xml"]
cd_label = "PROVISION"
boot_wait = "3s"
boot_command = ["<enter>"]
communicator = "winrm"
winrm_username = "packer"
winrm_password = "packer"
winrm_timeout = "2h"
}
Deux choix à expliquer, parce qu’ils m’ont coûté du temps.
disk_adapter_type = "sata". C’est le piège numéro un. Sans cette ligne, la VM démarre, l’install Windows se lance, et tu arrives sur l’écran de sélection du disque avec une liste vide. Aucun disque. Le disque existe, Packer l’a créé, mais il est branché sur le contrôleur par défaut du builder, du LSI Logic, dont l’ISO Windows 11 n’a pas le pilote natif. Windows ne le voit pas. En SATA (AHCI), il est reconnu out of the box, sans charger de driver.

network_adapter_type = "e1000e". Même logique. L’ISO Windows 11 n’embarque pas le driver VMXNET3. Si tu prends vmxnet3, la carte réseau n’est pas reconnue pendant l’install, et sans réseau, pas de WinRM. Le e1000e est reconnu nativement.
Une fois le template en place, on initialise le plugin.
[CODE : packer-init.ps1]
powershell
packer init windows11.pkr.hcl

IV. L’autounattend, et la balise qui tue tout le fichier
L’autounattend.xml, c’est le fichier de réponses qui rend l’install de Windows silencieuse. Il partitionne le disque, crée le compte, contourne les vérifications matérielles de Windows 11 et active WinRM. Packer le monte dans un petit CD que Windows détecte tout seul.
Le contournement TPM et Secure Boot se fait en dur, via des clés de registre LabConfig écrites pendant la phase WinPE. C’est ce qui évite d’avoir à configurer un vTPM chiffré dans VMware, une vraie galère.
[CODE : autounattend.xml (extrait bypass)]
xml
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>reg add "HKLM\System\Setup\LabConfig" /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path>
</RunSynchronousCommand>
<!-- idem pour BypassSecureBootCheck, BypassRAMCheck, BypassCPUCheck, BypassStorageCheck -->
</RunSynchronous>
Et voilà le piège numéro deux, un des plus vicieux. Sur mon premier build correct, l’install s’est déroulée, puis au premier reboot, écran bleu d’erreur : « The computer restarted unexpectedly ». Plantage en phase specialize.
La cause était planquée dans C:\Windows\Panther\setuperr.log, qu’on lit avec un Shift+F10 dans la VM. Une ligne : The provided unattend file is not valid sur le composant CEIPEnabled. Cette balise a été supprimée de Windows 11 24H2. Elle n’existe plus. Et le setup, au lieu de l’ignorer, rejette tout le fichier autounattend à cause de cette seule ligne obsolète.

Le truc à retenir : une seule balise obsolète invalide l’autounattend entier. Si ton install plante en specialize, lis setuperr.log, il te donne le nom exact du composant fautif. On retire la ligne, et ça repart.
V. Valider puis lancer le build
Avant de lancer un build qui va tourner vingt minutes, on valide. packer validate attrape les erreurs de syntaxe et les paramètres manquants en deux secondes, ça évite de découvrir un X is required après dix minutes d’install Windows.
[CODE : packer-validate.ps1]
powershell
packer validate .\windows11.pkr.hcl
Tu veux lire The configuration is valid.. Si le plugin râle sur un champ obligatoire, tu corriges et tu revalides jusqu’à ce que ce soit propre.
Ensuite seulement, le build.
[CODE : packer-build.ps1]
powershell
packer build .\windows11.pkr.hcl
À partir de là, une fenêtre VMware Workstation s’ouvre, la VM boote sur l’ISO, et tout s’enchaîne sans que tu touches à rien. Surveille juste les premières secondes, le prompt « Press any key to boot from CD » où Packer envoie le <enter>. Si la VM le rate et part dans un shell EFI, augmente boot_wait et relance. C’est le réglage le plus capricieux du pipeline.
VI. Le DHCP absent, le décrochage WinRM et l’APIPA
Une fois Windows installé, Packer attend WinRM pour se connecter. Sur un de mes runs, il est resté bloqué sur Waiting for WinRM to become available sans fin, sans message d’erreur. Le genre de blocage silencieux qui te fait perdre une heure.
WinRM tournait pourtant dans la VM, le port 5985 répondait en local. Mais depuis l’hôte, rien. La cause : la VM avait une adresse en 169.254.x.x. Du APIPA. Autrement dit, elle n’avait pas reçu de bail DHCP, parce que le service DHCP de VMware était désactivé sur mon poste. Sans IP routable, Packer ne peut pas la joindre.

La réactivation du service DHCP côté hôte règle le problème.
[CODE : fix-dhcp.ps1]
powershell
Start-Service VMnetDHCP
Set-Service VMnetDHCP -StartupType Automatic
Note bien ce piège : un Waiting for WinRM qui ne finit jamais, ce n’est presque jamais WinRM. C’est le réseau. Vérifie l’IP de la VM en premier. Et méfie-toi du ping : le pare-feu Windows bloque l’ICMP par défaut, donc un ping qui échoue ne prouve rien. Le vrai test, c’est le port, avec Test-NetConnection -Port 5985.
VII. Les provisioners : Tools, langue, applis, et les reboots
On arrive au coeur du build. Le bloc build enchaîne les scripts PowerShell, avec des reboots entre les étapes sensibles. Et non, ces reboots ne sont pas gérés par Boxstarter, mais par le provisioner natif windows-restart de Packer. J’y reviens juste après, c’est important.
[CODE : windows11.pkr.hcl (extrait build)]
hcl
build {
sources = ["source.vmware-iso.win11"]
provisioner "powershell" {
scripts = ["./scripts/install-vmware-tools.ps1"]
valid_exit_codes = [0, 3010]
}
provisioner "windows-restart" {}
provisioner "powershell" {
scripts = ["./scripts/setup-language-french.ps1"]
}
provisioner "windows-restart" {}
provisioner "powershell" {
scripts = ["./scripts/install-choco-packages.ps1"]
}
}
Les VMware Tools : oublie l’upload WinRM, passe par choco
Première approche, j’ai voulu faire uploader l’ISO des Tools par Packer, via l’option tools_upload. Mauvaise idée. L’upload de fichiers par WinRM est lent et fragile. Sur une ISO de 130 Mo, il a bloqué complètement, rien n’atterrissait dans la VM, dix minutes pour zéro octet écrit.
La parade est plus simple et plus reproductible : installer les Tools par Chocolatey. Le package existe, il télécharge depuis le dépôt Broadcom, pas de gros transfert WinRM, pas de dépendance au chemin de ton install Workstation.
[CODE : install-vmware-tools.ps1 (extrait)]
powershell
choco install vmware-tools -y --no-progress
Le truc à retenir : pour tout artefact un peu lourd, le gestionnaire de paquets bat l’upload WinRM. C’est plus robuste, point.
Le code 3010 : un succès pris pour une erreur
Piège suivant, et c’est un classique Windows. Les VMware Tools s’installent nickel, mais choco retourne le code de sortie 3010. Ce code veut dire « succès, reboot nécessaire ». Or Packer considère par défaut que seul le code 0 est acceptable. Il prend donc ce 3010 pour un échec et coupe le build.
D’où le valid_exit_codes = [0, 3010] sur ce provisioner. On dit à Packer que 3010 est un succès valide. Ce code sort tout le temps avec les MSI et les features Windows, à connaître par coeur.
La bascule en français, et la leçon la plus chère
Mon idée de départ : installer trois packs de langue, allemand, espagnol et français, puis passer le système en français. Bien pour le spectacle. Sauf que le spectacle m’a coûté des heures.
Install-Language tire chaque pack depuis Windows Update, plusieurs centaines de Mo, plus les Features on Demand (reconnaissance vocale, écriture manuscrite, OCR). Et ces FODs déclenchent en cascade une maintenance des composants Windows via TrustedInstaller qui ne finit jamais dans un délai raisonnable. Sur une image eval fraîche, j’ai vu TrustedInstaller à plus de 13000 secondes de CPU, bloqué sur le deuxième pack après 30 minutes.

La correction, et c’est aussi la bonne pratique pour une vraie golden image : une seule langue, le français, et sans les FODs lourds.
[CODE : setup-language-french.ps1 (extrait)]
powershell
# -ExcludeFeatures evite les FODs (Speech, OCR, Handwriting)
# qui saturent TrustedInstaller pendant des heures.
Install-Language fr-FR -CopyToSettings -ExcludeFeatures
Set-WinUserLanguageList -LanguageList fr-FR -Force
Set-WinUILanguageOverride -Language fr-FR
Set-WinSystemLocale fr-FR
Set-SystemPreferredUILanguage fr-FR
Copy-UserInternationalSettingsToSystem -WelcomeScreen $true -NewUser $true
Le truc à retenir : ajouter des langues avec tous les FODs, c’est le meilleur moyen de rendre ton build ininstallable. Une langue, -ExcludeFeatures, sauf besoin explicite d’OCR ou de reconnaissance vocale.

Le piège dans le piège : la VM qui s’endort
Et le plus vicieux pour la fin, celui que personne ne documente. Même avec -ExcludeFeatures, mon build a décroché à 43 minutes, en plein Install-Language, avec une erreur réseau brute : wsarecv: the connected host has failed to respond. WinRM coupé net.
J’ai cru à un problème de pile réseau. Faux. La vraie cause : la VM se mettait en veille. Pendant qu’Install-Language mouline en silence, sans activité clavier ni souris, Windows applique son plan d’alimentation et met la machine en veille. Le réseau tombe, Packer perd WinRM, le build meurt.

La parade, à mettre en tout premier dans le pipeline : désactiver la veille via powercfg.
[CODE : disable-sleep.ps1]
powershell
powercfg /change standby-timeout-ac 0
powercfg /change standby-timeout-dc 0
powercfg /change monitor-timeout-ac 0
powercfg /change hibernate-timeout-ac 0
powercfg /setactive SCHEME_MIN
C’est une ligne à mettre dans n’importe quel pipeline de golden image Windows. Un provisioner long sur une VM qui s’endort, ça décroche WinRM sans jamais te dire pourquoi. Note-le bien.
Les applis, et le checksum qui casse tout seul
Dernier provisioner, les cinq applis via choco. Au départ, ma liste incluait Google Chrome. Erreur. Le package Chrome sur Chocolatey embarque un checksum figé du MSI, mais Google écrase le même fichier à chaque nouvelle version. Résultat, dès que le package est en retard d’une release, le checksum ne correspond plus et l’install échoue : hashes do not match.
Pour un build reproductible, un paquet qui casse au gré des sorties de Google, c’est exactement ce qu’on ne veut pas. Je l’ai remplacé par Firefox, qui n’a pas ce souci. Et j’ai rendu le script tolérant : un paquet qui échoue est loggé, mais ne fait plus planter tout le build.
[CODE : install-choco-packages.ps1 (extrait)]
powershell
$packages = @("7zip","notepadplusplus","firefox","git","vscode")
$failed = @()
foreach ($pkg in $packages) {
choco install $pkg -y --no-progress
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) {
Write-Warning "Echec de $pkg (code $LASTEXITCODE)"
$failed += $pkg
}
}
VIII. Et Boxstarter dans tout ça ?
J’ai gardé Boxstarter pour la fin, parce que c’est la nuance la plus honnête de cet article. Mon idée initiale était d’utiliser Boxstarter pour gérer les reboots entre les étapes. Bonne intuition sur le papier, mais mauvaise dans un contexte Packer.
Boxstarter gère les reboots via un autologon et une reprise du script en session interactive. Packer, lui, pilote la VM en WinRM, session non interactive. Quand Boxstarter reboote, la connexion WinRM de Packer tombe, et Packer ne voit pas la reprise Boxstarter qui se joue dans la session autologon. Les deux ne se coordonnent pas.
En clair : dans un build Packer, l’outil pour les reboots, c’est windows-restart. Pas Boxstarter. Boxstarter brille quand tu provisionnes une machine en direct, hors Packer, par exemple un poste physique où tu veux enchaîner des installs qui rebootent et reprendre toutes seules.
C’est ça, la vraie leçon : deux outils qui semblent faire la même chose, mais chacun a son terrain. Je détaillerai le vrai cas d’usage de Boxstarter, packs de langue et reboots en cascade sur une machine directe, dans un article séparé. Il mérite sa propre démo.
Ce que je retiens
Le build final tourne d’un bout à l’autre sans intervention. Windows installé, VMware Tools, système en français, cinq applis, arrêt propre, image compactée. Voilà les chiffres, sans enjoliver.
| Étape | Durée constatée |
|---|---|
| Build de base (Windows + Tools + 5 applis) | ~24 min |
| Même build avec 3 langues + FODs | décrochage à 43 min, jamais fini |
Avec 1 langue et -ExcludeFeatures | build qui repasse au vert |
| Gain du cache ISO sur les runs suivants | ISO déjà vérifiée, non retéléchargée |
Et la liste des pièges, parce que c’est ça le vrai contenu de ce tuto, pas le template :
- Le contrôleur disque en SATA, sinon Windows ne voit aucun disque.
- La balise CEIPEnabled obsolète en 24H2, qui invalide tout l’autounattend.
- Le DHCP VMware désactivé, qui envoie la VM en APIPA et bloque WinRM sans erreur claire.
- Le code 3010 à whitelister, un succès pris pour un échec.
- L’upload WinRM d’ISO qui bloque, à remplacer par choco.
- Les FODs de langue qui saturent TrustedInstaller pendant des heures.
- La veille de la VM qui décroche WinRM en plein provisioner long.
- Le checksum Chrome volatil, à remplacer par Firefox.
- oscdimg de l’ADK à mettre dans le PATH de façon permanente, sinon chaque console casse le build sur la création du CD.

Alors, est-ce que Packer vaut le coup pour une golden image Windows ? Oui, sans hésiter. Une fois le template stabilisé, tu reconstruis ton image parfaite en une commande, à l’identique, autant de fois que tu veux. C’est reproductible, c’est versionnable, c’est du vrai infrastructure as code.
Mais le template propre que tu copies en trois minutes, il cache une journée de pièges que personne ne documente. La reproductibilité se paie une fois, à la mise au point. Après, elle est gratuite. Comme souvent, la vraie valeur n’est pas dans la commande magique, elle est dans tout ce qu’on ne te montre pas.
A bientôt Louis 🐇 🤜 🤛 🐰