{"id":4667,"date":"2025-11-28T10:18:41","date_gmt":"2025-11-28T10:18:41","guid":{"rendered":"https:\/\/www.it-react.com\/?p=4667"},"modified":"2025-11-28T10:20:10","modified_gmt":"2025-11-28T10:20:10","slug":"provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part","status":"publish","type":"post","link":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/","title":{"rendered":"Provisioning a Windows 10 Client from a vSphere Template (2nd part)"},"content":{"rendered":"\n<p>In the <a href=\"https:\/\/www.it-react.com\/index.php\/2025\/11\/25\/building-a-multi-os-provisioning-and-configuration-framework-with-ansible-and-vmware\/\">first part<\/a> of this series, we built the foundation of our automation framework: a clean directory structure, proper inventories, OS-specific variable separation, secure Vault usage, and the vCenter settings that glue everything together. Think of it as laying out all tools on the workbench before touching the actual machine.<\/p>\n\n\n\n<p>Now comes the fun part.<\/p>\n\n\n\n<p>Before we start provisioning, there\u2019s one important lesson we need to get out of the way \u2014 something you will absolutely run into in any real-world vSphere environment:<\/p>\n\n\n\n<p><strong>Windows guest customization breaks completely if your template has been manually sysprep\u2019d.<\/strong><\/p>\n\n\n\n<p>Yes, really.<br>If you generalize a Windows image yourself <em>and then<\/em> let vCenter try to run its own sysprep during OS Customization, you\u2019ll be greeted with the legendary:<\/p>\n\n\n\n<p><em>\u201cThe computer restarted unexpectedly or encountered an unexpected error\u2026\u201d<\/em><\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69dc9788f2f39&quot;}\" data-wp-interactive=\"core\/image\" class=\"wp-block-image aligncenter size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"591\" height=\"247\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on-async--click=\"actions.showLightbox\" data-wp-on-async--load=\"callbacks.setButtonStyles\" data-wp-on-async-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/grafik.png\" alt=\"\" class=\"wp-image-4668\" srcset=\"https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/grafik.png 591w, https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/grafik-300x125.png 300w\" sizes=\"auto, (max-width: 591px) 100vw, 591px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on-async--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p>And your VM will loop itself into oblivion.<br>We\u2019ve been there. We\u2019ve stared at it long enough. Trust us \u2014 don\u2019t do manual sysprep for templates that will be cloned with OS customization.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">So, the rule is simple:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Template that uses OS Customization \u2192 NO manual sysprep.<\/strong><\/li>\n\n\n\n<li><strong>Template for bare clones\/labs with no OS Customization installation \u2192 manual sysprep is fine.<\/strong><\/li>\n<\/ul>\n\n\n\n<p>And one more thing \u2014 <strong>your template MUST have VMware Tools installed.<\/strong><br>This is absolutely essential because the customization engine, IP assignment, hostname injection, and later WinRM bootstrapping all rely on VMware Tools being present and working. Without it, nothing moves.<\/p>\n\n\n\n<p>In this post, we\u2019ll take that clean Windows 10 template \u2014 with VMware Tools installed, but <em>without<\/em> sysprep \u2014 and automate the full provisioning workflow:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>deploying a new VM from template using <code>vmware_guest<\/code><\/li>\n\n\n\n<li>setting CPU, RAM, datastore, folder, and VM name<\/li>\n\n\n\n<li>applying vSphere Guest Customization with static IP<\/li>\n\n\n\n<li>verifying the VM boots cleanly with the correct hostname and network settings<\/li>\n\n\n\n<li>preparing the machine for the next stage (WinRM bootstrap and domain join in Part 3)<\/li>\n<\/ul>\n\n\n\n<p>By the end of this article, we\u2019ll have a reproducible, fully automated Windows 10 provisioning process that works exactly like an enterprise environment \u2014 but without the clicking and praying in the vSphere UI.<\/p>\n\n\n\n<p>Let\u2019s get started.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Defining the VM Metadata (host_vars)<\/h2>\n\n\n\n<p>With the template ready, it\u2019s time to give our new Windows 10 VM an identity.<br>One of the reasons we spent time building a clean repository structure in Part 1 is so that each virtual machine can be defined in a simple and predictable way \u2014 without touching the playbooks themselves.<\/p>\n\n\n\n<p>In other words:<br><strong>playbooks define <em>how<\/em> provisioning works, and host_vars define <em>what<\/em> we want to provision.<\/strong><\/p>\n\n\n\n<p>This separation is what allows the setup to scale later into CI\/CD pipelines. A pipeline doesn\u2019t need to know anything about VM names, IPs, templates, or network settings. It simply notices that a new <code>host_vars<\/code> file appeared and runs the provisioning workflow for that machine. Clean, elegant, and future-proof.<\/p>\n\n\n\n<p>For our first Windows 10 client, we create a metadata file inside:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>inventories\/lab\/host_vars\/HAM01AP001.yml<\/code><\/pre>\n\n\n\n<p>The structure is straightforward:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Windows 10 client metadata\n# Everything Ansible needs for provisioning and post-configuration\n\n# Basic VM identity\nvm_name: \"HAM01AP001\"\ncomputer_name: \"HAM01AP001\"\n\n# Placement in vSphere\nvm_datacenter: \"Datacenter1\"\nvm_cluster: \"Cluster1\"\nvm_folder: \"HAM-Test\"\nvm_datastore: \"DatastoreESXi0_1\"\nvm_network: \"Production\"\n\n\n# Hardware configuration\nvm_cpu: 2\nvm_memory_mb: 4096\nvm_disk_gb: 60\n\n# Template selection (from template mapping defined in group_vars)\nos_family: \"win_client\"\nvm_template: \"{{ win_client_template }}\"\n\n# Static network configuration (used by vSphere Guest Customization)\nip_address: \"192.168.1.60\"\nip_subnet_mask: \"255.255.255.0\"\nip_gateway: \"192.168.1.1\"\ndns_servers:\n  - \"192.168.1.90\"\n  - \"192.168.1.91\"\n\n# Windows local administrator (stored securely in Vault)\nwindows_admin_user: \"Admin\"\nwin_local_admin_password: \"{{ vault_win_admin_password }}\"\n\n# Ansible connection settings (activated after WinRM bootstrap)\nansible_host: \"{{ ip_address }}\"\nansible_connection: winrm\nansible_port: 5985\nansible_user: \"{{ windows_admin_user }}\"\nansible_password: \"{{ win_local_admin_password }}\"\nansible_winrm_transport: ntlm\nansible_winrm_server_cert_validation: ignore<\/code><\/pre>\n\n\n\n<p>This single file fully describes the VM from start to finish.<br>When the provisioning playbook runs, Ansible loads this metadata and passes it to vCenter via the <code>vmware_guest<\/code> module. After the VM boots and WinRM is enabled through our bootstrap process, the same file already contains everything needed for post-configuration tasks.<\/p>\n\n\n\n<p>By keeping all host-specific data in one place, we avoid hard-coding anything in our playbooks and maintain a structure that is clean, scalable, and CI\/CD-friendly.<\/p>\n\n\n\n<p>This is the exact pattern used by production automation frameworks:<br><strong>machine-specific data lives in inventory, automation logic lives in the playbooks.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Updating the Inventory for Provisioning<\/h2>\n\n\n\n<p>With the VM metadata defined, the inventory only needs to do one thing:<br><strong>declare that this machine exists and assign it to the correct group.<\/strong><\/p>\n\n\n\n<p>That\u2019s it.<br>No IP address, no credentials, no WinRM details \u2014 those all live in <code>host_vars<\/code>, where they belong.<\/p>\n\n\n\n<p>Our inventory structure from Part 1 already includes the groups we need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>windows<\/code><br>\u2514\u2500\u2500 <code>win_clients<\/code><br>\u2514\u2500\u2500 <code>win_servers<\/code><\/li>\n<\/ul>\n\n\n\n<p>For a Windows 10 workstation, we simply add the hostname under <code>win_clients<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>inventories\/lab\/inventory.yml\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>all:\n  children:\n\n    windows:\n      children:\n        win_clients:\n          hosts:\n            HAM01AP001:\n\n        win_servers:\n          hosts: {}\n\n    vcenter:\n      hosts:\n        vcsa.racklab.local:\n<\/code><\/pre>\n\n\n\n<p>This is intentionally minimal.<br>The inventory\u2019s job is only to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>declare the host\u2019s <strong>logical name<\/strong> (HAM01AP001)<\/li>\n\n\n\n<li>place it in the correct <strong>functional group<\/strong> (win_clients)<\/li>\n\n\n\n<li>let Ansible load its matching <code>host_vars\/HAM01AP001.yml<\/code> file<\/li>\n<\/ul>\n\n\n\n<p>Notice what\u2019s missing:<br>There is <strong>no ansible_host<\/strong>, no IP address, no connection settings.<\/p>\n\n\n\n<p>Why?<\/p>\n\n\n\n<p>Because the VM does not exist yet.<\/p>\n\n\n\n<p>In our workflow:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>During provisioning, Ansible talks only to vCenter.<\/li>\n\n\n\n<li>vCenter configures the machine\u2019s static IP through Guest Customization.<\/li>\n\n\n\n<li>After bootstrap enables WinRM, the VM becomes reachable \u2014 using the connection settings stored in <code>host_vars<\/code>.<\/li>\n<\/ol>\n\n\n\n<p>This design keeps the inventory clean, predictable, and easy to scale.<br>If you add 10 new host_vars files tomorrow, you simply list their logical names in <code>win_clients<\/code>, and everything just works.<\/p>\n\n\n\n<p>Playbooks remain generic.<br>host_vars contain all host-specific details.<br>The inventory remains the lightweight index that ties everything together.<\/p>\n\n\n\n<p>Next, we finally get to the interesting part:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Windows 10 Provisioning Playbook<\/h2>\n\n\n\n<p>With the inventory in place and all VM-specific metadata defined in <code>host_vars<\/code>, we can finally build the playbook responsible for creating our Windows 10 virtual machine in vSphere. This is the first point where Ansible interacts with vCenter and actually produces a VM.<\/p>\n\n\n\n<p>The goal of this playbook is simple:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>clone the VM from the correct template<\/li>\n\n\n\n<li>apply the static IP configuration<\/li>\n\n\n\n<li>set the hostname<\/li>\n\n\n\n<li>choose the cluster, datastore, and folder<\/li>\n\n\n\n<li>attach the correct network<\/li>\n\n\n\n<li>and ensure the VM starts successfully<\/li>\n<\/ul>\n\n\n\n<p>All the values come from the host_vars file; the playbook itself contains <strong>zero host-specific values<\/strong>.<br>This is exactly what makes it reusable.<\/p>\n\n\n\n<p>Create the file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>playbooks\/provision\/vmware_windows_client.yml\n<\/code><\/pre>\n\n\n\n<p>Here is the full playbook:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n# Provision Windows 10 client VMs from vSphere templates\n# All VM-specific data comes from host_vars for hosts in the win_clients group.\n\n- name: Provision Windows 10 clients from template\n  hosts: vcenter\n  gather_facts: false\n  collections:\n    - community.vmware\n\n  vars_files:\n    - \"..\/..\/inventories\/lab\/group_vars\/vcenter.yml\"\n\n  tasks:\n    - name: Collect Windows client hosts from inventory\n      set_fact:\n        win_client_hosts: \"{{ groups&#91;'win_clients'] | default(&#91;]) }}\"\n\n    - name: Fail if no Windows client hosts are defined\n      fail:\n        msg: \"No hosts found in win_clients group. Nothing to provision.\"\n      when: win_client_hosts | length == 0\n\n    - name: Clone Windows 10 VM(s) from template with static IP and customization\n      vmware_guest:\n        # vCenter connection\n        hostname: \"{{ vcenter_hostname }}\"\n        username: \"{{ vcenter_username }}\"\n        password: \"{{ vcenter_password }}\"\n        validate_certs: \"{{ vcenter_validate_certs | default(false) }}\"\n\n        # vSphere placement (per host)\n        datacenter: \"{{ hostvars&#91;vm_name].vm_datacenter }}\"\n        cluster: \"{{ hostvars&#91;vm_name].vm_cluster }}\"\n        folder: \"{{ hostvars&#91;vm_name].vm_folder }}\"\n        datastore: \"{{ hostvars&#91;vm_name].vm_datastore }}\"\n\n        # VM identity and template\n        name: \"{{ hostvars&#91;vm_name].vm_name }}\"\n        template: \"{{ hostvars&#91;vm_name].vm_template }}\"\n\n        # Hardware configuration\n        hardware:\n          memory_mb: \"{{ hostvars&#91;vm_name].vm_memory_mb }}\"\n          num_cpus: \"{{ hostvars&#91;vm_name].vm_cpu }}\"\n\n        # Disk configuration\n        disk:\n          - size_gb: \"{{ hostvars&#91;vm_name].vm_disk_gb }}\"\n            type: thin\n            datastore: \"{{ hostvars&#91;vm_name].vm_datastore }}\"\n\n        # Network with static IP (vSphere Guest Customization)\n        networks:\n          - name: \"{{ hostvars&#91;vm_name].vm_network }}\"\n            type: static\n            ip: \"{{ hostvars&#91;vm_name].ip_address }}\"\n            netmask: \"{{ hostvars&#91;vm_name].ip_subnet_mask }}\"\n            gateway:\n              - \"{{ hostvars&#91;vm_name].ip_gateway }}\"\n            dns_servers: \"{{ hostvars&#91;vm_name].dns_servers }}\"\n\n        # Windows guest customization\n        customization:\n          hostname: \"{{ hostvars&#91;vm_name].computer_name }}\"\n          joinworkgroup: \"WORKGROUP\"\n          fullname: \"Automation\"\n          orgname: \"Lab\"\n          timezone: 110\n\n        state: poweredon\n        wait_for_ip_address: false\n        wait_for_customization: true\n\n      loop: \"{{ win_client_hosts }}\"\n      loop_control:\n        loop_var: vm_name\n      register: win10_vms\n\n    - name: Show resulting VM info\n      debug:\n        var: win10_vms\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">What this playbook actually does<\/h3>\n\n\n\n<p>This is a full provisioning workflow using VMware\u2019s SOAP API:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Clones<\/strong> from the template defined in <code>host_vars<\/code><\/li>\n\n\n\n<li>Applies a <strong>static IP address<\/strong> via Guest Customization<\/li>\n\n\n\n<li>Injects the <strong>hostname<\/strong><\/li>\n\n\n\n<li>Attaches the VM to the correct <strong>portgroup<\/strong><\/li>\n\n\n\n<li>Defines CPU, RAM, and disk settings<\/li>\n\n\n\n<li>Places the VM in the correct folder, datastore, and cluster<\/li>\n\n\n\n<li>Starts the VM automatically<\/li>\n\n\n\n<li>Waits until customization is complete<\/li>\n<\/ul>\n\n\n\n<p>And the key part:<\/p>\n\n\n\n<p>All the parameters come from:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>host_vars\/HAM01AP001.yml\n<\/code><\/pre>\n\n\n\n<p>The playbook remains static and generic.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Why is this important?<\/h3>\n\n\n\n<p>In enterprise automation, you never duplicate logic.<br>A single provisioning playbook should be capable of deploying:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>1 Windows 10 workstation<\/li>\n\n\n\n<li>5 Windows Server domain controllers<\/li>\n\n\n\n<li>100 application servers<\/li>\n\n\n\n<li>or 2000 machines in a VDI rollout<\/li>\n<\/ul>\n\n\n\n<p>\u2026simply by adding metadata files in host_vars.<\/p>\n\n\n\n<p>This is the entire philosophy behind Ansible\u2019s inventory architecture.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Running the playbook<\/h3>\n\n\n\n<p>From your Ansible environment (virtualenv active):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\/infra-ansible\nsource .venv\/bin\/activate\n\nansible-playbook -i inventories\/lab\/inventory.yml \\\n  playbooks\/provision\/vmware_windows_client.yml \\\n  --ask-vault-pass<\/code><\/pre>\n\n\n\n<p>If everything is configured correctly, your VM appears in vSphere after 2\u20133 minutes with:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>the correct name<\/li>\n\n\n\n<li>the correct static IP<\/li>\n\n\n\n<li>the correct DNS<\/li>\n\n\n\n<li>the correct gateway<\/li>\n\n\n\n<li>and fully completed guest customization without errors<\/li>\n<\/ul>\n\n\n\n<p>No manual sysprep.<br>No UI clicking.<br>No guessing.<\/p>\n\n\n\n<p>Just clean automation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Bootstrapping WinRM Through VMware Tools<\/h2>\n\n\n\n<p>After provisioning the VM, we have a fully customized Windows 10 machine running in vSphere \u2014 with a hostname, static IP, DNS settings, and VMware Tools operational. However, we still cannot manage it directly from Ansible, because Windows does not come with WinRM enabled by default.<\/p>\n\n\n\n<p>We want Ansible to continue the workflow without any manual steps, so the solution must:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>run <strong>inside<\/strong> the guest OS<\/li>\n\n\n\n<li>without requiring WinRM yet<\/li>\n\n\n\n<li>without requiring credentials over the network<\/li>\n\n\n\n<li>and without logging into the Windows console<\/li>\n<\/ul>\n\n\n\n<p>Luckily, VMware gives us exactly what we need.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Using <code>vmware_vm_shell<\/code><\/h3>\n\n\n\n<p>This module executes commands inside the VM <strong>through VMware Tools<\/strong>, not over WinRM or SSH. That means:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>the VM doesn\u2019t need to be reachable on the network<\/li>\n\n\n\n<li>firewall rules don\u2019t matter yet<\/li>\n\n\n\n<li>WinRM can be activated entirely offline<\/li>\n\n\n\n<li>everything happens through the vSphere API<\/li>\n<\/ul>\n\n\n\n<p>This is the ideal method for preparing Windows machines for remote management.<\/p>\n\n\n\n<p>In our case, we will use PowerShell to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>enable WinRM<\/li>\n\n\n\n<li>configure HTTP listener<\/li>\n\n\n\n<li>open firewall rules<\/li>\n\n\n\n<li>set up basic authentication<\/li>\n\n\n\n<li>restart the WinRM service<\/li>\n<\/ol>\n\n\n\n<p>Once these steps are completed, the VM becomes accessible to Ansible through classic WinRM modules (<code>ansible.windows.win_ping<\/code>, <code>win_command<\/code>, <code>win_feature<\/code>, etc.).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">The WinRM Bootstrap Playbook<\/h3>\n\n\n\n<p>Create the file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>playbooks\/provision\/bootstrap_winrm.yml\n<\/code><\/pre>\n\n\n\n<p>And add the following content:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n# Enable WinRM inside Windows VMs using VMware Tools\n# All VM-specific data is taken from host_vars for hosts in win_clients.\n\n- name: Bootstrap WinRM on Windows VM via VMware Tools\n  hosts: vcenter\n  gather_facts: false\n  collections:\n    - community.vmware\n\n  vars_files:\n    - \"..\/..\/inventories\/lab\/group_vars\/vcenter.yml\"\n\n  tasks:\n\n    - name: Collect Windows client hosts from inventory\n      set_fact:\n        win_client_hosts: \"{{ groups&#91;'win_clients'] | default(&#91;]) }}\"\n\n    - name: Fail if no Windows client hosts are defined\n      fail:\n        msg: \"No hosts found in win_clients group. Nothing to bootstrap.\"\n      when: win_client_hosts | length == 0\n\n    - name: Enable WinRM and open firewall via VMware Tools\n      vmware_vm_shell:\n        hostname: \"{{ vcenter_hostname }}\"\n        username: \"{{ vcenter_username }}\"\n        password: \"{{ vcenter_password }}\"\n        validate_certs: \"{{ vcenter_validate_certs | default(false) }}\"\n\n        vm_id: \"{{ hostvars&#91;vm_name].vm_name }}\"\n        vm_username: \"{{ hostvars&#91;vm_name].windows_admin_user }}\"\n        vm_password: \"{{ hostvars&#91;vm_name].win_local_admin_password }}\"\n\n        vm_shell: \"C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\"\n        vm_shell_args: &gt;\n          -NoProfile -NonInteractive -ExecutionPolicy Bypass\n          -Command \"winrm quickconfig -force;\n                    winrm set winrm\/config\/service\/auth '@{Basic=\\\"true\\\"}';\n                    Set-Item WSMan:\\\\localhost\\\\service\\\\AllowUnencrypted -Value true;\n                    netsh advfirewall firewall add rule name='WinRM 5985' dir=in action=allow protocol=TCP localport=5985;\n                    Restart-Service winrm\"\n\n        wait_for_process: true\n      loop: \"{{ win_client_hosts }}\"\n      loop_control:\n        loop_var: vm_name\n      register: winrm_bootstrap\n\n    - name: Show vmware_vm_shell result\n      debug:\n        var: winrm_bootstrap<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">What this playbook actually does<\/h3>\n\n\n\n<p>When executed, Ansible connects to vCenter \u2014 not to Windows.<br>vCenter then uses VMware Tools to run a PowerShell command <strong>inside<\/strong> the VM:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>WinRM is enabled<\/li>\n\n\n\n<li>Basic auth is allowed<\/li>\n\n\n\n<li>The plaintext listener is permitted (lab-friendly)<\/li>\n\n\n\n<li>Firewall rules are created<\/li>\n\n\n\n<li>WinRM service is restarted<\/li>\n<\/ul>\n\n\n\n<p>Once finished, the VM is ready for remote management.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\/infra-ansible\nsource .venv\/bin\/activate\n\nansible-playbook -i inventories\/lab\/inventory.yml \\\n  playbooks\/provision\/bootstrap_winrm.yml \\\n  --ask-vault-pass<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Testing WinRM Connectivity<\/h3>\n\n\n\n<p>After the bootstrap is complete, you can test it directly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible -i inventories\/lab\/inventory.yml HAM01AP001 -m win_ping --ask-vault-pass<\/code><\/pre>\n\n\n\n<p>If everything is configured correctly, you should see:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HAM01AP001 | SUCCESS =&gt; {\n    \"changed\": false,\n    \"ping\": \"pong\"\n}<\/code><\/pre>\n\n\n\n<p>This confirms that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>the VM is reachable<\/li>\n\n\n\n<li>WinRM is functional<\/li>\n\n\n\n<li>credentials from host_vars are correct<\/li>\n\n\n\n<li>Ansible is ready for post-provisioning steps<\/li>\n<\/ul>\n\n\n\n<p>At this point, the machine is fully manageable through Ansible.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Why this approach is ideal<\/h3>\n\n\n\n<p>This method avoids:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>logging into the VM<\/li>\n\n\n\n<li>running scripts manually<\/li>\n\n\n\n<li>enabling WinRM via ISO\/USB<\/li>\n\n\n\n<li>temporary network hacks<\/li>\n\n\n\n<li>or depending on DHCP connectivity<\/li>\n<\/ul>\n\n\n\n<p>Everything is handled cleanly and remotely through vSphere \u2014 exactly how enterprises automate Windows deployments at scale.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Applying a Baseline Configuration to Windows 10<\/h2>\n\n\n\n<p>Now that WinRM is active and the VM is fully reachable from Ansible, we can finally start applying real configuration. This is where Windows stops being a black box and becomes an automated, predictable part of our infrastructure.<\/p>\n\n\n\n<p>In this section, we\u2019ll walk through a small but representative baseline:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>creating a directory on C:\\<\/li>\n\n\n\n<li>installing a Windows optional feature<\/li>\n\n\n\n<li>(optionally) preparing for domain join later<\/li>\n<\/ul>\n\n\n\n<p>This keeps the example simple, but demonstrates the entire end-to-end flow:<\/p>\n\n\n\n<p>Ansible \u2192 WinRM \u2192 Windows \u2192 Expected Result<\/p>\n\n\n\n<p>If this works, anything else you add later (software installs, registry changes, DSC, domain joins, compliance enforcement) will also work.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">The Baseline Playbook<\/h3>\n\n\n\n<p>Create the file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>playbooks\/configure\/win10_baseline.yml<\/code><\/pre>\n\n\n\n<p>And add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n# Baseline configuration for Windows 10 clients\n\n- name: Apply baseline configuration to Windows 10 client\n  hosts: win_clients\n  gather_facts: false\n  collections:\n    - ansible.windows\n  vars_files:\n    - \"..\/..\/inventories\/lab\/group_vars\/all\/vault.yml\"\n\n  tasks:\n\n    - name: Verify WinRM connectivity\n      win_ping:\n\n    - name: \"Create tools directory on C:\"\n      win_file:\n        path: \"C:\\\\Tools\"\n        state: directory\n\n    - name: \"Create marker file in C:\\\\Tools with timestamp\"\n      win_shell: |\n        $ts = Get-Date -Format \"yyyy-MM-dd HH:mm:ss\"\n        \"Baseline applied via Ansible on $ts\" | Out-File -FilePath \"C:\\Tools\\baseline.txt\" -Encoding UTF8\n\n    - name: \"Ensure WinRM service is running and set to Automatic\"\n      win_service:\n        name: WinRM\n        start_mode: auto\n        state: started\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">What This Playbook Demonstrates<\/h3>\n\n\n\n<p>This baseline might look simple, but it proves the most important things:<\/p>\n\n\n\n<p><strong>\u2022 Ansible can reach the VM over WinRM<\/strong><br>Connectivity works, credentials work, and firewall\/WinRM configuration is solid.<\/p>\n\n\n\n<p><strong>\u2022 Windows responds reliably to automation<\/strong><br>Creating directories, writing files, running PowerShell \u2014 all executed cleanly.<\/p>\n\n\n\n<p><strong>\u2022 The VM is now a fully manageable node<\/strong><br>From here you can layer anything:<\/p>\n\n\n\n<p>\u2013 install applications<br>\u2013 tune Windows settings<br>\u2013 join the VM to Active Directory<br>\u2013 enforce baselines and compliance<br>\u2013 deploy scripts or agents<br>\u2013 prepare golden images<\/p>\n\n\n\n<p>And all future work uses the same structure \u2014 host_vars \u2192 playbook \u2192 results.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Running the Baseline<\/h3>\n\n\n\n<p>From the project root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\/infra-ansible\nsource .venv\/bin\/activate\n\nansible-playbook -i inventories\/lab\/inventory.yml playbooks\/configure\/win10_baseline.yml --ask-vault-pass<\/code><\/pre>\n\n\n\n<p>Here\u2019s what you should expect when running it:<\/p>\n\n\n\n<p>\u2022 The <em>WinRM connectivity check<\/em> succeeds (<code>ok<\/code>).<br>\u2022 The <code>C:\\Tools<\/code> directory is created (<code>changed<\/code> on the first run).<br>\u2022 A <code>baseline.txt<\/code> marker file appears inside <code>C:\\Tools<\/code> (<code>changed<\/code>).<br>\u2022 The WinRM service is confirmed to be running and set to Automatic (<code>ok<\/code> or <code>changed<\/code>).<\/p>\n\n\n\n<p>The exact formatting may vary depending on your Ansible callback plugin,<br>but the key point is that the play finishes cleanly and the expected files show up on the VM.<\/p>\n\n\n\n<p>From here, expanding the baseline is straightforward \u2014 installing applications, joining the domain, applying security policies, running scripts, or building consistent Windows images all follow the same structure.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Joining Windows 10 to Active Directory<\/h2>\n\n\n\n<p>Once our Windows 10 VM is provisioned, bootstrapped, and reachable over WinRM, the next natural step is to integrate it into the domain. In an enterprise environment, this is where a \u201cmachine\u201d officially becomes a \u201cworkstation\u201d. Without domain join, you\u2019re practically stuck in standalone-land \u2014 no policies, no central credentials, no management.<\/p>\n\n\n\n<p>Fortunately, Ansible makes this step predictable and repeatable.<\/p>\n\n\n\n<p>In this chapter we\u2019ll:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>extend the <code>host_vars<\/code> with domain credentials<\/li>\n\n\n\n<li>create a dedicated playbook for domain join<\/li>\n\n\n\n<li>reboot automatically after completion<\/li>\n\n\n\n<li>verify that the join succeeded<\/li>\n<\/ul>\n\n\n\n<p>This completes the workflow from raw VM \u2192 fully integrated domain member.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Extending host_vars With Domain Credentials<\/h2>\n\n\n\n<p>Add the following to your VM\u2019s variable file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>inventories\/lab\/host_vars\/HAM01AP001.yml<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># Active Directory domain to join\ndomain_name: \"corp.local\"\ndomain_admin_user: \"CORP\\\\Administrator\"\n\n# Password stored securely in Vault\ndomain_admin_password: \"{{ vault_domain_join_password }}\"<\/code><\/pre>\n\n\n\n<p>These settings are host-specific, which is why they belong in host_vars.<\/p>\n\n\n\n<p>All sensitive values continue to live inside Vault (<code>vault_domain_join_password<\/code>).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The AD Join Playbook<\/h2>\n\n\n\n<p>Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>playbooks\/configure\/win10_domain_join.yml<\/code><\/pre>\n\n\n\n<p>And add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n# Join Windows 10 client to Active Directory\n\n- name: Join Windows 10 client to Active Directory\n  hosts: win_clients\n  gather_facts: false\n  collections:\n    - microsoft.ad\n    - ansible.windows\n  vars_files:\n    - \"..\/..\/inventories\/lab\/group_vars\/all\/vault.yml\"\n\n  tasks:\n\n    - name: Verify WinRM connectivity before attempting domain join\n      win_ping:\n\n    - name: \"Ensure C:\\\\Tools exists (safety check)\"\n      win_file:\n        path: \"C:\\\\Tools\"\n        state: directory\n\n    - name: Join machine to domain\n      microsoft.ad.membership:\n        hostname: \"{{ computer_name }}\"\n        dns_domain_name: \"{{ domain_name }}\"\n        domain_admin_user: \"{{ domain_admin_user }}\"\n        domain_admin_password: \"{{ domain_admin_password }}\"\n        state: domain\n        reboot: false\n      register: domain_join_result\n\n    - name: Reboot if domain join requires it\n      win_reboot:\n        msg: \"Rebooting to complete domain join\"\n        timeout: 600\n      when: domain_join_result.reboot_required | default(false)<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Why This Works Reliably<\/h3>\n\n\n\n<p><strong>1. microsoft.ad.membership is the replacement for the deprecated win_domain_membership module.<\/strong><br>It\u2019s fully maintained and works on Windows 10, 11, and all supported Windows Server versions.<\/p>\n\n\n\n<p><strong>2. Domain join logic is fully idempotent.<\/strong><br>If the machine is already joined, the playbook reports <code>ok<\/code> instead of triggering a join.<\/p>\n\n\n\n<p><strong>3. Automatic reboot handling.<\/strong><br>Windows almost always needs a reboot after joining a domain \u2014 Ansible detects this and reboots only when necessary.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Running the AD Join<\/h3>\n\n\n\n<p>From the project root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\/infra-ansible\nsource .venv\/bin\/activate\n\nansible-playbook -i inventories\/lab\/inventory.yml playbooks\/configure\/win10_domain_join.yml --ask-vault-pass<\/code><\/pre>\n\n\n\n<p>Expected output:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>TASK [Verify WinRM connectivity before attempting domain join] \u2192 ok: [HAM01AP001]<\/li>\n\n\n\n<li>TASK [Ensure C:\\Tools exists (safety check)] \u2192 ok: [HAM01AP001]<\/li>\n\n\n\n<li>TASK [Join machine to domain] \u2192 changed: [HAM01AP001]<\/li>\n\n\n\n<li>TASK [Reboot if domain join requires it] \u2192 changed: [HAM01AP001] (if a reboot is needed)<\/li>\n<\/ul>\n\n\n\n<p>After reboot, you should be able to log in with a domain account (e.g., CORP\\User).<\/p>\n\n\n\n<p>This completes the transformation from:<\/p>\n\n\n\n<p><strong>freshly deployed VM \u2192 domain-integrated workstation<\/strong><br>with no manual interaction.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Bringing It All Together: The Full Windows 10 Provisioning Runbook<\/h2>\n\n\n\n<p>Up to this point in our automation journey, we built everything in small, clean blocks:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>we provisioned a Windows 10 VM from a vSphere template<\/li>\n\n\n\n<li>we bootstrapped WinRM using VMware Tools<\/li>\n\n\n\n<li>we applied a baseline configuration<\/li>\n\n\n\n<li>we joined the machine to Active Directory<\/li>\n<\/ul>\n\n\n\n<p>Each piece worked individually \u2014 great for development, testing, and debugging.<\/p>\n\n\n\n<p>But in real infrastructure, nobody wants to run four playbooks by hand.<br>What we want is the DevOps equivalent of a <em>lightsaber<\/em>: press one button, and the whole workflow slices through everything from zero to a fully configured domain workstation.<\/p>\n\n\n\n<p>So let\u2019s build exactly that.<\/p>\n\n\n\n<p>A <strong>runbook<\/strong>.<\/p>\n\n\n\n<p>Think of it as the orchestrator: it doesn\u2019t do the work itself, but it knows which sub-playbooks to call, in what order, and under what conditions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Why a Runbook?<\/h3>\n\n\n\n<p>A runbook gives you:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>one entry point for your entire provisioning workflow<\/li>\n\n\n\n<li>a clean separation between logic (runbook) and implementation (playbooks)<\/li>\n\n\n\n<li>the ability to drop it into CI\/CD with zero changes<\/li>\n\n\n\n<li>consistency: every VM goes through the same steps, the same way, every time<\/li>\n\n\n\n<li>easier debugging \u2014 each step is still its own playbook<\/li>\n<\/ul>\n\n\n\n<p>It\u2019s how large automation frameworks stay maintainable over time.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">The Windows 10 Full Provisioning Runbook<\/h3>\n\n\n\n<p>Create the file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>playbooks\/runbooks\/win10_full_deploy.yml<\/code><\/pre>\n\n\n\n<p>And add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n# Full provisioning runbook for Windows 10 clients\n# Steps:\n#   1. Provision VM in vSphere\n#   2. Bootstrap WinRM via VMware Tools\n#   3. Apply baseline configuration\n#   4. Join to Active Directory\n\n- name: Provision Windows 10 VM\n  import_playbook: \"..\/provision\/vmware_windows_client.yml\"\n\n- name: Bootstrap WinRM on newly deployed Windows VM\n  import_playbook: \"..\/provision\/bootstrap_winrm.yml\"\n\n- name: Apply Windows 10 baseline configuration\n  import_playbook: \"..\/configure\/win10_baseline.yml\"\n\n- name: Join Windows 10 to Active Directory\n  import_playbook: \"..\/configure\/win10_domain_join.yml\"<\/code><\/pre>\n\n\n\n<p>Yes \u2014 that\u2019s everything.<br>Simple on the outside, powerful on the inside.<\/p>\n\n\n\n<p>Each component stays testable and reusable, but when combined, they form a complete, end-to-end Windows provisioning pipeline.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Running the Runbook<\/h3>\n\n\n\n<p>From the project root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd ~\/infra-ansible\nsource .venv\/bin\/activate\n\nansible-playbook -i inventories\/lab\/inventory.yml playbooks\/runbooks\/win10_full_deploy.yml --ask-vault-pass<\/code><\/pre>\n\n\n\n<p>What happens next:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A brand-new VM is created in vSphere<\/li>\n\n\n\n<li>VMware Tools executes a PowerShell script to activate WinRM<\/li>\n\n\n\n<li>Ansible connects via WinRM and applies baseline configuration<\/li>\n\n\n\n<li>The VM joins the domain and reboots<\/li>\n\n\n\n<li>You log in with a domain account, and everything \u201cjust works\u201d<\/li>\n<\/ol>\n\n\n\n<p>This is the moment where the project stops being just a bunch of playbooks\u2026<br>and becomes real operational automation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrap-Up: Windows Provisioning Fully Automated<\/h2>\n\n\n\n<p>In this part, we completed the entire lifecycle of deploying a Windows 10 VM using Ansible and VMware:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>cloned the VM from a vSphere template using OS customization<\/li>\n\n\n\n<li>bootstrapped WinRM remotely through VMware Tools<\/li>\n\n\n\n<li>verified full remote execution via Ansible<\/li>\n\n\n\n<li>applied a baseline configuration<\/li>\n\n\n\n<li>joined the machine to Active Directory<\/li>\n\n\n\n<li>confirmed that everything works end-to-end<\/li>\n<\/ul>\n\n\n\n<p>At this point, Windows provisioning is no longer a manual task \u2014 it\u2019s fully automated, reproducible, and ready to scale.<\/p>\n\n\n\n<p>So what\u2019s next?<\/p>\n\n\n\n<p>In the next article, we\u2019ll extend the framework beyond Windows and start <strong>provisioning and configuring Linux systems<\/strong> (Debian and RedHat). We\u2019ll look at SSH key injection, cloud-init-like setups, OS-specific roles, and how to reuse the same repository structure for multiple platforms.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the first part of this series, we built the foundation of our automation framework: a clean directory structure, proper inventories, OS-specific variable separation, secure Vault usage, and the vCenter settings that glue everything together. Think of it as laying out all tools on the workbench before touching the actual machine. Now comes the fun [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4707,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_FSMCFIC_featured_image_caption":"","_FSMCFIC_featured_image_nocaption":"","_FSMCFIC_featured_image_hide":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-4667","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Provisioning a Windows 10 Client from a vSphere Template (2nd part) - IT-REACT<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Provisioning a Windows 10 Client from a vSphere Template (2nd part) - IT-REACT\" \/>\n<meta property=\"og:description\" content=\"In the first part of this series, we built the foundation of our automation framework: a clean directory structure, proper inventories, OS-specific variable separation, secure Vault usage, and the vCenter settings that glue everything together. Think of it as laying out all tools on the workbench before touching the actual machine. Now comes the fun [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/\" \/>\n<meta property=\"og:site_name\" content=\"IT-REACT\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-28T10:18:41+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-28T10:20:10+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"540\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Ioan Penu\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ioan Penu\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/\"},\"author\":{\"name\":\"Ioan Penu\",\"@id\":\"https:\\\/\\\/www.it-react.com\\\/#\\\/schema\\\/person\\\/bf08cffeb4b02ee6baff5d56ab17c8f0\"},\"headline\":\"Provisioning a Windows 10 Client from a vSphere Template (2nd part)\",\"datePublished\":\"2025-11-28T10:18:41+00:00\",\"dateModified\":\"2025-11-28T10:20:10+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/\"},\"wordCount\":2518,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/#\\\/schema\\\/person\\\/bf08cffeb4b02ee6baff5d56ab17c8f0\"},\"image\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.it-react.com\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/\",\"url\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/\",\"name\":\"Provisioning a Windows 10 Client from a vSphere Template (2nd part) - IT-REACT\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.it-react.com\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg\",\"datePublished\":\"2025-11-28T10:18:41+00:00\",\"dateModified\":\"2025-11-28T10:20:10+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.it-react.com\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg\",\"contentUrl\":\"https:\\\/\\\/www.it-react.com\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg\",\"width\":1024,\"height\":540,\"caption\":\"Photo by Marek Piwnicki on Unsplash\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.it-react.com\\\/index.php\\\/2025\\\/11\\\/28\\\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.it-react.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Provisioning a Windows 10 Client from a vSphere Template (2nd part)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.it-react.com\\\/#website\",\"url\":\"https:\\\/\\\/www.it-react.com\\\/\",\"name\":\"it-react\",\"description\":\"Ctrl\u2022Alt\u2022Automate\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.it-react.com\\\/#\\\/schema\\\/person\\\/bf08cffeb4b02ee6baff5d56ab17c8f0\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.it-react.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.it-react.com\\\/#\\\/schema\\\/person\\\/bf08cffeb4b02ee6baff5d56ab17c8f0\",\"name\":\"Ioan Penu\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2a2a1b6be0f322a113eea11669895227e284c6091424d65be6c3c706c2822975?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2a2a1b6be0f322a113eea11669895227e284c6091424d65be6c3c706c2822975?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2a2a1b6be0f322a113eea11669895227e284c6091424d65be6c3c706c2822975?s=96&d=mm&r=g\",\"caption\":\"Ioan Penu\"},\"logo\":{\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2a2a1b6be0f322a113eea11669895227e284c6091424d65be6c3c706c2822975?s=96&d=mm&r=g\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Provisioning a Windows 10 Client from a vSphere Template (2nd part) - IT-REACT","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/","og_locale":"en_US","og_type":"article","og_title":"Provisioning a Windows 10 Client from a vSphere Template (2nd part) - IT-REACT","og_description":"In the first part of this series, we built the foundation of our automation framework: a clean directory structure, proper inventories, OS-specific variable separation, secure Vault usage, and the vCenter settings that glue everything together. Think of it as laying out all tools on the workbench before touching the actual machine. Now comes the fun [&hellip;]","og_url":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/","og_site_name":"IT-REACT","article_published_time":"2025-11-28T10:18:41+00:00","article_modified_time":"2025-11-28T10:20:10+00:00","og_image":[{"width":1024,"height":540,"url":"https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg","type":"image\/jpeg"}],"author":"Ioan Penu","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Ioan Penu","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/#article","isPartOf":{"@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/"},"author":{"name":"Ioan Penu","@id":"https:\/\/www.it-react.com\/#\/schema\/person\/bf08cffeb4b02ee6baff5d56ab17c8f0"},"headline":"Provisioning a Windows 10 Client from a vSphere Template (2nd part)","datePublished":"2025-11-28T10:18:41+00:00","dateModified":"2025-11-28T10:20:10+00:00","mainEntityOfPage":{"@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/"},"wordCount":2518,"commentCount":0,"publisher":{"@id":"https:\/\/www.it-react.com\/#\/schema\/person\/bf08cffeb4b02ee6baff5d56ab17c8f0"},"image":{"@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/#primaryimage"},"thumbnailUrl":"https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/","url":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/","name":"Provisioning a Windows 10 Client from a vSphere Template (2nd part) - IT-REACT","isPartOf":{"@id":"https:\/\/www.it-react.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/#primaryimage"},"image":{"@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/#primaryimage"},"thumbnailUrl":"https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg","datePublished":"2025-11-28T10:18:41+00:00","dateModified":"2025-11-28T10:20:10+00:00","breadcrumb":{"@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/#primaryimage","url":"https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg","contentUrl":"https:\/\/www.it-react.com\/wp-content\/uploads\/2025\/11\/marek-piwnicki-4kyb_KApK1E-unsplash-e1764323839352.jpg","width":1024,"height":540,"caption":"Photo by Marek Piwnicki on Unsplash"},{"@type":"BreadcrumbList","@id":"https:\/\/www.it-react.com\/index.php\/2025\/11\/28\/provisioning-a-windows-10-client-from-a-vsphere-template-2nd-part\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.it-react.com\/"},{"@type":"ListItem","position":2,"name":"Provisioning a Windows 10 Client from a vSphere Template (2nd part)"}]},{"@type":"WebSite","@id":"https:\/\/www.it-react.com\/#website","url":"https:\/\/www.it-react.com\/","name":"it-react","description":"Ctrl\u2022Alt\u2022Automate","publisher":{"@id":"https:\/\/www.it-react.com\/#\/schema\/person\/bf08cffeb4b02ee6baff5d56ab17c8f0"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.it-react.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.it-react.com\/#\/schema\/person\/bf08cffeb4b02ee6baff5d56ab17c8f0","name":"Ioan Penu","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/2a2a1b6be0f322a113eea11669895227e284c6091424d65be6c3c706c2822975?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/2a2a1b6be0f322a113eea11669895227e284c6091424d65be6c3c706c2822975?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/2a2a1b6be0f322a113eea11669895227e284c6091424d65be6c3c706c2822975?s=96&d=mm&r=g","caption":"Ioan Penu"},"logo":{"@id":"https:\/\/secure.gravatar.com\/avatar\/2a2a1b6be0f322a113eea11669895227e284c6091424d65be6c3c706c2822975?s=96&d=mm&r=g"}}]}},"_links":{"self":[{"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/posts\/4667","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/comments?post=4667"}],"version-history":[{"count":26,"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/posts\/4667\/revisions"}],"predecessor-version":[{"id":4714,"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/posts\/4667\/revisions\/4714"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/media\/4707"}],"wp:attachment":[{"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/media?parent=4667"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/categories?post=4667"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.it-react.com\/index.php\/wp-json\/wp\/v2\/tags?post=4667"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}