Background

Back in 2022, after a lot of testing, I developed a way to install any machine wide installer in SYSTEM context using WinGet . For the most part this worked great, however I noticed that if I deployed an application targeted to a device group, the application would not reliable install during autopilot / OOBE. This is because the DesktopAppInstaller UWP appxpacakage needs to be installed under a user account before WinGet is available to use. At the time, I did include a function to download a copy of a zipped copy of the DesktopAppInstaller program folder from a blob storage account. That account no longer exists and I feel this is not the best solution.

This is an update to that effort to make the process 100% reliable during autopilot before any user account is created and not rely on downloading any WinGet files.

The Solution

After some testing and brainstorming, I had an epiphany. MSIXBundles are basically just compressed archives containing the file structure of the UWP app so what if we could just download the DesktopAppInstaller msixbundle and extract the contents to a known location, then directly call the WinGet executable from system context? To accoplish this, I made the following function to download the Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle then use the 7zip CLI utility program to extract the contents to %ProgramData%\Microsoft.DesktopAppInstaller. From there, set a script scoped variable $WinGet for winget.exe.

Download and extract winget

function Download-Winget {
	<#
	.SYNOPSIS
	Download Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle and extract contents with 7zip cli to %ProgramData%
	#>
	$ProgressPreference = 'SilentlyContinue'
	$7zipFolder = "${env:WinDir}\Temp\7zip"
	try {
		Write-Log "Downloading WinGet..."
		# Create staging folder
		New-Item -ItemType Directory -Path "${env:WinDir}\Temp\WinGet-Stage" -Force
		# Download Desktop App Installer msixbundle
		Invoke-WebRequest -UseBasicParsing -Uri https://aka.ms/getwinget -OutFile "${env:WinDir}\Temp\WinGet-Stage\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
	}
	catch {
		Write-Log "Failed to download WinGet!"
		Write-Log $_.Exception.Message
		return
	}
	try {
		Write-Log "Downloading 7zip CLI executable..."
		# Create temp 7zip CLI folder
		New-Item -ItemType Directory -Path $7zipFolder -Force
		Invoke-WebRequest -UseBasicParsing -Uri https://www.7-zip.org/a/7zr.exe -OutFile "$7zipFolder\7zr.exe"
		Invoke-WebRequest -UseBasicParsing -Uri https://www.7-zip.org/a/7z2408-extra.7z -OutFile "$7zipFolder\7zr-extra.7z"
		Write-Log "Extracting 7zip CLI executable to ${7zipFolder}..."
		& "$7zipFolder\7zr.exe" x "$7zipFolder\7zr-extra.7z" -o"$7zipFolder" -y
	}
	catch {
		Write-Log "Failed to download 7zip CLI executable!"
		Write-Log $_.Exception.Message
		return
	}
	try {
	# Create Folder for DesktopAppInstaller inside %ProgramData%
	New-Item -ItemType Directory -Path "${env:ProgramData}\Microsoft.DesktopAppInstaller" -Force
	Write-Log "Extracting WinGet..."
	& "$7zipFolder\7za.exe" x "${env:WinDir}\Temp\WinGet-Stage\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" -o"${env:WinDir}\Temp\WinGet-Stage" -y
	& "$7zipFolder\7za.exe" x "${env:WinDir}\Temp\WinGet-Stage\AppInstaller_x64.msix" -o"${env:ProgramData}\Microsoft.DesktopAppInstaller" -y
	}
	catch {
		Write-Log "Failed to extract WinGet!"
		Write-Log $_.Exception.Message
		return
	}
	if (-Not (Test-Path "${env:ProgramData}\Microsoft.DesktopAppInstaller\WinGet.exe")){
		Write-Log "Failed to extract WinGet!"
		exit 1
	}
	$Script:WinGet = "${env:ProgramData}\Microsoft.DesktopAppInstaller\WinGet.exe"
}

The only other requirement is making sure Visual C++ x64 2015-2022 redistributable is installed as winget has dependancies for it.

Download and Install Visual C++ 2015-2022 x64

function Install-VisualC {
	try {
		$downloadurl = 'https://aka.ms/vs/17/release/vc_redist.x64.exe'
		$WebClient = New-Object System.Net.WebClient
		$WebClient.DownloadFile($downloadurl, "$env:Temp\vc_redist.x64.exe")
		$WebClient.Dispose()
	}
	catch {
		Write-Log "Failed to download Visual C++!"
		Write-Log $_.Exception.Message
	}
	# Check if another installation is in progress, then wait for it to complete
	$MSIExecCheck = Get-Process | Where-Object {$_.processname -eq 'msiexec'}
	if ($Null -ne $MSIExecCheck){
		Write-Log "another msi installation is in progress. Waiting for process to complete..."
		Wait-Process msiexec
		Write-Log "Continuing installation..."
	}
	try {
		$Install = start-process "$env:temp\vc_redist.x64.exe" -argumentlist "/q /norestart" -Wait -PassThru
		Write-Log "Installation completed with exit code $($Install.ExitCode)"
		return $Install.ExitCode
	}
	catch {
		Write-Log $_.Exception.Message
	}
	try {
		remove-item "$env:Temp\vc_redist.x64.exe"
	}
	catch {
		Write-Log "Failed to remove vc_redist.x64.exe after installation"
	}
}

Once those tasks are done, we can simply execute Winget.exe with the appropriate arguments

function WingetInstallPackage {
param (
	$PackageID,
	$PackageName,
	$AdditionalInstallArgs
)
	# Check if another msi install is in progress and wait
	
	$MSIExecCheck = Get-Process | Where-Object {$_.processname -eq 'msiexec'}
	if ($Null -ne $MSIExecCheck){
		Write-Log "another msi installation is in progress. Waiting for process to complete..."
		Wait-Process msiexec
		Write-Log "Continuing installation..."
	}
	if ($PackageID){
		& $Winget install --id $PackageID --source Winget --silent $AdditionalInstallArgs --accept-package-agreements --accept-source-agreements 
	}
	elseif ($PackageName){
		& $Winget install --name $PackageName --source Winget --silent $AdditionalInstallArgs --accept-package-agreements --accept-source-agreements
	}
}

switch ($Mode){
		'Install'{
				if ($PackageID){
				Write-Log -message "executing $Mode on $PackageID"
				$Install = WingetInstallPackage -PackageID $PackageID -AdditionalArgs $AdditionalInstallArgs	
				}
				elseif ($PackageName){
				Write-Log -message "executing $Mode on $PackageName"
				$Install = WingeInstallPackage -PackageName $PackageName -AdditionalArgs $AdditionalInstallArgs		
				}
				Write-Log $Install
			}
		'Uninstall'{
			if ($PackageID){
				Write-Log -message "executing $Mode on $PackageID"
				$Uninstall = & $Winget uninstall --id $PackageID --source WinGet --silent
			}
			elseif ($PackageName){
				Write-Log -message "executing $Mode on $PackageName"
				$Uninstall = & $Winget uninstall --name $PackageName --source WinGet --silent

			}
			Write-Log $Uninstall
		}

I added a couple other minor improvements as well. The script now supports uninstllation and logging has been moved to the IntuneManagementExtension log folder. I have done extensive testing and this has been rock solid for installing system-wide software through Intune both inside and outside OOBE / Autopilot. Try it out for yourself and let me know if you have any questions!

Check out the full script: https://github.com/djust270/Intune-Scripts/blob/master/Winget-InstallPackage.ps1 Or you can download the intunewin package: https://github.com/djust270/Intune-Scripts/blob/master/Winget-InstallPackage.intunewin