I come across all kinds of situations these days where I think back to myself "Man, this would have been SO much easier with PowerShell!". I just had one of those moment today.

I had a need to find all updates that were missing from a group of computers. Back in the day when I didn't have SCCM, I used Microsoft's HFNetChk tool; yea, it's been that long. I remember having to scan dozens of machines and then figure out how to best merge all that data together. It was a nightmare!

This time I leveraged my PowerShell kung-fu to whip a tiny little function to point at a computer and find all the missing updates. This one's not quite the level that I normally like to share but it gets the job done well.

Download this script on the Technet Script Repository

function Get-MissingUpdates { 
    [CmdletBinding()] 
    [OutputType([System.Management.Automation.PSCustomObject])] 
    param ( 
        [Parameter(Mandatory, 
        ValueFromPipeline, 
        ValueFromPipelineByPropertyName)] 
        [string]$ComputerName 
    ) 
    begin { 
        function Get-32BitProgramFilesPath { 
            if ((Get-Architecture) -eq 'x64') { 
                ${ env:ProgramFiles(x86) } 
            } else { 
                $env:ProgramFiles 
            } 
        } 
         
        function Get-Architecture { 
            if ([System.Environment]::Is64BitOperatingSystem) { 
                'x64' 
            } else { 
                'x86' 
            } 
        } 
         
        $Output = @{ } 
    } 
    process { 
        try { 
             
            ## Remove any previous reports 
            Get-ChildItem "$($Env:USERPROFILE)\SecurityScans\*" -Recurse -ea 'SilentlyContinue' | Remove-Item -Force -Recurse 
            ## Run the report to create the output XML 
            $ExeFilePath = "$(Get-32BitProgramFilesPath)\Microsoft Baseline Security Analyzer 2\mbsacli.exe" 
            if (!(Test-Path $ExeFilePath)) { 
                throw "$ExeFilePath not found" 
            } 
            & $ExeFilePath /target $ComputerName /wi /nvc /o %C% 2>&1> $null 
            ## Convert the report to XML so I can use it 
            [xml]$ScanResults = Get-Content "$($Env:USERPROFILE)\SecurityScans\$($Computername.Split('.')[0]).mbsa" 
 
                        $UpdateSeverityLabels = @{ 
                '0' = 'Other' 
                '1' = 'Low' 
                '2' = 'Moderate' 
                '3' = 'Important' 
                '4' = 'Critical' 
            } 
             
            $MissingUpdates = $ScanResults.SelectNodes("//Check[@Name='Windows Security Updates']/Detail/UpdateData[@IsInstalled='false']") 
            foreach ($Update in $MissingUpdates) { 
                $Ht = @{ } 
                $Properties = $Update | Get-Member -Type Property 
                foreach ($Prop in $Properties) { 
                    $Value = ($Update | select -expandproperty $Prop.Name) 
                    if ($Prop.Name -eq 'Severity') { 
                        $Value = $UpdateSeverityLabels[$Value] 
                    } 
                    $Ht[$Prop.Name] = $Value 
                } 
                [pscustomobject]$Ht 
            } 
        } catch { 
            Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)" 
        } 
    } 
}

Join the Jar Tippers on Patreon

It takes a lot of time to write detailed blog posts like this one. In a single-income family, this blog is one way I depend on to keep the lights on. I'd be eternally grateful if you could become a Patreon patron today!

Become a Patron!