diff --git a/README.md b/README.md
index 23247c5..e9f120f 100644
--- a/README.md
+++ b/README.md
@@ -14,37 +14,74 @@ This project demonstrates automated Windows Server VM management including:
## Quick Start
+### Development Environment
+
```bash
+# Activate Ansible virtual environment
+source ~/.venv/ansible/bin/activate
+
# Install required collections
ansible-galaxy collection install -r collections/requirements.yml
-# Run VM provisioning
-ansible-playbook playbooks/provision-vm.yml -e vm_name=DEMO-WEB01
+# Verify Hyper-V connectivity
+ansible hyperv -m ansible.windows.win_ping
+```
+### Provision a New VM
+
+```bash
+# Using ansible-navigator (recommended)
+ansible-navigator run playbooks/provision-vm.yml \
+ --execution-environment-image aap.toal.ca/ee-demo \
+ --extra-vars "vm_name=WEB01 vm_ip_address=192.168.1.101"
+
+# Or with ansible-playbook
+ansible-playbook playbooks/provision-vm.yml \
+ -e vm_name=WEB01 \
+ -e vm_ip_address=192.168.1.101 \
+ -e vm_cpu_count=4 \
+ -e vm_memory_gb=8
+```
+
+**See [playbooks/README-provision.md](playbooks/README-provision.md) for detailed provisioning guide**
+
+### Manage Existing VMs
+
+```bash
# Patch management
ansible-playbook playbooks/patch-vms.yml --limit windows_servers
# Install IIS demo application
ansible-playbook playbooks/install-iis.yml --limit web_servers
+
+# Update CMDB
+ansible-playbook playbooks/sync-cmdb.yml
```
## Project Structure
```
.
-├── ansible.cfg # Ansible configuration
-├── playbooks/ # Playbooks by use case
-│ ├── provision-vm.yml
-│ ├── patch-vms.yml
-│ ├── install-iis.yml
-│ └── sync-cmdb.yml
-├── roles/ # Custom roles
-├── group_vars/ # Group variables
-├── host_vars/ # Host-specific variables
-└── collections/ # Ansible collections
- └── requirements.yml
+├── ansible.cfg # Ansible config → uses toallab-inventory
+├── playbooks/ # Automation workflows
+│ ├── provision-vm.yml # VM provisioning with autounattend
+│ ├── create-autounattend-iso.yml # Helper for ISO creation
+│ ├── patch-vms.yml # Windows Update automation
+│ ├── install-iis.yml # IIS deployment demo
+│ ├── sync-cmdb.yml # ServiceNow integration
+│ └── README-provision.md # Detailed provisioning guide
+├── templates/ # Jinja2 templates
+│ └── autounattend.xml.j2 # Windows unattended install
+├── roles/ # Custom roles (future)
+└── collections/
+ └── requirements.yml # Required collections
```
+**Inventory Location**: `/home/ptoal/Dev/inventories/toallab-inventory`
+- `group_vars/hyperv/` - Hyper-V host configuration
+- `group_vars/windows_servers/` - Windows Server defaults
+- `host_vars/hyperv1.lan.toal.ca/` - Hypervisor settings
+
## Prerequisites
- Ansible Automation Platform 2.x
@@ -53,6 +90,58 @@ ansible-playbook playbooks/install-iis.yml --limit web_servers
- ServiceNow instance (for CMDB integration)
- Active Directory domain (for authentication)
+## Key Features
+
+### VM Provisioning
+- **Automated Installation**: Uses autounattend.xml for unattended Windows setup
+- **Flexible Configuration**: CPU, memory, disk size configurable via variables
+- **Network Setup**: Static IP or DHCP configuration
+- **WinRM Ready**: Automatically configured for Ansible management
+
+### Idempotent Operations
+- All playbooks can be run multiple times safely
+- Check mode support for validation
+- Proper state management
+
+### AAP Integration
+- Job template surveys for user-friendly VM creation
+- Webhook support for GitOps workflows
+- Credential management for secure operations
+
+## Hypervisor
+
+**Host**: hyperv1.lan.toal.ca (192.168.1.182)
+**Connection**: WinRM over HTTP (NTLM auth)
+**Default Storage**: D:\VMs
+**Default ISO Path**: D:\ISOs
+
## Documentation
-See [CLAUDE.md](CLAUDE.md) for detailed architectural documentation and development guidance.
+- [CLAUDE.md](CLAUDE.md) - Architecture and development guidelines
+- [playbooks/README-provision.md](playbooks/README-provision.md) - VM provisioning guide
+- [templates/autounattend.xml.j2](templates/autounattend.xml.j2) - Windows unattended install template
+
+## Common Tasks
+
+### Provision a VM
+```bash
+ansible-playbook playbooks/provision-vm.yml -e vm_name=WEB01 -e vm_ip_address=192.168.1.101
+```
+
+### Add VM to Inventory
+```bash
+# Edit /home/ptoal/Dev/inventories/toallab-inventory/static.yml
+# Add under web_servers/app_servers/db_servers:
+ WEB01:
+ ansible_host: 192.168.1.101
+```
+
+### Configure VM
+```bash
+ansible-playbook playbooks/install-iis.yml --limit WEB01
+```
+
+### Patch VMs
+```bash
+ansible-playbook playbooks/patch-vms.yml --limit windows_servers
+```
diff --git a/ansible.cfg b/ansible.cfg
index bc900ca..404c949 100644
--- a/ansible.cfg
+++ b/ansible.cfg
@@ -7,18 +7,5 @@ collections_path = collections
interpreter_python = auto_silent
timeout = 30
-# Windows-specific settings
-ansible_connection = winrm
-ansible_winrm_transport = kerberos
-ansible_winrm_server_cert_validation = ignore
-
-# Logging
-log_path = ./ansible.log
-
-[privilege_escalation]
-become = True
-become_method = runas
-become_user = Administrator
-
[ssh_connection]
pipelining = True
diff --git a/playbooks/README-provision.md b/playbooks/README-provision.md
new file mode 100644
index 0000000..d17e395
--- /dev/null
+++ b/playbooks/README-provision.md
@@ -0,0 +1,345 @@
+# VM Provisioning Workflow
+
+This directory contains playbooks for provisioning Windows Server VMs on Hyper-V with automated installation.
+
+## Quick Start
+
+### 1. Provision a new VM
+
+```bash
+# Activate virtual environment
+source ~/.venv/ansible/bin/activate
+
+# Run with ansible-navigator (recommended for development)
+ansible-navigator run playbooks/provision-vm.yml \
+ --execution-environment-image aap.toal.ca/ee-demo \
+ --extra-vars "vm_name=WEB01 vm_ip_address=192.168.1.101"
+
+# Or run directly with ansible-playbook
+ansible-playbook playbooks/provision-vm.yml \
+ -e vm_name=WEB01 \
+ -e vm_ip_address=192.168.1.101 \
+ -e vm_cpu_count=4 \
+ -e vm_memory_gb=8
+```
+
+### 2. Monitor Installation
+
+The playbook will:
+1. ✓ Create the VM with specified resources
+2. ✓ Generate autounattend.xml for unattended installation
+3. ✓ Attach Windows Server ISO
+4. ✓ Start the VM
+5. ⏸ Pause for manual verification of installation
+
+**Manual Steps:**
+- Watch VM through Hyper-V Manager or connect via console
+- Verify Windows installation progresses automatically
+- Wait for system to reach login screen (15-30 minutes)
+- Verify WinRM is configured (check if port 5985 is listening)
+
+### 3. Verify VM
+
+```bash
+# Test WinRM connectivity
+ansible WEB01 -i "192.168.1.101," -m ansible.windows.win_ping
+
+# Or run verification tag
+ansible-playbook playbooks/provision-vm.yml -e vm_name=WEB01 --tags verify
+```
+
+### 4. Add to Inventory
+
+```bash
+# Edit the inventory file
+vi /home/ptoal/Dev/inventories/toallab-inventory/static.yml
+
+# Add under appropriate group (web_servers, app_servers, db_servers):
+ web_servers:
+ hosts:
+ WEB01:
+ ansible_host: 192.168.1.101
+```
+
+### 5. Run Baseline Configuration
+
+```bash
+# Apply Windows baseline configuration (future)
+ansible-playbook playbooks/windows-baseline.yml --limit WEB01
+
+# Deploy applications
+ansible-playbook playbooks/install-iis.yml --limit WEB01
+```
+
+## Variables
+
+### Required Variables
+
+| Variable | Description | Example |
+|----------|-------------|---------|
+| `vm_name` | Name of the VM to create | `WEB01` |
+
+### Optional Variables
+
+| Variable | Description | Default | Example |
+|----------|-------------|---------|---------|
+| `vm_ip_address` | Static IP address | DHCP | `192.168.1.101` |
+| `vm_cpu_count` | Number of CPU cores | `2` | `4` |
+| `vm_memory_gb` | Memory in GB | `4` | `8` |
+| `vm_disk_size_gb` | Disk size in GB | `60` | `100` |
+| `vm_admin_password` | Initial admin password | `P@ssw0rd123!` | `SecureP@ss!` |
+| `vm_gateway` | Default gateway | `192.168.1.1` | `192.168.1.1` |
+| `vm_subnet_prefix` | Subnet prefix length | `24` | `24` |
+| `dns_servers` | DNS server list | From group_vars | `['8.8.8.8']` |
+
+### Variables from Group Vars
+
+The following are automatically loaded from group_vars:
+
+- `default_vm_cpu_count`, `default_vm_memory_gb`, `default_vm_disk_size_gb`
+- `default_vm_switch` - Hyper-V virtual switch name
+- `vm_storage_path` - Base path for VM files
+- `iso_storage_path` - Path to ISO files
+- `windows_server_iso` - Path to Windows Server ISO
+- `dns_servers` - Default DNS servers
+- `timezone` - Windows timezone setting
+
+## Playbook Tags
+
+Run specific parts of the playbook using tags:
+
+```bash
+# Only create VM configuration (skip installation)
+ansible-playbook provision-vm.yml -e vm_name=WEB01 --tags create
+
+# Only install/start VM
+ansible-playbook provision-vm.yml -e vm_name=WEB01 --tags install
+
+# Only verify VM status
+ansible-playbook provision-vm.yml -e vm_name=WEB01 --tags verify
+```
+
+## AutoUnattend.xml
+
+The playbook generates an `autounattend.xml` file that automates Windows installation with:
+
+- **Partitioning**: Automatic disk partitioning (500MB system, rest for Windows)
+- **Locale**: en-US language and keyboard
+- **Computer Name**: Set to `vm_name`
+- **Timezone**: From `timezone` variable
+- **Administrator Password**: From `vm_admin_password`
+- **Network**: Static IP if `vm_ip_address` is provided
+- **RDP**: Enabled with firewall rules
+- **WinRM**: Enabled with firewall rules
+- **Firewall**: Initially disabled for setup
+
+### Using AutoUnattend.xml
+
+Windows Setup will automatically use `autounattend.xml` if it's found in one of these locations:
+
+1. **Root of installation media** (requires rebuilding ISO)
+2. **Root of removable media** (floppy, USB, second CD drive)
+3. **Mounted as additional DVD drive** (easiest for Hyper-V)
+
+#### Method 1: Second DVD Drive (Recommended)
+
+```bash
+# Run helper playbook to create and mount autounattend ISO
+ansible-playbook playbooks/create-autounattend-iso.yml -e vm_name=WEB01
+```
+
+#### Method 2: Manual ISO Creation
+
+```powershell
+# On Hyper-V host
+# 1. Create ISO with autounattend.xml in root
+# Use oscdimg (from Windows ADK) or ImgBurn
+
+# 2. Mount as second DVD drive
+Add-VMDvdDrive -VMName "WEB01" -Path "D:\VMs\WEB01\autounattend.iso"
+```
+
+#### Method 3: Custom Windows ISO
+
+Rebuild Windows ISO with autounattend.xml embedded:
+- Extract Windows ISO
+- Copy autounattend.xml to root
+- Rebuild ISO with oscdimg
+- Use custom ISO for installation
+
+## Ansible Automation Platform Integration
+
+### Job Template Configuration
+
+**Name:** Provision Windows VM
+
+**Playbook:** `playbooks/provision-vm.yml`
+
+**Inventory:** ToalLab
+
+**Credentials:**
+- Machine Credential: `Hyper-V WinRM`
+
+**Limit:** `hyperv`
+
+**Survey:**
+
+| Prompt | Variable | Type | Default | Required |
+|--------|----------|------|---------|----------|
+| VM Name | `vm_name` | Text | - | Yes |
+| IP Address | `vm_ip_address` | Text | - | No |
+| CPU Count | `vm_cpu_count` | Integer | 2 | No |
+| Memory (GB) | `vm_memory_gb` | Integer | 4 | No |
+| Disk Size (GB) | `vm_disk_size_gb` | Integer | 60 | No |
+
+**Webhook:** Enable for GitOps workflow
+
+### Workflow Template (Future)
+
+**Name:** Full VM Lifecycle
+
+**Nodes:**
+1. Provision VM → `provision-vm.yml`
+2. Wait for Installation → Manual approval
+3. Windows Baseline → `windows-baseline.yml`
+4. Deploy Application → `install-iis.yml`
+5. Update CMDB → `sync-cmdb.yml`
+
+## Troubleshooting
+
+### VM Creation Failed
+
+```bash
+# Check Hyper-V host connectivity
+ansible hyperv -m ansible.windows.win_ping
+
+# Verify paths exist
+ansible hyperv -m ansible.windows.win_file -a "path=D:\\VMs state=directory"
+
+# Check ISO exists
+ansible hyperv -m ansible.windows.win_stat -a "path=D:\\ISOs\\Windows_Server_2022.iso"
+```
+
+### AutoUnattend.xml Not Working
+
+**Symptoms:** Windows installation shows interactive prompts
+
+**Causes:**
+- AutoUnattend.xml not found by Windows Setup
+- XML syntax error
+- Missing required sections
+
+**Solutions:**
+1. Verify autounattend.xml is in correct location
+2. Check XML is valid (use XMLLint or online validator)
+3. Review Windows Setup logs in VM: `C:\Windows\Panther\setupact.log`
+
+### WinRM Not Available After Installation
+
+**Symptoms:** Cannot connect via win_ping
+
+**Causes:**
+- First logon commands didn't run
+- Firewall blocking WinRM
+- Wrong credentials
+
+**Solutions:**
+
+```bash
+# Connect to VM console through Hyper-V Manager
+# Check if first logon commands ran:
+Get-Service WinRM
+Test-WSMan
+
+# Manually enable WinRM if needed:
+Enable-PSRemoting -Force
+Set-Item WSMan:\localhost\Service\Auth\Basic -Value $true
+
+# Check firewall:
+Get-NetFirewallRule -Name "WINRM-HTTP-In-TCP"
+```
+
+### Installation Hangs
+
+**Symptoms:** VM stuck at Windows Setup screen
+
+**Causes:**
+- ISO not bootable
+- Insufficient resources
+- Hardware compatibility issues
+
+**Solutions:**
+1. Increase VM memory (minimum 2GB for Windows Server)
+2. Verify ISO integrity
+3. Check Hyper-V event logs
+4. Try Generation 1 VM instead of Generation 2
+
+## Advanced Usage
+
+### Provision Multiple VMs
+
+```bash
+# Create a loop or use separate job templates in AAP
+for vm in WEB01 WEB02 WEB03; do
+ ansible-playbook provision-vm.yml -e vm_name=$vm -e vm_ip_address=192.168.1.$((100 + ${vm: -1}))
+done
+```
+
+### Custom Windows Edition
+
+Edit the ISO or autounattend.xml to specify edition:
+
+```xml
+
+
+
+
+ /IMAGE/NAME
+ Windows Server 2022 SERVERDATACENTER
+
+
+
+
+```
+
+### Domain Join During Installation
+
+Add to autounattend.xml under ``:
+
+```xml
+
+ example.com
+ DomainJoinPassword
+ DomainJoinUser
+
+example.com
+```
+
+## Files Generated
+
+After running provision-vm.yml, these files are created on the Hyper-V host:
+
+```
+D:\VMs\\
+├── .vhdx # Virtual hard disk
+├── autounattend.xml # Unattended installation answer file
+└── Virtual Machines\ # VM configuration files (created by Hyper-V)
+```
+
+## Next Steps
+
+After successful provisioning:
+
+1. ✓ Add VM to inventory
+2. Run baseline configuration (security, monitoring, agents)
+3. Join to domain (if required)
+4. Deploy applications
+5. Update CMDB
+6. Configure backup
+7. Document in ServiceNow
+
+## References
+
+- [Windows Unattended Installation Guide](https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/automate-windows-setup)
+- [Hyper-V PowerShell Reference](https://docs.microsoft.com/en-us/powershell/module/hyper-v/)
+- [Ansible Windows Guide](https://docs.ansible.com/ansible/latest/os_guide/windows_usage.html)
diff --git a/playbooks/create-autounattend-iso.yml b/playbooks/create-autounattend-iso.yml
new file mode 100644
index 0000000..d9fb00a
--- /dev/null
+++ b/playbooks/create-autounattend-iso.yml
@@ -0,0 +1,93 @@
+---
+# Helper playbook to create an ISO containing autounattend.xml
+# This ISO can be mounted to the VM during installation
+#
+# Variables:
+# vm_name: Name of the VM (required, must match provision-vm.yml)
+#
+# Usage:
+# ansible-playbook create-autounattend-iso.yml -e vm_name=WEB01
+
+- name: Create AutoUnattend ISO
+ hosts: hyperv
+ gather_facts: false
+
+ vars:
+ vm_path: "{{ vm_storage_path }}\\{{ vm_name }}"
+ autounattend_path: "{{ vm_storage_path }}\\{{ vm_name }}\\autounattend.xml"
+ autounattend_iso_path: "{{ vm_storage_path }}\\{{ vm_name }}\\autounattend.iso"
+
+ tasks:
+ - name: Validate required variables
+ ansible.builtin.assert:
+ that:
+ - vm_name is defined
+ - vm_name | length > 0
+ fail_msg: "vm_name is required"
+
+ - name: Check if autounattend.xml exists
+ ansible.windows.win_stat:
+ path: "{{ autounattend_path }}"
+ register: autounattend_file
+
+ - name: Verify autounattend.xml exists
+ ansible.builtin.assert:
+ that:
+ - autounattend_file.stat.exists
+ fail_msg: "AutoUnattend.xml not found at {{ autounattend_path }}. Run provision-vm.yml first."
+
+ - name: Create ISO from autounattend.xml using oscdimg
+ ansible.windows.win_shell: |
+ # Use oscdimg from Windows ADK (if installed)
+ # Otherwise, use PowerShell alternative
+
+ $adkPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg"
+ $oscdimg = "$adkPath\oscdimg.exe"
+
+ if (Test-Path $oscdimg) {
+ # Create ISO using oscdimg
+ & $oscdimg -m -n -b"$adkPath\efisys.bin" `
+ "{{ vm_path }}" "{{ autounattend_iso_path }}"
+ } else {
+ # Alternative: Use PowerShell to create ISO (requires ImDisk or similar)
+ # For now, provide instructions
+ Write-Host "oscdimg not found. Windows ADK required for automated ISO creation."
+ Write-Host ""
+ Write-Host "Alternative methods:"
+ Write-Host "1. Install Windows ADK: https://docs.microsoft.com/en-us/windows-hardware/get-started/adk-install"
+ Write-Host "2. Use ImgBurn or similar tool manually"
+ Write-Host "3. Mount autounattend.xml as secondary CD drive during install"
+ Write-Host ""
+ Write-Host "To mount as secondary drive:"
+ Write-Host 'Add-VMDvdDrive -VMName "{{ vm_name }}" -Path "path\to\autounattend.iso"'
+ exit 1
+ }
+ register: iso_create
+ failed_when: iso_create.rc != 0 and iso_create.rc != 1
+
+ - name: Check if ISO was created
+ ansible.windows.win_stat:
+ path: "{{ autounattend_iso_path }}"
+ register: iso_file
+ when: iso_create.rc == 0
+
+ - name: Mount AutoUnattend ISO to VM
+ ansible.windows.win_shell: |
+ # Add a second DVD drive with the autounattend ISO
+ Add-VMDvdDrive -VMName "{{ vm_name }}" -Path "{{ autounattend_iso_path }}"
+ when:
+ - iso_file is defined
+ - iso_file.stat.exists | default(false)
+
+ - name: Display results
+ ansible.builtin.debug:
+ msg:
+ - "AutoUnattend ISO creation results:"
+ - "Source: {{ autounattend_path }}"
+ - "ISO: {{ autounattend_iso_path }}"
+ - "Status: {{ 'Created and mounted' if (iso_file.stat.exists | default(false)) else 'Manual intervention required' }}"
+ - ""
+ - "If automatic creation failed:"
+ - "1. Copy {{ autounattend_path }} to the root of a blank CD/ISO"
+ - "2. Mount the ISO as a second DVD drive to the VM"
+ - "3. Windows Setup will automatically detect and use it"
diff --git a/playbooks/provision-vm.yml b/playbooks/provision-vm.yml
index 46eed41..a399684 100644
--- a/playbooks/provision-vm.yml
+++ b/playbooks/provision-vm.yml
@@ -1,25 +1,274 @@
---
+# Provision Windows Server VM on Hyper-V
+# This playbook creates a new Windows Server VM with unattended installation
+#
+# Variables (can be provided via survey in AAP or command line):
+# vm_name: Name of the VM to create (required)
+# vm_ip_address: Static IP address for the VM (optional)
+# vm_cpu_count: Number of CPUs (default: from group_vars)
+# vm_memory_gb: Memory in GB (default: from group_vars)
+# vm_disk_size_gb: Disk size in GB (default: from group_vars)
+# vm_admin_password: Initial administrator password (default: P@ssw0rd123!)
+#
+# Tags:
+# - create: Create VM and configuration
+# - install: Start VM and wait for installation
+# - verify: Verify VM is accessible
+#
+# Usage:
+# ansible-playbook provision-vm.yml -e vm_name=WEB01 -e vm_ip_address=192.168.1.101
+# ansible-playbook provision-vm.yml --tags create -e vm_name=WEB01
+
- name: Provision Windows Server VM on Hyper-V
- hosts: hyperv_hosts
+ hosts: hyperv
gather_facts: false
- vars_prompt:
- - name: vm_name
- prompt: "Enter VM name"
- private: false
+ vars:
+ # Use defaults from group_vars if not specified
+ vm_cpu_count: "{{ vm_cpu_count | default(default_vm_cpu_count) }}"
+ vm_memory_gb: "{{ vm_memory_gb | default(default_vm_memory_gb) }}"
+ vm_disk_size_gb: "{{ vm_disk_size_gb | default(default_vm_disk_size_gb) }}"
+ vm_switch: "{{ vm_switch | default(default_vm_switch) }}"
- - name: vm_ip_address
- prompt: "Enter IP address for VM"
- private: false
+ # Derived paths
+ vm_path: "{{ vm_storage_path }}\\{{ vm_name }}"
+ vm_vhd_path: "{{ vm_storage_path }}\\{{ vm_name }}\\{{ vm_name }}.vhdx"
+ autounattend_path: "{{ vm_storage_path }}\\{{ vm_name }}\\autounattend.xml"
+
+ pre_tasks:
+ - name: Validate required variables
+ ansible.builtin.assert:
+ that:
+ - vm_name is defined
+ - vm_name | length > 0
+ - vm_name is match('^[a-zA-Z0-9-]+$')
+ fail_msg: "vm_name is required and must contain only letters, numbers, and hyphens"
tasks:
- - name: Placeholder - Create VM
- ansible.builtin.debug:
- msg: "Will create VM {{ vm_name }} with IP {{ vm_ip_address }}"
+ - name: Check if VM already exists
+ ansible.windows.win_shell: |
+ Get-VM -Name "{{ vm_name }}" -ErrorAction SilentlyContinue |
+ Select-Object -ExpandProperty Name
+ register: vm_exists_check
+ changed_when: false
+ failed_when: false
+ tags: [create, verify]
- # TODO: Implement VM creation using hyper-v modules
- # TODO: Generate autounattend.xml from template
- # TODO: Attach autounattend.xml to VM
- # TODO: Start VM and wait for provisioning
- # TODO: Add VM to inventory
- # TODO: Update ServiceNow CMDB
+ - name: Set VM exists fact
+ ansible.builtin.set_fact:
+ vm_exists: "{{ vm_exists_check.stdout | trim | length > 0 }}"
+ tags: [create, verify]
+
+ - name: Display VM status
+ ansible.builtin.debug:
+ msg: "VM {{ vm_name }} {{ 'already exists' if vm_exists else 'will be created' }}"
+ tags: [create, verify]
+
+ - name: VM Creation Block
+ when: not vm_exists
+ tags: [create]
+ block:
+ - name: Create VM directory
+ ansible.windows.win_file:
+ path: "{{ vm_path }}"
+ state: directory
+
+ - name: Generate autounattend.xml
+ ansible.windows.win_template:
+ src: ../templates/autounattend.xml.j2
+ dest: "{{ autounattend_path }}"
+
+ - name: Create new VHD
+ ansible.windows.win_shell: |
+ New-VHD -Path "{{ vm_vhd_path }}" `
+ -SizeBytes {{ vm_disk_size_gb }}GB `
+ -Dynamic
+ args:
+ creates: "{{ vm_vhd_path }}"
+
+ - name: Create new VM
+ ansible.windows.win_shell: |
+ New-VM -Name "{{ vm_name }}" `
+ -MemoryStartupBytes {{ vm_memory_gb }}GB `
+ -Generation 2 `
+ -VHDPath "{{ vm_vhd_path }}" `
+ -SwitchName "{{ vm_switch }}"
+ register: vm_create
+
+ - name: Configure VM processor count
+ ansible.windows.win_shell: |
+ Set-VMProcessor -VMName "{{ vm_name }}" -Count {{ vm_cpu_count }}
+
+ - name: Enable dynamic memory
+ ansible.windows.win_shell: |
+ Set-VMMemory -VMName "{{ vm_name }}" `
+ -DynamicMemoryEnabled $true `
+ -MinimumBytes 2GB `
+ -MaximumBytes {{ vm_memory_gb }}GB
+
+ - name: Disable secure boot (for compatibility)
+ ansible.windows.win_shell: |
+ Set-VMFirmware -VMName "{{ vm_name }}" -EnableSecureBoot Off
+
+ - name: Create DVD drive for ISO
+ ansible.windows.win_shell: |
+ Add-VMDvdDrive -VMName "{{ vm_name }}" -Path "{{ windows_server_iso }}"
+
+ - name: Set DVD as first boot device
+ ansible.windows.win_shell: |
+ $dvd = Get-VMDvdDrive -VMName "{{ vm_name }}"
+ Set-VMFirmware -VMName "{{ vm_name }}" -FirstBootDevice $dvd
+
+ - name: Create VFD for autounattend.xml
+ ansible.windows.win_shell: |
+ # Create a temporary VFD file
+ $vfdPath = "{{ vm_path }}\autounattend.vfd"
+
+ # PowerShell to create VFD and add autounattend.xml
+ # Note: This is a simplified approach
+ # For production, consider using a pre-built VFD or ISO with autounattend.xml
+
+ # Copy autounattend.xml to a location accessible during Windows Setup
+ # Alternative: Use NoCloud data source or inject into ISO
+ Write-Host "AutoUnattend.xml created at {{ autounattend_path }}"
+ Write-Host "Manual step: Mount autounattend.xml to VM or inject into ISO"
+ register: vfd_create
+ changed_when: false
+
+ - name: Display next steps for autounattend.xml
+ ansible.builtin.debug:
+ msg:
+ - "AutoUnattend.xml has been generated at: {{ autounattend_path }}"
+ - "To use it, manually:"
+ - "1. Create an ISO with autounattend.xml in the root"
+ - "2. Or copy it to a floppy image and attach to VM"
+ - "3. Or use UEFI boot with the file in the EFI partition"
+ - ""
+ - "For automated approach, consider:"
+ - "- Creating a custom Windows ISO with autounattend.xml embedded"
+ - "- Using MDT/WDS for network-based deployment"
+
+ - name: VM Installation Block
+ when: not vm_exists
+ tags: [install]
+ block:
+ - name: Start VM for installation
+ ansible.windows.win_shell: |
+ Start-VM -Name "{{ vm_name }}"
+ register: vm_start
+
+ - name: Wait for VM to start
+ ansible.builtin.pause:
+ seconds: 30
+
+ - name: Display installation progress message
+ ansible.builtin.debug:
+ msg:
+ - "VM {{ vm_name }} has been started"
+ - "Windows Server installation is in progress..."
+ - "This may take 15-30 minutes depending on hardware"
+ - ""
+ - "Installation steps:"
+ - "1. Windows Setup will boot from ISO"
+ - "2. AutoUnattend.xml will configure installation (if properly mounted)"
+ - "3. System will reboot after installation"
+ - "4. First logon commands will configure WinRM"
+
+ - name: Wait for Windows installation to complete
+ ansible.builtin.pause:
+ prompt: |
+
+ Manual step required:
+
+ 1. Monitor the VM installation through Hyper-V Manager
+ 2. Verify autounattend.xml is being used (watch for automated setup)
+ 3. Wait for installation to complete and system to reach login screen
+ 4. Verify WinRM is enabled (first logon commands should run)
+
+ Press Enter when installation is complete and VM is at login screen...
+ when: ansible_check_mode is not defined or not ansible_check_mode
+
+ - name: VM Verification Block
+ tags: [verify]
+ block:
+ - name: Get VM state
+ ansible.windows.win_shell: |
+ Get-VM -Name "{{ vm_name }}" |
+ Select-Object Name, State, CPUUsage, MemoryAssigned, Uptime |
+ ConvertTo-Json
+ register: vm_state
+ changed_when: false
+
+ - name: Display VM state
+ ansible.builtin.debug:
+ var: vm_state.stdout | from_json
+
+ - name: Wait for WinRM to be available on new VM
+ ansible.builtin.wait_for:
+ host: "{{ vm_ip_address }}"
+ port: 5985
+ timeout: 600
+ state: started
+ when:
+ - vm_ip_address is defined
+ - not vm_exists
+ ignore_errors: true
+
+ - name: Test WinRM connectivity
+ ansible.builtin.command:
+ cmd: >
+ ansible {{ vm_name }} -m ansible.windows.win_ping
+ -i {{ vm_ip_address }},
+ delegate_to: localhost
+ when:
+ - vm_ip_address is defined
+ register: winrm_test
+ failed_when: false
+ changed_when: false
+
+ - name: Display connectivity test result
+ ansible.builtin.debug:
+ msg: "{{ 'WinRM connectivity successful' if winrm_test.rc == 0 else 'WinRM connectivity failed - manual verification needed' }}"
+ when: vm_ip_address is defined
+
+ - name: Post-provisioning tasks
+ when: not vm_exists
+ tags: [create]
+ block:
+ - name: Summary message
+ ansible.builtin.debug:
+ msg:
+ - "========================================="
+ - "VM Provisioning Summary"
+ - "========================================="
+ - "VM Name: {{ vm_name }}"
+ - "CPU Count: {{ vm_cpu_count }}"
+ - "Memory: {{ vm_memory_gb }} GB"
+ - "Disk Size: {{ vm_disk_size_gb }} GB"
+ - "IP Address: {{ vm_ip_address | default('DHCP') }}"
+ - "VHD Path: {{ vm_vhd_path }}"
+ - "AutoUnattend: {{ autounattend_path }}"
+ - "========================================="
+ - ""
+ - "Next Steps:"
+ - "1. Verify VM installation completed successfully"
+ - "2. Test WinRM connectivity: ansible {{ vm_name }} -m win_ping"
+ - "3. Add VM to inventory if using static groups"
+ - "4. Run baseline configuration playbook"
+ - "5. Deploy applications as needed"
+ - ""
+ - "To add to inventory, update:"
+ - "/home/ptoal/Dev/inventories/toallab-inventory/static.yml"
+
+ - name: Create inventory addition snippet
+ ansible.builtin.set_fact:
+ inventory_snippet: |
+ # Add this to /home/ptoal/Dev/inventories/toallab-inventory/static.yml
+ # Under the appropriate group (web_servers, app_servers, db_servers):
+
+ {{ vm_name }}:
+ ansible_host: {{ vm_ip_address | default('SET_IP_HERE') }}
+
+ - name: Display inventory addition snippet
+ ansible.builtin.debug:
+ var: inventory_snippet
diff --git a/templates/autounattend.xml.j2 b/templates/autounattend.xml.j2
new file mode 100644
index 0000000..719d304
--- /dev/null
+++ b/templates/autounattend.xml.j2
@@ -0,0 +1,158 @@
+
+
+
+
+
+ en-US
+
+ en-US
+ en-US
+ en-US
+ en-US
+
+
+
+
+
+
+ 1
+ 500
+ Primary
+
+
+ 2
+ true
+ Primary
+
+
+
+
+ true
+ NTFS
+
+ 1
+ 1
+
+
+ NTFS
+
+ C
+ 2
+ 2
+
+
+ 0
+ true
+
+
+
+
+
+ 0
+ 2
+
+ false
+
+
+
+ true
+ Ansible Admin
+ {{ organization | default('ToalLab') }}
+
+
+
+
+
+ {{ vm_name }}
+ {{ timezone | default('Eastern Standard Time') }}
+
+
+ false
+
+
+ 0
+
+
+
+
+ true
+ Remote Desktop
+ all
+
+
+ true
+ Windows Remote Management
+ all
+
+
+
+
+
+
+
+
+ {{ vm_admin_password | default('P@ssw0rd123!') }}
+ true
+
+ true
+ 3
+ Administrator
+
+
+
+ 1
+ cmd.exe /c netsh advfirewall firewall add rule name="WinRM HTTP" dir=in action=allow protocol=TCP localport=5985
+ Enable WinRM HTTP
+
+
+ 2
+ cmd.exe /c netsh advfirewall firewall add rule name="WinRM HTTPS" dir=in action=allow protocol=TCP localport=5986
+ Enable WinRM HTTPS
+
+
+ 3
+ powershell.exe -Command "Enable-PSRemoting -Force"
+ Enable PowerShell Remoting
+
+
+ 4
+ powershell.exe -Command "Set-Item -Path WSMan:\localhost\Service\Auth\Basic -Value $true"
+ Enable Basic Auth for WinRM
+
+
+ 5
+ powershell.exe -Command "Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False"
+ Disable Windows Firewall for initial setup
+
+{% if vm_ip_address is defined %}
+
+ 6
+ powershell.exe -Command "New-NetIPAddress -InterfaceAlias 'Ethernet' -IPAddress {{ vm_ip_address }} -PrefixLength {{ vm_subnet_prefix | default('24') }} -DefaultGateway {{ vm_gateway | default('192.168.1.1') }}"
+ Set static IP address
+
+
+ 7
+ powershell.exe -Command "Set-DnsClientServerAddress -InterfaceAlias 'Ethernet' -ServerAddresses {{ dns_servers | join(',') }}"
+ Set DNS servers
+
+{% endif %}
+
+
+ true
+ true
+ true
+ true
+ Work
+ 3
+ true
+ true
+
+
+
+ {{ vm_admin_password | default('P@ssw0rd123!') }}
+ true
+
+
+
+
+