It’s built into most OEM installs of Windows 10 and 11, and can often be installed. On server builds it’s touch and go.
To see if you have it, try winget list
from CMD or Powershell.
One good way to test it, is to install Microsoft .NET framework (SDK) 6, thus, from administrative Powershell:
winget install --id Microsoft.DotNet.Runtime.6 --silent --accept-source-agreements
I learned just now that if you add other seemingly valuable options to the one above, e.g., --scope machine
, at least while running as SYSTEM, it will fail citing package not found. So you’ll want to test carefully.
Here’s one proven just now for 7zip (there’s a “search” option in winget to get the ID):
winget install --exact --id 7zip.7zip --accept-package-agreements --silent --scope machine
Here’s one for Google Chrome, needs a bit of extra:
winget.exe install --exact --id Google.Chrome --silent --accept-package-agreements --accept-source-agreements --scope machine
And here’s a way to upgrade everything Winget can upgrade. There are some systems to not use this on, e.g., anything with some Autodesk products:
winget upgrade --all --include-unknown
If you do want to use it from the SYSTEM account, in scripting, it gets interesting. You’ll want to first run the below, and then winget will run as expected.
# Function to find the path to winget.exe function Find-WinGet-Path { # Get the WinGet path (for use when running in SYSTEM context). $WinGetPathToResolve = Join-Path -Path $ENV:ProgramFiles -ChildPath 'WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe' $ResolveWinGetPath = Resolve-Path -Path $WinGetPathToResolve | Sort-Object { [version]($_.Path -replace '^[^\d]+_((\d+\.)*\d+)_.*', '$1') } if ($ResolveWinGetPath) { # If we have multiple versions - use the latest. $WinGetPath = $ResolveWinGetPath[-1].Path } # Get the User-Context WinGet exe location. $WinGetExePath = Get-Command -Name winget.exe -CommandType Application -ErrorAction SilentlyContinue # Select the correct WinGet exe if (Test-Path -Path (Join-Path $WinGetPath 'winget.exe')) { # Running in SYSTEM-Context. $WinGet = Join-Path $WinGetPath 'winget.exe' } elseif ($WinGetExePath) { # Get User-Context if SYSTEM-Context not found. $WinGet = $WinGetExePath.Path } else { Write-Output 'WinGet not Found!' Stop-Transcript exit 1 } # Return WinGet path return ($WinGet -replace '\winget.exe','') } Function Add-PathVariable { param ( [string]$addPath ) if (Test-Path $addPath){ $regexAddPath = [regex]::Escape($addPath) $arrPath = $env:Path -split ';' | Where-Object {$_ -notMatch "^$regexAddPath\\?"} $env:Path = ($arrPath + $addPath) -join ';' } else { Throw "'$addPath' is not a valid path." } } Add-PathVariable (Find-Winget-Path)
Installing is even more interesting. There is a module in PSGallery called winget-install which works well in console, but not scripting. Below is one that works well in scripting, though it cannot be pasted interactively:
<# Winget-Install.ps1 Source for Some Functions: https://github.com/homotechsual/ninjaget/blob/dev/PS/NinjaGet.Functions.Installation.ps1 2023-11-22 - 0.0.2 - assembled and modified by David Szpunar - Initial version DOWNLOAD: https://discord.com/channels/676451788395642880/1232432179141808228 2024-04-23 - 0.1.0 - Simplified and Modified by GraphicHealer - Initial Version DESCRIPTION: Installs or updates WinGet.exe to the latest version. Intended to run as SYSTEM. Use '-Force' to ignore version check. ----- LICENSE: The MIT License (MIT) Original Copyright 2023 Mikey O'Toole, modified by David Szpunar, Simplified and Modified by GraphicHealer. #> # Add -Force switch to force install param( [switch]$Force = $false ) # Start Logging Start-Transcript -Path ".\winget-install.log" -IncludeInvocationHeader -Append -Force # Setup Error Codes $Script:ExitCode = 0 # Function to get OS Information function Get-OSInfo { try { # Get registry values $registryValues = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $releaseIdValue = $registryValues.ReleaseId $displayVersionValue = $registryValues.DisplayVersion $nameValue = $registryValues.ProductName $editionIdValue = $registryValues.EditionId # Strip out "Server" from the $editionIdValue if it exists $editionIdValue = $editionIdValue -replace 'Server', '' # Get OS details using Get-CimInstance because the registry key for Name is not always correct with Windows 11 $osDetails = Get-CimInstance -ClassName Win32_OperatingSystem $nameValue = $osDetails.Caption # Get architecture details of the OS (not the processor) # Get only the numbers $architecture = ($osDetails.OSArchitecture -replace '[^\d]').Trim() # If 32-bit or 64-bit replace with x32 and x64 if ($architecture -eq '32') { $architecture = 'x32' } elseif ($architecture -eq '64') { $architecture = 'x64' } # Get OS version details (as version object) $versionValue = [System.Environment]::OSVersion.Version # Determine product type # Reference: https://learn.microsoft.com/en-us/dotnet/api/microsoft.powershell.commands.producttype?view=powershellsdk-1.1.0 if ($osDetails.ProductType -eq 1) { $typeValue = 'Workstation' } elseif ($osDetails.ProductType -eq 2 -or $osDetails.ProductType -eq 3) { $typeValue = 'Server' } else { $typeValue = 'Unknown' } # Extract numerical value from Name $numericVersion = ($nameValue -replace '[^\d]').Trim() # Create and return custom object with the required properties $result = [PSCustomObject]@{ ReleaseId = $releaseIdValue DisplayVersion = $displayVersionValue Name = $nameValue Type = $typeValue NumericVersion = $numericVersion EditionId = $editionIdValue Version = $versionValue Architecture = $architecture } return $result } catch { Write-Error "Unable to get OS version details.`nError: $_" exit 1 } } # Function to Install WinGet (modified) function Install-WinGet { # Install VCPPRedist Requirements $Visual2019 = 'Microsoft Visual C++ 2015-2019 Redistributable*' $Visual2022 = 'Microsoft Visual C++ 2015-2022 Redistributable*' $VCPPInstalled = Get-Item @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') | Where-Object { $_.GetValue('DisplayName') -like $Visual2019 -or $_.GetValue('DisplayName') -like $Visual2022 } if ([System.Environment]::Is64BitOperatingSystem) { $OSArch = 'x64' } else { $OSArch = 'x86' } if (!($VCPPInstalled)) { Write-Output 'VCPPRedist Requirements Not Installed. Installing...' $VCPPRedistURL = ('https://aka.ms/vs/17/release/vc_redist.{0}.exe' -f $OSArch) $VCPPRedistFileName = [Uri]$VCPPRedistURL | Select-Object -ExpandProperty Segments | Select-Object -Last 1 $WebClient = New-Object System.Net.WebClient $VCPPRedistDownloadPath = "$ENV:SystemRoot\Temp\VCPRedist" if (!(Test-Path -Path $VCPPRedistDownloadPath)) { $null = New-Item -Path $VCPPRedistDownloadPath -ItemType Directory -Force } $VCPPRedistDownloadFile = "$VCPPRedistDownloadPath\$VCPPRedistFileName" $WebClient.DownloadFile($VCPPRedistURL, $VCPPRedistDownloadFile) try { Start-Process -FilePath $VCPPRedistDownloadFile -ArgumentList '/quiet', '/norestart' -Wait -ErrorAction Stop | Out-Null Write-Output 'VCPPRedist Requirements Installed.' } catch { Write-Output 'Failed to install VCPPRedist Requirements!' $Script:ExitCode = 1 return } finally { Remove-Item -Path $VCPPRedistDownloadPath -Recurse -Force -ErrorAction SilentlyContinue } } else { Write-Output 'VCPPRedist Requirements Installed.' } # Find Latest Download $LatestRelease = (Invoke-RestMethod -Uri 'https://api.github.com/repos/microsoft/winget-cli/releases/latest' -Method Get) $LatestAsset = ($LatestRelease).assets | Where-Object { $_.name.EndsWith('.msixbundle') } [version]$LatestVersion = $LatestRelease.tag_name.TrimStart('v') if ((Find-WinGet)) { [version]$CurrentVersion = (& (Find-WinGet) '-v').TrimStart('v') } else { [version]$CurrentVersion = 0 } # Check if WinGet is Updated if (!($CurrentVersion -lt $LatestVersion) -and !$Force) { # Exit Write-Output 'WinGet is up to date. Exiting.' return } # Installing WinGet Write-Output 'WinGet not installed or out of date. Installing/updating...' # Download WinGet to Temp $WebClient = New-Object System.Net.WebClient $WinGetDownloadPath = Join-Path -Path "$ENV:SystemRoot\Temp" -ChildPath $LatestAsset.name Write-Output "Downloading WinGet to $WinGetDownloadPath..." $WebClient.DownloadFile($LatestAsset.browser_download_url, $WinGetDownloadPath) # Download WinGet Source to Temp $WinGetSourceDownloadPath = Join-Path -Path "$ENV:SystemRoot\Temp" -ChildPath 'source.msix' Write-Output "Downloading WinGet to $WinGetSourceDownloadPath..." Invoke-WebRequest -Uri 'https://cdn.winget.microsoft.com/cache/source.msix' -OutFile $WinGetSourceDownloadPath $WinArch = (Get-OSInfo).Architecture # Download VCLibs $VCLibs_Path = Join-Path -Path "$ENV:SystemRoot\Temp" -ChildPath "Microsoft.VCLibs.${WinArch}.14.00.Desktop.appx" $VCLibs_Url = "https://aka.ms/Microsoft.VCLibs.${WinArch}.14.00.Desktop.appx" Write-Output 'Downloading VCLibs...' Write-Debug "Downloading VCLibs from $VCLibs_Url to $VCLibs_Path`n`n" Invoke-WebRequest -Uri $VCLibs_Url -OutFile $VCLibs_Path # Download UI.Xaml $UIXaml_Path = Join-Path -Path "$ENV:SystemRoot\Temp" -ChildPath "Microsoft.UI.Xaml.2.8.${WinArch}.appx" $UIXaml_Url = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.${WinArch}.appx" Write-Output 'Downloading UI.Xaml...' Write-Debug "Downloading UI.Xaml from $UIXaml_Url to $UIXaml_Path`n" Invoke-WebRequest -Uri $UIXaml_Url -OutFile $UIXaml_Path # Install Downloaded WinGet try { Write-Output 'Installing WinGet...' Add-AppxProvisionedPackage -Online -PackagePath $WinGetDownloadPath -DependencyPackagePath $UIXaml_Path, $VCLibs_Path -SkipLicense -ErrorAction Stop Add-AppxProvisionedPackage -Online -PackagePath $WinGetSourceDownloadPath -SkipLicense -ErrorAction Stop Write-Output 'WinGet installed.' } catch { Write-Output 'Failed to install WinGet!' $Script:ExitCode = 1 } finally { Remove-Item -Path $WinGetDownloadPath -Force -ErrorAction SilentlyContinue } return } # Function to Find the WinGet exe (Modified) function Find-WinGet { # Get the WinGet path (for use when running in SYSTEM context). $WinGetPathToResolve = Join-Path -Path $ENV:ProgramFiles -ChildPath 'WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe' $ResolveWinGetPath = Resolve-Path -Path $WinGetPathToResolve | Sort-Object { [version]($_.Path -replace '^[^\d]+_((\d+\.)*\d+)_.*', '$1') } if ($ResolveWinGetPath) { # If we have multiple versions - use the latest. $WinGetPath = $ResolveWinGetPath[-1].Path } # Get the User-Context WinGet exe location. $WinGetExePath = Get-Command -Name winget.exe -CommandType Application -ErrorAction SilentlyContinue # Select the correct WinGet exe if (Test-Path -Path (Join-Path $WinGetPath 'winget.exe')) { # Running in SYSTEM-Context. $WinGet = Join-Path $WinGetPath 'winget.exe' } elseif ($WinGetExePath) { # Get User-Context if SYSTEM-Context not found. $WinGet = $WinGetExePath.Path } else { Write-Output 'WinGet not Found!' Stop-Transcript exit 1 } # Return WinGet Location return $WinGet } # Test internet connection $MaxNet = 15 Write-Output 'Checking Internet Connection...' for ($i = 0; ($i -lt $MaxNet) -and !$net; $i++) { $net = Test-Connection 1.1.1.1 -Count 1 -Delay 1 -Quiet } if (!$net) { Write-Output 'No Internet Detected. Cancelling Installation.' Stop-Transcript exit 1 } Write-Output 'Internet Detected, Continuing Installation.' # Test OS Compatibility $osVersion = Get-OSInfo # If it's a workstation, make sure it is Windows 10+ if ($osVersion.Type -eq 'Workstation' -and $osVersion.NumericVersion -lt 10) { Write-Error 'winget requires Windows 10 or later on workstations. Your version of Windows is not supported.' Stop-Transcript exit 0 } # If it's a workstation with Windows 10, make sure it's version 1809 or greater if ($osVersion.Type -eq 'Workstation' -and $osVersion.NumericVersion -eq 10 -and $osVersion.ReleaseId -lt 1809) { Write-Error 'winget requires Windows 10 version 1809 or later on workstations. Please update Windows to a compatible version.' Stop-Transcript exit 0 } # Run the Install/Update Install-WinGet & (Find-WinGet) 'list' '--accept-source-agreements' Stop-Transcript exit $Script:ExitCode