Using Winget with custom manifests (and auto updates) -Updated

With the release of Winget, we now have another app deployment method which works nicely with Intune.

At the moment building a custom Winget repo is not an easy task, but you can deploy using a custom manifest.

This blog post will cover how to use a custom manifest stored online to deploy an application and then using a runbook

The first thing to note is that a custom manifest has to be local, either on the machine itself, or on a network drive. Therefore we’ll grab the file and store it locally

The scripts I’m using are here: https://github.com/andrew-s-taylor/public/tree/main/Powershell%20Scripts/Winget

If you want to grab some manifests to test, the official repo is here: https://github.com/microsoft/winget-pkgs/tree/master/manifests

If we use Chrome as an example, most manifests will include 3 files, an installer, a locale and an app one, we need to get all 3

Update 12/11/21 – Thanks to the excellent work of Phil Jorgensen this can now run in the System Context which removes the admin rights requirement

Update 12/07/22 – Updated Winget.exe to replace old AppCLI one

First up, create a temp folder for the files to live in (I’ve opted for a random with the date):

$directory = $env:TEMP
#Create Temp location
$random = Get-Random -Maximum 1000 
$random = $random.ToString()
$date =get-date -format yyMMddmmss
$date = $date.ToString()
$path2 = $random + "-"  + $date
$path = $directory + "\" + $path2 + "\"
new-item -ItemType Directory -Path $path

Now specify the filenames to use:

##File Name
$templateFilePathinstaller = $path + "chrome.installer.yaml"
$templateFilePathlocale = $path + "chrome.locale.yaml"
$templateFilePathversion = $path + "chrome.yaml"

Then download them:

Invoke-WebRequest `
   -Uri "https://raw.githubusercontent.com/andrew-s-taylor/winget/main/manifests/g/Google/Chrome/92.0.4515.107/Google.Chrome.installer.yaml" `
   -OutFile $templateFilePathinstaller `
   -UseBasicParsing `
   -Headers @{"Cache-Control"="no-cache"}


   Invoke-WebRequest `
   -Uri "https://raw.githubusercontent.com/andrew-s-taylor/winget/main/manifests/g/Google/Chrome/92.0.4515.107/Google.Chrome.locale.en-US.yaml" `
   -OutFile $templateFilePathlocale `
   -UseBasicParsing `
   -Headers @{"Cache-Control"="no-cache"}
   
   

   Invoke-WebRequest `
   -Uri "https://raw.githubusercontent.com/andrew-s-taylor/winget/main/manifests/g/Google/Chrome/92.0.4515.107/Google.Chrome.yaml" `
   -OutFile $templateFilePathversion `
   -UseBasicParsing `
   -Headers @{"Cache-Control"="no-cache"}  

Finally, install them. Note, as it is a multi-file manifest, the path is the containing folder which is why it’s best to download each into their own folder. With a single file manifest, you can specify the manifest file, but I find it’s easier to use the folder to keep things the same

winget install --silent  --manifest $path

Or to run in the system context, create a variable for Winget to the AppInstallerCLI and use that

   $Winget = Get-ChildItem -Path (Join-Path -Path (Join-Path -Path $env:ProgramFiles -ChildPath "WindowsApps") -ChildPath "Microsoft.DesktopAppInstaller*_x64*\winget.exe")

   &$winget install --silent  --manifest $templateFilePath

The next thing we can do is use Proactive Remediations to keep your apps up to date:

Again, scripts are both on github:
https://github.com/andrew-s-taylor/public/blob/main/Powershell%20Scripts/winget-update-check.ps1

https://github.com/andrew-s-taylor/public/blob/main/Powershell%20Scripts/winget-upgrade.ps1

Winget at present isn’t the most powershell friendly so the check is simply counting the number of lines returned, anything 3 or less means no updates

Try {
    $updatecheck = winget upgrade
    If ($updatecheck.count -lt 3){
        Write-Output "Compliant"
        Exit 0
    } 
    Write-Warning "Not Compliant"
    Exit 1
} 
Catch {
    Write-Warning "Not Compliant"
    Exit 1
}

Update: Using the system context:

Try {
    $Winget = Get-ChildItem -Path (Join-Path -Path (Join-Path -Path $env:ProgramFiles -ChildPath "WindowsApps") -ChildPath "Microsoft.DesktopAppInstaller*_x64*\Winget.exe")

    $updatecheck = &$winget upgrade
    If ($updatecheck.count -lt 3){
        Write-Output "Compliant"
        Exit 0
    } 
    Write-Warning "Not Compliant"
    Exit 1
} 
Catch {
    Write-Warning "Not Compliant"
    Exit 1
}

Then upgrade:

winget upgrade --all --force --silent

Update: System Context:

$Winget = Get-ChildItem -Path (Join-Path -Path (Join-Path -Path $env:ProgramFiles -ChildPath "WindowsApps") -ChildPath "Microsoft.DesktopAppInstaller*_x64*\Winget.exe")

&$winget upgrade --all --force --silent

Note 1: I haven’t yet found a way to exclude an application from an -all upgrade which is slightly annoying because I’d prefer office updates to be handled via Intune directly. If anyone has found a way, let me know in the comments!

Note 2 – WHEN NOT USING NEW SYSTEM CONTEXT: When adding to Intune, make sure to select Run in the logged-in credentials or the scripts will fail:

That’s it, run at whatever schedule suits you and let the apps update themselves. Remember if it’s a custom app with a custom manifest, you’ll need to update the manifest file version too.

I’ll add a post on a custom repo once it’s a bit more user friendly

9 thoughts on “Using Winget with custom manifests (and auto updates) -Updated”

  1. Hello and thanks for the article. The problem is often not the first installation of the software but the update. how can i ensure that an update / upgrade is checked every day. at the end it says how to do it but where can i find the option where the script is run once a day?

    Reply
    • You have a few options for this one:
      1) You can push out a scheduled task using a powershell script within Intune
      2) Use Pro-Active Remediation with custom detection and remediation scripts to run the updates on a schedule (Check example & remediate example )

      If you are using the default repo, you can just use an “upgrade -all” command to do them all in one go.

      If you are using a custom manifest, you’ll have to update each one manually, that could be with multiple remediation scripts, or you could do one script with some logic to check which apps are installed and update accordingly (something I might put together in a future post)

      Hope this helps!

      Reply
  2. When running the upgrade all in system context script locally on a machine that has winget I get the error message

    The expression after ‘&’ in a pipeline element produced an object that was not valid. It must result in a command
    name, a script block, or a CommandInfo object.

    I may missing something?

    Reply
  3. I have a use case of using the mainfest option to create a list of apps, then after a replacement windows machine is obtained by the user, using a differences script to compare the two manifests, then create an install script for the missing app packages off these differences, however I am concerned with the community repo where our proprietary software could be exposed. Is there any security anyone has discovered surround access to this repo? or can a cert based access or other secure methods be used?

    Reply
    • There are private repos planned for Winget, you can set them up now, but it’s not an easy process.
      If you wanted to just use a custom manifest, you could store the manifest either in a private repo and download it passing a token, or in an Azure blob and download with the SAS string

      Then you could do the same with the executables, store in a repo and download and install with the SAS (I think it should work this way, I’ve never tried it myself though)

      Reply

Leave a Comment