With the Windows Package Manager (winget) applications can be installed very easily on a Windows device. Winget offers a large repository that you can use, like Chocolatey, for example. The big advantage of winget is that this client is already integrated into the system in the new Windows versions and will be more integrated into the Endpoint Manager (Intune) in the future.
How to winget applications as win32 app (as a system) can be distributed with Intune, I will show you in this article.
If you want to create the apps completely automatically and upload them to your environment, you can do that with my tool dem "Intune Win32 Deployer".
Table of Contents
- What is winget or the Windows Package Manager?
- Distribute Windows Package Manager (App Installer) with Intune
- Deploy winget packages with Intune (win32)
- Explanation of the template (install.ps1)
- Update of winget apps
What is winget or the Windows Package Manager?
Like Chocolatey, the Windows Package Manager is a package repository or manager that can be used freely. With this you can easily install and update programs via command line.
So far I have always used Chocolatey for this, mainly because winget could only be used in the user context. But this has changed and prompted me to gradually adapt my installations.
The Windows Package Manager will continue to play an increasingly important role in the future, so it is scheduled to replace the current Windows Store for Business at the beginning of 2023.
You can find a great contribution to the replacement of the Windows Store for Business on Rudy Ooms' website: Microsoft Store for Business Deprecated! What to do now? (call4cloud.nl)
Distribute Windows Package Manager (App Installer) with Intune
The app installer is actually included in the current versions of Windows, but the command winget or winget.exe is not yet available. This can lead to problems, especially with rollouts with autopilot.
That's why I distribute the App Installer as a separate Win32 package, which I can then store as a dependency for the individual applications.
To install it, I download the current version of the app installer from "https://aka.ms/getwinget", put it in a temporary directory and install it from there. Finally, I delete the directory again.
I package the whole thing together with an uninstallation routine and a validation file (check.ps1) in an intunewin / win32 app and upload it to Intune.
To upload, in Endpoint Manager navigate to "Apps > Windows" +Add (Windows app (win32)).
Here you upload the file "install.intunewin" from my template.
You also give the app a name, a description and fill in the publisher:
On the next page, add the following two commands:
install command | %SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -command .\install.ps1 |
uninstall command | %SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -command .\uninstall.ps1 |
In the detection rule you upload the script "check.ps1".
You do not have to define dependencies and a supersedence rule.
Finally, the app only has to be assigned.
Deploy winget packages with Intune (win32)
To distribute an application from the winget repository, I have put together a template that you can use. In this template, only the correct ID must be entered before packaging and uploading.
Find winget ID
You can find the ID either via CMD or PowerShell and the command "winget search" or even easier online at winget.run.
You can find the Docs article with further details on searching via "command line" here: searchCommand | Microsoft Docs
winget Vorlage anpassen und intunewin erstellen
Once you have found the ID, all you have to do is insert it into my template.
You have to do this in all three files:
- install.ps1
- uninstall.ps1
- check.ps1
In all files you will find the note "WINGET PROGRAMID", which you replace with the desired ID.
Once you have adjusted the three files, you convert them into an intunewin.
I have described how to do this here: Create Win32 App / .intunewin
Detection rule
You have two options for the detection rule, either you use a script (more flexible) or a static detection rule based on a folder.
If you decide to go via script, until you're already done here. Because you have already prepared this in the point above by adapting the "check.ps1" file.
If you prefer to use a rule via folder detection (manually configured detection rule), you must select the appropriate folder with the correct version for each package. The easiest way to do this is to install the program on you or a test device and look for the folder under the following path:
C:\Program Files\WindowsApps |
If you do not have access to the folder, here are instructions on how to grant it to you as an admin: How to Access WindowsApps folder in Windows 10/8 - wintips.org - Windows Tips & How-tos
Once you have found the appropriate folder, copy the path. You will only need this again in the next step.
Build Win32 app in Intune
If you have adapted the template, created the intunewin and decided on a recognition rule, you can use the app in the Endpoint Manager under "Apps > Windows apps" create.
On the first page you store the app name, the description and a publisher.
On the second page, add the following two commands:
install command | %SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -command .\install.ps1 |
uninstall command | %SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -command .\uninstall.ps1 |
I also set the "Device restart behavior" to "No specific action" so that the installation does not force a restart if possible.
You don't have to define anything special in the requirements. (e.g. x64 and 2004)
In the detection rule, you now either upload the script or store the copied path.
In the dependencies, select the "Windows Package Manager":
You don't have to define a supersedence rule and only assign the app at the end.
And that's it!
Explanation of the template (install.ps1)
In the first 6 lines the optional parameter "$param" is declared. This is only required if you have to add something to the installation (e.g. a license key).
You can then simply append these parameters to the installation command in the Win32 app in Intune.
In the line 8 the winget ID is declared, this is the ID according to the repository.
$ProgramName = "WINGETPROGRAMID"
Code language: PowerShell (powershell)
Then the path for a local log is specified in lines 9 and 10 and the transcript / log is started:
$Path_local = "$Env:Programfiles\_MEM"
Start-Transcript -Path "$Path_local\Log\$ProgramName-install.log" -Force
Code language: PowerShell (powershell)
Since the winget command is not directly known in the system context, we must first navigate to the correct directory and resolve the full path to "winget.exe".
I do that in the Line 13 to 16.
The EXE is then created using the variable $wingetexe
called.
$winget_exe = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe\winget.exe"
if ($winget_exe.count -gt 1){
$winget_exe = $winget_exe[-1].Path
}
Code language: PowerShell (powershell)
Afterward (line 19) comes the main process in which the program is installed via winget with the normal installation command and the parameters for the "silent" installation.
& $winget_exe install --exact --id $ProgramName --silent --accept-package-agreements --accept-source-agreements --scope=machine $param
Code language: PowerShell (powershell)
Finally, the transcript is ended again and the process is completed.
Stop-Transcript
Code language: PowerShell (powershell)
Update of winget apps
The quickest way to update winget applications is with the following two commands:
(The first for a specific application, the second for all.)
# Upgrade specific app (change Logitech.Options with the desired winget id)
winget upgrade Logitech.Options
# Upgrade all apps
winget upgrade --query --silent --force --accept-package-agreements --accept-source-agreements --all
Code language: PowerShell (powershell)
You can also distribute the "Upgrade all" command to your devices as a scheduled task to always keep all apps up-to-date. I have prepared a template (intunewin) for you here:
To distribute this package you can use the same parameters as described above for the "Windows Package Manager".
A disadvantage of this variant can be that all apps published on winget are included in the updater process. That is not always desired.
The tool offers a solution for this: Winget AutoUpdate: WAU daily updates apps as system and notify connected users. (Allowlist and Blocklist support) (github.com)
I wanted to post a quick thanks for the write-up, and to share a tweak I made. Some winget apps fail to install in machine scope (e.g. SanfordLP.DYMOConnect), I adjusted your scripts so they will try machine scope first and failover to user scope if the install fails. I have tested it very lightly, scripting isn't my forte...
Install: %SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -command .\install.ps1 -ProgramName "WINGETPROGRAMID"
Uninstall: %SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -command .\uninstall.ps1 -ProgramName "WINGETPROGRAMID"
Script:
## Script to install package from Winget in Machine Scope. If unavailable, will fallback to User Scope
## Package is case-sensitive
## Set install parameters if required
Param
(
[parameter(Mandatory=$false)]
[String[]]
$param,
[parameter(Mandatory=$true, HelpMessage="Specify the Winget App ID, e.g. Notepad++.Notepad++")]
[ValidateNotNullOrEmpty()]
[string]$ProgramName
)
## Set log path
$Path_local = "$Env:Programfiles\_MEM"
Start-Transcript -Path "$Path_local\Log\$ProgramName-install.log" -Force -Append
# Resolve winget_exe
$winget_exe = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe\winget.exe"
if ($winget_exe.count -gt 1){
$winget_exe = $winget_exe[-1].Path
}
if (!$winget_exe){Write-Error "Winget not installed"}
## Attempt install using Machine Scope
& $winget_exe install --exact --id $ProgramName --silent --accept-package-agreements --accept-source-agreements --scope=machine $param
## Check if app is installed, if not then attempt install using User Scope
$wingetPrg_Existing = & $winget_exe list --id $ProgramName --exact --accept-source-agreements
if ($wingetPrg_Existing -like "*$ProgramName*"){
Write-Host "Found it!"
}else{
& $winget_exe install --exact --id $ProgramName --silent --accept-package-agreements --accept-source-agreements $param
}
Stop-Transcript
Great Stuff! We are implementing all of this right now! Thanks for the help!
Did you get it to work? I can't seem to make it install apps under system context to install to lab machines I feel like I am missing something.
sometimes the behavior depends on the app
In the detection script, I do not see the script having any exit codes, so how is the detection script working?
With the feedback
Write-Host "Found it!"
it will work as well. If you lie you could also addexit 0
.Works like a charm, awsome and thanks
Thanks for this! It's just what I need! But I am running into this issue when deploying winget.exe:
"AppX Deployment operation failed...because the Local System account is not allowed to perform this operation"
But if I run the install script manually from powershell, it succeeds.
Any ideas on what may be wrong?
Some applications only work in user context
I just realised this sounds like the issue I posted about yesterday. The issue isn't the app being installed by winget, it is an issue deploying App Installer and its prerequisites as system from Intune. Maybe something has changed? As a test, I deployed as User and it installed ok but my local user was an admin so I need to test on a standard account and work on the detection script.
I had strange behaviors with winget this week as well, I hope thing get sortet soon. Looks like it has something to do with the latest windows updates.
I'm having trouble installing Windows Package Manager. Clean Windows 10 22H2 install, I upload the intunewin from the github, install the package using the command in the blog and using the detection script. In the logs I have errors:
Add-AppxPackage : Deployment failed with HRESULT: 0x80073CF9, Install failed. Please contact your software vendor.
(Exception from HRESULT: 0x80073CF9)
Deployment Add operation rejected on package Microsoft.VCLibs.140.00.UWPDesktop_14.0.30704.0_x64__8wekyb3d8bbwe from:
Microsoft.VCLibs.x64.14.00.Desktop.appx install request because the Local System account is not allowed to perform this
operation.
This same error is repeated for each component. Any ideas how to work around this? Thanks
Adding to my previous comment, I found it necessary to add the context check to the install script as well, moving the log folder to the user's profile folder and hard-setting the $winget_exe variable for user installs.
## Package is case-sensitive
## Set install parameters if required
Param
(
[parameter(Mandatory=$false)]
[String[]]
$param,
[parameter(Mandatory=$true, HelpMessage="Specify the Winget App ID, e.g. Notepad++.Notepad++")]
[ValidateNotNullOrEmpty()]
[string]$ProgramName
)
# Check if running with elevated privileges (system context)
$isSystemContext = ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem)
if ($isSystemContext) {
# SYSTEM Install
$winget_exe = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe\winget.exe"
if ($winget_exe.count -gt 1){
$winget_exe = $winget_exe[-1].Path
## Set log path
$Path_local = "$Env:Programfiles\_MEM"
Start-Transcript -Path "$Path_local\Log\$ProgramName-install.log" -Force -Append
if (!$winget_exe){Write-Error "Winget not installed"}
## Attempt install using Machine Scope
& $winget_exe install --exact --id $ProgramName --silent --accept-package-agreements --accept-source-agreements -scope:Machine $param
Stop-Transcript
Exit $LASTEXITCODE
}
} else {
# USER Install
$winget_exe = "winget"
## Set log path
$Path_local = "$Env:USERPROFILE\_MEM"
Start-Transcript -Path "$Path_local\Log\$ProgramName-install.log" -Force -Append
## Attempt install using User Scope
& $winget_exe install --exact --id $ProgramName --silent --accept-package-agreements --accept-source-agreements $param
Stop-Transcript
Exit $LASTEXITCODE
}