Skip to content

Instantly share code, notes, and snippets.

@bentman
Last active August 30, 2025 14:41
Show Gist options
  • Save bentman/638c478ae791598780c70749139e382f to your computer and use it in GitHub Desktop.
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
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
@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