App installations can be such a chore, even when using an MDM like Intune, you have to package, configure the AAD groups, detection methods and everything else. Great for the optional apps, but what about the core applications which everyone needs? Surely there is a quicker way!
Well, after reading the Winget updates in Rudy’s blog here it got me thinking, could I use my favourite Intune tool and automate this further.
May I introduce you to my latest scripts to deploy (and remove) applications via Proactive Remediations from a central text file
As usual, the scripts can all be found on my github here
To start off, we need a couple of basic text files to include a list of applications to install and uninstall. To find the app names, you can either use “winget search appname” or have a browse of this website
The important thing to note is the version number at the top, this is what we’ll use in the detection script to see if there have been any updates:
Version 1.0
WinMerge.WinMerge
SumatraPDF.SumatraPDF
I’m just storing my two text files in Github, but anywhere will work as long as it is accessible via the internet (and it’s not like it’s anything secret)
Detection Script
Now for the detection scripts, the install and uninstall are basically the same so I’m just concentrating on the install ones here:
First download the file
$installuri = "https://github.com/andrew-s-taylor/winget/raw/main/install-apps.txt"
##Create a folder to store the lists
$AppList = "C:\ProgramData\AppList"
If (Test-Path $AppList) {
Write-Output "$AppList exists. Skipping."
}
Else {
Write-Output "The folder '$AppList' doesn't exist. This folder will be used for storing logs created after the script runs. Creating now."
Start-Sleep 1
New-Item -Path "$AppList" -ItemType Directory
Write-Output "The folder $AppList was successfully created."
}
$templateFilePath = "C:\ProgramData\AppList\install-apps.txt"
##Download the list
Invoke-WebRequest `
-Uri $installuri `
-OutFile $templateFilePath `
-UseBasicParsing `
-Headers @{"Cache-Control"="no-cache"}
Then, at the end of the remediation, we’re renaming the file to include “old” to keep things neat. So let’s see if that exists, or if this is the first run:
$oldpath = "C:\ProgramData\AppList\install-apps-old.txt"
If (Test-Path $oldpath) {
If it doesn’t, we need to crash out to trigger remediation (and delete the file to stop any issues in the future:
remove-item -path $templateFilePath -force
Write-Warning "Not Compliant"
Exit 1
If the old file exists, it’s been run before so we need to compare the version numbers and see if they differ:
$newcontent = get-content $templateFilePath | select-object -first 1
$oldcontent = get-content $oldpath | select-object -first 1
If ($newcontent -eq $oldcontent) {
remove-item -path $templateFilePath -force
Write-Output "Compliant"
exit 0
}
else {
remove-item -path $templateFilePath -force
Write-Warning "Not Compliant"
Exit 1
}
To put it all together:
$installuri = "https://github.com/andrew-s-taylor/winget/raw/main/install-apps.txt"
##Create a folder to store the lists
$AppList = "C:\ProgramData\AppList"
If (Test-Path $AppList) {
Write-Output "$AppList exists. Skipping."
}
Else {
Write-Output "The folder '$AppList' doesn't exist. This folder will be used for storing logs created after the script runs. Creating now."
Start-Sleep 1
New-Item -Path "$AppList" -ItemType Directory
Write-Output "The folder $AppList was successfully created."
}
$templateFilePath = "C:\ProgramData\AppList\install-apps.txt"
##Download the list
Invoke-WebRequest `
-Uri $installuri `
-OutFile $templateFilePath `
-UseBasicParsing `
-Headers @{"Cache-Control"="no-cache"}
$oldpath = "C:\ProgramData\AppList\install-apps-old.txt"
If (Test-Path $oldpath) {
$newcontent = get-content $templateFilePath | select-object -first 1
$oldcontent = get-content $oldpath | select-object -first 1
If ($newcontent -eq $oldcontent) {
remove-item -path $templateFilePath -force
Write-Output "Compliant"
exit 0
}
else {
remove-item -path $templateFilePath -force
Write-Warning "Not Compliant"
Exit 1
}
}
else {
remove-item -path $templateFilePath -force
Write-Warning "Not Compliant"
Exit 1
}
Remediation Script
Now, if it is a first run, or the central store has been updated, we now need to remediate and install the apps on the list (it will ignore any already present and updated)
Again, we need to download the latest list:
$installuri = "https://github.com/andrew-s-taylor/winget/raw/main/install-apps.txt"
##Create a folder to store the lists
$AppList = "C:\ProgramData\AppList"
If (Test-Path $AppList) {
Write-Output "$AppList exists. Skipping."
}
Else {
Write-Output "The folder '$AppList' doesn't exist. This folder will be used for storing logs created after the script runs. Creating now."
Start-Sleep 1
New-Item -Path "$AppList" -ItemType Directory
Write-Output "The folder $AppList was successfully created."
}
$templateFilePath = "C:\ProgramData\AppList\install-apps.txt"
##Download the list
Invoke-WebRequest `
-Uri $installuri `
-OutFile $templateFilePath `
-UseBasicParsing `
-Headers @{"Cache-Control"="no-cache"}
Now we need to locate the Winget executable:
##Find Winget Path
$ResolveWingetPath = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe"
if ($ResolveWingetPath){
$WingetPath = $ResolveWingetPath[-1].Path
}
$config
##Navigate to the Winget Path
cd $wingetpath
Now loop through and install each app (ignore the first line with the version):
##Loop through app list
$apps = get-content $templateFilePath | select-object -skip 1
##Install each app
foreach ($app in $apps) {
write-host "Installing $app"
.\winget.exe install --exact --id $app --silent --accept-package-agreements --accept-source-agreements
}
And finally, rename the file to the old name (deleting any already there) to free up the file for the next run:
##Delete the .old file to replace it with the new one
$oldpath = "C:\ProgramData\AppList\install-apps-old.txt"
If (Test-Path $oldpath) {
remove-item $oldpath -Force
}
##Rename new to old
rename-item $templateFilePath $oldpath
The full code:
$installuri = "https://github.com/andrew-s-taylor/winget/raw/main/install-apps.txt"
##Create a folder to store the lists
$AppList = "C:\ProgramData\AppList"
If (Test-Path $AppList) {
Write-Output "$AppList exists. Skipping."
}
Else {
Write-Output "The folder '$AppList' doesn't exist. This folder will be used for storing logs created after the script runs. Creating now."
Start-Sleep 1
New-Item -Path "$AppList" -ItemType Directory
Write-Output "The folder $AppList was successfully created."
}
$templateFilePath = "C:\ProgramData\AppList\install-apps.txt"
##Download the list
Invoke-WebRequest `
-Uri $installuri `
-OutFile $templateFilePath `
-UseBasicParsing `
-Headers @{"Cache-Control"="no-cache"}
##Find Winget Path
$ResolveWingetPath = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe"
if ($ResolveWingetPath){
$WingetPath = $ResolveWingetPath[-1].Path
}
$config
##Navigate to the Winget Path
cd $wingetpath
##Loop through app list
$apps = get-content $templateFilePath | select-object -skip 1
##Install each app
foreach ($app in $apps) {
write-host "Installing $app"
.\winget.exe install --exact --id $app --silent --accept-package-agreements --accept-source-agreements
}
##Delete the .old file to replace it with the new one
$oldpath = "C:\ProgramData\AppList\install-apps-old.txt"
If (Test-Path $oldpath) {
remove-item $oldpath -Force
}
##Rename new to old
rename-item $templateFilePath $oldpath
Deployment
One thing to note, make sure the deployment is running in the 64-bit context or it will struggle with the Winget.exe location as we are hardcoding the x64 version:
Enjoy!
Hi Andrew,
Regarding WinGet app deployments, have you encountered any issues with regards to Autopilot OOBE provisioning on Windows 10 & 11?
We are bumping into a timeout/app not detected error for OOBE when deploying Win32 WinGet scripts.
Just wanted to know how youre going about deploying WinGet packages on your side
I normally just deploy Company Portal and Winget during OOBE and then let the rest pick up after login.
If it’s Winget which isn’t being detected, have a look at my application here, it includes detection rules etc.
If it’s a particular application, I’m happy to have a look at it
Brilliant, was looking for an easy way to manage our Winget apps.
Will use this as a reference. Tx
Glad you have found it useful!