389 lines
15 KiB
YAML
389 lines
15 KiB
YAML
---
|
|
# 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
|