mikelieman icon

collect-sxs-info.ps1

mikelieman | PRO | 03/10/21 02:18:00 AM UTC (Edited) | 0 ⭐ | 1862 👁️ | Never ⏰ | []
PowerShell |

11.88 KB

|

None

|

0 👍

/

0 👎

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