315 lines
12 KiB
YAML
315 lines
12 KiB
YAML
---
|
|
# 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
|
|
gather_facts: false
|
|
|
|
vars:
|
|
# 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: Set VM configuration with defaults
|
|
ansible.builtin.set_fact:
|
|
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: Get available Hyper-V virtual switches
|
|
ansible.windows.win_shell: |
|
|
Get-VMSwitch | Select-Object Name, SwitchType | ConvertTo-Json
|
|
register: available_switches
|
|
changed_when: false
|
|
tags: [create, verify]
|
|
|
|
- name: Parse available switches
|
|
ansible.builtin.set_fact:
|
|
switch_list: "{{ available_switches.stdout | from_json }}"
|
|
when: available_switches.stdout | trim | length > 0
|
|
tags: [create, verify]
|
|
|
|
- name: Display available switches
|
|
ansible.builtin.debug:
|
|
msg:
|
|
- "Available Hyper-V switches:"
|
|
- "{{ switch_list | default([]) | map(attribute='Name') | list }}"
|
|
- ""
|
|
- "Configured switch: {{ vm_switch }}"
|
|
tags: [create, verify]
|
|
|
|
- name: Validate virtual switch exists
|
|
ansible.builtin.assert:
|
|
that:
|
|
- switch_list is defined
|
|
- switch_list | selectattr('Name', 'equalto', vm_switch) | list | length > 0
|
|
fail_msg: |
|
|
Virtual switch '{{ vm_switch }}' not found on Hyper-V host.
|
|
Available switches: {{ switch_list | default([]) | map(attribute='Name') | list | join(', ') }}
|
|
|
|
To fix this:
|
|
1. Update the vm_switch variable: -e vm_switch="<switch_name>"
|
|
2. Or update default in group_vars/hyperv/vars.yml
|
|
3. Or create the switch on Hyper-V host:
|
|
New-VMSwitch -Name "{{ vm_switch }}" -SwitchType Internal
|
|
success_msg: "Virtual switch '{{ vm_switch }}' is available"
|
|
tags: [create]
|
|
|
|
- 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: 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]
|
|
|
|
- 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
|