Collect OSD / IPU Info with Hardware Inventory

Update 2019.01.22 To include User Account that triggered the Upgrade & Unique Build Record Info. Blog Post for "Gather Account Name during IPU" HERE

Update 2018.08.01 To include Hard  Blocker from  Compat Scan, requires you to add a step into the TS to capture the Hard  Blocker to a  TS Variable,  Blog Post HERE

Update 2018.06.05 - I've posted the first WaaS Post, which incorporates this Script into a Compatibility Scan Task Sequence.  I've updated the script since this post, which is available to download, along with the Task Sequence Export in this POST HERE

Several years ago I started to use Jason Sandys’ OSDInfo Script.  I liked the idea of having a script run during OSD that would write information to WMI, which made it easy to inventory.  Now I do IPU (In Place Upgrade) so much more than OSD, and I wanted to do the same, gather important information from IPU and be able to use it for Reporting or creating collection queries.

I’ve taken Jason’s script and modified it… a lot.  All of his plumbing is still in place (functionality to add information to WMI & Registry), but I’ve added a lot of logic around using the script for different functions.

The script is now broken into 3 sections, OSD (Operating System Deployment), CS (Compatibility Scan), IPU (In Place Upgrade).  Using TS Variables, the script will run different sections of the script.  This allows me to use the same script in numerous scenarios.
First, I’ll show you results, and then go into details about how it’s done.  But I feel it’s easier to understand the script and process if you can see the end product.Registry, Creates WaaS Key, then subkeys for OSD and IPU Build Numbers to keep thing separated for historical data.  If you’re a Reg2Mof person, this is probably the way you’ll want to go.

OSD Info:
image

CS & IPU:
CompatScan = Green
IPU = Yellow
Both Processes = Blue (IPU overwrites CS info)

image

WMI: (Makes it easy to import into Hardware Inventory, but the data is spread out a bit instead of in one nice view)
Classes: CompatScan / IPU / OSD
Instances: Build Number
Prosperities: Details from each process

image

SQL: (When using WMI & Hardware Inventory)

OSD:
image
CompatScan:
image
IPU:
image

Used for Report:
image

Ok, now to the “How” section, the Script:  Can be downloaded along with the entire WaaS_Scripts Package Here: WaaS_Scripts Package (685 downloads)
Requires Several TS Variables to function, will cover after script.

<#
.SYNOPSIS
	Sets information during OSD / IPU
   
.DESCRIPTION 
    This script will add build, task sequence, and other information to the OS so that it can later be examined or inventoried.
    Information can be added to the registry, WMI, or both.

.PARAMETER Registry
    This switch will add information to the following location:
        - Registry

.PARAMETER WMI
    This switch will add information to the following location:
        - WMI Repository
    
.EXAMPLE
     Set-OSDInfo.ps1 -WMI -Registry

     Will add all information to the following locations:
        - Registry
        - WMI Repository 

.NOTES
    Modified from the versions by Stephane van Gulick from www.powershellDistrict.com
	V1.1, 2016-5-6: Added, values for Dtae/Time, OS Image ID, UEFI, and Launch Mode
    V1.1G, 2018-5-12: GaryTown Modified Version.  Seperated OSD, IPU & CompatScan sections
     -Includes gathering Setup.exe return code and logging that in "normal terms"
     -Requires several TS variables for this to work
        -SetOSDInfoType (OSD / CS / IPU)
        -SMSTS_FinishTSTime (Time at the end of the TS, used to figure out how long it took)
        -SMSTS_StartTSTime (Time when TS starts, used to figure out how long it took)
        -SMSTS_FinishUpgradeTime (Time at end of Upgrade Step, figure out how long setup engine ran)
        -SMSTS_StartUpgradeTime (Time at star of Upgrade Step, figure out how long setup engine ran)
        -SMSTS_BUILD (used to keep Build Upgrades seperate)
        -SMSTS_DMDepartment (Purely Environmental, can modify to fit needs, or remove)
        -SMSTS_DMLocation (Purely Environmental, can modify to fit needs, or remove)
        -CheckReadinessResult (Created if CheckReadiness Step Fails)
    V1.2, 2019-1-17: Added Info to record UBR & UserAccount
.LINK
	http://blog.configmgrftw.com
	https://garytown.com for Modifications to Jason's Original Script

.VERSION
    2019.01.17
#>
[cmdletBinding()]
Param(
        [Parameter(Mandatory=$false)][switch]$WMI,
        [Parameter(Mandatory=$false)][switch]$Registry,
        [Parameter(Mandatory=$false)][String]$Namespace,
        [Parameter(Mandatory=$false)][String]$Class,
        [Parameter(Mandatory=$true)][String]$ID,
        [Parameter(Mandatory=$false)][String]$AttributePrefix = "WaaS_"
)
# Start-Transcript >> $env:temp\PowerShellTranscript.log

Function Get-WMINamespace
{
  <#
	.SYNOPSIS
		Gets information about a specified WMI namespace.

	.DESCRIPTION
		Returns information about a specified WMI namespace.

    .PARAMETER  Namespace
		Specify the name of the namespace where the class resides in (default is "root\cimv2").

	.EXAMPLE
		Get-WMINamespace
        Lists all WMI namespaces.

	.EXAMPLE
		Get-WMINamespace -Namespace cimv2
        Returns the cimv2 namespace.

	.NOTES
		Version: 1.0

	.LINK
		http://blog.configmgrftw.com

#>
[CmdletBinding()]
	Param
    (
        [Parameter(Mandatory=$false,valueFromPipeLine=$true)][string]$Namespace
	)  
    begin
	{
		Write-Verbose "Getting WMI namespace $Namespace"
    }
    Process
	{
        if ($Namespace)
        {
            $filter = "Name = '$Namespace'"
            $return = Get-WmiObject -Namespace "root" -Class "__namespace" -filter $filter
        }
		else
		{
            $return = Get-WmiObject -Namespace root -Class __namespace
        }
    }
    end
	{
        return $return
    }
}

Function New-WMINamespace
{
<#
	.SYNOPSIS
		This function creates a new WMI namespace.

	.DESCRIPTION
		The function creates a new WMI namespsace.

    .PARAMETER Namespace
		Specify the name of the namespace that you would like to create.

	.EXAMPLE
		New-WMINamespace -Namespace "ITLocal"
        Creates a new namespace called "ITLocal"
		
	.NOTES
		Version: 1.0

	.LINK
		http://blog.configmgrftw.com

#>
[CmdletBinding()]
	Param(
        [Parameter(Mandatory=$true,valueFromPipeLine=$true)][string]$Namespace
	)

	if (!(Get-WMINamespace -Namespace "$Namespace"))
	{
		Write-Verbose "Attempting to create namespace $($Namespace)"

		$newNamespace = ""
		$rootNamespace = [wmiclass]'root:__namespace'
        $newNamespace = $rootNamespace.CreateInstance()
		$newNamespace.Name = $Namespace
		$newNamespace.Put() | out-null
		
		Write-Verbose "Namespace $($Namespace) created."

	}
	else
	{
		Write-Verbose "Namespace $($Namespace) is already present. Skipping.."
	}
}

Function Get-WMIClass
{
  <#
	.SYNOPSIS
		Gets information about a specified WMI class.

	.DESCRIPTION
		Returns the listing of a WMI class.

	.PARAMETER  ClassName
		Specify the name of the class that needs to be queried.

    .PARAMETER  Namespace
		Specify the name of the namespace where the class resides in (default is "root\cimv2").

	.EXAMPLE
		get-wmiclass
        List all the Classes located in the root\cimv2 namespace (default location).

	.EXAMPLE
		get-wmiclass -classname win32_bios
        Returns the Win32_Bios class.

	.EXAMPLE
		get-wmiclass -Class MyCustomClass
        Returns information from MyCustomClass class located in the default namespace (root\cimv2).

    .EXAMPLE
		Get-WMIClass -Namespace ccm -Class *
        List all the classes located in the root\ccm namespace

	.EXAMPLE
		Get-WMIClass -NameSpace ccm -Class ccm_client
        Returns information from the cm_client class located in the root\ccm namespace.

	.NOTES
		Version: 1.0

	.LINK
		http://blog.configmgrftw.com

#>
[CmdletBinding()]
	Param
	(
		[Parameter(Mandatory=$false,valueFromPipeLine=$true)][string]$Class,
        [Parameter(Mandatory=$false)][string]$Namespace = "cimv2"
	)  
    begin
	{
		Write-Verbose "Getting WMI class $Class"
    }
    Process
	{
		if (Get-WMINamespace -Namespace $Namespace)
		{
			$namespaceFullName = "root\$Namespace"

            Write-Verbose $namespaceFullName
		
			if (!$Class)
			{
				$return = Get-WmiObject -Namespace $namespaceFullName -Class * -list
			}
			else
			{
				$return = Get-WmiObject -Namespace $namespaceFullName -Class $Class -list
			}
		}
		else
		{
			Write-Verbose "WMI namespace $Namespace does not exist."
			
			$return = $null
		}
    }
    end
	{
        return $return
    }
}

Function New-WMIClass
{
<#
	.SYNOPSIS
		This function creates a new WMI class.

	.DESCRIPTION
		The function create a new WMI class in the specified namespace.
        It does not create a new namespace however.

	.PARAMETER Class
		Specify the name of the class that you would like to create.

    .PARAMETER Namespace
		Specify the namespace where class the class should be created.
        If not specified, the class will automatically be created in "root\cimv2"

    .PARAMETER Attributes
		Specify the attributes for the new class.

    .PARAMETER Key
		Specify the names of the key attribute (or attributes) for the new class.

	.EXAMPLE
		New-WMIClass -ClassName "OSD_Info"
        Creates a new class called "OSD_Info"
    .EXAMPLE
        New-WMIClass -ClassName "OSD_Info1","OSD_Info2"
        Creates two classes called "OSD_Info1" and "OSD_Info2" in the root\cimv2 namespace

	.NOTES
		Version: 1.0

	.LINK
		http://blog.configmgrftw.com

#>
[CmdletBinding()]
	Param(
		[Parameter(Mandatory=$true,valueFromPipeLine=$true)][string]$Class,
        [Parameter(Mandatory=$false)][string]$Namespace = "cimv2",
        [Parameter(Mandatory=$false)][System.Management.Automation.PSVariable[]]$Attributes,
        [Parameter(Mandatory=$false)][string[]]$Key
	)

	$namespaceFullName = "root\$Namespace"
	
	if (!(Get-WMINamespace -Namespace $Namespace))
	{
		Write-Verbose "WMI namespace $Namespace does not exist."

	}

    elseif (!(Get-WMIClass -Class $Class -NameSpace $Namespace))
	{
		Write-Verbose "Attempting to create class $($Class)"
			
		$newClass = ""
		$newClass = New-Object System.Management.ManagementClass($namespaceFullName, [string]::Empty, $null)
		$newClass.name = $Class

        foreach ($attr in $Attributes)
        {
            $attr.Name -match "$AttributePrefix(?.*)" | Out-Null
            $attrName = $matches['attributeName']

            $newClass.Properties.Add($attrName, [System.Management.CimType]::String, $false)
            Write-Verbose "   added attribute: $attrName"
        }

        foreach ($keyAttr in $Key)
        {
            $newClass.Properties[$keyAttr].Qualifiers.Add("Key", $true)
            Write-Verbose "   added key: $keyAttr"
        }


		$newClass.Put() | out-null
			
		Write-Verbose "Class $($Class) created."
	}
	else
	{
		Write-Verbose "Class $($Class) is already present. Skipping..."
    }

}

Function New-WMIClassInstance
{
    <#
	.SYNOPSIS
		Creates a new WMI class instance.

	.DESCRIPTION
		The function creates a new instance of the specified WMI class.

	.PARAMETER  Class
		Specify the name of the class to create a new instance of.

	.PARAMETER Namespace
        Specify the name of the namespace where the class is located (default is Root\cimv2).

	.PARAMETER Attributes
        Specify the attributes and their values using PSVariables.

	.EXAMPLE
        $MyNewInstance = New-WMIClassInstance -Class OSDInfo
        
        Creates a new instance of the WMI class "OSDInfo" and sets its attributes.
		
	.NOTES
		Version: 1.0

	.LINK
		http://blog.configmgrftw.com

#>

[CmdletBinding()]
	Param
    (
		[Parameter(Mandatory=$true)]
        [ValidateScript({
            $_ -ne ""
        })][string]$Class,
        [Parameter(Mandatory=$false)][string]$Namespace="cimv2",
        [Parameter(Mandatory=$false)][System.Management.Automation.PSVariable[]]$Attributes
	)

    $classPath = "root\$($Namespace):$($Class)"
    $classObj = [wmiclass]$classPath
    $classInstance = $classObj.CreateInstance()

    Write-Verbose "Created instance of $Class class."

    foreach ($attr in $Attributes)
    {
        $attr.Name -match "$AttributePrefix(?.*)" | Out-Null
        $attrName = $matches['attributeName']

        if ($attr.Value) 
        {
            $attrVal = $attr.Value
        } 
        else 
        {
            $attrVal = ""
        }

        $classInstance[$attrName] = $attrVal
        "   added attribute value for $($attrName): $($attrVal)" >> $env:temp\newWMIInstance.log 
    }

    $classInstance.Put()
}

Function New-RegistryItem
{
<#
.SYNOPSIS
	Sets a registry value in the specified key under HKLM\Software.
   
.DESCRIPTION 
    Sets a registry value in the specified key under HKLM\Software.
	
	
.PARAMETER Key
    Species the registry path under HKLM\SOFTWARE\ to create.
    Defaults to OperatingSystemDeployment.


.PARAMETER ValueName
    This parameter specifies the name of the Value to set.

.PARAMETER Value
    This parameter specifies the value to set.
    
.Example
     New-RegistryItem -ValueName Test -Value "abc"

.NOTES
	-Version: 1.0
	
#>



    [cmdletBinding()]
    Param(


        [Parameter(Mandatory=$false)]
        [string]$Key = "OperatingSystemDeployment",

        [Parameter(Mandatory=$true)]
        [string]$ValueName,

        [Parameter(Mandatory=$false)]
        [string]$Value
        
    )
    begin
    {
        $registryPath = "HKLM:SOFTWARE\WaaS\$ID"
    }
    Process
    {
        if ($registryPath -eq "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\")
        {
            write-verbose "The registry path that is tried to be created is the uninstall string.HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\."
            write-verbose "Creating this here would have as consequence to erase the whole content of the Uninstall registry hive."
                        
            exit 
        }

        ##Creating the registry node
        if (!(test-path $registryPath))
        {
            write-verbose "Creating the registry key at : $($registryPath)."
            
            try
            {
                New-Item -Path $registryPath -force -ErrorAction stop | Out-Null
            }
            catch [System.Security.SecurityException]
            {
                write-warning "No access to the registry. Please launch this function with elevated privileges."
            }
            catch
            {
                write-host "An unknown error occurred : $_ "
            }
        }
        else
        {
            write-verbose "The registry key already exists at $($registryPath)"
        }

        ##Creating the registry string and setting its value
        write-verbose "Setting the registry string $($ValueName) with value $($Value) at path : $($registryPath) ."

        try
        {
            New-ItemProperty -Path $registryPath  -Name $ValueName -PropertyType STRING -Value $Value -Force -ErrorAction Stop | Out-Null
        }
        catch [System.Security.SecurityException]
        {
            write-host "No access to the registry. Please launch this function with elevated privileges."
        }
        catch
        {
            write-host "An unknown error occurred : $_ "
        }
    }

    End
    {
    }
}


try
{
    $tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
}
catch
{
	Write-Verbose "Not running in a task sequence."
}

$keyValue = "ID"

# New-Variable -Name "$($AttributePrefix)InstallationDate" -Value $(get-date -uformat "%Y%m%d-%T")

 New-Variable -Name "$($AttributePrefix)$keyValue" -Value $ID


        





if ($tsenv)
{
        #Get what kind of TS: CompatScan (CS) / InPlace Uprade (IPU) / Operating System Deployment (OSD) - Must have Step in TS that lets script know
        $SetOSDInfoType = $tsenv.Value("SetOSDInfoType")
        #Gets the Time in Minutes it takes to run Task Sequence - Requires you to set a Start Variable & Finish Variable (2 steps in TS) 
        $Difference = ([datetime]$TSEnv.Value('SMSTS_FinishTSTime')) - ([datetime]$TSEnv.Value('SMSTS_StartTSTime')) 
        $Difference = [math]::Round($Difference.TotalMinutes)

        #Gets CompatScan Results and Write Code & Friendly Name to Registry
        
        if ($SetOSDInfoType -eq 'IPU' -or $SetOSDInfoType -eq 'CS')
        {
        if ($tsenv.Value("_SMSTSOSUpgradeActionReturnCode"))
        {
        [int64] $decimalreturncode = $tsenv.Value("_SMSTSOSUpgradeActionReturnCode")
        #[int64] $hexreturncode = 0xC1900210
        $hexreturncode = "{0:X0}" -f [int64]$decimalreturncode

                $WinIPURet = @(
            @{ Err = "C1900210"; Msg = 'No compatibility issues.'}
            @{ Err = "C1900208"; Msg = 'Incompatible apps or drivers.' }
            @{ Err = "C1900204"; Msg = 'Selected migration choice is not available.' }
            @{ Err = "C1900200"; Msg = 'Not eligible for Windows 10.' }
            @{ Err = "C190020E"; Msg = 'Not enough free disk space.' }
            @{ Err = "C1900107"; Msg = 'Unsupported Operating System.' }
            @{ Err = "80070652"; Msg = 'Previous Install Pending, Reboot.' }
            @{ Err = "8024200D"; Msg = 'Update Needs to be Downloaded Again.' }
            @{ Err = "0"; Msg = 'Windows Setup completed successfully.' }
            )
        $ErrorMsg = $winipuret | ? err -eq $hexreturncode  | % Msg

        #Gets the Time in minutes it takes to run the Setup.exe Step (CS or IPU only)
        $DifferenceUpgrade = ([datetime]$TSEnv.Value('SMSTS_FinishUpgradeTime')) - ([datetime]$TSEnv.Value('SMSTS_StartUpgradeTime')) 
        $DifferenceUpgrade = [math]::Round($DifferenceUpgrade.TotalMinutes)
        }
        }
	$taskSequenceXML = $tsenv.Value("_SMSTSTaskSequence")
	$imageIDElement = @(Select-Xml -Content $taskSequenceXML -XPath "//variable[@name='ImagePackageID']")
	 
#Run These during OSD
    if ($SetOSDInfoType -eq 'OSD')
    {
        New-Variable -Name "$($AttributePrefix)OSD_BootImageID" -Value $tsenv.Value("_SMSTSBootImageID")
        New-Variable -Name "$($AttributePrefix)OSD_InstallationMethod" -Value $tsenv.Value("_SMSTSMediaType")
        New-Variable -Name "$($AttributePrefix)OSD_OSImageID" -Value $imageIDElement[0].node.InnerText
        
        New-Variable -Name "$($AttributePrefix)OSD_OSBuild" -Value $tsenv.Value("SMSTS_BUILD")
#       New-Variable -Name "$($AttributePrefix)OSD_DMDepartment" -Value $tsenv.Value("SMSTS_DMDepartment")
#	    New-Variable -Name "$($AttributePrefix)OSD_DMLocation" -Value $tsenv.Value("SMSTS_DMLocation")
        New-Variable -Name "$($AttributePrefix)OSD_TSRunTime" -Value "$Difference"
        New-Variable -Name "$($AttributePrefix)OSD_TaskSequenceName" -Value $tsenv.Value("_SMSTSPackageName")
        New-Variable -Name "$($AttributePrefix)OSD_TaskSequenceID" -Value $tsenv.Value("_SMSTSPackageID")
        New-Variable -Name "$($AttributePrefix)OSD_TSDeploymentID" -Value $tsenv.Value("_SMSTSAdvertID")
        New-Variable -Name "$($AttributePrefix)OSD_InstallationDate" -Value $(get-date -uformat "%Y%m%d-%T")
        if ($tsenv.Value("_SMSTSUserStarted") -ne $null)
            {
            New-Variable -Name "$($AttributePrefix)OSD_UserInitiated" -Value $tsenv.Value("_SMSTSUserStarted")
            }
    }

#Run These if IPU
    if ($SetOSDInfoType -eq 'IPU')
    {
        New-Variable -Name "$($AttributePrefix)IPU_UserInitiated" -Value $tsenv.Value("_SMSTSUserStarted")
        New-Variable -Name "$($AttributePrefix)IPU_OSBuild" -Value $tsenv.Value("SMSTS_BUILD")
#	    New-Variable -Name "$($AttributePrefix)IPU_DMDepartment" -Value $tsenv.Value("SMSTS_DMDepartment")
#	    New-Variable -Name "$($AttributePrefix)IPU_DMLocation" -Value $tsenv.Value("SMSTS_DMLocation")
        New-Variable -Name "$($AttributePrefix)IPU_TSRunTime" -Value "$Difference"
        New-Variable -Name "$($AttributePrefix)IPU_TaskSequenceName" -Value $tsenv.Value("_SMSTSPackageName")
        New-Variable -Name "$($AttributePrefix)IPU_TaskSequenceID" -Value $tsenv.Value("_SMSTSPackageID")
        New-Variable -Name "$($AttributePrefix)IPU_TSDeploymentID" -Value $tsenv.Value("_SMSTSAdvertID")
        New-Variable -Name "$($AttributePrefix)IPU_InstallationDate" -Value $(get-date -uformat "%Y%m%d-%T")
        New-Variable -Name "$($AttributePrefix)IPU_CheckReadiness" -Value $tsenv.Value("CheckReadinessResult")   
        #If User Initiated from Software Center, Record User who triggered Upgrade.
        if ($tsenv.Value("_SMSTSUserStarted") -eq "True")
            {
            New-Variable -Name "$($AttributePrefix)IPU_UserAccount" -Value $tsenv.Value("IPU_UserAccount")
            }
        #If Check Readiness Step Passes, Fill in Blanks with Data
        if ($tsenv.Value("CheckReadinessResult") -eq "Pass")
            {
            New-Variable -Name "$($AttributePrefix)IPU_SetupEngineReturn" -Value "$ErrorMsg"
            New-Variable -Name "$($AttributePrefix)IPU_SetupEngineHexCode" -Value "$hexreturncode"
            New-Variable -Name "$($AttributePrefix)IPU_SetupEngineRunTime" -Value "$DifferenceUpgrade"
            }
        #If Check Readiness Step fails, then there is no data, so place "NA" as place holders
        else
            {
            New-Variable -Name "$($AttributePrefix)IPU_SetupEngineReturn" -Value "NA"
            New-Variable -Name "$($AttributePrefix)IPU_SetupEngineHexCode" -Value "NA"
            New-Variable -Name "$($AttributePrefix)IPU_SetupEngineRunTime" -Value "NA"
            }
       
   #Add Build Record Info so you know which Build of OS was deployed
        $UBR = (Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' CurrentBuildNumber)+'.'+(Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' UBR)
        New-Variable -Name "$($AttributePrefix)IPU_Build" -Value $UBR
   
   #Set WaaS Stage Info
        
        # If Check Readiness Results Fail, set WaaS Stage to "IPU CheckReadiness Failed"
        if ($tsenv.Value("CheckReadinessResult") -ne "Pass")     
            {
            New-Variable -Name "$($AttributePrefix)WaaS_Stage" -Value "IPU CheckReadiness Failed"
            }
        #If the Upgrade Step was successful, Set WaaS Stage to "IPU Complete"
        if ($hexreturncode -eq "0") 
            {
            New-Variable -Name "$($AttributePrefix)WaaS_Stage" -Value "IPU Complete"
            }
        #If the Upgrade wasn't successful but got passed the Check Readiness Steps, Set WaaS Stage to "IPU Failed" 
        if ($hexreturncode -ne "0" -and $tsenv.Value("CheckReadinessResult") -eq "Pass") 
            {
            New-Variable -Name "$($AttributePrefix)WaaS_Stage" -Value "IPU Failed"
            }
         
        #Increments the amount of times the IPU TS runs
        try { [int]$Value = Get-ItemPropertyValue -Path "HKLM:SOFTWARE\WaaS\$ID" -Name "IPU_Attempts" -ErrorAction SilentlyContinue } catch {}
        New-Variable -Name "$($AttributePrefix)IPU_Attempts" -Value ($Value + 1).ToString() 
        }
         
 
#This Section runs for CompatScan Task Sequences
    if ($SetOSDInfoType -eq 'CS')
        {    
        #Check if Driver Download was specified
        if ($tsenv.Value("W10X64DRIVERPACKAGE") -eq $null)
            {
            $Skip = "True"
            New-Variable -Name "$($AttributePrefix)CS_TSDriverDLTime" -Value "NA"
            }
        Else
            {
            $DriverDifference = ([datetime]$TSEnv.Value('SMSTS_FinishTSDownTime')) - ([datetime]$TSEnv.Value('SMSTS_StartTSDownTime')) 
            $DriverDifference = [math]::Round($DriverDifference.TotalMinutes)
            #Check to see if this value is already populated from an earlier run, if so, use it, otherwise use the value from the download times.
            try { [int]$DLValue = Get-ItemPropertyValue -Path "HKLM:SOFTWARE\WaaS\$ID" -Name "CS_TSDriverDLTime" -ErrorAction SilentlyContinue } catch {}
            if ($DLValue -ge "1")
                {
                New-Variable -Name "$($AttributePrefix)CS_TSDriverDLTime" -Value ($DLValue).ToString()
                }
            if ($DriverDifference -ge "1")
                {
                New-Variable -Name "$($AttributePrefix)CS_TSDriverDLTime" -Value "$DriverDifference"
                }
            }
        #Check if Download Failed and Write Failed
        if ($tsenv.Value("DownloadDriversPHail") -eq "True"){New-Variable -Name "$($AttributePrefix)CS_Errors" -Value "Driver Download Failed"}
        #If it didn't failed, set value to NA
        Else{New-Variable -Name "$($AttributePrefix)CS_Errors" -Value "NA"}

        New-Variable -Name "$($AttributePrefix)CS_TaskSequenceName" -Value $tsenv.Value("_SMSTSPackageName")
        New-Variable -Name "$($AttributePrefix)CS_TaskSequenceID" -Value $tsenv.Value("_SMSTSPackageID")
        New-Variable -Name "$($AttributePrefix)CS_TSDeploymentID" -Value $tsenv.Value("_SMSTSAdvertID")
        if ($tsenv.Value("CheckReadinessResult") -eq "Pass")
            {
            New-Variable -Name "$($AttributePrefix)CS_SetupEngineReturn" -Value "$ErrorMsg"
            New-Variable -Name "$($AttributePrefix)CS_SetupEngineHexCode" -Value "$hexreturncode"
            New-Variable -Name "$($AttributePrefix)CS_SetupEngineRunTime" -Value "$DifferenceUpgrade"
            }
        else
            {
            New-Variable -Name "$($AttributePrefix)CS_SetupEngineReturn" -Value "NA"
            New-Variable -Name "$($AttributePrefix)CS_SetupEngineHexCode" -Value "NA"
            New-Variable -Name "$($AttributePrefix)CS_SetupEngineRunTime" -Value "NA"
            }
        New-Variable -Name "$($AttributePrefix)CS_TSRunTime" -Value "$Difference"
        New-Variable -Name "$($AttributePrefix)CS_InstallationDate" -Value $(get-date -uformat "%Y%m%d-%T")
        New-Variable -Name "$($AttributePrefix)CS_CheckReadiness" -Value $tsenv.Value("CheckReadinessResult")
        
        if ( $hexreturncode -eq "C1900208"){New-Variable -Name "$($AttributePrefix)CS_HardBlocker" -Value $tsenv.Value("SMSTS_HardBlocker")}
              
         if ( $hexreturncode -eq "C1900210") 
            {
               New-Variable -Name "$($AttributePrefix)WaaS_Stage" -Value "CS Completed Successfully"
               New-Variable -Name "$($AttributePrefix)CS_HardBlocker" -Value "NA"
            }
         Else
            {
             if ($tsenv.Value("CheckReadinessResult") -ne "Pass")
                {
                New-Variable -Name "$($AttributePrefix)WaaS_Stage" -Value "CS CheckReadiness Failed"
                }
            Else
                {
                New-Variable -Name "$($AttributePrefix)WaaS_Stage" -Value "CS CompatScan Failed"
                }
            }
            
        #Increments the amount of times the Precache CompatScan TS runs
        try { [int]$Value = Get-ItemPropertyValue -Path "HKLM:SOFTWARE\WaaS\$ID" -Name "CS_Attempts" -ErrorAction SilentlyContinue } catch {}
        New-Variable -Name "$($AttributePrefix)CS_Attempts" -Value ($Value + 1).ToString() 
                   
    }



    #$customInfo = @()
    #$customInfo = $tsenv.getVariables() | where {$_ -match "$($AttributePrefix).*"}

    #Foreach ($infoItem in $customInfo)
    #{
    #    New-Variable -Name $infoItem -Value $tsenv.value($infoItem)
    #}

}

$customAttributes = Get-Variable -Name "$AttributePrefix*"

if ($PSBoundParameters.ContainsKey("WMI"))
{
    New-WMINamespace -Namespace $Namespace
    New-WMIClass -Namespace $Namespace -Class $Class -Attributes $customAttributes -Key $keyValue
    New-WMIClassInstance -Namespace $Namespace -Class $Class -Attributes $customAttributes
}

if ($PSBoundParameters.ContainsKey("Registry"))
{
    foreach ($attr in $customAttributes)
    {
        $attr.Name -match "$AttributePrefix(?.*)" | Out-Null
        $attrName = $matches['attributeName']

        if ($attr.Value) 
        {
            $attrVal = $attr.Value
        } 
        else 
        {
            $attrVal = ""
        }
        
        Write-Verbose "Setting registry value named $attrName to $attrVal"



        New-RegistryItem -Key "$($Class)\$ID" -ValueName $attrName -Value $attrVal

    }
}

Step in Task Sequences
Required Variables in TS:
SetOSDInfoType: Set to OSD / CS / IPU - Tells the Script which section to run.

I set the osdbuildversion variable in the beginning of the TS, which gets used in the script & command line. NOTE… I’ve changed this to SMSTS_BUILD, as I was using that in other places and make my life eaiser for consisteancy.

image

SMSTS_StartTSTime - Records time at the very start of the TS

powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_StartTSTime') = Get-Date -f 's'}"

SMSTS_FinishTSTime - Records time near the end of the TS, used in the script to calculate run time of TS

powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_FinishTSTime') = Get-Date -f 's'}"

CheckReadinessResult: If Check Readiness Fails, Set to FAIL

SMSTS_StartUpgradeTime - Records time right before Upgrade Step Starts.

powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_StartUpgradeTime') = Get-Date -f 's'}"

SMSTS_FinishUpgradeTime - Records time right after Upgrade Step finishes, to calculate upgrade time

powershell.exe -command "&{$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('SMSTS_FinishUpgradeTime') = Get-Date -f 's'}"

SMSTS_DMDepartment - Collection Variable - Specific to my Test Lab to help track which Department a computer belongs to
SMSTS_DMLocation - Collection Variable - Specific to my Test Lab to help track which Branch computer belongs to
- These are just for sample to show you can collect information like this during IPU / OSD.  Recommend Customization them for your environment or removing them from the script.

OSD: Run Powershell Script:

-ID "OSD" -WMI -Registry -Namespace "GARYTOWN" -Class "OSD" –AttributePrefix "OSD_info"

image

CompatScan: Run PowerShell Script

-ID "%osdbuildversion%" -WMI -Registry -Class "CompatScan" -Namespace "GARYTOWN"

image

IPU: Run PowerShell Script:

-ID "%osdbuildversion%" -WMI -Registry -Class "IPU" -Namespace "GARYTOWN"

image

Add to Hardware Inventory: Set Classes –> Add –> Connect –> Computer Name of machine you ran the script on and you confirmed the WMI NameSpace and values are created –> Ensure you change the namespace to the namespace you specified in the TS-> Connect
image

Check the Boxes and click OK: (I’ve already done this, which is why they say “exists” in this screen capture, and the class names are greyed out, but when you do it the first time, you’ll be able to check those boxes)
image

You can then verify all the fields then and check the ones you want. If you change the script in the future, you’ll need to come back in here and modify what you’ve checked.
OSD:
image
CompatScan:
image

IPU:
image

Used in Collection Queries:

 

And now, as machines run through your process, and hardware inventory is collected, you’ll have useful data to provide.

If you have any questions, let me know.  I've been running this so long, that I might have missed a step I needed to set it all up originally.

Published originally on garytown.com

20 thoughts on “Collect OSD / IPU Info with Hardware Inventory”

  1. Gary, this looks very interesting and i am trying to test it in my IPU TS. I do have few questions...but to start where do you run the script in your TS?

    Reply
  2. Thanks again...This is great and everything is working...For some reason I have TSRun time as 0. I have SMSTS_StartUpgradeTime and SMSTS_FinishUpgradeTime before and after Upgrade Operating System step. I wanted to ask if you can have a post about a report you are using in this post or maybe share an rdl file?
    Thank you.

    Reply
  3. I'm having an issue with the TS Run time and It looks like it because i'm in Australia and our date time format is different to US.
    SMSTS_StartTSTime = 10/26/2018 10:47:27 AM
    SMSTS_FinishTSTime = 26/10/2018 11:11:45 AM
    Here you can see that the StartTSTime is in the US date format where at the end of the task sequence it's in the Australia date time format. Any ideas on how we could work around this issue?

    Reply
    • Interesting, so at the beginning of the TS. I assume this is OSD, not IPU, the machine is running in US, then later in the process you've applied the Australian region. I'd play with formatting it. Check out: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-date?view=powershell-6 -Format FileDateTime. Just remember, if you change for format, you'll have to update each step that sets a time stamp, and update the script as well. Hope that helps. Hit me up on twitter if you run into any issues. Once you've made all the changes, I'd like to hear your results.

      Reply
      • Correct it's only an issue on OSD as the WinPE we are using is en-US.
        I managed to find a way around it by using the following command instead of Get-Date for the SMSTS_StartTSTime variable.
        [datetime]::Now.ToString([cultureinfo]'en-AU')

        Also I did have issues with calculating the TSRunTime in minutes as well.
        I changed the first Difference line to the below.
        $Difference = ([datetime]::ParseExact($TSEnv.Value('SMSTS_FinishTSTime'),"dd/MM/yyyy h:mm:ss tt",$null)) - ([datetime]::ParseExact($TSEnv.Value('SMSTS_StartTSTime'),"dd/MM/yyyy h:mm:ss tt",$null))

        Reply
        • Had issues still calculating the TSRunTime and I've found that this works.
          $Difference = [datetime]$TSEnv.Value('SMSTS_FinishTSTime') - [datetime]$TSEnv.Value('SMSTS_StartTSTime')
          Not sure if your line works or not as I haven't had a chance to try it again but I have just less round brackets.

          Thanks for this amazing script.

          Reply
  4. Hey Gary... I've implemented what you have here and the registry is getting populated properly but when I look at the WMI on the IPU class on the instance IPU.ID it's only pulling in the ID and WaaS_Stage and not the rest of the IPU_* registry items. I'm running my compat scan and IPU in one TS so things go like this in the TS:

    SetOSDInfoType = CS
    Do the compat scan steps
    Pass or fail run Set-ComboInfo.ps1 with the following params: -ID "%SMSTS_Build%" -WMI -Registry -Class "CompatScan" -Namespace ""
    SetOSDInfoType = IPU
    Do the OS Upgrade steps
    Pass or fail run Set-ComboInfo.ps1 with the following params: -ID "%SMSTS_Build%" -WMI -Registry -Class "IPU" -Namespace ""

    Any ideas???

    Reply
    • Figured it out and am posting as reply to my own comment in case anyone else has a setup similar to mine:

      The problem was if it failed during the compat scan steps what I had as the "Pass or fail" on the compat scan... if it failed I was actually running Set-ComboInfo.ps1 with the following params: -ID "%SMSTS_Build%" -WMI -Registry -Class "IPU" -Namespace "" instead of -Class "CompatScan" so when I fixed whatever the issue was and ran the IPU again it wasn't writing anything to WMI for the IPU classes. In my fail section of my TS I've added a condition on what SetOSDInfoType is and to run the correct arguments on Set-ComboInfo.ps1.

      Reply
  5. I am having issues with Jason's part adding the info to WMI, it creates the Class, and only adds ID, Install Date, and UEFI.

    When I pause the TS, and run it manually, the PS dump a bunch of errors saying not found.
    Any suggestions, am I missing something?

    PS C:\Temp1> .\Set-OSDInfo.ps1 -ID Deploy3 -WMI
    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Not found
    At C:\Temp1\Set-OSDInfo.ps1:375 char:9
    + $classInstance[$attrName] = $attrVal
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (:) [], ManagementException
    + FullyQualifiedErrorId : System.Management.ManagementException

    Path : \\.\root\cimv2:OSD_Info.ID="Deploy3"
    RelativePath : OSD_Info.ID="Deploy3"
    Server : .
    NamespacePath : root\cimv2
    ClassName : OSD_Info
    IsClass : False
    IsInstance : True
    IsSingleton : False

    Reply
    • What's the context of when you're running the script? OSD / IPU? WinPE / Full OS? You're launching the command prompt via the Task Sequence and confirming you have access to the Task Sequence Engine? Does it write to the registry ok if you try that? Have you tried changing the Class & NameSpace params? I would be curious if you were to download my old TS that I used to run that in, and copy the step and see if you have different results.
      I know that I had some issues with writing to WMI when I first started out, and ended up having to tweak the Names I was using to get it to work. I no longer write to WMI, and only registry and use Reg2Mof, it's been working better.

      Reply
  6. Hello,

    This is likely a super dumb question. I download your TS's to start playing with and trying to incorporate some of the advanced techniques your using. We are NOT at 1903 sccm yet though, so I believe I HAVE to use the older TS examples? Can/Should I be trying to use the latter ones that indicate they require 1903? IS that a good idea? I currently tried the older pre-cache one but noticed it did not Align with what I added to my inventory.

    So my next question is - is there much reason to try and use the Newer REGtoMOF stuff you have with all those keys or should i be trying to get the ones mentioned above into my inventory? Are those reg keys and mof files here somewhere to download?

    Reply
  7. I have added this to my Hardware inventory and i see data on my primary site but the data is not replicating to my CAS. Any help would be grateful..

    Reply
    • Hey Joe, I'm unsure why it wouldn't be uploading to your CAS. I personally don't have experience with this situation. I'd recommend posting the question on TechNet, or asking it on Twitter.

      Reply
  8. Hi Gary -

    We've been working on IPU and noticed recently that machines running this powershell report the IPU_SetupEngineHexCode, IPU_Set Engine Return and IPU_Setup Engine Run Time is return null value. If you look at the HW Inventory of the client its blank, therefore, causing WaaS_Stage status as IPU Failed. The question is why would this be blank and not 0. Where do we troubleshoot this behavior? Is this script not running? No, that can't be the issue because all other values are written, Check Readiness, IPU_OS Build, etc. Can this be because the OS Upgrade didn't actually complete? No, because the client is reporting to be on 1809 under winver. What value or condition sets Setup engine values? Is there a log to look at or a TS step set to make this?

    Any thoughts?

    Reply
    • This is an older script which I haven't used in awhile, but it is based on Task Sequence Variables. The Task Sequence writes the Setup Engine Return value to a variable (_SMSTSOSUpgradeActionReturnCode), and the script should be gathering it from there. For testing, you dump the variables to a file to make sure you're getting for that.

      Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.