13 January, 2013

Invoke Inventory - SCCM

Continuing one of the SCCM related posts from last year where I showed a way to invoke Machine Policy Retrieval & Evaluation on SCCM clients remotely. Here is a quick script to invoke some other actions as well, namely:



  • Hardware Inventory Cycle (Delta)
  • Hardware Inventory Cycle (Full)
  • Software Inventory Cycle (Delta)
  • Software Inventory Cycle (Full)
  • Discovery Data Collection Cycle (Delta)
  • Discovery Data Collection Cycle (Full)
  • File Collection Cycle (Delta)
  • File Collection Cycle (Full)
  • Software Updates Deployment Evaluation Cycle
  • Software Updates Scan Cycle



An interesting piece of the code is this:
$wmiQuery = "\\$srv\root\ccm\invagt:InventoryActionStatus.InvetoryActionID=$fscheduleID"
$checkdelete = ([wmi]$wmiQuery).Delete()

These two lines take care of the cleanup of an existing WMI instance which tells the SCCM client to perform a delta inventory instead of a full one. While you can invoke client actions on SCCM client's GUI, you can only kick off delta Software and Hardware inventory (although the GUI doesn't say that). So this piece of code give you the opportunity to force a full Hardware or Software inventory remotely.
Why is this important? Because there are cases when you try to troubleshoot why HW or SW inventory data is not flowing into the SCCM database and the best way to do that is enforcing the client to perform a full inventory and look at the logs (like the InventoryAgent.log on the client or the DataTransferService.log on the Management Point...etc.)
Some explanation for the code:

$SMSCli = [wmiclass] \\$srv\root\ccm:SMS_Client
This is for binding the SCCM Client's WMI class, if it's done, then we can invoke the TriggerSchedule method to start an action.

switch ($action) {
...
}
In this section, depending on what action is selected with the -action switch, we set up 2 variables, one is the identifier of the WMI instance which identifies the particular action. The other one is just for displaying the friendly name of the action for the user.

$hostlist = @($Input)
if($($hostlist.length) -gt 0){
   foreach ($srv in $hostlist) {
      if(!$srv){
         executeSCCMAction $srv $action $scheduleID
      }
   }
}
Get the list of hosts from the pipe into an array, go through the array and call the executeSCCMAction  function to create and/or delete the necessary WMI instance.

Clipboard friendly code:
 param ( [string] $action = "") 

function executeSCCMAction ([string]$srv, [string]$action, $fscheduleID){

   #Binding SMS_Client wmi class remotely.... 
   $SMSCli = [wmiclass] "\\$srv\root\ccm:SMS_Client"

   if($SMSCli){
      if($action -imatch "full"){
         #Clearing HW or SW inventory delta flag...
         $wmiQuery = "\\$srv\root\ccm\invagt:InventoryActionStatus.InventoryActionID=$fscheduleID"
         $checkdelete = ([wmi]$wmiQuery).Delete()
      }   
      #Invoking $action ...
      Write-Host "$srv, Invoking action $script:actionName"
      $check = $SMSCli.TriggerSchedule($fscheduleID)
   }
   else{
      # could not get SCCM WMI Class
      Write-Host "$srv, could not get SCCM WMI Class"
   }
}

switch ($action) {
   "hw" {
      $scheduleID = "{00000000-0000-0000-0000-000000000001}"
      $script:actionName = "Hardware Inventory Cycle (Delta)"
   }
   "hwfull" {
      $scheduleID = "{00000000-0000-0000-0000-000000000001}"
      $script:actionName = "Hardware Inventory Cycle (Full)"
   }
   "sw" {
      $scheduleID = "{00000000-0000-0000-0000-000000000002}"
      $script:actionName = "Software Inventory Cycle (Delta)"
   }
   "swfull" {
      $scheduleID = "{00000000-0000-0000-0000-000000000002}"
      $script:actionName = "Software Inventory Cycle (Full)"
   }
   "datadisc" {
      $scheduleID = "{00000000-0000-0000-0000-000000000003}"
      $script:actionName = "Discovery Data Collection Cycle (Delta)"
   }
   "datadiscfull" {
      $scheduleID = "{00000000-0000-0000-0000-000000000003}"
      $script:actionName = "Discovery Data Collection Cycle (Full)"
   }
   "filecollect" {
      $scheduleID = "{00000000-0000-0000-0000-000000000010}"
      $script:actionName = "File Collection Cycle (Delta)"
   }
   "filecollectfull" {
      $scheduleID = "{00000000-0000-0000-0000-000000000010}"
      $script:actionName = "File Collection Cycle (Full)"
   } 
   "swupdatedeploy" {
      $scheduleID = "{00000000-0000-0000-0000-000000000108}"
      $script:actionName = "Software Updates Deployment Evaluation Cycle"
   }
   "swupdatescan" {
      $scheduleID = "{00000000-0000-0000-0000-000000000113}"
      $script:actionName = "Software Updates Scan Cycle"
   }
   default {
      Write-Host -ForegroundColor 'red' "No valid Action is specified. Exiting..."
      exit
   }
}

#getting hostlist from pipe i.e.: PS C:\> gc list.txt | script.ps1
$hostlist = @($Input)

if($($hostlist.length) -gt 0){
   foreach ($srv in $hostlist) {
      if($srv){
         executeSCCMAction $srv $action $scheduleID 
      }
   }
}
else{
   # No hostname or hostlist is specified
}
Let me know your thoughts.
May The Force...
t

14 comments:

  1. awesome, this was perfect. good for tagging on other sccm packages or stand alone.

    ReplyDelete
  2. Do you recommend running this in its entirety, or choose which Actions to run? I'm thinking of putting this at the end of an OSD Task Sequence so that users get other assigned applications quicker.

    ReplyDelete
    Replies
    1. Depends on how you want to do it. I usually have one script with multiple actions and just use the switches as applicable. It's easier in terms of code management. However, if something is a one-liner, it's easier to put it into the Task Sequence as one step running a command

      Delete
  3. Great script, but do you have a similar one which works for SCCM 2012 sp1? I have tried this for triggering a forced Software Updates Scan but no joy... I was hoping to make an unattended script for use during the OSD task sequence (replacing the text file input string $srv with $pcname = $env:computername)... Am I doing something wrong - pretty new to PowerShell.
    =================================================================

    ReplyDelete
    Replies
    1. SCCM 2012 probably has a different inventory code for Software Updates Scan.
      Kick it off on an host via GUI and check the InventoryAgent.log of the SCCM client. It will have an inventory ID, something like {00000000-0000-0000-0000-000000000113}.
      Put that into this script replacing the line:
      $scheduleID = "{00000000-0000-0000-0000-000000000113}"

      Delete
  4. How do I launch it?

    If I use gc list.txt | script.ps1, I get The term 'script.ps1' is not recognized as the name of a cmdlet.

    if I use gc list.txt | .\script.ps1, I get "No valid Action is specified. Exiting..."

    ReplyDelete
    Replies
    1. this is just one of the "limitations" of PS, if the script is in the current folder, you need to explicitly refer to it like .\script.ps1 or with full path as you just said above.

      The script needs the -action switch to be specified, so e.g.:
      gc list.txt | .\script.ps1 0action hwfull

      it can be, hw, hwfull, sw, policy...etc, as you can see it in the code

      Delete
  5. Also, you misspelled Inventory in Line 8

    ReplyDelete
  6. Great script, but I only got it really working, when I took "!" away in row "if(!$srv)"

    ReplyDelete
  7. Wow, I left it there from the original script, nice catch. Thanks.
    t

    ReplyDelete
  8. $wmiQuery = "\\$srv\root\ccm\invagt:InventoryActionStatus.InventoryActionID=$fscheduleID"

    does not work, should be (I think)

    $wmiQuery = "\\"+$srv+"\root\ccm\invagt:InventoryActionStatus.InventoryActionID="+$fscheduleID

    ReplyDelete
  9. I have this error:
    Cannot convert value "\\hostname\root\ccm\invagt:InventoryActionStatus.InventoryActionID={00000000-0000-0000-0000-000000000002}" to
    type "System.Management.ManagementObject". Error: "Invalid object path "
    At C:\Users\user\Desktop\script\test.ps1:19 char:9
    + $checkdelete = ([wmi]$wmiQuery).Delete()
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvalidCastToWMI

    hostname, Invoking action Software Inventory Cycle (Full)

    ReplyDelete
    Replies
    1. is you SCCM server name called "hostname"?" \\hostname\root\ccm\invagt:InventoryActionStatus

      I think the parameters are not lined up when you are running the script and the real server name is not taken by the script.

      Delete