Create provisioning

This commit is contained in:
2026-04-28 22:45:25 -04:00
parent 7bc1f8f702
commit 1717081550
6 changed files with 965 additions and 44 deletions

117
README.md
View File

@@ -14,37 +14,74 @@ This project demonstrates automated Windows Server VM management including:
## Quick Start ## Quick Start
### Development Environment
```bash ```bash
# Activate Ansible virtual environment
source ~/.venv/ansible/bin/activate
# Install required collections # Install required collections
ansible-galaxy collection install -r collections/requirements.yml ansible-galaxy collection install -r collections/requirements.yml
# Run VM provisioning # Verify Hyper-V connectivity
ansible-playbook playbooks/provision-vm.yml -e vm_name=DEMO-WEB01 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 # Patch management
ansible-playbook playbooks/patch-vms.yml --limit windows_servers ansible-playbook playbooks/patch-vms.yml --limit windows_servers
# Install IIS demo application # Install IIS demo application
ansible-playbook playbooks/install-iis.yml --limit web_servers ansible-playbook playbooks/install-iis.yml --limit web_servers
# Update CMDB
ansible-playbook playbooks/sync-cmdb.yml
``` ```
## Project Structure ## Project Structure
``` ```
. .
├── ansible.cfg # Ansible configuration ├── ansible.cfg # Ansible config → uses toallab-inventory
├── playbooks/ # Playbooks by use case ├── playbooks/ # Automation workflows
│ ├── provision-vm.yml │ ├── provision-vm.yml # VM provisioning with autounattend
│ ├── patch-vms.yml │ ├── create-autounattend-iso.yml # Helper for ISO creation
│ ├── install-iis.yml │ ├── patch-vms.yml # Windows Update automation
── sync-cmdb.yml ── install-iis.yml # IIS deployment demo
├── roles/ # Custom roles │ ├── sync-cmdb.yml # ServiceNow integration
├── group_vars/ # Group variables │ └── README-provision.md # Detailed provisioning guide
├── host_vars/ # Host-specific variables ├── templates/ # Jinja2 templates
└── collections/ # Ansible collections │ └── autounattend.xml.j2 # Windows unattended install
└── requirements.yml ├── 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 ## Prerequisites
- Ansible Automation Platform 2.x - Ansible Automation Platform 2.x
@@ -53,6 +90,58 @@ ansible-playbook playbooks/install-iis.yml --limit web_servers
- ServiceNow instance (for CMDB integration) - ServiceNow instance (for CMDB integration)
- Active Directory domain (for authentication) - 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 ## 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
```

View File

@@ -7,18 +7,5 @@ collections_path = collections
interpreter_python = auto_silent interpreter_python = auto_silent
timeout = 30 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] [ssh_connection]
pipelining = True pipelining = True

View File

@@ -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
<ImageInstall>
<OSImage>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/NAME</Key>
<Value>Windows Server 2022 SERVERDATACENTER</Value>
</MetaData>
</InstallFrom>
</OSImage>
</ImageInstall>
```
### Domain Join During Installation
Add to autounattend.xml under `<Identification>`:
```xml
<Credentials>
<Domain>example.com</Domain>
<Password>DomainJoinPassword</Password>
<Username>DomainJoinUser</Username>
</Credentials>
<JoinDomain>example.com</JoinDomain>
```
## Files Generated
After running provision-vm.yml, these files are created on the Hyper-V host:
```
D:\VMs\<vm_name>\
├── <vm_name>.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)

View File

@@ -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"

View File

@@ -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 - name: Provision Windows Server VM on Hyper-V
hosts: hyperv_hosts hosts: hyperv
gather_facts: false gather_facts: false
vars_prompt: vars:
- name: vm_name # Use defaults from group_vars if not specified
prompt: "Enter VM name" vm_cpu_count: "{{ vm_cpu_count | default(default_vm_cpu_count) }}"
private: false 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 # Derived paths
prompt: "Enter IP address for VM" vm_path: "{{ vm_storage_path }}\\{{ vm_name }}"
private: false 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: tasks:
- name: Placeholder - Create VM - name: Check if VM already exists
ansible.builtin.debug: ansible.windows.win_shell: |
msg: "Will create VM {{ vm_name }} with IP {{ vm_ip_address }}" 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 - name: Set VM exists fact
# TODO: Generate autounattend.xml from template ansible.builtin.set_fact:
# TODO: Attach autounattend.xml to VM vm_exists: "{{ vm_exists_check.stdout | trim | length > 0 }}"
# TODO: Start VM and wait for provisioning tags: [create, verify]
# TODO: Add VM to inventory
# TODO: Update ServiceNow CMDB - 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

View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DiskConfiguration>
<Disk wcm:action="add">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Size>500</Size>
<Type>Primary</Type>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>2</Order>
<Extend>true</Extend>
<Type>Primary</Type>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Active>true</Active>
<Format>NTFS</Format>
<Label>System</Label>
<Order>1</Order>
<PartitionID>1</PartitionID>
</ModifyPartition>
<ModifyPartition wcm:action="add">
<Format>NTFS</Format>
<Label>Windows</Label>
<Letter>C</Letter>
<Order>2</Order>
<PartitionID>2</PartitionID>
</ModifyPartition>
</ModifyPartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
</DiskConfiguration>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>2</PartitionID>
</InstallTo>
<InstallToAvailablePartition>false</InstallToAvailablePartition>
</OSImage>
</ImageInstall>
<UserData>
<AcceptEula>true</AcceptEula>
<FullName>Ansible Admin</FullName>
<Organization>{{ organization | default('ToalLab') }}</Organization>
</UserData>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComputerName>{{ vm_name }}</ComputerName>
<TimeZone>{{ timezone | default('Eastern Standard Time') }}</TimeZone>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<fDenyTSConnections>false</fDenyTSConnections>
</component>
<component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UserAuthentication>0</UserAuthentication>
</component>
<component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<FirewallGroups>
<FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop">
<Active>true</Active>
<Group>Remote Desktop</Group>
<Profile>all</Profile>
</FirewallGroup>
<FirewallGroup wcm:action="add" wcm:keyValue="WinRM">
<Active>true</Active>
<Group>Windows Remote Management</Group>
<Profile>all</Profile>
</FirewallGroup>
</FirewallGroups>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AutoLogon>
<Password>
<Value>{{ vm_admin_password | default('P@ssw0rd123!') }}</Value>
<PlainText>true</PlainText>
</Password>
<Enabled>true</Enabled>
<LogonCount>3</LogonCount>
<Username>Administrator</Username>
</AutoLogon>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="WinRM HTTP" dir=in action=allow protocol=TCP localport=5985</CommandLine>
<Description>Enable WinRM HTTP</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>2</Order>
<CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="WinRM HTTPS" dir=in action=allow protocol=TCP localport=5986</CommandLine>
<Description>Enable WinRM HTTPS</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>3</Order>
<CommandLine>powershell.exe -Command "Enable-PSRemoting -Force"</CommandLine>
<Description>Enable PowerShell Remoting</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>4</Order>
<CommandLine>powershell.exe -Command "Set-Item -Path WSMan:\localhost\Service\Auth\Basic -Value $true"</CommandLine>
<Description>Enable Basic Auth for WinRM</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>5</Order>
<CommandLine>powershell.exe -Command "Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False"</CommandLine>
<Description>Disable Windows Firewall for initial setup</Description>
</SynchronousCommand>
{% if vm_ip_address is defined %}
<SynchronousCommand wcm:action="add">
<Order>6</Order>
<CommandLine>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') }}"</CommandLine>
<Description>Set static IP address</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>7</Order>
<CommandLine>powershell.exe -Command "Set-DnsClientServerAddress -InterfaceAlias 'Ethernet' -ServerAddresses {{ dns_servers | join(',') }}"</CommandLine>
<Description>Set DNS servers</Description>
</SynchronousCommand>
{% endif %}
</FirstLogonCommands>
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>3</ProtectYourPC>
<SkipMachineOOBE>true</SkipMachineOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
</OOBE>
<UserAccounts>
<AdministratorPassword>
<Value>{{ vm_admin_password | default('P@ssw0rd123!') }}</Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
</component>
</settings>
</unattend>