function Convert-WuaResultCodeToName {
param( [Parameter(Mandatory=$true)]
[int] $ResultCode
)
$Result = $ResultCode
switch($ResultCode)
{
0 { $Result = "Not Started" }
1 { $Result = "In Progress" }
2 { $Result = "Succeeded" }
3 { $Result = "Succeeded With Errors" }
4 { $Result = "Failed" }
5 { $Result = "Aborted" }
}
return $Result
}
Function Get-PendingReboot {
<#
.SYNOPSIS
Gets the pending reboot status on a local computer. Return
.DESCRIPTION
Queries the registry and WMI to determine if the system waiting for a reboot, from:
CBServicing = Component Based Servicing (Windows 2008)
WindowsUpdate = Windows Update / Auto Update (Windows 2003 / 2008)
CCMClientSDK = SCCM 2012 Clients only (DetermineIfRebootPending method) otherwise $null value
PendFileRename = PendingFileRenameOperations (Windows 2003 / 2008)
Returns hash table similar to this:
Computer : MYCOMPUTERNAME
LastBootUpTime : 01/12/2014 11:53:04 AM
CBServicing : False
WindowsUpdate : False
CCMClientSDK : False
PendFileRename : False
PendFileRenVal :
RebootPending : False
ErrorMsg :
NOTES:
ErrorMsg only contains something if an error occured
.EXAMPLE
Get-PendingReboot
.EXAMPLE
$PRB=Get-PendingReboot
$PRB.RebootPending
.NOTES
Based On: http://gallery.technet.microsoft.com/scriptcenter/Get-PendingReboot-Query-bdb79542
#>
Begin { }## End Begin Script Block
Process {
Try {
## Setting pending values to false to cut down on the number of else statements
$CompPendRen,$PendFileRename,$Pending,$SCCM = $false,$false,$false,$false
## Setting CBSRebootPend to null since not all versions of Windows has this value
$CBSRebootPend = $null
## Querying WMI for build version
$WMI_OS = Get-WmiObject -Class Win32_OperatingSystem -Property BuildNumber, CSName -ErrorAction Stop
## Making registry connection to the local/remote computer
$HKLM = [UInt32] "0x80000002"
$WMI_Reg = Get-Wmiobject -list "StdRegProv" -namespace root\default
## If Vista/2008 & Above query the CBS Reg Key
If ([Int32]$WMI_OS.BuildNumber -ge 6001) {
$RegSubKeysCBS = $WMI_Reg.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\")
$CBSRebootPend = $RegSubKeysCBS.sNames -contains "RebootPending"
}
## Query WUAU from the registry
$RegWUAURebootReq = $WMI_Reg.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\")
$WUAURebootReq = $RegWUAURebootReq.sNames -contains "RebootRequired"
## Query PendingFileRenameOperations from the registry
$RegSubKeySM = $WMI_Reg.GetMultiStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\Session Manager\","PendingFileRenameOperations")
$RegValuePFRO = $RegSubKeySM.sValue
## Query ComputerName and ActiveComputerName from the registry
$ActCompNm = $WMI_Reg.GetStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\","ComputerName")
$CompNm = $WMI_Reg.GetStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\","ComputerName")
If ($ActCompNm -ne $CompNm) {
$CompPendRen = $true
}
## If PendingFileRenameOperations has a value set $RegValuePFRO variable to $true
If ($RegValuePFRO) {
$PendFileRename = $true
}
## Determine SCCM 2012 Client Reboot Pending Status
## To avoid nested 'if' statements and unneeded WMI calls to determine if the CCM_ClientUtilities class exist, setting EA = 0
$CCMClientSDK = $null
$CCMSplat = @{
NameSpace='ROOT\ccm\ClientSDK'
Class='CCM_ClientUtilities'
Name='DetermineIfRebootPending'
ErrorAction='Stop'
}
## Try CCMClientSDK
Try {
$CCMClientSDK = Invoke-WmiMethod @CCMSplat
}
Catch [System.UnauthorizedAccessException] {
$CcmStatus = Get-Service -Name CcmExec -ComputerName $Computer -ErrorAction SilentlyContinue
If ($CcmStatus.Status -ne 'Running') {
#Write-Warning "$Computer`: Error - CcmExec service is not running."
$CCMClientSDK = $null
}
}
Catch {
$CCMClientSDK = $null
}
If ($CCMClientSDK) {
If ($CCMClientSDK.ReturnValue -ne 0) {
Write-Warning "Error: DetermineIfRebootPending returned error code $($CCMClientSDK.ReturnValue)"
}
If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending) {
$SCCM = $true
}
}
Else {
$SCCM = $null
}
## Creating Custom PSObject and Select-Object Splat
$SelectSplat = @{
Property=(
'Computer',
'CBServicing',
'WindowsUpdate',
'CCMClientSDK',
'PendComputerRename',
'PendFileRename',
'PendFileRenVal',
'RebootPending'
)
}
$results = New-Object -TypeName PSObject -Property @{
Computer=$WMI_OS.CSName
CBServicing=$CBSRebootPend
WindowsUpdate=$WUAURebootReq
CCMClientSDK=$SCCM
PendComputerRename=$CompPendRen
PendFileRename=$PendFileRename
PendFileRenVal=$RegValuePFRO
RebootPending=($CompPendRen -or $CBSRebootPend -or $WUAURebootReq -or $SCCM -or $PendFileRename)
} | Select-Object @SelectSplat
if ($debugging) {$results}
elseIf ($results.rebootpending -eq $true) {
Return "Pending Reboot"
}
elseif ($results.rebootpending -eq $false) {
#Return "$results"
Return "No"
}
else {
Return "Unknown"
}
} # Outer Try Loop
Catch {
#Write-Warning "$Computer`: $_"
Return $_
}
} ## End Process
End { }## End End
}## End Function Get-PendingReboot
function Convert-Product {
return $_.Categories | Where-Object {$_.Type -eq 'Product'} | Select-Object -First 1 -ExpandProperty Name
}
function Get-WuaHistory {
# Get a WUA Session
$session = (New-Object -ComObject 'Microsoft.Update.Session')
# Query the latest 50 History starting with the first recordp
$history = $session.QueryHistory("",0,50) |
ForEach-Object {
$Result = Convert-WuaResultCodeToName -ResultCode $_.ResultCode
$product = Convert-Product $_.Categories
# Make the properties hidden in com properties visible.
$_ | Add-Member -MemberType NoteProperty -Value $Result -Name Result
$_ | Add-Member -MemberType NoteProperty -Value $_.UpdateIdentity.UpdateId -Name UpdateId
$_ | Add-Member -MemberType NoteProperty -Value $_.UpdateIdentity.RevisionNumber -Name RevisionNumber
$_ | Add-Member -MemberType NoteProperty -Value $Product -Name Product
Write-Output $_
}
#Remove null records and only return the fields we want
$history |
Where-Object {![String]::IsNullOrWhiteSpace($_.title)} |
Select-Object Result, Date, Title, SupportUrl, Product, UpdateId, RevisionNumber
}
function Get-PendingUpdates {
# Get a WUA Session
$UpdateSession = (New-Object -ComObject 'Microsoft.Update.Session')
$UpdateSearcher = $UpdateSession.CreateupdateSearcher()
$HasSilverlight = Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -like "*Silverlight*"}
if ($HasSilverlight) {
Log "Has-Silverlight = Yes"
$Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates) `
| Select-Object Title | Out-String
}
else {
Log "Has-Silverlight = No"
$Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates) `
| Where-Object {$_.Title -notlike "*Silverlight*"} `
| Select-Object Title | Out-String
}
return $Updates
}
function Get-ComponentCleanupHistory {
# Event filter for the initial query for all "Start" events in the last 90 days
$EventFilter = @{
LogName = 'Microsoft-Windows-TaskScheduler/Operational'
Id = 100
StartTime = [datetime]::Now.AddDays(-90)
}
# PropertySelector for the Correlation id (the InstanceId) and task name
[string[]]$PropertyQueries = @(
'Event/EventData/Data[@Name="InstanceId"]'
'Event/EventData/Data[@Name="TaskName"]'
)
$PropertySelector = New-Object System.Diagnostics.Eventing.Reader.EventLogPropertySelector @(,$PropertyQueries)
# Loop through the start events
$TaskInvocations = foreach($StartEvent in Get-WinEvent -FilterHashtable $EventFilter) {
# Grab the InstanceId and Task Name from the start event
$InstanceId,$TaskName = $StartEvent.GetPropertyValues($PropertySelector)
if ($TaskName -ne "\Microsoft\Windows\Servicing\StartComponentCleanup") {continue}
# Create custom object with the name and start event, query end event by InstanceId
[pscustomobject]@{
TaskName = $TaskName
StartTime = $StartEvent.TimeCreated
EndTime = $(Get-WinEvent -FilterXPath "*[System[(EventID=102)] and EventData[Data[@Name=""InstanceId""] and Data=""{$InstanceId}""]]" -LogName 'Microsoft-Windows-TaskScheduler/Operational' -ErrorAction SilentlyContinue).TimeCreated
}
} # TaskInvocations
return $TaskInvocations | Out-String
} # Get-ComponentCleanupHistory
function Get-ProcessOutput {
Param (
[Parameter(Mandatory=$true)]$FileName,
$Args
)
$process = New-Object System.Diagnostics.Process
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardError = $true
$process.StartInfo.FileName = $FileName
if($Args) { $process.StartInfo.Arguments = $Args }
$out = $process.Start()
$StandardError = $process.StandardError.ReadToEnd()
$StandardOutput = $process.StandardOutput.ReadToEnd()
$output = New-Object PSObject
$output | Add-Member -type NoteProperty -name StandardOutput -Value $StandardOutput
$output | Add-Member -type NoteProperty -name StandardError -Value $StandardError
return $output
# just return the STDIN string
#return $StandardOutput
}
Function Log {
param(
[Parameter(Mandatory=$true)][String]$msg
)
$now = Get-Date -UFormat "%Y/%m/%d %R"
Add-Content $logfile "$now - $env:computername - $msg"
}
###
### Define variables
###
$SxSDir = "C:\Windows\WinSxS"
#$SxSDir = "." # for testing
$logfile = "C:\Windows\Logs\WindowsUpdate\WinSxS-stats.txt"
$sep = "#" * 132
$crlf = "`r`n"
###
### Main
###
#
# Header
#
Log ("collect-sxs-info - starting" + $crlf)
#
# O/S Status
#
Log "Is a Reboot Pending?: $(Get-PendingReboot)"
Log "Pending Updates $crlf $(Get-PendingUpdates)"
#
# O/S Config
#
Log "Last StartComponentCleanup: $crlf $(Get-ComponentCleanupHistory)"
Log "Scheduled Maintenance Tasks: $(Get-ScheduledTask | ? {$_.Settings.MaintenanceSettings} | Format-Table | Out-String)"
#
# Disk Usage
#
Log "Free Space on C: $(Get-PSDrive C | Select-Object -Property @{E={"{0:N2} GB" -f ($_.Free /1GB)}} | Format-Wide | Out-String)"
#
# WinSxS Folder Usage
#
if ($env:computername -eq "DH3TMR2") {
$duargs = "/c C:\Users\Administrator.CORP\du64.exe -accepteula -nobanner $SxSDir"
}
else {
$duargs = "/c C:\Users\Administrator\du64.exe -accepteula -nobanner $SxSDir"
}
$retval = Get-ProcessOutput -FileName "cmd.exe" -Args $duargs
$output = "$SxSDir Folder Usage" + "$crlf$crlf" + $retval.StandardError + $retval.StandardOutput
Log $output
#
# History
#
Log "Update History $crlf$crlf $(Get-WuaHistory | ConvertTo-Csv -NoType | Out-String)"
#
# Diagnostics
#
# Resource Checker (sfc) quick verify ( no changes )
#
if (0) { # 0 means 'never run', because I don't want to automate this part without realtime runtime monitoring.
$args = "/c sfc /verifyonly"
$retval = Get-ProcessOutput -FileName "cmd.exe" -Args $args
$output = "Resource Checker (sfc) quick verify" + "$crlf$crlf" + $retval.StandardError + $retval.StandardOutput
Log $output
}
else {
Log "Resource Checker (sfc) quick verify is disabled"
}
Log "$crlf$sep$crlf$crlf"
Comments