The Problem: Windows Dev Machine, but CI/CD Runs Linux
Almost every developer working on Windows has run into this scenario: the CI/CD pipeline runs Ubuntu, the production server is Debian, but the dev machine is Windows 11. The result? “Works on my machine” but the build fails on the server, or a bash script runs fine on your colleague’s Mac but throws an error on the very first line.
The go-to solution is usually WSL2 — and yes, WSL2 is much better than it used to be. But WSL2 isn’t a real VM. There’s no independent kernel, you can’t test the network stack, and you can’t simulate a multi-host environment. Not to mention some services that require a full systemd setup simply won’t install.
I run a homelab with Proxmox VE managing 12 VMs and containers — it’s my playground for testing everything before pushing to production. But I don’t always want to SSH into a homelab server just to quickly test a script. When you need a Linux environment right on your Windows machine without installing extra software, Hyper-V is already there — you just need to turn it on.
Why Hyper-V Instead of VirtualBox?
VirtualBox and VMware Workstation are both Type-2 hypervisors — they run on top of Windows like regular applications. Hyper-V is a Type-1 hypervisor (bare-metal), meaning your Windows 11 actually runs on top of Hyper-V, not the other way around.
A direct comparison on the same hardware:
- Performance: Hyper-V VMs use CPU virtualization directly, bypassing the emulation layer. Ubuntu 24.04 boots in around 8–10 seconds compared to 20–25 seconds on VirtualBox.
- PowerShell Integration: The full VM lifecycle — create, snapshot, clone, delete — can all be managed via cmdlets, making it ideal for automation scripts.
- No conflicts: Running Hyper-V no longer requires disabling WSL2, Docker Desktop, or Android Emulator (Windows 11 fixed this as of build 22H2).
One caveat: Hyper-V is only available on Windows 11 Pro, Enterprise, and Education. If you’re on Windows 11 Home, you’ll need to upgrade your license or fall back to VirtualBox.
Checking and Enabling Hyper-V
Check CPU Virtualization Support
Run PowerShell as Administrator:
# Check if the CPU supports virtualization
Get-ComputerInfo -Property HyperV*
# Or use systeminfo for a quick overview
systeminfo | findstr /C:"Hyper-V"
If you see HyperVisorPresent: False and your CPU supports virtualization, go into BIOS/UEFI and enable Intel VT-x (Intel) or AMD-V/SVM (AMD). The menu name varies by motherboard but is usually under the Advanced → CPU Configuration tab.
Enable Hyper-V via PowerShell
# Enable Hyper-V and its companion tools (requires Admin rights and a reboot)
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All -All
# After rebooting, verify the installation
Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V | Select-Object State
Once the machine restarts, Hyper-V Manager will appear in the Start Menu and the Hyper-V cmdlets will be available in PowerShell immediately.
Creating a Linux Virtual Machine with PowerShell
Setup: Create a Virtual Switch
Before creating a VM, you need a virtual switch for network connectivity. There are two common types:
- External: The VM gets its own IP on your home LAN and is reachable from other devices. Use this when you need to test your app from a phone or another machine.
- Internal: The VM can only communicate with the Windows host and isn’t exposed to the outside network. Safer for dev environments.
# List available physical network adapters
Get-NetAdapter | Where-Object {$_.Status -eq "Up"}
# Create an External Switch (replace "Wi-Fi" with your actual adapter name)
New-VMSwitch -Name "ExternalSwitch" -NetAdapterName "Wi-Fi" -AllowManagementOS $true
# Or create an Internal Switch (no physical adapter needed)
New-VMSwitch -Name "InternalSwitch" -SwitchType Internal
Create an Ubuntu 24.04 VM
Download the Ubuntu Server ISO, then run the following commands in order:
# Define variables for cleaner code
$VMName = "ubuntu-dev"
$VMPath = "D:\Hyper-V\VMs"
$ISOPath = "D:\ISOs\ubuntu-24.04-live-server-amd64.iso"
$VHDSize = 40GB
# Create a Generation 2 VM (UEFI, for modern Linux)
New-VM `
-Name $VMName `
-Path $VMPath `
-Generation 2 `
-MemoryStartupBytes 2GB `
-SwitchName "ExternalSwitch"
# Create a virtual hard disk (dynamic VHDX)
New-VHD -Path "$VMPath\$VMName\$VMName.vhdx" -SizeBytes $VHDSize -Dynamic
Add-VMHardDiskDrive -VMName $VMName -Path "$VMPath\$VMName\$VMName.vhdx"
# Attach the ISO
Add-VMDvdDrive -VMName $VMName -Path $ISOPath
# Set DVD as the first boot device
$DVDDrive = Get-VMDvdDrive -VMName $VMName
Set-VMFirmware -VMName $VMName -FirstBootDevice $DVDDrive
# Disable Secure Boot (required for Linux without a shim)
Set-VMFirmware -VMName $VMName -EnableSecureBoot Off
# Enable Dynamic Memory (saves RAM when the VM is idle)
Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $true -MinimumBytes 512MB -MaximumBytes 4GB
# Assign 2 CPU cores
Set-VMProcessor -VMName $VMName -Count 2
# Start the VM
Start-VM -Name $VMName
# Open the console for installation
VMConnect localhost $VMName
Important Tip: Enable Integration Services
A commonly skipped but critical step: inside the freshly installed Ubuntu VM, run the commands below to enable Hyper-V Integration Services. Without them, copy-paste won’t work, the VM clock will drift, and the host won’t be able to perform a graceful shutdown:
# On the Ubuntu guest
sudo apt update && sudo apt install -y linux-image-virtual linux-tools-virtual linux-cloud-tools-virtual
sudo systemctl enable hv_kvp_daemon hv_vss_daemon
sudo reboot
Day-to-Day VM Management with PowerShell
This is the part I use most — instead of clicking through Hyper-V Manager, write a script once and use it forever:
# View the status of all VMs
Get-VM | Select-Object Name, State, CPUUsage, MemoryAssigned, Uptime
# Start / Stop / Suspend
Start-VM -Name "ubuntu-dev"
Stop-VM -Name "ubuntu-dev" -Force # Hard power off
Stop-VM -Name "ubuntu-dev" -TurnOff:$false # Clean shutdown (requires integration services)
Suspend-VM -Name "ubuntu-dev" # Hibernate the VM
# Snapshots (checkpoints)
Checkpoint-VM -Name "ubuntu-dev" -SnapshotName "before-upgrade-$(Get-Date -Format 'yyyyMMdd')"
Get-VMSnapshot -VMName "ubuntu-dev"
Restore-VMSnapshot -VMName "ubuntu-dev" -Name "before-upgrade-20260616"
# Delete old snapshots (free up disk space)
Remove-VMSnapshot -VMName "ubuntu-dev" -Name "before-upgrade-20260616"
# Clone a VM via export/import
Export-VM -Name "ubuntu-dev" -Path "D:\Hyper-V\Exports"
Import-VM -Path "D:\Hyper-V\Exports\ubuntu-dev\Virtual Machines\*.vmcx" -Copy -GenerateNewId
The Best Approach: Combining Hyper-V with Automation Scripts
Creating a VM manually like that takes 15–20 minutes every time — mostly clicking through wizards. I wrapped everything into a New-DevVM.ps1 script that gets the whole process down to about 2 minutes:
# New-DevVM.ps1 — Quickly spin up an Ubuntu dev environment VM
param(
[string]$Name = "ubuntu-$(Get-Date -Format 'MMdd')",
[int]$CPUCount = 2,
[long]$RAMBytes = 2GB,
[long]$DiskBytes = 40GB,
[string]$ISOPath = "D:\ISOs\ubuntu-24.04-live-server-amd64.iso",
[string]$VMBasePath = "D:\Hyper-V\VMs",
[string]$SwitchName = "InternalSwitch"
)
$VHDPath = "$VMBasePath\$Name\$Name.vhdx"
New-VM -Name $Name -Path $VMBasePath -Generation 2 -MemoryStartupBytes $RAMBytes -SwitchName $SwitchName
New-VHD -Path $VHDPath -SizeBytes $DiskBytes -Dynamic
Add-VMHardDiskDrive -VMName $Name -Path $VHDPath
Add-VMDvdDrive -VMName $Name -Path $ISOPath
Set-VMFirmware -VMName $Name -FirstBootDevice (Get-VMDvdDrive -VMName $Name) -EnableSecureBoot Off
Set-VMProcessor -VMName $Name -Count $CPUCount
Set-VMMemory -VMName $Name -DynamicMemoryEnabled $true -MinimumBytes 512MB -MaximumBytes ($RAMBytes * 2)
Write-Host "[OK] VM '$Name' created. Starting..."
Start-VM -Name $Name
VMConnect localhost $Name
Call it like this:
# Create a VM with an auto-generated name
.\New-DevVM.ps1
# Create a custom VM
.\New-DevVM.ps1 -Name "test-nginx" -CPUCount 4 -RAMBytes 4GB
Connect via SSH Instead of the Console
The Hyper-V Manager console is pretty clunky — there’s input lag and you can’t scroll through command history. Enable SSH inside the VM and connect from Windows Terminal instead:
# On the Ubuntu guest — enable SSH
sudo apt install -y openssh-server
sudo systemctl enable --now ssh
# Get the VM's IP address
ip -4 addr show eth0
# On the Windows host — get the VM's IP via PowerShell
(Get-VMNetworkAdapter -VMName "ubuntu-dev").IPAddresses
# SSH into the VM
ssh [email protected]
Common Errors and How to Fix Them
- “Hyper-V cannot be installed: A hypervisor is already running”: This is usually caused by Docker Desktop using the WSL2 backend. Try restarting your machine or check Task Manager to see if any
vmwp.exeprocesses are running. - VM won’t boot from ISO after creation: With Generation 2 and Linux, remember to disable Secure Boot (
Set-VMFirmware -EnableSecureBoot Off) or select the appropriate CA certificate. - VM loses network after suspend: An old Hyper-V bug that still pops up occasionally. Fix it by restarting the network adapter inside the VM (
sudo ip link set eth0 down && sudo ip link set eth0 up) or by loading thehv_netvscdriver. - VHDX bloats after many snapshots: Run
Optimize-VHD -Path path\to.vhdx -Mode Fullperiodically to compact the file.
