Intune: Software Inventory using Winget and Log Analytics

Over the last couple of weeks, I was learning about winget, setting up a private repository, creating and adding manifests and installing applications from the private reporsitory. While I will save the process for a seperate post, will discuss more about the winget client tool and inventorying using it in this post.

The winget client is a command line tool that enables users to discover, install, upgrade, remove and configure applications on Windows 10 and Windows 11 computers. This tool is the client interface to the Windows Package Manager service.

Install winget

Windows Package Manager winget command-line tool is supported on Windows 10 (1709+) and Windows 11. This is part of the App Installer and latest Windows 10/11 feature releases contain winget client by default. If you couldn’t find it installed in a device, you can install App Installer from the Microsoft Store. If it’s already installed, make sure it is updated with the latest version.

Discover Apps

The list command of the Winget tool displays a list of the applications currently installed on your computer. The list command will show apps that were installed through the Windows Package Manager as well as apps that were installed by other means. The list command will also display if an update is available for an app, and you can use the upgrade command to update the app. The list command also supports filters which can be used to limit your list query.

As winget tool supports the following types of installers, all applications of below type will be discovered.

  • EXE (with Silent and SilentWithProgress flags)
  • ZIP
  • INNO
  • NULLSOFT
  • MSI
  • WIX
  • APPX
  • MSIX
  • BURN
  • PORTABLE

Below is output from my device. As you can see the list contains all applications that are installed in my device. If an application is available in winget (not necessarily installed from winget), the source is marked accordingly. For the rest, source is null/empty.

Inventorying the Applications

Couple of days ago, I reached out to community on X (formerly twitter) to understand the requirements and from the responses, I thought below solution might solve some of the basic use cases discussed such as:

  1. Get number/list of devices per application per version
  2. Get number/list of devices where a particular version of an application is running and has an available update in winget.
  3. Get number/list of devices where required N number of applications are installed.
  4. Get number/list of devices which are missing a one or more required application.
  5. You can also add scope tag data of Intune managed devices to the Log Analytics workspace following the steps detailed in my previous post. This will give you ability to slice and dice the data based on Entra Id Groups.
  6. and many more
While Software Metering is not covered in this post, this solution can be extended further to meter software usage as well.

To inventory the applications, we will leverage winget powershell module which is still a pre-release version as on this day. Once you install the module, you can use Get-WinGetPackage cmdlet to get list of installed applications as array of objects. This command also lists, updated version of an installed app, if is available in winget public repo (no matter, how you installed the app).

Script:

For inventorying, we will upload this data from each of managed device to a specific Log Analytics workspace. You can leverage Proactive remediation to run a PowerShell script in detection only mode (without exit code 1) and schedule it once a day or any other method of your choice. Once the data gets uploaded from each device to the workspace, you can use KQL queries to do point in time analysis of the data or create intreactive Workbooks to derive insights from the data.

The data ingestion and subsequent retention in the Log Analytics workspace comes with an additional cost. Based on your requirement and available budget, you can add filters to Get-WingetPackage to trim down the amount of data collected and/or decrease the frequency of collection to once a week or few times a week rather than daily.

I have 200 apps installed in my device which equals to 73 kb of data upload in my test run. Considering 75 kb on average for 100,000 devices sending data every day for an year, cost comes around 6000 USD [ (75 kb * 100,000 * 365 / (1024 * 1024)) GB * 2.3 USD]. *Calculation as per listed price of 2.3 USD/GB East US, if you are using Pay-As-You-Go. You can reduce the cost by using reservations and/or obtaining discounts from the CSPs/Partners and/or reducing the frequency of data collection.

Configure the Log Analytics workspace id and primary/secodary key in Line #8 and #9. Uncomment the Line #10 and configure Proxy, if your devices are behind a firewall. Further configure the table name in Line #11, if you wish to. You can also write additional logic to obtain more details on each application application like install path, last accessed etc similar to how I added ComputerName property for each discovered application in Line #67 – Line #69

This script is based on Data Collector API which is deprecated but valid till 9/14/2026. I will update the script once I learn about the Log Ingestion API
<#
DISCLAIMER STARTS 
THIS SAMPLE CODE IS PROVIDED FOR THE PURPOSE OF ILLUSTRATION ONLY AND IS NOT INTENDED TO BE USED IN A PRODUCTION ENVIRONMENT. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED IS "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE."
DISCLAIMER ENDS
#>

#region LogAnalytics
$CustomerId = "" #Workspace Id
$SharedKey = "" #Input Primary or Secondary key
#$global:Proxy = "" #Use only if needed to send from a device connected to On-Premises network
$InstalledAppsTable = "installed_Apps" #Set once and do not change later.
$ComputerName = hostname

If (!$InstalledAppsTable -or !$SharedKey -or !$CustomerId){
    "Invalid Variables. Exiting..."; Exit
}

#Do Not Make Any Changes Below
$TimeStampField = ""

Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
	$xHeaders = "x-ms-date:" + $date
	$stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource

	$bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
	$keyBytes = [Convert]::FromBase64String($sharedKey)

	$sha256 = New-Object System.Security.Cryptography.HMACSHA256
	$sha256.Key = $keyBytes
	$calculatedHash = $sha256.ComputeHash($bytesToHash)
	$encodedHash = [Convert]::ToBase64String($calculatedHash)
	$authorization = 'SharedKey {0}:{1}' -f $customerId,$encodedHash
	return $authorization
}

Function Post-LogAnalyticsData ($customerId, $sharedKey, $json, $logType)
{
	$method = "POST"
	$contentType = "application/json"
	$resource = "/api/logs"
	$rfc1123date = [DateTime]::UtcNow.ToString("r")
	$contentLength = $json.Length
	$signature = Build-Signature `
		-customerId $customerId `
		-sharedKey $sharedKey `
		-date $rfc1123date `
		-contentLength $contentLength `
		-method $method `
		-contentType $contentType `
		-resource $resource
	$uri = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"

	$headers = @{
		"Authorization" = $signature;
		"Log-Type" = $logType;
		"x-ms-date" = $rfc1123date;
		"time-generated-field" = $TimeStampField;
	}
	$response = Invoke-WebRequest -Uri $uri -Proxy $global:Proxy -Method $method -ContentType $contentType -Headers $headers -Body $json -UseBasicParsing -TimeoutSec 900
	return $response.StatusCode
}
#endregion

#region Inventory Data
$PSObject = Get-WinGetPackage
$PSObject | ForEach-Object{
    $_ | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $ComputerName
}
$Json = $PSObject | ConvertTo-Json -Depth 32
$status = Post-LogAnalyticsData -customerId $CustomerId -sharedKey $SharedKey -json ([System.Text.Encoding]::UTF8.GetBytes($Json)) -logType $InstalledAppsTable
$DataUploadSize = [System.Text.Encoding]::UTF8.GetByteCount($Json)/1KB
if ($status -eq "200") {
	"Successfully posted $DataUploadSize KB to log analytics"
} Else {"log analytics post error" }

3 thoughts on “Intune: Software Inventory using Winget and Log Analytics

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.