Last active
January 14, 2026 14:03
-
-
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 | |
| 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" | |
| } |
Author
> 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!
Author
Updated dynamic version retrieval...
winget-cli (api.github.com)
Microsoft.WindowsTerminal (api.github.com)
Author
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
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'