Handoff
This commit is contained in:
42
playbooks/list-hyperv-switches.yml
Normal file
42
playbooks/list-hyperv-switches.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
# Quick playbook to list available Hyper-V virtual switches
|
||||
# Usage: ansible-playbook playbooks/list-hyperv-switches.yml
|
||||
|
||||
- name: List Hyper-V Virtual Switches
|
||||
hosts: hyperv
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Get virtual switches
|
||||
ansible.windows.win_shell: |
|
||||
Get-VMSwitch | Select-Object Name, SwitchType, NetAdapterInterfaceDescription | Format-Table -AutoSize
|
||||
register: switches
|
||||
changed_when: false
|
||||
|
||||
- name: Display switches
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ switches.stdout_lines }}"
|
||||
|
||||
- name: Get switches in JSON format
|
||||
ansible.windows.win_shell: |
|
||||
Get-VMSwitch | Select-Object Name, SwitchType | ConvertTo-Json
|
||||
register: switches_json
|
||||
changed_when: false
|
||||
|
||||
- name: Parse switches
|
||||
ansible.builtin.set_fact:
|
||||
switch_list: "{{ switches_json.stdout | from_json }}"
|
||||
|
||||
- name: Display switch names
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "========================================="
|
||||
- "Available Virtual Switches:"
|
||||
- "========================================="
|
||||
- "{{ switch_list | map(attribute='Name') | list }}"
|
||||
- ""
|
||||
- "To use a switch, either:"
|
||||
- "1. Pass as variable: -e vm_switch='<switch_name>'"
|
||||
- "2. Update default in: /home/ptoal/Dev/inventories/toallab-inventory/group_vars/hyperv/vars.yml"
|
||||
- ""
|
||||
- "Current default: {{ default_vm_switch }}"
|
||||
388
playbooks/provision-hyperv-host.yml
Normal file
388
playbooks/provision-hyperv-host.yml
Normal file
@@ -0,0 +1,388 @@
|
||||
---
|
||||
# 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: |
|
||||
Get-VMSwitch | Select-Object Name, SwitchType | ConvertTo-Json
|
||||
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
|
||||
Reference in New Issue
Block a user