Skip to main content

CM Inventory per-user installed applications

There are several examples of 'how to inventory' per-user installed applications and version, like Teams, or OneDrive, etc.  This is another one, from Benjamin Reynolds, and I've tested it a few times to make sure it worked like I thought it would.

Overall, the steps are..
1) deploy the CI inside a Baseline
2) Import the .mof and enable inventory.

That's the simple and short explanation.  For the nitty-gritty details and background story.  Per-user apps are recorded in the user context, i.e., in the hkey_current_user registry keys which are notoriously difficult to inventory.  Yes, there are methods to mount the hives, read inside, record, and inventory.  But then to me, that means you might be opening up and reading a user profile that hasn't been used in months or years--how relevant is it to know that Bob Smith, who left the company 2 years ago, still has an "old" version of Teams associated with the profile that he can't possibly be using for 2 years?  

What this routine does is multi-layered, and solves some (but not all) of the various issues I've felt "could" be encountered with inventorying per-user information.

First, a script inside the CI, running as system (not the logged in users) creates (if it doesn't already exist), a custom WMI Namespace called "CustomCMClasses".  If you so choose, you can change that if you like.  I've seen other examples using "ITLocal" as the custom namespace.  But for purposes of this blog, we'll assume you won't be modifying the name, you will leave it as 'CustomCMClasses'.  Then, it uses the well-known SIDs for "Everyone" and "Authenticated Users", to open up that namespace to allow those types of logins (aka, everyone and authenticated users) to write entries to classes in that namespace, like, for example... the per-user installed applications.

Second, a script inside the CI runs under user context.  It will first delete any records ALREADY in that class for that specific user, and then repopulate the class with anything found in the per-user uninstall information.  What's nice about that is that if this is a multi-user device, you will continue to get information for all of the users who log in.  

POTENTIAL drawback is that let's surmise that Bob Smith logged on in January, and entries were created for him.  Since then, he has not used this box or has even left the company.  There might be stale entries for Bob being inventoried... potentially for the life of the device.  That means that you will want to create reports where you filter on the 'ScriptRunTime' within the last xx days, so you don't pull stale data into reports.

--> Here <-- is the .zip containing the Configuration Item .cab to be imported into your CM Console (rename it before importing).  If you successfully import the CAB file, you don't have to do anything with the .renameAsPS1 files in the Zip.  Those .RenameAsPS1 files would be *IF* the .cab import fails, you could create your own CI, and add each of those as a Rule in the CI; one where you leave it to run as system, and the other where you carefully check the box for 'run scripts by using the logged on user credentials'.  Also in the .zip is a ImportIntoDefaulClientHardwareInventory.RenameToMof file.  Presuming you didn't change the custom class from being called 'CustomCMClasses', you would rename that to just .MOF, and import that into your Console, Administration, Client Settings, Default Client Settings, Hardware Inventory.  

Once you have the CI and a baseline including the CI created, you deploy the Baseline potentially to a small collection of devices. On those few devices, interactively do policy refreshes, and run the Baseline (from the Control Panel applet).  Note that you MIGHT have to run the baseline twice--the first time to create the initial custom class and set permissions.  Once that is done, it'll skip over that next time.  Then run the baseline again.  Then, using your favorite WMI browser (wmiexplorer?) look at customcmclasses namespace, and the class inside.  See if it contains what you expect it to contain.  If so, hooray!

If you are happy with the results, import the .mof (you'll get a view likely called v_gs_userinstalledapps0... usually). Enable inventory for that.  Deploy the baseline to the rest of your environment where you want to get user-installed apps.  Note, I would NOT have the baseline run frequently.  Perhaps every 4 days?  or every 7 days?  This information isn't mission critical, imo; it's a nice-to-have; for those (hopefully few) times when manager-types want to know what versions of Teams is out there (for example).

Sample report to get you started (once you have deployed the CI as a Baseline, tested it, and inventory is enabled):

DECLARE @60DaysAgo datetime = (GetDate()-60)
--This is so that if there are stale values from people who have left the company or are no longer using this machine, we don't see them in the reports
Select s1.netbios_name0 as 'ComputerName'
,uapps.Publisher0 as 'Publisher'
,uapps.DisplayName0 as 'DisplayName'
,uapps.Version0 as 'Version'
,uapps.InstallDate0 as 'Application Install Date If Known'
,uapps.user0 as 'Username associated with this Install'
,uapps.InstallLocation0 as 'Install Location If Known'
,uapps.UninstallString0 as 'UninstallString'
,uapps.ScriptRunTimeUTC0 as 'Date information was gathered'
from v_gs_userinstalledapps0 uapps
join v_r_system s1 on s1.resourceid=uapps.resourceid
where uapps.ScriptRunTimeUTC0 > CAST(@60DaysAgo as DATE)
order by s1.netbios_name0, uapps.publisher0, uapps.DisplayName0


SCCM, ConfigMgr

  • Created on .