Last active
August 30, 2025 14:41
-
-
Save bentman/638c478ae791598780c70749139e382f to your computer and use it in GitHub Desktop.
Install Dev Tools on Win Server 2022
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
<# | |
.SYNOPSIS | |
Script to install Dev Tools on Windows Server (tested on 2022) | |
.DESCRIPTION | |
Installs the following from multiple resources: | |
Microsoft.VCLibs *latest (GitHub via winget-cli deps) | |
Microsoft.UI.Xaml *latest (GitHub API) | |
winget-cli (dynamic version retrieval from api.github.com) | |
Microsoft.WindowsTerminal (dynamic version retrieval from api.github.com) | |
Microsoft pwsh.exe vCurrent (winget) | |
Microsoft VSCode vCurrent (winget) | |
Azure CLI vCurrent (PoSh/MSI) | |
.NOTES | |
Add-DevToMyWinServer.ps1 | |
Version: 1.5 | |
Creation Date: 2024-05-04 | |
Updated: 2025-08-30 | |
Author: https://github.com/bentman | |
#> | |
# Install NuGet (no-prompt) & set PSGallery trusted | |
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 | |
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | |
Push-Location ~\Downloads | |
# Detect system architecture | |
switch ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture) { | |
'X64' { $arch = 'x64' } | |
'Arm64' { $arch = 'arm64'; $packageArch = 'arm' } # AppX packages use 'arm' for ARM64 | |
'X86' { $arch = 'x86' } | |
default { throw "Unsupported architecture: $($_)" } | |
} | |
# Set package architecture (defaults to same as system arch for x64/x86) | |
if (-not $packageArch) { $packageArch = $arch } | |
Write-Host "Detected architecture: $arch (using '$packageArch' for package names)" | |
# Install Microsoft.VCLibs | |
$latestBase = 'https://github.com/microsoft/winget-cli/releases/latest/download' | |
$depsZip = 'DesktopAppInstaller_Dependencies.zip' | |
$depsOut = Join-Path $PWD $depsZip | |
$depsDir = Join-Path $PWD 'wingetDeps' | |
Invoke-WebRequest -Uri "$latestBase/$depsZip" -OutFile $depsOut -UseBasicParsing | |
if (-not (Test-Path $depsDir)) { New-Item -Path $depsDir -ItemType Directory -Force } | |
Expand-Archive -LiteralPath $depsOut -DestinationPath $depsDir -Force | |
# Pick the right VCLibs appx by architecture | |
$appxVCLibs = Get-ChildItem -Path (Join-Path $depsDir $packageArch) -Filter 'Microsoft.VCLibs*.appx' | Select-Object -First 1 | |
if (-not $appxVCLibs) { throw "VCLibs package for $packageArch not found." } | |
Write-Host "Installing VCLibs from $($appxVCLibs.FullName)" | |
Add-AppxPackage -Path $appxVCLibs.FullName -ForceApplicationShutdown | |
# Install Microsoft.UI.Xaml | |
$apiUrl = 'https://api.github.com/repos/microsoft/microsoft-ui-xaml/releases/latest' | |
$response = Invoke-WebRequest -Uri $apiUrl -UseBasicParsing | |
$json = $response.Content | ConvertFrom-Json | |
$tag = $json.tag_name # e.g. "v2.8.7" | |
$appxName = "Microsoft.UI.Xaml.$($tag.TrimStart('v')).$packageArch.appx" | |
$uiUrl = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/$tag/$appxName" | |
$uiOut = Join-Path $PWD $appxName | |
Write-Host "Downloading Microsoft.UI.Xaml ($packageArch) from $uiUrl" | |
Invoke-WebRequest -Uri $uiUrl -OutFile $uiOut -UseBasicParsing | |
Write-Host "Installing Microsoft.UI.Xaml from $uiOut" | |
Add-AppxPackage -Path $uiOut -ForceApplicationShutdown | |
# Install WinGet (winget-cli) | |
$winGetRepo = "https://api.github.com/repos/microsoft/winget-cli/releases/latest" | |
$release = Invoke-WebRequest -Uri $winGetRepo -UseBasicParsing | ConvertFrom-Json | |
$licXmlLink = $release.assets | Where-Object browser_download_url -Match '_License1.xml' | Select-Object -ExpandProperty browser_download_url | |
$licXmlName = '_License1.xml' | |
Invoke-WebRequest -Uri $licXmlLink -OutFile $licXmlName -UseBasicParsing | |
Unblock-File .\$licXmlName | |
$winGetLink = $release.assets | Where-Object browser_download_url -Match '.msixbundle' | Select-Object -ExpandProperty browser_download_url | |
$winGetName = "winget.msixbundle" | |
Invoke-WebRequest -Uri $winGetLink -OutFile $winGetName -UseBasicParsing | |
Unblock-File .\$winGetName | |
Add-AppxProvisionedPackage -Online -PackagePath .\$winGetName -LicensePath .\$licXmlName -Verbose | |
# Install Windows Terminal | |
$termRepo = "https://api.github.com/repos/microsoft/terminal/releases/latest" | |
$termRel = Invoke-WebRequest -Uri $termRepo -UseBasicParsing | ConvertFrom-Json | |
$termLink = $termRel.assets | Where-Object browser_download_url -Match '.msixbundle' | Select-Object -ExpandProperty browser_download_url | |
$termName = 'WindowsTerminal.msixbundle' | |
Invoke-WebRequest -Uri $termLink -OutFile .\$termName -Verbose | |
Unblock-File .\$termName | |
Add-AppPackage -Path .\$termName -Verbose | |
Pop-Location | |
################################################ | |
### NOTE: This now requires shell restart!!! ### | |
################################################ | |
# Search for available PowerShell versions (may prompt to accept terms) | |
winget search Microsoft.PowerShell | |
# Install PowerShell 7 from winget | |
winget install --id Microsoft.Powershell --source winget | |
# Search for available VS Code versions (may prompt to accept terms) | |
winget search Microsoft.VisualStudioCode | |
# Install VS Code from winget | |
winget install --id Microsoft.VisualStudioCode --source winget | |
# Search for available Azure CLI versions (may prompt to accept terms) | |
winget search Microsoft.AzureCLI | |
# Install Azure CLI from winget | |
winget install --id Microsoft.AzureCLI --source winget | |
# Update all installed packages | |
winget update --all | |
# Install OpenSSH Client and Server | |
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0 | |
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | |
# Start and configure SSH Server Service | |
Get-Service sshd | Start-Service | |
Set-Service -Name sshd -StartupType 'Automatic' | |
# Configure SSH Server firewall rule | |
$sshRule = Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | |
if (-not $sshRule) { | |
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | |
} | |
elseif (-not $sshRule.Enabled) { | |
Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Enabled True | |
} | |
# Set PowerShell 7 as default SSH shell (if available) | |
$pwshPath = (Get-Command pwsh -ErrorAction SilentlyContinue).Source | |
if ($pwshPath) { | |
New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value "`"$pwshPath`"" -PropertyType String -Force | |
Write-Host "SSH default shell set to: $pwshPath" | |
} | |
else { | |
# Fallback to Windows PowerShell 5.1 | |
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force | |
Write-Host "SSH default shell set to: Windows PowerShell 5.1" | |
} | |
# Disable NLA on RDP | |
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated to v1.5
-- VCLibs aka.ms download is 'version-locked', now getting directly from winget deps
-- Updated UI.Xaml to use dynamic version retrieval