Howdy folks, I hope everyone is doing well.
I’ve got a happy announcement for today. I’ve officially posted a Beta version 1.3 of the Windows Endpoint inventory script which adds support for Appx package installs.
To be clear, this is still in Beta. I will likely be tweaking the workbook and collection script further, but I see no reason not to go ahead and let folks play with it at this point.
In this article, I will cover…
- The new Data Collected
- The new Workbook Beta V2
- Deploying the Beta
- Upgrading an Existing Script/Workbook
- Conclusion
The new Data Collected:
You can find the Beta V1.3 detection script on my GitHub here.
As mentioned above, the new beta version of the script was created to collect information about Appx packages installed on devices, which the current V1.2 is entirely blind to. For example, Microsoft Teams is no longer an MSI piece of software registering to the HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall (or the 32-bit equivalent), the place where we previously pulled all app data from, and is instead a Appx package installed on the machine. So, we need to now collect additional applications in this new way such that we can truly see what all is out there.
What can we already easily see about Appx on a machine?
If you run Get-AppxPackage -allusers, you’ll find that this provides a significant amount of information on its own. However, some of it is rather unfriendly, especially from a data collection standpoint. Publishers are stored in lengthy “CN=Blah, O=Blah, L=Blah” format, or sometimes just garble, the users who actually have the app installed (as Appx’s are per user) are stored as SIDs, and apps can appear multiple times due to being under different users. What a mess! Luckily, PowerShell is amazing, and this can largely (more on that later) be improved for the sake of collection.
So, in the end, we collect the following new information…
- App name
Example: MSTeams - App version
Example: 26005.213.4315.4117 - App publisher
I clean this up as best I can, eliminating the CN=blah and just bringing it down to the company name. However, in looking at my handful of test devices and their corresponding data, this field sometimes has some complete garble in it.
Example: Microsoft Corporation - App type
This is a static field in the sense that for all Appx packages, the value is “Appx”. For classic apps (think an MSI or EXE registered to the uninstall registry), this field is “Classic.” This lets us separate classic and modern apps quickly and easily in queries. - AppUninstallString
This is the package’s full name, which is used in uninstall commands.
Example: MSTeams_26005.213.4315.4117_x64__8wekyb3d8bbwe - AppInstallLocation
This is the folder where the app’s content truly resides on the machine. This is helpful for anything from lining up a vulnerability to a given app (confirming relation based on detection location), as well as something like re-registering an app to a given user which requires knowing this path. - Architecture
This specifies the processor architectures supported by a package. As I have been playing with ARM devices, this is a very interesting field to me as it’s one of the few places I’ve found a more concrete feeling “is it supported?” answer. The possible values according to Microsoft are X86, ARM, X64, NEUTRAL, and ARM64.
That said… I have an Arm64 device, it’s relatively stock, but it has 30~ Appx packages of the type “X86” or “X64”, which would suggest they are not supported… Except one of them is my MSTeams, and I doubt Microsoft is going to tell me that Teams is not supported on Arm64. The rest are largely common requirements like .Net, Windows App Runtimes, VClibs, etc, or other smaller apps like Windows Speech, etc. So, I guess this is another scenario of “not technically supported (or at least this field is outdated), except it probably works, and people are probably doing it just fine” that I have come to love and know when it comes to the Arm64 topic. - InstalledUsers
This is a list of the users who actually have the app installed on their accounts. Yes, Appx pacakges are installed in folders that are not user-specific. But, they can be registered, not registered, or unregistered, by users individually. This field is stored as a CSV list which, after lots of playing around, I think gets us the data we need without dramatically overcomplicating this (an array just doesn’t make sense).
Note that the installed users are stored as a SID. We are using PowerShell to translate that SID back into a common name. Sometimes, I’m sure, that translation will fail.
Examples:
None (yeah, that’s an option)
Max (your username)
System (Yes, they register to the system account often)
Local Services
A SID (when this happens, the machine could not locate a translation of the SID. You can look this up though https://intunediff.com/tools/entra-id-converter)
The new Workbook Beta V2:
You can find the new Workbook Beta on my GitHub here.
In order to display this new data, the workbook has been lightly updated, largely by just adding these additional columns as columns to display.
For example, here is the full app list showing the various versions of Teams. Note that Appx packages NEVER have an install date (I don’t trust folder/file modification dates or creation dates as that is not related to registration date, nor do I see dates in the JSONs and XMLs in the install folders of Appx packages to help give this info).

Outside of simply displaying this additional data, the workbook is almost entirely the same. That said, this new workbook has filters in place to ensure the data it displays is only data uploaded with the version tag of 2 that comes from V1.3 of the script (I’d just rather work with whole numbers in my KQL).
Additionally, it is expecting the table name to be AppInventoryV2_CL, not the old “AppInventory_CL”, again, just to help separate the data and versions.
Deploying the Beta:
If you wish to deploy this, simply follow the existing articles under the “Log Analytics for Windows Endpoints” header here, instead using the script and workbook linked in this article.
The only thing you need to do differently is, assuming you want to make as few modifications as possible, name the table AppInventoryV2_CL instead of AppInventory_CL. Otherwise, name it whatever you want, and just plan to CTRL+F and replace that table name in the workbook JSON so it looks to the right location.
Upgrading an Existing Script/Workbook:
I would not recommend taking an existing table, especially if it’s in production, and upgrading it, especially while this is still in Beta. But, technically, you can.
This will require modifying the existing DCR (Data Collection Rule) to expect/handle the new fields, modifying the table to have columns for those new fields, and connecting the new workbook to your existing table name (again, CTRL+F and replace AppInventoryV2_CL with whatever your table name is).
This process is all detailed in a prior article here. The process is a bit of a manual pain as it requires using PowerShell to upload a new and hand-modified JSON that contains the new fields. It looks like, per this same linked article, this process still does not have a GUI.
Conclusion:
I hope folks find this useful. I am very open to any feedback you have for improvements and alterations.
Thanks and happy logging!
Disclaimer
The following is the disclaimer that applies to all scripts, functions, one-liners, setup examples, documentation, etc. This disclaimer supersedes any disclaimer included in any script, function, one-liner, article, post, etc.
You running this script/function or following the setup example(s) means you will not blame the author(s) if this breaks your stuff. This script/function/setup-example is provided AS IS without warranty of any kind. Author(s) disclaim all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall author(s) be held liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the script or documentation. Neither this script/function/example/documentation, nor any part of it other than those parts that are explicitly copied from others, may be republished without author(s) express written permission. Author(s) retain the right to alter this disclaimer at any time.
It is entirely up to you and/or your business to understand and evaluate the full direct and indirect consequences of using one of these examples or following this documentation.
The latest version of this disclaimer can be found at: https://azuretothemax.net/disclaimer/
