Automated Windows 11 development VM setup using Vagrant and VirtualBox, designed for developers who need a clean, reproducible Windows development environment.
This project provides a fully automated way to provision a Windows 11 virtual machine with proper security practices (no hardcoded passwords), a dedicated development drive, and common development tools pre-installed.
- 🔧 500GB Dev Drive (D:) - Dedicated NTFS volume optimized for development work, persists across VM rebuilds
- 🔐 Secure credential management - Random passwords generated locally, never committed to source control
- 👥 Dual user setup -
adminaccount for WinRM management,useraccount for daily development - 🛡️ UAC configured - User has admin rights with one-click elevation for installs and system changes
- ⚡ Fully automated provisioning - One-command setup with multi-phase credential switching
- 🎯 Auto-login configured - VM boots directly into the
useraccount with a welcome screen - 📦 Pre-installed development tools - Git, Node.js, Python, and more via WinGet
- 🔄 Guest Additions - Automatically managed via vagrant-vbguest plugin
-
VirtualBox - Virtualization platform (installed on the host)
-
Vagrant - VM orchestration tool (installed on the host)
-
GitHub Personal Access Token - Required for WinGet installation to avoid API rate limiting
Create a
.envfile in the project root with your GitHub token:echo "GITHUB_TOKEN=$(gh auth token)" > .env
Or manually create
.envwith:GITHUB_TOKEN=your_token_here
bash ./vagrant_provision.bashThis automated script:
- Auto-installs
vagrant-vbguestplugin if needed - Generates random passwords (stored in
.credentials/) - Provisions the VM through three phases with automatic reloads
- Installs development tools (see below)
- Configures autologon for the
useraccount
After provisioning: View the VM GUI to see auto-login and welcome popup.
Installed tools: See provision_install_tools.ps1 for the full list.
The provisioning happens in three phases with automatic credential switching:
| Phase | Credentials | Actions |
|---|---|---|
| Phase 1 | vagrant:vagrant |
Set up 500GB Dev Drive (reuses existing), create admin and user accounts, install WinGet, create admin-ready flag, reload VM |
| Phase 2 | vagrant:vagrant |
Install Git and development tools via WinGet, reload VM |
| Phase 3 | admin:ADMIN_PASSWORD |
Configure UAC for user account, setup welcome popup, remove vagrant user, configure autologon for user |
The Vagrantfile detects the admin-ready flag and switches credentials automatically between phases.
If you prefer step-by-step control instead of the automated script:
# 1. Create .env file with GITHUB_TOKEN (see Prerequisites)
# 2. Generate credentials (optional, auto-generated if missing)
bash ./generate-credentials.sh
# 3. Run Phases 1 & 2 (user creation, WinGet, tools installation)
./vagrant.sh up
# 4. Run Phase 3 (switch to admin credentials, finalize setup)
./vagrant.sh reload --provision
# 5. Final reload to activate autologon
./vagrant.sh reloadPasswords are stored in .credentials/ (git-ignored):
admin.txt- Admin user password (for WinRM access)user.txt- User account password (for autologon)
Credentials are passed to VM only via environment variables, never hardcoded.
The user account is a member of the Administrators group but protected by User Account Control:
- Installing software - UAC prompts "Do you want to allow this app to make changes?" - click Yes
- System modifications - Protected by UAC consent prompts (no password required, just click confirmation)
- Development work - Runs with standard user privileges until elevation is needed
- Security + Convenience - Prevents accidental system changes while allowing one-click elevation
Why two accounts?
user- Your daily development account (auto-login, UAC-protected admin rights)admin- Reserved for Vagrant/WinRM infrastructure operations only
This setup follows Windows development best practices: admin privileges when needed, UAC protection always active.
The vagrant.sh wrapper ensures correct env setup.
# Access VM via WinRM as admin
./vagrant.sh winrm
# Check VM status
./vagrant.sh status
# Shutdown VM
./vagrant.sh halt
# Start existing VM
./vagrant.sh up
# Destroy and recreate from scratch
./vagrant.sh destroy -f
bash ./vagrant_provision.bashNote: The Dev Drive (devdrive.vdi) persists when you destroy the VM, so your data on the D: drive is preserved. To completely wipe everything including the Dev Drive, manually delete devdrive.vdi before recreating:
./vagrant.sh destroy -f
rm devdrive.vdi
bash ./vagrant_provision.bashMissing credentials error:
bash ./generate-credentials.shStart over (destroy and recreate, preserves Dev Drive data):
./vagrant.sh destroy -f
bash ./vagrant_provision.bashCheck if admin-ready flag exists:
ls synced/admin-readyVerify UAC settings (from within VM):
Open PowerShell as administrator and run:
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" | Select-Object EnableLUA, ConsentPromptBehaviorAdminExpected output: EnableLUA = 1, ConsentPromptBehaviorAdmin = 5
Check if user is in Administrators group (from within VM):
Get-LocalGroupMember -Group "Administrators"Should show both admin and user accounts.
Reset Dev Drive (delete all persistent data):
If you want to start with a fresh Dev Drive:
./vagrant.sh destroy -f
rm devdrive.vdi
bash ./vagrant_provision.bashNuclear cleanup (stuck provisioning / VirtualBox conflicts):
If you encounter VirtualBox UUID conflicts, inaccessible VMs, or stuck provisioning:
# 1. Destroy VM and preserve Dev Drive
./vagrant.sh destroy -f
# 2. Remove Dev Drive disk
rm -f devdrive.vdi
# 3. Clean up stale VirtualBox VM directories
rm -rf /home/felix/.virtualbox_vms/Windows\ Development\ Environment
rm -rf /home/felix/.virtualbox_vms/windows-11-*
# 4. Verify no VMs remain registered
VBoxManage list vms # Should be empty
# 5. Start fresh
bash ./vagrant_provision.bashThis resolves issues like:
Could not rename the directory ... (VERR_ALREADY_EXISTS)UUID {xxx} does not match the value {yyy} stored in the media registry"<inaccessible>"VMs inVBoxManage list vms- Stuck provisioning phases
.
├── Vagrantfile # Main Vagrant configuration
├── vagrant.sh # Wrapper script for Vagrant commands
├── vagrant_provision.bash # Automated provisioning orchestrator
├── generate-credentials.sh # Random password generator
├── provision_*.ps1 # PowerShell provisioning scripts
├── devdrive.vdi # Persistent Dev Drive disk (git-ignored, survives VM destroy)
├── synced/ # Shared folder between host and VM
│ ├── admin-ready # Flag file for phase detection
└── .credentials/ # Generated passwords (git-ignored)
Edit provision_install_tools.ps1 and add your desired WinGet package IDs:
winget install --id YourPackage.ID --silent --accept-source-agreements --accept-package-agreementsModify the --size parameter (in MB) in the VBoxManage createhd command in the Vagrantfile. Then delete the existing devdrive.vdi and reprovision to create a new disk with the updated size.
Contributions are welcome! This project is in the public domain (see License below).
If you have improvements or bug fixes:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
Feel free to open issues for bugs, feature requests, or questions.
This project is released into the public domain under the Unlicense.
You are free to use, modify, and distribute this code for any purpose without restriction.
- Built with Vagrant and VirtualBox
- Uses WinGet for package management
- Inspired by the need for reproducible Windows development environments