Welcome to MSDN Blogs Sign in | Join | Help

After calling UpdateAlerts() an exception is returned: "Value does not fall within the expected range". DBGView will show the following output:

[4680] [E] 04680.01796, (cdasmomconnectorframework.cpp::2114) Could not decode unknown vartype 36 !
[4680] [E] 04680.01796, (cdasmomconnectorframework.cpp::2045) Could not decode unknown vartype 36 !
[4680] [E] 04680.01796, (cdasmomconnectorframework.cpp::1991) Could not determine ADO type or typeLength for [0][0] Param Name is @AlertId and VARTYPE is 36 [hr=0x80070057]!
[4680] [E] 04680.01796, (cdasmomconnectorframework.cpp::1342) AppendParamsFromSafeArray() failed for AlertHistory # 0 [hr=0x80070057] !

This error occurred for a customer after compiling their custom application in Visual Studio 2005. This did not happen when it was compiled with Visual Studio 2003. It turns out that the MCF isn't designed to work with the .NET 2.0 runtime. The solution here is to compile the application in VS 2003 or us MSBee (http://www.codeplex.com/MSBee) to force the use of the .NET 1.1 runtime.

I recently had the opportunity to learn a lot about SNMPVarBind values and populating those values in the Alert description Field from a monitor and rule. A customer was having difficulty determining the proper syntax to use for this and with the help of some folks in the OpsMgr product group I've documented it below:

For a Rule:

Added "Simple Network Management Protocol" through Add/Remove programs on the OpsMgr server
Configured the community name as "public" and to accept packets from any host
In the OpsMgr console, ran the Network Devices Discovery Wizard
It discovered my SNMP device and I selected it to manage
Created a new SNMP Trap (Alert) rule and tested generating alerts using trapgen
Used the following syntax for the Alert description: $Data/EventData/DataItem/SnmpVarBinds/SnmpVarBind[1]/Value$
Tested by creating an SNMP Trap using trapgen.exe
Alert appeared and the alert description contained the value specified

For a Monitor:

Use the following syntax for the Alert description: $Data/Context/SnmpVarBinds/SnmpVarBind[1]/Value$

To figure out the proper value we used a collection rule and viewed the raw event data from the collection rule. Also, we found that if you enter more than 10 parameters for Alert description they won't get reported properly. This is currently a limitation in OpsMgr 07.

The System Center Product Group has started a new blog that will contain some useful high level information about the System Center product line. The first post is by Brad Anderson who is "the man" when it comes to the System Center branded products. I expect to see more posts like this from management within the product team so if you're interested I would suggest subscribing to the feed.

http://blogs.technet.com/systemcenter

-Russ

If you are running a script via a rule, and that script returns a propertybag, then it is possible you'll want the data returned to show up in the alert description assuming an alert is generated. The syntax for this in OpsMgr 2007 isn't that straight forward, but with some assistance we figured it out.

Here is what the proper syntax is for the alert description: $Data/Property[@Name='ret']$

Here is an example of the script that returns a PropertyBag
Set oAPI = CreateObject("MOM.ScriptAPI")
Set oBag = oAPI.CreatePropertyBag()
strReturn = "Test"
Call oBag.AddValue("ret",strReturn)
Call oAPI.Return(oBag)
Call oAPI.LogScriptEvent("TestScript.vbs", 9999, 0, strReturn)

There will be a Live Meeting on October 24th going over the new certifications for SCCM and SCOM. I will be adding these new certs to my list of things to do. See Trika's blog on the subject and for details on the Live Meeting.

Recently a customer complained that you can't run more than one advertised program at a time on a client, but they could in SMS 2.0. Here is an example VBScript that runs all available advertised programs on a client.

'rslaten 10/10/2007
'Example of how to run all available programs
'If you want to select some programs, but not all, then use C# or VB.NET to build a simple UI
'or accept parameters into this script to only run certain programs

Set UI = CreateObject("UIResource.UIResourceMgr")
Set ads = UI.GetAvailableApplications
For each o in ads
    WScript.Echo "DEBUG: Program = " & o.Name
    lastRunTime = o.LastRunTime
    bIsMandatoryProgramPending = UI.IsMandatoryProgramPending
    While bIsMandatoryProgramPending
        WScript.Sleep(10000)
        bIsMandatoryProgramPending = UI.IsMandatoryProgramPending
        WScript.Echo "DEBUG: Program is running, sleeping"
    WEnd
    WScript.Echo "DEBUG: Running program: " & o.Name
    UI.ExecuteProgram o.ID, o.PackageID, True
    bProgramComplete = false
    While Not bProgramComplete
        WScript.Echo "DEBUG: Checking if " & o.Name & " has completed running..."
        WScript.Sleep(10000)
        Set p = UI.GetProgram(o.ID, o.PackageID)
        If p.LastRunTime <> o.LastRunTime Then
            WScript.Echo "DEBUG: " & o.Name & " has completed running..."
            bProgramComplete = true
        End If
    WEnd
Next
I recently had a customer ask me why the TimeOfFirstEvent and TimeOfLastEvent fields in an alert get set to 12/30/1899 when their custom application, which uses the MOM Connector Framework (MCF), uses UpdateAlerts() to set the resolution state of an alert.  I did some research and found that SQL Profiler actually shows this old date being passed to the MCF_DoAddPendingAlertUpdate stored procedure.  After doing a code review and review of the SDK I noticed that this will occur if you don't specifically set a value for TimeOfFirstEvent and TimeOfLastEvent and/or you don't flip the boolean bit on TimeOfLastEventUseExisting and TimeOfFirstEventUseExisting.

The SMS MP Guide actually tells you this:

"To specify the location of the SMS installation folder the script uses a system environment variable on the SMS site server. The administrator must create the %SMS_INSTALL_DIR_PATH% environment variable as a system environment variable so that the MOM Agent running under any user context has access to the log files. The script will then look in the %SMS_INSTALL_DIR_PATH%\Logs directory for the sender.log and distmgr.log files. For more information about setting system environment variables, see the system environment variable web page (<http://go.microsoft.com/fwlink/?LinkId=92316>).
In order for the MOM Health Agent to use this system environment variable the SMS Site Server may need to be restarted."


This seems easy enough, but what if you have thousands of site servers?  My co-worker, Lenny Wile, ran into such a situation with a customer.  Obviously at this point we need an automated method of setting this environment variable.  Using SMS or SCOM a script such as this could be run on each site server to set the environment variable appropriately:

Set oWMI = GetObject("winMgmts:root\default:StdRegProv")
oWMI.GetStringValue &H80000002,"SOFTWARE\Microsoft\SMS\Setup","Installation Directory",sPath
Set oWMI = GetObject("winMgmts:root\cimv2:Win32_Environment")
Set oNewVar = oWMI.SpawnInstance_
oNewVar.UserName = "<SYSTEM>"
oNewVar.Name = "SMS_INSTALL_DIR_PATH"
oNewVar.VariableValue = sPath
oNewVar.Put_

For your reference, here is one of the errors you might see if this variable isn't set appropriately:

Error opening log file directory
Directory = %SMS_INSTALL_DIR_PATH%\Logs
Error: 0x80070003
Details: The system cannot find the path specified.
One or more workflows were affected by this.
Workflow name: SMS_2003_Component__The_sender_cannot_connect_to_remote_site_over_the_LAN__Advanced_Security__4_Rule
Instance name: Microsoft.SMS.2003.Microsoft_SMS_2003_Site_Servers_Installation
Instance ID: {34B6B4DF-3EF9-A320-7AD1-69EF7A68A288}

I recently worked on an issue sequencing an application with the Softgrid 4.1 SP1 and 4.2 Sequencer. After selecting a location to download the virtual environment (after the install was completed) an error would appear "SystemGuard download failed (error code 53256)".

The sft-seq-log.txt would show the following:
[08/01/2007 16:51:38 VRB VFSX] SxS: Starting SxS public-to-private assembly conversion.
[08/01/2007 16:51:38 ERR VFSX] SxSPrivateAssembly::LoadConfigFile : Load failed.
[08/01/2007 16:51:38 ERR VFSX] SxSPrivateAssembly::LoadConfigFile : Load failed.
[08/01/2007 16:51:40 WRN VFSX] SxSPE::parseUTF : Unsupported UTF format for file Q:\MyApp\MyFile.dll (format # 3).
[08/01/2007 16:51:40 VRB RTSK] Failed to convert public SxS assemblies.
[08/01/2007 16:51:40 ERR RTSK] SystemGuard download failed (error code 53256).
[08/01/2007 16:51:40 ERR RTSK] SystemGuard download failed (error code 53256).

I found that the root cause of this specific problem (unique to the "Unsupported UTF format" error) is that we attempt to read the manifest in MyFile.dll and the manifest in that file is corrupt. A corrupt manifest in a file will only result in this error if the application you are installing contains SxS binaries. 

You can view or edit the manifest of any file with a PE editor.  I was able to workaround the issue by deleting the corrupt manifest from MyFile.dll after the application had installed but before I selected "Stop Monitoring".  Obviously we don't recommend modifying files of applications without contacting the manufacturer of the application for their guidance.

I have recently done some research on exactly how inventory is generated, including deltas, and have listed the information below.  This information can be obtained by monitoring the inventory agent logs, verbose WMI logs, and applicable inventory WMI classes:

  • Instruction to run inventory comes from an instance of InventoryAction.  This specifies full, delta, or resync report type.
  • The state of each inventory action is stored in the InventoryActionStatus class.
  • Inventory history is stored in a class named after the inventory type (the C00…01 number for hinv)
  • Each instance is of the InventoryReport class
  • When inventory starts… (hinv in this example)
    • If a delta is being specified, make sure a full report has already been generated (checks the InventoryActionStatus class)
    • Prepares the applicable history class (deletes all non-current reports)
      • New instances are given a 'New' state
      • Updated instances are given an 'Updated' state
      • Instances that don't change are given a 'Verified' state
    • Inventory is reported: Only 'New', 'Deleted', and 'Updated' instances are reported for deltas
    • Cleans up applicable history class (creates 'Current' reports for all 'New', 'Updated', and 'Verified' reports)
    • Sends inventory report via messaging system
    • Updates the InventoryActionStatus class

I recently worked on an issue where a customer was trying to programmatically delete a mandatory assignment from an SMS 2003 advertisement. I saw that adding an assignment is a very easy and documented task, however deleting the same assignment is not.

Unfortunately I was only able to work on the issue for a few hours because I was going on vacation but one of my peers, David Stewart, figured it out while I was out. I thought this example would be useful for anyone looking to delete an assignment using a script.

' Parse cmd line args
' ==============================
sSiteCode = WScript.Arguments(0)
sAdvertID = WScript.Arguments(1)

' Connect to local site provider
' ==============================
Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")
Set objSWbemServices = objSWbemLocator.ConnectServer(".", "root\sms")
Set ProviderLoc = objSWbemServices.InstancesOf("SMS_ProviderLocation")
For Each Location In ProviderLoc
    If Location.ProviderForLocalSite = True Then
        Set objSWbemServices = objSWbemLocator.ConnectServer(Location.Machine, "root\sms\site_" + Location.SiteCode)
        Set objSWbemLocator = Nothing
        Set ProviderLoc = Nothing
    End If
Next

' Retrieve the advertisment instance of concern
' =============================================
Set objAdvert = objSWbemServices.Get("SMS_Advertisement.AdvertisementID='" & sAdvertID & "'")

' Display advertisement properties
' ================================
WScript.Echo "ActionInProgress = " & objAdvert.ActionInProgress
WScript.Echo "AdvertFlags = " & objAdvert.AdvertFlags
WScript.Echo "AdvertisementID = " & objAdvert.AdvertisementID
WScript.Echo "AdvertisementName = " & objAdvert.AdvertisementName
WScript.Echo "CollectionID = " & objAdvert.CollectionID
WScript.Echo "Comment = " & objAdvert.Comment
WScript.Echo "Assigned Schedule = {"

' Display advertisement properties
' ================================
set Advertisement_Properties = objAdvert.Properties_ 'get property set
set AssignedScheduleArray = Advertisement_Properties.Item("AssignedSchedule")

' Display advertisement properties
' ================================
Dim replacementScheduleArray()

intSize = 0

' Loop through current assignments, copy them to new array to update advertisement instance.
' Leave any assignments we want to remove out of the new array.
' ==========================================================================================
for i = 0 to UBound(AssignedScheduleArray)

    'Retrieve next schedule token from instance array
    '================================================
    Dim SMS_ScheduleToken
    Set SMS_ScheduleToken = AssignedScheduleArray(i)
    
    'Display schedule token, ask if it should be deleted.
    '====================================================
    WScript.Echo AssignedScheduleArray.Name & " " & i & ":" & SMS_ScheduleToken.GetObjectText_()
    If MsgBox("Do you want to delete this assignment?", 4, "Assignment:" & SMS_ScheduleToken.StartTime) <> vbYes Then
        
        'Not deleting this schedule token, add it to the replacement array.
        '==================================================================
        redim preserve replacementScheduleArray(intSize)
        set replacementScheduleArray(intSize) = SMS_ScheduleToken.Clone_()
        intSize = intSize + 1
    end if
next
WScript.Echo "}"

'Update the advertisement instance with the replacement schedule token array
'===========================================================================
if(IntSize < 1) then
    'We removed all the schedule tokens.
    objAdvert.Properties_.Item("AssignedSchedule") = null   
else
    ' There is at least one value to keep
    objAdvert.Properties_.Item("AssignedSchedule") = replacementScheduleArray
end if
objAdvert.Put_()

In large environments, with tens of thousands of SMS 2003 clients, inventory may get "lost" for various reasons. An example of a lost inventory is below:

  • An SMS 2003 Advanced Client reports a full inventory upon install
  • The client continues to report delta inventory of changes
  • The client reports a delta inventory but that inventory gets "lost"
  • The client continues to report delta inventory but the changes in the "lost" inventory never appear in the database
  • The client gets resync'd, sending up a full inventory, and the changes that were "lost" now appear


Clients don't get resync'd under normal conditions therefore that inventory may not ever appear in the database. This can become an issue for inventory items such as security updates (staying applicable in the database but not applicable on the client). System Center Configuration Manager 2007 contains architectural changes which prevent this scenario from occurring. Specifically SCCM 2007 serializes software and hardware inventory reporting so that missing reports, which are based on serial numbers instead of timestamps, trigger a re-synchronization of inventory at the client.

So if you don't plan on upgrading to SCCM 2007 in the near future how might you prevent inventory from being "lost"? I documented some steps that should assist with detecting, and possibly resolving, the rare scenario when clients send up inventory that somehow disappears before getting loaded into the database. These steps are simply an example of how one might do this and the scripts are not tested and do not contain any type of error handling, logging, or reporting.

Step # 1 – Modify the SMS_DEF.MOF

  • Modify the Win32_OperatingSystem section of the MOF to report "LocalDateTime".  This is required because the "Workstation Status" section shows "Last Hardware Scan" but there isn't a way to keep the history of this data therefore we need a consistent time we can key in on that writes to a history table.
  • Create a new class in the SMS_DEF.MOF to pick up a new WMI class which we'll create a populate later:

    //SMSInventoryHistory
     [SMS_Report(TRUE),
    SMS_Group_Name("SMS Inventory"),
    SMS_Class_ID("MICROSOFT|SMS_INVENTORY|1.0"),
    Namespace("\\\\\\\\.\\\\root\\\\SMSINVENTORY")]
    class InvHistory: SMS_Class_Template
    {
    [SMS_Report(TRUE),key]
    datetime   ScanTime;
    [SMS_Report(TRUE)]
    uint32   KeyIncrement;
    [SMS_Report(TRUE)]
    string   ScanType;
    };

  • Notice the KeyIncrement field.  This is there because we want to report every instance of this class for every inventory.  It is for this reason that we don't need history enabled for this class so it can be disabled by using KB836432 (This still applies to SMS 2003 and likely SCCM 2007).

Step # 2 – Create a recurring advertisement, which should probably run at least twice as often as hardware inventory (if not more), which runs client.vbs

  • Creates a new "SMSInventory" class under "root" if it doesn't exist
  • Gets the last hardware scan from root\ccm\invagt:InventoryActionStatus
  • Creates a new instance of InvHistory in the SMSInventory namespace if the hardware scan is new
  • If the inventory is already recorded then update the "KeyIncrement" field so SMS reports this in every delta
  • Cleans up any instances older than 14 days by default to keep the delta inventory from growing too large
'client.vbs
'rslaten
'08/15/2007

'Determines when to clean out old inventory instances
iDays = 14

'Connect to WMI locally
Set oLocator = CreateObject("WbemScripting.sWbemLocator")
Set oSvc = oLocator.ConnectServer(".", "root")

'Create a custom namespace to hold data if it doesn't already exist
Set oNamespaces = oSvc.InstancesOf("__namespace")
bFound = False
For each oNamespace in oNamespaces
    If oNamespace.Name = "SMSInventory" Then
        bFound = True
        Exit For
    End If
Next

If Not bFound Then
    Set oNamespace = oSvc.Get("__namespace")
    Set oCustomNameSpace = oNamespace.SpawnInstance_
    oCustomNamespace.name = "SMSInventory"
    oCustomNamespace.Put_()

    'Create a custom class to hold data
    wbemCimtypeUint32 = 19
    wbemCimtypeDatetime = 101
    wbemCimtypeString = 8
    Set oWMI = GetObject("winmgmts:root\SMSInventory")
    Set oClass = oWMI.Get()
    oClass.Path_.Class = "InvHistory"
    oClass.Properties_.add "ScanTime", wbemCimtypeDatetime
    oClass.Properties_("ScanTime").Qualifiers_.add "key", true
    oClass.Properties_.add "KeyIncrement", wbemCimtypeUint32
    oClass.Properties_.add "ScanType", wbemCimtypeString
    oClass.Put_()
End If

'Get timezone
Set oSvc = oLocator.ConnectServer(".", "root\cimv2")
Set collTimeZone = oSvc.ExecQuery("select Bias, DaylightBias from Win32_TimeZone")
For Each oTimeZone in collTimeZone
    iTimeZone = oTimeZone.Bias - oTimeZone.DaylightBias
Next

'Get last Hinv report date and convert to local time
Set oSvc = oLocator.ConnectServer(".", "root\ccm\invagt")
HActionID = """{00000000-0000-0000-0000-000000000001}"""
Set oHinv = oSvc.Get("InventoryActionStatus.InventoryActionID=" & _
    HActionID)
Const CONVERT_TO_LOCAL_TIME = true
'dLastReportDate = ConvertToGMT(oHinv.LastReportDate)
dLastReportDate = oHinv.LastReportDate
dLastReportDate = WMIDateStringToDate(dLastReportDate)
Set dNewTime = CreateObject("WbemScripting.SWbemDateTime")
dNewTime.SetVarDate dLastReportDate, CONVERT_TO_LOCAL_TIME
dLastReportDate = dNewTime.GetVarDate(CONVERT_TO_LOCAL_TIME)
dLastReportDate = DateAdd("n", iTimeZone, dLastReportDate)
dLastReportDate = GetWMIDate(dLastReportDate, "+000")

'Set last Hinv report date
Set oSvc = oLocator.ConnectServer(".", "root\SMSInventory")
Set oHinvHistory = oSvc.InstancesOf("InvHistory")

'First instance in class
If oHinvHistory.Count = 0 Then
    Set oHinv = oSvc.Get("InvHistory").SpawnInstance_
    oHinv.ScanTime = dLastReportDate
    oHinv.KeyIncrement = 0
    oHinv.ScanType = "Hardware"
    oHinv.Put_()
Else
    bFound = False
    
    'Calculate date to clean up old instances
    Set dExpire = CreateObject("WbemScripting.SWbemDateTime")
    dExpire.SetVarDate Now, CONVERT_TO_LOCAL_TIME
    dRegular = dExpire.GetVarDate(CONVERT_TO_LOCAL_TIME)
    dExpireDate = DateAdd("d", -iDays, dRegular)
    dExpire.SetVarDate dExpireDate, CONVERT_TO_LOCAL_TIME
    
    'Updating all instances
    For each oInstance in oHinvHistory
        If dLastReportDate = oInstance.ScanTime Then
            bFound = True
        End If
        
        'Clean up old instances
        If oInstance.ScanTime < dExpire Then
            oInstance.Delete_
        Else
            'Increment the KeyIncrement property to force SMS to pick
            'up the instance during inventory
            oInstance.KeyIncrement = oInstance.KeyIncrement + 1
            oInstance.Put_
        End If
    Next
    
    'Create a new instance if needed
    If Not bFound Then
        Set oHinv = oSvc.Get("InvHistory").SpawnInstance_
        oHinv.ScanTime = dLastReportDate
        oHinv.KeyIncrement = 0
        oHinv.ScanType = "Hardware"
        oHinv.Put_()    
    End If
End If

Function ConvertToGMT(dDate)
    ConvertToGMT = Replace(dDate, "-000", iTimeZone)
End Function

Function WMIDateStringToDate(dtmInstallDate)
     WMIDateStringToDate = CDate(Mid(dtmInstallDate, 5, 2) & "/" & _
     Mid(dtmInstallDate, 7, 2) & "/" & Left(dtmInstallDate, 4) _
     & " " & Mid (dtmInstallDate, 9, 2) & ":" & _
     Mid(dtmInstallDate, 11, 2) & ":" & Mid(dtmInstallDate, _
     13, 2))
End Function

Function GetWMIDate(vd,strOffset)
    On Error Resume Next
    GetWMIDate = Year(vd) & AddZero(Month(vd)) & AddZero(Day(vd)) & _
            AddZero(Hour(vd)) & AddZero(Minute(vd)) & _
            AddZero(Second(vd)) & ".000000" & strOffset
End Function

Function AddZero(pNum)
    On Error Resume Next
    If pNum <=9 then
        AddZero = "0" & pNum
    Else AddZero = pNum
    End If
End Function

Step # 3 – Periodically check to see if there is any missing hardware inventory by running server.vbs

  • Queries the SMS database for OS LocalDateTime and the custom SMSInventory ScanTime
  • Compares the ScanTime to LocalDateTime, searching for missing LocalDateTime entries.  To find a match the LocalDateTime must be within 5 minutes from the ScanTime.  If any clients take longer than 5 minutes to complete a hardware inventory cycle then this may need to be increased.
  • For any ScanTime without a matching LocalDateTime, we display the fact that there is a missing inventory.
'server.vbs
'rslaten
'08/15/2007

'Globals
Dim aClients(), iClients, iInstanceCount, iTotal

'Get SMS Namespace
sSMSNameSpace = GetSMSNameSpace()

'Connect to site server
Set oWMI = GetObject("winMgmts:" & sSMSNameSpace)

'Query clients and create and array of client objects
sQuery = "select distinct SMS_R_System.Name, SMS_R_System.SMSUniqueIdentifier," & _
    " SMS_G_System_SMS_INVENTORY.ScanTime, SMS_GH_System_OPERATING_SYSTEM.LocalDateTime" & _
    " from  SMS_R_System inner join SMS_G_System_SMS_INVENTORY on " & _
    "SMS_G_System_SMS_INVENTORY.ResourceID = SMS_R_System.ResourceId inner" & _
    " join SMS_GH_System_OPERATING_SYSTEM on " & _
    "SMS_GH_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId " & _
    "where SMS_G_System_SMS_INVENTORY.ScanTime is not NULL and " & _
    "SMS_GH_System_OPERATING_SYSTEM.LocalDateTime is not NULL"

Set aCollection = oWMI.ExecQuery(sQuery)
iTotal = aCollection.count
For Each oInstance In aCollection
    If sLastName <> oInstance.SMS_R_System.Name Then
        'Make sure this isn't the first iteration in the array
        If iInstanceCount <> 0 Then
            'Add to master array
            ReDim Preserve aClients(iClients)
            Set aClients(iClients) = oClient
            iClients = iClients + 1
        End If
        
        'Now create new client object
        Set oClient = New Client
        oClient.sClient = oInstance.SMS_R_System.Name
        oClient.sGUID = oInstance.SMS_R_System.SMSUniqueIdentifier
    End If
    
    If sLastScan <> oInstance.SMS_G_System_SMS_Inventory.ScanTime Then
        oClient.AddSMSInventoryScanDate(oInstance.SMS_G_System_SMS_Inventory.ScanTime)
    End If
    
    If sLastOSScan <> oInstance.SMS_GH_System_OPERATING_SYSTEM.LocalDateTime Then
        oClient.AddOSScanDate(oInstance.SMS_GH_System_OPERATING_SYSTEM.LocalDateTime)
    End If 

    sLastName = oInstance.SMS_R_System.Name
    sLastScan = oInstance.SMS_G_System_SMS_Inventory.ScanTime
    sLastOSScan = oInstance.SMS_GH_System_OPERATING_SYSTEM.LocalDateTime
    iInstanceCount = iInstanceCount + 1
    
    'If this is the last instance add it to the array
    If iInstanceCount = iTotal Then
        'Add to master array
        ReDim Preserve aClients(iClients)
        Set aClients(iClients) = oClient
        iClients = iClients + 1    
    End If
Next

'Loop through clients in memory to check for missing inventory
For each oClient in aClients
    aScans = oClient.aScans
    WScript.Echo "******************************************"
    WScript.Echo "Client = " & oClient.sClient
    WScript.Echo "GUID = " & oClient.sGUID
    For each oScan in oClient.aScans
        If not oScan.isOS Then
            Set oMatch = new Match
            oMatch.dScan = WMIDateStringToDate(oScan.dDate)
            oMatch.bMatch = false
            
            'Find a matching OS time (look for match within 5 minutes)
            For each oTime in aScans
                If oTime.isOS Then
                    If DateDiff("n", oMatch.dScan, _
                        WMIDateStringToDate(oTime.dDate)) < 5 and _
                        DateDiff ("n", oMatch.dScan, _
                        WMIDateStringToDate(oTime.dDate)) > -5 Then             
                        oMatch.dOS = WMIDateStringToDate(oTime.dDate)
                        oMatch.bMatch = true
                    End If
                End If
            Next
            
            'Make sure we found a match
            If not oMatch.bMatch Then
                WScript.Echo "*PROBLEM FOUND*"
                WScript.Echo "SMS Inventory Report = " & oMatch.dScan
                WScript.Echo "*NO MATCHING OS DATE FOUND*"
            Else
                WScript.Echo oMatch.dScan & " = " & oMatch.dOS
            End If
        End If
    Next
    WScript.Echo "******************************************"
Next

'Get regular date
Function WMIDateStringToDate(dtmInstallDate)
    WMIDateStringToDate = CDate(Mid(dtmInstallDate, 5, 2) & "/" & _
        Mid(dtmInstallDate, 7, 2) & "/" & Left(dtmInstallDate, 4) _
            & " " & Mid (dtmInstallDate, 9, 2) & ":" & _
                Mid(dtmInstallDate, 11, 2) & ":" & Mid(dtmInstallDate, _
                    13, 2))
End Function


'Gets SMS Namespace from local site server
Function GetSMSNameSpace()
    Set refWMI = GetObject("winMgmts:\root\sms")    
    Set colNameSpaceQuery = refWMI.ExecQuery("select * from SMS_ProviderLocation")
    For Each refitem in colNameSpaceQuery
        GetSMSNameSpace = refitem.NamespacePath
    Next
End Function

Class Client
    Public aScans()
    Private i
    Public sClient
    Public sGUID
    
    Public Sub AddOSScanDate(dScanDate)
        'Check for duplicates
        bFound = false
        For Each oScan in aScans
            If dScanDate = oScan.dDate And _
                oScan.isOS = true Then
                bFound = true
                Exit For
            End If
        Next
        
        If Not bFound Then
            Set oScan = New Scan
            oScan.dDate = dScanDate
            oScan.isOS = true
            ReDim Preserve aScans(i)
            Set aScans(i) = oScan
            i = i + 1
        End If
    End Sub
    
    Public Sub AddSMSInventoryScanDate(dScanDate)
        'Check for duplicates
        bFound = false
        For Each oScan in aScans
            If dScanDate = oScan.dDate And _
                oScan.isOS = false Then
                bFound = true
                Exit For
            End If
        Next
        
        If Not bFound Then        
            Set oScan = New Scan
            oScan.dDate = dScanDate
            oScan.isOS = false
            ReDim Preserve aScans(i)
            Set aScans(i) = oScan
            i = i + 1    
        End If
    End Sub
End Class

Class Scan
    Public dDate
    Public isOS
End Class

Class Match
    Public dScan
    Public dOS
    Public bMatch
End Class

Step # 4 – Force a resync on the clients that have "lost" inventory

  • The method to delete the local inventory cache is the known and documented method of doing this.
  • There may be a way to use the MP API to create a server side policy, but this would require significant work and a non-script (C++) application.  Internally, as you can see with SQL profiler, we execute the sp_RC_InvResync stored procedure but as you know executing these directly isn't recommended or supported.

These examples do not contain error checking and need to be optimized for performance.  The examples have also only been tested in a very small test environment using only the scenario documented at the top of this post.  If this method is to be used in a production environment more code work and testing needs to be done, particularly on the server end, to improve the detection logic, add history to which clients resyncs have been sent to, and add reporting capabilities.

**Steps 3 and 4 might not be required.  During testing I found a potentially unforeseen benefit of running the client.vbs script on clients with "lost" inventory.  Since every instance of the InvHistory class is reported during every inventory, when an instance is listed as "Update" but isn't in the database, dataloader will automatically request a resync for that client.  More testing of this finding needs to be done to verify that this will work, and if it does it should simplify detecting which clients have lost inventory**

I recently worked on an issue involving a customer attempting to create a mandatory Advert using a C# application. The advertisement worked but wasn't mandatory even though it looked to be in the SMS Admin console. There was also some confusion as to how the bit field properties work and why the advert wasn't mandatory. The customer had noticed that the TimeFlags value in the SMS_Advertisement class was different in their custom application than one created by the SMS Admin Console. I sent the following explanation which I wanted to share:

Dear <customer>,

I can't debug your custom application but I did end up having to create a test application (code attached) that emulates what the <custom> application does so I could see what your instance of SMS_Advertisement looks like.  After viewing the differences I saw that an advert created in the SMS Admin console, and one created with the <custom> application, were different and it wasn't just the TimeFlags property.  The SMS Admin console uses the SDK just like your program so it is just a matter of seeing that there is a difference between the two classes and informing the developer of the custom application of the difference so they can correct it. 

There was also some confusion over how the TimeFlags property works so I explain this below:

SMS uses bit fields for lots of on/off settings.  The TimeFlags property is no exception.  The TimeFlags property is a bit field, which is actually just a set of boolean flags.  By default, when a regular mandatory advertisement is created through the SMS Admin console, a decimal value of 8209 is assigned to the TimeFlags property.  8209 converted to binary is "10000000010001".  This is the important number.

Here is what the SDK says about the TimeFlags property:

TimeFlags
Datatype: uint32
Access type: Read-only
Qualifiers: Bits

Reserved for internal use. Duplicates the information in the time-related properties. For example, ENABLE_PRESENT is set when PresentTimeEnabled equals TRUE. Bit flags are as follows:

ENABLE_PRESENT (bit 0 = 1)
ENABLE_EXPIRATION (bit 1 = 2)
ENABLE_AVAILABLE (bit 2 = 4)
ENABLE_UNAVAILABLE (bit 3 = 8)
ENABLE_MANDATORY (bit 4 = 16)
GMT_PRESENT (bit 8 = 256)
GMT_EXPIRATION (bit 9 = 512)
GMT_AVAILABLE (bit 10 = 1024)
GMT_UNAVAILABLE (bit 11 = 2048)
GMT_MANDATORY (bit 12 = 4096)

So now we know what the real settings are.  You count from right to left of the binary number ignoring the first bit.  As you can see 8209 really means ENABLE_MANDATORY.  In the case of 8193 (10000000000001) none of the bits are on.  I wrote a test application that emulates what the <custom> program does (I couldn't view the differences between an advert created by the console and one by their application without doing this) and also created a test mandatory advert using the SMS admin console.  I compared the potentially relevant differences and those are noted below:

AdvertFlags Property:
<customer> App. is set to 67108864 (100000000000000000000000000)
Admin Console is set to 0
*This shouldn't matter since none of the flags are enabled anyway

AssignedScheduleEnabled Property:
<custom> App. is set to False
Admin Console is set to True
Per SDK: Indicates whether the schedule defined in AssignedSchedule is active. The default value is FALSE.

I changed the test application by setting the AssignedScheduleEnabled property and it started working and the TimeFlags property was now set to ENABLE_MANDATORY.  Basically, the problem appears to be that the advertisement created with the <custom> program isn't ever being set to mandatory.

Thanks,

Russ

Here is the code in the test application that I wrote to emulate what the customer was doing:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SystemsManagementServer.Automation;

namespace CreateAdvert
{
    class Program
    {
        static void Main(string[] args)
        {
            SMSProvider oProvider = new SMSProvider("server", "administrator", "password");
            SMSPackage oPackage = oProvider.Packages.Get("CEN00001");
            SMSProgram oProgram = oPackage.Programs.Create("Notepad", "notepad.exe");
            oProgram.AllowUserToInteractWithProgram = true;
            oProgram.RunMode = ProgramRunModes.Normal;
            oProgram.RunOnAnyPlatform = true;
            oProgram.UserRequirements = UserRequirementsFlags.OnlyRunWhenUserLoggedOn;
            oProgram.Save();
            SMSCollection oCollection = oProvider.Collections.Get("SMS00001");
            SMSAdvertisement oAdvert = oProvider.Advertisements.Create("Test", oCollection, oProgram);

            oAdvert.Comment = "Test";
            oAdvert.PresentTimeEnabled = true;
            DateTime now = DateTime.Now;
            oAdvert.PresentTime = now;
            DateTime tomorrow = DateTime.Now.AddDays(1);
            oAdvert.IncludeSubCollections = false;
            oAdvert.MandatoryOverSlowLinks = true;
            oAdvert.WhenLocalDPAvailable = LocalDPOptions.DownloadFromDistributionPoint;
            oAdvert.AssignedScheduleEnabled = true; //this is what was missing

            DateTime dtTenMin = DateTime.Now.AddMinutes(10);
            SMSScheduleToken_NonRecurring tokNonRec = new SMSScheduleToken_NonRecurring(new DateTime(dtTenMin.Year, dtTenMin.Month, dtTenMin.Day, dtTenMin.Hour, dtTenMin.Minute, dtTenMin.Second));
            oAdvert.AssignedSchedules.Add(tokNonRec);
            DateTime later = DateTime.Now.AddYears(1);
            oAdvert.ExpirationTime = new DateTime(later.Date.Year, later.Date.Month, later.Date.Day, 5, 0, 0);
       
            oAdvert.Save();
        }
    }
}

I was looking back over some of my older posts and realized that everything I've blogged about was technical and read more like a KB article than a blog. This is my humble attempt at adding a personal touch to this blog.

I was born and raised just outside of Dallas in Mesquite, Texas. My real name is Rusty Slaten but most everyone calls me Russ. I am single and currently reside in Carrollton, Texas. I have two Labrador Retrievers and enjoy the outdoors, basketball, golf, fishing, poker, and video games (although I don't play them anymore because I don't have time). I enjoy all sports but am especially a fan of the Dallas Mavericks. I am currently reading several programming books: Assembly Language for Intel Based Computers 5e by Kip R. Irvine, Introduc