So your boss is going to want stats for your OSD sequence? [Part 1 – Data Gathering]

How to provide hard statistics on your build sequences.


A while back my boss gave me two goals for our OS deployments; 1) he set a target for 90% successful builds and 2) build times as close to 1 hour as possible.  Okay, getting there is one thing, but how do I report on that?

I would need to record the time the build started, when it finished and how it finished.  As I worked on this project I ended up gathering more data, the model for example, and if USMT was used, etc.  My solution collects all of this data and writes it to a master CSV located on a server share.  That CSV can be pulled into Excel, or Tableau, or Power BI and used to generate a report similar to this one I publish to our SharePoint site.

Screen Shot 2017-08-20 at 5.19.46 PM

[Yes, I know that our builds are falling short of those two goals.  😉 ]

There are two parts to this solution.  The first is the data collector, a script that runs as part of the task sequence; and the second is taking that data and generating a report.


Part 1: Collecting the Data

I wrote a PowerShell script that collects the data that I was interested in and writes it to a central CSV file kept on a file share.   There are other ways to accomplish this of course.  You could write it out to individual CSVs, or you could write it out to a database.  In my case, I’m using a central CSV file.

What data to collect?

Since every environment is different, we’ll start simple and you can expand the script to meet your needs.

We will collect:

  • Computer Name
  • Build Start Time
  • Build Finish Time
  • Elapsed Build Time
  • Model of Machine
  • Task Sequence Name
  • Result of the Build (Success/Failure)
  • Failure Reason

Running the Script

The script will be run twice during the build.  The first time it is run it will simply take the current date/time and assign it to a task sequence variable.  This variable will be used later at the end of the build when the rest of the data is collected.

The second time the script is executed is at the end of the build.  This running will collect all of the data and use the task sequence variable from the first running to calculate the build time.  This all will then be written to the CSV.

Task Sequence Actions

As with all things, there are different ways to execute the script.

The simplest is to put it in a package and use a “Run PowerShell Script” action at the start and completion of the task sequence.  The problem with that is you will want to get the first execution of the script (to record the start time) as early in the sequence as possible, and if that is before the drive is in a state to download content to then the package option won’t work.

You could add it to your boot image and run the first execution from there, then run the second execution from a package.

Or you could run it from a share hanging off of a server.

For this example, I’ll be running it from a share hung off of a server.

Server\Share Setup

On my server (I’ll use my ConfigMgr server for this example) I created a folder called “OSDResults”.  I then shared it and granted my Network Access Account Read/Write permissions.

OSDResults_SharePermissions

Inside the OSDResults folder, I create a subfolder called “Data”.  The CSV of results will be written inside the Data folder and the script will reside in the root of OSDResults.

OSDResults_Setup

The Script

[Download]

# =============================================================================
# File:     OSDResult.ps1
# Version:  1.00
# Date:     19 August 2017
# Author:   Mike Marable
#
# Purpose: Tallys success vs. failure of build results in a CSV on the network
#

# =============================================================================
# Initialization
param ([string]$Record = $(throw "-Record is required [Start/Success/Failure]."))

# =============================================================================# Functions
#--------------------------------------
Function Get-SmstsFailAction
#--------------------------------------
{
Param ($SmstsLogPath)
$LogContents = $null
$FailedActionsIndexArray = $null
$strLastFailAction = $null
$LogContents = Get-Content -Path
$SmstsLogPath -ReadCount 0
Try
{
$FailedActionsIndexArray = @(0..($LogContents.Count - 1) | where {$LogContents[$_] -match "Failed to run the action"})
$strLastFailAction = (($LogContents[($FailedActionsIndexArray[-1])].tostring())-replace '.*\[LOG\[') -replace 'Failed to run the action: ' -replace '\]LOG\].*'
}
Catch
{
$strLastFailAction = "Unkown"
}
Return $strLastFailAction
}
#end function Get-SmstsFailAction

# =============================================================================# Start of Code
# Set Variables
$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$MyVersion = "1.00"
$myScript = "OSDResults"
$CSVfileName = "OSDResultsTally.csv"
$TargetFolder = $tsenv.Value("OSDResultsShare")
$OutPut = "$TargetFolder\Data"
$CSVFile = "$OutPut\$CSVfileName"
$SMSTSfldr = $tsenv.Value("_SMSTSLogPath")
$SMSTSfile = "$SMSTSfldr\smsts.log"
Write-Host "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
"Write-Host "Starting $MyScript (v $MyVersion)
"Write-Host "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
IF ($Record -eq "Start")
{
# Record the starting date and time for this build
Write-Host "Recording the starting date and time for this build"
$tsenv.Value("OSDStartInfo") = (Get-Date -Format "dd-MMM-yyyy HH:mm:ss")
Write-Host "This build began on: " $tsenv.Value("OSDStartInfo")
Write-Host "Recording the model of computer being built..."
$tsenv.Value("OSDModelInfo") = (Get-WmiObject -Class Win32_ComputerSystem).Model
Write-Host "This model: " $tsenv.Value("OSDModelInfo")
}

IF ($Record -eq "Success" -OR $Record -eq "Failure")
 {
 # Record the results for this build
 Write-Host "Recording the results for this build"
 $OSDFinishInfo = (Get-Date -Format "dd-MMM-yyyy HH:mm:ss")
 # Gather up the data to include in the report
 $OSDStartInfo = $tsenv.Value("OSDStartInfo")
 $OSModelInfo = $tsenv.Value("OSDModelInfo")
 $TSName = $tsenv.Value("_SMSTSPackageName")
 # Calculate the elapsed build runtime
 $TimeDiff = New-TimeSpan -Start $OSDStartInfo -End $OSDFinishInfo
 # Break the elapsed time down into units
 $Days = $TimeDiff.Days
 $Hrs = $TimeDiff.Hours
 $Mins = $TimeDiff.Minutes
 $Sec = $TimeDiff.Seconds
 # Boil it all down into the number of minutes the build took
 # Days (just in case)
 IF ($Days -gt 0) {$dMins = ($Days * 24) * 60}
 # Hours
 IF ($Hrs -gt 0) {$hMins = $Hrs * 60}
 # Seconds into a fraction of a minute
 $mSec = $Sec / 60
 # Limit to 2 decimal places
 $mSec = "{0:N2}" -f $mSec
 # Add it all up to get the number of minutes the build took
 $ElapsedTime = $TimeDiff.TotalMinutes
 $ElapsedTime = "{0:N2}" -f $ElapsedTime
 $ElapsedTime = $ElapsedTime -replace '[,]'
 # Write all of the results data to the master CSV
 Add-Content -Value "$env:COMPUTERNAME," -Path $CSVfile -NoNewline
 Add-Content -Value "$OSDStartInfo," -Path $CSVfile -NoNewline
 Add-Content -Value "$OSDFinishInfo," -Path $CSVfile -NoNewline
 Add-Content -Value "$OSModelInfo," -Path $CSVfile -NoNewline
 Add-Content -Value "$TSName," -Path $CSVfile -NoNewline
 Add-Content -Value "$Record," -Path $CSVfile -NoNewline
Add-Content -Value "$ElapsedTime," -Path $CSVfile -NoNewline

# If it was a failed build, find the reason in the SMSTS log and record it
IF ($Record -eq "Failure")
{
$SmstFailAction = Get-SmstsFailAction $SMSTSfile
Add-Content -Value "$SmstFailAction" -Path $CSVfile
}

IF ($Record -eq "Success")
{
# We'll record "success" in the failure reason so as not to cause problems with reporting
Add-Content -Value "Success" -Path $CSVfile
}
}

# =============================================================================
Write-Host "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
Write-Host "Finished $MyScript (v $MyVersion)"
Write-Host "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
# Finished
# =============================================================================

Breakdown of the Script

The script accepts 3 parameters; 1) Start, 2) Success, and 3) Failure.  These parameters trigger the different blocks of code within the script.

Start

When the script is executed at the start of the task sequence and passed the “Start” parameter it will set a task sequence variable (OSDStartInfo) to the current date and time.  It then pulls the computer model from WMI and sets that to a variable (OSDModelInfo).

IF ($Record -eq "Start")
{
# Record the starting date and time for this build
Write-Host "Recording the starting date and time for this build"
$tsenv.Value("OSDStartInfo") = (Get-Date -Format "dd-MMM-yyyy HH:mm:ss")
Write-Host "This build began on: " $tsenv.Value("OSDStartInfo")
Write-Host "Recording the model of computer being built..."
$tsenv.Value("OSDModelInfo") = (Get-WmiObject -Class Win32_ComputerSystem).Model
Write-Host "This model: " $tsenv.Value("OSDModelInfo")
}

I query WMI directly because 1) I’m running this script well before the MDT Gather action and 2) not everyone will be using and MDT integrated task sequence.

Success or Failure

At the end of the task sequence, the script is run again but passed either the parameter “Sucess” or “Failure”.

In either case, the finish time is recorded, the elapsed build time is calculated and the data is written to the CSV file on the network share.

In the case of a failed build, the function “Get-SmstsFailAction” is executed.  This function will search the SMSTS log for the reason for the failure.  This is then also written to the CSV file.

The data written will ultimately look like the following samples:

Successful:

PC01,19-Aug-2017 04:22:06,19-Aug-2017 05:40:03,HP ProDesk 600 G2 SFF,Windows 7 v 6.01.3 (64bit),Success,77.95,Success

Failed:

PC02,19-Aug-2017 9:24,19-Aug-2017 11:07,HP ProDesk 600 G2 DM,Windows 7 v 6.01 (64bit),Failure,102.85,Run Invoke-MbamClientDeployment Script.

Okay, we have our share set up and the script ready.  Now let’s add it to a task sequence.

OSDResults_TaskSequence

First Running of the Script

Executing the script will be accomplished using a group of 3 actions.

  1. Connect to the network share
  2. Execute the script
  3. Disconnect from the network share

Screen Shot 2017-08-20 at 6.05.29 PM

At the start of my sequence, I set a variable for the server\share that I will be using.  I prefer using a variable so that if I have to change things I just have to change the variable once instead of making multiple changes (and possibly missing some) throughout the sequence.

OSDResults_TS_SetVariable

Connect to the network share

OSDResults_TS_ConnectShare

Execute the script

OSDResults_TS_Executescript

“Record Build Start Data”
Command Line:
PowerShell -ExecutionPolicy Bypass -File %OSDResultShare%\OSDResults.ps1 -Record Start

Disconnect from the network share

OSDResults_TS_DisConnectShare

Second Running of the Script

The second running of the script at the end of the sequence will use the same 3 actions with the only difference being a change in the parameter fed to the script.

I use the same logic Steve Rachui uses in his blog Capturing Logs During Failed Task Sequence Execution to handle success or failure processing at the end of the task sequence.

Screen Shot 2017-08-20 at 6.04.41 PM

If everything completed successfully we flow into the “Success Catching Group” and we record the build as being successful.

“Record Build Finish Data (Success)”
Command Line:
PowerShell -ExecutionPolicy Bypass -File %OSDResultShare%\OSDResults.ps1 -Record Success

If there was a build failure then we flow into the “Failure Catching Group” and we record the build as failing.  This also triggers the script to search the SMSTS log for the reason for the failure which is recorded.

“Record Build Finish Data (Failure)”
Command Line:
PowerShell -ExecutionPolicy Bypass -File %OSDResultShare%\OSDResults.ps1 -Record Failure

Boot Image Changes

For this script to work at the beginning of the task sequence (when running inside of WinPE) you will need to add the PowerShell component to your boot image.

Screen Shot 2017-08-20 at 6.07.42 PM

Without this component, the script will fail to run in WinPE.  You will get a “file not found” error in the SMSTS log when the task sequence engine can not find PowerShell.exe in your boot image.

Deploying Windows 7/8/8.1

The -NoNewLine parameter used in the script is not available in PowerShell versions prior to 5.0.  If you are deploying Windows 7/8/8.1 you will need to make sure that PowerShell 5+ is installed and functioning otherwise, the script will fail on the Add-Content lines.

As an alternative,  you could modify the script and generate 1 single line that is added to the CSV all at once.  Something similar to this:


# Write all of the results data to the master CSV

# If it was a failed build, find the reason in the SMSTS log and record it
IF ($Record -eq "Failure")
{
$SmstFailAction = Get-SmstsFailAction
$Results = "$env:COMPUTERNAME,$OSDStartInfo,$OSDFinishInfo,$OSModelInfo,$TSName,$Record,$ElapsedTime,$SmstFailAction"
$SMSTSfile Add-Content -Value $Results -Path $CSVfile
}


Part 2: Reporting on the Results

With the data collected, you can then report on it using a variety of tools.  You could open the CSV in Excel and create graphs and pivot tables to illustrate the results.  You could import it into Tableau or Power BI and create the reports.

I’ll cover that in my next posting.


Download OSDResults.ps1

 

Posted on August 22, 2017, in Configuration Manager, Handy to Have, PowerShell, SCCM, Task Sequences and tagged , , , . Bookmark the permalink. 6 Comments.

  1. Nicholas Pryor

    Looking forward to trying this out. Thanks for your hard work

    Like

  2. Great script, thanks for sharing. I can see loads of thought has gone into writing the script.

    One tweak I would make, use a command line task to set the TS Variable OSDStartInfo

    powershell (New-Object -COMObject Microsoft.SMS.TSEnvironment).Value(‘OSDStartInfo’) = (Get-Date -Format dd.MM.yyyy’ ‘HH:mm:ss)

    Liked by 1 person

  1. Pingback: MiSCUG – Using Power BI to Report OS Deployment Stats | The Systems Monkey

  2. Pingback: Send Task Sequence Microsoft Teams | Did You Check The Logs?

Leave a comment