Thursday, 10 September 2015

PowerShell - Get-SCCMSoftwareUpdateCompliance 2012 R2 Module

Below is an update module I have previously published to now work against SCCM 2012 R2 agents.




function Get-SCCMSoftwareUpdateCompliance {

<#
 .SYNOPSIS
  Checks a ConfigMgr client for required Software Update compliance.

 .DESCRIPTION
  Checks a ConfigMgr client for required Software Update compliance, if it is found non-compliant for any assigned security updates the list of updates is returned.

 .PARAMETER  Computer
  The server name to target.

 .EXAMPLE
  PS C:\> Get-SCCMSoftwareUpdateCompliance -ComputerName "Foo"

#>

[CmdletBinding()]
 param(
  [Parameter(Position=1, Mandatory=$true, HelpMessage="Computer Name", ValueFromPipelineByPropertyName=$True, ValueFromPipeline=$true)]
        [STRING[]] $ComputerName
 )

process{

    foreach ($computer in $ComputerName) {

        if(Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet){
   
            try
      {
                Write-Host "Checking Software Updates Compliance on [$Computer]" -BackgroundColor Blue
         
                try
                    {
                        $SiteCode = $([WmiClass]"\\$computer\ROOT\ccm:SMS_Client").getassignedsite()
                    }
                catch
                    {
                        $SiteCode = "No Site Code Assigned"
                    }
                $ScanHistory = Get-WmiObject -Computer $Computer -Namespace root\ccm\scanagent -Class CCM_scanupdatesourcehistory -ErrorAction Stop #CCM_scantoolhistory -ErrorAction Stop
                $AgentGUID = Get-WmiObject -Computer $Computer -Namespace root\ccm -Class CCM_Client -ErrorAction Stop
                $ScanSource = Get-WmiObject -Computer $Computer -Namespace root\ccm\SoftwareUpdates\WUAhandler -Class CCM_UpdateSource -ErrorAction Stop
                $OSVer = Get-WmiObject -Computer $Computer -Namespace root\cimv2 -Class Win32_OperatingSystem -ErrorAction Stop
                $InstalledComponents = Get-WmiObject -Computer $Computer -Namespace root\ccm -Class CCM_InstalledComponent -ErrorAction SilentlyContinue
                $HighestComponentVer = $InstalledComponents | Sort version | Select version -last 1
                $Explorer = Get-WmiObject -Class win32_process -Computer $Computer -Filter "Name = 'explorer.exe'" | Select -first 1 -ErrorAction SilentlyContinue
                $LastBootUpTime = $osver.ConvertToDateTime($osver.LastBootUpTime)
                $SCCMservice = Get-Service -Computer $Computer ccmexec
                $WUservice = Get-Service -Computer $Computer wuauserv
                $BITSservice = Get-Service -Computer $Computer bits
                $PowerComponent = $InstalledComponents | Where {$_.Name -like '*Power*'}
                $WUVer = (Get-Command \\$Computer\c$\windows\system32\wuapi.dll).fileversioninfo
         

                try
              {
                        $User = $Explorer.getowner()
                    }
                catch
                    {
                    $User = "No User Logged On"
                    }

                $ScanHistory | Select @{Name="SCCM Assigned Site Code";Expression={$SiteCode.sSiteCode}},
                @{Name="Highest Agent Component Version";Expression={$HighestComponentVer.Version}},
                @{Name="Windows Update Agent Version";Expression={$WUVer.ProductVersion}},
                @{Name="Agent GUID";Expression={$AgentGUID.ClientId}},
                @{Name="Logged On User";Expression={$User.Domain,$User.User}},
                @{Name="Last Boot Time";Expression={$LastBootUpTime}},
                @{Name="Last Scan Time";Expression={$_.ConvertToDateTime($_.LastCompletionTime)}},
                @{Name="WSUS CAB Version";Expression={$ScanHistory.UpdateSourceVersion}},
                @{Name="WSUS Scan CAB Source";Expression={$scanSource.Contentlocation}},
                @{Name="Windows Update Service";Expression={$WUservice.Status}},
                @{Name="SMS Agent Host Service";Expression={$SCCMservice.Status}},
                @{Name="BITS Transfer Service";Expression={$BITSservice.Status}},
                @{Name="Operating System";Expression={$OSver.Caption}}

       #check if the machine has an update assignment targeted at it
       $UpdateAssigment = Get-WmiObject -Query "Select * from CCM_AssignmentCompliance" -Namespace root\ccm\SoftwareUpdates\DeploymentAgent -Computer $Computer -ErrorAction Stop
       $UpdateCIAssigment = Get-WmiObject -Query "SELECT * FROM CCM_UpdateCIAssignment" -Namespace "ROOT\ccm\policy\machine\Actualconfig" -ComputerName $Computer -ErrorAction Stop
 
                $statusHash = [hashtable]@{
                    "0" = 'No Content Sources'
                    "1" = 'Available'
                    "2" = 'Submitted'
                    "3" = 'Detecting'
                    "4" = 'Downloading CIDef'
                    "5" = 'Downloading SdmPkg'
                    "6" = 'PreDownload'
                    "7" = 'Downloading'
                    "8" = 'Wait Install'
                    "9" = 'Installing'
                    "10" = 'Pending Soft Reboot'
                    "11" = 'Pending Hard Reboot'
                    "12" = 'Wait Reboot'
                    "13" = 'Verifying'
                    "14" = 'Install Complete'
                    "15" = 'State Error'
                    "16" = 'Wait Service Window'
            }

            $DPLocalityHash = [hashtable]@{
                    "10" = 'UNPROTECTED DP'
                    "74" = 'PROTECTED DP'
             
            }

       #if update assignments were returned check to see if any are non-compliant
       if($UpdateAssigment)
       {
        $IsCompliant = $true
        $UpdateAssigment | ForEach-Object{
         $ID = $_.AssignmentId
         Write-Host "Update Assignment: $($($UpdateCIAssigment | where {$_.AssignmentId -EQ $ID} ).AssignmentName) : " -NoNewline
         if($_.IsCompliant -eq $true){Write-Host "Compliant" -ForegroundColor Green}else{Write-Host "Non-Compliant" -ForegroundColor Red}
   
   
         #mark the compliance as false
         if($_.IsCompliant -eq $false -and $IsCompliant -eq $true){$IsCompliant = $false}
        }
       }
       else
       {
        Write-Host "No Software Update Assignment targeted!" -ForegroundColor Yellow
        return
       }
   
       #the machine is not compliant; search for the updates
       if($UpdateAssigment -and $IsCompliant -eq $false)
       {
 
        #check if the machine has any targeted updates that are missing
        $TargetedUpdates = Get-WmiObject -Query "Select * from CCM_TargetedUpdateEX1 where UpdateState = 0" -Namespace root\ccm\SoftwareUpdates\DeploymentAgent -Computer $Computer -ErrorAction Stop
 
        if($TargetedUpdates)
        {
         $iMissing=0
         $UpdatesMissing=@()



         #loop through updates and get the details.
         $TargetedUpdates | ForEach-Object {

          #get the GUID
          $uID=$_.UpdateID | Select-String -Pattern "SUM_[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}" | Select -Expand Matches | Select -Expand Value
          #strip out the SUM_
          $uID=$uID.Remove(0,4)  
          $uBulletinID=""  
          $uTitle=""
                            $uDPLocality = $DPLocalityHash[$_.DPLocality.tostring()]
                            $uPercentComplete = $_.PercentComplete
                            $uStatus=$statusHash[$_.UpdateStatus.tostring()]
                     
                            #[decimal]$StatusNo = $_.UpdateStatus
                            #$uStatus=$statusHash.ContainsValue($StatusNo)
   
          #query the update status from WMI
          Get-WmiObject -Query "Select * from CCM_UpdateStatus where UniqueID = '$($uID)'" -Namespace root\ccm\SoftwareUpdates\UpdatesStore -Computer $Computer | ForEach-Object {
           $iMissing++
           $uBulletinID = $_.Bulletin
           #if there is no MS00-000 ID swap it for the KB article number
           if($uBulletinID -eq ""){$uBulletinID="KB$($_.Article)"}  
           $uTitle=$_.Title
                         
          }
   
          #Write-Host "[$uBulletinID] :: [$uTitle]"
          $UpdatesMissing+= "[$uBulletinID] :: [$uTitle]::[$uDPLocality]::[%$uPercentComplete]::[$uStatus]"
         }
   
         Write-Host "[$iMissing] required security updates are missing:"-ForegroundColor Red
         #resort the array of missing updates and return it
         $UpdatesMissing=$UpdatesMissing | Sort-Object -Descending
         return $UpdatesMissing
        }  
       }
       #machine is targeted and compliant
       else
       {
        Write-Host "No Missing Software Updates found." -ForegroundColor Green
       }

      }

      catch
      {
       throw
      }

     }
    else
        {
             Write-Host ""
             Write-Host $($Computer).ToUpper()"is NOT Online." -ForegroundColor DarkYellow
             Write-Host ""
        }

        }

    }

}
#Get-SCCMSoftwareUpdateCompliance cptprdmon203

Wednesday, 12 March 2014

SCCM 2012 R2 - Scripted removal of expired or superseded updates with Powershell

Hi All,

Unfortunately SCCM 2012 still has a requirement to manually remove expired and superseded updates. Quite sometime ago I found and modified Trevor Sullivan's Powershell script for use with SCCM 2007. As I have just build a new SCCM 2012 R2 environment for a client I decided I would spend the time to update the script for use with SCCM 2012.

Note: By removing superseded updates via this script I would assume that the newer superseding updates are being included in new deployments.

Original script source:
Trevor Sullivan's Blog
http://trevorsullivan.net/2011/11/29/configmgr-cleanup-software-updates-objects/

New and Updated Features:
  • Updated the script to a parameterized advanced Powershell script.
  • Added Powershell Help functionality.
  • Updated to enable targeting of remote primary site servers.
  • Updated to only trigger a DP refresh for packages which have had updates removed.
  • Commented out the 'SMS_UpdatesAssignment' (Deployments) code section as updates listed in this WMI class now appear to automatically mirror the updates contained in the associated 'Software Update Group' so do not need to reviewed\updated.
  • Added progress bars.
  • Added  the use of Powershell transcripts for basic logging.
  • Tested against SCCM 2012 R2
Actions Performed:
  • Removal of Expired or Superseded updates from:
    • Software Update Groups
    • Software updates packages
  • Distribution Point refresh is triggered for packages which have been modified.
Usage:  

.\Remove-SCCMExpiredSupersededContent_1.0.ps1 [PrimarySiteServerName]

Note: If no server name is specified you will be prompted to provide one.


Download

Sunday, 7 July 2013

SCCM - SSRS report for Summary Update List Non-Compliance


Within the organization I currently contract too SCCM is utilized for the deployment of monthly Microsoft patches and to report on compliance through monthly and quarterly 'Update Lists'. To assist regional server and desktop teams track compliance of assets they manage against these update lists I have created various SSRS Reports, this is the first one I will be blogging about. 

My goal was to enable support teams to easily specify the CollectionID which groups the assets they are responsible for and select one or more update lists to easily identify which machines have not reached a compliant state. I  have tried to include other key pieces of information which are directly related to this kind of a analysis such as the windows update agent version, heartbeat, scan date and a link to another report for all available maintenance windows for the machine.

In addition to grouping all key information for reporting compliance on a per machine basis I have tried to immediately assist teams with their analysis by automatically highlighting when a heartbeat date or scan date is more than three days old. I also highlight when the windows update agent version is below a minimum known good version for SCCM 2007. These indicators should be investigated as a matter of priority, i.e. if the heartbeat is aged then local SCCM agent service is either stopped\offline or the agent is generally unhealthy. 

It is important that the role of the scan date\data is understood clearly by teams performing troubleshooting. I have seen it many times where server teams see a non-compliant server so believe that by manually installing an update this will immediately result in a compliant state. Support teams must understand that the scan process is not only utilized to determine update applicability locally on the machine but also all parent SCCM site servers in the hierarchy will independently calculate the compliance state for the machine based on the submitted scan state data.

Example Report Results. 

Report Features

  • Simple drop down selection criteria for SCCM deployments and deployment collections.
  • More complete overview of key information for each stage of the deployment of updates.
  • Near real time, changes are dependent on inter-site replication and the scan state submitted by client agents.
  • Ideal for drill through links to specific custom and default SCCM reports to display more detailed information on devices in a specific state.
All that should be required to get this report working in other environments that already have SSRS available is to upload the RDL and use Report Builder to modify the server details to point to their SCCM SQL database server name and database name. 

RDL Downloads
https://docs.google.com/file/d/0B2PiqIWBY0KsYlNGcWpfdzBrMEU/edit?usp=sharing

Another report which displays the same data but in a Excel friendly table format.
https://docs.google.com/file/d/0B2PiqIWBY0KsbDNoNEEtMzdfdUE/edit?usp=sharing

Please feel free to post a comment if you are unable to get the report working successfully.

Tuesday, 30 April 2013

Tuning SCCM 2007 & 2012 Site-to-Site Replication with Thread Settings.

For the longest time I continually listened to people referring to SMS\SCCM as 'slow management server' and for a while I agreed, that is until I discovered the settings that I believe can dramatically improve the time it takes to replicate configuration changes throughout a multiple site the hierarchy. I have looked around on the web and spoken with a Microsoft PFE and there does not appear be any publicly available guidance from Microsoft on best practices for tuning site and software distribution thread settings. The approach I outline below was discovered more through trial and observation that anything else and so your mileage may vary if applied to your environment.

To further illustrate my point I have added the below graphs and related notes:

Fig 1.  Multiple concurrent active package distributions and metadata replication backlogging indicating possible thread misconfiguration.
          Top - Schedule.box\outboxes\lan  (Site to Site metadata)
          Bottom – Distribution Manage.box\Incoming (Package Distribution)



Notice in Fig1 the relationship between the number of concurrent package distributions (below) and the minor backlogging in the Schedule.box\outboxes\lan inbox (above). Once the active package distributions completed the backlogging would clear almost immediately. This was due to software package distributions consuming all available threads for a child site resulting in normal site-to-site metadata replication to backlog. Ideally with the all thread settings tuned correctly both package distributions and site to site metadata changes will flow concurrently with neither adversely impacting the other.

Fig 2. The results of tuning thread settings, note the sharp drop in the top graph immediately post change.
          Top - Schedule.box\outboxes\lan  (Site to Site metadata)
          Bottom – Distribution Manage.box\Incoming (Package Distribution)




Notice in Fig 2. that as soon as the four configurable thread settings were tuned, we immediately saw a noticeable reduction in metadata backlogging which has improved the efficiently of site-to-site communication and reliability of package distribution. The graphs above represent a second tier primary site which has over a dozen child primary and secondary sites supporting 25k clients under normal load conditions. Historically the expectation was that configuration changes could take hours or days to replicate to the lowest site tier (5 levels) however after tuning these settings the average is now 15 minutes or less for a change made at the central site to fully replication to all sites globally. This has also had a positive impact on the upstream replication of client 'state' and site 'status' messages.

Standard Sender Properties:

  • 'All Sites: Number of directly connected child sites multiplied by 10 threads.
  •  Per Site: 10 threads
Note: The above assumes three directly connected child sites.

Software Distribution Properties:

  •  Max number of packages’ multiplied by ‘Max threads per package’ = 'Per Site' -2


i.e. 4 Max number of concurrent packages distributions multiplied by 2 threads per package = Software distribution (Packages) is limited to a maximum of 8 threads, thus always allowing 2 spare threads for site-to-site replication. 


The above settings will result 2 spare threads per site to always allow site-to-site configuration\metadata to flow and not be blocked by any active package distributions.

Key Points:


  • Rate limiting on addresses should be avoided as its use results in only 1 thread being available for site to site metadata replication and package content transfers. Where ever possible rely on other networking technologies such as QOS or Riverbeds to manage WAN link utilization.
  • Sender ‘Maximum Concurrent Sendings’ thread settings should be set based on the number of directly connected child sites and reviewed periodically.
  • Sender thread settings per site should exceed by at least 2 threads what is configured for package distribution to allow headroom for site to site configuration metadata replication to always occur.
  • As a result of the above tuning, issues or abnormal inbox traffic trends are much easier to identify.

Disclaimer: The above has been tested in a 60k production SCCM 2007  environment. ConfigMgr 2012 has the same configurable settings so I assume the same principles can be applied.


Monday, 11 March 2013

PowerShell - Find SCCM agents that are in an 'unknown' state in DCM reporting.

 
Last week I was able to resolve an ongoing and extremely annoying aspect to SCCM and DCM. When reviewing the evaluation results of a DCM baseline in either the classic or SRS reports you will sometimes find that the machines in compliant and non-compliant state do NOT add up to the total machines targeted for a rule. The annoying part is there is no easy way to determine which machines have not returned and state information, we even engaged Microsoft PSS for a long running support case to attempt to write a custom report to enable us to identify these machines. Using a module I wrote recently I was able to remotely check all assigned DCM baselines against all our site servers and was able to determine which one was having issues evaluating the rules and not returning a state value. As you can see below the server has received policy to assign the additional baselines but is having issues completeing the rule evaluation.
 
 

Wednesday, 5 December 2012

PowerShell Module - Test SCCM Management Point

I regularly find myself having to remotely start the SMS Agent Host service on site servers and found it annoying to have to open an internet browser just to test the SCCM management point URL's. So in an effort to save time I wrote the following PowerShell module to enable me to easily remotely check the availability of the management point for one or many servers from the command line. The example screen capture below displays the default set of results however if you pipe to 'Select *' then you can additionally see the same page content that would have been displayed in the Internet Explorer, such as the certificate value and child site listing.


PowerShell Module Code


function Get-SCCMMPTest {
    <#
   .SYNOPSIS
        Tests the MPList and MPCert SCCM management point URLs
   
        URLs being tested.
        http://localhost/sms_mp/.sms_aut?mpCert
        http://localhost/sms_mp/.sms_aut?mplist

   .PARAMETER  ComputerName
   The computer name to target.

   .EXAMPLE
   PS C:\> Get-SCCMMPTest -ComputerName "Foo"

        .EXAMPLE
   PS C:\> Get-SCCMMPTest Foo

        .EXAMPLE
   PS C:\> Get-SCCMMPTest Foo Foo2

    #>
[CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, HelpMessage="Computer Name",ValueFromPipeline=$true)] $ComputerName
    )

Process {
 
    Foreach ($Computer in $ComputerName) {

        if(Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet){

            $Computer = $Computer.toupper()
            $urlsToTest = @{}
            $urlsToTest["MPCert"] = "http://$Computer/sms_mp/.sms_aut?mpcert"
            $urlsToTest["MPList"] = "http://$Computer/sms_mp/.sms_aut?mplist"

            $userAgent = "PowerShell User"
            $webClient = new-object System.Net.WebClient
            $webClient.Headers.Add("user-agent", $userAgent)

            Foreach ($key in $urlsToTest.Keys) {
         
               $output = $null
               $startTime = get-date
         
               Try{              
                    $output = $webClient.DownloadString($urlsToTest[$key])                          
                     }
         
               Catch {
               
                     }
         
               $endTime = get-date

               Try{
                    $SCCMservice = Get-Service -ComputerName $Computer ccmexec
                    }
                Catch{
           
                    }

               if ($output) {
                  $Results = New-Object System.Object
                  $Results | Add-Member -type NoteProperty -name "Computer Name" -value $Computer
                  $Results | Add-Member -type NoteProperty -name "SMS Agent Host Service" -value $SCCMservice.Status
                  $Results | Add-Member -type NoteProperty -name "URL Tested" -value $key
                  $Results | Add-Member -type NoteProperty -name "State" -value "Success"
                  $Results | Add-Member -type NoteProperty -name "Start Time" -value $startTime.DateTime
                  $Results | Add-Member -type NoteProperty -name "Response Time (Sec)" -value ($endTime - $startTime).TotalSeconds  
                  $Results | Add-Member -type NoteProperty -name "Content" -value $Output                    
                  [String[]]$properties = 'Computer Name','SMS Agent Host Service','URL Tested','State','Start Time','Response Time (Sec)'          
                  [System.Management.Automation.PSMemberInfo[]]$PSStandardMembers = New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet,$properties          
                  $Results | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers                                            
                  $Results    
                            }          
               else {
                  $Results = New-Object System.Object
                  $Results | Add-Member -type NoteProperty -name "Computer Name" -value $Computer
                  $Results | Add-Member -type NoteProperty -name "SMS Agent Host Service" -value $SCCMservice.Status
                  $Results | Add-Member -type NoteProperty -name "URL Tested" -value $key
                  $Results | Add-Member -type NoteProperty -name "State" -value "Fail"
                  $Results | Add-Member -type NoteProperty -name "Start Time" -value $startTime.DateTime
                  $Results | Add-Member -type NoteProperty -name "Response Time (Sec)" -value ($endTime - $startTime).TotalSeconds                    
                  $Results
                            }
                     }
                }
        else
            {
                  $Results = New-Object System.Object
                  $Results | Add-Member -type NoteProperty -name "Computer Name" -value $Computer
                  $Results | Add-Member -type NoteProperty -name "SMS Agent Host Service" -value "Offline"
                  $Results | Add-Member -type NoteProperty -name "URL Tested" -value "Offline"
                  $Results | Add-Member -type NoteProperty -name "State" -value "Fail"
                  $Results | Add-Member -type NoteProperty -name "Start Time" -value "Offline"
                  $Results | Add-Member -type NoteProperty -name "Response Time (Sec)" -value ($endTime - $startTime).TotalSeconds
                  $Results
            }
        }
    }
}

Wednesday, 14 November 2012

PowerShell - Refresh content on all DP's assigned for a package.

Whilst debugging a large SCCM software updates maintenance script I needed to determine how to refresh the content on all assigned distribution points after an update had been removed from a package. The below code will trigger a distribution point refresh on all assigned DP's immediately. When I get time I will look to turn this command in to a module for easier reuse and piping.

$(Get-WmiObject -ComputerName SCCMServer -Namespace root\sms\site_abc -query "Select * from SMS_SoftwareUpdatesPackage where PackageID = 'xxx00088'").RefreshPkgSource()