--- # Provision Hyper-V Host for Demo Environment # This playbook configures a fresh Windows Server with Hyper-V for demonstration purposes # # WARNING: This is for DEMO environments only. Production deployments require: # - Proper security hardening # - Network segmentation # - Storage configuration (SAN, clustering, etc.) # - Backup and DR planning # - Monitoring and alerting # # Usage: # ansible-playbook playbooks/provision-hyperv-host.yml - name: Provision Hyper-V Host hosts: hyperv gather_facts: true vars: # Virtual switch configuration demo_switches: - name: "External-NAT" type: "External" notes: "External switch with NAT for internet access" - name: "Internal-Lab" type: "Internal" notes: "Internal switch for isolated lab network" # NAT configuration for demo nat_network: "192.168.100.0/24" nat_gateway: "192.168.100.1" nat_name: "DemoNAT" tasks: - name: Display provisioning plan ansible.builtin.debug: msg: - "=========================================" - "Hyper-V Host Provisioning Plan" - "=========================================" - "Host: {{ inventory_hostname }}" - "Storage Drive: {{ vm_storage_drive }}" - "VM Path: {{ vm_storage_path }}" - "ISO Path: {{ iso_storage_path }}" - "Virtual Switches: {{ demo_switches | map(attribute='name') | list | join(', ') }}" - "NAT Network: {{ nat_network }}" - "=========================================" - name: Check if Hyper-V is installed ansible.windows.win_shell: | Get-WindowsFeature -Name Hyper-V | Select-Object -ExpandProperty InstallState register: hyperv_state changed_when: false - name: Install Hyper-V role and management tools ansible.windows.win_feature: name: - Hyper-V - Hyper-V-PowerShell - RSAT-Hyper-V-Tools state: present include_management_tools: true register: hyperv_install when: hyperv_state.stdout | trim != 'Installed' - name: Reboot if Hyper-V was installed ansible.windows.win_reboot: reboot_timeout: 600 post_reboot_delay: 60 when: - hyperv_install is defined - hyperv_install.reboot_required | default(false) - name: Wait for system to be ready after reboot ansible.windows.win_ping: retries: 10 delay: 30 when: - hyperv_install is defined - hyperv_install.reboot_required | default(false) - name: Create storage directories ansible.windows.win_file: path: "{{ item }}" state: directory loop: - "{{ vm_storage_path }}" - "{{ iso_storage_path }}" - "{{ vhd_template_path }}" - name: Set storage directory permissions ansible.windows.win_shell: | # Grant Hyper-V full control to storage directories icacls "{{ item }}" /grant "NT VIRTUAL MACHINE\Virtual Machines:(OI)(CI)F" loop: - "{{ vm_storage_path }}" - "{{ iso_storage_path }}" - "{{ vhd_template_path }}" register: acl_result changed_when: "'Successfully processed' in acl_result.stdout" - name: Get existing virtual switches ansible.windows.win_shell: | ConvertTo-Json -InputObject @(Get-VMSwitch | Select-Object Name, SwitchType) register: existing_switches changed_when: false failed_when: false - name: Parse existing switches ansible.builtin.set_fact: existing_switch_names: "{{ (existing_switches.stdout | from_json | default([])) | map(attribute='Name') | list }}" when: existing_switches.stdout | trim | length > 0 - name: Set empty switch list if none exist ansible.builtin.set_fact: existing_switch_names: [] when: existing_switches.stdout | trim | length == 0 - name: Display existing switches ansible.builtin.debug: msg: "Existing switches: {{ existing_switch_names | join(', ') if existing_switch_names | length > 0 else 'None' }}" - name: Create External virtual switch ansible.windows.win_shell: | # Get the first network adapter that is up and has a default gateway $adapter = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' -and (Get-NetIPConfiguration -InterfaceIndex $_.ifIndex).IPv4DefaultGateway -ne $null } | Select-Object -First 1 if ($adapter) { New-VMSwitch -Name "External-NAT" ` -NetAdapterName $adapter.Name ` -AllowManagementOS $true ` -Notes "External switch for VM internet access" Write-Host "Created External-NAT switch using adapter: $($adapter.Name)" } else { Write-Host "No suitable network adapter found for external switch" exit 1 } register: external_switch when: "'External-NAT' not in existing_switch_names" failed_when: external_switch.rc != 0 changed_when: "'Created External-NAT' in external_switch.stdout" - name: Create Internal virtual switch ansible.windows.win_shell: | New-VMSwitch -Name "Internal-Lab" ` -SwitchType Internal ` -Notes "Internal switch for isolated lab network" when: "'Internal-Lab' not in existing_switch_names" - name: Configure NAT for internal network block: - name: Get Internal-Lab switch interface index ansible.windows.win_shell: | $adapter = Get-NetAdapter | Where-Object { $_.Name -like '*Internal-Lab*' } $adapter.ifIndex register: internal_adapter_index changed_when: false - name: Configure IP address on Internal-Lab switch ansible.windows.win_shell: | $ifIndex = {{ internal_adapter_index.stdout | trim }} # Remove existing IP if present Remove-NetIPAddress -InterfaceIndex $ifIndex -Confirm:$false -ErrorAction SilentlyContinue # Add new IP address New-NetIPAddress -InterfaceIndex $ifIndex ` -IPAddress {{ nat_gateway }} ` -PrefixLength 24 ` -ErrorAction Stop register: ip_config changed_when: true failed_when: false - name: Check if NAT already exists ansible.windows.win_shell: | Get-NetNat -Name "{{ nat_name }}" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name register: existing_nat changed_when: false failed_when: false - name: Create NAT for internal network ansible.windows.win_shell: | New-NetNat -Name "{{ nat_name }}" ` -InternalIPInterfaceAddressPrefix "{{ nat_network }}" when: existing_nat.stdout | trim | length == 0 - name: Configure WinRM for Ansible block: - name: Enable WinRM HTTPS listener ansible.windows.win_shell: | # Check if HTTPS listener exists $httpsListener = Get-ChildItem WSMan:\localhost\Listener | Where-Object { $_.Keys -contains 'Transport=HTTPS' } if (-not $httpsListener) { # Create self-signed certificate $cert = New-SelfSignedCertificate -DnsName $env:COMPUTERNAME ` -CertStoreLocation Cert:\LocalMachine\My ` -NotAfter (Get-Date).AddYears(5) # Create HTTPS listener New-Item -Path WSMan:\localhost\Listener ` -Transport HTTPS ` -Address * ` -CertificateThumbPrint $cert.Thumbprint -Force Write-Host "Created HTTPS listener" } else { Write-Host "HTTPS listener already exists" } register: winrm_https changed_when: "'Created HTTPS listener' in winrm_https.stdout" - name: Configure WinRM service settings ansible.windows.win_shell: | # Set WinRM service to start automatically Set-Service WinRM -StartupType Automatic # Configure WinRM settings for Ansible Set-Item WSMan:\localhost\Service\Auth\Basic -Value $true Set-Item WSMan:\localhost\Service\Auth\CredSSP -Value $true Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value $false Set-Item WSMan:\localhost\MaxTimeoutms -Value 1800000 # Restart WinRM Restart-Service WinRM register: winrm_config changed_when: true - name: Configure Windows Firewall for demo ansible.windows.win_shell: | # Enable WinRM firewall rules Enable-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" Enable-NetFirewallRule -Name "WINRM-HTTPS-In-TCP-PUBLIC" # Enable RDP for management Enable-NetFirewallRule -Name "RemoteDesktop-UserMode-In-TCP" # Enable Hyper-V management Enable-NetFirewallRule -DisplayGroup "Hyper-V*" register: firewall_config changed_when: true - name: Set Hyper-V default VM storage locations ansible.windows.win_shell: | Set-VMHost -VirtualHardDiskPath "{{ vm_storage_path }}" ` -VirtualMachinePath "{{ vm_storage_path }}" register: vm_defaults changed_when: true - name: Enable Hyper-V Enhanced Session Mode ansible.windows.win_shell: | Set-VMHost -EnableEnhancedSessionMode $true changed_when: true - name: Configure power settings for demo ansible.windows.win_shell: | # Set to High Performance power plan powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c # Disable sleep and hibernation powercfg /change monitor-timeout-ac 0 powercfg /change disk-timeout-ac 0 powercfg /change standby-timeout-ac 0 powercfg /hibernate off register: power_config changed_when: true - name: Install helpful PowerShell modules ansible.windows.win_shell: | # Install PowerShell modules for Hyper-V management Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction SilentlyContinue # Install modules if not present $modules = @('Posh-SSH', 'PSWindowsUpdate') foreach ($module in $modules) { if (-not (Get-Module -ListAvailable -Name $module)) { Install-Module -Name $module -Force -SkipPublisherCheck -Scope AllUsers Write-Host "Installed $module" } else { Write-Host "$module already installed" } } register: ps_modules changed_when: "'Installed' in ps_modules.stdout" - name: Create README file on Hyper-V host ansible.windows.win_copy: content: | HYPER-V DEMO HOST CONFIGURATION ================================ This host has been configured by Ansible for demonstration purposes. Storage Locations: - VMs: {{ vm_storage_path }} - ISOs: {{ iso_storage_path }} - Templates: {{ vhd_template_path }} Virtual Switches: {% for switch in demo_switches %} - {{ switch.name }} ({{ switch.type }}): {{ switch.notes }} {% endfor %} NAT Configuration: - Network: {{ nat_network }} - Gateway: {{ nat_gateway }} - NAT Name: {{ nat_name }} Management: - WinRM: Enabled (HTTP: 5985, HTTPS: 5986) - RDP: Enabled - Enhanced Session Mode: Enabled Next Steps: 1. Copy Windows Server ISOs to {{ iso_storage_path }} 2. Use Ansible to provision VMs: ansible-playbook provision-vm.yml 3. Access Hyper-V Manager remotely or via RDP WARNING: This is a DEMO configuration - not suitable for production! - Firewall rules are permissive - Self-signed certificates for WinRM - No backup/DR configuration - No monitoring configured Managed by: Ansible Automation Platform Provisioned: {{ ansible_date_time.iso8601 }} dest: "{{ vm_storage_drive }}\\HYPERV-README.txt" - name: Get Hyper-V host information ansible.windows.win_shell: | $host = Get-VMHost [PSCustomObject]@{ ComputerName = $host.ComputerName LogicalProcessorCount = $host.LogicalProcessorCount MemoryCapacity = [math]::Round($host.MemoryCapacity / 1GB, 2) VirtualHardDiskPath = $host.VirtualHardDiskPath VirtualMachinePath = $host.VirtualMachinePath MacAddressMinimum = $host.MacAddressMinimum MacAddressMaximum = $host.MacAddressMaximum EnhancedSessionModeEnabled = $host.EnableEnhancedSessionMode } | ConvertTo-Json register: hyperv_info changed_when: false - name: Display Hyper-V configuration summary ansible.builtin.debug: msg: - "=========================================" - "Hyper-V Host Provisioning Complete" - "=========================================" - "{{ hyperv_info.stdout | from_json | to_nice_json }}" - "" - "Virtual Switches Created:" - "{{ demo_switches | map(attribute='name') | list }}" - "" - "Storage Configured:" - " VMs: {{ vm_storage_path }}" - " ISOs: {{ iso_storage_path }}" - " Templates: {{ vhd_template_path }}" - "" - "NAT Network:" - " Network: {{ nat_network }}" - " Gateway: {{ nat_gateway }}" - "" - "Next Steps:" - "1. Upload Windows Server ISO to {{ iso_storage_path }}" - "2. Update group_vars/hyperv/vars.yml with:" - " - windows_server_iso: {{ iso_storage_path }}\\Windows_Server_2022.iso" - " - default_vm_switch: External-NAT or Internal-Lab" - "3. Run: ansible-playbook playbooks/list-hyperv-switches.yml" - "4. Provision VMs: ansible-playbook playbooks/provision-vm.yml" - "" - "WARNING: Demo configuration - not production ready!" - "=========================================" - name: Update inventory group_vars with discovered configuration ansible.builtin.debug: msg: - "Update /home/ptoal/Dev/inventories/toallab-inventory/group_vars/hyperv/vars.yml:" - "" - "vm_storage_path: '{{ vm_storage_path }}'" - "iso_storage_path: '{{ iso_storage_path }}'" - "default_vm_switch: 'External-NAT' # or 'Internal-Lab'" delegate_to: localhost