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"