- Creating Azure AD App Registration
- Creating GitHub Repo and Token
- The PowerShell Script
- Creating the DevOps Pipeline
- The YAML File
- Other potential improvements
Application packaging can be one of the most time and resource intensive parts of managing a modern desktop estate, especially if you look after multiple customers.
Winget gets us part of the way there (see my previous scripts here and here), but they all use the community repository which could be a security concern for some and also restricts which apps you can use.
After discovering you can use custom manifests to deploy apps (see my post here), I have combined that into a DevOps Pipeline which will automatically create your IntuneWin package, AAD groups and assign it as soon as it detects a new YAML file in the Github Repository.
Obviously you want your repo to be private as well so we’ll be using a token to authenticate.
For those of you with mutliple tenants, fear not, it can be cross-tenant!
Creating Azure App Registration
The first thing we need to do is create an App Registration in Azure AD which can be used to create the apps and groups.
Navigate to Azure AD and click App Registrations, then create a new Application
Give your application a name you will recognise and then select either Single Tenant or Multi-Tenant depending on your requirements, then click Register
Grab the Application (Client ID) from the Overview screen
Now click Authentication
Click Add Platform
You want Mobile or Desktop Application
Tick the 3 boxes and click Configure
Next, select API Permissions and click Add a Permission
Select Microsoft Graph
Select Application Permissions
You’ll see it’s still not finished, you need to click Grant admin consent
Last step here, click Certificates & secrets
Add a new secret
Add a name you’ll remember and set an expiry (I’m using 24 months because I’m lazy)
Copy the Secret Value somewhere safe, it won’t re-display it and if you lose it you’ll need to create a new one
Creating GitHub Repo and Token
Now we need to create a repo we can use to host both the YAML files and the devops pipeline itself.
Create a repository, public will also work, but private probably makes more sense for source code
Now we need to create the token, in GitHub navigate to Settings (in the top right menu) – Developer Settings (at the bottom)
I’m using a Personal Access Token, but the API commands are all compatible with a GitHub App if you would prefer
Generate a new classic token and grab the output, it only needs Repo access
Before we create a pipeline, we need to add the PowerShell script into the new repo.
The script can be found here
After installing modules and authenticating, the script will first use the GitHub API to find the most recently modified YAML file as this is what will have triggered the pipeline
Then it grabs the app details from the YAML, creates an install script and configures install/uninstall commands and detection using the details we added to the YAML.
Next it will create the intunewin file (it downloaded the utility earlier) and upload it to Intune
It also creates install and uninstall groups and assigns them to the newly created application
Remember these installs all use Winget so the intunewin file does not contain any source code, it is simply telling winget where to grab everything from in the manifest file.
The install script will look like this:
$filename = $filename2.Substring($filename2.LastIndexOf("/") + 1) $curDir = Get-Location $filebase = Join-Path $curDir $filename $Winget = Get-ChildItem -Path (Join-Path -Path (Join-Path -Path $env:ProgramFiles -ChildPath "WindowsApps") -ChildPath "Microsoft.DesktopAppInstaller*_x64*\Winget.exe") Start-Process -NoNewWindow -FilePath $winget -ArgumentList "settings --enable LocalManifestFiles" Start-Process -NoNewWindow -FilePath $winget -ArgumentList "install --silent --manifest $filename"
Creating DevOps Pipeline
If you don’t have a DevOps account yet, follow the Microsoft guides here:
Once you’re setup, start a new project
Fill in the details
Now click Pipelines
And create a Pipeline
After authenticating, select the Repo you created earlier
Click Starter Pipeline
In the YML, change the VM image to windows-latest
Add the following:
- clientid (the app reg ID)
- clientsecret (the secret value)
- ownername (the Github account name)
- reponame (the repo we created earlier)
- tenantID (the tenant ID we are deploying to)
- token (your GitHub token)
Add this code to your YML
- task: PowerShell@2 inputs: filePath: '.\add-winget-package-pipeline.ps1' arguments: '-reponame $(reponame) -tenant $(tenantID) -clientid $(clientid) -clientsecret $(clientsecret) -ownername $(ownername) -token $(token)'
Click the save button (not save and run, we’re not done yet). Change the filepath if you have renamed the PS file created earlier
Click the 3 dots and select Triggers
We only want this pipeline to trigger if we add or update a YAML file, not if someone updates readme.md or something else so override the trigger
Add this to only look at YAML files
That’s the pipeline created and sitting happily waiting for you to add some juicy YAML content
As mentioned earlier, the only things in the repo are manifest files, no source code it present so it’s all lightweight and the manifest is telling Winget what to do.
An example manifest can be found here
PackageIdentifier: Foxit.FoxitReader Publisher: Foxit PackageName: FoxitReader PackageVersion: 184.108.40.206893 License: 2020 © Foxit Software Incorporated. All rights reserved. InstallerType: exe Tags: - PDF - Foxit - ICON=https://cdn.imgbin.com/1/24/20/imgbin-foxit-reader-pdf-foxit-software-computer-icons-adobe-acrobat-top-view-m5LPTpiUBTVMVjaHzkfHBRtDb.jpg - DETECTION="c:\program files(x86)\Foxit Software\Foxit Reader\FoxitReader.exe" - UNINSTALLCOMMAND="%ProgramFiles%\Foxit Software\Foxit Reader\unins000.exe" /silent - ADGROUPI=Intune-App-FoxitReader-Install - ADGROUPU=Intune-App-FoxitReader-Uninstall ShortDescription: Industry's Most Powerful PDF Reader. PackageUrl: https://www.foxitsoftware.com/pdf-editor/ Installers: - Architecture: x86 InstallerUrl: https://cdn01.foxitsoftware.com/product/reader/desktop/win/11.0.0/FoxitReader110_L10N_Setup_Prom.exe InstallerSha256: 6D33CA56B5C6E7F412FF7E7AF6A78036DF4E7AFFD1754975CA9A0A89DF9D570C InstallerSwitches: Silent: /silent /COMPONENTS="pdfviewer,ffSpellCheck,ffse" CPDF_DISABLE="1" /TASKS="startmenufolder" SilentWithProgress: /silent /COMPONENTS="pdfviewer,ffSpellCheck,ffse" CPDF_DISABLE="1" /TASKS="startmenufolder" PackageLocale: en-US ManifestType: singleton ManifestVersion: 1.0.0 MinimumOSVersion: 10.0.18362.0
As you can see, I have exteded the Tags field (no limit on this one) to include Detection, Uninstall Commands and the AAD Group Names.
I would also suggest including version numbers in the Package Name if you plan on deploying multiple versions to make things easier (also o the AAD group names)
The InstallerUrl can be absolutely anywhere so you can have your own source files on an Azure Blob or similar if you need to use non-vendor installers.
What else can this do?
Now we have this automated, you can add approvals so the pipeline won’t run until someone approves it.
You’ll also notice the TenantID is a variable and not hard-coded. If you have used a multi-tenant app reg, you can have multiple pipelines within the same DevOps account with different tenants to service multiple environments at the same time.
You could also combine the two and have approvals before it hits each tenant so for example, you deploy to Dev, test and then approve deployment to live, or get client approval to deploy to them.
Hopefully this is of use, DevOps is a powerful tool, especially when combined with PowerShell and Graph to make anything ‘as code’