diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000..5852538 --- /dev/null +++ b/HANDOFF.md @@ -0,0 +1,509 @@ +# Hyper-V Automation Project - Handoff Document + +**Date**: 2026-04-29 +**Status**: Development - Core provisioning implemented +**Next Session**: Ready for Hyper-V host setup and VM testing + +## Project Overview + +Enterprise-grade automation for Windows Server VM lifecycle management on Hyper-V using Ansible Automation Platform. Demonstrates GitOps and Infrastructure as Code principles for demo/lab environments. + +**Primary Use Case**: Automated VM provisioning with unattended Windows installation + +**Target Environment**: Demo/Lab (not production-hardened) + +## Current State + +### ✅ Completed + +1. **Project Structure** + - Integrated with toallab standard inventory (`/home/ptoal/Dev/inventories/toallab-inventory`) + - Group variables organized (hyperv, windows_servers, web_servers) + - Host variables configured for hyperv1.lan.toal.ca (192.168.1.182) + - Collections requirements defined + +2. **Hyper-V Host Provisioning** ⭐ NEW + - Playbook: `playbooks/provision-hyperv-host.yml` + - Installs Hyper-V role and management tools + - Creates storage structure (D:\VMs, D:\ISOs, D:\Templates) + - Configures virtual switches (External-NAT, Internal-Lab) + - Sets up NAT networking (192.168.100.0/24) + - Hardens WinRM with HTTPS listener + - Demo-optimized settings (power, firewall, etc.) + +3. **VM Provisioning Workflow** + - Playbook: `playbooks/provision-vm.yml` + - Creates VMs with configurable resources (CPU, RAM, disk) + - Generates autounattend.xml for unattended Windows installation + - Validates virtual switch availability + - Supports tags for selective execution (create/install/verify) + - Idempotent operations + - AAP-ready with survey support + +4. **Templates** + - `templates/autounattend.xml.j2` - Complete Windows unattended install + - Configures: network, WinRM, RDP, computer name, timezone, admin password + - Static IP or DHCP support + - First-logon commands for Ansible readiness + +5. **Helper Playbooks** + - `playbooks/list-hyperv-switches.yml` - Discover available switches + - `playbooks/create-autounattend-iso.yml` - Helper for ISO creation + - `playbooks/install-iis.yml` - IIS deployment demo (existing) + - `playbooks/patch-vms.yml` - Windows Update automation (existing) + +6. **Documentation** + - `QUICKSTART.md` - Complete step-by-step guide from scratch + - `playbooks/README-provision.md` - Detailed VM provisioning guide + - `CLAUDE.md` - Architecture and development guidelines + - `README.md` - Project overview + +### ⚠️ Known Issues / Limitations + +1. **Virtual Switch Discovery** + - Fixed: Playbook now validates switches before VM creation + - Default switch changed from "Internal Switch" → "External-NAT" + - Run `list-hyperv-switches.yml` to see available switches + +2. **AutoUnattend.xml Delivery** + - Currently requires manual intervention or helper playbook + - Windows Setup needs to find autounattend.xml (second DVD drive, floppy, or custom ISO) + - Helper playbook provided but requires Windows ADK on host + +3. **Circular Variable References** + - Fixed: Moved variable defaults from `vars:` to `set_fact` in `pre_tasks` + - Variables now compute correctly with extra vars or group_vars fallbacks + +4. **WinRM Configuration** + - Currently using NTLM over HTTP (port 5985) for hyperv1.lan.toal.ca + - Demo-appropriate but not production-ready + - Host provisioning playbook sets up HTTPS for new hosts + +### 🔄 In Progress + +- **Testing**: Hyper-V host provisioning playbook not yet tested on actual host +- **VM Provisioning**: Ready to test once host is configured +- **AutoUnattend ISO**: Helper playbook exists but untested + +### ❌ Not Yet Implemented + +1. **Windows Baseline Configuration** (future role) + - Security hardening + - Monitoring agent installation + - Compliance scanning + +2. **ServiceNow CMDB Integration** + - Playbook exists (`sync-cmdb.yml`) but needs implementation + - Requires ServiceNow credentials and table configuration + +3. **Custom Execution Environment** + - Currently using `aap.toal.ca/ee-demo` + - Could build project-specific EE with all dependencies + +4. **Event-Driven Ansible** + - Phase 3 enhancement + - React to Hyper-V events, ServiceNow incidents, etc. + +5. **VM Templates** + - Sysprep and convert base VM to reusable template + - Faster provisioning than full install + +## Inventory Configuration + +### Hypervisor + +**Host**: `hyperv1.lan.toal.ca` +**IP**: `192.168.1.182` +**Connection**: WinRM over HTTP (NTLM auth, port 5985) +**Group**: `hyperv` + +**Inventory Location**: `/home/ptoal/Dev/inventories/toallab-inventory/static.yml` + +```yaml +hyperv: + hosts: + hyperv1.lan.toal.ca: + ansible_host: 192.168.1.182 +``` + +### Group Variables + +**Location**: `/home/ptoal/Dev/inventories/toallab-inventory/group_vars/` + +**hyperv/vars.yml**: +```yaml +default_vm_cpu_count: 2 +default_vm_memory_gb: 4 +default_vm_disk_size_gb: 60 +default_vm_switch: "External-NAT" # Changed from "Internal Switch" +vm_storage_path: "D:\\VMs" +iso_storage_path: "D:\\ISOs" +windows_server_iso: "D:\\ISOs\\Windows_Server_2022.iso" +``` + +**windows_servers/vars.yml**: +- Windows Update categories +- DNS servers +- Timezone settings +- Features to remove + +**web_servers/vars.yml**: +- IIS features list +- Application pool settings +- Website configuration + +### Host Variables + +**hyperv1.lan.toal.ca/vars.yml**: +```yaml +ansible_connection: winrm +ansible_winrm_transport: ntlm +ansible_winrm_server_cert_validation: ignore +ansible_port: 5985 +``` + +## Next Steps + +### Immediate (Next Session) + +1. **Configure Hyper-V Host** (if fresh install) + ```bash + source ~/.venv/ansible/bin/activate + cd /home/ptoal/Dev/Projects/HyperV + + # Test connectivity + ansible hyperv -m ansible.windows.win_ping + + # Configure host + ansible-playbook playbooks/provision-hyperv-host.yml + + # Verify switches + ansible-playbook playbooks/list-hyperv-switches.yml + ``` + +2. **Upload Windows Server ISO** + - Copy ISO to D:\ISOs\Windows_Server_2022.iso on Hyper-V host + - Or download directly on host + - Verify path matches `windows_server_iso` in group_vars + +3. **Test VM Provisioning** + ```bash + # Provision first test VM + ansible-playbook playbooks/provision-vm.yml \ + -e vm_name=TEST01 \ + -e vm_ip_address=192.168.100.10 + + # Monitor installation via Hyper-V console + # Wait for WinRM to become available (~20-30 min) + + # Test connectivity + ansible TEST01 -i "192.168.100.10," -m ansible.windows.win_ping + ``` + +4. **Add VM to Inventory** + ```bash + vi /home/ptoal/Dev/inventories/toallab-inventory/static.yml + # Add under web_servers or appropriate group + ``` + +5. **Test Application Deployment** + ```bash + # Deploy IIS + ansible-playbook playbooks/install-iis.yml --limit TEST01 + + # Verify + curl http://192.168.100.10 + ``` + +### Short Term (Next Few Sessions) + +1. **Resolve AutoUnattend Delivery** + - Test `create-autounattend-iso.yml` helper + - OR: Create custom Windows ISO with embedded autounattend.xml + - OR: Document manual second-DVD-drive approach + - Update provisioning playbook with working method + +2. **Create Windows Baseline Role** + ``` + roles/windows_baseline/ + ├── tasks/ + │ ├── main.yml + │ ├── security.yml + │ ├── monitoring.yml + │ └── compliance.yml + ├── templates/ + └── defaults/ + ``` + - Security hardening (CIS benchmarks subset) + - Time sync with domain/NTP + - Windows Update configuration + - Logging and auditing + - Monitoring agent (if available) + +3. **Implement ServiceNow CMDB Sync** + - Test ServiceNow API connectivity + - Implement `sync-cmdb.yml` playbook + - Create/update CI records + - Sync on VM create/update/delete + +4. **Test Workflow in AAP** + - Create job templates with surveys + - Test webhook integration + - Create workflow template (provision → baseline → deploy → CMDB) + +### Medium Term + +1. **VM Template Creation** + - Build golden image VM + - Sysprep and generalize + - Convert to template/library + - Create playbook to clone from template (faster than full install) + +2. **Backup and Recovery** + - Hyper-V checkpoint management playbook + - Export/import VM playbooks + - Backup scheduling + +3. **Network Configuration** + - VLAN tagging playbooks + - Multiple NIC configuration + - DNS/DHCP integration + +4. **Application Deployment** + - Expand beyond IIS demo + - SQL Server installation role + - Custom application deployment patterns + +### Long Term (Future Phases) + +1. **Event-Driven Ansible** + - ServiceNow incident → remediation playbook + - Hyper-V event monitoring → capacity management + - Windows Event Log → security response + +2. **Multi-Host Hyper-V** + - Cluster configuration + - Live migration support + - Shared storage + +3. **Advanced Features** + - Nested virtualization + - Container deployment on Windows VMs + - Azure Arc integration + +## Key Files Reference + +### Playbooks +``` +playbooks/ +├── provision-hyperv-host.yml # Configure Hyper-V host (one-time) +├── provision-vm.yml # Create Windows VMs +├── list-hyperv-switches.yml # Discover switches +├── create-autounattend-iso.yml # AutoUnattend helper +├── install-iis.yml # IIS deployment +├── patch-vms.yml # Windows Updates +└── sync-cmdb.yml # ServiceNow (stub) +``` + +### Configuration +``` +ansible.cfg # Points to toallab-inventory +collections/requirements.yml # Required collections +templates/autounattend.xml.j2 # Windows unattended install +``` + +### Documentation +``` +README.md # Project overview +QUICKSTART.md # Step-by-step guide +HANDOFF.md # This file +CLAUDE.md # Architecture details +playbooks/README-provision.md # Provisioning deep-dive +``` + +### Inventory (External) +``` +/home/ptoal/Dev/inventories/toallab-inventory/ +├── static.yml # Hypervisor and VMs +├── group_vars/ +│ ├── hyperv/vars.yml +│ ├── windows_servers/vars.yml +│ └── web_servers/vars.yml +└── host_vars/ + └── hyperv1.lan.toal.ca/vars.yml +``` + +## Quick Reference Commands + +### Setup +```bash +# Activate environment +source ~/.venv/ansible/bin/activate +cd /home/ptoal/Dev/Projects/HyperV + +# Test connectivity +ansible hyperv -m ansible.windows.win_ping + +# Configure host (one-time) +ansible-playbook playbooks/provision-hyperv-host.yml +``` + +### VM Operations +```bash +# List switches +ansible-playbook playbooks/list-hyperv-switches.yml + +# Provision VM +ansible-playbook playbooks/provision-vm.yml \ + -e vm_name=WEB01 \ + -e vm_ip_address=192.168.100.10 + +# List all VMs +ansible hyperv -m ansible.windows.win_shell \ + -a "Get-VM | Select-Object Name, State, CPUUsage | Format-Table" + +# VM power operations +ansible hyperv -m ansible.windows.win_shell -a "Start-VM -Name WEB01" +ansible hyperv -m ansible.windows.win_shell -a "Stop-VM -Name WEB01 -Force" +ansible hyperv -m ansible.windows.win_shell -a "Remove-VM -Name WEB01 -Force" +``` + +### Application Deployment +```bash +# Deploy IIS +ansible-playbook playbooks/install-iis.yml --limit WEB01 + +# Patch VMs +ansible-playbook playbooks/patch-vms.yml --limit windows_servers + +# Test connectivity to VM +ansible WEB01 -m ansible.windows.win_ping +``` + +### Troubleshooting +```bash +# Verbose output +ansible hyperv -m ansible.windows.win_ping -vvv + +# Check WinRM +ansible hyperv -m ansible.windows.win_shell -a "Get-Service WinRM" + +# Verify paths +ansible hyperv -m ansible.windows.win_stat -a "path=D:\\ISOs\\Windows_Server_2022.iso" + +# Get Hyper-V info +ansible hyperv -m ansible.windows.win_shell -a "Get-VMHost | ConvertTo-Json" +``` + +## Important Notes + +### Security Considerations +⚠️ **Current configuration is for DEMO/LAB only** + +**Current State**: +- WinRM over HTTP (not HTTPS) +- NTLM authentication (not Kerberos) +- Certificate validation disabled +- Permissive firewall rules +- No backup/DR +- No monitoring + +**For Production**: +- Enable HTTPS for WinRM (port 5986) +- Use Kerberos authentication +- Enable certificate validation +- Implement network segmentation +- Configure backup and DR +- Deploy monitoring/alerting +- Implement change management +- Regular security patching +- Compliance scanning + +### AutoUnattend.xml Gotchas + +1. **Delivery Methods** (in order of difficulty): + - Second DVD drive (easiest, use helper playbook) + - Rebuild ISO with autounattend in root (moderate) + - Floppy image (legacy, difficult) + +2. **Common Issues**: + - File not found → wrong location + - Interactive prompts → XML syntax error + - Network not configured → check IP settings in template + - WinRM not available → first logon commands didn't run + +3. **Debugging**: + - Check logs: `C:\Windows\Panther\setupact.log` + - Watch VM console during install + - Verify XML with validator before use + +### Variable Precedence Reminder + +``` +1. Extra vars (-e on command line) [highest] +2. Task vars (in playbook) +3. Host vars (host_vars/) +4. Group vars (group_vars/) +5. Role defaults +6. Inventory vars [lowest] +``` + +## Testing Checklist + +Before declaring completion: + +- [ ] Hyper-V host provisioning successful +- [ ] Virtual switches created and accessible +- [ ] VM provisioning creates VM successfully +- [ ] AutoUnattend.xml performs unattended install +- [ ] WinRM available after installation +- [ ] VM pingable from Ansible +- [ ] IIS deployment works +- [ ] Windows Update playbook works +- [ ] AAP job template with survey works +- [ ] Documentation is accurate + +## Questions for Next Session + +1. **Hyper-V Host Status**: + - Is hyperv1.lan.toal.ca a fresh install or already configured? + - Are virtual switches already created? + - Is storage already set up? + +2. **ISO Availability**: + - Is Windows Server 2022 ISO available? + - Where is it located? + +3. **Network**: + - What network should VMs be on? + - Static IPs or DHCP? + - Internet access required? + +4. **Scope**: + - How many VMs to provision initially? + - What applications to deploy? + - Integration with existing systems needed? + +## Contact / References + +**Project Location**: `/home/ptoal/Dev/Projects/HyperV` +**Inventory Location**: `/home/ptoal/Dev/inventories/toallab-inventory` +**Virtual Environment**: `~/.venv/ansible` +**Execution Environment**: `aap.toal.ca/ee-demo` + +**Key Technologies**: +- Ansible Core 2.15+ +- Ansible Automation Platform 2.x +- Microsoft Hyper-V (Windows Server 2019/2022) +- Windows Server 2019/2022 + +**Collections Used**: +- ansible.windows (>=2.0.0) +- community.windows (>=2.0.0) +- servicenow.itsm (>=2.0.0) + +--- + +**Ready for next session**: Yes +**Blockers**: None - ready to test on actual Hyper-V host +**Recommended first task**: Run `provision-hyperv-host.yml` to set up the environment diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..ba70ad2 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,394 @@ +# Hyper-V Automation - Quick Start Guide + +This guide walks through setting up a Hyper-V host and provisioning Windows VMs using Ansible. + +## Prerequisites + +- Fresh Windows Server 2019/2022 installation +- Network connectivity from Ansible control node +- Windows Server ISO image + +## Step 1: Initial Hyper-V Host Setup + +### 1.1 Configure WinRM on Hyper-V Host + +On the Windows Server (as Administrator): + +```powershell +# Enable PowerShell remoting +Enable-PSRemoting -Force + +# Configure WinRM for Ansible +winrm quickconfig -transport:http +Set-Item WSMan:\localhost\Service\Auth\Basic -Value $true +Set-Item WSMan:\localhost\Service\Auth\NTLM -Value $true + +# Allow unencrypted traffic for initial setup (demo only!) +Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value $true + +# Configure firewall +Enable-NetFirewallRule -Name "WINRM-HTTP-In-TCP" +``` + +### 1.2 Test Ansible Connectivity + +```bash +# Activate virtual environment +source ~/.venv/ansible/bin/activate + +# Test connection +ansible hyperv -m ansible.windows.win_ping + +# If successful, you should see: +# hyperv1.lan.toal.ca | SUCCESS => { +# "changed": false, +# "ping": "pong" +# } +``` + +## Step 2: Provision Hyper-V Host + +This playbook configures the Hyper-V host with: +- Hyper-V role and management tools +- Storage directories for VMs and ISOs +- Virtual switches (External-NAT and Internal-Lab) +- NAT networking for VM internet access +- WinRM security hardening +- Demo-appropriate settings + +```bash +# Run the provisioning playbook +ansible-playbook playbooks/provision-hyperv-host.yml + +# This takes 5-10 minutes and may reboot the host +``` + +### What Gets Created + +``` +D:\ +├── VMs\ # VM storage +├── ISOs\ # ISO images +└── Templates\ # VHD templates (future) + +Virtual Switches: +├── External-NAT # For internet access via NAT +└── Internal-Lab # Isolated internal network + +NAT Network: 192.168.100.0/24 +Gateway: 192.168.100.1 +``` + +## Step 3: Upload Windows ISO + +### Option 1: Manual Upload + +```powershell +# On Hyper-V host or via RDP +# Copy ISO to D:\ISOs\Windows_Server_2022.iso +``` + +### Option 2: Ansible Copy (slower) + +```bash +# From Ansible control node +ansible hyperv -m ansible.windows.win_copy \ + -a "src=/path/to/Windows_Server_2022.iso dest=D:\\ISOs\\Windows_Server_2022.iso" +``` + +### Option 3: Direct Download (if internet available) + +```bash +ansible hyperv -m ansible.windows.win_get_url \ + -a "url=https://your-iso-source/Windows_Server_2022.iso dest=D:\\ISOs\\Windows_Server_2022.iso" +``` + +## Step 4: Verify Configuration + +### 4.1 List Available Virtual Switches + +```bash +ansible-playbook playbooks/list-hyperv-switches.yml +``` + +Expected output: +``` +Available Virtual Switches: +- External-NAT +- Internal-Lab +``` + +### 4.2 Verify ISO Path + +```bash +ansible hyperv -m ansible.windows.win_stat \ + -a "path=D:\\ISOs\\Windows_Server_2022.iso" +``` + +## Step 5: Provision Your First VM + +### 5.1 Basic VM Provisioning + +```bash +ansible-playbook playbooks/provision-vm.yml \ + -e vm_name=WEB01 \ + -e vm_ip_address=192.168.100.10 +``` + +### 5.2 Custom VM Configuration + +```bash +ansible-playbook playbooks/provision-vm.yml \ + -e vm_name=APP01 \ + -e vm_ip_address=192.168.100.20 \ + -e vm_cpu_count=4 \ + -e vm_memory_gb=8 \ + -e vm_disk_size_gb=100 +``` + +### 5.3 What Happens + +1. ✓ Creates VM with specified resources +2. ✓ Generates autounattend.xml for unattended installation +3. ✓ Attaches Windows Server ISO +4. ✓ Starts VM +5. ⏸ Waits for you to verify installation (15-30 min) +6. ✓ Verifies WinRM connectivity + +## Step 6: Monitor Installation + +### Via Hyper-V Manager + +```powershell +# On Hyper-V host +vmconnect.exe localhost WEB01 +``` + +### Via PowerShell + +```bash +ansible hyperv -m ansible.windows.win_shell \ + -a "Get-VM WEB01 | Select-Object Name, State, CPUUsage, Uptime" +``` + +### Installation Progress + +- **0-5 min**: Windows Setup boots from ISO +- **5-20 min**: Windows installation (with autounattend.xml) +- **20-25 min**: First boot and configuration +- **25-30 min**: WinRM configuration completes + +## Step 7: Add VM to Inventory + +Once installation completes: + +```bash +# Edit inventory +vi /home/ptoal/Dev/inventories/toallab-inventory/static.yml + +# Add under web_servers: + web_servers: + hosts: + WEB01: + ansible_host: 192.168.100.10 +``` + +## Step 8: Verify VM Connectivity + +```bash +# Test WinRM +ansible WEB01 -m ansible.windows.win_ping + +# Gather facts +ansible WEB01 -m ansible.windows.setup +``` + +## Step 9: Deploy Applications + +### Install IIS + +```bash +ansible-playbook playbooks/install-iis.yml --limit WEB01 +``` + +### Apply Windows Updates + +```bash +ansible-playbook playbooks/patch-vms.yml --limit WEB01 +``` + +## Common Tasks + +### List All VMs + +```bash +ansible hyperv -m ansible.windows.win_shell \ + -a "Get-VM | Select-Object Name, State, CPUUsage | Format-Table" +``` + +### Start/Stop VM + +```bash +# Start +ansible hyperv -m ansible.windows.win_shell -a "Start-VM -Name WEB01" + +# Stop +ansible hyperv -m ansible.windows.win_shell -a "Stop-VM -Name WEB01" + +# Shutdown gracefully +ansible hyperv -m ansible.windows.win_shell -a "Stop-VM -Name WEB01 -Force" +``` + +### Delete VM + +```bash +ansible hyperv -m ansible.windows.win_shell -a "Remove-VM -Name WEB01 -Force" +``` + +### Check VM State + +```bash +ansible hyperv -m ansible.windows.win_shell \ + -a "Get-VM WEB01 | ConvertTo-Json" +``` + +## Troubleshooting + +### WinRM Connection Failed + +**Problem**: `unreachable` or `connection timeout` + +**Solutions**: +1. Verify firewall allows WinRM: + ```powershell + Get-NetFirewallRule -Name "WINRM-HTTP-In-TCP" | Select-Object Name, Enabled + ``` + +2. Check WinRM service: + ```powershell + Get-Service WinRM + winrm enumerate winrm/config/listener + ``` + +3. Test from Ansible host: + ```bash + ansible hyperv -m ansible.windows.win_ping -vvv + ``` + +### Virtual Switch Not Found + +**Problem**: `Hyper-V was unable to find a virtual switch` + +**Solutions**: +1. List available switches: + ```bash + ansible-playbook playbooks/list-hyperv-switches.yml + ``` + +2. Update group_vars or pass correct switch: + ```bash + -e vm_switch="External-NAT" + ``` + +3. Create missing switch on Hyper-V host: + ```powershell + New-VMSwitch -Name "External-NAT" -SwitchType External -NetAdapterName "Ethernet" + ``` + +### AutoUnattend Not Working + +**Problem**: Windows installation shows interactive prompts + +**Solutions**: +1. Verify autounattend.xml was created: + ```bash + ansible hyperv -m ansible.windows.win_stat \ + -a "path=D:\\VMs\\WEB01\\autounattend.xml" + ``` + +2. Check XML syntax in file + +3. Mount as second DVD drive (requires manual step or helper playbook) + +4. Review Windows Setup logs on VM: + ``` + C:\Windows\Panther\setupact.log + ``` + +### VM Won't Start + +**Problem**: `failed to start` or `invalid configuration` + +**Solutions**: +1. Check VM configuration: + ```bash + ansible hyperv -m ansible.windows.win_shell \ + -a "Get-VM WEB01 | Select-Object *" + ``` + +2. Verify VHD exists: + ```bash + ansible hyperv -m ansible.windows.win_stat \ + -a "path=D:\\VMs\\WEB01\\WEB01.vhdx" + ``` + +3. Check Hyper-V event logs: + ```powershell + Get-EventLog -LogName "Microsoft-Windows-Hyper-V-*" -Newest 20 + ``` + +## Directory Structure + +``` +/home/ptoal/Dev/Projects/HyperV/ +├── playbooks/ +│ ├── provision-hyperv-host.yml # Configure Hyper-V host +│ ├── provision-vm.yml # Create VMs +│ ├── list-hyperv-switches.yml # List available switches +│ ├── install-iis.yml # Deploy IIS +│ └── patch-vms.yml # Windows Updates +├── templates/ +│ └── autounattend.xml.j2 # Unattended install template +└── ansible.cfg # Project config + +/home/ptoal/Dev/inventories/toallab-inventory/ +├── static.yml # Static inventory +├── group_vars/ +│ ├── hyperv/vars.yml # Hyper-V defaults +│ └── windows_servers/vars.yml # Windows defaults +└── host_vars/ + └── hyperv1.lan.toal.ca/vars.yml # Host-specific config +``` + +## Next Steps + +1. **Create VM templates** - Sysprep a base VM and convert to template +2. **Implement backups** - Use Hyper-V checkpoints or backup playbooks +3. **Configure monitoring** - Integrate with Grafana/Prometheus +4. **Domain join** - Add VMs to Active Directory +5. **Application deployment** - Deploy real applications beyond IIS demo +6. **CMDB sync** - Implement ServiceNow integration +7. **Event-Driven Ansible** - React to Hyper-V events automatically + +## Production Considerations + +⚠️ **This is a demo configuration.** For production: + +- [ ] Enable HTTPS for WinRM (not HTTP) +- [ ] Use Kerberos authentication (not NTLM/Basic) +- [ ] Configure storage on SAN/redundant storage +- [ ] Implement Hyper-V clustering for HA +- [ ] Network segmentation and VLANs +- [ ] Security hardening (CIS benchmarks) +- [ ] Backup and disaster recovery +- [ ] Monitoring and alerting +- [ ] Change management and approvals +- [ ] Documentation and runbooks + +## Resources + +- [Hyper-V Documentation](https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/) +- [Ansible Windows Guide](https://docs.ansible.com/ansible/latest/os_guide/windows_usage.html) +- [WinRM Setup](https://docs.ansible.com/ansible/latest/os_guide/windows_setup.html) +- [Project CLAUDE.md](CLAUDE.md) - Architecture documentation +- [Provisioning Guide](playbooks/README-provision.md) - Detailed VM provisioning diff --git a/playbooks/list-hyperv-switches.yml b/playbooks/list-hyperv-switches.yml new file mode 100644 index 0000000..566b1a9 --- /dev/null +++ b/playbooks/list-hyperv-switches.yml @@ -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=''" + - "2. Update default in: /home/ptoal/Dev/inventories/toallab-inventory/group_vars/hyperv/vars.yml" + - "" + - "Current default: {{ default_vm_switch }}" diff --git a/playbooks/provision-hyperv-host.yml b/playbooks/provision-hyperv-host.yml new file mode 100644 index 0000000..267f57d --- /dev/null +++ b/playbooks/provision-hyperv-host.yml @@ -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