Last active
April 17, 2025 18:42
-
-
Save garyo/3c39e294a65d666a4ba0b1f538137989 to your computer and use it in GitHub Desktop.
Script to set up a Windows dev machine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script to set up a Windows 11 machine for software development | |
# Author: Gary Oberbrunner, [email protected] | |
# License: MIT | |
# Requires elevation | |
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { | |
Write-Host "This script requires admin privileges. Please run PowerShell as Administrator and try again." | |
Exit 1 | |
} | |
# Enable Developer Mode | |
Write-Host "Enabling Developer Mode..." | |
$RegistryKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" | |
if (-NOT(Test-Path -Path $RegistryKeyPath)) { | |
New-Item -Path $RegistryKeyPath -ItemType Directory -Force | |
} | |
Set-ItemProperty -Path $RegistryKeyPath -Name "AllowDevelopmentWithoutDevLicense" -Value 1 | |
Set-ItemProperty -Path $RegistryKeyPath -Name "AllowAllTrustedApps" -Value 1 | |
# Enable symlink creation without admin privileges | |
Write-Host "Enabling symlink creation for non-admin users..." | |
$symlinkKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" | |
Set-ItemProperty -Path $symlinkKeyPath -Name "EnableLinksAcrossUserAccounts" -Value 1 | |
# Set HOME environment variable | |
Write-Host "Setting HOME environment variable..." | |
[System.Environment]::SetEnvironmentVariable('HOME', $env:USERPROFILE, [System.EnvironmentVariableTarget]::User) | |
$env:HOME = $env:USERPROFILE | |
######################################################################## | |
# Unlink standard folders from OneDrive, store them locally | |
######################################################################## | |
# Stop OneDrive process before unlinking folders | |
Stop-Process -Name "OneDrive" -Force -ErrorAction SilentlyContinue | |
# Registry paths for Known Folders | |
$RegistryPaths = @( | |
"HKCU:\Software\Microsoft\OneDrive\Accounts\Business1\Tenants", | |
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{018D5C66-4533-4307-9B53-224DE2ED1FE6}", | |
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\OneDrive", | |
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders", | |
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" | |
) | |
# Default folder locations | |
$DefaultFolders = @{ | |
"Desktop" = [Environment]::GetFolderPath("Desktop") | |
"Documents" = [Environment]::GetFolderPath("MyDocuments") | |
"Pictures" = [Environment]::GetFolderPath("MyPictures") | |
} | |
# Update Shell Folders registry keys to default locations | |
foreach ($Path in $RegistryPaths) { | |
if (Test-Path $Path) { | |
foreach ($Folder in $DefaultFolders.Keys) { | |
$DefaultLocation = $DefaultFolders[$Folder] | |
try { | |
Set-ItemProperty -Path $Path -Name $Folder -Value $DefaultLocation -ErrorAction SilentlyContinue | |
Write-Host "Updated $Folder to $DefaultLocation" | |
} | |
catch { | |
Write-Host "Could not update $Folder location: $_" | |
} | |
} | |
} | |
} | |
# Remove OneDrive sync relationships | |
$OneDrivePath = "$env:USERPROFILE\OneDrive" | |
if (Test-Path $OneDrivePath) { | |
Remove-Item -Path "$OneDrivePath\Desktop" -Force -Recurse -ErrorAction SilentlyContinue | |
Remove-Item -Path "$OneDrivePath\Documents" -Force -Recurse -ErrorAction SilentlyContinue | |
Remove-Item -Path "$OneDrivePath\Pictures" -Force -Recurse -ErrorAction SilentlyContinue | |
Write-Host "Removed OneDrive sync folders" | |
} | |
# Restart Explorer to apply changes | |
Stop-Process -Name "explorer" -Force -ErrorAction SilentlyContinue | |
Start-Process "explorer" | |
Write-Host "OneDrive folders have been unlinked. Please verify your files are in the correct locations." | |
######################################################################## | |
# Install apps and utilities | |
######################################################################## | |
# Helper function to run commands as the original non-admin user | |
# Helper function to run commands as the original non-admin user | |
function Invoke-AsUser { | |
param([string]$command) | |
$wrappedCommand = @" | |
`$ErrorActionPreference = 'Stop' | |
try { | |
$command | |
if (`$LASTEXITCODE) { exit `$LASTEXITCODE } | |
} catch { | |
Write-Host "Error: `$_" -ForegroundColor Red | |
Write-Host "Stack Trace: `$(`$_.ScriptStackTrace)" -ForegroundColor Red | |
pause | |
exit 1 | |
} | |
Write-Host "Command completed successfully" | |
exit 0 | |
"@ | |
$processInfo = New-Object System.Diagnostics.ProcessStartInfo | |
$processInfo.FileName = "powershell.exe" | |
$processInfo.Arguments = "-Command $wrappedCommand" | |
$processInfo.UseShellExecute = $true | |
$processInfo.Verb = "open" | |
$process = New-Object System.Diagnostics.Process | |
$process.StartInfo = $processInfo | |
$process.Start() | |
$process.WaitForExit() | |
if ($process.ExitCode -ne 0) { | |
throw "Process exited with code $($process.ExitCode)" | |
} | |
return $true | |
} | |
# Install Scoop and packages as non-admin user | |
Write-Host "Installing Scoop and packages as regular user..." | |
$scoopInstallScript = @' | |
# Install Scoop if not already installed | |
if (!(Get-Command scoop -ErrorAction SilentlyContinue)) { | |
Write-Host "Installing Scoop..." | |
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser | |
# Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') | |
Invoke-RestMethod get.scoop.sh | Invoke-Expression | |
} | |
# Install aria2 for parallel downloads first | |
if (!(Get-Command aria2c -ErrorAction SilentlyContinue)) { | |
Write-Host "Installing aria2 for faster downloads..." | |
scoop install aria2 | |
scoop config aria2-enabled true | |
scoop config aria2-warning-enabled false | |
} | |
# Add scoop buckets (need git first) | |
Write-Host "Adding Scoop buckets with git and 7zip..." | |
scoop install 7zip git | |
$buckets = @('extras', 'sysinternals', 'nerd-fonts', 'nonportable') | |
foreach ($bucket in $buckets) { | |
Write-Host "Adding $bucket bucket..." | |
scoop bucket add $bucket | |
} | |
# Install all packages in parallel | |
Write-Host "Installing all packages..." | |
scoop install ` | |
googlechrome vscode vim rapidee ` | |
cppcheck gh diffutils kdiff3 uv ` | |
everything-np debugview procmon sysmon dependencies ` | |
fd ripgrep eza fzf msys2 ` | |
logseq synctrayzor | |
'@ | |
Invoke-AsUser $scoopInstallScript | |
# Define MSYS2 paths for Scoop installation | |
$msys2Root = "$env:USERPROFILE\scoop\apps\msys2\current" | |
$msys2Shell = "$msys2Root\msys2.exe" | |
# Update nsswitch.conf using bash | |
Write-Host "Configuring nsswitch.conf..." | |
$bashCommand = 'sed -i "/^db_home:/c\db_home: windows" /etc/nsswitch.conf' | |
Start-Process -FilePath $msys2Shell -ArgumentList "-defterm", "-no-start", "-here", "-c", $bashCommand -Wait | |
# Configure Git line endings | |
Write-Host "Configuring Git line endings..." | |
git config --global core.autocrlf false | |
# Post-installation configurations | |
Write-Host "Applying post-installation configurations..." | |
# VSCode extensions and settings | |
Write-Host "Installing VSCode extensions..." | |
$vsCodeExtensions = @( | |
'ms-vscode.cpptools', | |
'eamodio.gitlens', | |
'GitHub.copilot', | |
'ms-python.python', | |
'rust-lang.rust-analyzer', | |
'vadimcn.vscode-lldb' | |
) | |
foreach ($extension in $vsCodeExtensions) { | |
code --install-extension $extension | |
} | |
# Configure Everything | |
Write-Host "Configuring Everything search..." | |
# Start Everything service | |
Start-Process "$env:USERPROFILE\scoop\shims\everything.exe" -ArgumentList "-startup" | |
# Wait for service to start | |
Start-Sleep -Seconds 2 | |
# Configure Sysmon | |
Write-Host "Configuring Sysmon..." | |
$sysmonConfigUrl = "https://raw.githubusercontent.com/SwiftOnSecurity/sysmon-config/master/sysmonconfig-export.xml" | |
$sysmonConfigPath = "$env:TEMP\sysmonconfig.xml" | |
Invoke-WebRequest -Uri $sysmonConfigUrl -OutFile $sysmonConfigPath | |
Start-Process "sysmon.exe" -ArgumentList "-accepteula -i $sysmonConfigPath" -Wait | |
# Configure fzf | |
Write-Host "Configuring fzf..." | |
$profileContent = @" | |
# fzf configuration | |
`$env:FZF_DEFAULT_COMMAND = 'fd --type f --hidden --follow --exclude .git' | |
`$env:FZF_DEFAULT_OPTS = '--height 40% --layout=reverse --border' | |
# Set eza as default list command | |
Set-Alias -Name ls -Value eza -Option AllScope | |
Set-Alias -Name ll -Value 'eza -l' -Option AllScope | |
Set-Alias -Name la -Value 'eza -la' -Option AllScope | |
Set-Alias -Name lt -Value 'eza --tree' -Option AllScope | |
"@ | |
# Create PowerShell profile directory if it doesn't exist | |
$profileDir = Split-Path $PROFILE -Parent | |
if (!(Test-Path $profileDir)) { | |
New-Item -Path $profileDir -ItemType Directory -Force | |
} | |
Add-Content $PROFILE $profileContent | |
# Install commonly needed MSYS2 packages | |
Write-Host "Installing common MSYS2 packages..." | |
Start-Process -FilePath $msys2Shell -ArgumentList "-defterm", "-no-start", "-here", "-c", "pacman -S --noconfirm mingw-w64-x86_64-toolchain base-devel" -Wait | |
Write-Host "Setting ssh-agent service to start manually" | |
Get-Service -Name ssh-agent | Set-Service -StartupType Manual | |
Write-Host "Setup complete! Additional notes: | |
1. VSCode: Log in to GitHub to activate Copilot | |
2. Everything: The service is now running in the background | |
3. Sysmon: Configured with SwiftOnSecurity's config | |
4. PowerShell: New aliases and fzf configuration added to profile | |
5. MSYS2: Basic development tools installed | |
Please log out and back in for all changes to take effect. | |
GitHub CLI: Run 'gh auth login' to authenticate." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment