Summary: Use Windows PowerShell to create a formatted report from data stored in XML files.

 

Microsoft Scripting Guy Ed Wilson here. It is still dark outside, and the lights of the neighborhood are slowly beginning to come on. It is really humid this morning. I am sitting at the kitchen table with my laptop checking comments that were posted to the Hey, Scripting Guy! Blog. When I was talking to my friends in Australia the other day, they were complaining that it was only 5 degrees Celsius (41 degrees Fahrenheit). To me that sounds great (the weather guesser is predicting 37 degrees Celsius—100 degrees Fahrenheit today).

Yesterday, I talked with you about using Group Policy to deploy the script I wrote to collect process information.

The GPO has now had time to replicate among my various domain controllers and has updated the various workstations on my network (doesn’t everyone have multiple domain controllers at home?). The network share that is collecting the XML files that are produced via this logon process is shown in the following image.

Image of network share collecting XML files

Because the entire point of this exercise is to reduce the amount of manual work involved in examining startup processes, I realized I needed to write a script that would automatically process the XML files for me. The ParseProcesses.ps1 script is shown here.

ParseProcesses.ps1

Get-ChildItem -path \\hyperv-box\shared -Include "Process*.xml" -Recurse |
ForEach-Object {
New-Variable -Name $_.baseName -Value (Import-Clixml -path $_.FullName)
}
"Getting startup time:"
Get-Variable -Name Process1* |
ForEach-Object { 
$v1 = New-Variable -Name "$($_.name.substring(9))Smss" -Value ($_.value | 
where-object {$_.name -eq "smss"}) -passthru
$v2 = New-Variable -Name "$($_.name.substring(9))Search" -Value ($_.value | 
Where-Object {$_.name -eq "SearchIndexer"}) -passthru
"$($_.name.substring(9)) start time $( $($v2.value.startTime) - 
$($v1.value.startTime) )"
"Startup process order:"
$_.Value | Sort-Object -Property startTime |
Format-Table -Property name, starttime -AutoSize
}
"Listing Process counts:"
Get-Variable -Name process* | Sort-Object -Property name |
Format-Table -Property name, 
@{LABEL="count";EXPRESSION = {$_.value.count} } -autosize
"Getting the late starting processes from point 4"
Get-Variable -Name process4* | 
ForEach-Object {
"$($_.name.substring(9))"
$task = $_.value | Where-Object { $_.name -eq 'taskhost'}
$_.value | Where-Object { $_.startTime -gt $task.startTime } |
Sort-Object -Property company | 
Format-Table -Property name,company, product, starttime -AutoSize
$task = $null
}

The ParseProcesses.ps1 script begins by obtaining a listing of all the XML files in the \\hyperv-box\shared folder that begin with the Word Process. It then creates a new variable using the baseName of the file that contains the reconstituted process object. This section of the script is shown here:

Get-ChildItem -path \\hyperv-box\shared -Include "Process*.xml" -Recurse |
ForEach-Object {
New-Variable -Name $_.baseName -Value (Import-Clixml -path $_.FullName)
}

The next section of the script is a bit jumbled. After displaying a message about getting the startup time, the script gets every variable that begins with the name Process1. These will be the first data points for each computer that has data stored in the folder. Now a new variable is created that contains only an SMSS process object. Next, a new variable is created that only contains a SearchIndexer process object. The starttime of the SMSS process is subtracted from the starttime of the SearchIndexer process, and the result is displayed. Next, a sorted list of all processes from the first process snapshot is displayed in a table. This section of the script is shown here:

"Getting startup time:"
Get-Variable -Name Process1* |
ForEach-Object { 
$v1 = New-Variable -Name "$($_.name.substring(9))Smss" -Value ($_.value | 
where-object {$_.name -eq "smss"}) -passthru
$v2 = New-Variable -Name "$($_.name.substring(9))Search" -Value ($_.value | 
Where-Object {$_.name -eq "SearchIndexer"}) -passthru
"$($_.name.substring(9)) start time $( $($v2.value.startTime) - 
$($v1.value.startTime) )"
"Startup process order:"
$_.Value | Sort-Object -Property startTime |
Format-Table -Property name, starttime -AutoSize
}

This portion of the report is shown in the following image.

Image of portion of script

A table listing the process counts from each of the process snapshots is created. The table uses a custom column that displays a count of the processes. This is shown here:

"Listing Process counts:"
Get-Variable -Name process* | Sort-Object -Property name |
Format-Table -Property name, 
@{LABEL="count";EXPRESSION = {$_.value.count} } -autosize

The process counts portion of the report is shown in the following image.

Image of process counts portion of report

The last thing that is done is to look at all the processes that start up during the final process snapshot. The taskhost process is used as the starting point for this list. The processes, the starttime, the company name, and product name are all listed in a table. This section of the code is shown here:

"Getting the late starting processes from point 4"
Get-Variable -Name process4* | 
ForEach-Object {
"$($_.name.substring(9))"
$task = $_.value | Where-Object { $_.name -eq 'taskhost'}
$_.value | Where-Object { $_.startTime -gt $task.startTime } |
Sort-Object -Property company | 
Format-Table -Property name,company, product, starttime -AutoSize
$task = $null
}

The process table report from this portion of the script is shown here.

Image of process table report

Join us tomorrow as we begin User Management Week. We invite you to follow us on Twitter and Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys