Browsed by
Tag: WMI

Capturing installed Windows Features from Client OS into SCCM without touching the MOF

Capturing installed Windows Features from Client OS into SCCM without touching the MOF

Recently I was asked how to capture the Enabled Windows features from client machines, to help identify the crazy cats who have Hyper-V turned on just so they can run an extra OS or 2 on there Surface Pro 3. So this one was an interesting question in the server OS there is the Win32_ServerFeatures WMI class which is great on servers, but doesn’t exist on client OS’s. All of the information is captured in DSIM right, but how do you turn that into a WMI Class for reporting purposes.

I started off by creating a PowerShell Script with plans to run it in DCM to create the Registry keys which we have traditionally used to capture the information in SCCM, which looks like this:

$f = “”

$feature = dism /online /get-features

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

 

Push-Location

Set-Location HKLM:

if ((Test-Path .\software\clientFeatures) -eq $false){New-Item -Path .\software -name clientFeatures | out-null}

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

New-ItemProperty .\software\clientFeatures -Name $feat -Value $featset -PropertyType “string” -Force | Out-Null

$feat = “”

$featset = “”

}

}

Pop-Location

So let’s breakdown the script and explain what each of the sections are doing

$feature = dism /online /get-features

This puts the default DISM result into the $feature variable

I know your sitting there like me and wondering how you can turn that into a registry entry.

Well that’s where the next phase of the script comes in and creates a single string

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

Which steps through each of the lines in the result of the DISM command, and finds the lines which have “Feature Names” and “State” then writes into string called $f which we will use later. As we have put the , after the Feature name and the ; after the state, we can then split these in the next phase of the script. Which I will now explain what we are doing there:

Push-Location

Set-Location HKLM:

if ((Test-Path .\software\clientFeatures) -eq $false){New-Item -Path .\software -name clientFeatures | out-null}

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

New-ItemProperty .\software\clientFeatures -Name $feat -Value $featset -PropertyType “string” -Force | Out-Null

$feat = “”

$featset = “”

}

}

Pop-Location

At a high level we are connecting to HKEY_LOCAL_MACHINE and created a new Key called ClientFeatures under the Software key, then creating a new REG_SZ for each of the features with the states as the value of the key. At this point I was really happy I had all of the features in the registry in a format I could then create a MOF from, this was short lived as I noticed I had over 100 features in the client OS which would create a heartache when creating the MOF file as it would be around 300 lines of boring code. So I went to myself, self why don’t you just create the WMI class with PowerShell then you don’t have to worry about getting the MOF file right, which I responded to self with that’s a great idea I’m amazed I didn’t think of it myself. Below you will see the resulting script:

if ((Get-WmiObject -Class Win32_ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“key”,[System.Management.CimType]::String, $false)

$newClass.Properties[“key”].Qualifiers.Add(“Key”, $true)

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

$newClass.Properties.Add($feat,[System.Management.CimType]::String, $false)

$newClass.Put() | Out-Null

$feat = “”

$featset = “”

}

}

}

if ((Get-WmiObject -Class Win32_ClientFeatures).__path -eq $null){$new = $true} else {$inst = (Get-WmiObject -Class Win32_ClientFeatures).key}

if ($new) {$classinstance = $newClass.CreateInstance()}

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ($new)

{

$classinstance.$feat = $featset

$classinstance.put() | Out-Null

}

else

{

$fe = Get-WmiObject -Class Win32_clientFeatures

$fe.$feat = $featset

$fe.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

Let’s dive into the breakdown of the slab of code,

if ((Get-WmiObject -Class Win32_ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

This if statement is checking to see if the WMI Class called Win32_ClientFeatures has any properties assigned to it, if the count is less than 1 then we will go ahead and create the WMI Class and create a Property for each of the Windows Features which is this slab of code:

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“key”,[System.Management.CimType]::String, $false)

$newClass.Properties[“key”].Qualifiers.Add(“Key”, $true)

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

$newClass.Properties.Add($feat,[System.Management.CimType]::String, $false)

$newClass.Put() | Out-Null

$feat = “”

$featset = “”

}

}

}

We have also created a key called “key” inventive I know, but it works. So we now have a WMI Class with all of the properties the next step is to create a WMI instance of the state of each of the Windows Features like so:

if ((Get-WmiObject -Class Win32_ClientFeatures).__path -eq $null){$new = $true} else {$inst = (Get-WmiObject -Class Win32_ClientFeatures).key}

if ($new) {$classinstance = $newClass.CreateInstance()}

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ($new)

{

$classinstance.$feat = $featset

$classinstance.put() | Out-Null

}

else

{

$fe = Get-WmiObject -Class Win32_clientFeatures

$fe.$feat = $featset

$fe.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

The if statement is checking to see if the there is an existing instance in the WMI object and selects that instance to update, otherwise we will go ahead and create a new instance. As you can see in the script these are 2 different groups of code.

So I now have a great WMI class which I can query with PowerShell and it returns like so:

We are now sitting there really happy with our new WMI class which captures all of the Windows Features settings for the computer great. The next step is to import the new WMI class into SCCM Hardware Inventory, and this is where it became interesting, as I got this error message:

I started doing some digging into this and found that the Remote Server Administration Tools features all started the same along with the language packs, so I tweaked the script to handle this and is still didn’t correct the issue. At about this point I came to the realisation that as easy as it would be from a reporting point of view I would be next to impossible to maintain as each OS version has different features, and then you need to include the RSAT features, and Language packs, which would result of in hundreds of properties required in WMI for each OS just to capture the information.

To get around this we then need to change the WMI class to have 2 properties called Feature which we will make as the Key for the instances, and value which will contain the state for the Feature. To complete this we run the following script:

$f = “”

$new = $true

$feature = dism /online /get-features

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

if ((Get-WmiObject -Class ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“Feature”,[System.Management.CimType]::String, $false)

$newClass.Properties[“Feature”].Qualifiers.Add(“Key”, $true)

$newClass.Properties.Add(“value”,[System.Management.CimType]::String, $false)

$newClass.Put() | out-null

}

 

foreach ($r in $f.Split(“;”))

{

 

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ((Get-WmiObject -Class Win32_ClientFeatures).feature -eq $feat)

{

$fe = Get-WmiObject -query “select * from win32_clientFeatures where feature = ‘$feat‘”

$fe.value = $featset

$fe.put() | Out-Null

}

else

{

$classinstance = $newClass.CreateInstance()

$classinstance.feature = $feat

$classinstance.value = $featset

$classinstance.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

The first part is the same as above, but once we get to defining the WMI class it changes a little, now rather than stepping through each of windows features and creating a property for each feature we are now just creating 2 properties called “Feature” and “Value”, only if the WMI class doesn’t exist, as defined in the below script:

if ((Get-WmiObject -Class ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“Feature”,[System.Management.CimType]::String, $false)

$newClass.Properties[“Feature”].Qualifiers.Add(“Key”, $true)

$newClass.Properties.Add(“value”,[System.Management.CimType]::String, $false)

$newClass.Put() | out-null

}

The next step is to create a new instance for each features, defining the feature name and the state like so:

foreach ($r in $f.Split(“;”))

{

 

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ((Get-WmiObject -Class Win32_ClientFeatures).feature -eq $feat)

{

$fe = Get-WmiObject -query “select * from win32_clientFeatures where feature = ‘$feat‘”

$fe.value = $featset

$fe.put() | Out-Null

}

else

{

$classinstance = $newClass.CreateInstance()

$classinstance.feature = $feat

$classinstance.value = $featset

$classinstance.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

We are also checking to see if the instance already exists for the feature, if it does exist, we will need to update the value, otherwise we create a new class instance.

Now when we import the WMI class into the SCCM console it doesn’t throw an error, and we can see the v_GS_ClientFeatures in SQL Reporting Services as an option to report upon now, so how do I get the list of computers which have Hyper-V Enabled, well you create a SQL Report with the following query:

select rsys.Name0

from v_R_System as rsys join

v_GS_CLIENT_FEATURES as feat on rsys.ResourceID = feat.ResourceID

where Feature0 = ‘Microsoft-Hyper-V’ and

value0 = ‘enabled’

And the WMI Query for a collection looks like this:

select * from SMS_R_System inner join SMS_G_System_CLIENT_FEATURES on SMS_G_System_CLIENT_FEATURES.ResourceId = SMS_R_System.ResourceId where SMS_G_System_CLIENT_FEATURES.Feature = “Microsoft-Hyper-V” and SMS_G_System_CLIENT_FEATURES.value = “Enabled”

The DCM detection PowerShell script looks like this:

$compliance = “Compliant”

$f = “”

$feature = dism /online /get-features

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

if ((Get-WmiObject -Class win32_ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -eq 2)

{

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

$cls = Get-WmiObject -query “select * from win32_clientFeatures where feature = ‘$feat‘”

if ($cls.value -ne $featset) {$compliance = “Non-Compliant”}

$feat = “”

$featset = “”

}

}

}

else

{

$compliance = “Non-Compliant”

}

$compliance

And the DCM remediation PowerShell script looks like this:

$f = “”

$new = $true

$feature = dism /online /get-features

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

if ((Get-WmiObject -Class ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“Feature”,[System.Management.CimType]::String, $false)

$newClass.Properties[“Feature”].Qualifiers.Add(“Key”, $true)

$newClass.Properties.Add(“value”,[System.Management.CimType]::String, $false)

$newClass.Put() | out-null

}

 

foreach ($r in $f.Split(“;”))

{

 

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ((Get-WmiObject -Class Win32_ClientFeatures).feature -eq $feat)

{

$fe = Get-WmiObject -query “select * from win32_clientFeatures where feature = ‘$feat‘”

$fe.value = $featset

$fe.put() | Out-Null

}

else

{

$classinstance = $newClass.CreateInstance()

$classinstance.feature = $feat

$classinstance.value = $featset

$classinstance.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

$compliance = “Compliant”

$compliance

For the DCM you will need to do a detection on a string that equals “Compliant” and remediate if it not.

As an recap of the whole blog, you can see there is a couple of ways to capture the information, depending upon the amount of properties it might make sense to have a single WMI instance in the WMI class, but in other cases it might make sense to have a large number of WMI instances in the WMI class, each have their advantages and disadvantages for the methods. The nice part of this solution is we don’t need to worry about making any changes to the MOF which makes life so much easier.

Good Luck

Steve

WMI and PowerShell v4 a match made in heaven

WMI and PowerShell v4 a match made in heaven

Natively Windows has a tool to access and view the WMI Namespaces, called WBEMtest.exe. In my opinion it can be quite clunky to use, especially when you are starting to learn WMI. There are multiple WMI Explorer’s out there that do a much better job of presenting the NameSpaces, Classes, Properties & Methods (personally, I use WMI Explorer from Sapien).

Another option is to use Window’s PowerShell to access the WMI objects.

You ask, why would I move from a GUI to a CLI for something like this? Answer: Because it is quicker, especially if it is something you query, quite often you can just simply save the query. It’s hard to save where you need to go in a GUI.

For this blog I’m going to discuss the 2 primary PowerShell cmdlets for WMI which are Get-WmiObject and Invoke-WmiMethod.

Get-WmiObject is used to get a WMIObject be it a NameSpace or a Class. You can also run a WQL Query using this same CmdLet.

An example would be Get-WmiObject -Class win32_bios which will return the following:

Again I hear well that’s great Steve you know to get that information you need to grab the win32_bios class, how the heck do I find what I’m looking for in PowerShell?

Well since PowerShell 3.0 there is a great parameter added to the Out-GridView cmdlet called PassThru. What this allows us to do is select the result we want from the gridview, and do something with it. As a script you could do something like this;

$wmi = Get-WmiObject -List | Out-GridView -passthru
Get-WmiObject -Class $wmi.Name 

 When we run the Script a box will appear like below, in the filter box you can treat this like a search;

When we search for Bios the list will be filtered down to only objects which contain BIOS, we can then select Win32_bios and click ok which places the whole Win32_BIOS object as $WMI;

And the final line is to get the WMI object we have selected;

Invoke-WmiMethod is used to invoke a method that is attached to a Class or Namespace, this one has a little bit more of a niche use case, but is very powerful.

For example we can use the following line to start an application like WBEMTest.exe;

Invoke-WmiMethod -class win32_process -name create -ArgumentList “wbemtest.exe”

As you can see WBEMTest will then appear on the screen.

You can find methods out the same way as you find classes with the following line of code;

$wmi = Get-WmiObject -List | Out-GridView -passthru
Get-WmiObject -Class $wmi.Name | Get-Member -MemberType Methods

 

So the same box as earlier will appear when we run the above lines;


In this example we have searched for Win32_process as it has Methods attached as you can see in the list

Once you select win32_process and Ok you will get the below results.

Ok so we have now used the Get-WmiObject and Invoke-WmiMethod
commandlets separately, let’s join them together and show how there is 2 ways to invoke the methods.

So in this example we are going to start WBEMTest.exe again and then close it by using the Win32_process Methods, the script looks like this;

$wbem = Invoke-WmiMethod -class win32_process -name create -ArgumentList
“wbemtest.exe”
$procid = $wbem.ProcessID
$res = Get-WmiObject -Query “Select * from win32_process where ProcessID = $procid
$res
$res.Terminate() 

 

So as you can see we are defining $wbem
with the result of Invoke-WmiMethod
which is creating a process for WBEMTest.exe, this allows us to capture the ProcessID which we then define as $procid on the next line.

From here we are querying the WMIObject Win32_Process where ProcessID = $procid, which will return the results of the process we just created into the $res variable.

We then return the $res
variable onto the screen to show that the script is doing something.

And the last line we actually close the WBEMtext.exe process we created in the first line. Which as you can see from the list of methods for Win32_process Terminate exists there.

Good Luck

Steve

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

Reporting on Shares with SCCM 2012

Reporting on Shares with SCCM 2012

To follow in the same vain as my last post for reporting on usage of logon scripts, we now need to find which shares have been removed since the last time the logon script was updated. To do this is reasonably easy to complete by connecting to each share and checking to see if they still exist, until you have a list of over 200 shares to check. So let’s take a look at what we can do with SCCM to handle this.

First task would be to identify if we can utilise an existing WMI class to query, thankfully there is the Win32_Share class which will return the information back to SCCM. In addition to this we can enable this class via selecting it in the Hardware Inventory for the client settings.

RSpic1

Once this has been enabled and some of the computers have reported back you can use the below query to your database. To present the data back in a usable form I have joined the share path.

RSpic2

 

Now we have the server names tied to the share paths, from this we can grab the shares from the logon script and do a query like this

 

select ‘\\’ + rsys.name0 + ‘\’ + sha.name0
from v_GS_SHARE as sha
Join v_r_system as rsys on sha.resourceID = rsys.resourceID
where ‘\\’ + rsys.name0 + ‘\’ + sha.name0 in (‘\\cm7\admin4’,
‘\\or7\c$’)

This will bring back a nice list we can then copy into excel and do a simple countif on the 2 columns of share paths.

The problem we have is when it comes to Clustered shares, as since Server 2000 Microsoft hasn’t represented the shares for Clusters with the Win32_Share class. But from Server 2008 R2 we have be able to query the Win32_Clustershare. The process to add this class to the hardware inventory in SCCM is a little bit different as you need to add the class to the list,

To do this open the “default client settings” policy and browse the hardware inventory, then select Classes to inventory. once this appears select Add and then on the screen that appears hit the connect button. Which will bring a prompt like this. (Yes Microsoft forgot to select the password field as hidden). Make sure you select Recursive otherwise most classes wont appear.

RSpic3

Once you select connect, search the win32_clustershare class and enable it.

RSpic4

Then we can select the Fields that we want to inventory either in the Default policy to apply to all computers in the environment, or apply to a new/existing client settings policy.

RSpic5

Once we have deployed the Client Settings to the cluster we can return the data with the below query, as you can see the default fields are structured a little bit differently to the win32_share class. The class has both the server name and the Share Path which saves creating a constructed string.

RSpic6

Much like the Win32_Share class we can do a where in query which would look something like this:

select ServerName0,
Path0,
Name0
from v_GS_CLUSTER_SHARE
where name0 in (‘\\svr354\ab’,
‘\\svr354\acc’)

From here you have a couple of options on how to move forward, for example you can create both of these queries as data sources in an Excel Workbook with the 3rd sheet containing a list of all your shares from your logon script, from which you can derive the exists of each of the shares from either the win32_share or the Win32_clustershare data sources.

Cheers

Steve

UPDATED: missing image.