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.
CS & IPU:
CompatScan = Green
IPU = Yellow
Both Processes = Blue (IPU overwrites CS info)
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
SQL: (When using WMI & Hardware Inventory)
Ok, now to the “How” section, the Script: Can be downloaded along with the entire WaaS_Scripts Package Here:
WaaS_Scripts Package (21769 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.
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"
CompatScan: Run PowerShell Script
-ID "%osdbuildversion%" -WMI -Registry -Class "CompatScan" -Namespace "GARYTOWN"
IPU: Run PowerShell Script:
-ID "%osdbuildversion%" -WMI -Registry -Class "IPU" -Namespace "GARYTOWN"
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
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)
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:
CompatScan:
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
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?
check out my latest post, hopefully that will answer your question. https://garytown.com/waas-post-1-precache-compat-scan-ts
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.
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?
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.
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))
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.
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???
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.
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
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.
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?
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..
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.
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?
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.
I would just like to use this for OSD, so do I just add the required variables and the OSD step ?
Just trying the original script at the moment which is not working (not writing to registry) once figured out this and got it working would like to use yours.
The script looks for different variables to run different portions. As long as you set the variable SetOSDInfoType to OSD, it will run the OSD portion of the script.