If you are using ConfigMgr to deploy Operating systems, and leveraging USMT (User State Migration Tool) for User State migration, you would be very much interested in finding out answers for the following questions.

  1. How long did USMT take to capture the User State data on a system?
  2. How long did USMT take to restore User State data back on a system?
  3. What was the size of the data that was migrated on each system?

Hopefully you have used USMT 4.0 with its hard link capabilities to speed things up in contrast with copying data over the network onto a remote share. Regardless of the method you had chosen for your USMT implementation, trying to capture the USMT usage statistics isn’t as simple as it sounds for all systems across your environment.

In this post, we will share the process we chose to use at Microsoft for collecting USMT statistics.

Overview of the Process

At a high level, the process of collecting USMT usage statistics can be broken down as below:

  • 1. Collect USMT usage statistics from USMT logs
  • 2. Store collected values into Task Sequence Variables
  • 3. Store Task Sequence Variables’ values into the Registry
  • 4. Use ConfigMgr inventory mechanism to report data from Registry keys.

Except for the last step of collecting inventory data using ConfigMgr, the first 3 steps are basically task sequence steps within the OS deployment task sequence.

Collect USMT usage statistics from USMT logs

This step is where we will parse USMT logs to get the required details that will be reported back to ConfigMgr site. To start with we need to know the location of the logs where the USMT executables are configured to write. Based on our configuration, the progress logs are written to %usmtlogs%\Scanprogress.log & %usmtlogs%\loadprogress.log as indicated by the command lines below.

 
"%usmtbits%\scanstate.exe" "%usmtsafe%" /o /c /hardlink /nocompress 
/efs:hardlink /i:"%usmtconf%\MigDocs.xml" /i:"%usmtconf%\SelfHostApps.xml" 
/i:"%usmtbits%\MigApp.xml" /offlinewindir:"%2" /localonly /uel:45 /v:13 
/L:"%usmtlogs%\Scanstate.log" 
/progress:"%usmtlogs%\Scanprogress.log" 
/ListFiles:"%usmtlogs%\ListFiles.txt"
"%usmtbits%\loadstate.exe" "%usmtsafe%" /hardlink /nocompress /lac /c /v:13 
/i:"%usmtconf%\MigDocs.xml" /i:"%usmtconf%\SelfHostApps.xml" 
/i:"%usmtbits%\MigApp.xml" /L:"%usmtlogs%\loadstate.log" 
/progress:"%usmtlogs%\loadprogress.log"

After locating the log files, now let’s see where the information about ‘how long it took to capture’ and the ‘size of data captured’ is hidden. For simplicity sake, we will consider only scanstate information; however the same logic is applicable for loadstate as well.

If you search for a string “, totalSizeinMBToTransfer,” in ScanProgress.log, you will locate a line that looks something like the one below. The value next to the string (16605.29) is the size of data that is captured as part of scanstate in MB. Now, it is just a matter of reading that value alone using any scripting method, and storing this value as a Task Sequence variable.

11 Sep 2009, 23:34:52 +05:30, 00:02:23, totalSizeInMBToTransfer, 16605.29

The same logic goes for finding the ‘time it took to capture the user state’, except that, this time you will be looking for a string “, errorCode,” in the same ScanProgress.log. It is usually the last line in the log with an error code of 0 if all went well. Again, using any scripting method you need to pick up the value just before the string “, errorCode”, which is 00:06:34 in the sample below.

11 Sep 2009, 23:39:02 +05:30, 00:06:34, errorCode, 0, numberOfIgnoredErrors, 0, message, "Successful run"

Store collected values into Task Sequence Variables

Before we get into, how we store the values into Task Sequence variables - why we store these data in Task Sequence variables and then move them again into registry deserves some attention.

Even though USMT data is the focus of this post, there is lots of other information that we capture which is relevant to OS deployment process. To name a few - information like OSD Start & End times, Old & New computer Names, and OSD Image Package ID are captured that are already either part of default or custom Task Sequence Variables in our deployment process. Hence, to keep the complete tracking of data in cohesion, we chose to populate USMT data also in Task Sequence Variables, and at a later state all the Task Sequence Variables’ values are collectively written to registry. So, in short, you could technically skip this step, and directly populate the USMT values into registry and jump to the last step of ‘Use ConfigMgr to report data’.

Now, let’s come back to ‘how we store values’ into Task Sequence Variables. This is a relatively easy step with only few lines of code that can do the trick. Assuming the USMT_ScanTime, USMT_ScanSize, USMT_LoadTime & USMT_LoadSize are already populated from the earlier step; it can be directly assigned to the variables after instantiating the TSEnv object as shown below.

 
SET TSEnv = CreateObject("Microsoft.SMS.TSEnvironment")
wscript.echo "--------------- Updating Task Sequence Variables 
--"
TSEnv("USMT_ScanTime") = 
USMT_ScanTime
TSEnv("USMT_ScanSize") = 
USMT_ScanSize
TSEnv("USMT_LoadTime") = 
USMT_LoadTime
TSEnv("USMT_LoadSize") = 
USMT_LoadSize

 

The entire script is available from our Connect site and instructions for joining that program can be found here.

Store Task Sequence Variables’ values into the Registry

With the required data in the Task Sequence variables, we are now onto the stage where we need to move them into Registry keys; which will be later collected via ConfigMgr inventory process.

Reading Task Sequence environment variables are very much similar to the method, which we saw in the earlier step. Basically, you start with instantiating Task Sequence Environment, and looping through the variables you can read them and perform any action that is required. In the snippet below, you will notice that after reading the variables, a MatchMaker function is used to compare the value against a mapping of values which need to be included or excluded.

Dim oTSE    : SET oTSE = CreateObject("Microsoft.SMS.TSEnvironment")   
For Each tV in oTSE.GetVariables() 
    IF (MatchMaker( tV, incArray ) = TRUE) Then
        IF (MatchMaker( tV, excArray ) = FALSE ) Then
            Call BrandValue( tV, oTSE(tV) )
        End IF
    End IF
 Next

If it meets the inclusion criteria and is not part of the Exclusion criteria, a function by name BrandValue is called; which will populate the registry key as seen below. Based on prior evaluation of OS architecture (64/32 bit system), appropriate reg.exe is used and the registry location is chosen.

' //////////////////////////////////////////////////// Brand a name and value to registry
    '
    ' ////////////////////////////////////////////////////
    Sub BrandValue( theName, theValue )
    
    Dim retVal : retVal = 0
    Dim runCmd : runCmd = REGBRAND & " ADD " & REGBRANDPATH & " /F /V " & theName & " /T REG_SZ /D """ & theValue & """"
 
    Wscript.Echo " Branding : [" & runCmd & "]"
    retVal = oWSH.Run( runCMD, 0, True )
    Wscript.Echo " Result   : [" & retVal & "]"
 
    End Sub

This step is typically the last step in our task sequence to ensure we capture all the Task Sequence Variables onto registry for inventory purposes. With the data now in registry keys, we are now ready to collect that data using ConfigMgr.

Use ConfigMgr to report data from Registry Keys

Finally, after all the pre-requisite work of populating the required data in the registry of machines, we will see what needs to happen next to get that data into the ConfigMgr database for easy retrieval in the form of reports.

As with any inventory data collection using MOF in ConfigMgr, retrieval of USMT values from Registry Key here is no different. However for the sake of completeness, we will see how this is actually done within ConfigMgr. There are set of MOF files named Configuration.MOF and SMS_DEF.MOF, which control what data is collected as part of inventory agent by ConfigMgr clients. Configuration.MOF instructs ConfigMgr clients to create WMI class, and SMS_DEF.mof tells ConfigMgr clients what to pick up from the WMI class and report to ConfigMgr site up the hierarchy.

The snippets available below, pretty much do the job of collecting inventory information and reporting them to ConfigMgr site. You would need to append these snippets into the respective MOF files at the site server. Of course, after tweaking to meet your needs and testing it.

As mentioned earlier, our approach of data collection is more than just USMT and hence you see those additional lines which are not related to USMT.

Configuration.MOF snippet
 
#pragma namespace("\\\\.\\root\\CIMV2")
#pragma deleteclass("HWINV_OSD_Details", NOFAIL)
[DYNPROPS]
class HWINV_OSD_Details 
{
[key] string KeyName = "";
string TSType;
string TSVersion;
string OSDImagePackageId;
string OSDBitlockerStatus;
string OldComputerName;
string OSDComputerName;
string OSDJoinAccount;
string OSDStartTime;
string OSDEndTime;
string OSDInstallLP;
string OSDGUID;
string USMT_ScanTime;
string USMT_ScanSize;
string USMT_LoadTime;
string USMT_LoadSize;
};
[DYNPROPS]
instance of HWINV_OSD_Details 
{
KeyName = "HWINV_OSD_Details";
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|TSType"),
Dynamic, Provider("RegPropProv")] TSType;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|TSVersion"),
Dynamic, Provider("RegPropProv")] TSVersion;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|OSDImagePackageId"),
Dynamic, Provider("RegPropProv")] OSDImagePackageId;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|OSDBitlockerStatus"),
Dynamic, Provider("RegPropProv")] OSDBitlockerStatus;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|OldComputerName"),
Dynamic, Provider("RegPropProv")] OldComputerName;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|OSDComputerName"),
Dynamic, Provider("RegPropProv")] OSDComputerName;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|OSDJoinAccount"),
Dynamic, Provider("RegPropProv")] OSDJoinAccount;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|OSDStartTime"),
Dynamic, Provider("RegPropProv")] OSDStartTime;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|OSDEndTime"),
Dynamic, Provider("RegPropProv")] OSDEndTime;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\Software\\Microsoft\\MPSD\\OSD|OSDInstallLP"),
Dynamic, Provider("RegPropProv")] OSDInstallLP;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\Software\\Microsoft\\MPSD\\OSD|OSDGUID"),
Dynamic, Provider("RegPropProv")] OSDGUID;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|USMT_ScanTime"),
Dynamic, Provider("RegPropProv")] USMT_ScanTime;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|USMT_ScanSize"),
Dynamic, Provider("RegPropProv")] USMT_ScanSize;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|USMT_LoadTime"),
Dynamic, Provider("RegPropProv")] USMT_LoadTime;
[PropertyContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MPSD\\OSD|USMT_LoadSize"),
Dynamic, Provider("RegPropProv")] USMT_LoadSize;
};
SMS_DEF.MOF snippet
#pragma namespace("\\\\.\\root\\CIMV2\\SMS")
[ SMS_Report (TRUE),
SMS_Group_Name ("HWINV_OSD_Details"),
SMS_Class_ID ("MICROSOFT|HWINV_OSD_Details|1.0") ]
class HWINV_OSD_Details : SMS_Class_Template
{
[SMS_Report (TRUE), key ]
string KeyName;
[SMS_Report (TRUE) ]
string TSType;
[SMS_Report (TRUE) ]
string TSVersion;
[SMS_Report (TRUE) ]
string OSDImagePackageId;
[SMS_Report (TRUE) ]
string OSDBitlockerStatus;
[SMS_Report (TRUE) ]
string OldComputerName;
[SMS_Report (TRUE) ]
string OSDComputerName;
[SMS_Report (TRUE) ]
string OSDJoinAccount;
[SMS_Report (TRUE) ]
string OSDStartTime;
[SMS_Report (TRUE) ]
string OSDEndTime;
[SMS_Report (TRUE) ]
string OSDInstallLP;
[SMS_Report (TRUE) ]
string OSDGUID;
[SMS_Report (TRUE) ]
string USMT_ScanTime;
[SMS_Report (TRUE) ]
string USMT_ScanSize;
[SMS_Report (TRUE) ]
string USMT_LoadTime;
[SMS_Report (TRUE) ]
string USMT_LoadSize;
};

 

After the site server had a chance to create new inventory policies for clients; clients during its next inventory cycle will send the data up to its ConfigMgr site server. Once the data is processed by the data loader thread (one of the threads of SMS_Executive service), the data finally shows up in SQL database. Based on the class name we used, the below query will show data in ConfigMgr DB.

   1: SELECT * FROM V_GS_HWINV_OSD_Details

Summary

To summarize what we have seen so far - We went through the process of parsing the USMT logs for specific strings to identify the values we need to collect. After that is done, based on our implementation approach, we populated those values into Task Sequence variable and then to registry. Finally, using standard inventory collection process of ConfigMgr we saw how the MOF needs to be updated to see the data in ConfigMgr database and hence reports.

We hope this was helpful in identifying the hidden information of USMT usage statistics using few simple scripts as Task Sequence steps and the standard inventory process of ConfigMgr.