Skip to content

App Catalog and Cloud Packaging

App Catalog and Cloud Packaging

The App Catalog tab in the Admin Dashboard lets you browse over 9,000 apps from Microsoft’s official WinGet repository (microsoft/winget-pkgs) and publish them directly to Intune as Win32 apps. Package manifests are fetched directly from GitHub (no third-party services), ensuring zero supply chain attack risk.

  1. Go to Admin > App Catalog tab
  2. Popular packages are displayed 24 per page with pagination controls
  3. Use the search box to find specific apps (search uses “Load More” infinite scroll)
  4. Each package shows: an icon, name, publisher, package identifier, and version

Catalog cards show real app icons without your admins’ browsers ever calling a third party for them. Icons come from two sources:

  • Curated icons. A hand-picked set of the most common apps ships already encoded in the daily catalog index, so those apps render an icon immediately on the first page.
  • Resolved icons. For apps outside the curated set, the portal resolves the icon once, on the server, when the app is first viewed in the catalog. It fetches the icon from a public source, stores the bytes in your own database, and serves every later view from there. Only a public domain name leaves your tenant during resolution; no tenant data is sent, and the browser never makes the call.

Anything still without an icon shows a colored letter tile. Icon resolution is controlled by Resolve catalog icons automatically under Store Administration > System, in Catalog Sourcing, which is on by default. Turn it off to run with the curated icons and letter tiles only, with no server-side icon lookups. The curated set grows over time, so the portal resolves fewer and fewer icons on its own.

  1. Find the app you want to publish
  2. Select architecture and locale (if available):
    • Each package card displays available architectures (e.g., x64, x86, arm64) and locales (e.g., en-US, de-DE, fr-FR)
    • If multiple options are available, they appear as dropdown selectors
    • If only one option is available, it appears as a label showing what will be published
    • Default selections: x64 for architecture, en-US for locale
  3. Select the Publish to Intune button on the package card
  4. A packaging job is created with your selected architecture and locale
  5. Monitor job status in the Packaging Jobs section below the catalog

The first step of the publish wizard, App information, sets what Intune shows for the app.

  • Name, Publisher and App Version come from the package and can’t be changed. App Version decides which installer is downloaded, and Name and Publisher can be used to detect the installed app, so changing them could break packaging or detection.
  • Description, Category, Information URL, Privacy URL, Featured app in Company Portal, Developer, Owner and Notes are sent to Intune as you enter them. Developer starts as the publisher. Category puts the app in the Intune app category of that name, and creates the category if your tenant doesn’t have one.
  • Logo is optional. Upload a PNG or JPEG file of 1 MB or less to use instead of the catalog icon. Intune accepts only these two formats, so other files are refused.
  • Tags are used in App Store only.

Architecture, locale, and version tracking:

When apps are published from the WinGet catalog:

  • The version you choose is the version that is packaged. Pick a specific version and exactly that version is downloaded and packaged, even when a newer one exists. If the chosen version is no longer published in the catalog, publishing fails with a message rather than packaging a different version. Leave the version at its default to package the latest.
  • The installer is checked before it is packaged. The downloaded installer must match the SHA-256 the catalog publishes for that version, or packaging stops.
  • Machine-wide installers are preferred. Intune installs apps as the local SYSTEM account, so when a package offers both a machine-wide and a per-user installer, the machine-wide one is used. A package that only installs per user is refused with a message saying so, instead of being published and then failing on devices.
  • The selected architecture, locale, version, installer type and scope are stored with the packaging job, and the app inherits them when it syncs from Intune.
  • Deployment groups are named {prefix}{Vendor} {AppName}-{arch}-{locale}-Required, for example AppStore-7-Zip 7-Zip-x64-en-US-Required, with no version segment, so later versions of the same app reuse the same group.

When the end-user portal is enabled, employees can ask for an app that isn’t in the catalog using the Request new app button on the Browse Apps page (see Ask IT to add an app). The portal emails every member of the Admin Group and logs the request in the audit trail. The email includes the requestor’s name and email, the app name and publisher, the business justification, the download URL if provided, and suggestions for how to add the app.

When you receive a new app request:

  1. Evaluate the request: decide whether the app is appropriate for your organization.
  2. Find the app: search the App Catalog above for easy publishing, look in Intune if it’s already there, or download it from the vendor.
  3. Add it to the portal: publish it from the App Catalog above, or add it to Intune manually and sync.
  4. Configure visibility: make the app visible in the portal so the requestor can request it.
  5. Notify the user: reply to the email, or tell the requestor directly that the app is available.

New app requests reuse your existing email notification settings, so no extra configuration is required. Admin Group members receive the emails, sent with the same Mail.Send configuration as other notifications (see Communications). If email notifications are disabled, the Request new app feature returns an error to the user.

The Packaging Jobs section shows all queued and completed packaging operations:

StatusDescription
PendingJob is queued, waiting for processing
DownloadingDownloading the installer from WinGet manifest URL
PackagingWrapping in PSADT v4 and creating .intunewin package
UploadingUploading package to Intune via Graph API
CreatingCreating Win32LobApp in Intune
CompletedApp successfully created in Intune
FailedError occurred - select Retry to requeue. Retry is available only for failed jobs.

Cancel stops a running job: the job is marked cancelled and the worker abandons it at its next checkpoint rather than finishing in the background.

The packaging process runs in-process on the App Service, no separate containers, agents, or functions needed:

  1. Queue message: When you select “Publish to Intune”, a job is added to Azure Storage Queue
  2. Background service: The in-process PackagingQueueService picks up the job
  3. Download: Installer is downloaded from the URL in the WinGet manifest
  4. Wrapping (PSADT mode only): Installer is wrapped in PSAppDeployToolkit v4 for standardized deployment
  5. Package creation: .intunewin package is created using the cross-platform SvRooij.ContentPrep library
  6. Intune upload: The package is uploaded and a Win32LobApp is created via Graph API

When publishing an app from the App Catalog, you can choose between two packaging methods using the dropdown on each package card:

MethodDescription
Raw (default)Packages the installer directly. The installer executable is the entry point, Intune runs it with silent switches. Simpler, smaller package size.
PSADTWraps the installer in PSAppDeployToolkit v4. Provides standardized logging, pre/post-install hooks, and consistent exit codes across all installer types. Larger package size due to framework files.

When to use PSADT:

  • You need standardized deployment logging across all apps
  • You want consistent install/uninstall behavior regardless of installer type
  • You need the pre-installation and post-installation hooks (e.g., closing running applications before install)

When to use Raw:

  • You want the simplest, most direct installation
  • Package size matters (PSADT adds ~5 MB of framework files)
  • The app’s native installer already handles silent installation well

When you upload a custom installer, the App Store inspects the file itself rather than trusting its extension, and identifies which installer engine built it. The engine determines the silent switches offered, whether the file wraps an MSI, and how detection is generated.

The following engines are recognized:

EngineWraps an MSI
Windows Installer (MSI)Yes
WiX Burn bundleYes
Advanced InstallerYes
Inno SetupNo
Nullsoft Scriptable Install System (NSIS)No
InstallShieldNo
InstallAwareNo
Squirrel.WindowsNo
7-Zip self-extracting archiveNo
WinRAR self-extracting archiveNo
MSIX / AppX packageNo

An installer that matches none of these is treated as a generic executable, which still packages and deploys. Identifying the engine improves the defaults you are offered; it is not a prerequisite for publishing.

For a .zip upload, the archive is inspected and you choose which file inside it is the installer to run. That file is then identified the same way.

In Raw mode, the installer is packaged directly into the .intunewin file. The install and uninstall commands sent to Intune depend on the installer type:

Install commands by file type:

File TypeInstall CommandUninstall Command
.msimsiexec /i "installer.msi" {silent switch}msiexec /x "installer.msi" {silent switch}
.exe"installer.exe" {silent switch}Registry lookup (see below)
.msix / .appxpowershell.exe Add-AppxPackage -Path 'installer.msix'Registry lookup (see below)

Registry-based uninstall (for non-MSI): For EXE and other non-MSI installers, the uninstall command finds the app in the Windows registry (HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall and the Wow6432Node equivalent) by the identity the package itself registers: its Add/Remove Programs key when the catalog states it, otherwise the display name the package declares, and only as a last resort the catalog name. This matters for apps whose Windows name differs from their catalog name, such as “Mozilla Firefox (x64 en-US)”. It uses the QuietUninstallString if available, otherwise appends /S /silent /quiet to the UninstallString. When a newer version’s package cannot prove an uninstall identity, an existing working uninstall command is kept rather than replaced.

Verbose logging (raw MSI): In Raw mode, .msi install and uninstall commands append /L*v to write a verbose Windows Installer log to the Intune Management Extension Logs folder (C:\ProgramData\Microsoft\IntuneManagementExtension\Logs), which Intune collects automatically when you gather logs from the console. PSADT mode does its own logging and is not modified.

In PSADT mode, the installer is placed inside a PSAppDeployToolkit v4 folder structure. The PSADT framework provides a standardized wrapper around the installer with logging, error handling, and consistent exit codes.

If the PSADT wrap fails (for example, the toolkit template cannot be fetched on first use, or blob storage is not configured), the job fails with the reason. The portal does not fall back to packaging the bare installer, because that would produce a package with a different install command and detection than the one you configured. Fix the cause and retry the job, or switch the app to Raw mode.

Package structure created:

Package/
+-- Invoke-AppDeployToolkit.exe <-- Entry point (what Intune runs)
+-- Deploy-Application.ps1 <-- Generated install/uninstall logic
+-- AppDeployToolkit/ <-- Framework files (from PSADT v4 template)
| +-- AppDeployToolkitMain.ps1
| +-- AppDeployToolkitHelpers.ps1
| +-- ...
+-- Files/
+-- installer.exe (or .msi) <-- Your actual installer

Intune commands (all PSADT packages use the same commands):

  • Install: Invoke-AppDeployToolkit.exe -DeploymentType Install -DeployMode Silent
  • Uninstall: Invoke-AppDeployToolkit.exe -DeploymentType Uninstall -DeployMode Silent

Generated Deploy-Application.ps1 script variables:

VariableValue
$appVendorPublisher name from the WinGet manifest
$appNamePackage name from the WinGet manifest
$appVersion(empty, version tracked via Intune metadata)
$appLangEN
$appRevision01
$appScriptVersion1.0.0
$appScriptAuthorAppRequestPortal

Install section, what runs during installation:

For MSI installers:

Terminal window
Execute-MSI -Action Install -Path "$dirFiles\installer.msi"

PSADT automatically adds /qn /norestart when running in Silent deploy mode. If a custom silent switch was provided that includes MSI properties (e.g., TRANSFORMS=..., PROPERTY=VALUE), those are passed via -AddParameters.

For EXE installers:

Terminal window
Execute-Process -Path "$dirFiles\installer.exe" -Parameters '{silent switch}' -WaitForMsiExec

The -WaitForMsiExec parameter ensures PSADT waits if another MSI installation is already in progress. EXE installers always get explicit silent switches since PSADT doesn’t know the correct flags for each installer.

Uninstall section, what runs during uninstallation:

For MSI installers:

Terminal window
Execute-MSI -Action Uninstall -Path "$dirFiles\installer.msi"

For non-MSI installers, the script finds the app’s uninstall entry by the identity the package registers, as described under Raw mode above (the Add/Remove Programs key first, then the declared display name, then the catalog name). Simplified:

Terminal window
$regPaths = @(
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
$app = Get-ItemProperty $regPaths |
Where-Object { $_.DisplayName -like '*AppName*' } |
Select-Object -First 1
if ($app.QuietUninstallString) {
Execute-Process -Path 'cmd.exe' -Parameters "/c $($app.QuietUninstallString)"
} elseif ($app.UninstallString) {
Execute-Process -Path 'cmd.exe' -Parameters "/c $($app.UninstallString) /S /silent /quiet"
}

Repair section: Runs the same commands as the install section. PSADT supports Invoke-AppDeployToolkit.exe -DeploymentType Repair -DeployMode Silent for reinstallation.

Error handling: If the PSADT script encounters a fatal error, it exits with an error code in the 60000 range and logs the error details. PSADT’s built-in logging writes to C:\Windows\Logs\Software\ on the target device.

The following PSADT error codes are mapped as “Failed” return codes in Intune:

Exit CodeDescription
60001General PSADT error (catch block in Deploy-Application.ps1)
60002Error during pre-installation phase
60003Error during installation phase
60008Generic non-zero exit from the wrapped installer

These are registered in the Intune Win32 app return code mappings so that PSADT failures are properly reported in Intune and reflected in the portal’s install status tracking.

If PSADT wrapping fails, the job fails. There is no fallback to Raw packaging. Shipping the bare installer instead would produce a package with a different install command, different detection, and none of PSADT’s logging or deferral behaviour, recorded as a success; an admin who asked for PSADT would be running something they did not configure. The job stops with the reason. Fix the cause and retry, or switch the app to Raw mode deliberately.

PSADT template acquisition: As of 1.39.0, App Store ships one qualified PSADT release, 4.1.8. It is downloaded from that release’s own immutable tag URL rather than from releases/latest, and every download is checked against an expected host, size, and SHA-256 before it is used or cached. A download that fails any of those checks is refused, and the job reports which check failed, so a network problem reads differently from a content mismatch.

The verified artifact is cached in Azure Blob Storage under a version-scoped path (psadt-template/4.1.8/PSAppDeployToolkit_Template_v4.zip), so qualifying a different release later cannot silently reuse the previous one. Upstream publishing a new PSADT version does not change what your packages contain: adopting one is a deliberate change on our side, after the generated packages are validated against it. Choosing a PSADT version per tenant is not supported today.

Silent switches tell the installer to run without displaying any UI. The portal resolves the silent switch in this order:

  1. WinGet manifest value, if the manifest specifies InstallerSwitches.Silent, that value is used
  2. Installer type default, based on the InstallerType field in the WinGet manifest
  3. File extension fallback, based on the installer’s file extension
  4. Last resort, /S /silent

Default silent switches by installer type:

Installer TypeSilent SwitchUsed By
MSI / WiX/qn /norestartWindows Installer packages
InnoSetup/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-Inno Setup installers
Nullsoft / NSIS/SNSIS installers
Burn/quiet /norestartWiX Burn bootstrapper bundles
EXE (generic)/S /silentGeneric executables

These defaults are applied in both Raw and PSADT modes.

Every app published to Intune includes an auto-generated PowerShell detection script. Intune runs this script on target devices to determine whether the app is already installed.

Detection strategy (in order of preference):

  1. ProductCode detection (most reliable), if the WinGet manifest includes AppsAndFeaturesEntries with a ProductCode (MSI GUID), the script checks for that exact GUID in the registry uninstall paths. This is the most precise detection method.

  2. DisplayName detection (fallback), if no ProductCode is available, the script searches the registry for an entry where DisplayName matches the app name (using wildcard matching). Publisher name is also checked if available in the manifest.

Version checking: Detection for a publish (a new install) checks only whether the app is present, at any version, not a minimum version. A version-gated rule would report a device running an older build as “Not Installed”, and Intune would then upgrade it outside a maintenance window. Rolling out a newer version is the job of App Updates, which deploys on rings and a schedule; an update deployment’s detection is version-gated (installed version at or above the target) so the rollout knows the update applied.

Registry paths checked:

  • HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
  • HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*

Script execution context: Detection scripts run as 64-bit PowerShell (runAs32Bit: false) without signature enforcement (enforceSignatureCheck: false). The script exits with code 0 if the app is detected, or 1 if not installed.

When the app is created in Intune via the Graph API, the following settings are applied:

SettingValue
Applicable architecturesx64, x86
Minimum OSWindows 10 1903
Install contextSystem (runs as SYSTEM account)
Device restart behaviorBased on return code

Return codes configured:

CodeTypeMeaning
0SuccessInstallation completed successfully
1707SuccessInstallation completed successfully (MSI)
3010Soft rebootInstallation succeeded, reboot recommended
1641Hard rebootInstallation succeeded, reboot initiated
1618RetryAnother installation is in progress, retry later

App icon: The logo you upload in the publish wizard is used. Otherwise, if available, the app icon is fetched automatically (via Google S2 Favicon API or GitHub publisher avatar). Intune accepts PNG and JPEG logos only, so an icon in any other format is left off the app rather than sent.

Packaging works out of the box with the default deployment. No additional setup is required beyond:

  1. Azure Storage account (deployed by default via Bicep template)
  2. Graph API permissions for Intune app management (see Prerequisites)
  3. Verify connectivity:
    • Go to Admin > App Catalog
    • Try publishing a test app (e.g., “7-Zip”)
    • Check Packaging Jobs for status

The qualified PSADT release (4.1.8) is downloaded on first use, verified against its expected host, size, and SHA-256, then cached in Azure Blob Storage for later jobs.

By default, the packaging pipeline trusts WinGet manifests fetched from raw.githubusercontent.com. Each manifest declares a SHA256 for its installer, and the pipeline verifies the downloaded installer matches before publishing to Intune. That defends against a tampered installer at the publisher’s download server, but it does not defend against a tampered manifest on the GitHub fetch path.

To close that gap, enable Settings → “Verify manifest integrity against Microsoft’s signed index”.

When the setting is enabled:

  1. Before packaging any WinGet-sourced app, the portal downloads Microsoft’s signed community index (source2.msix, ~10 MB) from https://cdn.winget.microsoft.com/cache/source2.msix.
  2. The portal extracts the SQLite database embedded in that MSIX and looks up the package by its identifier.
  3. If the package is not present in the index, the packaging job fails immediately with an explicit “Manifest integrity check failed” error. There is no silent fallback.
  4. The downloaded MSIX and extracted SQLite are cached for 12 hours to avoid re-downloading the index on every packaging job.

When to enable this: Any environment that wants the same manifest-trust posture as the WinGet client itself. The cost is a one-time ~10 MB download every 12 hours and a brief extra step on the first packaging job after a cache refresh.

When to leave it off: If you’ve already implemented your own integrity controls (e.g. a hardened internal mirror of the WinGet repo with its own signing pipeline) and prefer to rely on those.

Failure mode: If verification fails for a specific package, only that packaging job fails, other jobs continue normally. To temporarily bypass verification (for example, to package a package that is genuinely missing from the index due to an upstream removal), an admin can disable the setting from the Settings page and retry. Re-enable it afterward.

For the full security chain and v2 roadmap (per-version manifest hash comparison, MSIX signature validation), see Security Overview.

The App Updates tab in the Admin Dashboard shows all apps that have been published from WinGet and are being tracked for version updates.

Tracked apps table:

ColumnDescription
AppApp name and publisher
WinGet Package IDThe WinGet identifier (e.g., Microsoft.VisualStudioCode)
Published VersionVersion currently in your portal/Intune
Latest VersionLatest version available in the WinGet repository
StatusSteady state for this app, Update Available, Up to Date, Unknown (sticky, when the last deploy attempt failed), or Check failed (sticky, when the most recent WinGet update lookup couldn’t resolve a version). Unknown is intentionally non-alarming, when packaging or assignment fails, we genuinely don’t know what state Intune is in until you investigate via the activity bell or the Packaging Jobs tab. Transient deploy progress (Queued, Packaging, Assigning, etc.) is surfaced in the activity bell at top-right of the admin pages, not in this column.
Ring TemplateName of the ring template configured for this app, or ”, ” when the app uses custom rings or ringless mode (per-app settings live in the Update Ring Configuration side panel)
Last CheckedWhen the version was last compared. A small red dot is shown when the most recent check failed (e.g., the WinGet package ID could not be resolved)
ActionsDeploy Update or Dismiss (only shown when update is available). Deploy Update opens a confirmation wizard showing exactly what will be deployed (app, version, ring mode, target, ring breakdown, auto-deploy setting) before you commit.

Select any row in the table to open a per-app detail view where you can configure ring deployment, set the auto-deploy override, and run an immediate update check (Check Now) just for that app, useful when you want to confirm a single app’s status without waiting for the next scheduled run.

Automatic tracking:

  • Apps published from the WinGet catalog are automatically tracked for updates
  • The SourceWingetPackageId is backfilled automatically by the background service (no manual Intune sync required)
  • Update checks run on a configurable schedule (default: every hour when enabled)
  • The “Last Checked” date now stamps on every attempt, success or failure, so a stale date in the UI tells you “this app’s lookup has been failing”, not “the service is broken”. The failure reason is shown on the per-app detail view under Last Check Result.

Actions:

  • Check for Updates: Manually trigger an update check for all tracked apps
  • Check Now (per-app, on the detail view): Run an update check for a single app without waiting for the schedule
  • Deploy Update: Opens a confirmation wizard (v1.24.0+) showing the deployment summary, app, version transition, ring mode (template/custom rings/ringless), ring breakdown or ringless target+filter, and the per-app auto-deploy override. Once you select Deploy in the wizard, a packaging job is queued that downloads the new version from WinGet, wraps it in PSADT, archives the .intunewin to blob storage for rollback, creates a new Install Win32 app in Intune (advancing the portal’s version pointer) and a paired Update Win32 app with a requirement script that targets only devices already running the app. Watch the activity bell at top-right for in-flight progress.
  • Dismiss: Hides the update notification for that app

Activity bell (v1.24.0+):

The bell icon at the top-right of every admin page is an Intune-style task tracker. The badge shows the count of in-flight deploys plus any sticky-failed entries; select the bell to open a drawer listing recent activity with each entry’s current state, age, and any error or pause message. Adaptive polling: 15 seconds while at least one task is in-flight, 60 seconds otherwise to keep the badge accurate; stops entirely when nothing’s happening and the drawer is closed (so you’re not burning API calls just to show a zero badge). Use this when you need to know what’s happening right now across all your deploys; use the App Updates Status column when you need to know what state each app is in; use the Activity tab (below) when you need the full historical timeline.

Dismissing sticky failures (v1.27.0+):

Each terminal entry in the bell drawer (Up-to-Date, Packaging Failed, Assignment Failed) has a × dismiss button. Dismissing removes the entry from the drawer’s default view and from the Activity Log tab’s default view. Active entries, Queued, Packaging, Assigning, Paused, can’t be dismissed; they’re still moving and would just reappear on the next poll. A new failure for the same app is a distinct row, so a re-failure shows up regardless of whether you dismissed the prior one. Dismissals persist per-browser via localStorage. To restore everything you’ve dismissed, open the Activity tab and select Restore dismissed.

Activity tab (v1.27.0+):

Full unified timeline of packaging jobs and update deployments, the same data the bell drawer surfaces, but with no terminal-entry cap. Use this when you need to answer “what happened last week” or “did this app’s last three deploys all fail?”, questions the 8-entry drawer can’t span.

The filter bar above the activity table:

  • State, All / Active (in flight) / Failed / Up-to-Date / Paused
  • Time, Last 24 hours / 7 days / 30 days / 90 days / All time (default 30 days)
  • Search, free-text match against app name and version
  • Show dismissed, toggle to include entries you’ve dismissed in the bell drawer

Filters compose. The table paginates at 25 rows per page so a long history doesn’t bog the page down. Each row has its own Dismiss button mirroring the bell drawer’s behaviour, plus the standard state badge, started/finished timestamps, and the deploy’s error or pause message.

The Run cleanup button on this tab runs both retention sweeps in one action:

  • Packaging jobs older than 30 days (keeping the 100 most-recent)
  • Update deployments older than 90 days (keeping the 100 most-recent)

In-flight and Paused work is never deleted regardless of age, and the most-recent N rows are always preserved regardless of age. Cleanup is manual, there is no scheduled background sweep, so retention happens on whatever cadence admins choose.

The bell drawer’s footer link View all activity → deep-links straight here (/admin?tab=activity).

Restarts during publishing and updates: if the App Service restarts while an app is being packaged, uploaded to Intune or finished off, the portal picks the work up again when it starts. It does not create a second copy of the app in Intune, and a deployment does not stay stuck at Packaging because of the restart. You don’t need the cleanup actions below for a restart.

Deleting an updater app: the Delete updater app action on a finished update deployment reports what really happened. An updater that someone already deleted in Intune counts as deleted. If Intune refuses the deletion, the action reports a failure and the updater stays visible so you can retry; it is never shown as deleted while it still exists.

Recovery for stuck or orphaned deploys (v1.24.0+):

If a deploy fails in a way the portal cannot recover from on its own, or completes packaging but the Update app handler doesn’t advance the deployment, the recovery action is reachable from either side of the same orphaned state:

  • Packaging Jobs table surfaces Cancel and Cleanup for in-flight stuck jobs and Cleanup orphaned deploy for completed jobs whose linked deployment is still on Packaging.
  • Update Deployments table (v1.26.0+) surfaces a parallel Cancel and Cleanup for deployments stuck on Packaging.

Both routes call the same cancel-and-cleanup endpoint: one action marks the packaging job Failed for audit, marks the linked deployment Failed, and deletes the per-job blob folder to free storage. The Cleanup actions are time-gated to deployments that have been stuck for more than 10 minutes, brand-new deployments legitimately sit at Packaging for a few minutes while the Update-app handler runs, so the button is hidden until the threshold is exceeded. Re-trigger the deploy from App Updates → Deploy Update when ready, the new attempt starts clean.

Ring templates define a staged deployment strategy for app updates. Instead of deploying an update to all devices at once, rings let you roll out to a pilot group first, wait a configurable number of days, then expand to broader groups.

Managing templates:

On the App Updates page, scroll to the Update Ring Templates section:

  • Select Create Template to define a new ring template
  • Each template has a name and one or more rings (up to 10)
  • Each ring has a name (e.g., “IT Pilot”), a delay in days, and one or more Microsoft Entra ID security groups
  • Each ring has its own deployment settings: available/deadline timing, install behavior, restart settings, notifications, and delivery optimization
  • Select Show Deployment Settings on any ring to configure available time (days + time of day), deadline time, restart grace period, snooze settings, and more
  • Ring 1 typically has a 0-day delay (deploys immediately); subsequent rings deploy after their configured delay
  • Set one template as the default to auto-assign it to new apps
  • Select Import from Autopatch to discover Windows Autopatch ring groups from your Microsoft Entra ID and pre-populate rings with those groups

Example configuration:

RingNameDelayGroups
1IT Pilot0 daysIT-Pilot-Devices
2Early Adopters3 daysEarly-Adopters
3Production7 daysAll-Managed-Devices

This means Ring 1 deploys immediately, Ring 2 deploys 3 days later, and Ring 3 deploys 7 days after Ring 2, a total rollout of 10 days.

Per-app configuration:

For apps published from the WinGet catalog, select the app’s row on the App Updates tab to open its detail view. The detail view includes an Update Rings section with three deployment styles:

  • Ringless, updates deploy immediately to a single Intune assignment with no staged rollout. Pick All Devices or All Users as the target (default: All Devices), and optionally apply an Intune assignment filter to narrow the audience (e.g. limit by OS version or department). Ringless still creates the same two-app Install/Update pair as ringed mode (so rollback, install/update telemetry, and the lifecycle Status column work identically), it just collapses the rollout to a single ring targeting the chosen built-in audience.
  • Use a template, apply a reusable ring template defined under Update Ring Templates. If a default template is set, it is preselected.
  • Custom rings, define rings specific to this one app. Same per-ring controls as a template (groups, availability/deadline timing, install behavior, restart settings, end-user notifications, delivery optimization, assignment filter), scoped to a single app. Use this when no global template fits, for example when this app needs a different pilot group, a different rollout pace, or a specific assignment filter.

When you select Custom rings, choose Edit custom rings to open the ring editor. Edits in the editor are held in the side panel until you select Save, switching between deployment styles before saving doesn’t lose your in-progress custom rings. Ringless target/filter choices are likewise preserved when you flip to a ringed mode and back, so admins can experiment without losing prior settings.

The detail view also has an Auto-Deploy Updates section to set the per-app override (use the global default, always auto-deploy, or never auto-deploy). As of v1.23.0 auto-deploy works for ringless apps too, the only safety check is that the Update Ring Configuration side panel has been opened at least once for the app (so the admin has explicitly picked ringed or ringless mode rather than leaving it unconfigured).

Note (v1.20.0): The Update Rings and Auto-Deploy controls used to live on the App Management tab’s app detail view. They have moved to the App Updates tab because not every app under App Management is tracked for updates, only apps published from the WinGet catalog are.

When you select Deploy Update for an app with ring-based updates enabled, the system:

  1. Creates a packaging job for the new version (same as non-ringed updates)
  2. When packaging completes, creates a new “Update” Win32 app in Intune
  3. Activates Ring 1 immediately, creates Required assignments for Ring 1’s groups
  4. Schedules subsequent rings based on their configured delays
  5. A background service checks every 15 minutes for rings that are ready to activate
  6. When each ring’s delay expires, its groups receive the Required assignment

Deployment dashboard:

The App Updates tab shows active and recent deployments below the updates table:

  • Ring progress indicator, colored circles show which ring is active
  • Select a deployment row to expand and see detailed ring status, schedule dates, and group assignments
  • Pause, stops ring progression (active ring stays deployed, next ring is held)
  • Resume, restarts ring progression after a pause
  • Advance, manually skip the delay for the next ring (useful for accelerating after pilot validation)
  • Halt rollout, removes active ring assignments and marks the deployment rolled back. Devices that already updated keep the new version; future rings are stopped. (Renamed from Rollback in v1.26.0 to disambiguate from the App-level rollback that swaps the Install app back to the prior version on all devices.)
  • Cancel, removes all assignments and marks the deployment as canceled
  • Cancel and Cleanup, for deployments stuck on Packaging for more than 10 minutes. Marks the deployment Failed, marks the linked packaging job Failed, and deletes the blob folder for that job in one atomic action. Mirrors the Cancel and Cleanup button on the Packaging Jobs table.

Two-app model:

When you deploy an update, ringed or ringless (v1.23.0+), the system creates two separate Win32 apps in Intune:

  1. Install app (user-targeted) — immediately updated to the new version so new installs always get the latest. Existing assignments are preserved.
  2. Update app — a separate Win32 app with a requirement script that only installs on devices that already have the app. Devices without the app see it as “Not Applicable.” Assignments are created on this app:
    • In ringed mode, one Required assignment per group per ring, activated as the rollout progresses.
    • In ringless mode, a single Required assignment to All Devices or All Users (the admin’s choice in the Update Ring Configuration side panel), with an optional Intune assignment filter. Created and activated immediately at deploy time, there’s only one ring to roll out.

This separation ensures:

  • New device enrollments always get the latest version (no vulnerability window)
  • Updates to existing devices are controlled separately from new installs (and, in ringed mode, gated through rings with health checks)
  • User-targeted and device-targeted assignments don’t conflict

Per-ring assignment settings:

When a ring activates, the per-ring deployment settings configured on the template are applied to the Intune assignment. Each ring’s assignment includes:

  • Available time — computed from the ring activation time plus the ring’s “Available after days” + “Available at time” (or “as soon as possible” if not configured)
  • Deadline time — computed the same way for the install deadline (or none if not configured)
  • Time zone — assignment uses device local time when “Use device time zone” is enabled, otherwise UTC
  • End user notifications — Show all, Show reboot only, or Hide all
  • Delivery optimization priority — Foreground or Not configured
  • Restart settings — grace period, countdown, and snooze duration (omitted when restart behavior is “Suppress”)
  • Assignment filter — optional Intune filter (Include or Exclude) scoping the ring to a subset of devices

Settings are read from the deployment-time snapshot on the ring, so changes to the template after a deployment starts do not affect rings already in flight.

Per-ring assignment filters:

Each ring in a template can target an Intune assignment filter. Open the ring’s “Show Deployment Settings” panel and pick a filter from the list (Windows-platform filters from your tenant). Choose Include matching devices or Exclude matching devices, or clear the filter to assign without one. The filter is snapshotted onto the deployment ring at deploy time alongside the other ring settings.

Install app rollback:

If a new version has installation issues, you can roll back the Install app to the previous version in one action. In the app detail view, the Version and Rollback section card (top of the Properties view, v1.26.0+) has a primary Roll back to previous version button. This swaps the Install app back to the previous version’s Intune app (which still exists in Intune), so new installs receive the older working version. No re-packaging is needed.

To pick a specific historical version (rather than the immediate predecessor), select View full history… in the same section card. The version history modal lists every published version with archive status and a per-version Rollback button.

Update app rollback:

Rolling back a ring-based deployment removes the Intune assignments from active rings so no additional devices receive the update. Devices that already installed the update retain the new version — Intune does not uninstall it.

Ring advancement is not just delay-based — the system checks device install health from Intune before advancing to the next ring. This prevents a failed update from rolling out broadly.

How it works:

After a ring’s delay expires, the progression service queries Intune for device install status before advancing:

ConditionAction
Success rate >= threshold (default 95%)Advance to next ring
Failure rate > failure threshold (default 10%)Auto-pause deployment, notify admin
Not enough data yet (evaluation period not met)Wait, even if delay expired

Configuration (on ring templates):

SettingDefaultDescription
Minimum success rate95%Required success rate before advancing
Maximum failure rate10%Failure rate that triggers auto-pause
Minimum evaluation period24 hoursWait time after ring activation before checking status

Set the minimum success rate to 0% to advance based on delay only (disables health checks).

Updates can be deployed automatically when detected, eliminating the need to select “Deploy Update” for each app.

Global setting (App Updates page, Update Settings panel):

  • Auto-deploy updates — when enabled, all apps with ring-based updates configured will automatically package and deploy through their rings when a new version is detected

Per-app override (App detail view):

  • Always auto-deploy — this app always auto-deploys, regardless of the global setting
  • Never auto-deploy — this app never auto-deploys, regardless of the global setting
  • Use global default — follows the global setting

Safety requirements:

  • Auto-deploy only works for apps whose update settings you have saved, either ring-based or ringless, in the app’s detail view. An app you never configured still requires a manual Deploy Update, even if auto-deploy is on, so nothing is pushed without a deliberate choice of target.
  • An app you have rolled back is paused for auto-deploy until an administrator selects Resume Auto-Deploy in App Updates. See A rollback pauses automatic updates.
  • Only one update deployment can be active for an app at a time.
  • The Deploy Update button always works for manual one-off deployments, including for a paused app.

The App Catalog’s WinGet settings, the GitHub personal access token and the WinGet repository URL, are configured under Store Administration > System, in Catalog Sourcing. A token is optional but strongly recommended for higher API rate limits and live search. See Configure WinGet integration for the full walkthrough.

For developers testing the packaging feature locally:

  1. Prerequisites:

    • Azure Storage account configured (uses the same storage queue and blob container as production)
    • Graph API credentials configured for Intune access
  2. Configure the API (src/AppRequestPortal.API/appsettings.json):

    • Ensure AzureStorage:ConnectionString points to your Azure Storage account
  3. Run the services:

    Terminal window
    # API with background packaging service (port 5000)
    cd src/AppRequestPortal.API && dotnet run
    # Frontend (port 3000)
    cd src/AppRequestPortal.Web && npm start
  4. Test: Go to Admin > App Catalog, search for an app, select “Publish to Intune”. The in-process background service will pick up the job and process it.

Jobs stuck in Pending:

  • Verify the App Service is running and the background service started (check Application Insights logs for PackagingQueueService)
  • Ensure the AzureStorage:ConnectionString is configured correctly
  • Check that the storage queue packaging-jobs exists

Jobs failing during download:

  • The service downloads directly from the installer URL in the WinGet manifest
  • Check if the package exists in the Microsoft winget-pkgs repository
  • Review logs for HTTP errors or GitHub API rate limits

Jobs failing during PSADT wrapping:

  • The job fails rather than packaging the raw installer instead, so the failure is the whole story: no package was created and nothing was published to Intune
  • Check logs for PsadtService entries; the failure names the cause
  • First use downloads the PSADT template from github.com, so outbound HTTPS to that host must be allowed
  • A failure reported as a content mismatch rather than a download error means the artifact did not match its expected size or SHA-256. That is not a retry-until-it-works condition: treat it as a supply-chain signal and report it

Jobs failing during upload:

  • Verify the API has Graph API permissions for Intune (DeviceManagementApps.ReadWrite.All)
  • Review API logs for Graph API errors (detailed error messages are logged)