Intune Discovered Apps – Detecting your applications and gaining back control

One critical aspect and goal for IT, and for the tools they use – is the ability to inventory which applications EXIST in the environment, and which of those applications are actually USED. The feature to detect EXISTANCE is often called Application Inventory or Software Inventory. The feature to track USAGE, is often called Software Metering, or Application Usage Monitoring, or Application Metering. This blog will focus on the application inventory feature (specifically the Intune “Discovered Apps” feature), and not cover anything related to Software Metering (although I may revisit this part in a future blog).

This blog post is organized into 8 parts:

  1. Discovered Apps in the Intune Portal
  2. Requirements for Intune Application Inventory
  3. Application Inventory Overview
  4. Application Inventory data stored in the Client Registry
  5. Microsoft Graph and a PowerShell scripting
  6. Using the PowerShell Script
  7. Get control back over your tenant
  8. Summary and Footnotes

1. Discovered Apps in the Intune Portal

The key feature discussed in this blog can be found at Intune portal > Apps > Monitor > Discovered apps

Intune Discovered apps

Microsoft collect an inventory for the installed appx packages, MSIs and EXEs on your Intune managed devices by way of the Intune Management Extension. A WMI class is referenced to find the installed software and this information is stored in the registry. The inventory is posted to Intune by the Intune Management Extension at regular intervals and this information can be viewed at the device level or as an aggregated report in the Intune portal.

[Quick side note. During the week of 7/7/23 – this functionality broke in Intune. Discovered Apps in Intune managed clients is now always returning blank. We at Patch My PC are still trying to track down if this was an intentional change by Microsoft and the Intune Product Team; or an accidental bug. More on this as soon as we have better information.]

2. Requirements for Intune Application Inventory

Not all devices visible in the Intune portal can return application inventory information. In order for this to be collected, and populated for a device, the device must be:

  1. A corporate-owned device, not personal (Only for a Win32 app).
  2. An Intune Enrolled Device.
  3. Co-managed devices need the client apps workload switched to Intune. The IME (Intune Management Extension) is required in order to collect the win32 app inventory.

The requirement for a device to be corporate owned is related to privacy rules around collecting information from end users on personal devices. This is part of the challenge for Intune, and why it sometimes seems less capable in application inventory than other management products.

3. Application Inventory Overview

While testing this service out, I noticed the following, crucial, details.

Slow data ingestion.

A Win32 application inventory gets synced every 24 hours per device.

Modern Microsoft Store apps are even slower. The sync happens once every seven days.

The aggregated main list populates longer than 24 hours, even for a Win32 app. Again we are averaging 7 days in my tests.

Numbers don’t match with actual install count.

Due to the potential overlap of multiple users and targeting changes, numbers are usually incorrect. Let’s hope this will improve, but it is a technical challenge for Microsoft with the many sources of application data, and the different speeds of the different data collection cycles and channels.

Export is supported.

You have 2 options to export. The raw data contains more detailed information:

Export discovered apps

4. Application Inventory data stored in the Client Registry

Inventory in the registry

In the following registry entry there is a key where you can find the time when your device last synced his app inventory.

Full path: ComputerHKEY_LOCAL_MACHINESOFTWAREMicrosoftIntuneManagementExtensionInventorySetting

Registry value: LastFullSyncTimeUtc

5. Microsoft Graph and a PowerShell scripting

You can manually discover Applications in Intune with PowerShell and Microsoft Graph.

I wrote a custom PowerShell script that can be used as a starting example for your custom detection script. The script will: –

$excludedAps = @( 'Microsoft Intune Management Extension', 'MicrosoftWindows.Client.WebExperience', 'Microsoft Edge Update', 'Microsoft Edge WebView2 Runtime', 'Teams Machine-Wide Installer', 'Microsoft Update Health Tools' )

  • Sort the application count from low to high. Low is interesting because they are probably unmanaged applications that you need to check.
  • In the end, you can input an Application name, and it will output the devices that have that application installed. The result is a tabled output that shows the file version and device count per application.
  • 6. Using the PowerShell Script

    The following permissions are needed for the app registration. You can find more info on app registrations on this Microsoft Learn page.

    Here are some quick steps to help you out with this:

    1. Go to portal.azure.com > App Registrations > New Registration
    2. Choose a name > choose accounts in this organizational directory only > click register
    3. API permissions > add the following permissions -> Microsoft Graph > Application permission
    4. Create a client secret.

    Explaining the script:

    Setup your authentication for the app registration. Replace ClientId, TenantId, Clientsecret with your values.

    #Region Authentication to Azure Application // Get token to authenticate to Azure AD $authparams = @ < ClientId = 'c9eb62ce-b748-4a6f-8ccf-21f347cd1fd96' TenantId = '44647b32-d6c6-43e9-a136-dcbaa396dc973' ClientSecret = (ConvertTo-SecureString 'YourClientSecretHere' -AsPlainText -Force ) >$auth = Get-MsalToken @authParams #Set Access token variable for use when making API calls $AccessToken = $Auth.AccessToken #endregion
    Initialize your variables and arrays:
    #Region initialize arrays/variables $allAps = @() $alldevices = @() #Apps that need to be extra filtered out and that cannot be patched $excludedAps = @( 'Microsoft Intune Management Extension', 'MicrosoftWindows.Client.WebExperience', 'Microsoft Edge Update', 'Microsoft Edge WebView2 Runtime', 'Teams Machine-Wide Installer', 'Microsoft Update Health Tools' ) #endregion

    Apps that are not useful to discover are listed in the $excludedAps array. You can add some more if you want.

    #Apps that need to be extra filtered out and that cannot be patched $excludedAps = @( 'Microsoft Intune Management Extension', 'MicrosoftWindows.Client.WebExperience', 'Microsoft Edge Update', 'Microsoft Edge WebView2 Runtime', 'Teams Machine-Wide Installer', 'Microsoft Update Health Tools' ) #endregion

    Now let’s form the graph call: https://graph.microsoft.com/beta/deviceManagement/detectedApps While scripting this graph call, I learned a new Powershell technique called splatting. (“Splatting makes your commands shorter and easier to read. You can reuse the splatting values in different command calls and use splatting to pass parameter values from the $PSBoundParameters automatic variable to other scripts and functions.”) You can find more info about Powershell splatting on this Microsoft Learn page. We now get the data from the graph with

    $discoveredApps = Invoke-RestMethod @paramApps

    And get those apps that are discovered in your tenant. This command also taught me to use an @odata.nextLink‘. The problem you will encounter is that you can only query 50 apps per request. So ‘@odata.nextLink’ allows me to loop through the discovery of more than 50 apps at one time.

    '@odata.nextLink'#Region Form the graph call $URLupn = 'https://graph.microsoft.com/beta/deviceManagement/detectedApps?$filter=&$top=50' $paramApps = @ < Headers = @< "Content-Type" = "application/json" "Authorization" = "Bearer $($AccessToken)" >Method = "GET" URI = $URLupn ErrorAction = "SilentlyContinue" > #endregion#Region Get all apps with paging trough the graph call $discoveredApps = Invoke-RestMethod @paramApps $discoveredAppsNextLink = $discoveredApps.'@odata.nextLink' $allAps += $discoveredApps.value #paging while ($discoveredAppsNextLink) < $discoveredApps = Invoke-RestMethod -Uri $discoveredAppsNextLink -Headers $paramApps.Headers -Method Get $discoveredAppsNextLink = $discoveredApps.'@odata.nextLink' $allAps += $discoveredApps.value >#endregion

    Next, we count the apps we discovered and filter out the ones we want to exclude.

    #Region Count apps we have discovered that are not Microsoft Store apps and not in excluded $exludedAps array Write-Host "We discovered $(($allaps | Where-Object ).count) non Microsoft apps that can be managed" #endregion

    Then we will ask you to output the list and filter Microsoft Apps to clean up your list even more. I recommend filtering out those results because they are installed on every Windows device.

    #Region Ask for list of all apps Write-Host "You want a list of all apps? (y/n)" $answer = Read-Host if ($answer -eq "y") < write-host "Do you want to filter Microsoft apps? (y/n)" $answer = Read-Host if ($answer -eq "y") < $allApsNoMicrosoftStore = $allAps | Where-Object < $_.displayName -notlike 'Microsoft.*' -and $_.displayName -notin $excludedAps >$allApsNoMicrosoftStore | Sort-Object -Property deviceCount | Format-Table -AutoSize $listApps = $allApsNoMicrosoftStore > else < $allAps | Sort-Object -Property deviceCount | Format-Table -AutoSize $listApps = $allAps >> #endregion

    Finally, you can type a display name of a specific app in the list and get the devices that have that application installed.

    #Region Ask for an app and show the devices that have this app installed Write-Host "Do you want to see the devices that have a specific app installed? (y/n)" $answer = Read-Host if ($answer -eq "y") < Write-Host 'Please chose an app displayName:' $appDisplayname = Read-Host $app = $listApps | Where-Object < $_.displayName -eq $appDisplayname >$appID = $app.id $URL = "https://graph.microsoft.com/beta/deviceManagement/detectedApps('$appID')/managedDevices?$filter=&$top=20" $paramApps = @ < Headers = @< "Content-Type" = "application/json" "Authorization" = "Bearer $($AccessToken)" >Method = "GET" URI = $URL ErrorAction = "SilentlyContinue" > #Get for a specific app all the devices $discoverDevices = Invoke-RestMethod @paramApps $discoverDevicesNextlink = $discoverdevices.'@odata.nextLink' $allDevices += $discoverDevices.value #need to page trough the results while ($discoverDevicesNextlink) < $discoverDevices = Invoke-RestMethod -Uri $discoverDevicesNextlink -Headers $paramApps.Headers -Method Get $discoverDevicesNextlink = $discoverDevices.'@odata.nextlink' $allDevices += $discoverDevices.value >foreach ($device in $allDevices) < $devicename = $device.deviceName write-host "The Application $appDisplayname is found in $devicename" >> else < Write-Host "Ok, bye!" >#endregion

    7. Get control back over your tenant

    You got your results now. You detected probably a few apps that we should investigate, patch or block.

    You can find the complete script on my Github page.

    Euh..Uh, block or remove applications…How?

    It’s possible to uninstall unwanted unmanaged applications with our products automatically, and our products are, of course 😉 completely amazing. But there is still a time gap for the detection to kick in, so the app that you are trying to block can still be used for a short period before it uninstalls. Remember what I said in my blog that the app could take up to 7 days to appear on the aggregated discovery application list.

    I want total control!

    If you want to pre-emptively block unwanted unmanaged applications, you will probably want to look into Applocker and Windows defender application control.

    Applocker is older, Windows Defender application control is newer, WDAC(Windows Defender Application Control) recently also got a managed installer for Intune that is worth trying out.

    8. Summary and Footnotes

    Some additional peripheral information you may find interesting:

    A. The IME Log

    The IME (Intune Management Extension) is the agent in Intune that handles win32 app installation, and App Discovery. (Yes, Intune has an AGENT!). It has a log that can be very useful for learning and troubleshooting. You can often look into the intune management extension log for more details. The file path for the log is: %programdata%MicrosoftIntuneManagementExtensionlogsIntuneManagementExtension.log. It is key source of troubleshooting information for things related to win32 app installations; including app installation successes and failures; application detection status, and script failures.

    There also recently seems to be a new file in that folder that appears to collect the win32app inventory in a separate log file. The reason for that is still unknown.

    B. Application Detection Rules

    One key aspect of Application Inventory features across many of the Management Products is how applications are detected. This is often called “Application Detection rules”. Basically a way to look for breadcrumbs or fingerprints that an applications leaves that are indicative that the application is installed. The following multiple detection rules, methods are at your disposal:

    Intune for now is more basic with its Intune Detection Rules. It’s most versatile detection method is trough Powershell scripting. For example you can use Powershell in features like proactive remediations to script just about anything by building customer detection, and checking the exit code or stdout detection state.