SCCM Script: Fix State Messages

Overview

I developed this tool, Fix-DMGSCCMStateMessage.ps1, to assit in troubleshooting SCCM “In Progress” and State Message Communication issues. If the UpdateStore.log shows that a particular windows update component is installed, but it is still in progress in the SCCM console, the State Message is likely not communicating properly to the SQL server. State messaging is a mechanism in SCCM which replicates point in time conditions on the client.

Fix-DGMSCCMStateMessage Tool

The tool will automatically update the State Message locally on the SCCM client by invoking the following two commands:


$SCCMUpdatesStore = New-Object -ComObject Microsoft.CCM.UpdatesStore
$SCCMUpdatesStore.RefreshServerComplianceState()

The tool requires the –csvfile argument, which is the path to a .CSV file containing one column, Hostname, with the computer hostnames listed in the column and can be run as in the example below.

Fix-DGMSCCMStateMessage Log File

The utility will create a log file that is compatible with the CMTrace tool, which includes the thread, time, state and component for each process.


<#
.Synopsis
   Update the State Message locally on the SCCM client
.DESCRIPTION
   The tool, Fix-DMGSCCMStateMessage.ps1 was written by David Maiolo which will automatically update the State Message locally on the SCCM client by 
   invoking the following two commands: $SCCMUpdatesStore = New-Object -ComObject Microsoft.CCM.UpdatesStore and $SCCMUpdatesStore.RefreshServerComplianceState()
.EXAMPLE
   Fix-DMGSCCMStateMessage -CSVFile state_message_import.csv
.EXAMPLE
   Fix-DMGSCCMStateMessage -Hostname WORKSTATION01
#>


function New-DGMLogFile
{
  param (
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Global:Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}><thread={4}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}><thread={4}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($Global:ScriptStatus -ne 'Error')) { $Global:ScriptStatus = $type }
  if ($type -eq 'Error') { $Global:ScriptStatus = $type }

  if ((Get-Item $Global:LogFile).Length/1KB -gt $Global:MaxLogSizeInKB)
  {
    $log = $Global:LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $Global:LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

function GetScriptDirectory
{
  $invocation = (Get-Variable MyInvocation -Scope 1).Value
  Split-Path $invocation.MyCommand.Path
} 

function GetStringBetweenTwoStrings($firstString, $secondString, $importString){

    #Get content from file
    $file = $importString

    #Regex pattern to compare two strings
    $pattern = "$firstString(.*?)$secondString"

    #Perform the opperation
    $result = [regex]::Match($file,$pattern).Groups[1].Value

    #Return result
    return $result

}

function Fix-DMGSCCMStateMessage
{
    [CmdletBinding()]
    [Alias()]
    [OutputType([int])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='Parameter Set 1')]
                   [ValidateScript({(Test-Path $_)})]
                   $CSVFile,
        # Param2 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='Parameter Set 2')]
                   [ValidateScript({(Get-ADComputer -Identity $_).objectclass -eq 'computer' })]
                   [String]$Hostname
    )

    Begin
    {
        $path = (get-item -Path .).FullName 
        
        if ($CSVFile -ne $null){
            Write-Host Importing $CSVFile...
            $csv = import-csv "$CSVFile"
        }else{
            $csv = [PSCustomObject]@{
                Hostname = $Hostname}
        }
        Write-Host =========================================
        Write-Host SCCM State Message Update Tool
        Write-Host =========================================
        Write-Host "dmaiolo"
        New-DGMLogFile -message ("Starting Logging for Fix-DMGSCCMStateMessage") -component "Main()" -type 1 
    }
    Process
    {
    

    $computers = @();
        
    $csv | foreach-object {
        $h = $_.Hostname
    
        if(Test-Connection -ComputerName $h -Count 1 -ErrorAction SilentlyContinue){

            Try{
               
                $g = $null               
                Invoke-Command -ComputerName $h -ScriptBlock { $SCCMUpdatesStore = New-Object -ComObject Microsoft.CCM.UpdatesStore
                                                               $SCCMUpdatesStore.RefreshServerComplianceState() } -ErrorAction Stop 
                New-DGMLogFile -message ("$h`: State Message Updated Succesfully (RefreshServerComplianceState())") -component "Main()" -type 1
                Write-Host $h`: Sleeping 5 Seconds...
                sleep 5
                $resultsfile = "\\$h\c$\Windows\CCM\Logs\UpdatesStore.log"

                $logLines = @()

                $g = Get-Content  $resultsfile -ErrorAction Stop
                $lastLine = $g.Length
                for($i = $lastLine-1; $i -ge 0; $i--)
                {
                    $g[$i] -match '<\!\[LOG\[(.*?)\]LOG.*<time="(.*?)".*date="(.*?)"' | out-null
                    $logLines += ($h + ': ' + $matches[1] + ' on ' + $matches[3] + ' at ' + $matches[2])
                    if ($matches[1] -eq "Initiated refresh of update compliance to site server.") { break }
                }

                [array]::reverse($logLines)
                Write-Host $h`: Searching for components in $resultsfile...
                $missing = 0
                $installed = 0
                foreach ($i in $logLines){
                    if ($i | Select-String ("Initiated")){
                        New-DGMLogFile -message ("$i") -component "Main()" -type 1
                    }
                    if ($i | Select-String (" Message received")){
                        New-DGMLogFile -message ("$i") -component "Main()" -type 1
                    }
                    if ($i | Select-String ("Successfully called refresh")){
                        New-DGMLogFile -message ("$i") -component "Main()" -type 1
                    }
                    if ($i | Select-String ("Missing")){
                        #New-DGMLogFile -message ("$i") -component "Main()" -type 4
                        $missing++
                    }
                    if ($i | Select-String ("Installed")){
                        $installed++
                    }
                    if ($i | Select-String ("Resend status")){
                        New-DGMLogFile -message ("$i") -component "Main()" -type 1
                    }
                    if ($i | Select-String (" Successfully raised Resync")){
                        New-DGMLogFile -message ("$i") -component "Main()" -type 1
                    }
                    if ($i | Select-String ("Successfully called refresh")){
                        New-DGMLogFile -message ("$i") -component "Main()" -type 1
                    }

                }
                New-DGMLogFile -message ("$h`: $missing missing components included in State Message") -component "Main()" -type 1
                New-DGMLogFile -message ("$h`: $installed installed components included in State Message") -component "Main()" -type 1
            }
            Catch [System.Management.Automation.Remoting.PSRemotingTransportException]{
                New-DGMLogFile -message ("$h`: State Message Could Not Update (errorcode 0x80090322 occurred while using Kerberos authentication: An unknown security error occurred.)") -component "Main()" -type 3
            }
            Catch{
                New-DGMLogFile -message ("$h`: State Message Could Not Update (generic error)") -component "Main()" -type 3
            }
        }
        else{
            New-DGMLogFile -message ("$h`: is offline.") -component "Main()" -type 2
        }

       }
    }
    End
    {
       Write-Host ===============================================================
       Write-Host Log File of Results Generated at $Global:LogFile VIEW WITH CMTRACE.EXE
       New-DGMLogFile -message ("Ending Logging for Fix-DMGSCCMStateMessage") -component "Main()" -type 1
    }
}

$VerboseLogging = "true"
[bool]$Global:Verbose = [System.Convert]::ToBoolean($VerboseLogging)
#$Global:LogFile = Join-Path (GetScriptDirectory) "Fix-DMGSCCMStateMessage_$(Get-Date -Format dd-MM-yyyy).log"
$Global:LogFile = Join-Path (GetScriptDirectory) "Fix-DMGSCCMStateMessage.log"
$Global:MaxLogSizeInKB = 10240
$Global:ScriptName = 'Fix-DMGSCCMStateMessage.ps1' 
$Global:ScriptStatus = 'Success'

Leave a Comment

Your email address will not be published.