function vce2020-1472{
    # Source: 
    # Original script has been modified to fix a couple of bugs with connection error detection
    # Create output directory on executiong user's desktop
    $date = get-date -Format MMddyyyy
    $rootPath = $env:USERPROFILE + '\Desktop\CISA'
    $fullPath = $rootPath + '\' + $date
    $UpdateList = $fullPath + '\complianceReport.csv'
    if (Test-Path $rootPath){
        if (!(test-path $fullPath)){
                mkdir $fullPath
            }
    }else{
        mkdir $fullPath
        }

        function checkPort{
            Param(
                [string]$server,
                [int]$port=135,
                [bool]$verbose=$true
                )
         
                function importPortQry{
                    if (!(Get-Command portqry.exe -ErrorAction SilentlyContinue)){
                        if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                        Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
                        choco install portqry -y
                        if (Get-Command portqry.exe -ErrorAction SilentlyContinue){return $true}else{return $false}
                    }else{
                        return $true
                    }
                }
                 function Check-NetConnection($server, $port) {
                    $session = New-Object System.Net.Sockets.TcpClient;
                    $result=$false
                    try {
                        $session.Connect($server, $port);
                        $result=$true;
                    } catch {
                        $result=$false;
                    } finally {
                        $session.Close();
                        
                    }
                    return $result;
                }        
        
            function testPortArray {
                # Casting parameters is better than passing arguments as that allows for switches that can be arranged in any order
                param ([string]$targetServer,[array]$arrPorts,$verbose=$false)
                $source = $env:computername
                $destination=$targetServer.toUpper()
                $portsCount=$arrPorts.count
                $portsSuccess=0
        
                foreach ($port in $arrPorts){        
                    $testResult = Check-NetConnection -server $targetServer -port $port;
                    If ($testResult){
                        if($verbose){Write-Output "$port`: reachable"}
                        $portsSuccess++
                        }
                    Else{
                        if($verbose){Write-Output "$port`: unreachable"}
                        }
                }
        
                if($portsCount -eq $portsSuccess){
                    return $true
                }else{
                    return $false
                }
        
            }
             
            if($verbose){write-host "Checking for existence of PortQry.exe..."}
            do {
                $portQryExists=Get-Command "PortQry.exe" -ErrorAction SilentlyContinue
                if(!$portQryExists){
                    importPortQry;
                    sleep 1;
                }
            }while(!$portQryExists)
        
            $portReachable=Check-NetConnection -server $server -port $port     
            if ($portReachable){
                if($verbose){write-host "$env:computername is able to reach $server on the primary port of $port."}
                $strInvokeCommand = "PortQry.exe -e $port -n $server";
                $arrQuryResult = Invoke-Expression $strInvokeCommand;
             
                $arrPorts = @();    #Initiates and clears the collection of ports array
                ForEach ($strResult in $arrQuryResult)
                {
                  If ($strResult.Contains("ip_tcp"))
                  {
                  $arrSplt = $strResult.Split("[");
                  $strPort = $arrSplt[1];
                  $strPort = $strPort.Replace("]","");
                  $arrPorts += $strPort;
                  }
                }
         
                #  Remove duplicate port records from within the array
                $arrPorts = $arrPorts | select -uniq
                if ($arrPorts){            
                    if($verbose){write-host "These are the detected dynamic ephemeral ports being used:`r`n$arrPorts"  }      
                    $result=testPortArray -targetServer $server -arrPorts $arrPorts #  Pass the ports array into the workflow as a parameter
                    return $result
                }else{
                    if($verbose){write-host "Ephemeral ports were NOT detected.`r`n"}
                        return $portReachable
                        }
            }else{
                if($verbose){write-host "$env:computername is NOT able to reach $server on port: $port`r`n"}
                return $false
                }
        }

    ## Get all DCs in forest
    $allDCs = ($((Get-ADForest).Domains | %{ Get-ADDomainController -Filter * -Server $_ })).hostname
    $validHotFixes=@(
        'KB4571729',
        'KB4571719',
        'KB4571736',
        'KB4571702',
        'KB4571703',
        'KB4571723',
        'KB4571694',
        'KB4565349',
        'KB4565351',
        'KB4566782',
        'KB4577051',
        'KB4577038',
        'KB4577066',
        'KB4577015',
        'KB4577069',
        'KB4574727',
        'KB4577062',
        'KB4571744',
        'KB4571756',
        'KB4571748',
        'KB4570333'
    )
    Write-Host -ForegroundColor White ("There are $($allDcs.count) DCs")

    ## Foreach DC, get Component Based Servicing provided updates and MSI installed updates. Then dump to a common CSV
    foreach ($dc in $allDCs) {
        $winRmIsAvailable=Test-NetConnection $dc -commonTCPPort WinRm -informationlevel Quiet
        $rpcIsAvailable=checkPort $dc -port 135 -verbose $false
        
        if($rpcIsAvailable){
            $OS = $(Get-WmiObject -ComputerName $DC -Class Win32_OperatingSystem).caption
            Write-Host -ForegroundColor White ("Getting updates for $DC")
            Write-Host -ForegroundColor White ("Checking CBS Updates...")
            try{
                $quickFixes=Get-WmiObject Win32_quickfixengineering -ComputerName $dc | Select-Object *
            }catch{
                write-warning $_
            }
            if($quickFixes){
                $quickFixes|%{
                    $hotfix=$_.HotFixID 
                    if($hotfix -in $validHotFixes){
                        $hotfixFound=$true
                        $CBSObj = New-Object -TypeName psobject
                        $CBSObj | Add-Member -MemberType NoteProperty -Name "DomainController" -Value $DC
                        $CBSObj | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value $OS
                        $CBSObj | Add-Member -MemberType NoteProperty -Name "Update" -Value $hotfix
                        $CBSObj | Add-Member -MemberType NoteProperty -Name "Compliance" -Value $hotfixFound
                        $CBSObj | Export-Csv -path $UpdateList -Append -NoTypeInformation                
                        #break;
                    }
                }
            }
        }elseif($winRmIsAvailable){
            Write-Host -ForegroundColor White ("Getting updates for $dc`r`nChecking MSI Updates...")
            $session=new-pssession -ComputerName $dc
            if($session){
                $x=Invoke-Command -session $session -ScriptBlock { 
                        $updatesession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$env:computername))
                        $UpdateSearcher = $updatesession.CreateUpdateSearcher()
                        $searchresult = $updatesearcher.Search("IsInstalled=1").Updates|select Title
                        #$updatesSession = New-Object -ComObject Microsoft.Update.Session ;
                        #$updateSearch = $updatesSession.CreateUpdateSearcher();
                        #$hotfixes=$updateSearch.Search("IsInstalled=1").Updates | Select Title;
                        <#
                        Exception calling "Search" with "1" argument(s): "Exception from HRESULT: 0x8024401C"
                        + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
                        + FullyQualifiedErrorId : ComMethodTargetInvocation
                        + PSComputerName        : TESTSERVER
                        #>
                        $os=(Get-WmiObject -Class Win32_OperatingSystem).caption;
                        return @($os,$searchresult)
                    }
                remove-pssession $session
                $hotfixCount=0
                $x[1] | ForEach-Object {
                    $Title = $_.Title
                    $kb=switch -Wildcard ($Title){
                        "*4571729*" {"KB4571729"}
                        "*4571719*" {"KB4571719"}
                        "*4571736*" {"KB4571736"}
                        "*4571702*" {"KB4571702"}
                        "*4571703*" {"KB4571703"}
                        "*4571723*" {"KB4571723"}
                        "*4571694*" {"KB4571694"}
                        "*4565349*" {"KB4565349"}
                        "*4565351*" {"KB4565351"}
                        "*4566782*" {"KB4566782"}
                        "*4577051*" {"KB4577051"}
                        "*4577038*" {"KB4577038"}
                        "*4577066*" {"KB4577066"}
                        "*4577015*" {"KB4577015"}
                        "*4577069*" {"KB4577069"}
                        "*4574727*" {"B4574727"}
                        "*4577062*" {"KB4577062"}
                        "*4571744*" {"KB4571744"}
                        "*4571756*" {"KB4571756"}
                        "*4571748*" {"KB4571748"}
                        "*4570333*" {"KB4570333"}                
                    }
                    if($kb){
                        $hotfixCount++
                        $CBSObj = New-Object -TypeName psobject
                        $CBSObj | Add-Member -MemberType NoteProperty -Name "DomainController" -Value $DC
                        $CBSObj | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value $x[0]
                        $CBSObj | Add-Member -MemberType NoteProperty -Name "Update" -Value $kb
                        $CBSObj | Add-Member -MemberType NoteProperty -Name "Compliance" -Value $True
                        $CBSObj | Export-Csv -path $UpdateList -Append -NoTypeInformation
                        #break
                    }
                }
                if($hotfixCount -eq 0){
                    $MSIObj = New-Object -TypeName psobject
                    $MSIObj | Add-Member -MemberType NoteProperty -Name "DomainController" -Value $dc
                    $MSIObj | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value $x[0]
                    $MSIObj | Add-Member -MemberType NoteProperty -Name "Update" -Value 'Missing'
                    $MSIObj | Add-Member -MemberType NoteProperty -Name "Compliance" -Value $false
                    $MSIObj | Export-Csv -path $UpdateList -Append -NoTypeInformation
                }            
            }else{
                write-warning "Unable to connect to $dc via WinRM"
                $MSIObj = New-Object -TypeName psobject
                $MSIObj | Add-Member -MemberType NoteProperty -Name "DomainController" -Value $dc
                $MSIObj | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value 'unknown'
                $MSIObj | Add-Member -MemberType NoteProperty -Name "Update" -Value 'unknown'
                $MSIObj | Add-Member -MemberType NoteProperty -Name "Compliance" -Value 'unknown'
                $MSIObj | Export-Csv -path $UpdateList -Append -NoTypeInformation
            }
        }else{
            write-warning "Unable to connect to $dc via RPC nor WinRM"
            $MSIObj = New-Object -TypeName psobject
            $MSIObj | Add-Member -MemberType NoteProperty -Name "DomainController" -Value $dc
            $MSIObj | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value 'unknown'
            $MSIObj | Add-Member -MemberType NoteProperty -Name "Update" -Value 'unknown'
            $MSIObj | Add-Member -MemberType NoteProperty -Name "Compliance" -Value 'unknown'
            $MSIObj | Export-Csv -path $UpdateList -Append -NoTypeInformation
        }
    }
    Write-Host -ForegroundColor White ("INFORMATION: DONE!")
}

vce2020-1472