mikelieman

collect-sxs-info.ps1

Mar 7th, 2021 (edited)
1,108
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. function Convert-WuaResultCodeToName {
  2.     param( [Parameter(Mandatory=$true)]
  3.     [int] $ResultCode
  4.     )
  5.     $Result = $ResultCode
  6.     switch($ResultCode)
  7.     {
  8.         0 { $Result = "Not Started" }
  9.         1 { $Result = "In Progress" }
  10.         2 { $Result = "Succeeded" }
  11.         3 { $Result = "Succeeded With Errors" }
  12.         4 { $Result = "Failed" }
  13.         5 { $Result = "Aborted" }
  14.     }
  15.     return $Result
  16. }
  17.  
  18. Function Get-PendingReboot {
  19.  
  20. <#
  21.  
  22. .SYNOPSIS
  23.     Gets the pending reboot status on a local computer. Return
  24.  
  25. .DESCRIPTION
  26.     Queries the registry and WMI to determine if the system waiting for a reboot, from:
  27.         CBServicing = Component Based Servicing (Windows 2008)
  28.         WindowsUpdate = Windows Update / Auto Update (Windows 2003 / 2008)
  29.         CCMClientSDK = SCCM 2012 Clients only (DetermineIfRebootPending method) otherwise $null value
  30.         PendFileRename = PendingFileRenameOperations (Windows 2003 / 2008)
  31.  
  32.     Returns hash table similar to this:
  33.  
  34.     Computer : MYCOMPUTERNAME
  35.     LastBootUpTime : 01/12/2014 11:53:04 AM
  36.     CBServicing : False
  37.     WindowsUpdate : False
  38.     CCMClientSDK : False
  39.     PendFileRename : False
  40.     PendFileRenVal :
  41.     RebootPending : False
  42.     ErrorMsg :
  43.  
  44.     NOTES:
  45.     ErrorMsg only contains something if an error occured
  46.  
  47. .EXAMPLE
  48.     Get-PendingReboot
  49.  
  50. .EXAMPLE
  51.     $PRB=Get-PendingReboot
  52.     $PRB.RebootPending
  53.  
  54. .NOTES
  55.     Based On: http://gallery.technet.microsoft.com/scriptcenter/Get-PendingReboot-Query-bdb79542
  56. #>
  57.  
  58.     Begin {  }## End Begin Script Block
  59.    
  60.     Process {
  61.  
  62.         Try {
  63.             ## Setting pending values to false to cut down on the number of else statements
  64.             $CompPendRen,$PendFileRename,$Pending,$SCCM = $false,$false,$false,$false
  65.            
  66.             ## Setting CBSRebootPend to null since not all versions of Windows has this value
  67.             $CBSRebootPend = $null
  68.                  
  69.             ## Querying WMI for build version
  70.             $WMI_OS = Get-WmiObject -Class Win32_OperatingSystem -Property BuildNumber, CSName -ErrorAction Stop
  71.      
  72.             ## Making registry connection to the local/remote computer
  73.             $HKLM = [UInt32] "0x80000002"
  74.             $WMI_Reg = Get-Wmiobject -list "StdRegProv" -namespace root\default
  75.            
  76.             ## If Vista/2008 & Above query the CBS Reg Key
  77.             If ([Int32]$WMI_OS.BuildNumber -ge 6001) {
  78.                 $RegSubKeysCBS = $WMI_Reg.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\")
  79.                 $CBSRebootPend = $RegSubKeysCBS.sNames -contains "RebootPending"    
  80.             }
  81.                    
  82.             ## Query WUAU from the registry
  83.             $RegWUAURebootReq = $WMI_Reg.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\")
  84.             $WUAURebootReq = $RegWUAURebootReq.sNames -contains "RebootRequired"
  85.                  
  86.             ## Query PendingFileRenameOperations from the registry
  87.             $RegSubKeySM = $WMI_Reg.GetMultiStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\Session Manager\","PendingFileRenameOperations")
  88.             $RegValuePFRO = $RegSubKeySM.sValue
  89.      
  90.             ## Query ComputerName and ActiveComputerName from the registry
  91.             $ActCompNm = $WMI_Reg.GetStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\","ComputerName")      
  92.             $CompNm = $WMI_Reg.GetStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\","ComputerName")
  93.             If ($ActCompNm -ne $CompNm) {
  94.                 $CompPendRen = $true
  95.             }
  96.                  
  97.             ## If PendingFileRenameOperations has a value set $RegValuePFRO variable to $true
  98.             If ($RegValuePFRO) {
  99.                 $PendFileRename = $true
  100.             }
  101.      
  102.             ## Determine SCCM 2012 Client Reboot Pending Status
  103.             ## To avoid nested 'if' statements and unneeded WMI calls to determine if the CCM_ClientUtilities class exist, setting EA = 0
  104.             $CCMClientSDK = $null
  105.             $CCMSplat = @{
  106.                 NameSpace='ROOT\ccm\ClientSDK'
  107.                 Class='CCM_ClientUtilities'
  108.                 Name='DetermineIfRebootPending'
  109.                 ErrorAction='Stop'
  110.             }
  111.             ## Try CCMClientSDK
  112.             Try {
  113.                 $CCMClientSDK = Invoke-WmiMethod @CCMSplat
  114.             }
  115.             Catch [System.UnauthorizedAccessException] {
  116.                 $CcmStatus = Get-Service -Name CcmExec -ComputerName $Computer -ErrorAction SilentlyContinue
  117.                 If ($CcmStatus.Status -ne 'Running') {
  118.                     #Write-Warning "$Computer`: Error - CcmExec service is not running."
  119.                     $CCMClientSDK = $null
  120.                 }
  121.             }
  122.             Catch {
  123.                 $CCMClientSDK = $null
  124.             }
  125.      
  126.             If ($CCMClientSDK) {
  127.                 If ($CCMClientSDK.ReturnValue -ne 0) {
  128.                     Write-Warning "Error: DetermineIfRebootPending returned error code $($CCMClientSDK.ReturnValue)"    
  129.                 }
  130.                 If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending) {
  131.                     $SCCM = $true
  132.                 }
  133.             }      
  134.             Else {
  135.                 $SCCM = $null
  136.             }
  137.      
  138.             ## Creating Custom PSObject and Select-Object Splat
  139.             $SelectSplat = @{
  140.                 Property=(
  141.                     'Computer',
  142.                     'CBServicing',
  143.                     'WindowsUpdate',
  144.                     'CCMClientSDK',
  145.                     'PendComputerRename',
  146.                     'PendFileRename',
  147.                     'PendFileRenVal',
  148.                     'RebootPending'
  149.                 )
  150.             }
  151.  
  152.             $results = New-Object -TypeName PSObject -Property @{
  153.                 Computer=$WMI_OS.CSName
  154.                 CBServicing=$CBSRebootPend
  155.                 WindowsUpdate=$WUAURebootReq
  156.                 CCMClientSDK=$SCCM
  157.                 PendComputerRename=$CompPendRen
  158.                 PendFileRename=$PendFileRename
  159.                 PendFileRenVal=$RegValuePFRO
  160.                 RebootPending=($CompPendRen -or $CBSRebootPend -or $WUAURebootReq -or $SCCM -or $PendFileRename)
  161.             } | Select-Object @SelectSplat
  162.  
  163.  
  164.             if ($debugging) {$results}
  165.  
  166.             elseIf ($results.rebootpending -eq $true) {
  167.                 Return "Pending Reboot"
  168.             }
  169.             elseif ($results.rebootpending -eq $false) {
  170.                 #Return "$results"
  171.                 Return "No"
  172.             }
  173.             else {
  174.                 Return "Unknown"
  175.             }
  176.         } # Outer Try Loop
  177.         Catch {
  178.             #Write-Warning "$Computer`: $_"
  179.             Return $_
  180.         }      
  181.     } ## End Process
  182.  
  183.     End {  }## End End
  184.  
  185. }## End Function Get-PendingReboot
  186.  
  187. function Convert-Product {
  188.  
  189. return $_.Categories | Where-Object {$_.Type -eq 'Product'} | Select-Object -First 1 -ExpandProperty Name
  190. }
  191.  
  192. function Get-WuaHistory {
  193.  
  194.     # Get a WUA Session
  195.     $session = (New-Object -ComObject 'Microsoft.Update.Session')
  196.     # Query the latest 50 History starting with the first recordp
  197.     $history = $session.QueryHistory("",0,50) |
  198.     ForEach-Object {
  199.  
  200.         $Result = Convert-WuaResultCodeToName -ResultCode $_.ResultCode
  201.         $product = Convert-Product $_.Categories
  202.  
  203.  
  204.         # Make the properties hidden in com properties visible.
  205.         $_ | Add-Member -MemberType NoteProperty -Value $Result -Name Result
  206.         $_ | Add-Member -MemberType NoteProperty -Value $_.UpdateIdentity.UpdateId -Name UpdateId
  207.         $_ | Add-Member -MemberType NoteProperty -Value $_.UpdateIdentity.RevisionNumber -Name RevisionNumber
  208.         $_ | Add-Member -MemberType NoteProperty -Value $Product -Name Product
  209.         Write-Output $_
  210.     }  
  211.     #Remove null records and only return the fields we want
  212.     $history |
  213.         Where-Object {![String]::IsNullOrWhiteSpace($_.title)} |
  214.         Select-Object Result, Date, Title, SupportUrl, Product, UpdateId, RevisionNumber
  215. }
  216.  
  217. function Get-PendingUpdates {
  218.    
  219.    
  220.     # Get a WUA Session
  221.     $UpdateSession = (New-Object -ComObject 'Microsoft.Update.Session')
  222.     $UpdateSearcher = $UpdateSession.CreateupdateSearcher()
  223.  
  224.     $HasSilverlight = Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -like "*Silverlight*"}
  225.    
  226.     if ($HasSilverlight) {
  227.         Log "Has-Silverlight = Yes"
  228.         $Updates    = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates) `
  229.                     | Select-Object Title | Out-String
  230.     }
  231.     else {
  232.         Log "Has-Silverlight = No"
  233.         $Updates    = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates) `
  234.                     | Where-Object {$_.Title -notlike "*Silverlight*"} `
  235.                     | Select-Object Title | Out-String
  236.     }
  237.    
  238.     return $Updates
  239. }
  240.  
  241. function Get-ComponentCleanupHistory {
  242.  
  243.     # Event filter for the initial query for all "Start" events in the last 90 days
  244.     $EventFilter = @{
  245.         LogName = 'Microsoft-Windows-TaskScheduler/Operational'
  246.         Id = 100
  247.         StartTime = [datetime]::Now.AddDays(-90)
  248.     }
  249.  
  250.     # PropertySelector for the Correlation id (the InstanceId) and task name
  251.     [string[]]$PropertyQueries = @(
  252.         'Event/EventData/Data[@Name="InstanceId"]'
  253.         'Event/EventData/Data[@Name="TaskName"]'
  254.     )
  255.    
  256.     $PropertySelector = New-Object System.Diagnostics.Eventing.Reader.EventLogPropertySelector @(,$PropertyQueries)
  257.  
  258.     # Loop through the start events
  259.     $TaskInvocations = foreach($StartEvent in Get-WinEvent -FilterHashtable $EventFilter) {
  260.         # Grab the InstanceId and Task Name from the start event
  261.         $InstanceId,$TaskName = $StartEvent.GetPropertyValues($PropertySelector)
  262.    
  263.         if ($TaskName -ne "\Microsoft\Windows\Servicing\StartComponentCleanup") {continue}
  264.  
  265.         # Create custom object with the name and start event, query end event by InstanceId
  266.         [pscustomobject]@{
  267.             TaskName = $TaskName
  268.             StartTime = $StartEvent.TimeCreated
  269.             EndTime = $(Get-WinEvent -FilterXPath "*[System[(EventID=102)] and EventData[Data[@Name=""InstanceId""] and Data=""{$InstanceId}""]]" -LogName 'Microsoft-Windows-TaskScheduler/Operational' -ErrorAction SilentlyContinue).TimeCreated
  270.         }
  271.            
  272.     } # TaskInvocations
  273.    
  274.     return $TaskInvocations | Out-String
  275.    
  276. } # Get-ComponentCleanupHistory
  277.  
  278. function Get-ProcessOutput {
  279.  
  280.     Param (
  281.                 [Parameter(Mandatory=$true)]$FileName,
  282.                 $Args
  283.     )
  284.    
  285.     $process = New-Object System.Diagnostics.Process
  286.     $process.StartInfo.UseShellExecute = $false
  287.     $process.StartInfo.RedirectStandardOutput = $true
  288.     $process.StartInfo.RedirectStandardError = $true
  289.     $process.StartInfo.FileName = $FileName
  290.     if($Args) { $process.StartInfo.Arguments = $Args }
  291.     $out = $process.Start()
  292.    
  293.     $StandardError = $process.StandardError.ReadToEnd()
  294.     $StandardOutput = $process.StandardOutput.ReadToEnd()
  295.    
  296.     $output = New-Object PSObject
  297.     $output | Add-Member -type NoteProperty -name StandardOutput -Value $StandardOutput
  298.     $output | Add-Member -type NoteProperty -name StandardError -Value $StandardError
  299.     return $output
  300.    
  301.     # just return the STDIN string
  302.     #return $StandardOutput
  303. }
  304.  
  305. Function Log {
  306.     param(
  307.         [Parameter(Mandatory=$true)][String]$msg
  308.     )
  309.    
  310.     $now = Get-Date -UFormat "%Y/%m/%d %R"
  311.     Add-Content $logfile "$now - $env:computername - $msg"
  312. }
  313.  
  314.  
  315. ###
  316. ###  Define variables
  317. ###
  318.  
  319. $SxSDir = "C:\Windows\WinSxS"
  320. #$SxSDir = "."  # for testing
  321.  
  322. $logfile = "C:\Windows\Logs\WindowsUpdate\WinSxS-stats.txt"
  323.  
  324. $sep = "#" * 132
  325. $crlf = "`r`n"
  326.  
  327. ###
  328. ###  Main
  329. ###
  330.  
  331. #
  332. # Header
  333. #
  334. Log ("collect-sxs-info - starting" + $crlf)
  335.  
  336.  
  337. #
  338. # O/S Status
  339. #
  340.  
  341. Log "Is a Reboot Pending?: $(Get-PendingReboot)"
  342.  
  343. Log "Pending Updates $crlf $(Get-PendingUpdates)"
  344.  
  345.  
  346. #
  347. # O/S Config
  348. #
  349.  
  350. Log "Last StartComponentCleanup: $crlf $(Get-ComponentCleanupHistory)"
  351.  
  352. Log "Scheduled Maintenance Tasks: $(Get-ScheduledTask | ? {$_.Settings.MaintenanceSettings} | Format-Table | Out-String)"
  353.  
  354.  
  355. #
  356. # Disk Usage
  357. #
  358.  
  359. Log "Free Space on C: $(Get-PSDrive C | Select-Object -Property @{E={"{0:N2} GB" -f ($_.Free /1GB)}} | Format-Wide | Out-String)"
  360.  
  361. #
  362. # WinSxS Folder Usage
  363. #
  364. if ($env:computername -eq "DH3TMR2") {
  365.     $duargs = "/c C:\Users\Administrator.CORP\du64.exe -accepteula -nobanner $SxSDir"
  366. }
  367. else {
  368.     $duargs = "/c C:\Users\Administrator\du64.exe -accepteula -nobanner $SxSDir"
  369. }
  370. $retval =  Get-ProcessOutput -FileName "cmd.exe" -Args $duargs
  371. $output = "$SxSDir Folder Usage" + "$crlf$crlf" + $retval.StandardError + $retval.StandardOutput
  372. Log $output
  373.  
  374.  
  375. #
  376. # History
  377. #
  378.  
  379. Log "Update History $crlf$crlf $(Get-WuaHistory | ConvertTo-Csv -NoType | Out-String)"
  380.  
  381.  
  382. #
  383. # Diagnostics
  384. #
  385. # Resource Checker (sfc) quick verify ( no changes )
  386. #
  387. if (0) { # 0 means 'never run', because I don't want to automate this part without realtime runtime monitoring.
  388.  
  389.     $args = "/c sfc /verifyonly"
  390.     $retval =  Get-ProcessOutput -FileName "cmd.exe" -Args $args
  391.     $output = "Resource Checker (sfc) quick verify" + "$crlf$crlf" + $retval.StandardError + $retval.StandardOutput
  392.     Log $output
  393.  
  394. }
  395. else {
  396.     Log "Resource Checker (sfc) quick verify is disabled"
  397. }
  398.  
  399. Log "$crlf$sep$crlf$crlf"
  400.  
  401.  
RAW Paste Data