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
The key feature discussed in this blog can be found at Intune portal > Apps > Monitor > 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:
A corporate-owned device, not personal (Only for a Win32 app).
An Intune Enrolled Device.
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:
4. Application Inventory data stored in the Client 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: –
Give you a sorted filtered list of discovered applications detected through Intune.
(You can add additional apps to filter on. Adding an application to the following array will ensure it will be excluded from the output)
$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:
Go to portal.azure.com > App Registrations > New Registration
Choose a name > choose accounts in this organizational directory only > click register
API permissions > add the following permissions -> Microsoft Graph > Application permission
Device.Read.ALL
Directory.Read.All
User.Read.All
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 {$_.displayName -notlike 'Microsoft.*' -and $_.displayName -notin $excludedAps }).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.
“Win32AppInventory”
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:
File detection rule, or folder detection
Registry detection rule
Msi detection rule (MSI product code)
WMI detection rules
- Custom detection rules
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.