Having worked on SCCM for many years now I have lost count of how many times I have been asked, “Since you guys have an agent on all of the computers can I get you to provide me a list of ALL software that is installed on all of the computers?” after which there is a long draw out discussion explaining that when SCCM returns the information from Programs and Features it doesn’t discriminate between System install applications which are not visible in Programs and Features in which you will typically end up with double the amount of applications per computer, then what actually appears in Programs and Features. To this end I’ve had some spare time recently, so I have done some investigating, and found that with a simple update of the MOF we can typically get within +/-98% accuracy of only the applications that appear in Programs and Features appearing in the SCCM reports, which for me it a huge win.
As we know the Registry is the master of what appears in the Programs and Features list, so for example you can change the Display Name of applications in Programs and Features by changing the following registry Item:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\<Application Identifier>|DisplayName
This Key also includes such things as the Publisher, Version numbers, and Uninstallation strings. The Item’s which we are interested in for this solution are the following:
SystemComponent
ParentDisplayName
The SystemComponent item will appear as 1 if the application is to be hidden from Programs and Features, so for example the SCCM Client will have SystemComponent = 1 which appears to hide it from Programs and Features, if this item is not defined it is assumed to be a 0 (This is also used by Microsoft Office to hide each of the components from Programs and Features).
Whereas the ParentDisplayName item is used for patches to applications and Operating system, an example of can be seen in Windows XP where you could select the check box to hide updates. When not referring to the install Operating System it will refer to the DisplayName of the parent, for example “Microsoft .NET Framework 4 Client Profile” will have all of the updates that are dependent upon it.
Now we have explained what we are doing and why let’s step through the process of actually capturing the details in SCCM.
- Browse to \\<SCCMSERVER>\SMS_<SITECODE>\inboxes\clifiles.src\hinv
- Copy Configuration.mof to c:\data\mofs
- Open Configuration.mof with Notepad
- Find the Follow:
class Win32Reg_AddRemovePrograms
{
[key]
string ProdID;
[PropertyContext(“DisplayName”)]
string DisplayName;
[PropertyContext(“InstallDate”)]
string InstallDate;
[PropertyContext(“Publisher”) ]
string Publisher;
[PropertyContext(“DisplayVersion”)]
string Version;
}
and Replace with the following:
class Win32Reg_AddRemovePrograms
{
[key]
string ProdID;
[PropertyContext(“DisplayName”)]
string DisplayName;
[PropertyContext(“InstallDate”)]
string InstallDate;
[PropertyContext(“Publisher”) ]
string Publisher;
[PropertyContext(“DisplayVersion”)]
string Version;
[PropertyContext(“SystemComponent”)]
string SystemComponent;
[PropertyContext(“ParentDisplayName”)]
string ParentDisplayName;
}
- Then find the following directly below:
class Win32Reg_AddRemovePrograms64
{
[key]
string ProdID;
[PropertyContext(“DisplayName”)]
string DisplayName;
[PropertyContext(“InstallDate”)]
string InstallDate;
[PropertyContext(“Publisher”) ]
string Publisher;
[PropertyContext(“DisplayVersion”)]
string Version;
}
and Replace with the following:
class Win32Reg_AddRemovePrograms64
{
[key]
string ProdID;
[PropertyContext(“DisplayName”)]
string DisplayName;
[PropertyContext(“InstallDate”)]
string InstallDate;
[PropertyContext(“Publisher”) ]
string Publisher;
[PropertyContext(“DisplayVersion”)]
string Version;
[PropertyContext(“SystemComponent”)]
string SystemComponent;
[PropertyContext(“ParentDisplayName”)]
string ParentDisplayName;
}
- Close and Save Configuration.mof
-
In an elevated command prompt run the following commands:
Mofcomp c:\data\configuration.mof
the result should look like this:
-
- configurationYYYYMMDD.mof
- Copy configuration.mof from c:\data to \\<SCCMSERVER>\SMS_<SITECODE>\inboxes\clifiles.src\hinv
- Open SCCM 2012 Console and browse Administration à Client Settings
- Right click on Default Client Settings and select Properties
- Select Set Classes under the Hardware Inventory tab on the Default Settings Form
- Select Export on the Hardware Inventory Classes form
- Save the exported file to c:\data\export.mof
- Open C:\data\export.mof in notepad
- Find the following:
class Win32Reg_AddRemovePrograms : SMS_Class_Template
{
[SMS_Report (TRUE), key ]
string ProdID;
[SMS_Report (TRUE) ]
string DisplayName;
[SMS_Report (TRUE) ]
string InstallDate;
[SMS_Report (TRUE) ]
string Publisher;
[SMS_Report (TRUE) ]
string Version;
};
and replace with:
class Win32Reg_AddRemovePrograms : SMS_Class_Template
{
[SMS_Report (TRUE), key ]
string ProdID;
[SMS_Report (TRUE) ]
string DisplayName;
[SMS_Report (TRUE) ]
string InstallDate;
[SMS_Report (TRUE) ]
string Publisher;
[SMS_Report (TRUE) ]
string Version;
[SMS_Report (TRUE) ]
string SystemComponent;
[SMS_Report (TRUE) ]
string ParentDisplayName;
};
- Then find the following directly below:
class Win32Reg_AddRemovePrograms64 : SMS_Class_Template
{
[SMS_Report (TRUE), key ]
string ProdID;
[SMS_Report (TRUE) ]
string DisplayName;
[SMS_Report (TRUE) ]
string InstallDate;
[SMS_Report (TRUE) ]
string Publisher;
[SMS_Report (TRUE) ]
string Version;
};
and replace with:
class Win32Reg_AddRemovePrograms64 : SMS_Class_Template
{
[SMS_Report (TRUE), key ]
string ProdID;
[SMS_Report (TRUE) ]
string DisplayName;
[SMS_Report (TRUE) ]
string InstallDate;
[SMS_Report (TRUE) ]
string Publisher;
[SMS_Report (TRUE) ]
string Version;
[SMS_Report (TRUE) ]
string SystemComponent;
[SMS_Report (TRUE) ]
string ParentDisplayName;
};
- Close and Save c:\data\export.mof
- On the Hardware Inventory Classes form select Import
- Review the Message to ensure everything is correct, and Select Import.
- On the Hardware Inventory Classes form search for Win32reg_addremoveprograms and ensure that SystemComponent & ParentDisplayName are selected, then click on OK, and OK, back to the SCCM Console
To test ensure that you have completed a Policy refresh on a targeted computer, wait a few minutes then trigger a hardware inventory, you can see if the policy has updated by reviewing the InventoryAgent.log on the client computer and filter with CMTrace for Win32reg_addremoveprograms, you should see something like this:
And on the server side you can confirm the new inventory has been loaded by reviewing the DataLdr.log file on the Site server, looking for the line
“Done: Machine=<Computername>(GUID:<ComputerGUID>) code=0 (1473 stored procs in <InventoryMIF>.MIF)”
to ensure that the data has been loaded into the SQL database.
From here we can create a report using the following example SQL query:
Count of all applications installed on all computers:
SELECT
*
FROM
(
SELECT
DISTINCT
v_GS_ADD_REMOVE_PROGRAMS_64.DisplayName0 AS “Product Name”, v_GS_ADD_REMOVE_PROGRAMS_64.Publisher0 AS “Publisher”,
count(*)
as
‘Install count’
FROM v_GS_ADD_REMOVE_PROGRAMS_64
INNER
JOIN v_R_System_Valid ON v_R_System_Valid.ResourceID = v_GS_ADD_REMOVE_PROGRAMS_64.ResourceID
JOIN v_GS_OPERATING_SYSTEM ON v_GS_ADD_REMOVE_PROGRAMS_64.ResourceID = v_GS_OPERATING_SYSTEM.ResourceID
WHERE v_GS_ADD_REMOVE_PROGRAMS_64.SystemComponent0 is
null
and v_GS_ADD_REMOVE_PROGRAMS_64.parentdisplayname0 is
null
and
not v_GS_ADD_REMOVE_PROGRAMS_64.Displayname0 is
null
group
by v_GS_ADD_REMOVE_PROGRAMS_64.DisplayName0, v_GS_ADD_REMOVE_PROGRAMS_64.Publisher0
UNION ALL
(
SELECT
DISTINCT
v_GS_ADD_REMOVE_PROGRAMS.DisplayName0 AS “Product Name”, v_GS_ADD_REMOVE_PROGRAMS.Publisher0 AS “Publisher”,count(*)
as
‘Install count’
FROM v_GS_ADD_REMOVE_PROGRAMS
INNER
JOIN v_R_System_Valid ON v_R_System_Valid.ResourceID = v_GS_ADD_REMOVE_PROGRAMS.ResourceID
JOIN v_GS_OPERATING_SYSTEM ON v_GS_ADD_REMOVE_PROGRAMS.ResourceID = v_GS_OPERATING_SYSTEM.ResourceID
WHERE v_GS_ADD_REMOVE_PROGRAMS.SystemComponent0 is
null
and v_GS_ADD_REMOVE_PROGRAMS.parentdisplayname0 is
null
and
not v_GS_ADD_REMOVE_PROGRAMS.Displayname0 is
null
group
by v_GS_ADD_REMOVE_PROGRAMS.DisplayName0, v_GS_ADD_REMOVE_PROGRAMS.Publisher0
))
AS u
ORDER
BY “Product Name”, Publisher
Installed applications on a Single Computer:
DECLARE @compname VARCHAR(MAX) = ‘Computer Name’
SELECT
*
FROM
(
SELECT
DISTINCT
v_GS_ADD_REMOVE_PROGRAMS_64.DisplayName0 AS “Product Name”, v_GS_ADD_REMOVE_PROGRAMS_64.Publisher0 AS “Publisher”, v_GS_ADD_REMOVE_PROGRAMS_64.Version0 AS “Version”
FROM v_GS_ADD_REMOVE_PROGRAMS_64
INNER
JOIN v_R_System_Valid ON v_R_System_Valid.ResourceID = v_GS_ADD_REMOVE_PROGRAMS_64.ResourceID
JOIN v_GS_OPERATING_SYSTEM ON v_GS_ADD_REMOVE_PROGRAMS_64.ResourceID = v_GS_OPERATING_SYSTEM.ResourceID
WHERE v_GS_ADD_REMOVE_PROGRAMS_64.SystemComponent0 is
null
and v_GS_ADD_REMOVE_PROGRAMS_64.parentdisplayname0 is
null
and
not v_GS_ADD_REMOVE_PROGRAMS_64.Displayname0 is
null
and v_R_System_Valid.Netbios_Name0 = @compname
UNION ALL
(
SELECT
DISTINCT
v_GS_ADD_REMOVE_PROGRAMS.DisplayName0 AS “Product Name”, v_GS_ADD_REMOVE_PROGRAMS.Publisher0 AS “Publisher”, v_GS_ADD_REMOVE_PROGRAMS_64.Version0 AS “Version”
FROM v_GS_ADD_REMOVE_PROGRAMS
INNER
JOIN v_R_System_Valid ON v_R_System_Valid.ResourceID = v_GS_ADD_REMOVE_PROGRAMS.ResourceID
JOIN v_GS_OPERATING_SYSTEM ON v_GS_ADD_REMOVE_PROGRAMS.ResourceID = v_GS_OPERATING_SYSTEM.ResourceID
WHERE v_GS_ADD_REMOVE_PROGRAMS.SystemComponent0 is
null
and v_GS_ADD_REMOVE_PROGRAMS.parentdisplayname0 is
null
and
not v_GS_ADD_REMOVE_PROGRAMS.Displayname0 is
null
and v_R_System_Valid.Netbios_Name0 = @compname
))
AS u
ORDER
BY “Product Name”, Publisher
You will notice that we are not using the “v_add_remove_programs” view, this appears to be a derived view which doesn’t appear to be updated when new columns added to the Class, but we can get around this by using the Union function in SQL.
Good Luck
Steve