Skip to content

Instantly share code, notes, and snippets.

@rrmistry
Last active October 14, 2025 18:22
Show Gist options
  • Select an option

  • Save rrmistry/04d282751122fd41fc5ebb9962e60602 to your computer and use it in GitHub Desktop.

Select an option

Save rrmistry/04d282751122fd41fc5ebb9962e60602 to your computer and use it in GitHub Desktop.
Run Ubuntu VM using Qemu in Windows User Space (without admin rights)
$ErrorActionPreference = "Stop"
If (-not (Get-Command -Name "scoop" -ErrorAction "SilentlyContinue")) {
# Install Scoop
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
Invoke-RestMethod get.scoop.sh | Invoke-Expression
}
If (-not (Get-Command -Name "qemu-system-x86_64" -ErrorAction "SilentlyContinue")) {
# Install qemu
scoop install qemu
}
$qemuFolder = "$ENV:USERPROFILE/qemu/"
If (-Not (Test-Path -Path $qemuFolder -ErrorAction "SilentlyContinue")) {
New-Item -Path $qemuFolder -ItemType "Directory" -Force -Verbose
}
# Define paths and settings
$qemuPath = "qemu-system-x86_64.exe" # Adjust this path to where your QEMU executable is located
$qemuImgPath = "qemu-img.exe" # Adjust this path to where your QEMU image utility is located
$diskImagePath = "$qemuFolder/linux.qcow2" # Path where the VM's disk image will be created
$memorySize = "4096M" # Memory size (e.g., 2048M for 2GB)
$cpuCount = 8 # Number of CPUs
$isNewBootDisk = $false
If (-not (Test-Path -Path $diskImagePath -ErrorAction SilentlyContinue)) {
$qemuImageUrl = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-amd64.qcow2"
# Download the QEMU Image file
Invoke-WebRequest -Uri $qemuImageUrl -OutFile $diskImagePath -Verbose
Write-Host "Download completed! The QEMU Image is saved at: $diskImagePath" -ForegroundColor "Green"
# Resize to add working space (e.g., add 20GB)
& $qemuImgPath resize $diskImagePath +100G
$isNewBootDisk = $true
}
# Run QEMU with the specified settings and setup Ubuntu automatically
$argumentList = @(
"-m", $memorySize,
"-smp", $cpuCount,
"-drive", "file=$diskImagePath,format=qcow2,if=virtio",
"-boot", "d",
"-netdev", "user,id=mynet0,hostfwd=tcp::8022-:22,hostfwd=tcp::4180-:4180,hostfwd=tcp::3000-:3000,hostfwd=tcp::9229-:9229,hostfwd=tcp::8000-:8000",
"-device", "virtio-net,netdev=mynet0",
"-accel", "whpx", # Hardware acceleration on Windows using Hypbervisor
"-no-reboot",
"-nographic"
)
function Test-QEMUProcessExists {
param(
[string]$QemuPath,
[string[]]$ArgumentList
)
$qemuExeName = [System.IO.Path]::GetFileNameWithoutExtension($QemuPath)
$existingProcesses = Get-Process -Name $qemuExeName -ErrorAction SilentlyContinue
# Convert argument list to normalized string for comparison
$newArgsString = ($ArgumentList -join ' ') -replace '\s+', ' '
foreach ($proc in $existingProcesses) {
try {
$wmiProcess = Get-CimInstance Win32_Process -Filter "ProcessId = $($proc.Id)"
$commandLine = $wmiProcess.CommandLine
# Extract just the arguments part (everything after the executable)
$executablePath = $wmiProcess.ExecutablePath
if ($commandLine -match [regex]::Escape($executablePath)) {
$existingArgs = $commandLine.Substring($commandLine.IndexOf($executablePath) + $executablePath.Length).Trim().Trim('"').Trim()
} else {
continue
}
# Normalize whitespace for comparison
$normalizedExistingArgs = $existingArgs -replace '\s+', ' '
$normalizedNewArgs = $newArgsString -replace '\s+', ' '
if ($normalizedExistingArgs -eq $normalizedNewArgs) {
return @{
Found = $true
ProcessId = $proc.Id
CommandLine = $commandLine
}
}
}
catch {
continue
}
}
return @{ Found = $false }
}
# # Run QEMU
# Start-Process -FilePath $qemuPath -ArgumentList $argumentList -NoNewWindow -Wait -PassThru
# Check for existing process
$result = Test-QEMUProcessExists -QemuPath $qemuPath -ArgumentList $argumentList
if ($result.Found) {
Write-Host "Existing QEMU process found!" -ForegroundColor Green
Write-Host "PID: $($result.ProcessId)" -ForegroundColor Yellow
Write-Host "Command: $($result.CommandLine)" -ForegroundColor Cyan
} else {
Write-Host "No matching process found. Launching QEMU..." -ForegroundColor Yellow
$newProcess = $(Start-Process -FilePath $qemuPath -ArgumentList $argumentList -WindowStyle Hidden -Verbose)
Write-Host "Started new process with PID: $($newProcess)" -ForegroundColor Green
$result = Test-QEMUProcessExists -QemuPath $qemuPath -ArgumentList $argumentList
if ($result.Found) {
Write-Host "Existing QEMU process found!" -ForegroundColor Green
Write-Host "PID: $($result.ProcessId)" -ForegroundColor Yellow
Write-Host "Command: $($result.CommandLine)" -ForegroundColor Cyan
} else {
Write-Host "Warning! QEMU is not running!" -ForegroundColor Yellow
}
}
# Install VS Code CLI
apk add gcompat libstdc++ curl
curl -sL "https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64" --output /tmp/vscode-cli.tar.gz \
&& tar -xf /tmp/vscode-cli.tar.gz -C /usr/bin \
&& rm /tmp/vscode-cli.tar.gz
# Setup script for quick access to code server
echo "code serve-web --host 0.0.0.0 --port 8000 --without-connection-token --accept-server-license-terms" > ~/code-server.sh
chmod u+x ~/code-server.sh
# Edit sshd_config
vi /etc/ssh/sshd_config
# # Change this line
# PermitRootLogin yes
# AllowTcpForwarding yes
# Restart sshd service
rc-service sshd restart
# Install docker
apk add docker
apk add docker-cli-compose
# Run docker daemon in the background
addgroup ${USER} docker
rc-update add docker default
service docker start
rc-update add cgroups
# Install Python
apk add python3 py3-pip
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment