Skip to content

Instantly share code, notes, and snippets.

@johnazariah
Last active November 27, 2025 04:52
Show Gist options
  • Select an option

  • Save johnazariah/b85448f097702e95b330e9b284e78a04 to your computer and use it in GitHub Desktop.

Select an option

Save johnazariah/b85448f097702e95b330e9b284e78a04 to your computer and use it in GitHub Desktop.
Dev Machine Setup

Dev Environment Setup

Cross-platform dev environment setup using PowerShell Core.

Quick Start

Windows

# Run as Administrator - you'll be prompted for Username and Email
$u = Read-Host "Username"; $e = Read-Host "Email"
iwr https://gist.githubusercontent.com/johnazariah/b85448f097702e95b330e9b284e78a04/raw/bootstrap-windows.ps1 -OutFile $env:TEMP\bootstrap.ps1; & $env:TEMP\bootstrap.ps1 -Username $u -Email $e

macOS

curl -fsSL https://gist.githubusercontent.com/johnazariah/b85448f097702e95b330e9b284e78a04/raw/bootstrap-macos.sh | bash -s -- "Your Name" "your@email.com"

Linux

curl -fsSL https://gist.githubusercontent.com/johnazariah/b85448f097702e95b330e9b284e78a04/raw/bootstrap-linux.sh | bash -s -- "Your Name" "your@email.com"

What Gets Installed

Tool Windows macOS Linux
Git βœ“ βœ“ βœ“
VS Code βœ“ βœ“ βœ“
PowerShell Core βœ“ βœ“ βœ“
.NET SDK 8/9 βœ“ βœ“ βœ“
Node.js (via nvm) βœ“ βœ“ βœ“
Python + uv βœ“ βœ“ βœ“
GitHub CLI βœ“ βœ“ βœ“
Azure CLI βœ“ βœ“ βœ“
Containers Podman OrbStack Podman
WSL + Ubuntu βœ“ - -

Files

  • bootstrap-*.{ps1,sh} - Platform bootstrap (installs pwsh, launches setup)
  • setup-core.ps1 - Main setup orchestrator (runs under pwsh)
  • DevSetup.psm1 - Cross-platform install functions
  • windows-wsl-manage.ps1 - WSL install/configure/reset
  • windows-wsl-ubuntu-setup.sh - Ubuntu dev environment (zsh, nvm, uv, etc.)

WSL Management (Windows)

# Install WSL + Ubuntu + configure
.\windows-wsl-manage.ps1 -Action Install

# Reconfigure existing Ubuntu
.\windows-wsl-manage.ps1 -Action Configure

# Nuke everything and start fresh
.\windows-wsl-manage.ps1 -Action Reset
#!/bin/bash
#
# Linux Bootstrap Script
# Downloads setup files, installs PowerShell Core, then runs the main setup.
#
# Usage:
# curl -fsSL https://gist.githubusercontent.com/johnazariah/b85448f097702e95b330e9b284e78a04/raw/bootstrap-linux.sh | bash -s -- "Your Name" "your@email.com"
#
set -e
USERNAME="${1:-}"
EMAIL="${2:-}"
if [ -z "$USERNAME" ] || [ -z "$EMAIL" ]; then
echo "Usage: ./bootstrap-linux.sh \"Your Name\" \"your@email.com\""
echo " or: curl -fsSL <url>/bootstrap-linux.sh | bash -s -- \"Your Name\" \"your@email.com\""
exit 1
fi
# Gist configuration
GIST_ID="b85448f097702e95b330e9b284e78a04"
GIST_BASE_URL="https://gist.githubusercontent.com/johnazariah/$GIST_ID/raw"
SETUP_DIR="$HOME/.setup"
GIST_FILES=(
"setup-core.ps1"
"DevSetup.psm1"
)
echo "🐧 Linux Bootstrap - Installing prerequisites..."
# Download all gist files
echo "πŸ“₯ Downloading setup files to $SETUP_DIR..."
mkdir -p "$SETUP_DIR"
for file in "${GIST_FILES[@]}"; do
echo " Downloading $file..."
curl -fsSL "$GIST_BASE_URL/$file" -o "$SETUP_DIR/$file"
done
echo "βœ… All setup files downloaded."
# Detect distro
if [ -f /etc/os-release ]; then
. /etc/os-release
DISTRO=$ID
else
DISTRO="unknown"
fi
echo "πŸ“¦ Detected distribution: $DISTRO"
# Install PowerShell Core if not present
if ! command -v pwsh &> /dev/null; then
echo "πŸ“¦ Installing PowerShell Core..."
case $DISTRO in
ubuntu|debian)
# Install prerequisites
sudo apt-get update
sudo apt-get install -y wget apt-transport-https software-properties-common
# Get Ubuntu version
VERSION_ID=${VERSION_ID:-$(lsb_release -rs)}
# Download and install Microsoft repository
wget -q "https://packages.microsoft.com/config/ubuntu/$VERSION_ID/packages-microsoft-prod.deb" -O /tmp/packages-microsoft-prod.deb
sudo dpkg -i /tmp/packages-microsoft-prod.deb
rm /tmp/packages-microsoft-prod.deb
# Install PowerShell
sudo apt-get update
sudo apt-get install -y powershell
;;
fedora|rhel|centos)
# Register Microsoft repository
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo
# Install PowerShell
sudo dnf install -y powershell || sudo yum install -y powershell
;;
arch|manjaro)
# Install from AUR or official repos
sudo pacman -Sy --noconfirm powershell-bin || {
echo "Installing from AUR..."
git clone https://aur.archlinux.org/powershell-bin.git /tmp/pwsh-aur
cd /tmp/pwsh-aur && makepkg -si --noconfirm
}
;;
*)
echo "❌ Unsupported distribution: $DISTRO"
echo "Please install PowerShell Core manually: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux"
exit 1
;;
esac
else
echo "βœ… PowerShell Core is already installed."
fi
# Verify pwsh is available
if ! command -v pwsh &> /dev/null; then
echo "❌ Failed to install PowerShell Core. Please check the errors above."
exit 1
fi
echo "βœ… PowerShell Core installed: $(pwsh --version)"
SETUP_SCRIPT="$SETUP_DIR/setup-core.ps1"
if [ ! -f "$SETUP_SCRIPT" ]; then
echo "❌ setup-core.ps1 not found at: $SETUP_SCRIPT"
exit 1
fi
echo ""
echo "πŸš€ Launching main setup under PowerShell Core..."
echo ""
# Run the main setup script
pwsh -NoLogo -ExecutionPolicy Bypass -File "$SETUP_SCRIPT" -Username "$USERNAME" -Email "$EMAIL"
exit $?
#!/bin/bash
#
# macOS Bootstrap Script
# Downloads setup files, installs Homebrew and PowerShell Core, then runs the main setup.
#
# Usage:
# curl -fsSL https://gist.githubusercontent.com/johnazariah/b85448f097702e95b330e9b284e78a04/raw/bootstrap-macos.sh | bash -s -- "Your Name" "your@email.com"
#
set -e
USERNAME="${1:-}"
EMAIL="${2:-}"
if [ -z "$USERNAME" ] || [ -z "$EMAIL" ]; then
echo "Usage: ./bootstrap-macos.sh \"Your Name\" \"your@email.com\""
echo " or: curl -fsSL <url>/bootstrap-macos.sh | bash -s -- \"Your Name\" \"your@email.com\""
exit 1
fi
# Gist configuration
GIST_ID="b85448f097702e95b330e9b284e78a04"
GIST_BASE_URL="https://gist.githubusercontent.com/johnazariah/$GIST_ID/raw"
SETUP_DIR="$HOME/.setup"
GIST_FILES=(
"setup-core.ps1"
"DevSetup.psm1"
)
echo "🍎 macOS Bootstrap - Installing prerequisites..."
# Download all gist files
echo "πŸ“₯ Downloading setup files to $SETUP_DIR..."
mkdir -p "$SETUP_DIR"
for file in "${GIST_FILES[@]}"; do
echo " Downloading $file..."
curl -fsSL "$GIST_BASE_URL/$file" -o "$SETUP_DIR/$file"
done
echo "βœ… All setup files downloaded."
# Detect architecture
ARCH=$(uname -m)
if [ "$ARCH" = "arm64" ]; then
BREW_PREFIX="/opt/homebrew"
else
BREW_PREFIX="/usr/local"
fi
# Install Homebrew if not present
if ! command -v brew &> /dev/null; then
echo "πŸ“¦ Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Add Homebrew to PATH for this session
eval "$($BREW_PREFIX/bin/brew shellenv)"
# Add to shell profile
if [ -f "$HOME/.zprofile" ]; then
echo "eval \"\$($BREW_PREFIX/bin/brew shellenv)\"" >> "$HOME/.zprofile"
fi
else
echo "βœ… Homebrew is already installed."
eval "$($BREW_PREFIX/bin/brew shellenv)"
fi
# Update Homebrew
echo "πŸ”„ Updating Homebrew..."
brew update
# Install PowerShell Core if not present
if ! command -v pwsh &> /dev/null; then
echo "πŸ“¦ Installing PowerShell Core..."
brew install powershell/tap/powershell
else
echo "βœ… PowerShell Core is already installed."
fi
# Verify pwsh is available
if ! command -v pwsh &> /dev/null; then
echo "❌ Failed to install PowerShell Core. Please check the errors above."
exit 1
fi
echo "βœ… PowerShell Core installed: $(pwsh --version)"
SETUP_SCRIPT="$SETUP_DIR/setup-core.ps1"
if [ ! -f "$SETUP_SCRIPT" ]; then
echo "❌ setup-core.ps1 not found at: $SETUP_SCRIPT"
exit 1
fi
echo ""
echo "πŸš€ Launching main setup under PowerShell Core..."
echo ""
# Run the main setup script
pwsh -NoLogo -ExecutionPolicy Bypass -File "$SETUP_SCRIPT" -Username "$USERNAME" -Email "$EMAIL"
exit $?
<#
.SYNOPSIS
Windows bootstrap script - runs on PowerShell 5.1 to install PowerShell Core.
.DESCRIPTION
This script can be run directly from the web:
irm https://gist.githubusercontent.com/johnazariah/b85448f097702e95b330e9b284e78a04/raw/bootstrap-windows.ps1 | iex
It downloads all setup files from the gist, installs PowerShell 7 (Core) via winget,
and then invokes the main setup.
.PARAMETER Username
Your display name for Git configuration.
.PARAMETER Email
Your email for Git configuration.
.EXAMPLE
# Run from PowerShell 5.1 as Administrator:
Set-ExecutionPolicy Bypass -Scope Process -Force
.\bootstrap-windows.ps1 -Username "John Doe" -Email "john@example.com"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$Username,
[Parameter(Mandatory=$true)][string]$Email
)
$ErrorActionPreference = 'Stop'
# Gist configuration
$GistId = "b85448f097702e95b330e9b284e78a04"
$GistBaseUrl = "https://gist.githubusercontent.com/johnazariah/$GistId/raw"
$SetupDir = "C:\.setup"
$GistFiles = @(
"setup-core.ps1",
"DevSetup.psm1",
"windows-wsl-manage.ps1",
"windows-wsl-ubuntu-setup.sh"
)
function Write-Info($msg) { Write-Host "[INFO] $msg" -ForegroundColor Cyan }
function Write-Warn($msg) { Write-Warning $msg }
function Write-Err($msg) { Write-Host "[ERROR] $msg" -ForegroundColor Red }
# Ensure TLS 1.2 for web calls in PS 5.1
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
} catch { }
# Check if running as Administrator
function Test-IsAdmin {
try {
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
return $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
} catch { return $false }
}
$IsAdmin = Test-IsAdmin
if (-not $IsAdmin) {
Write-Err "This script must be run as Administrator."
Write-Info "Right-click PowerShell and select 'Run as Administrator', then try again."
exit 1
}
Write-Info "Windows Bootstrap - Running as Administrator"
# Download all gist files to setup directory
Write-Info "Downloading setup files to $SetupDir..."
if (-not (Test-Path $SetupDir)) {
New-Item -ItemType Directory -Path $SetupDir -Force | Out-Null
}
foreach ($file in $GistFiles) {
$url = "$GistBaseUrl/$file"
$dest = Join-Path $SetupDir $file
Write-Info " Downloading $file..."
try {
Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing
} catch {
Write-Err "Failed to download $file : $($_.Exception.Message)"
exit 1
}
}
Write-Info "All setup files downloaded."
# Start transcript
$transcriptPath = Join-Path $env:TEMP "bootstrap-windows_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
try {
Start-Transcript -Path $transcriptPath -ErrorAction SilentlyContinue | Out-Null
Write-Info "Transcript: $transcriptPath"
} catch { }
# Create system restore point (only works in PS 5.1 Desktop)
if ($PSVersionTable.PSEdition -eq 'Desktop') {
try {
Write-Info "Creating system restore point..."
Checkpoint-Computer -Description "Before developer environment setup" -RestorePointType "Modify_Settings" -ErrorAction SilentlyContinue
Write-Info "Restore point created."
} catch {
Write-Warn "Restore point failed (non-fatal): $($_.Exception.Message)"
}
}
# Ensure winget is available
function Test-Winget {
if (Get-Command winget -ErrorAction SilentlyContinue) {
Write-Info "winget is available."
return $true
}
Write-Err "winget not found. Please install 'App Installer' from the Microsoft Store and try again."
return $false
}
if (-not (Test-Winget)) {
exit 1
}
# Check if already running on PowerShell Core
if ($PSVersionTable.PSEdition -eq 'Core') {
Write-Info "Already running on PowerShell Core. Proceeding to main setup..."
$setupScript = Join-Path $SetupDir "setup-core.ps1"
if (Test-Path $setupScript) {
& $setupScript -Username $Username -Email $Email
} else {
Write-Err "setup-core.ps1 not found at $setupScript"
}
exit $LASTEXITCODE
}
# Install PowerShell Core via winget
Write-Info "Installing PowerShell Core via winget..."
$installed = $false
try {
$list = winget list --id Microsoft.PowerShell --exact --source winget 2>$null
if ($list -match 'Microsoft\.PowerShell') {
$installed = $true
Write-Info "PowerShell Core is already installed."
}
} catch { }
if (-not $installed) {
Write-Info "Installing PowerShell Core..."
winget install --id Microsoft.PowerShell -e --source winget --accept-package-agreements --accept-source-agreements
if ($LASTEXITCODE -ne 0) {
Write-Err "Failed to install PowerShell Core."
exit 1
}
Write-Info "PowerShell Core installed successfully."
}
# Find pwsh.exe
$pwshPath = $null
$searchPaths = @(
"$env:ProgramFiles\PowerShell\7\pwsh.exe",
"$env:ProgramFiles\PowerShell\7-preview\pwsh.exe"
)
foreach ($p in $searchPaths) {
if (Test-Path $p) { $pwshPath = $p; break }
}
if (-not $pwshPath) {
# Search recursively as fallback
$pwshPath = Get-ChildItem -Path "$env:ProgramFiles\PowerShell" -Recurse -Filter "pwsh.exe" -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty FullName
}
if (-not $pwshPath) {
Write-Err "Could not find pwsh.exe after installation. Please check the installation and try again."
exit 1
}
Write-Info "Found PowerShell Core at: $pwshPath"
# Stop transcript before relaunching
try { Stop-Transcript -ErrorAction SilentlyContinue | Out-Null } catch { }
# Relaunch the main setup script under PowerShell Core
$setupScript = Join-Path $SetupDir "setup-core.ps1"
if (-not (Test-Path $setupScript)) {
Write-Err "setup-core.ps1 not found at: $setupScript"
exit 1
}
Write-Info "Launching main setup under PowerShell Core..."
Write-Info "Command: $pwshPath -NoLogo -ExecutionPolicy Bypass -File `"$setupScript`" -Username `"$Username`" -Email `"$Email`""
# Use Start-Process with -Verb RunAs to maintain elevation
Start-Process -FilePath $pwshPath -ArgumentList @(
'-NoLogo',
'-NoProfile',
'-ExecutionPolicy', 'Bypass',
'-File', "`"$setupScript`"",
'-Username', "`"$Username`"",
'-Email', "`"$Email`""
) -Verb RunAs -Wait
exit 0
<#
.SYNOPSIS
Cross-platform development environment setup module for PowerShell Core.
.DESCRIPTION
This module provides functions to install and configure a complete development
environment on Windows, macOS, and Linux. It requires PowerShell Core (7+).
.NOTES
This module is designed to be imported by setup-core.ps1 and should only run
on PowerShell Core, not Windows PowerShell 5.1.
#>
#Requires -Version 7.0
# ============================================================================
# PLATFORM DETECTION
# ============================================================================
function Get-Platform {
<#
.SYNOPSIS
Detects the current operating system platform.
.OUTPUTS
String: 'Windows', 'macOS', or 'Linux'
#>
if ($IsWindows) { return 'Windows' }
if ($IsMacOS) { return 'macOS' }
if ($IsLinux) { return 'Linux' }
throw "Unknown platform"
}
function Get-PackageManager {
<#
.SYNOPSIS
Returns the appropriate package manager for the current platform.
#>
switch (Get-Platform) {
'Windows' { return 'winget' }
'macOS' { return 'brew' }
'Linux' { return 'apt' } # Default to apt, can be extended
}
}
# ============================================================================
# LOGGING
# ============================================================================
$script:LogFile = $null
function Initialize-Logging {
param([string]$LogDirectory = $null)
if (-not $LogDirectory) {
$LogDirectory = if ($IsWindows) { $env:TEMP } else { "/tmp" }
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$script:LogFile = Join-Path $LogDirectory "setup-core_$timestamp.log"
Write-Info "Log file: $script:LogFile"
}
function Write-Info {
param([string]$Message)
$timestamp = Get-Date -Format 'HH:mm:ss'
$line = "[$timestamp] [INFO] $Message"
Write-Host $line -ForegroundColor Cyan
if ($script:LogFile) { Add-Content -Path $script:LogFile -Value $line -ErrorAction SilentlyContinue }
}
function Write-Success {
param([string]$Message)
$timestamp = Get-Date -Format 'HH:mm:ss'
$line = "[$timestamp] [OK] $Message"
Write-Host $line -ForegroundColor Green
if ($script:LogFile) { Add-Content -Path $script:LogFile -Value $line -ErrorAction SilentlyContinue }
}
function Write-Warn {
param([string]$Message)
$timestamp = Get-Date -Format 'HH:mm:ss'
$line = "[$timestamp] [WARN] $Message"
Write-Host $line -ForegroundColor Yellow
if ($script:LogFile) { Add-Content -Path $script:LogFile -Value $line -ErrorAction SilentlyContinue }
}
function Write-Err {
param([string]$Message)
$timestamp = Get-Date -Format 'HH:mm:ss'
$line = "[$timestamp] [ERROR] $Message"
Write-Host $line -ForegroundColor Red
if ($script:LogFile) { Add-Content -Path $script:LogFile -Value $line -ErrorAction SilentlyContinue }
}
# ============================================================================
# PACKAGE INSTALLATION - CROSS PLATFORM
# ============================================================================
function Install-WithWinget {
<#
.SYNOPSIS
Installs a package using winget (Windows only).
#>
param(
[Parameter(Mandatory)][string]$Id,
[string]$Name = $Id,
[string[]]$ExtraArgs = @()
)
# Check if already installed
$installed = $false
try {
$list = winget list --id $Id --exact --source winget 2>$null
if ($list -match [Regex]::Escape($Id)) { $installed = $true }
} catch { }
if ($installed) {
Write-Info "$Name is already installed."
return $true
}
Write-Info "Installing $Name via winget..."
$args = @('install', '--id', $Id, '-e', '--source', 'winget', '--accept-package-agreements', '--accept-source-agreements') + $ExtraArgs
try {
& winget @args
if ($LASTEXITCODE -eq 0) {
Write-Success "$Name installed successfully."
return $true
} else {
Write-Warn "$Name installation returned exit code $LASTEXITCODE"
return $false
}
} catch {
Write-Err "Failed to install ${Name}: $($_.Exception.Message)"
return $false
}
}
function Install-WithBrew {
<#
.SYNOPSIS
Installs a package using Homebrew (macOS/Linux).
#>
param(
[Parameter(Mandatory)][string]$Formula,
[string]$Name = $Formula,
[switch]$Cask
)
# Check if already installed
$installed = $false
try {
if ($Cask) {
$list = brew list --cask 2>$null
} else {
$list = brew list --formula 2>$null
}
if ($list -contains $Formula) { $installed = $true }
} catch { }
if ($installed) {
Write-Info "$Name is already installed."
return $true
}
Write-Info "Installing $Name via Homebrew..."
try {
if ($Cask) {
brew install --cask $Formula
} else {
brew install $Formula
}
if ($LASTEXITCODE -eq 0) {
Write-Success "$Name installed successfully."
return $true
} else {
Write-Warn "$Name installation returned exit code $LASTEXITCODE"
return $false
}
} catch {
Write-Err "Failed to install ${Name}: $($_.Exception.Message)"
return $false
}
}
function Install-WithApt {
<#
.SYNOPSIS
Installs a package using apt (Debian/Ubuntu Linux).
#>
param(
[Parameter(Mandatory)][string]$Package,
[string]$Name = $Package
)
# Check if already installed
$installed = $false
try {
$result = dpkg -l $Package 2>$null | grep "^ii"
if ($result) { $installed = $true }
} catch { }
if ($installed) {
Write-Info "$Name is already installed."
return $true
}
Write-Info "Installing $Name via apt..."
try {
sudo apt-get install -y $Package
if ($LASTEXITCODE -eq 0) {
Write-Success "$Name installed successfully."
return $true
} else {
Write-Warn "$Name installation returned exit code $LASTEXITCODE"
return $false
}
} catch {
Write-Err "Failed to install ${Name}: $($_.Exception.Message)"
return $false
}
}
# ============================================================================
# CROSS-PLATFORM PACKAGE INSTALLATION
# ============================================================================
function Install-Package {
<#
.SYNOPSIS
Cross-platform package installation wrapper.
#>
param(
[string]$WingetId,
[string]$BrewFormula,
[string]$BrewCask,
[string]$AptPackage,
[string]$Name
)
$platform = Get-Platform
$displayName = if ($Name) { $Name } else { $WingetId ?? $BrewFormula ?? $BrewCask ?? $AptPackage }
switch ($platform) {
'Windows' {
if ($WingetId) {
return Install-WithWinget -Id $WingetId -Name $displayName
}
}
'macOS' {
if ($BrewCask) {
return Install-WithBrew -Formula $BrewCask -Name $displayName -Cask
} elseif ($BrewFormula) {
return Install-WithBrew -Formula $BrewFormula -Name $displayName
}
}
'Linux' {
if ($AptPackage) {
return Install-WithApt -Package $AptPackage -Name $displayName
}
}
}
Write-Warn "No package definition for $displayName on $platform"
return $false
}
# ============================================================================
# CORE TOOL INSTALLATION
# ============================================================================
function Install-Git {
Write-Info "Checking Git..."
Install-Package -WingetId 'Git.Git' -BrewFormula 'git' -AptPackage 'git' -Name 'Git'
}
function Install-VSCode {
Write-Info "Checking Visual Studio Code..."
Install-Package -WingetId 'Microsoft.VisualStudioCode' -BrewCask 'visual-studio-code' -Name 'Visual Studio Code'
# Note: On Linux, VS Code is typically installed via .deb or snap - handled separately
}
function Install-WindowsTerminal {
if (-not $IsWindows) { return }
Write-Info "Checking Windows Terminal..."
Install-WithWinget -Id 'Microsoft.WindowsTerminal' -Name 'Windows Terminal'
}
function Install-PowerToys {
if (-not $IsWindows) { return }
Write-Info "Checking PowerToys..."
Install-WithWinget -Id 'Microsoft.PowerToys' -Name 'PowerToys'
}
function Install-WindowsApp {
<#
.SYNOPSIS
Installs Windows App (formerly Remote Desktop) for connecting to Dev Boxes and Azure Virtual Desktop.
#>
if (-not $IsWindows) { return }
Write-Info "Checking Windows App (for Dev Boxes)..."
Install-WithWinget -Id 'Microsoft.WindowsApp' -Name 'Windows App'
}
function Install-Python {
Write-Info "Checking Python..."
Install-Package -WingetId 'Python.Python.3.12' -BrewFormula 'python@3.12' -AptPackage 'python3' -Name 'Python'
}
function Install-GitHubCLI {
Write-Info "Checking GitHub CLI..."
Install-Package -WingetId 'GitHub.cli' -BrewFormula 'gh' -AptPackage 'gh' -Name 'GitHub CLI'
}
function Install-AzureCLI {
Write-Info "Checking Azure CLI..."
Install-Package -WingetId 'Microsoft.AzureCLI' -BrewFormula 'azure-cli' -Name 'Azure CLI'
}
function Install-AzureDeveloperCLI {
Write-Info "Checking Azure Developer CLI..."
if ($IsMacOS) {
# azd requires a tap on macOS
try {
brew tap azure/azd 2>$null
} catch { }
}
Install-Package -WingetId 'Microsoft.Azd' -BrewFormula 'azd' -Name 'Azure Developer CLI'
}
function Install-DotNetSDK {
Write-Info "Checking .NET SDKs..."
if ($IsWindows) {
Install-WithWinget -Id 'Microsoft.DotNet.SDK.8' -Name '.NET SDK 8'
Install-WithWinget -Id 'Microsoft.DotNet.SDK.9' -Name '.NET SDK 9'
} elseif ($IsMacOS) {
Install-WithBrew -Formula 'dotnet@8' -Name '.NET SDK 8'
# .NET 9 may need --cask dotnet-sdk
} elseif ($IsLinux) {
# Linux needs Microsoft repo setup - handled in bootstrap or separately
Install-WithApt -Package 'dotnet-sdk-8.0' -Name '.NET SDK 8'
}
}
function Install-NodeJS {
Write-Info "Checking Node.js (via nvm)..."
if ($IsWindows) {
Install-WithWinget -Id 'CoreyButler.NVMforWindows' -Name 'NVM for Windows'
Write-Info "After setup, run 'nvm install lts' and 'nvm use lts' in a new terminal."
} else {
# Install nvm for macOS/Linux
$nvmDir = Join-Path $HOME ".nvm"
if (-not (Test-Path $nvmDir)) {
Write-Info "Installing nvm..."
try {
bash -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash'
Write-Success "nvm installed. Run 'nvm install --lts' after restarting your shell."
} catch {
Write-Warn "Failed to install nvm: $($_.Exception.Message)"
}
} else {
Write-Info "nvm is already installed."
}
}
}
function Install-ContainerEngine {
<#
.SYNOPSIS
Installs the appropriate container engine for the platform.
- Windows: Podman
- macOS: OrbStack (preferred) or Podman
- Linux: Podman
#>
param(
[ValidateSet('podman', 'orbstack', 'auto')]
[string]$Engine = 'auto'
)
$platform = Get-Platform
# Auto-select engine
if ($Engine -eq 'auto') {
$Engine = if ($platform -eq 'macOS') { 'orbstack' } else { 'podman' }
}
Write-Info "Installing container engine: $Engine..."
switch ($Engine) {
'orbstack' {
if ($platform -ne 'macOS') {
Write-Warn "OrbStack is only available on macOS. Falling back to Podman."
Install-ContainerEngine -Engine 'podman'
return
}
Install-WithBrew -Formula 'orbstack' -Name 'OrbStack' -Cask
}
'podman' {
if ($IsWindows) {
Install-WithWinget -Id 'RedHat.Podman' -Name 'Podman CLI'
Install-WithWinget -Id 'RedHat.Podman-Desktop' -Name 'Podman Desktop'
} elseif ($IsMacOS) {
Install-WithBrew -Formula 'podman' -Name 'Podman'
Install-WithBrew -Formula 'podman-desktop' -Name 'Podman Desktop' -Cask
} else {
Install-WithApt -Package 'podman' -Name 'Podman'
}
}
}
}
function Install-WSL {
<#
.SYNOPSIS
Installs WSL with Ubuntu on Windows.
#>
if (-not $IsWindows) { return }
Write-Info "Checking WSL..."
# Check if WSL is installed
$wslInstalled = $false
try {
$wslVersion = wsl --version 2>$null
if ($wslVersion) { $wslInstalled = $true }
} catch { }
if (-not $wslInstalled) {
Write-Info "Installing WSL..."
Install-WithWinget -Id 'Microsoft.WSL' -Name 'Windows Subsystem for Linux'
} else {
Write-Info "WSL is already installed."
}
# Check for Ubuntu
$distros = wsl --list --quiet 2>$null
if ($distros -notmatch 'Ubuntu') {
Write-Info "Installing Ubuntu distribution..."
try {
wsl --install -d Ubuntu --no-launch
Write-Success "Ubuntu installed. Please restart your computer, then run 'wsl' to complete Ubuntu setup."
} catch {
Write-Warn "Failed to install Ubuntu: $($_.Exception.Message)"
Write-Info "You can manually install Ubuntu from the Microsoft Store or run 'wsl --install -d Ubuntu'"
}
} else {
Write-Info "Ubuntu is already installed in WSL."
}
}
# ============================================================================
# VS CODE EXTENSIONS
# ============================================================================
function Install-VSCodeExtensions {
Write-Info "Installing VS Code extensions..."
$extensions = @(
# Python
'ms-python.python',
'ms-python.vscode-pylance',
'ms-python.debugpy',
# Remote Development
'ms-vscode.remote-containers',
'ms-vscode-remote.remote-ssh',
'ms-vscode-remote.remote-wsl',
# GitHub & Live Share
'ms-vsliveshare.vsliveshare',
'GitHub.copilot',
'GitHub.copilot-chat',
# PowerShell & YAML
'ms-vscode.PowerShell',
'redhat.vscode-yaml',
# Kubernetes
'ms-kubernetes-tools.vscode-kubernetes-tools',
# Azure
'ms-azuretools.vscode-azurefunctions',
'ms-azuretools.vscode-azureresourcegroups',
'ms-azuretools.vscode-azureappservice',
'ms-azuretools.vscode-azurestorage',
'ms-azuretools.vscode-cosmosdb',
# .NET / C#
'ms-dotnettools.csharp',
'ms-dotnettools.csdevkit',
'ms-dotnettools.vscode-dotnet-runtime',
'ms-semantic-kernel.semantic-kernel',
# Git
'eamodio.gitlens',
'mhutchie.git-graph',
# Utilities
'usernamehw.errorlens',
'esbenp.prettier-vscode',
'dbaeumer.vscode-eslint',
'ms-vscode.makefile-tools',
'tamasfe.even-better-toml',
# Additional Languages
'golang.go',
'rust-lang.rust-analyzer'
)
# Find VS Code CLI
$codeCmd = Get-Command code -ErrorAction SilentlyContinue
if (-not $codeCmd) {
if ($IsWindows) {
$codePath = Join-Path $env:LOCALAPPDATA 'Programs\Microsoft VS Code\bin\code.cmd'
if (Test-Path $codePath) { $codeCmd = $codePath }
} elseif ($IsMacOS) {
$codePath = '/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code'
if (Test-Path $codePath) { $codeCmd = $codePath }
}
}
if (-not $codeCmd) {
Write-Warn "VS Code CLI not found. Please open VS Code once to enable the 'code' command."
return
}
# Get installed extensions
try {
$installedExtensions = & $codeCmd --list-extensions 2>$null
} catch {
$installedExtensions = @()
}
foreach ($ext in $extensions) {
if ($installedExtensions -contains $ext) {
Write-Info "Extension already installed: $ext"
} else {
Write-Info "Installing extension: $ext"
try {
& $codeCmd --install-extension $ext --force 2>&1 | Out-Null
} catch {
Write-Warn "Failed to install extension: $ext"
}
}
}
Write-Success "VS Code extensions installation complete."
}
# ============================================================================
# GIT CONFIGURATION
# ============================================================================
function Set-GitConfiguration {
param(
[Parameter(Mandatory)][string]$Username,
[Parameter(Mandatory)][string]$Email
)
Write-Info "Configuring Git..."
try {
git config --global user.name "$Username"
git config --global user.email "$Email"
git config --global core.editor "code --wait"
git config --global init.defaultBranch "main"
git config --global pull.rebase false
if ($IsWindows) {
git config --global core.autocrlf true
} else {
git config --global core.autocrlf input
}
Write-Success "Git configured for: $Username <$Email>"
} catch {
Write-Err "Failed to configure Git: $($_.Exception.Message)"
}
}
function Set-GitAliases {
Write-Info "Setting up Git aliases..."
try {
git config --global alias.co "checkout"
git config --global alias.br "branch"
git config --global alias.ci "commit"
git config --global alias.st "status"
git config --global alias.lg "log --oneline --graph --decorate"
git config --global alias.last "log -1 HEAD"
git config --global alias.unstage "reset HEAD --"
Write-Success "Git aliases configured."
} catch {
Write-Warn "Failed to configure some Git aliases."
}
}
# ============================================================================
# SHELL & TERMINAL CONFIGURATION
# ============================================================================
function Install-NerdFont {
Write-Info "Checking Nerd Fonts..."
if ($IsWindows) {
# Try different winget IDs for Nerd Fonts
$fontIds = @(
'NerdFonts.CascadiaCode',
'NerdFonts.CaskaydiaCove'
)
foreach ($fontId in $fontIds) {
$result = Install-WithWinget -Id $fontId -Name 'CaskaydiaCove Nerd Font'
if ($result) { break }
}
} elseif ($IsMacOS) {
try {
brew tap homebrew/cask-fonts 2>$null
} catch { }
Install-WithBrew -Formula 'font-caskaydia-cove-nerd-font' -Name 'CaskaydiaCove Nerd Font' -Cask
}
}
function Install-OhMyPosh {
Write-Info "Checking Oh My Posh..."
if ($IsWindows) {
Install-WithWinget -Id 'JanDeDobbeleer.OhMyPosh' -Name 'Oh My Posh'
} elseif ($IsMacOS) {
Install-WithBrew -Formula 'oh-my-posh' -Name 'Oh My Posh'
}
# Add to PowerShell profile
$profileContent = @"
# Oh My Posh prompt
if (Get-Command oh-my-posh -ErrorAction SilentlyContinue) {
oh-my-posh init pwsh --config `"`$env:POSH_THEMES_PATH/agnoster.omp.json`" | Invoke-Expression
}
"@
$profilePath = $PROFILE.CurrentUserAllHosts
$profileDir = Split-Path $profilePath -Parent
if (-not (Test-Path $profileDir)) {
New-Item -ItemType Directory -Path $profileDir -Force | Out-Null
}
if (-not (Test-Path $profilePath)) {
New-Item -ItemType File -Path $profilePath -Force | Out-Null
}
$existingProfile = Get-Content $profilePath -Raw -ErrorAction SilentlyContinue
if ($existingProfile -notmatch 'oh-my-posh') {
Add-Content -Path $profilePath -Value $profileContent
Write-Success "Oh My Posh added to PowerShell profile."
}
}
function Install-ZshSetup {
<#
.SYNOPSIS
Installs Zsh, Oh My Zsh, and Powerlevel10k on macOS/Linux.
#>
if ($IsWindows) { return }
Write-Info "Setting up Zsh environment..."
# Install Zsh if not present
if (-not (Get-Command zsh -ErrorAction SilentlyContinue)) {
if ($IsMacOS) {
Write-Info "Zsh should be pre-installed on macOS."
} else {
Install-WithApt -Package 'zsh' -Name 'Zsh'
}
}
# Install Oh My Zsh
$omzDir = Join-Path $HOME ".oh-my-zsh"
if (-not (Test-Path $omzDir)) {
Write-Info "Installing Oh My Zsh..."
try {
bash -c 'RUNZSH=no CHSH=no KEEP_ZSHRC=yes sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"'
Write-Success "Oh My Zsh installed."
} catch {
Write-Warn "Failed to install Oh My Zsh: $($_.Exception.Message)"
}
} else {
Write-Info "Oh My Zsh is already installed."
}
# Install Powerlevel10k theme
$p10kDir = Join-Path $HOME ".oh-my-zsh/custom/themes/powerlevel10k"
if (-not (Test-Path $p10kDir)) {
Write-Info "Installing Powerlevel10k theme..."
try {
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git $p10kDir
Write-Success "Powerlevel10k installed."
} catch {
Write-Warn "Failed to install Powerlevel10k: $($_.Exception.Message)"
}
}
}
# ============================================================================
# DOTNET GLOBAL TOOLS
# ============================================================================
function Install-DotNetGlobalTools {
Write-Info "Installing .NET global tools..."
if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) {
Write-Warn ".NET SDK not found. Skipping global tools installation."
return
}
$tools = @('dotnet-ef', 'dotnet-format')
foreach ($tool in $tools) {
try {
dotnet tool update --global $tool 2>$null
if ($LASTEXITCODE -ne 0) {
dotnet tool install --global $tool
}
Write-Success "$tool installed/updated."
} catch {
Write-Warn "Failed to install $tool"
}
}
}
# ============================================================================
# EXPORTS
# ============================================================================
Export-ModuleMember -Function @(
# Platform detection
'Get-Platform',
'Get-PackageManager',
# Logging
'Initialize-Logging',
'Write-Info',
'Write-Success',
'Write-Warn',
'Write-Err',
# Package installation
'Install-Package',
'Install-WithWinget',
'Install-WithBrew',
'Install-WithApt',
# Core tools
'Install-Git',
'Install-VSCode',
'Install-WindowsTerminal',
'Install-PowerToys',
'Install-Python',
'Install-GitHubCLI',
'Install-AzureCLI',
'Install-AzureDeveloperCLI',
'Install-DotNetSDK',
'Install-NodeJS',
'Install-ContainerEngine',
'Install-WSL',
# VS Code
'Install-VSCodeExtensions',
# Git
'Set-GitConfiguration',
'Set-GitAliases',
# Shell & Terminal
'Install-NerdFont',
'Install-OhMyPosh',
'Install-ZshSetup',
# Windows-specific
'Install-WindowsApp',
# .NET
'Install-DotNetGlobalTools'
)
<#
.SYNOPSIS
Main development environment setup script for PowerShell Core.
.DESCRIPTION
This is the unified setup script that runs on PowerShell Core (7+) across
Windows, macOS, and Linux. It should be invoked by the platform-specific
bootstrap scripts, not directly on Windows PowerShell 5.1.
.PARAMETER Username
Your display name for Git configuration.
.PARAMETER Email
Your email for Git configuration.
.PARAMETER ContainerEngine
Container engine to install: 'podman', 'orbstack', or 'auto' (default).
'auto' selects OrbStack on macOS, Podman elsewhere.
.PARAMETER SkipWSLSetup
Skip WSL Ubuntu configuration (Windows only).
.EXAMPLE
pwsh -ExecutionPolicy Bypass -File setup-core.ps1 -Username "John Doe" -Email "john@example.com"
#>
#Requires -Version 7.0
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Username,
[Parameter(Mandatory=$true)]
[string]$Email,
[ValidateSet('podman', 'orbstack', 'auto')]
[string]$ContainerEngine = 'auto',
[switch]$SkipWSLSetup
)
$ErrorActionPreference = 'Stop'
# Import the setup module
$modulePath = Join-Path $PSScriptRoot "DevSetup.psm1"
if (-not (Test-Path $modulePath)) {
Write-Host "ERROR: DevSetup.psm1 not found at: $modulePath" -ForegroundColor Red
exit 1
}
Import-Module $modulePath -Force
# Initialize logging
Initialize-Logging
$platform = Get-Platform
Write-Info "=============================================="
Write-Info "Development Environment Setup"
Write-Info "=============================================="
Write-Info "Platform: $platform"
Write-Info "PowerShell: $($PSVersionTable.PSVersion)"
Write-Info "User: $Username <$Email>"
Write-Info "Container Engine: $ContainerEngine"
Write-Info "=============================================="
Write-Host ""
# ============================================================================
# PHASE 1: Core Development Tools
# ============================================================================
Write-Info "PHASE 1: Installing core development tools..."
Write-Host ""
Install-Git
Install-VSCode
Install-Python
Install-GitHubCLI
Install-AzureCLI
Install-AzureDeveloperCLI
Install-DotNetSDK
Install-NodeJS
Write-Host ""
# ============================================================================
# PHASE 2: Platform-Specific Tools
# ============================================================================
Write-Info "PHASE 2: Installing platform-specific tools..."
Write-Host ""
if ($IsWindows) {
Install-WindowsTerminal
Install-PowerToys
Install-WindowsApp
Install-WSL
}
Install-ContainerEngine -Engine $ContainerEngine
Write-Host ""
# ============================================================================
# PHASE 3: Configuration
# ============================================================================
Write-Info "PHASE 3: Configuring environment..."
Write-Host ""
Set-GitConfiguration -Username $Username -Email $Email
Set-GitAliases
Install-VSCodeExtensions
Write-Host ""
# ============================================================================
# PHASE 4: Shell & Terminal Enhancements
# ============================================================================
Write-Info "PHASE 4: Setting up shell enhancements..."
Write-Host ""
Install-NerdFont
Install-OhMyPosh
if (-not $IsWindows) {
Install-ZshSetup
}
Write-Host ""
# ============================================================================
# PHASE 5: .NET Global Tools
# ============================================================================
Write-Info "PHASE 5: Installing .NET global tools..."
Write-Host ""
Install-DotNetGlobalTools
Write-Host ""
# ============================================================================
# PHASE 6: WSL Ubuntu Setup (Windows only)
# ============================================================================
if ($IsWindows -and -not $SkipWSLSetup) {
Write-Info "PHASE 6: Configuring WSL Ubuntu..."
Write-Host ""
$wslManageScript = Join-Path $PSScriptRoot "windows-wsl-manage.ps1"
if (Test-Path $wslManageScript) {
# Use the WSL management script
& $wslManageScript -Action Install -Force:$false
} else {
Write-Warn "WSL management script not found: $wslManageScript"
Write-Info "You can install/configure WSL manually with:"
Write-Info " wsl --install -d Ubuntu"
}
} else {
Write-Warn "Ubuntu not found in WSL. You may need to:"
Write-Info " 1. Restart your computer to complete WSL installation"
Write-Info " 2. Run 'wsl --install -d Ubuntu' to install Ubuntu"
} else {
Write-Warn "WSL management script not found: $wslManageScript"
Write-Info "You can install/configure WSL manually with:"
Write-Info " wsl --install -d Ubuntu"
}
Write-Host ""
}
# ============================================================================
# COMPLETION
# ============================================================================
Write-Host ""
Write-Info "=============================================="
Write-Success "Setup Complete!"
Write-Info "=============================================="
Write-Host ""
Write-Info "Next steps:"
if ($IsWindows) {
Write-Host " 1. Restart your terminal (or computer if WSL was installed)" -ForegroundColor White
Write-Host " 2. Run 'wsl' to complete Ubuntu setup if not done" -ForegroundColor White
Write-Host " 3. In a new terminal, run 'nvm install lts' for Node.js" -ForegroundColor White
} elseif ($IsMacOS) {
Write-Host " 1. Restart your terminal or run: source ~/.zshrc" -ForegroundColor White
Write-Host " 2. Run 'nvm install --lts' for Node.js" -ForegroundColor White
Write-Host " 3. Launch OrbStack/Podman Desktop to complete container setup" -ForegroundColor White
} else {
Write-Host " 1. Restart your terminal or run: source ~/.zshrc" -ForegroundColor White
Write-Host " 2. Run 'nvm install --lts' for Node.js" -ForegroundColor White
Write-Host " 3. Run 'chsh -s \$(which zsh)' to set Zsh as default shell" -ForegroundColor White
}
Write-Host ""
Write-Info "Configuration for: $Username <$Email>"
Write-Host ""
<#
.SYNOPSIS
Windows WSL management script - install, configure, or reset WSL with Ubuntu.
.DESCRIPTION
This script manages WSL on Windows:
- Install WSL and Ubuntu if not present
- Configure Ubuntu with development tools
- Reset (unregister all distros and reinstall fresh)
Can be run standalone after the main setup, or to reset your WSL environment.
.PARAMETER Action
The action to perform:
- Install : Install WSL + Ubuntu and configure (default)
- Configure: Just run the Ubuntu setup script (WSL must exist)
- Reset : Unregister all distros and reinstall fresh
- Uninstall: Remove all WSL distros (keeps WSL itself)
.PARAMETER Distro
The Linux distribution to install. Default: Ubuntu
.PARAMETER SkipConfigure
Skip running the Ubuntu configuration script after installation.
.PARAMETER Force
Skip confirmation prompts (useful for automation).
.EXAMPLE
# Install WSL + Ubuntu and configure
.\windows-wsl-manage.ps1
# Just reconfigure existing Ubuntu
.\windows-wsl-manage.ps1 -Action Configure
# Blow away everything and start fresh
.\windows-wsl-manage.ps1 -Action Reset
# Remove all distros but keep WSL
.\windows-wsl-manage.ps1 -Action Uninstall
#>
[CmdletBinding()]
param(
[ValidateSet('Install', 'Configure', 'Reset', 'Uninstall')]
[string]$Action = 'Install',
[string]$Distro = 'Ubuntu',
[switch]$SkipConfigure,
[switch]$Force
)
$ErrorActionPreference = 'Stop'
# ============================================================================
# HELPERS
# ============================================================================
function Write-Info($msg) { Write-Host "[INFO] $msg" -ForegroundColor Cyan }
function Write-Success($msg) { Write-Host "[OK] $msg" -ForegroundColor Green }
function Write-Warn($msg) { Write-Host "[WARN] $msg" -ForegroundColor Yellow }
function Write-Err($msg) { Write-Host "[ERROR] $msg" -ForegroundColor Red }
function Test-IsAdmin {
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
return $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
function Test-WSLInstalled {
try {
$version = wsl --version 2>$null
return $null -ne $version
} catch {
return $false
}
}
function Get-WSLDistros {
try {
# WSL outputs UTF-16LE with null bytes - Trim() handles it cleanly
$raw = wsl --list --quiet 2>$null
if ($raw) {
$cleaned = $raw | ForEach-Object { $_.Trim() } | Where-Object { $_ }
return $cleaned
}
return @()
} catch {
return @()
}
}
function Test-DistroInstalled {
param([string]$Name)
$distros = Get-WSLDistros
# Use -eq for exact match after Trim() has cleaned the strings
return ($distros | Where-Object { $_ -eq $Name }).Count -gt 0
}
function Confirm-Action {
param([string]$Message)
if ($Force) { return $true }
$response = Read-Host "$Message (y/N)"
return $response -eq 'y' -or $response -eq 'Y'
}
# ============================================================================
# ACTIONS
# ============================================================================
function Install-WSLAndDistro {
Write-Info "Installing WSL and $Distro..."
# Check for admin (needed for WSL install)
if (-not (Test-IsAdmin)) {
Write-Err "WSL installation requires Administrator privileges."
Write-Info "Please run this script as Administrator."
exit 1
}
# Install WSL if not present
if (-not (Test-WSLInstalled)) {
Write-Info "Installing WSL..."
# Try winget first
$wingetAvailable = Get-Command winget -ErrorAction SilentlyContinue
if ($wingetAvailable) {
winget install --id Microsoft.WSL -e --source winget --accept-package-agreements --accept-source-agreements
} else {
# Fallback to wsl --install
wsl --install --no-distribution
}
if ($LASTEXITCODE -ne 0) {
Write-Err "Failed to install WSL. You may need to restart and try again."
exit 1
}
Write-Success "WSL installed."
Write-Warn "A restart may be required before installing a distro."
# Check if WSL is now available
if (-not (Test-WSLInstalled)) {
Write-Info ""
Write-Info "Please restart your computer, then run this script again to install $Distro."
exit 0
}
} else {
Write-Info "WSL is already installed."
}
# Install distro if not present
if (-not (Test-DistroInstalled -Name $Distro)) {
Write-Info "Installing $Distro..."
$installOutput = wsl --install -d $Distro --no-launch 2>&1
# Check if reboot is required
$rebootRequired = $installOutput -match "reboot|restart|Changes will not be effective"
if ($LASTEXITCODE -ne 0 -and -not $rebootRequired) {
Write-Err "Failed to install $Distro."
Write-Info "You can try manually: wsl --install -d $Distro"
exit 1
}
if ($rebootRequired) {
Write-Success "$Distro installation initiated."
Write-Info ""
Write-Warn "A REBOOT IS REQUIRED to complete the installation."
Write-Info ""
Write-Info "After rebooting:"
Write-Info " 1. Open PowerShell and run: wsl -d $Distro"
Write-Info " 2. Create your Linux username and password"
Write-Info " 3. Exit the Linux terminal"
Write-Info " 4. Run: .\windows-wsl-manage.ps1 -Action Configure"
Write-Info ""
$reboot = Confirm-Action "Reboot now?"
if ($reboot) {
Write-Info "Rebooting in 10 seconds... Save your work!"
Start-Sleep -Seconds 10
Restart-Computer -Force
}
exit 0
}
Write-Success "$Distro installed."
# Verify the distro is actually available
Start-Sleep -Seconds 2
if (-not (Test-DistroInstalled -Name $Distro)) {
Write-Warn "$Distro was installed but is not yet available."
Write-Info "You may need to reboot, then run this script again."
exit 0
}
Write-Info ""
Write-Info "IMPORTANT: You need to launch $Distro once to complete initial setup."
Write-Info "This will prompt you to create a username and password."
Write-Info ""
$launch = Confirm-Action "Launch $Distro now to complete setup?"
if ($launch) {
Write-Info "Launching $Distro... Please create your user account."
Write-Info "After setup completes, close the terminal and run this script with -Action Configure"
wsl -d $Distro
exit 0
} else {
Write-Info "Run 'wsl -d $Distro' to complete initial setup, then run:"
Write-Info " .\windows-wsl-manage.ps1 -Action Configure"
exit 0
}
} else {
Write-Info "$Distro is already installed."
}
# Configure if not skipped
if (-not $SkipConfigure) {
Invoke-DistroConfiguration
}
}
function Invoke-DistroConfiguration {
Write-Info "Configuring $Distro..."
# Check distro exists
if (-not (Test-DistroInstalled -Name $Distro)) {
Write-Err "$Distro is not installed. Run with -Action Install first."
exit 1
}
# Find the setup script
$setupScript = Join-Path $PSScriptRoot "windows-wsl-ubuntu-setup.sh"
if (-not (Test-Path $setupScript)) {
Write-Err "Setup script not found: $setupScript"
exit 1
}
Write-Info "Running Ubuntu development environment setup..."
Write-Info "This may take several minutes..."
Write-Info ""
# Convert Windows path to WSL path and execute
$wslPath = wsl -d $Distro -e bash -c "wslpath -u '$setupScript'" 2>$null
if (-not $wslPath) {
Write-Err "Failed to convert path for WSL."
exit 1
}
# Make executable and run
wsl -d $Distro -e bash -c "chmod +x '$wslPath' && '$wslPath'"
if ($LASTEXITCODE -eq 0) {
Write-Success "$Distro configuration complete!"
Write-Info ""
Write-Info "Your WSL Ubuntu environment is ready."
Write-Info "Run 'wsl' to enter Ubuntu."
} else {
Write-Warn "Configuration script returned exit code: $LASTEXITCODE"
Write-Info "Check the output above for errors."
}
}
function Reset-WSL {
Write-Warn "This will UNREGISTER ALL WSL distributions and reinstall fresh."
Write-Warn "All data in your WSL distros will be PERMANENTLY DELETED."
Write-Info ""
$distros = Get-WSLDistros
if ($distros.Count -gt 0) {
Write-Info "Installed distros that will be removed:"
foreach ($d in $distros) {
Write-Host " - $d" -ForegroundColor White
}
Write-Info ""
}
if (-not (Confirm-Action "Are you sure you want to reset WSL?")) {
Write-Info "Cancelled."
exit 0
}
# Double-check for destructive action
if (-not $Force) {
if (-not (Confirm-Action "This is IRREVERSIBLE. Type 'y' again to confirm")) {
Write-Info "Cancelled."
exit 0
}
}
# Unregister all distros
foreach ($d in $distros) {
Write-Info "Unregistering $d..."
wsl --unregister $d 2>$null
}
Write-Success "All distros unregistered."
# Reinstall
Write-Info ""
Install-WSLAndDistro
}
function Uninstall-AllDistros {
$distros = Get-WSLDistros
if ($distros.Count -eq 0) {
Write-Info "No WSL distros installed."
exit 0
}
Write-Warn "This will UNREGISTER the following WSL distributions:"
foreach ($d in $distros) {
Write-Host " - $d" -ForegroundColor White
}
Write-Warn "All data in these distros will be PERMANENTLY DELETED."
Write-Info ""
if (-not (Confirm-Action "Are you sure?")) {
Write-Info "Cancelled."
exit 0
}
foreach ($d in $distros) {
Write-Info "Unregistering $d..."
wsl --unregister $d 2>$null
}
Write-Success "All distros unregistered."
Write-Info "WSL itself is still installed. Run with -Action Install to add a new distro."
}
# ============================================================================
# MAIN
# ============================================================================
Write-Info "=============================================="
Write-Info "WSL Management - $Action"
Write-Info "=============================================="
Write-Info ""
switch ($Action) {
'Install' {
Install-WSLAndDistro
}
'Configure' {
Invoke-DistroConfiguration
}
'Reset' {
Reset-WSL
}
'Uninstall' {
Uninstall-AllDistros
}
}
#!/bin/bash
#
# WSL Ubuntu Development Environment Setup
# This script is called by setup-core.ps1 on Windows to configure the Ubuntu WSL distro.
#
# Can also be run standalone:
# chmod +x wsl-ubuntu-setup.sh
# ./wsl-ubuntu-setup.sh
#
set -e
echo "πŸ”§ Starting WSL Ubuntu development environment setup..."
echo ""
# ============================================================================
# SYSTEM UPDATE
# ============================================================================
echo "πŸ“¦ Updating system packages..."
sudo apt update && sudo apt upgrade -y
# ============================================================================
# ESSENTIAL PACKAGES
# ============================================================================
echo "πŸ“¦ Installing essential packages..."
sudo apt install -y \
git \
curl \
wget \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
software-properties-common \
unzip \
build-essential \
jq \
htop
# ============================================================================
# PYTHON & UV
# ============================================================================
echo "🐍 Installing Python and uv..."
sudo apt install -y python3 python3-venv python3-dev
# Install uv - the fast Python package/project manager
if ! command -v uv &> /dev/null; then
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
fi
echo "uv $(uv --version) installed."
echo "Use 'uv pip install', 'uv venv', 'uv tool install' for Python workflows."
# ============================================================================
# GITHUB CLI
# ============================================================================
echo "πŸ™ Installing GitHub CLI..."
if ! command -v gh &> /dev/null; then
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
else
echo "GitHub CLI already installed."
fi
# ============================================================================
# AZURE CLI
# ============================================================================
echo "☁️ Installing Azure CLI..."
if ! command -v az &> /dev/null; then
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
else
echo "Azure CLI already installed."
fi
# ============================================================================
# AZURE DEVELOPER CLI
# ============================================================================
echo "☁️ Installing Azure Developer CLI..."
if ! command -v azd &> /dev/null; then
curl -fsSL https://aka.ms/install-azd.sh | bash
else
echo "Azure Developer CLI already installed."
fi
# ============================================================================
# .NET SDK
# ============================================================================
echo "πŸ”· Installing .NET SDKs..."
# Add Microsoft repository
if [ ! -f /etc/apt/sources.list.d/microsoft-prod.list ]; then
wget "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb" -O /tmp/packages-microsoft-prod.deb
sudo dpkg -i /tmp/packages-microsoft-prod.deb
rm /tmp/packages-microsoft-prod.deb
sudo apt update
fi
sudo apt install -y dotnet-sdk-8.0 dotnet-sdk-9.0 || true
# Install .NET global tools
echo "Installing .NET global tools..."
export PATH="$HOME/.dotnet/tools:$PATH"
dotnet tool update --global dotnet-ef 2>/dev/null || dotnet tool install --global dotnet-ef || true
dotnet tool update --global dotnet-format 2>/dev/null || dotnet tool install --global dotnet-format || true
# ============================================================================
# PODMAN (CONTAINERS)
# ============================================================================
echo "🐳 Installing Podman..."
sudo apt install -y podman podman-compose || true
# Configure rootless mode
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$USER" 2>/dev/null || true
podman system migrate 2>/dev/null || true
# Enable podman socket for Docker API compatibility
systemctl --user enable --now podman.socket 2>/dev/null || true
echo "Podman installed. Use 'podman' commands (compatible with Docker)."
# ============================================================================
# NVM & NODE.JS
# ============================================================================
echo "πŸ“— Installing nvm and Node.js..."
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
fi
# Load nvm
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Install LTS Node.js
nvm install --lts
nvm use --lts
nvm alias default 'lts/*'
echo "Node.js $(node --version) installed."
# ============================================================================
# ZSH & OH MY ZSH
# ============================================================================
echo "🐚 Installing Zsh and Oh My Zsh..."
if ! command -v zsh &> /dev/null; then
sudo apt install -y zsh
fi
if [ ! -d "$HOME/.oh-my-zsh" ]; then
RUNZSH=no CHSH=no KEEP_ZSHRC=yes sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
fi
# Install Powerlevel10k theme
ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
if [ ! -d "$ZSH_CUSTOM/themes/powerlevel10k" ]; then
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "$ZSH_CUSTOM/themes/powerlevel10k"
fi
# Configure .zshrc
if [ -f "$HOME/.zshrc" ]; then
# Set theme
sed -i 's/^ZSH_THEME=.*/ZSH_THEME="powerlevel10k\/powerlevel10k"/' "$HOME/.zshrc" 2>/dev/null || true
fi
# Set Zsh as default shell
ZSH_PATH="$(command -v zsh)"
if [ -n "$ZSH_PATH" ]; then
# Try chsh (may require password or fail in WSL)
sudo chsh -s "$ZSH_PATH" "$USER" 2>/dev/null || true
# Also set in /etc/wsl.conf for WSL default
if [ -f /etc/wsl.conf ]; then
if ! grep -q "default=$ZSH_PATH" /etc/wsl.conf 2>/dev/null; then
echo "[user]" | sudo tee -a /etc/wsl.conf > /dev/null
echo "default=$USER" | sudo tee -a /etc/wsl.conf > /dev/null
fi
fi
# Update /etc/passwd directly as fallback
sudo sed -i "s|$USER:/bin/bash|$USER:$ZSH_PATH|" /etc/passwd 2>/dev/null || true
echo "Default shell set to Zsh."
fi
# ============================================================================
# PATH CONFIGURATION
# ============================================================================
echo "πŸ”§ Configuring PATH..."
# Ensure .dotnet/tools is in PATH for both shells
for rcfile in "$HOME/.zshrc" "$HOME/.bashrc"; do
if [ -f "$rcfile" ]; then
grep -q '.dotnet/tools' "$rcfile" || echo 'export PATH="$HOME/.dotnet/tools:$PATH"' >> "$rcfile"
grep -q 'NVM_DIR' "$rcfile" || {
echo 'export NVM_DIR="$HOME/.nvm"' >> "$rcfile"
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> "$rcfile"
}
grep -q '.local/bin' "$rcfile" || echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$rcfile"
fi
done
# ============================================================================
# VERIFICATION
# ============================================================================
echo ""
echo "πŸ” Verifying installations..."
echo ""
echo "Git: $(git --version 2>/dev/null || echo 'not found')"
echo "Python: $(python3 --version 2>/dev/null || echo 'not found')"
echo "Node.js: $(node --version 2>/dev/null || echo 'not found')"
echo "npm: $(npm --version 2>/dev/null || echo 'not found')"
echo "gh: $(gh --version 2>/dev/null | head -1 || echo 'not found')"
echo "az: $(az --version 2>/dev/null | head -1 || echo 'not found')"
echo "azd: $(azd version 2>/dev/null || echo 'not found')"
echo "dotnet: $(dotnet --version 2>/dev/null || echo 'not found')"
echo "podman: $(podman --version 2>/dev/null || echo 'not found')"
# ============================================================================
# CLEANUP
# ============================================================================
echo ""
echo "🧹 Cleaning up..."
sudo apt autoremove -y
sudo apt clean
echo ""
echo "=============================================="
echo "βœ… WSL Ubuntu development environment setup complete!"
echo "=============================================="
echo ""
echo "Next steps:"
echo " 1. Restart your terminal or run: source ~/.zshrc"
echo " 2. Configure Zsh theme with: p10k configure"
echo " 3. Authenticate with: gh auth login"
echo " 4. Authenticate with: az login"
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment