Skip to content

Instantly share code, notes, and snippets.

@likamrat
Created February 13, 2023 00:41
Show Gist options
  • Save likamrat/cae833a6e5b3461709f14c093c21c293 to your computer and use it in GitHub Desktop.
Save likamrat/cae833a6e5b3461709f14c093c21c293 to your computer and use it in GitHub Desktop.
Installing Winget om Windows Server 2019/2022
Write-Information "This script needs be run on Windows Server 2019 or 2022"
If ($PSVersionTable.PSVersion.Major -ge 7){ Write-Error "This script needs be run by version of PowerShell prior to 7.0" }
# Define environment variables
$downloadDir = "C:\WinGet"
$gitRepo = "microsoft/winget-cli"
$msiFilenamePattern = "*.msixbundle"
$licenseFilenamePattern = "*.xml"
$releasesUri = "https://api.github.com/repos/$gitRepo/releases/latest"
# Preparing working directory
New-Item -Path $downloadDir -ItemType Directory
Push-Location $downloadDir
# Downloaing artifacts
function Install-Package {
param (
[string]$PackageFamilyName
)
Write-Host "Querying latest $PackageFamilyName version and its dependencies..."
$response = Invoke-WebRequest `
-Uri "https://store.rg-adguard.net/api/GetFiles" `
-Method "POST" `
-ContentType "application/x-www-form-urlencoded" `
-Body "type=PackageFamilyName&url=$PackageFamilyName&ring=RP&lang=en-US" -UseBasicParsing
Write-Host "Parsing response..."
$regex = '<td><a href=\"([^\"]*)\"[^\>]*\>([^\<]*)<\/a>'
$packages = (Select-String $regex -InputObject $response -AllMatches).Matches.Groups
$result = $true
for ($i = $packages.Count - 1; $i -ge 0; $i -= 3) {
$url = $packages[$i - 1].Value;
$name = $packages[$i].Value;
$extCheck = @(".appx", ".appxbundle", ".msix", ".msixbundle") | % { $x = $false } { $x = $x -or $name.EndsWith($_) } { $x }
$archCheck = @("x64", "neutral") | % { $x = $false } { $x = $x -or $name.Contains("_$($_)_") } { $x }
if ($extCheck -and $archCheck) {
# Skip if package already exists on system
$currentPackageFamilyName = (Select-String "^[^_]+" -InputObject $name).Matches.Value
$installedVersion = (Get-AppxPackage "$currentPackageFamilyName*").Version
$latestVersion = (Select-String "_(\d+\.\d+.\d+.\d+)_" -InputObject $name).Matches.Value
if ($installedVersion -and ($installedVersion -ge $latestVersion)) {
Write-Host "${currentPackageFamilyName} is already installed, skipping..." -ForegroundColor "Yellow"
continue
}
try {
Write-Host "Downloading package: $name"
$tempPath = "$(Get-Location)\$name"
Invoke-WebRequest -Uri $url -Method Get -OutFile $tempPath
Add-AppxPackage -Path $tempPath
Write-Host "Successfully installed:" $name
} catch {
$result = $false
}
}
}
return $result
}
Write-Host "`n"
function Install-Package-With-Retry {
param (
[string]$PackageFamilyName,
[int]$RetryCount
)
for ($t = 0; $t -le $RetryCount; $t++) {
Write-Host "Attempt $($t + 1) out of $RetryCount..." -ForegroundColor "Cyan"
if (Install-Package $PackageFamilyName) {
return $true
}
}
return $false
}
$licenseDownloadUri = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $licenseFilenamePattern ).browser_download_url
$licenseFilename = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $licenseFilenamePattern ).name
$licenseJoinPath = Join-Path -Path $downloadDir -ChildPath $licenseFilename
Invoke-WebRequest -Uri $licenseDownloadUri -OutFile ( New-Item -Path $licenseJoinPath -Force )
$result = @("Microsoft.DesktopAppInstaller_8wekyb3d8bbwe") | ForEach-Object { $x = $true } { $x = $x -and (Install-Package-With-Retry $_ 3) } { $x }
$msiFilename = ((Get-ChildItem -Path $downloadDir) | Where-Object name -like $msiFilenamePattern ).name
$msiJoinPath = Join-Path -Path $downloadDir -ChildPath $msiFilename
# Installing winget
Add-ProvisionedAppPackage -Online -PackagePath $msiJoinPath -LicensePath $licenseJoinPath -Verbose
Write-Host "`n"
# Test if winget has been successfully installed
if ($result -and (Test-Path -Path "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe")) {
Write-Host "Congratulations! Windows Package Manager (winget) $(winget --version) installed successfully" -ForegroundColor "Green"
} else {
Write-Host "Oops... Failed to install Windows Package Manager (winget)" -ForegroundColor "Red"
}
# Cleanup
Push-Location $HOME
Remove-Item $downloadDir -Recurse -Force
@Karl-WE
Copy link

Karl-WE commented Mar 7, 2024

The easiest thing is currently to use the "predeploy" package of Microsoft Terminal which comes with similar prereqs installing these and then continueing with winget msix.

@cheema-corellian
Copy link

The easiest thing is currently to use the "predeploy" package of Microsoft Terminal which comes with similar prereqs installing these and then continueing with winget msix.

@Karl-WE I don't mean to sound ungrateful, but why not provide a link to the said "predeploy" package? And yes, I did Google. And did find various different pieces of software that could be considered relevant. None were called "predeploy" package for MS Terminal.

@aweedman
Copy link

aweedman commented Mar 9, 2024

Hi had winget working for a while on Server 2019. I installed the different packages manually. At some point recently winget stopped working. I tried the non-Chocately script above. It seemed to run currently. Getting when I run winget:

Program 'winget.exe' failed to run: The file cannot be accessed by the systemAt line:1 char:1

  • winget upgrade --all

At line:1 char:1

  • winget upgrade --all
  •   + CategoryInfo          : ResourceUnavailable: (:) [], ApplicationFailedException
      + FullyQualifiedErrorId : NativeCommandFailed
    
    

@Karl-WE
Copy link

Karl-WE commented Mar 12, 2024

@cheema-corellian understand your point. No worries.

Head to Microsoft Terminal releases >Release Version
https://github.com/microsoft/terminal/releases

Use the files from the preinstall kit they contain files also required for winget.

These can be installed on Windows Server 2019/2022 (unsupported), Windows Server 2025 has winget inbox.

Example
Microsoft.WindowsTerminal_1.19.10302.0_8wekyb3d8bbwe.msixbundle_Windows10_PreinstallKit.zip

One can argue that by today some prerequisites are available for download via public webpage, offered by Microsoft like vc redist msix packages.

The hardest part is certainly to get the the package manager running that's a requirement for winget and MSIX.

@pariswells
Copy link

@garzamSEA If you are still struggling, this is the easiest way to get WinGet installed on Server 2019 or 2022:

# Install Chocolately Package Manager
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
$env:Path = [System.Environment]::ExpandEnvironmentVariables([System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User"))

# Install WinGet from Chocolatey
choco install winget
choco install winget.powershell

# Fix Permissions
TAKEOWN /F "C:\Program Files\WindowsApps" /R /A /D Y
ICACLS "C:\Program Files\WindowsApps" /grant Administrators:F /T

# Add Environment Path
$ResolveWingetPath = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe"
if ($ResolveWingetPath) {
	$WingetPath = $ResolveWingetPath[-1].Path
}
$ENV:PATH += ";$WingetPath"
$SystemEnvPath = [System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::Machine)
$SystemEnvPath += ";$WingetPath;"
setx /M PATH "$SystemEnvPath"

If you prefer to do it manually without the assistance of Chocolatey:

# Create WinGet Folder
New-Item -Path C:\WinGet -ItemType directory -ErrorAction SilentlyContinue

# Install VCLibs
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile "C:\WinGet\Microsoft.VCLibs.x64.14.00.Desktop.appx"
Add-AppxPackage "C:\WinGet\Microsoft.VCLibs.x64.14.00.Desktop.appx"

# Install Microsoft.UI.Xaml from NuGet
Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.3 -OutFile "C:\WinGet\Microsoft.UI.Xaml.2.7.3.zip"
Expand-Archive "C:\WinGet\Microsoft.UI.Xaml.2.7.3.zip" -DestinationPath "C:\WinGet\Microsoft.UI.Xaml.2.7.3"
Add-AppxPackage "C:\WinGet\Microsoft.UI.Xaml.2.7.3\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx"

# Install latest WinGet from GitHub
Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/latest/download/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -OutFile "C:\WinGet\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
Add-AppxPackage "C:\WinGet\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"

# Fix Permissions
TAKEOWN /F "C:\Program Files\WindowsApps" /R /A /D Y
ICACLS "C:\Program Files\WindowsApps" /grant Administrators:F /T

# Add Environment Path
$ResolveWingetPath = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe"
if ($ResolveWingetPath) {
	$WingetPath = $ResolveWingetPath[-1].Path
}
$ENV:PATH += ";$WingetPath"
$SystemEnvPath = [System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::Machine)
$SystemEnvPath += ";$WingetPath;"
setx /M PATH "$SystemEnvPath"

With either method, you should have a working installation of WinGet on your server. Good luck! image

You need to use the latest version of .UI.Xaml for this to work now

Install Microsoft.UI.Xaml from NuGet

Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.8.6 -OutFile "C:\WinGet\Microsoft.UI.Xaml.2.8.6.zip"
Expand-Archive "C:\WinGet\Microsoft.UI.Xaml.2.8.6.zip" -DestinationPath "C:\WinGet\Microsoft.UI.Xaml.2.8.6"
Add-AppxPackage "C:\WinGet\Microsoft.UI.Xaml.2.8.6\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.8.appx"

@pariswells
Copy link

The Invoke-WebRequest looks like its triggering Cloudflares Bot Captcha now for me so I had to use @garzamSEA method

document.createElement('script');cpo.src =
'/cdn-cgi/challenge-platform/h/b/orchestrate/chl_page/v1?ray=87303dcae82cdfab';window._cf_chl_opt.cOgUHash =
location.hash === '' && location.href.indexOf('#') !== -1 ? '#' : location.hash;window._cf_chl_opt.cOgUQuery =
location.search === '' && location.href.slice(0, location.href.length -
window._cf_chl_opt.cOgUHash.length).indexOf('?') !== -1 ? '?' : location.search;if (window.history &&

@Karl-WE
Copy link

Karl-WE commented Apr 16, 2024

you might want to use start-bitstransfer instead of invoke-webrequest for better performance.
i am currently working on a "side quest" to enable a customer with Windows Server 2022 and winget and so far the best automation experience for this unsupported scenario is undoubtly this project:

https://github.com/asheroto/winget-install

@cquresphere
Copy link

My summary:

# Create download directory
if (-not $(Test-Path -Path "C:\WinGet")) {
    New-Item -Path "C:" -Name "WinGet" -ItemType Directory -Force
}

# Dependencies
## Microsoft.UI.Xaml
$UIXamlPath = "C:\WinGet\Microsoft.UI.Xaml.zip"
$downloadUIXamlUri = "https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.8.6"
(New-Object System.Net.WebClient).DownloadFile($downloadUIXamlUri, $UIXamlPath)
Unblock-File -Path $UIXamlPath
Expand-Archive -Path $UIXamlPath -DestinationPath "C:\WinGet\Microsoft.UI.Xaml.2.8.6"
$UIXamlAppxPath = "C:\WinGet\Microsoft.UI.Xaml.2.8.6\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.8.appx"
Add-AppxPackage -Path $UIXamlAppxPath

## Microsoft.VCLibs.x64.14.00.Desktop
$VCLibsPath = "C:\WinGet\Microsoft.VCLibs.x64.14.00.Desktop.appx"
$downloadVClibsUri = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"
(New-Object System.Net.WebClient).DownloadFile($downloadVClibsUri, $VCLibsPath)
Unblock-File -Path $VCLibsPath
Add-AppxPackage -Path $VCLibsPath

# Main Package
# Get the newest WinGet's version the download URL
$assetPattern = "*Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
$releasesUri = "https://api.github.com/repos/microsoft/winget-cli/releases/latest"
$asset = (Invoke-RestMethod -Uri $releasesUri -Method Get -ErrorAction stop).assets | Where-Object name -like $assetPattern
$downloadAssetUri = $asset.browser_download_url

# Download newest version of WinGet
$installerPath = "C:\WinGet\$($asset.name)"
(New-Object System.Net.WebClient).DownloadFile($downloadAssetUri, $installerPath)
Unblock-File -Path $installerPath

# Get the newest WinGet's license file download URL
$licensePattern = "*_License1.xml"
$license = (Invoke-RestMethod -Uri $releasesUri -Method Get -ErrorAction stop).assets | Where-Object name -like $licensePattern
$downloadLicenseUri = $license.browser_download_url

# Download license file
$licensePath = "C:\WinGet\$($license.name)"
(New-Object System.Net.WebClient).DownloadFile($downloadLicenseUri, $licensePath)
Unblock-File -Path $licensePath

# Install Package
Add-AppxPackage $installerPath

# Register Package with License
Try {
    Add-AppxProvisionedPackage -Online -PackagePath $installerPath -LicensePath $licensePath -Verbose -LogPath "C:\WinGet\DesktopAppInstaller_Install.log" #-DependencyPackagePath "$UIXamlAppxPath", "$VCLibsPath"
}
Catch {
    try {
        Add-AppxPackage -Path $installerPath -DependencyPath "$UIXamlAppxPath", "$VCLibsPath" -InstallAllResources
    }
    catch {
        Write-Output $_.Exception.Message
        Write-Output $Error[0]
    }
}

# Fix Permissions (S-1-5-32-544) Local Administrators Group (Language independent)
TAKEOWN /F "C:\Program Files\WindowsApps" /R /A /D Y
ICACLS "C:\Program Files\WindowsApps" /grant "*S-1-5-32-544:(F)" /T

# Add Environment Path
$ResolveWingetPath = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe"
if ($ResolveWingetPath) {
    $WingetPath = $ResolveWingetPath[-1].Path
}
$ENV:PATH += ";$WingetPath"
$SystemEnvPath = [System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::Machine)
$SystemEnvPath += ";$WingetPath;"

@Karl-WE
Copy link

Karl-WE commented May 22, 2024

not sure if all of this is really required. Can you check your approach with the one done with the linked GitHub @cquresphere

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