Skip to content

Instantly share code, notes, and snippets.

@bentman
Last active January 14, 2026 14:03
Show Gist options
  • Select an option

  • Save bentman/638c478ae791598780c70749139e382f to your computer and use it in GitHub Desktop.

Select an option

Save bentman/638c478ae791598780c70749139e382f to your computer and use it in GitHub Desktop.
Install Dev Tools on Win Server 2022+
<#
.SYNOPSIS
Script to install Dev Tools on Windows Server (tested on 2022)
.DESCRIPTION
Reference script (gist) for manual execution on Windows Server 2022+.
Requires user interaction for installations. Installs dev tools in order:
1. Microsoft.VCLibs (latest via GitHub winget-cli deps)
2. Microsoft.UI.Xaml (latest via GitHub API)
3. winget-cli (latest via GitHub API)
4. Microsoft.WindowsTerminal (latest via GitHub API)
NOTE: Installing from winget, now requires shell restart after winget install.
5. Microsoft pwsh.exe (current via winget)
6. Microsoft VSCode (current via winget)
7. Azure CLI (current via winget)
Also configures SSH and RDP.
.NOTES
Add-DevToMyWinServer.ps1
Version: 1.6
Creation Date: 2024-05-04
Updated: 2026-01-14
Author: https://github.com/bentman
#>
Push-Location ~\Downloads
# 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
# Detect system architecture
$systemArch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture
$archMap = @{
'X64' = @{ Arch = 'x64'; PackageArch = 'x64' }
'Arm64' = @{ Arch = 'arm64'; PackageArch = 'arm' } # AppX packages use 'arm' for ARM64 due to Microsoft's naming convention
'X86' = @{ Arch = 'x86'; PackageArch = 'x86' }
}
if ($archMap.ContainsKey($systemArch)) {
$arch = $archMap[$systemArch].Arch
$packageArch = $archMap[$systemArch].PackageArch
}
else { throw "Unsupported architecture: $systemArch" }
# Validate package architecture
if (-not $packageArch) { throw "Failed to determine package architecture for $arch." }
Write-Host "Detected architecture: $arch (using '$packageArch' for package names)"
Write-Verbose "System architecture detected as $systemArch"
### 1. Install Microsoft.VCLibs (dependency for UI.Xaml and WinGet)
$latestBase = 'https://github.com/microsoft/winget-cli/releases/latest/download' # Base URL for latest winget-cli release downloads
$depsZip = 'DesktopAppInstaller_Dependencies.zip' # Name of dependencies zip file
$depsOut = Join-Path $PWD $depsZip # Local path for deps zip
$depsDir = Join-Path $PWD 'wingetDeps' # Directory for extracted deps
Invoke-WebRequest -Uri "$latestBase/$depsZip" -OutFile $depsOut -UseBasicParsing # Download deps zip
Unblock-File $depsOut # Unblock zip file
if (-not (Test-Path $depsDir)) { New-Item -Path $depsDir -ItemType Directory -Force } # Create extraction dir
Expand-Archive -LiteralPath $depsOut -DestinationPath $depsDir -Force # Extract zip
$appxVCLibs = Get-ChildItem -Path (Join-Path $depsDir $packageArch) -Filter 'Microsoft.VCLibs*.appx' | Select-Object -First 1 # Find VCLibs appx file
if (-not $appxVCLibs) { throw "VCLibs package for $packageArch not found." } # Validate VCLibs found
Write-Host "Installing VCLibs from $($appxVCLibs.FullName)" # Inform user of installation
Add-AppxPackage -Path $appxVCLibs.FullName -ForceApplicationShutdown # Install VCLibs
### 2. Install Microsoft.WindowsAppRuntime (dependency for WinGet)
$winAppSdkRepo = "https://api.github.com/repos/microsoft/WindowsAppSDK/releases/latest" # URL for latest WindowsAppSDK release
$winAppSdkRelease = Invoke-WebRequest -Uri $winAppSdkRepo -UseBasicParsing | ConvertFrom-Json # Fetch latest release info
$versionMatch = $winAppSdkRelease.body | Select-String -Pattern 'Microsoft\.WindowsAppSDK/(\d+\.\d+\.\d+)' # Extract version from release body
if (-not $versionMatch) { throw "Could not find WindowsAppSDK version in release body." } # Validate version found
$fullVersion = $versionMatch.Matches[0].Groups[1].Value # Get full version string
$majorMinor = $fullVersion -replace '\.\d+$', '' # Extract major.minor version
$winAppRunUrl = "https://aka.ms/windowsappsdk/$majorMinor/$fullVersion/WindowsAppRuntimeInstall-$arch.exe" # Construct download URL
$winAppRunName = Join-Path $PWD "WindowsAppRuntimeInstall-$arch.exe" # Local file path
Write-Host "Downloading WindowsAppRuntime installer ($arch) from $winAppRunUrl" # Inform user of download
Invoke-WebRequest -Uri $winAppRunUrl -OutFile $winAppRunName -UseBasicParsing # Download installer
Unblock-File $winAppRunName # Unblock installer file
Write-Host "Installing WindowsAppRuntime from $winAppRunName" # Inform user of installation
Start-Process -FilePath $winAppRunName -Wait # Install WindowsAppRuntime
### 3. Install WinGet (winget-cli) (depends on VCLibs and UI.Xaml)
$winGetRepo = "https://api.github.com/repos/microsoft/winget-cli/releases/latest" # URL for latest winget-cli release
$release = Invoke-WebRequest -Uri $winGetRepo -UseBasicParsing | ConvertFrom-Json # Fetch latest release
$licXmlLink = $release.assets | `
Where-Object browser_download_url -Match '_License1.xml' | `
Select-Object -ExpandProperty browser_download_url # Find license XML download URL
$licXmlName = '_License1.xml' # Local name for license file
Invoke-WebRequest -Uri $licXmlLink -OutFile $licXmlName -UseBasicParsing # Download license
Unblock-File .\$licXmlName # Unblock license file
$winGetLink = $release.assets | Where-Object browser_download_url -Match '.msixbundle' | `
Select-Object -ExpandProperty browser_download_url # Find WinGet msixbundle download URL
$winGetName = "winget.msixbundle" # Local name for WinGet bundle
Invoke-WebRequest -Uri $winGetLink -OutFile $winGetName -UseBasicParsing # Download WinGet bundle
Unblock-File .\$winGetName # Unblock bundle file
Add-AppxProvisionedPackage -Online -PackagePath .\$winGetName -LicensePath .\$licXmlName -Verbose # Install WinGet
### 4. Install Windows Terminal (depends on VCLibs and UI.Xaml)
$termRepo = "https://api.github.com/repos/microsoft/terminal/releases/latest" # URL for latest Windows Terminal release
$termRel = Invoke-WebRequest -Uri $termRepo -UseBasicParsing | ConvertFrom-Json # Fetch latest release
$termLink = $termRel.assets | Where-Object { # Find the appropriate msixbundle for Windows Terminal
$_.browser_download_url -match '\.msixbundle$' -and $_.browser_download_url -notmatch 'Windows10_PreinstallKit' # Exclude Windows 10 preinstall kits
} | Select-Object -ExpandProperty browser_download_url # Get download URL
$termName = 'WindowsTerminal.msixbundle' # Local name for Terminal bundle
Invoke-WebRequest -Uri $termLink -OutFile .\$termName -Verbose # Download Terminal bundle
Unblock-File .\$termName # Unblock bundle file
Add-AppPackage -Path .\$termName -Verbose # Install Windows Terminal
Pop-Location
#################################################################################################################
### NOTE: Shell restart required after WinGet installation for winget command to be available in new sessions ###
#################################################################################################################
# 5. Install PowerShell 7 via winget (requires winget from step 3)
# Search for available PowerShell versions (may prompt to accept terms)
winget search Microsoft.PowerShell
# Install PowerShell 7
winget install --id Microsoft.Powershell --source winget
# 6. Install VS Code via winget
# Search for available VS Code versions (may prompt to accept terms)
winget search Microsoft.VisualStudioCode
# Install VS Code
winget install --id Microsoft.VisualStudioCode --source winget
# 7. Install Azure CLI via winget
# Search for available Azure CLI versions (may prompt to accept terms)
winget search Microsoft.AzureCLI
# Install Azure CLI
winget install --id Microsoft.AzureCLI --source winget
# 8. Update all installed packages via winget
winget update --all
# 9. Disable NLA on RDP (optional, not recommended for production)
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 0
### SSH Configuration ###
# 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 # Start SSH service
Set-Service -Name sshd -StartupType 'Automatic' # Set to auto-start
# 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 # Create firewall rule for SSH
}
elseif (-not $sshRule.Enabled) {
Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Enabled True # Enable existing rule
}
# 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 # Set pwsh as default shell
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 # Set PowerShell 5.1 as default
Write-Host "SSH default shell set to: Windows PowerShell 5.1"
}
@jakehildreth
Copy link

Thanks! This rules!

@bentman
Copy link
Author

bentman commented May 4, 2024

Updated versions...
winget-cli v1.7.11132 (github)
Microsoft.WindowsTerminal v1.19.11213.0 (github)

@JonSuper
Copy link

VERBOSE: GET https://github.com/microsoft/terminal/releases/download/v1.19.11213.0/Microsoft.WindowsTerminal_1.19.11213.0_8wekyb3d8bbwe.msixbundle with 0-byte payload
VERBOSE: received 21550188-byte response of content type application/octet-stream
VERBOSE: Performing the operation "Deploy package" on target "D:\Microsoft.WindowsTerminal_1.19.11213.0_8wekyb3d8bbwe.msixbundle".
Add-AppPackage : Deployment failed with HRESULT: 0x80073CF3, Package failed updates, dependency or conflict validation.
Windows cannot install package Microsoft.WindowsTerminal_1.19.11213.0_x64__8wekyb3d8bbwe because this package depends on a framework that could not be found. Provide the framework "Microsoft.UI.Xaml.2.8" published by "CN=Microsof
t Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", with neutral or x64 processor architecture and minimum version 8.2305.5001.0, along with this package to install. The frameworks with name "Microsoft.UI.Xaml
.2.8" currently installed are: {}

fail on Windows Server 2022
should upgrade to 2.8:
$MsftUi_Link = 'https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx'

@bentman
Copy link
Author

bentman commented May 21, 2024

> fail on Windows Server 2022
should upgrade to 2.8:

$MsftUi_Link = 'https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx'

yup. looking for a way to do them all more 'dynamically' (aka - query the latest version to download). i'll update the static until then. thanks!

@bentman
Copy link
Author

bentman commented May 25, 2024

Updated dynamic version retrieval...
winget-cli (api.github.com)
Microsoft.WindowsTerminal (api.github.com)

@bentman
Copy link
Author

bentman commented Aug 30, 2025

Updated to v1.5

  • System Architecture handling
  • Compensates for the 'moving targets' (VCLibs & UI.Xaml)
    -- VCLibs aka.ms download is 'version-locked', now getting directly from winget deps
    -- Updated UI.Xaml to use dynamic version retrieval

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment