Browsed by
Category: Client Side Compute

Actual Installed Application Reporting from SCCM

Actual Installed Application Reporting from SCCM

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\|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.

  1. Browse to \\<SCCMSERVER>\SMS_<SITECODE>\inboxes\clifiles.src\hinv
  2. Copy Configuration.mof to c:\data\mofs
  3. Open Configuration.mof with Notepad
  4. 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;
    }

  5. 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;
    }

  6. Close and Save Configuration.mof
  7. In an elevated command prompt run the following commands:
    1. Mofcomp c:\data\configuration.mof
      the result should look like this:
  8. Browse back to: \\<SCCMSERVER>\SMS_<SITECODE>\inboxes\clifiles.src\hinv and rename the existing file to match this naming convention:
    1. configurationYYYYMMDD.mof
  9. Copy configuration.mof from c:\data to \\<SCCMSERVER>\SMS_<SITECODE>\inboxes\clifiles.src\hinv
  10. Open SCCM 2012 Console and browse Administration à Client Settings
  11. Right click on Default Client Settings and select Properties
  12. Select Set Classes under the Hardware Inventory tab on the Default Settings Form
  13. Select Export on the Hardware Inventory Classes form
  14. Save the exported file to c:\data\export.mof
  15. Open C:\data\export.mof in notepad
  16. 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;
    };

  17. 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;
    };
  18. Close and Save c:\data\export.mof
  19. On the Hardware Inventory Classes form select Import
  20. Review the Message to ensure everything is correct, and Select Import.
  21. 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=(GUID:) code=0 (1473 stored procs in .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

How to update BIOS on laptops that have Bitlocker Encrypted drives.

How to update BIOS on laptops that have Bitlocker Encrypted drives.

Download Script from here(updateBios)

Completing an unattended update of computers BIOS was something I never thought I would be championing but with hardware vendors now releasing updates every other month which include performance and stability fixes we have been forced into investigating how to complete this.

So my initial plan was to finally get SCUP up and running and then utilise the BIOS updates that are included in the HP pack. This was great for our desktops and laptops that a.) didn’t have a BIOS password, and b.) didn’t have BitLocker enabled.

So from here we needed to come up with a different route, that could handle bother the BIOS password and BitLocker, what also needed to be taking into consideration is to check to make sure the laptop has AC power connected, as most vendors have put safe guards in place to stop BIOS installing if not connected. Along with the re-enablement of BitLocker encryption after the BIOS has been updated.

So lets get into the code, for simplicity it is all written in vbs, and has been tested on Windows 7 & 8. Below is a break down of the Sub Routines & Functions.

Application Execution

sn = wscript.scriptname ' gets the script name
fn = wscript.scriptfullname 'gets the scripts full UNC Path
fp = replace(fn, "\" & sn, "") 'provides the path of all of the files.
Set objShell = WScript.CreateObject("WScript.Shell")
opt = WScript.Arguments.Item(0) 'returns the Argument that has been entered in at script calling.

if opt = "flash" then 'is the option to Flash turned on
 if power then 'is Power Attached?
  manageBDE ("disable") 'Suspend BitLocker Drive Encryption
  install (fp & "\hpqflash.exe -s -p" & fp & _
   "\pwfile.bin") 'Call the HP Flash applicaiton - Change this to match your Vendor
  writekey "SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" _
  , "ReenableBDE", fn & " encrypt" 'Create regkey to unsuspend BDE after reboot
  install ("c:\windows\system32\shutdown.exe -r -f -t" _ 
  " 600") 'Restart the computer change the timeout to match you need
 end if
elseif opt = "encrypt" then 'is the option to encrypt?
 manageBDE ("enable") 'unsuspend BDE
elseif opt = "/?" then
 wscript.echo "Usage of script, to kick off Flashing" _ 
 " call script with the argument "& chr(34) & "Flash" & chr(34)
end if

This is the code that is used to call the Sub-Routines & Functions detailed below.

Checking the AC:

'#################################################
' Function Name: power
' Outputs:
' power (boolen) = TRUE means power is plugged in,
'             FALSE means power it not plugged in
'#################################################
function power
 Set objWMIService = GetObject("winmgmts:\\.\root\wmi")
 Set colItems = objWMIService.ExecQuery("Select *" _
 " From BatteryStatus Where Voltage > 0")
 For Each objItem in colItems
  if objItem.Discharging = FALSE then
   'wscript.echo "AC plugged in"
   power = true

  else
   'wscript.echo "AC NOT plugged in"
   power = false
  end if
 Next
end function

The reason why we use Discharging rather then Charging is that we found if the Laptop was fully charged it would return that it wasn’t charging, but if the AC was plugged in, the laptop isn’t discharging (have a think about it and it makes sense).

Manage BDE (BitLocker Drive Encryption)

'#################################################
' Routine Name: manageBDE
' Inputs:
' switch (String) = options are "Enable" or "Disable"
' checks to see if BitLocker is enabled and if it is it will 
' Suspend if required. it will also report the various 
' states with different exit codes as listed in the script.
'#################################################
sub manageBDE(switch)
 dim nProtStatus, intRC
 strConnectionStr2 = "winmgmts:{impersonationLevel=impersonate," _
    "authenticationLevel=pktPrivacy}!root\cimv2\Security\" _
    "MicrosoftVolumeEncryption"
 Set objWMIBDE = GetObject(strConnectionStr2)
 Set colEnVol = objWMIBDE.ExecQuery("Select * from" _
    " Win32_EncryptableVolume")
 if colEnVol.count > 0 then
  For Each objEnVol in colEnVol
   if objEnVol.DriveLetter = "C:"  then
    intRC = objEnVol.GetConversionStatus(nProtStatus)
    if switch = "disable" then
     select case nProtStatus
     case 0 'fully decrypted
      wscript.quit 663
     case 1 'fully encrypted
      objShell.Run ("c:\windows\system32\manage-bde.exe" _
      " -protectors -disable C:")
     case 2 'Encryption in Progress
      wscript.quit 665
     case 3 'Decryption in progress
      wscript.quit 666
     case 4 'Encryption Paused

     case 5 'Decryption Paused
      wscript.quit 664
     end select

    elseif switch = "enable" then
     select case nProtStatus
     case 0 'fully decrypted
      wscript.quit 663
     case 1 'fully encrypted
      objShell.Run ("c:\windows\system32\manage-bde.exe" _
      " -protectors -disable C:")
      objShell.Run ("c:\windows\system32\manage-bde.exe" _
      " -protectors -enable C:")
     case 2 'Encryption in Progress
      wscript.quit 665
     case 3 'Decryption in progress
      wscript.quit 666
     case 4 'Encryption Paused
      objShell.Run ("c:\windows\system32\manage-bde.exe" _
      " -protectors -enable C:")
     case 5 'Decryption Paused
      wscript.quit 664
     end select
    end if
   end if
  next
 end if
end sub

We ended up using the GetProvisionStatus rather then the GetEncryptionStatus option for this solution to be able to handle if the hard drive is currently in the process of being encrypted, for example when a system is recently been built. When using GetEncryptionStatus WMI reported correctly that BDE was enabled, it just didn’t report that it was currently being encrypted.

Install Sub Routine

'#################################################
' Routine Name: install
' Inputs:
' exestring (string) = command you want to call.
' this Routine will start an application and wait
' until the application has stopped before moveing
' on to the next task in the script. useful for 
'applications that close automaticly with exit code 0
'#################################################
sub install(exestring)
 running = 1
 strComputer = "."
 strCommand = exestring
 Set objWMIService = GetObject("winmgmts:" _ 
 "{impersonationLevel=impersonate}!\\" & strComputer _
 & "\root\cimv2")
 Set objProcess = objWMIService.Get("Win32_Process")
 errReturn = objProcess.Create(strCommand, null, null, _
 intProcessID)
 If errReturn = 0 Then
  'wscript.echo exestring & " : " & intprocessid
  do while running = 1
   Set objProcess = objWMIService.execquery("select *" _
   " from Win32_Process")
   for each objproc in objprocess
    if objproc.processid = intprocessid then
     running = 1
     exit for
    else
     running = 0
    end if
   next
  loop
 Else
 End If
end sub

This routine is something I wrote a few years back to deal with those awesome applications that report exit code 0 immediately on execution for the application. (contact me if you need a routine to handle an executable inside an executable….)

Write RegKey

'#################################################
' Routine Name: WriteKey
' Inputs:
' key (string) = this is the path to the key to update
' vn (string) = this is the new Value name
' v (string) = this is the new Value
' Creates a DWORD value at the KEY path.
'#################################################
sub writekey(key,vn,v) 
 const HKEY_LOCAL_MACHINE = &H80000002
 Set oReg=GetObject("winmgmts:" _
 "{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
 oReg.SetStringValue HKEY_LOCAL_MACHINE,key,vn,v
end sub

This writes a DWORD into the registry in this script we use it to handle the re-enablement of BDE after a restart.

One thing to note is that to complete the HP Bios update from the command line you are required to create a pwfile.bin file by using this application http://ftp.hp.com/pub/caps-softpaq/cmit/softpaq/sp62065.exe when installing the application it displays as HP ElitePad 900 from my experience this file will work for all HP Laptops that utilise the HPQFlash application

I think that pretty much covers it off, we have successfully tried this on multiple HP laptop models to great success.

Good Luck

Steve