├── .attachments ├── Dashboard-B.png └── UpdateReporting001-level.PNG ├── Import-SSRSReports.ps1 ├── README.md └── SourceFiles ├── Compare Update Compliance.rdl ├── Software Updates Compliance - ErrorList.rdl ├── Software Updates Compliance - Overview ABC Last ADDS Logon.rdl ├── Software Updates Compliance - Overview ABC Last Policy Update.rdl ├── Software Updates Compliance - Overview ABC Last Reboot.rdl ├── Software Updates Compliance - Overview ABC Last Update Install.rdl ├── Software Updates Compliance - Overview Pending Reboot.rdl ├── Software Updates Compliance - Overview Updates missing not deployed.rdl ├── Software Updates Compliance - Overview compliance list.rdl ├── Software Updates Compliance - Overview current rollup status.rdl ├── Software Updates Compliance - Overview last rollup status.rdl ├── Software Updates Compliance - Overview.rdl ├── Software Updates Compliance - Per device deployments.rdl ├── Software Updates Compliance - Per device.rdl ├── UpdatesInstallErrorList.rsd ├── UpdatesStatePerDevice.rsd ├── UpdatesSummary.rsd └── UpdatesSummary.sql /.attachments/Dashboard-B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasatgit/updatereporting/0ca927dc4442915997421d70dc645b8172f6f7bf/.attachments/Dashboard-B.png -------------------------------------------------------------------------------- /.attachments/UpdateReporting001-level.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasatgit/updatereporting/0ca927dc4442915997421d70dc645b8172f6f7bf/.attachments/UpdateReporting001-level.PNG -------------------------------------------------------------------------------- /Import-SSRSReports.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Uploads SQL Server Reporting Services report and dataset files. 4 | 5 | .DESCRIPTION 6 | The script will change the content of rdl and rsd files and will upload them to a SQL Server Reporting Services (SSRS) of your choice. 7 | The rdl and rsd files contain specific strings which are simply replaced by the parameter values of this script. 8 | 9 | Disclaimer 10 | This sample script is not supported under any Microsoft standard support program or service. This sample 11 | script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties 12 | including, without limitation, any implied warranties of merchantability or of fitness for a particular 13 | purpose. The entire risk arising out of the use or performance of this sample script and documentation 14 | remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, 15 | production, or delivery of this script be liable for any damages whatsoever (including, without limitation, 16 | damages for loss of business profits, business interruption, loss of business information, or other 17 | pecuniary loss) arising out of the use of or inability to use this sample script or documentation, even 18 | if Microsoft has been advised of the possibility of such damages. 19 | 20 | .PARAMETER ReportServerURI 21 | The URL of the SQL Reporting Services Server. For example: http://reportserver.domain.local/reportserver 22 | 23 | .PARAMETER TargetFolderPath 24 | The folder were the reports should be placed in. I created a folder called "Custom_UpdateReporting" below the default MECM reporting folder. My sitecode is P11, so the default folder is called "ConfigMgr_P11". 25 | For example: "ConfigMgr_P11/Custom_UpdateReporting" 26 | Use "/"" instead of "\"" because it's a website 27 | 28 | .PARAMETER TargetDataSourcePath 29 | The path should point to the default ConfigMgr/MECM data source. 30 | In my case the Sitecode is P11 and the default data source is therefore in the folder "ConfigMgr_P11" and has the ID "{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}" 31 | The path with the default folder is required. For example: "ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}"" 32 | Use "/"" instead of "\"" because it's a website 33 | 34 | .PARAMETER DefaultCollection 35 | The report can show data of a default collection when it will be run, so that you don't need to provide a collection name each time you run the report. 36 | The default value is "SMS00001" which is the CollectionID of "All Systems", which might not be the best choice for bigger environments. 37 | 38 | .PARAMETER DefaultCollectionFilter 39 | The filter is used to find the collection you are interested in and the value needs to match the name of the collection you choose to be the default collection for the parameter "defaultCollection". 40 | In my case "All%" or All Syst% or "Servers%" to get the "Servers of the environment" collection for example. 41 | 42 | .PARAMETER DoNotHideReports 43 | Array of reports which should not be set to hidden. You should not use the parameter unless you really want more reports to be visible. 44 | 45 | .PARAMETER $DoNotUpload 46 | If used each reports will be changed to have the correct values, but will not be uploaded. 47 | That might be helpful, if you do not have the rights to upload and need to give the files to another person so that they can be uploaded manually 48 | 49 | .PARAMETER ReportSourcePath 50 | The script will use the script root path to look for a folder called "Sourcefiles" and will copy all the report files from there. 51 | But you could also provide a different path where the script should look for a "Sourcefiles" folder. 52 | 53 | .PARAMETER ForceLegacyFormat 54 | The report xml definition will be changed to the older pre SSRS 2016 format. That way the reports also work with SSRS 2014 for example. 55 | 56 | .PARAMETER ForceLegacyCardinalitySQL2016SP1AndHigher 57 | Will add SQL query hint "OPTION (USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'))" to important queries to increase performance. 58 | Works only with SQL Server 2016 SP1 and higher (SQL version >= 13.0.4001.0) 59 | More infos can be found here: https://support.microsoft.com/en-us/help/3196320/sql-query-times-out-or-console-slow-on-certain-configuration-manager-d 60 | 61 | .PARAMETER ForceLegacyCardinalityOlderThanSQL2016SP1 62 | Will add SQL query trace flag "OPTION (QUERYTRACEON 9481)" to important queries to increase performance. 63 | Works only with older SQL version than SQL Server 2016 SP1 (SQL version < 13.0.4001.0) 64 | More infos can be found here: https://support.microsoft.com/en-us/help/3196320/sql-query-times-out-or-console-slow-on-certain-configuration-manager-d 65 | 66 | .PARAMETER TryOverwrite 67 | If set, the script will try to overwrite existing reports. In some cases not all settings are overwritten unfortunately, hence the name TRY-Overwrite 68 | 69 | .INPUTS 70 | None. You cannot pipe objects to Import-SSRSReports.ps1 71 | 72 | .OUTPUTS 73 | Just normal console output. Nothing to work with. 74 | 75 | .EXAMPLE 76 | PS> .\Import-SSRSReports.ps1 -ReportServerURI "http://reportserver.domain.local/reportserver" -TargetFolderPath "ConfigMgr_P11/Custom_UpdateReporting" -TargetDataSourcePath "ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}" 77 | 78 | .EXAMPLE 79 | PS> .\Import-SSRSReports.ps1 -ReportServerURI "http://reportserver.domain.local/reportserver" -TargetFolderPath "ConfigMgr_P11/Custom_UpdateReporting" -TargetDataSourcePath "ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}" -TryOverwrite 80 | 81 | .EXAMPLE 82 | PS> .\Import-SSRSReports.ps1 -ReportServerURI "http://reportserver.domain.local/reportserver" -TargetFolderPath "ConfigMgr_P11/Custom_UpdateReporting" -TargetDataSourcePath "ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}" -ForceLegacyFormat -ForceLegacyCardinalitySQL2016SP1AndHigher 83 | 84 | .EXAMPLE 85 | PS> .\Import-SSRSReports.ps1 -ReportServerURI "http://reportserver.domain.local/reportserver" -TargetFolderPath "ConfigMgr_P11/Custom_UpdateReporting" -TargetDataSourcePath "ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}" -DoNotUpload 86 | 87 | .EXAMPLE 88 | PS> .\Import-SSRSReports.ps1 -ReportServerURI "http://reportserver.domain.local/reportserver" -TargetFolderPath "ConfigMgr_P11/Custom_UpdateReporting" -TargetDataSourcePath "ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}" -ForceLegacyCardinalitySQL2016SP1AndHigher 89 | 90 | .EXAMPLE 91 | PS> .\Import-SSRSReports.ps1 -ReportServerURI "http://reportserver.domain.local/reportserver" -TargetFolderPath "ConfigMgr_P11/Custom_UpdateReporting" -TargetDataSourcePath "ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}" -DefaultCollectionID "P1100012" -DefaultCollectionFilter "All Servers of Contoso%" 92 | 93 | .LINK 94 | https://github.com/jonasatgit/updatereporting 95 | 96 | .LINK 97 | https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/mastering-configuration-manager-patch-compliance-reporting/ba-p/1415088 98 | 99 | #> 100 | [CmdletBinding(DefaultParametersetName='None')] 101 | param( 102 | 103 | [parameter(Mandatory=$true)] 104 | [string]$ReportServerUri = "http://reportserver.domain.local/reportserver", 105 | 106 | [parameter(Mandatory=$true)] 107 | [string]$TargetFolderPath = 'ConfigMgr_P11/Custom_UpdateReporting', 108 | 109 | [parameter(Mandatory=$true)] 110 | [string]$TargetDataSourcePath = 'ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}', 111 | 112 | [parameter(ParameterSetName = 'CollectionInfo',Mandatory=$false)] 113 | [string]$DefaultCollectionID = 'SMS00001', 114 | 115 | [parameter(ParameterSetName = 'CollectionInfo',Mandatory=$true)] 116 | [string]$DefaultCollectionFilter = 'All%', 117 | 118 | [parameter(Mandatory=$false)] 119 | [array]$DoNotHideReports = @('Software Updates Compliance - Overview','Software Updates Compliance - Per device','Software Updates Compliance - Per device deployments','Software Updates Compliance - Overview compliance list','Compare Update Compliance','Software Updates Compliance - Offline Scan Results'), 120 | 121 | [parameter(Mandatory=$false)] 122 | [Switch]$DoNotUpload, 123 | 124 | #[parameter(Mandatory=$false)] 125 | #[bool]$UseViewForDataset = $false, 126 | 127 | [parameter(Mandatory=$false)] 128 | [switch]$ForceLegacyFormat, 129 | 130 | [parameter(Mandatory=$false)] 131 | [switch]$ForceLegacyCardinalitySQL2016SP1AndHigher, 132 | 133 | [parameter(Mandatory=$false)] 134 | [switch]$ForceLegacyCardinalityOlderThanSQL2016SP1, 135 | 136 | [parameter(Mandatory=$false)] 137 | [string]$ReportSourcePath = $($PSScriptRoot), 138 | 139 | [parameter(Mandatory=$false)] 140 | [switch]$TryOverwrite 141 | 142 | ) 143 | 144 | #[string]$datasetUsingSQLView = 'UpdatesSummaryView' 145 | 146 | $cleanFolder = "$reportSourcePath\SourceFiles" 147 | $workFolder = "$reportSourcePath\work" 148 | 149 | $overwriteReportItem = $false 150 | if ($TryOverwrite) 151 | { 152 | $overwriteReportItem = $true 153 | Write-Host "Will try to overwrite existing reports..." -ForegroundColor Yellow 154 | } 155 | 156 | if ($ForceLegacyCardinalityOlderThanSQL2016SP1 -and $ForceLegacyCardinalitySQL2016SP1AndHigher) 157 | { 158 | Write-Host "Get-Help .\Import-SSRSReports.ps1 -Examples" 159 | Get-Help .\Import-SSRSReports.ps1 -Examples 160 | Write-Host " " 161 | Write-host "Use either ForceLegacyCardinalityOlderThanSQL2016SP1 or ForceLegacyCardinalitySQL2016SP1AndHigher" -ForegroundColor Yellow 162 | Write-Host "Run `"Get-Help .\Import-SSRSReports.ps1 -Full`" to get help" -ForegroundColor Yellow 163 | break 164 | } 165 | 166 | # not using validatepattern to genereate nice error messages 167 | if ($ReportServerUri -notmatch '^[a-z0-9\./:\{\}\-_ ]+$') 168 | { 169 | Write-Host "Get-Help .\Import-SSRSReports.ps1 -Examples" 170 | Get-Help .\Import-SSRSReports.ps1 -Examples 171 | Write-Host " " 172 | Write-host "Parameter `"ReportServerUri`" needs to match regex: '^[a-z0-9\./:\{\}\-_ ]+$'" -ForegroundColor Yellow 173 | Write-host "Please use slash `"/`" instead of backslash `"\`" for parameter `"ReportServerUri`"" -ForegroundColor Yellow 174 | Write-Host "Run `"Get-Help .\Import-SSRSReports.ps1 -Full`" to get help" -ForegroundColor Yellow 175 | break 176 | } 177 | 178 | 179 | if ($TargetFolderPath -notmatch '^[a-z0-9\./:\{\}\-_ ]+$') 180 | { 181 | Write-Host "Get-Help .\Import-SSRSReports.ps1 -Examples" 182 | Get-Help .\Import-SSRSReports.ps1 -Examples 183 | Write-Host " " 184 | Write-host "Parameter `"TargetFolderPath`" needs to match regex: '^[a-z0-9\./:\{\}\-_ ]+$'" -ForegroundColor Yellow 185 | Write-host "Please use slash `"/`" instead of backslash `"\`" for parameter `"TargetFolderPath`"" -ForegroundColor Yellow 186 | Write-Host "Run `"Get-Help .\Import-SSRSReports.ps1 -Full`" to get help" -ForegroundColor Yellow 187 | break 188 | } 189 | 190 | 191 | if ($TargetDataSourcePath -notmatch '^[a-z0-9\./:\{\}\-_ ]+$') 192 | { 193 | Write-Host "Get-Help .\Import-SSRSReports.ps1 -Examples" 194 | Get-Help .\Import-SSRSReports.ps1 -Examples 195 | Write-Host " " 196 | Write-host "Parameter `"TargetDataSourcePath`" needs to match regex: '^[a-z0-9\./:\{\}\-_ ]+$'" -ForegroundColor Yellow 197 | Write-host "Please use slash `"/`" instead of backslash `"\`" for parameter `"TargetDataSourcePath`"" -ForegroundColor Yellow 198 | Write-Host "Run `"Get-Help .\Import-SSRSReports.ps1 -Full`" to get help" -ForegroundColor Yellow 199 | break 200 | } 201 | 202 | 203 | if ($DefaultCollectionFilter -match '&') 204 | { 205 | Write-Host "Get-Help .\Import-SSRSReports.ps1 -Examples" 206 | Get-Help .\Import-SSRSReports.ps1 -Examples 207 | Write-Host " " 208 | Write-host "Parameter `"DefaultCollectionFilter`" should not contain the ampersand sign `"&`"" -ForegroundColor Yellow 209 | Write-Host "Run `"Get-Help .\Import-SSRSReports.ps1 -Full`" to get help" -ForegroundColor Yellow 210 | break 211 | } 212 | 213 | 214 | if (-not (Test-Path $cleanFolder)) 215 | { 216 | Write-Host "Folder `"$($cleanFolder)`" not found!" -ForegroundColor Yellow 217 | break 218 | } 219 | 220 | if (-not (Test-Path $workFolder)) 221 | { 222 | $null = New-Item -ItemType "directory" -Path $workFolder -Force 223 | } 224 | else 225 | { 226 | Get-ChildItem $workFolder | Remove-Item -Force 227 | } 228 | Write-host "Copy `"$($cleanFolder)\*`" to `"$($workFolder)\`"" -ForegroundColor Green 229 | $null = Copy-Item -Path "$($cleanFolder)\*" -Destination "$($workFolder)\" -Force 230 | 231 | [array]$reportsToWorkWith = Get-ChildItem -Path "$workFolder" | Where-Object {$_.Extension -eq '.rdl' -or $_.Extension -eq '.rsd'} 232 | Write-host "Found $($reportsToWorkWith.Count) .rdl and .rsd files in `"$reportSourcePath\work`"" -ForegroundColor Green 233 | if ($reportsToWorkWith.Count -gt 0) 234 | { 235 | if ($ForceLegacyFormat) 236 | { 237 | Write-Host "Changing xml format to work with pre 2016 SSRS versions" -ForegroundColor Yellow 238 | } 239 | 240 | if ($ForceLegacyCardinalitySQL2016SP1AndHigher) 241 | { 242 | Write-Host "Will add SQL query hint: `"OPTION (USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'))`" to larger queries" -ForegroundColor Yellow 243 | } 244 | 245 | if ($ForceLegacyCardinalityOlderThanSQL2016SP1) 246 | { 247 | Write-Host "Will add SQL query trace flag `"OPTION (QUERYTRACEON 9481)`" to larger queries" -ForegroundColor Yellow 248 | } 249 | 250 | $reportsToWorkWith | ForEach-Object { 251 | 252 | Write-host "Working on: $($_.Name)" -ForegroundColor Green 253 | 254 | $reportContent = '' 255 | $reportContent = Get-Content -Path $_.FullName 256 | # simply replacing the neccesary parts 257 | $reportContent = $reportContent.Replace("/ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602}","/$($targetDataSourcePath)") 258 | $reportContent = $reportContent.Replace("/ConfigMgr_P11/Custom_UpdateReporting","/$($targetFolderPath)") 259 | $reportContent = $reportContent.Replace("http://reportserver.domain.local/reportserver","$($ReportServerUri)") # case sensitive 260 | $reportContent = $reportContent.Replace("http://reportserver.domain.local/ReportServer","$($ReportServerUri)") # case sensitive 261 | $reportContent = $reportContent.Replace("/ConfigMgr_P11/Custom_UpdateReporting/","/$($targetFolderPath)/") 262 | $reportContent = $reportContent.Replace("All%","$defaultCollectionFilter") 263 | $reportContent = $reportContent.Replace('SMS00001',"$($defaultCollectionID)") 264 | 265 | if ($ForceLegacyCardinalitySQL2016SP1AndHigher) # uncomment SQL hint 266 | { 267 | $reportContent = $reportContent.Replace('--OPTION (USE HINT','OPTION (USE HINT') 268 | } 269 | 270 | if ($ForceLegacyCardinalityOlderThanSQL2016SP1) 271 | { 272 | $reportContent = $reportContent.Replace('--OPTION (QUERYTRACEON 9481)','OPTION (QUERYTRACEON 9481)') 273 | } 274 | 275 | 276 | <# 277 | if ($UseViewForDataset) 278 | { 279 | $reportContent = $reportContent.Replace('UpdatesSummary',"$($datasetUsingSQLView)") 280 | } 281 | #> 282 | 283 | if ($ForceLegacyFormat) 284 | { 285 | # Changing xml format to work with pre 2016 SSRS versions 286 | [xml]$ContentXML=$reportContent 287 | if ($ContentXML.report.xmlns -eq "http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition") 288 | { 289 | $ContentXML.report.xmlns = "http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition" 290 | $ContentXML.report.SetAttribute("cl","http://schemas.microsoft.com/sqlserver/reporting/2010/01/componentdefinition") 291 | } 292 | if ($ContentXML.report.ReportParameterslayout -ne $NULL) 293 | { 294 | [void]$ContentXML.Report.RemoveChild($ContentXML.report.ReportParameterslayout) 295 | } 296 | $ContentXML.Save($_.FullName) 297 | } 298 | else 299 | { 300 | # save all the changes to the file 301 | $reportContent | Out-File -FilePath $($_.FullName) -Encoding utf8 -Force 302 | } 303 | } 304 | 305 | if (-NOT ($DoNotUpload)) 306 | { 307 | Write-host "Connecting to: $ReportServerUri..." -ForegroundColor Green 308 | 309 | $ReportServerUriFull = "$ReportServerUri/ReportService2010.asmx?wsdl" 310 | $ReportServerConnection = New-WebServiceProxy -Uri $ReportServerUriFull -Namespace "SSRS" -UseDefaultCredential; 311 | if($ReportServerConnection) 312 | { 313 | Write-host "Connected to: $ReportServerUri" -ForegroundColor Green 314 | 315 | # import datasets first to make them available to reports 316 | $reportsToWorkWith | Sort-Object Extension -Descending | ForEach-Object { 317 | Write-host "Uploading: $($_.Name)..." -ForegroundColor Green 318 | 319 | $reportName = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) 320 | $reportBytes = [System.IO.File]::ReadAllBytes($_.FullName) 321 | 322 | $targetPath = "/$targetFolderPath" 323 | 324 | if($_.Extension -eq '.rsd') 325 | { 326 | $itemType = "DataSet" 327 | } 328 | else 329 | { 330 | $itemType = "Report" 331 | } 332 | $warnings = $null 333 | $null = $ReportServerConnection.CreateCatalogItem( 334 | $itemType, # Catalog item type: Report, Model, Dataset, Component, Resource, and DataSource 335 | $reportName, # Name of the item 336 | $targetPath, # Destination folder 337 | $overwriteReportItem, # Overwrite report if it exists, not all settings are overwritten, therefore set to false. Delete items manually or use TryOverwrite parameter 338 | $reportBytes, # Bytes of item 339 | $null, # Item properties 340 | [ref]$warnings) # Warnings during upload 341 | 342 | if(-NOT($warnings -eq $null)) 343 | { 344 | $warnings | ForEach-Object { 345 | Write-Host "Warning: $($_.Message)" -ForegroundColor Yellow 346 | } 347 | } 348 | 349 | # hide all reports exept for reports found in $doNotHideReports 350 | if($doNotHideReports -notcontains $reportName) 351 | { 352 | $Properties = $ReportServerConnection.GetProperties("$targetPath/$reportName",$tmp) 353 | $prop = $Properties | Where-Object {$_.Name -eq 'Hidden'} 354 | $prop.Value = $true 355 | $ReportServerConnection.SetProperties("$targetPath/$reportName",$prop) 356 | } 357 | } 358 | 359 | } 360 | else 361 | { 362 | Write-host "Problem with connection..." -ForegroundColor Yellow 363 | } 364 | } 365 | else 366 | { 367 | Write-host "Parameter is set to NOT upload any reports to: $ReportServerUri" -ForegroundColor Yellow 368 | } 369 | } 370 | Write-host "End of script!" -ForegroundColor Green 371 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Original blog and documentation: 2 | The original blog and documentation can be found [HERE](https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/mastering-configuration-manager-patch-compliance-reporting/ba-p/1415088 "Mastering configuration manager patch compliance reporting") 3 | 4 | 5 | # Other blogs 6 | All my other blogs can be found [HERE](https://aka.ms/JonasOhmsenBlogs "JonasOhmsenBlogs") 7 | and [HERE](https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/mastering-configuration-manager-bandwidth-limitations-for-vpn/ba-p/1280002 "Mastering Configuration Manager Bandwidth limitations for VPN connected Clients") 8 | 9 | 10 | # MEM/MECM/ConfigMgr patch compliance report solution 11 | ![Update dashboard](/.attachments/Dashboard-B.png) 12 | ![Simplified report dependencies](/.attachments/UpdateReporting001-level.PNG) 13 | 14 | 15 | # Changes 16 | (The version number can be found in the lower left corner of the dashboard. No version number means v1.0) 17 | 18 | ## 2024-05-06 v4.1: 19 | 1. Changed update compliance definition and removed the dependency on "LastInstallTime" 20 | 21 | ## 2023-11-10 v4.0: 22 | 1. Added more possible error fix actions 23 | 24 | ## 2022-10-12 v3.9: 25 | 1. Added logic to hide zero values in diagrams based on feedback from original blog: [HERE](https://aka.ms/JonasOhmsenBlogs "JonasOhmsenBlogs") 26 | 1. Fixed minor issues 27 | 28 | ## 2022-05-06 v3.8: 29 | 1. Fixed a sub-report link problem for WSUS error and install error list 30 | 31 | ## 2022-04-04 v3.7: 32 | 1. Added parameter to show data based on current or previous month. The previous month setting is only applicable if updates are deployed with a month delay and does not rely on historical data 33 | 1. Changed the way compliance for update rollups are shown between first day of month and second Tuesday based on: https://github.com/jonasatgit/updatereporting/pull/11 34 | 1. Changed the "exclude future deployments" parameter to be able to filter out deployments in one of the following states: Deployed as available, deployment disabled, start time or deadline in the future 35 | 1. Changed the column "Missing updated approved" to only show missing updates if the corresponding deployment has not been filtered out via the new exclude parameter. The "per device" report still shows all updates no matter the deployment selection 36 | 1. Added the new deployment exclude parameter also to the "Per device deployments" report. The report will now exclude deployments based on the parameter. 37 | 1. Added cumulative update prefix like "2022-04" to the dashboard for each rollup bar graph 38 | 1. Added systems domain name column to each list report 39 | 1. Added switch "TryOverwrite" to import script. If set, the script will try to overwrite existing report items. Might not work in every case. If successful subscriptions will also be kept. 40 | 1. Added new filter to the "Per device" report called: "All missing Security and Critical updates deployed or not" 41 | 1. Removed Security Update requirement in QFE query to improve QFE detection accuracy 42 | 1. Fixed typo in "Per device" report based on: https://github.com/jonasatgit/updatereporting/issues/9 43 | 1. Fixes typo in import script 44 | 1. Fixed sorting issue in "per device" report 45 | 1. Fixed sorting issue in "per device deployments" report 46 | 1. Fixed "uncompliant" typo in "compliance list" report via: https://github.com/jonasatgit/updatereporting/pull/15 47 | 1. Fixed typo in "compare update compliance" via: https://github.com/jonasatgit/updatereporting/pull/14 48 | 1. Fixed an issue with parameters not correctly handled between the dashboard and most of the sub-reports 49 | 50 | ## 2021-11-18 v3.6: 51 | 1. Added "Cumulative Update for Microsoft server operating system" string for server 2022 updates 52 | 53 | ## 2021-07-02 v3.5: 54 | 1. Changed the overall compliance state from "all approved and missing updates" + "a security update installation happend within one month" to "All deployments are compliant" + "either the last or the current cumulative update is installed" + "a security update installation happend within one month" 55 | 1. Added help text to all report column headers 56 | 1. Added Update install errors bar graph to dashboard (below WSUS scan errors) 57 | 1. Changed filter for top 10 systems on dashboard to be more accurate 58 | 1. Added top 10 update install errors to dashboard 59 | 1. Added new report with details about install errors and WSUS scan errors 60 | 1. Contains around 400 common windows update related errors with possible actions on how to fix them 61 | 1. Added new parameter to exclude deployments containing Microsoft Defender and System Center Endpoint Protection updates 62 | 1. Was previously part of the SQL query and not easily changeable nor visible to the report user 63 | 1. Removed Server 2008 specific parts 64 | 1. Added new filter to "per device" report called: "Missing updates with errors" and “All missing updates deployed or not” 65 | 1. Added more details about errors to "per device" report 66 | 1. Added update collection and maintenance window list to “per device” report 67 | 1. Added column: “Earliest Deadline” to “per device” report 68 | 1. Changed first sub-report name from “all uncompliant” to “compliance list” 69 | 1. Changed default sort order from "count of missing updates" to "month since last update install" 70 | 1. Changed "WSUS version" to "OS build version". Easier to determine actual OS version and patch level 71 | 1. Changed "Defender Pattern Version" to "Defender Pattern Age" to be able to spot systems with older pattern more easily 72 | 1. Added column "WSUS scan error" to system list 73 | 1. Added column count of "Updates with install error" to system list 74 | 1. Added column number of "Deployments non compliant" to system list 75 | 1. Helps to determine any problems with deployments when all updates are installed, but deployments are still marked as uncompliant 76 | 1. Added new report to list all update deployments and their states per device 77 | 1. Made "Per device" and “compliance list" report visible to be able to schedule subscriptions without the dashboard 78 | 1. Fixed several minor issues with each report 79 | 1. Changed SQL query for deployed updates to work better in larger environments 80 | 1. Changed import script to also handle SSRS folder path with spaces in it 81 | 1. Changed import script to delete existing contents of "work" folder from a previous run 82 | 1. Changed import script parameter name "Upload" to "DoNotUpload". Function is the same. 83 | 1. Removed import script parameter "UseViewForDataset". (To much work to keep the view consistent with regular query) 84 | 1. Added new import script parameters: "ForceLegacyCardinalitySQL2016SP1AndHigher" and "ForceLegacyCardinalityOlderThanSQL2016SP1" Read more about it here 85 | 86 | ## 2020-12-09 v2.1: 87 | 1. Fixed language and QFE problem 88 | 1. Added new parameter -ForceLegacyFormat, 89 | 1. Fixed minor issues and linked all reports to the per device sub-report 90 | 91 | ## 2020-11-03 v1.0: 92 | 1. Fixed wrong parameter name, updated repository with several fixes 93 | -------------------------------------------------------------------------------- /SourceFiles/Compare Update Compliance.rdl: -------------------------------------------------------------------------------- 1 |  2 | 3 | Comparison of a maximum of six systems and their update compliance statuses 4 | 0 5 | 6 | 7 | /ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602} 8 | None 9 | 7f594453-7be9-46cf-aa6c-e4065a7931e2 10 | 11 | 12 | 13 | 14 | 15 | DataSource1 16 | 17 | 18 | =Parameters!onlyRegUpdates.Value 19 | 20 | 21 | =Parameters!Systems.Value 22 | 23 | 24 | IF @onlyRegUpdates = 1 25 | 26 | With SystemsLimited_CTE (ResourceID) 27 | AS 28 | ( 29 | select top 6 ResourceID from v_R_System T00 where T00.ResourceID in (@Systems) 30 | 31 | ) 32 | 33 | select SYS.Name0, 34 | concat(OS.caption0, ' ' ,OS.CSDVersion0) as OSystem, 35 | ui.Title, 36 | Targeted=(case when ctm.ResourceID is not null then 'X' end), 37 | Installed=(case when css.Status=3 then 'X' end), 38 | IsRequired=(case when css.Status=2 then 'X' end) 39 | from v_Update_ComplianceStatus css 40 | join v_R_System SYS on SYS.ResourceID = css.ResourceID 41 | join v_UpdateInfo ui on ui.CI_ID=css.CI_ID 42 | join v_CICategoryInfo_All vnd on vnd.CI_ID=ui.CI_ID and vnd.CategoryTypeName='Company' 43 | join v_CICategoryInfo_All cls on cls.CI_ID=ui.CI_ID and cls.CategoryTypeName='UpdateClassification' 44 | left join v_CITargetedMachines ctm on ctm.CI_ID=css.CI_ID and ctm.ResourceID = css.ResourceID 45 | left join v_GS_OPERATING_SYSTEM OS on OS.ResourceID = CSS.ResourceID 46 | where css.ResourceID in (select ResourceID from SystemsLimited_CTE) 47 | and css.Status=2 48 | order by SYS.name0 49 | 50 | ELSE 51 | 52 | With SystemsLimited_CTE (ResourceID) 53 | AS 54 | ( 55 | select top 6 ResourceID from v_R_System T00 where T00.ResourceID in (@Systems) 56 | 57 | ) 58 | select SYS.Name0, 59 | concat(OS.caption0, ' ' ,OS.CSDVersion0) as OSystem, 60 | ui.Title, 61 | Targeted=(case when ctm.ResourceID is not null then 'X' end), 62 | Installed=(case when css.Status=3 then 'X' end), 63 | IsRequired=(case when css.Status=2 then 'X' end) 64 | from v_Update_ComplianceStatus css 65 | join v_R_System SYS on SYS.ResourceID = css.ResourceID 66 | join v_UpdateInfo ui on ui.CI_ID=css.CI_ID 67 | join v_CICategoryInfo_All vnd on vnd.CI_ID=ui.CI_ID and vnd.CategoryTypeName='Company' 68 | join v_CICategoryInfo_All cls on cls.CI_ID=ui.CI_ID and cls.CategoryTypeName='UpdateClassification' 69 | left join v_CITargetedMachines ctm on ctm.CI_ID=css.CI_ID and ctm.ResourceID = css.ResourceID 70 | left join v_GS_OPERATING_SYSTEM OS on OS.ResourceID = CSS.ResourceID 71 | where css.ResourceID in (select ResourceID from SystemsLimited_CTE) 72 | order by SYS.name0 73 | true 74 | 75 | 76 | 77 | Name0 78 | System.String 79 | 80 | 81 | Title 82 | System.String 83 | 84 | 85 | OSystem 86 | System.String 87 | 88 | 89 | Targeted 90 | System.String 91 | 92 | 93 | Installed 94 | System.String 95 | 96 | 97 | IsRequired 98 | System.String 99 | 100 | 101 | 102 | 103 | 104 | DataSource1 105 | 106 | 107 | =Parameters!Search.Value 108 | 109 | 110 | select Name0, ResourceID 111 | from v_R_System T00 112 | where T00.Client0 = 1 113 | and T00.Decommissioned0 !=1 114 | and T00.Obsolete0 !=1 115 | and T00.Name0 like @Search 116 | order by Name0 117 | true 118 | 119 | 120 | 121 | Name0 122 | System.String 123 | 124 | 125 | ResourceID 126 | System.Int32 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | true 143 | true 144 | 145 | 146 | 147 | 148 | Choose systems that are equal related to their operating system and installed roles. 149 | 150 | 151 | 152 | 153 | 154 | 155 | The report will only show applicable updates to the specific system and not a list of all updates available. 156 | 157 | 158 | 159 | 160 | Textbox13 161 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 2pt 176 | 2pt 177 | 2pt 178 | 2pt 179 | 180 | 181 | 3 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | true 196 | true 197 | 198 | 199 | 200 | 201 | Update-Title 202 | 207 | 208 | 209 | 210 | 211 | Textbox18 212 | 216 | 217 | Silver 218 | 2pt 219 | 2pt 220 | 2pt 221 | 2pt 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 0.45833in 233 | 234 | 235 | 0.5in 236 | 237 | 238 | 0.41667in 239 | 240 | 241 | 242 | 243 | 0.25in 244 | 245 | 246 | 247 | 248 | true 249 | true 250 | 251 | 252 | 253 | 254 | =Fields!Installed.Value 255 | 260 | 261 | 262 | 265 | 266 | 267 | Installed1 268 | 272 | 273 | 274 | Gray 275 | 2pt 276 | 277 | Middle 278 | 2pt 279 | 2pt 280 | 2pt 281 | 2pt 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | true 290 | true 291 | 292 | 293 | 294 | 295 | =Fields!Targeted.Value 296 | 301 | 302 | 303 | 306 | 307 | 308 | Targeted 309 | 313 | 314 | Middle 315 | 2pt 316 | 2pt 317 | 2pt 318 | 2pt 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | true 327 | true 328 | 329 | 330 | 331 | 332 | =Fields!IsRequired.Value 333 | 338 | 339 | 340 | 343 | 344 | 345 | IsRequired 346 | 350 | 351 | 352 | Gray 353 | 2pt 354 | 355 | Middle 356 | 2pt 357 | 2pt 358 | 2pt 359 | 2pt 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | =Fields!Name0.Value 374 | 375 | 376 | 377 | 378 | =Fields!Name0.Value 379 | 380 | 381 | 382 | 0.25in 383 | 384 | 385 | true 386 | true 387 | 388 | 389 | 390 | 391 | =Fields!Name0.Value 392 | 396 | 397 | 398 | 399 | 400 | Name0 401 | 405 | 406 | 407 | Gray 408 | 2pt 409 | 410 | 411 | Gray 412 | 2pt 413 | 414 | LightGrey 415 | 2pt 416 | 2pt 417 | 2pt 418 | 2pt 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 0.25in 427 | 428 | 429 | true 430 | true 431 | 432 | 433 | 434 | 435 | =Fields!OSystem.Value 436 | 440 | 441 | 442 | 443 | 444 | OSystem 445 | 449 | 450 | 451 | Gray 452 | 2pt 453 | 454 | 455 | Gray 456 | 2pt 457 | 458 | LightGrey 459 | 2pt 460 | 2pt 461 | 2pt 462 | 2pt 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 0.25in 471 | 472 | 473 | true 474 | true 475 | 476 | 477 | 478 | 479 | Inst 480 | 486 | 487 | 488 | 489 | 490 | Textbox5 491 | X to indicate an installed update 492 | 496 | 497 | 498 | Gray 499 | 2pt 500 | 501 | #4c68a2 502 | 2pt 503 | 2pt 504 | 2pt 505 | 2pt 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 0.25in 514 | 515 | 516 | true 517 | true 518 | 519 | 520 | 521 | 522 | =Count(Fields!Installed.Value) 523 | 528 | 529 | 530 | 533 | 534 | 535 | Textbox19 536 | Count of installed updates 537 | 541 | 542 | 543 | Gray 544 | 2pt 545 | 546 | Silver 547 | Middle 548 | 2pt 549 | 2pt 550 | 2pt 551 | 2pt 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 0.25in 562 | 563 | 564 | true 565 | true 566 | 567 | 568 | 569 | 570 | Targ 571 | 577 | 578 | 579 | 580 | 581 | Textbox8 582 | X to indicate a deployed / targeted update 583 | 587 | 588 | #4c68a2 589 | 2pt 590 | 2pt 591 | 2pt 592 | 2pt 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 0.25in 601 | 602 | 603 | true 604 | true 605 | 606 | 607 | 608 | 609 | =Count(Fields!Targeted.Value) 610 | 615 | 616 | 617 | 620 | 621 | 622 | Textbox22 623 | Count of deployed / targeted updates 624 | 628 | 629 | Silver 630 | Middle 631 | 2pt 632 | 2pt 633 | 2pt 634 | 2pt 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 0.25in 645 | 646 | 647 | true 648 | true 649 | 650 | 651 | 652 | 653 | Req 654 | 660 | 661 | 662 | 663 | 664 | Textbox11 665 | X to indicate a required / missing update 666 | 670 | 671 | 672 | Gray 673 | 2pt 674 | 675 | #4c68a2 676 | 2pt 677 | 2pt 678 | 2pt 679 | 2pt 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 0.25in 688 | 689 | 690 | true 691 | true 692 | 693 | 694 | 695 | 696 | =Count(Fields!IsRequired.Value) 697 | 701 | 702 | 703 | 706 | 707 | 708 | Textbox21 709 | Count of required / missing updates 710 | 714 | 715 | 716 | Gray 717 | 2pt 718 | 719 | Silver 720 | Middle 721 | 2pt 722 | 2pt 723 | 2pt 724 | 2pt 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | =Fields!Title.Value 744 | 745 | 746 | 747 | 748 | =Fields!Title.Value 749 | 750 | 751 | 752 | 4.95833in 753 | 754 | 755 | true 756 | true 757 | 758 | 759 | 760 | 761 | =Fields!Title.Value 762 | 765 | 766 | 767 | 768 | 769 | Title 770 | 774 | 775 | 2pt 776 | 2pt 777 | 2pt 778 | 2pt 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | Compare 790 | 0in 791 | 0in 792 | 1.25in 793 | 6.33333in 794 | 797 | 798 | 799 | 800 | 801 | 4.51736in 802 | 803 | 19.82292in 804 | 805 | 806 | 0.27084in 807 | true 808 | true 809 | 812 | 813 | 814 | 815 | 816 | 0.01944in 817 | true 818 | true 819 | 822 | 823 | 824 | 825 | 1in 826 | 1in 827 | 1in 828 | 1in 829 | 830 | 831 | 832 | 833 | 834 | String 835 | 836 | 837 | =User!Language 838 | 839 | 840 | true 841 | 842 | 843 | String 844 | 845 | 846 | % 847 | 848 | 849 | Limit list of systems: (wildcard %): 850 | 851 | 852 | String 853 | Choose Systems (max 6): 854 | 855 | 856 | Systems 857 | ResourceID 858 | Name0 859 | 860 | 861 | true 862 | 863 | 864 | Integer 865 | 866 | 867 | 0 868 | 869 | 870 | Show only required updates 871 | 872 | 873 | 874 | 1 875 | 876 | 877 | 878 | 0 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 2 888 | 3 889 | 890 | 891 | 0 892 | 0 893 | locale 894 | 895 | 896 | 0 897 | 1 898 | Search 899 | 900 | 901 | 1 902 | 1 903 | Systems 904 | 905 | 906 | 0 907 | 2 908 | onlyRegUpdates 909 | 910 | 911 | 912 | 913 | =User!Language 914 | 915 | SrsResources, culture=neutral 916 | 917 | true 918 | Inch 919 | http://reportserver.domain.local/ReportServer 920 | f3e5ae98-66c5-4863-b1e2-69153dbb97e3 921 | 922 | -------------------------------------------------------------------------------- /SourceFiles/Software Updates Compliance - ErrorList.rdl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 0 4 | 5 | 6 | /ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602} 7 | None 8 | 7f594453-7be9-46cf-aa6c-e4065a7931e2 9 | 10 | 11 | 12 | 13 | 14 | UpdatesInstallErrorList 15 | 16 | 17 | =Parameters!ReportType.Value 18 | 19 | 20 | =Parameters!CollectionIDs.Value 21 | 22 | 23 | =Parameters!CI_ID.Value 24 | 25 | 26 | =Parameters!LastErrorCode.Value 27 | 28 | 29 | http://reportserver.domain.local/reportserver 30 | 31 | 32 | 33 | Title 34 | true 35 | 36 | 37 | Name 38 | true 39 | 40 | 41 | LastErrorCode 42 | true 43 | 44 | 45 | ErrorDescription 46 | true 47 | 48 | 49 | Action 50 | true 51 | 52 | 53 | InfoURL 54 | true 55 | 56 | 57 | 58 | 59 | 60 | DataSource1 61 | 62 | 63 | =Parameters!CI_ID.Value 64 | 65 | 66 | Select Title, InfoURL from v_UpdateInfo where CI_ID = @CI_ID 67 | true 68 | 69 | 70 | 71 | Title 72 | System.String 73 | 74 | 75 | InfoURL 76 | System.String 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 2.25in 90 | 91 | 92 | 1.69167in 93 | 94 | 95 | 4.4in 96 | 97 | 98 | 11.35833in 99 | 100 | 101 | 0.85833in 102 | 103 | 104 | 105 | 106 | 0.25in 107 | 108 | 109 | 110 | 111 | true 112 | 113 | =Fields!Name.Value 114 | 115 | true 116 | 117 | 118 | 119 | 120 | Name 121 | 126 | 127 | 128 | 131 | 132 | 133 | Textbox2 134 | Name of computer 135 | 139 | 140 | 141 | 142 | 1.5pt 143 | 144 | 145 | 146 | 1.5pt 147 | 148 | White 149 | Bottom 150 | 5pt 151 | 5pt 152 | 5pt 153 | 5pt 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | true 162 | 163 | =Fields!LastErrorCode.Value 164 | 165 | true 166 | 167 | 168 | 169 | 170 | ErrorCode 171 | 176 | 177 | 178 | 181 | 182 | 183 | Last error code 184 | 188 | 189 | 190 | 191 | 1.5pt 192 | 193 | 194 | 195 | 1.5pt 196 | 197 | White 198 | Bottom 199 | 5pt 200 | 5pt 201 | 5pt 202 | 5pt 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | true 211 | 212 | =Fields!ErrorDescription.Value 213 | 214 | true 215 | 216 | 217 | 218 | 219 | Error Description 220 | 225 | 226 | 227 | 230 | 231 | 232 | Error description 233 | 237 | 238 | 239 | 240 | 1.5pt 241 | 242 | 243 | 244 | 1.5pt 245 | 246 | White 247 | Bottom 248 | 5pt 249 | 5pt 250 | 5pt 251 | 5pt 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | true 260 | 261 | =Fields!Action.Value 262 | 263 | true 264 | 265 | 266 | 267 | 268 | Possible Action (might not work in every case) 269 | 274 | 275 | 276 | 279 | 280 | 281 | A possible action to fix the error based on different Microsoft docs articles or Customer Engineer experience. The possible solution might not help in every case, but should give you a starting point to fix the underlaying issue. 282 | 286 | 287 | 288 | 289 | 1.5pt 290 | 291 | 292 | 293 | 1.5pt 294 | 295 | White 296 | Bottom 297 | 5pt 298 | 5pt 299 | 5pt 300 | 5pt 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | true 309 | 310 | =Fields!InfoURL.Value 311 | 312 | true 313 | 314 | 315 | 316 | 317 | InfoURL 318 | 323 | 324 | 325 | 328 | 329 | 330 | A link to a docs article describing the error or the solution in more detail 331 | 335 | 336 | 337 | 338 | 1.5pt 339 | 340 | 341 | 342 | 1.5pt 343 | 344 | White 345 | Bottom 346 | 5pt 347 | 5pt 348 | 5pt 349 | 5pt 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 0.25in 358 | 359 | 360 | 361 | 362 | true 363 | true 364 | 365 | 366 | 367 | 368 | =Fields!Name.Value 369 | 374 | 375 | 376 | 379 | 380 | 381 | Name 382 | 383 | 384 | 385 | 386 | Software Updates Compliance - Per device 387 | 388 | 389 | =IIf(Parameters!ReportType.Value = 4,0,3) 390 | 391 | 392 | =Fields!Name.Value 393 | 394 | 395 | 396 | 397 | 398 | 399 | 402 | 403 | 404 | Silver 405 | 406 | 407 | =iif(RowNumber(Nothing) Mod 2, "Transparent", "Gainsboro") 408 | Top 409 | 5pt 410 | 5pt 411 | 5pt 412 | 5pt 413 | 414 | 415 | true 416 | 417 | 418 | 419 | 420 | 421 | true 422 | true 423 | 424 | 425 | 426 | 427 | ="0x" & Hex(Fields!LastErrorCode.Value) 428 | 433 | 434 | 435 | 438 | 439 | 440 | 443 | 444 | 445 | Silver 446 | 447 | 448 | =iif(RowNumber(Nothing) Mod 2, "Transparent", "Gainsboro") 449 | Top 450 | 5pt 451 | 5pt 452 | 5pt 453 | 5pt 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | true 462 | true 463 | 464 | 465 | 466 | 467 | =Fields!ErrorDescription.Value 468 | 473 | 474 | 475 | 478 | 479 | 480 | 483 | 484 | 485 | Silver 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | =iif(RowNumber(Nothing) Mod 2, "Transparent", "Gainsboro") 495 | Top 496 | 5pt 497 | 5pt 498 | 5pt 499 | 5pt 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | true 508 | true 509 | 510 | 511 | 512 | 513 | =Fields!Action.Value 514 | 519 | 520 | 521 | 524 | 525 | 526 | 529 | 530 | 531 | Silver 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | =iif(RowNumber(Nothing) Mod 2, "Transparent", "Gainsboro") 541 | Top 542 | 5pt 543 | 5pt 544 | 5pt 545 | 5pt 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | true 554 | true 555 | 556 | 557 | 558 | 559 | =IIf(Fields!InfoURL.Value="","","Link") 560 | 565 | 566 | 567 | 570 | 571 | 572 | 573 | 574 | 575 | =Fields!InfoURL.Value 576 | 577 | 578 | 579 | 582 | 583 | 584 | Silver 585 | 586 | 587 | =iif(RowNumber(Nothing) Mod 2, "Transparent", "Gainsboro") 588 | Top 589 | 5pt 590 | 5pt 591 | 5pt 592 | 5pt 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | After 614 | true 615 | true 616 | 617 | 618 | 619 | Detail 620 | 621 | 622 | 623 | 624 | Detail_Collection 625 | Output 626 | true 627 | 628 | 629 | 630 | UpdatesInstallErrorList 631 | 0.36111in 632 | 0in 633 | 0.5in 634 | 20.55833in 635 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | Tahoma 652 | 8pt 653 | Normal 654 | None 655 | General 656 | Top 657 | 658 | 659 | 660 | true 661 | true 662 | 663 | 664 | 665 | 666 | =Parameters!ReportType.Label 667 | 670 | 671 | 672 | 675 | 676 | 677 | Textbox1 678 | 0.11333in 679 | 0in 680 | 0.24778in 681 | 5.80667in 682 | 1 683 | 686 | 687 | 2pt 688 | 2pt 689 | 2pt 690 | 2pt 691 | 692 | 693 | 694 | true 695 | true 696 | 697 | 698 | 699 | 700 | =IIf(Parameters!ReportType.Value=1,First(Fields!Title.Value, "UpdateTitle"),"") 701 | 704 | 705 | 706 | 709 | 710 | 711 | Textbox1 712 | 713 | 714 | 715 | =First(Fields!InfoURL.Value, "UpdateTitle") 716 | 717 | 718 | 719 | 0.11333in 720 | 5.94125in 721 | 0.24778in 722 | 13.18167in 723 | 2 724 | 727 | 728 | 2pt 729 | 2pt 730 | 2pt 731 | 2pt 732 | 733 | 734 | 735 | 1.03819in 736 | 737 | 21.0725in 738 | 739 | 740 | 0.01042in 741 | true 742 | true 743 | 746 | 747 | 748 | 749 | 750 | 0.01944in 751 | true 752 | true 753 | 756 | 757 | 758 | 759 | 1in 760 | 1in 761 | 1in 762 | 1in 763 | 764 | 765 | 766 | 767 | 768 | Integer 769 | Report Type 770 | 771 | 772 | 773 | 1 774 | 775 | 776 | 777 | 2 778 | 779 | 780 | 781 | 3 782 | 783 | 784 | 785 | 4 786 | 787 | 788 | 789 | 5 790 | 791 | 792 | 793 | 794 | 795 | 796 | String 797 | Collection IDs 798 | true 799 | 800 | 801 | Integer 802 | Update CI_ID (set to 0 if errorcode is set) 803 | 804 | 805 | Integer 806 | ErrorCode (set to 0 if update ID is set) 807 | 808 | 809 | 810 | 811 | 2 812 | 3 813 | 814 | 815 | 0 816 | 1 817 | ReportType 818 | 819 | 820 | 1 821 | 1 822 | CollectionIDs 823 | 824 | 825 | 0 826 | 2 827 | CI_ID 828 | 829 | 830 | 1 831 | 2 832 | LastErrorCode 833 | 834 | 835 | 836 | 837 | =User!Language 838 | 839 | SrsResources, culture=neutral 840 | 841 | true 842 | Inch 843 | http://reportserver.domain.local/reportserver 844 | f3e5ae98-66c5-4863-b1e2-69153dbb97e3 845 | 846 | -------------------------------------------------------------------------------- /SourceFiles/Software Updates Compliance - Overview Updates missing not deployed.rdl: -------------------------------------------------------------------------------- 1 |  2 | 3 | 0 4 | 5 | 6 | /ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602} 7 | None 8 | 66169a94-0965-431f-8bdb-6ff7fbdeb71b 9 | 10 | 11 | 12 | 13 | 14 | DataSource1 15 | 16 | 17 | =Parameters!SupersededVisible.Value 18 | 19 | 20 | =Parameters!CollectionIDs.Value 21 | 22 | 23 | IF @SupersededVisible = 'Yes' 24 | select UPD.Title 25 | ,VRS.Netbios_Name0 26 | ,VRS.ResourceID 27 | ,CCI.CategoryInstanceName 28 | ,UCS.CI_ID 29 | ,UPD.IsSuperseded 30 | ,UPD.DatePosted 31 | ,count(UCS.CI_ID) over(partition by UCS.CI_ID) as [SystemsWithSameUpdate] 32 | from v_UpdateComplianceStatus UCS 33 | left join v_CITargetedMachines CTM on CTM.CI_ID = UCS.CI_ID and CTM.ResourceID = UCS.ResourceID 34 | inner join v_CICategoryInfo_All CCI on CCI.CI_ID = UCS.CI_ID and CCI.CategoryTypeName = 'UpdateClassification' and (CCI.CategoryInstanceName = 'Security Updates' or CCI.CategoryInstanceName = 'Critical Updates') 35 | inner join v_UpdateInfo UPD on UPD.CI_ID = UCS.CI_ID 36 | inner join v_R_System VRS on VRS.ResourceID = UCS.ResourceID 37 | where UCS.ResourceID IN (Select ResourceID from v_FullCollectionMembership fcm where fcm.CollectionID IN (@CollectionIDs)) 38 | and UCS.Status = 2 and CTM.ResourceID is null --and UPD.IsSuperseded = 0 39 | order by SystemsWithSameUpdate desc, UPD.Title, UPD.DatePosted 40 | ---- fix for SQL compat level problem 41 | ---- https://support.microsoft.com/en-us/help/3196320/sql-query-times-out-or-console-slow-on-certain-configuration-manager-d 42 | ---- Force legacy cardinality for SQL server versions before 2016 SP1 (SQL version less than 13.0.4001.0) 43 | --OPTION (QUERYTRACEON 9481) 44 | ---- Force legacy cardinality for SQL server versions 2016 SP1 and higher (SQL version equal or greater than 13.0.4001.0) 45 | --OPTION (USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION')) 46 | ELSE 47 | select UPD.Title 48 | ,VRS.Netbios_Name0 49 | ,VRS.ResourceID 50 | ,CCI.CategoryInstanceName 51 | ,UCS.CI_ID 52 | ,UPD.IsSuperseded 53 | ,UPD.DatePosted 54 | ,count(UCS.CI_ID) over(partition by UCS.CI_ID) as [SystemsWithSameUpdate] 55 | from v_UpdateComplianceStatus UCS 56 | left join v_CITargetedMachines CTM on CTM.CI_ID = UCS.CI_ID and CTM.ResourceID = UCS.ResourceID 57 | inner join v_CICategoryInfo_All CCI on CCI.CI_ID = UCS.CI_ID and CCI.CategoryTypeName = 'UpdateClassification' and (CCI.CategoryInstanceName = 'Security Updates' or CCI.CategoryInstanceName = 'Critical Updates') 58 | inner join v_UpdateInfo UPD on UPD.CI_ID = UCS.CI_ID 59 | inner join v_R_System VRS on VRS.ResourceID = UCS.ResourceID 60 | where UCS.ResourceID IN (Select ResourceID from v_FullCollectionMembership fcm where fcm.CollectionID IN (@CollectionIDs)) 61 | and UCS.Status = 2 and CTM.ResourceID is null and UPD.IsSuperseded = 0 62 | order by SystemsWithSameUpdate desc, UPD.Title, UPD.DatePosted 63 | ---- fix for SQL compat level problem 64 | ---- https://support.microsoft.com/en-us/help/3196320/sql-query-times-out-or-console-slow-on-certain-configuration-manager-d 65 | ---- Force legacy cardinality for SQL server versions before 2016 SP1 (SQL version less than 13.0.4001.0) 66 | --OPTION (QUERYTRACEON 9481) 67 | ---- Force legacy cardinality for SQL server versions 2016 SP1 and higher (SQL version equal or greater than 13.0.4001.0) 68 | --OPTION (USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION')) 69 | true 70 | 71 | 72 | 73 | Title 74 | System.String 75 | 76 | 77 | Netbios_Name0 78 | System.String 79 | 80 | 81 | ResourceID 82 | System.Int32 83 | 84 | 85 | CategoryInstanceName 86 | System.String 87 | 88 | 89 | CI_ID 90 | System.Int32 91 | 92 | 93 | IsSuperseded 94 | System.Int32 95 | 96 | 97 | DatePosted 98 | System.DateTime 99 | 100 | 101 | SystemsWithSameUpdate 102 | System.Int32 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 86.29515mm 116 | 117 | 118 | 37.34722mm 119 | 120 | 121 | 25mm 122 | 123 | 124 | 125 | 126 | 6mm 127 | 128 | 129 | 130 | 131 | true 132 | true 133 | 134 | 135 | 136 | 137 | ComputerName 138 | 141 | 142 | 143 | 144 | 145 | Textbox149 146 | 150 | 151 | 2pt 152 | 2pt 153 | 2pt 154 | 2pt 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | true 163 | true 164 | 165 | 166 | 167 | 168 | Date Posted 169 | 172 | 173 | 174 | 175 | 176 | 177 | Textbox150 178 | 182 | 183 | 2pt 184 | 2pt 185 | 2pt 186 | 2pt 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | true 195 | true 196 | 197 | 198 | 199 | 200 | Superseded 201 | 204 | 205 | 206 | 207 | 208 | 209 | Textbox168 210 | 214 | 215 | 2pt 216 | 2pt 217 | 2pt 218 | 2pt 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 6mm 227 | 228 | 229 | 230 | 231 | true 232 | true 233 | 234 | 235 | 236 | 237 | ="Systems with same update missing: " & First(Fields!SystemsWithSameUpdate.Value) 238 | 239 | 240 | 241 | 242 | SystemsWithSameUpdate1 243 | 247 | 248 | LightGrey 249 | 2pt 250 | 2pt 251 | 2pt 252 | 2pt 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | true 261 | true 262 | 263 | 264 | 265 | 266 | 267 | 270 | 271 | 272 | 273 | 274 | 275 | Textbox159 276 | 280 | 281 | 2pt 282 | 2pt 283 | 2pt 284 | 2pt 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | true 293 | true 294 | 295 | 296 | 297 | 298 | 299 | 302 | 303 | 304 | 305 | 306 | Textbox169 307 | 311 | 312 | 2pt 313 | 2pt 314 | 2pt 315 | 2pt 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 6mm 324 | 325 | 326 | 327 | 328 | true 329 | true 330 | 331 | 332 | 333 | 334 | =Fields!Netbios_Name0.Value 335 | 338 | 339 | 340 | 341 | 342 | Netbios_Name03 343 | 344 | 345 | 346 | 347 | Software Updates Compliance - Per device 348 | 349 | 350 | 0 351 | 352 | 353 | =Fields!Netbios_Name0.Value 354 | 355 | 356 | 357 | 358 | 359 | 360 | 364 | 365 | 2pt 366 | 2pt 367 | 2pt 368 | 2pt 369 | 370 | 371 | true 372 | 373 | 374 | 375 | 376 | 377 | true 378 | true 379 | 380 | 381 | 382 | 383 | =Fields!DatePosted.Value 384 | 387 | 388 | 389 | 390 | 391 | 392 | DatePosted3 393 | 397 | 398 | 2pt 399 | 2pt 400 | 2pt 401 | 2pt 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | true 410 | true 411 | 412 | 413 | 414 | 415 | =IIf(Fields!IsSuperseded.Value = 1, "Yes", "No") 416 | 417 | 418 | 419 | 420 | 421 | 422 | IsSuperseded 423 | 427 | 428 | 2pt 429 | 2pt 430 | 2pt 431 | 2pt 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 271.38054mm 452 | 453 | 454 | true 455 | true 456 | 457 | 458 | 459 | 460 | Update 461 | 464 | 465 | 466 | 467 | 468 | 469 | Textbox152 470 | 474 | 475 | 2pt 476 | 2pt 477 | 2pt 478 | 2pt 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | After 487 | 488 | 489 | 490 | 491 | =Fields!Title.Value 492 | 493 | 494 | 495 | 496 | =Fields!SystemsWithSameUpdate.Value 497 | Descending 498 | 499 | 500 | 501 | 271.38054mm 502 | 503 | 504 | true 505 | true 506 | 507 | 508 | 509 | 510 | =Fields!Title.Value 511 | 512 | 513 | 514 | 515 | 516 | 517 | Title2 518 | 522 | 523 | LightGrey 524 | 2pt 525 | 2pt 526 | 2pt 527 | 2pt 528 | 529 | 530 | 531 | 532 | 533 | 534 | After 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | true 543 | Title2 544 | 545 | 546 | 547 | 548 | 549 | 550 | DataSet1 551 | 23.8125mm 552 | 10.55158mm 553 | 18mm 554 | 420.02291mm 555 | 558 | 559 | 560 | 561 | 562 | true 563 | true 564 | 565 | 566 | 567 | 568 | Systems with missing security updates not yet approved 569 | 573 | 574 | 575 | 578 | 579 | 580 | Textbox7 581 | 6.05366mm 582 | 10.55158mm 583 | 9.75783mm 584 | 420.02291mm 585 | 1 586 | 589 | 590 | 2pt 591 | 2pt 592 | 2pt 593 | 2pt 594 | 595 | 596 | 597 | true 598 | true 599 | 600 | 601 | 602 | 603 | (Superseded can be filtered above) 604 | 605 | 606 | 609 | 610 | 611 | Textbox1 612 | 16.16427mm 613 | 10.55158mm 614 | 5.85258mm 615 | 420.02291mm 616 | 2 617 | 620 | 621 | Top 622 | 2pt 623 | 2pt 624 | 2pt 625 | 2pt 626 | 627 | 628 | 629 | 49.70993mm 630 | 633 | 634 | 635 | 636 | 681.87034mm 637 | 638 | 297mm 639 | 210mm 640 | 20mm 641 | 20mm 642 | 20mm 643 | 20mm 644 | 0.13cm 645 | 646 | 647 | 648 | 649 | 650 | String 651 | 652 | 653 | SMS00001 654 | 655 | 656 | CollectionIDs 657 | true 658 | true 659 | 660 | 661 | String 662 | 663 | 664 | Yes 665 | 666 | 667 | Also show superseded updates 668 | 669 | 670 | 671 | Yes 672 | 673 | 674 | 675 | No 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 1 685 | 2 686 | 687 | 688 | 0 689 | 0 690 | CollectionIDs 691 | 692 | 693 | 0 694 | 1 695 | SupersededVisible 696 | 697 | 698 | 699 | 700 | Mm 701 | http://reportserver.domain.local/reportserver 702 | 8649b1b8-cc33-4ef7-998b-ae4c41462811 703 | 704 | -------------------------------------------------------------------------------- /SourceFiles/UpdatesSummary.rsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | /ConfigMgr_P11/{5C6358F2-4BB6-4a1b-A16E-8D96795D8602} 6 | 7 | 8 | false 9 | false 10 | false 11 | String 12 | 13 | 14 | false 15 | false 16 | false 17 | String 18 | 19 | 20 | false 21 | false 22 | false 23 | Int32 24 | 25 | 26 | false 27 | false 28 | false 29 | String 30 | 31 | 32 | ---SCRIPTVERSION: 20240506 33 | 34 | ----------------------------------------------------------------------------------------------------------------------- 35 | ---- Disclaimer 36 | ---- 37 | ---- This sample script is not supported under any Microsoft standard support program or service. This sample 38 | ---- script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties 39 | ---- including, without limitation, any implied warranties of merchantability or of fitness for a particular 40 | ---- purpose. The entire risk arising out of the use or performance of this sample script and documentation 41 | ---- remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, 42 | ---- production, or delivery of this script be liable for any damages whatsoever (including, without limitation, 43 | ---- damages for loss of business profits, business interruption, loss of business information, or other 44 | ---- pecuniary loss) arising out of the use of or inability to use this sample script or documentation, even 45 | ---- if Microsoft has been advised of the possibility of such damages. 46 | ----------------------------------------------------------------------------------------------------------------------- 47 | ---- Changelog: 48 | ---- 2024-05-06: Changed update compliance definition and removed "and (DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE()) <= 1+@MonthIndexInternal)" 49 | ---- 2022-04-04: Changed the way deployed updates are shown. "MissingUpdatesApproved" will not show updates for excluded deployments anymore. Makes it more consistant with the rest of the report 50 | ---- Added systems domain name to the list 51 | ---- Removed unnecessary where clause: "where (QFE.Description0 = 'Security Update' or QFE.Description0 is null)" 52 | ---- Added Parameter @MonthIndex to be able to set the report one month back. Only possible if updates are delayed for one month and not deployed within the month of their release date 53 | ---- Added Paremeter to be able exclude future deployments based on starttime or deadline, exclude available and diabled deployments 54 | ---- 2022-03-02: Fixed @SecondTuesdayOfMonth and change logic for pulling the current and last rollups. Based on pull request 11 55 | ---- 2021-11-18: Added "Cumulative Update for Microsoft server operating system" string for server 2022 updates. 56 | ---- 2021-08-01: Changed multi value parameter handling to use CTEs due to performance issues with some environments. 57 | ---- Also changed overall compliance to be only compliant in case the last rollup has been installed and changed name from "UpdateAssignmentCompliance" to "OverallComplianceState" 58 | ---- Changed name from "UpdateStatusCompliant" to "UpdateAssignmentCompliant" to be more accurate with the wording 59 | ---- Removed condition "UPDATESTATES.UpdatesApprovedAndMissing = 0" of overall compliance state since it does not really work with the way we exclude deployments 60 | ---- Added option to exclude deployments with starttime in the future 61 | ---- Disabled update deployments will now be excluded from overall compliance state 62 | ---- 2021-06-14: Simplified RollupStatus and CurrentRollupStatus case when clauses 63 | ---- 2021-06-10: Added update install errors and changed query logic due to performance problems starting with 35k systems 64 | ---- 2020-11-30: Added "System Center Endpoint Protection" back to the exclusion list 65 | ---- 2020-11-24: Changed some descriptions, the QuickFixEngineering query due to missing date entries and the language problem from 20201103, 66 | ---- QuickFixEngineering query will also use the rollups as a reference if possible, simplified the current- and last-rollup queries and removed the limitation for Server 2008 67 | ---- 2020-11-03: Changed 'max(CASE WHEN (ISDATE(QFE.InstalledOn0) = 0' to 'max(CASE WHEN (LEN(QFE.InstalledOn0) > 10' due to language problems 68 | ---- 2020-11-03: Changed 'Windows Defender' to 'Microsoft Defender Antivirus' 69 | 70 | ----- START just for testing in SQL directly 71 | --Declare @CollectionID as varchar(max) = 'SMS00001' ---- semicolon seperated list of collectionIDs 72 | --Declare @ExcludeProductList as varchar(max) = N'Microsoft Defender Antivirus;System Center Endpoint Protection'; ---- semicolon seperated list of update products 73 | --Declare @MonthIndex as int = 0; -- 0 = current month, 1 = previous month 74 | 75 | ---- Using a bitmask like parameter. Makes it possible to use a multivalue parameter instead of multiple parameters to filter out some deployments 76 | ---- Using the same CTE function to convert parameter arraylist to CTE as with other multi value parameters due to perf issues on some SQL systems 77 | --Declare @ExcludeDeplBitMask as varchar(20) = '0'---'2;4;8;16'; 78 | ---- 2 = Deployments with starttime in the future will be excluded 79 | ---- 4 = Deployments with deadline in the future will be excluded 80 | ---- 8 = Available deployments will be excluded 81 | ---- 16 = Disabled deployments will be excluded 82 | ----- END just for testing in SQL directly 83 | 84 | ---- Extra variables to prevent variable performance issues in some SQL configurations 85 | Declare @CollectionIDList as varchar(max) = @CollectionID ---- semicolon seperated list of collectionIDs 86 | Declare @ExcludeProducts as varchar(max) = @ExcludeProductList ---- semicolon seperated list of update products 87 | Declare @MonthIndexInternal as int = @MonthIndex 88 | Declare @ExcludeDeplBitMaskInternal as varchar(20) = @ExcludeDeplBitMask 89 | 90 | -- using prefix like "2017-07" to filter for the security rollup of the past month 91 | DECLARE @LastRollupPrefix as char(7); 92 | DECLARE @CurrentRollupPrefix as char(7); 93 | DECLARE @SecondTuesdayOfMonth datetime; 94 | 95 | -- Calculate 2nd Tuesday of month to add the correct date string in case install date is missing in v_GS_QUICK_FIX_ENGINEERING 96 | -- Also used to fill the gap between the first day of a month and the next time a rollup will be released 97 | SET @SecondTuesdayOfMonth = (DATEADD(Month, DATEDIFF(Month, 0, GETDATE()), 0) + 6 + 7 - (DATEPART(Weekday, DATEADD(Month, DATEDIFF(Month, 0, GETDATE()), 0)) + (@@DateFirst + 3) + 7) %7) 98 | 99 | IF GETDATE() < @SecondTuesdayOfMonth 100 | BEGIN 101 | SET @LastRollupPrefix = (SELECT convert(char(7),DATEADD(MONTH,-2-@MonthIndexInternal,GETDATE()),126)) 102 | SET @CurrentRollupPrefix = (SELECT convert(char(7),DATEADD(MONTH,-1-@MonthIndexInternal,GETDATE()),126)) 103 | END 104 | ELSE 105 | BEGIN 106 | SET @LastRollupPrefix = (SELECT convert(char(7),DATEADD(MONTH,-1-@MonthIndexInternal,GETDATE()),126)) 107 | SET @CurrentRollupPrefix = (SELECT convert(char(7),DATEADD(MONTH,0-@MonthIndexInternal,GETDATE()),126)) 108 | END; 109 | 110 | -- PRE QUERIES 111 | -- Create table for collection IDs. Converting string based list into CTE to avoid any parameter performance issues with certain SQL configurations. 112 | WITH CTE_CollIDPieces 113 | AS 114 | ( 115 | SELECT 1 AS ID 116 | ,1 AS [StartString] 117 | ,Cast(CHARINDEX(';', @CollectionIDList,0) as int) AS StopString 118 | UNION ALL 119 | SELECT ID + 1 120 | ,StopString + 1 121 | ,Cast(CHARINDEX(';', @CollectionIDList, StopString + 1) as int) 122 | FROM CTE_CollIDPieces 123 | WHERE StopString > 0 124 | ) 125 | ,CTE_CollIDs 126 | AS 127 | ( 128 | SELECT (SUBSTRING(@CollectionIDList, StartString, 129 | CASE WHEN StopString > 0 THEN StopString - StartString 130 | ELSE LEN(@CollectionIDList) 131 | END)) AS CollectionID 132 | FROM CTE_CollIDPieces 133 | ) 134 | -- Create CTE for excluded products. Converting string based list into CTE to avoid any parameter performance issues with certain SQL configurations. 135 | ,CTE_ProductPieces 136 | AS 137 | ( 138 | SELECT 1 AS ID 139 | ,1 AS [StartString] 140 | ,Cast(CHARINDEX(';', @ExcludeProducts,0) as int) AS StopString 141 | UNION ALL 142 | SELECT ID + 1 143 | ,StopString + 1 144 | ,Cast(CHARINDEX(';', @ExcludeProducts, StopString + 1) as int) 145 | FROM CTE_ProductPieces 146 | WHERE StopString > 0 147 | ) 148 | ,CTE_Products 149 | AS 150 | ( 151 | SELECT (SUBSTRING(@ExcludeProducts, StartString, 152 | CASE WHEN StopString > 0 THEN StopString - StartString 153 | ELSE LEN(@ExcludeProducts) 154 | END)) AS Product 155 | FROM CTE_ProductPieces 156 | ), 157 | -- Using the same CTE function to convert parameter arraylist to CTE as with other multi value parameters due to perf issues on some SQL systems 158 | CTE_ExcludePieces 159 | AS 160 | ( 161 | SELECT 1 AS ID 162 | ,1 AS [StartString] 163 | ,Cast(CHARINDEX(';', @ExcludeDeplBitMaskInternal,0) as int) AS StopString 164 | UNION ALL 165 | SELECT ID + 1 166 | ,StopString + 1 167 | ,Cast(CHARINDEX(';', @ExcludeDeplBitMaskInternal, StopString + 1) as int) 168 | FROM CTE_ExcludePieces 169 | WHERE StopString > 0 170 | ) 171 | ,CTE_ExcludeIDs 172 | AS 173 | ( 174 | SELECT (SUBSTRING(@ExcludeDeplBitMaskInternal, StartString, 175 | CASE WHEN StopString > 0 THEN StopString - StartString 176 | ELSE LEN(@ExcludeDeplBitMaskInternal) 177 | END)) AS ExcludeID 178 | FROM CTE_ExcludePieces 179 | ), 180 | -- generate list of systems we are insterested in 181 | ResourceList (ResourceID) as 182 | ( 183 | Select Distinct ResourceID from v_FullCollectionMembership FCM 184 | inner join CTE_CollIDs on CTE_CollIDs.CollectionID = FCM.CollectionID 185 | ), 186 | -- List of deployments we might need to exclude 187 | ExcludedDeploymentsBaseList (AssignmentID, AssignmentType) as 188 | ( 189 | --- Getting a list of deployments/assignments based on different criteria to further limit the output later on 190 | -- parameter to be able to toggle to ex or include future deployments 191 | -- deployments with STARTTIME in the future 192 | select CIA.AssignmentID, AssignmentType = 2 193 | from v_CIAssignment cia 194 | where cia.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 195 | and cia.StartTime > GETDATE() 196 | -- deployments with DEADLINE in the future 197 | UNION ALL 198 | select CIA.AssignmentID, AssignmentType = 4 199 | from v_CIAssignment cia 200 | where cia.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 201 | and cia.EnforcementDeadline > GETDATE() 202 | -- availabe deployments 203 | UNION ALL 204 | select CIA.AssignmentID, AssignmentType = 8 205 | from v_CIAssignment cia 206 | where cia.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 207 | and cia.EnforcementDeadline is null 208 | -- disabled deployments 209 | UNION ALL 210 | select CIA.AssignmentID, AssignmentType = 16 211 | from v_CIAssignment cia 212 | where cia.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 213 | and cia.AssignmentEnabled = 0 214 | ), 215 | ExcludedDeployments (AssignmentID,AssignmentType, CI_ID) as 216 | ( 217 | -- Select just the deployments we need to exclude 218 | Select AssignmentID, AssignmentType, CI_ID = 0 from ExcludedDeploymentsBaseList 219 | where AssignmentType in (Select ExcludeID from CTE_ExcludeIDs) 220 | UNION ALL 221 | -- Defender Updates will be filtered out, because they will be updated more frequently 222 | -- Other producst can be filtered as well via @ExcludeProductList 223 | select CIA.AssignmentID, AssignmentType = 1, CIATOCI.CI_ID 224 | from v_CIAssignment CIA 225 | inner join v_CIAssignmentToCI CIATOCI on CIATOCI.AssignmentID = CIA.AssignmentID 226 | inner join v_CICategoryInfo CICI on CICI.Ci_ID = CIATOCI.CI_ID 227 | inner join CTE_Products PR on PR.Product = CICI.CategoryInstanceName 228 | ), 229 | -- list of cumulative updates of the current and the past month 230 | CumulativeUpdates (ArticleID,CI_ID, Latest) as 231 | ( 232 | Select 'KB' + updi.ArticleID as Article, CI_ID, Latest = 0 from v_updateinfo updi where (updi.Title like @LastRollupPrefix + ' Cumulative Update for Windows%' or updi.Title like @LastRollupPrefix + ' Security Monthly Quality Rollup%' or updi.Title like @LastRollupPrefix + ' Cumulative Update for Microsoft server operating system%') 233 | UNION ALL 234 | Select 'KB' + updi.ArticleID as Article, CI_ID, Latest = 1 from v_updateinfo updi where (updi.Title like @CurrentRollupPrefix + ' Cumulative Update for Windows%' or updi.Title like @CurrentRollupPrefix + ' Security Monthly Quality Rollup%' or updi.Title like @CurrentRollupPrefix + ' Cumulative Update for Microsoft server operating system%') 235 | ) 236 | 237 | 238 | --MAIN QUERY 239 | select [Name] = VRS.Name0 240 | ,[ResourceID] = VRS.ResourceID 241 | ,[Counter] = 1 -- a counter to make it easier to count in reporting services 242 | ,[Domain] = VRS.Resource_Domain_OR_Workgr0 243 | ,[PendingReboot] = BGBL.ClientState 244 | ,[OSType] = GOS.Caption0 245 | ,[OSBuild] = BGBL.DeviceOSBuild 246 | ,[ClientVersion] = VRS.Client_Version0 247 | ,[WSUSVersion] = USS.LastWUAVersion 248 | ,[DefenderPattern] = AHS.AntivirusSignatureVersion 249 | ,[DefenderPatternAge] = AHS.AntivirusSignatureAge 250 | ,[WSUSScanError] = USS.LastErrorCode 251 | ,[DaysSinceLastOnline] = ISNULL((DateDiff(DAY,BGBL.LastPolicyRequest,GETDATE())),999) 252 | ,[DaysSinceLastAADSLogon] = ISNULL((DateDiff(DAY,VRS.Last_Logon_Timestamp0,GETDATE())), 999) 253 | ,[DaysSinceLastBoot] = ISNULL((DateDiff(DAY,GOS.LastBootUpTime0,GETDATE())), 999) 254 | ,[DaysSinceLastUpdateInstall] = ISNULL((DateDiff(DAY,UPDINSTDATE.LastInstallTime,GETDATE())), 999) 255 | ,[MonthSinceLastOnline] = ISNULL((DateDiff(MONTH,BGBL.LastPolicyRequest,GETDATE())), 999) 256 | ,[MonthSinceLastOnlineABC] = case when ISNULL((DateDiff(MONTH,BGBL.LastPolicyRequest,GETDATE())), 999) = 0 then 'A' 257 | when ISNULL((DateDiff(MONTH,BGBL.LastPolicyRequest,GETDATE())), 999) = 1 then 'B' else 'C' end 258 | ,[MonthSinceLastAADSLogon] = ISNULL((DateDiff(MONTH,VRS.Last_Logon_Timestamp0,GETDATE())), 999) 259 | ,[MonthSinceLastAADSLogonABC] = case when ISNULL((DateDiff(MONTH,VRS.Last_Logon_Timestamp0,GETDATE())), 999) = 0 then 'A' 260 | when ISNULL((DateDiff(MONTH,VRS.Last_Logon_Timestamp0,GETDATE())), 999) = 1 then 'B' else 'C' end 261 | ,[MonthSinceLastBoot] = ISNULL((DateDiff(MONTH,GOS.LastBootUpTime0,GETDATE())), 999) 262 | ,[MonthSinceLastBootABC] = case when ISNULL((DateDiff(MONTH,GOS.LastBootUpTime0,GETDATE())), 999) = 0 then 'A' 263 | when ISNULL((DateDiff(MONTH,GOS.LastBootUpTime0,GETDATE())), 999) = 1 then 'B' else 'C' end 264 | ,[MonthSinceLastUpdateInstall] = ISNULL((DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE())), 999) 265 | ,[MonthSinceLastUpdateInstallABC] = case when ISNULL((DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE())), 999) = 0 then 'A' 266 | when ISNULL((DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE())), 999) = 1 then 'B' else 'C' end 267 | ,[MonthSinceLastUpdateScan] = ISNULL((DateDiff(MONTH,USS.LastScanTime,GETDATE())), 999) 268 | ---- custom compliance state based on deploymentstatus, update status, last installdate and last rollup state. Last update install needs to be at least in the past month 269 | ---- 20240506: Changed compliance and removed last install date. Makes no sense when we also look for the last or current rollup 270 | ,[OverallComplianceState] = Case when AssignmentStatus.Compliant = AssignmentStatus.AssignmentSum and (LASTROLLUPSTAT.Status = 3 or CURRROLLUPSTAT.Status = 3) then 1 else 0 end 271 | ---- 20210801: Changed compliance to contain the last rollup 272 | ---- ,[OverallComplianceState] = Case when AssignmentStatus.Compliant = AssignmentStatus.AssignmentSum and (DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE()) <= 1+@MonthIndexInternal) and (LASTROLLUPSTAT.Status = 3 or CURRROLLUPSTAT.Status = 3) then 1 else 0 end 273 | ---- 20210801: Original UpdateAssignmentCompliance query without last rollup state and with UPDATESTATES.UpdatesApprovedAndMissing = 0 274 | ---- Query does not work anymore because of the way we filter future and not enabled update deployments and the changed name to "OverallComplianceState" 275 | --,[UpdateAssignmentCompliance] = Case when AssignmentStatus.Compliant = AssignmentStatus.AssignmentSum and UPDATESTATES.UpdatesApprovedAndMissing = 0 and (DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE()) <= 1) then 1 else 0 end 276 | ---- "UpdateStatusCompliant" is now called "UpdateAssignmentCompliant" 277 | ,[UpdateAssignmentCompliant] = AssignmentStatus.Compliant 278 | ,[UpdateAssignmentSum] = AssignmentStatus.AssignmentSum 279 | ,[UpdateAssignmentNonCompliant] = AssignmentStatus.AssignmentSum - AssignmentStatus.Compliant 280 | --,[UpdateStatusNonCompliant] = AssignmentStatus.NonCompliant 281 | --,[UpdateStatusUnknown] = case when AssignmentStatus.Unknown is null then 999 282 | --when (AssignmentStatus.Compliant is null and AssignmentStatus.NonCompliant is null and AssignmentStatus.Failed is null and AssignmentStatus.Pending is null) then 999 283 | --else AssignmentStatus.Unknown end ---some are unknown, because the state message seems to be missing 284 | --,[UpdateStatusFailed] = AssignmentStatus.Failed 285 | --,[UpdateStatusPending] = AssignmentStatus.Pending 286 | ,[MissingUpdatesAll] = UPDATESTATES.UpdatesMissingAll 287 | ,[MissingUpdatesApproved] = UPDATESTATES.UpdatesApprovedAndMissing 288 | ,[UpdatesApprovedAll] = UPDATESTATES.UpdatesApproved 289 | ,[UpdatesApprovedMissingAndError] = UPDATESTATES.UpdatesApprovedAndMissingAndError 290 | ---- show status of reference security rollup update 291 | ---- if current rollup ist installed, the last one doesn't matter and will be set to installed as well 292 | ,[LastRollupStatus] = case when LASTROLLUPSTAT.Status = 3 or CURRROLLUPSTAT.Status = 3 then 3 else 2 end 293 | ,[CurrentRollupStatus] = case when CURRROLLUPSTAT.Status = 3 then 3 else 2 end 294 | ---- add a list of all the collections having a software update or all deployments maintenance window 295 | --,[UpdateMWs] = STUFF((select ';' + Description 296 | -- from v_FullCollectionMembership TX0 297 | -- inner join v_ServiceWindow TX1 on TX1.CollectionID = TX0.CollectionID 298 | -- where TX1.ServiceWindowType in (1,4) and TX0.ResourceID = VRS.ResourceID FOR XML PATH('')), 1, 1, '') 299 | ---- add a list of all collections with update deployments to see gaps 300 | ,[UpdateCollections] = STUFF((select '; ' + CIA.CollectionName 301 | from v_FullCollectionMembership FCM 302 | inner join v_CIAssignment CIA on CIA.CollectionID = FCM.CollectionID 303 | where CIA.AssignmentType in (1,5) and FCM.ResourceID = VRS.ResourceID 304 | group by FCM.ResourceID, CIA.CollectionName FOR XML PATH('')), 1, 1, '') 305 | from v_R_System VRS 306 | inner join ResourceList on ResourceList.ResourceID = VRS.ResourceID 307 | ----- Join update states 308 | left join ( 309 | Select ucs.ResourceID 310 | ,[UpdatesApproved] = sum(case when AssignedUpdates.CI_ID is not null then 1 else 0 end) 311 | ,[UpdatesApprovedAndMissing] = sum(case when AssignedUpdates.CI_ID is not null and ucs.Status = 2 then 1 else 0 end) 312 | ,[UpdatesMissingAll] = sum(case when ucs.Status = 2 then 1 else 0 end) 313 | ,[UpdatesApprovedAndMissingAndError] = sum(case when AssignedUpdates.CI_ID is not null and ucs.Status = 2 and (ucs.LastErrorCode is not null and ucs.LastErrorCode !=0) then 1 else 0 end) 314 | from v_UpdateComplianceStatus ucs 315 | inner join v_UpdateCIs upd on upd.CI_ID = ucs.CI_ID 316 | inner join ResourceList on ResourceList.ResourceID = ucs.ResourceID 317 | left join ( 318 | -- updates deployed to the system 319 | select ATM.ResourceID, CATC.CI_ID 320 | from v_CIAssignmentTargetedMachines ATM 321 | inner join v_CIAssignmentToCI CATC on CATC.AssignmentID = ATM.AssignmentID 322 | inner join ResourceList on ResourceList.ResourceID = ATM.ResourceID 323 | inner join v_CIAssignment CIA on CIA.AssignmentID = CATC.AssignmentID 324 | where CIA.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 325 | and CIA.AssignmentID not in (Select AssignmentID from ExcludedDeployments) -- Exclude some deployments based on parameter settings 326 | group by ATM.ResourceID, CATC.CI_ID 327 | ) as AssignedUpdates on UCS.ResourceID = AssignedUpdates.ResourceID and AssignedUpdates.CI_ID = UCS.CI_ID 328 | where upd.IsHidden = 0 and upd.CIType_ID in (1,8) --- 1 = update, 8 = update bundle, 9 = update group 329 | and ucs.CI_ID not in (Select CI_ID from ExcludedDeployments) -- exclude Defender and SCEP from statistic 330 | group by ucs.ResourceID 331 | ) UPDATESTATES on UPDATESTATES.ResourceID = VRS.ResourceID 332 | --- join last Rollup as reference 333 | left join ( 334 | select UCS.ResourceID, 335 | max(UCS.Status) as Status 336 | from v_UpdateComplianceStatus UCS 337 | inner join ResourceList on ResourceList.ResourceID = ucs.ResourceID 338 | inner join CumulativeUpdates CUU on CUU.CI_ID = UCS.CI_ID and CUU.Latest = 0 339 | group by UCS.ResourceID 340 | ) as LASTROLLUPSTAT on LASTROLLUPSTAT.ResourceID = VRS.ResourceID 341 | --- join current Rollup as reference 342 | left join ( 343 | select UCS.ResourceID, 344 | max(UCS.Status) as Status 345 | from v_UpdateComplianceStatus UCS 346 | inner join ResourceList on ResourceList.ResourceID = ucs.ResourceID 347 | inner join CumulativeUpdates CUU on CUU.CI_ID = UCS.CI_ID and CUU.Latest = 1 348 | group by UCS.ResourceID 349 | ) as CURRROLLUPSTAT on CURRROLLUPSTAT.ResourceID = VRS.ResourceID 350 | --- Join OS Information 351 | left join v_GS_OPERATING_SYSTEM GOS on GOS.ResourceID = VRS.ResourceID 352 | --- Join Client Health status 353 | --left join v_CH_ClientSummary CHCS on CHCS.ResourceID = VRS.ResourceID 354 | --- Join WSUS Client Info 355 | left join v_UpdateScanStatus USS on USS.ResourceID = VRS.ResourceID 356 | --- Join Antimalware Info 357 | left join v_GS_AntimalwareHealthStatus AHS on AHS.ResourceID = VRS.ResourceID 358 | --- join update compliance status (for overall compliance, failed and pending status) 359 | left join ( 360 | SELECT uas.ResourceID, 361 | count(uas.ResourceID) as AssignmentSum, 362 | --max(uas.LastComplianceMessageTime) as LastComplianceMessageTime, 363 | sum(CASE WHEN (IsCompliant = 1) THEN 1 ELSE 0 END) AS Compliant 364 | --sum(CASE WHEN (IsCompliant = 0) THEN 1 ELSE 0 END) AS NonCompliant, 365 | --sum(CASE WHEN (IsCompliant is null) THEN 1 ELSE 0 END) AS Unknown, 366 | --sum(CASE WHEN (IsCompliant = 0) AND LastEnforcementMessageID in (6,9) THEN 1 ELSE 0 END) AS Failed, 367 | --sum(CASE WHEN (IsCompliant = 0) AND LastEnforcementMessageID not in (0,6,9) THEN 1 ELSE 0 END) AS Pending 368 | FROM v_UpdateAssignmentStatus uas 369 | inner join ResourceList on ResourceList.ResourceID = uas.ResourceID 370 | where UAS.AssignmentID not in (Select AssignmentID from ExcludedDeployments) -- exclude defender and scep deployments from compliants as well as other deployments if selected 371 | group by uas.ResourceID 372 | ) as AssignmentStatus on AssignmentStatus.ResourceID = VRS.ResourceID 373 | ---- join last update installdate for security updates just as a reference to find problematic clients not installing security updates at all 374 | ---- need to convert datetime for older OS from 64bit binary to datetime 375 | left join ( 376 | select qfe.ResourceID 377 | --- CUU.CI_ID is not null means the current rollup is installed and we can use that installdate as reference 378 | ,LastInstallTime = max(CASE WHEN CUU.CI_ID is not null then 379 | ( 380 | --- the current rollup is installed, but do we have a valid install date? In some cases the installdate seems to be mising. For some Win10 1803 systems for example. 381 | --- "-05" impossible date, since the update could not be released the 5th day of the month (normally). That should indicate the missing date info and give us enough info about the state of the system 382 | --CASE WHEN qfe.InstalledOn0 = '' THEN @UpdatePrefix + '-05 00:00:00' ELSE TRY_CONVERT(datetime,qfe.InstalledOn0,101) END 383 | CASE WHEN qfe.InstalledOn0 = '' THEN @CurrentRollupPrefix + '-05 00:00:00' ELSE TRY_CONVERT(datetime,qfe.InstalledOn0,101) END 384 | ) 385 | else --CUU.CI_ID IS null 386 | ( 387 | --- due to some older systems sending datetime as binary like this: 01cc31160e1c4bac and since there is no cumulative update for such systems "CUU.CI_ID is null" should always be valid 388 | --- Found three different date strings so far: MM/dd/yyyy or yyyMMdd or binary 389 | CASE WHEN (LEN(QFE.InstalledOn0) > 10) 390 | THEN CAST((TRY_CONVERT(BIGINT,TRY_CONVERT(VARBINARY(64), '0x' + QFE.InstalledOn0,1)) / 864000000000.0 - 109207) AS DATETIME) 391 | ELSE TRY_CONVERT(datetime,qfe.InstalledOn0,101) 392 | END 393 | ) END) 394 | from v_GS_QUICK_FIX_ENGINEERING QFE 395 | inner join ResourceList on ResourceList.ResourceID = QFE.ResourceID 396 | left join CumulativeUpdates CUU on CUU.ArticleID = QFE.HotFixID0 397 | --where (QFE.Description0 = 'Security Update' or QFE.Description0 is null) 398 | group by qfe.ResourceID 399 | ) UPDINSTDATE on UPDINSTDATE.ResourceID = VRS.ResourceID 400 | ---- join Pending Reboot Info 401 | --left join BGB_LiveData BGBL on BGBL.ResourceID = VRS.ResourceID ---- <- no direct rights assigned and no other view available, using v_CombinedDeviceResources instead 402 | left join v_CombinedDeviceResources BGBL on BGBL.MachineID = VRS.ResourceID 403 | 404 | ---- fix for SQL compat level problem 405 | ---- https://support.microsoft.com/en-us/help/3196320/sql-query-times-out-or-console-slow-on-certain-configuration-manager-d 406 | ---- Force legacy cardinality for SQL server versions before 2016 SP1 (SQL version less than 13.0.4001.0) 407 | --OPTION (QUERYTRACEON 9481) 408 | ---- Force legacy cardinality for SQL server versions 2016 SP1 and higher (SQL version equal or greater than 13.0.4001.0) 409 | --OPTION (USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION')) 410 | true 411 | 412 | 413 | 414 | Name 415 | System.String 416 | 417 | 418 | ResourceID 419 | System.Int32 420 | 421 | 422 | Counter 423 | System.Int32 424 | 425 | 426 | Domain 427 | System.String 428 | 429 | 430 | PendingReboot 431 | System.Int32 432 | 433 | 434 | OSType 435 | System.String 436 | 437 | 438 | OSBuild 439 | System.String 440 | 441 | 442 | ClientVersion 443 | System.String 444 | 445 | 446 | WSUSVersion 447 | System.String 448 | 449 | 450 | DefenderPattern 451 | System.String 452 | 453 | 454 | DaysSinceLastOnline 455 | System.Int32 456 | 457 | 458 | DefenderPatternAge 459 | System.Int32 460 | 461 | 462 | WSUSScanError 463 | System.Int32 464 | 465 | 466 | DaysSinceLastAADSLogon 467 | System.Int32 468 | 469 | 470 | DaysSinceLastBoot 471 | System.Int32 472 | 473 | 474 | DaysSinceLastUpdateInstall 475 | System.Int32 476 | 477 | 478 | MonthSinceLastOnlineABC 479 | System.String 480 | 481 | 482 | MonthSinceLastOnline 483 | System.Int32 484 | 485 | 486 | MonthSinceLastAADSLogon 487 | System.Int32 488 | 489 | 490 | MonthSinceLastAADSLogonABC 491 | System.String 492 | 493 | 494 | MonthSinceLastBoot 495 | System.Int32 496 | 497 | 498 | MonthSinceLastBootABC 499 | System.String 500 | 501 | 502 | MonthSinceLastUpdateInstall 503 | System.Int32 504 | 505 | 506 | MonthSinceLastUpdateInstallABC 507 | System.String 508 | 509 | 510 | MonthSinceLastUpdateScan 511 | System.Int32 512 | 513 | 514 | OverallComplianceState 515 | System.Int32 516 | 517 | 518 | UpdateAssignmentCompliant 519 | System.Int32 520 | 521 | 522 | UpdateAssignmentSum 523 | System.Int32 524 | 525 | 526 | UpdateAssignmentNonCompliant 527 | System.Int32 528 | 529 | 530 | MissingUpdatesAll 531 | System.Int32 532 | 533 | 534 | MissingUpdatesApproved 535 | System.Int32 536 | 537 | 538 | UpdatesApprovedAll 539 | System.Int32 540 | 541 | 542 | UpdatesApprovedMissingAndError 543 | System.Int32 544 | 545 | 546 | LastRollupStatus 547 | System.Int32 548 | 549 | 550 | CurrentRollupStatus 551 | System.Int32 552 | 553 | 554 | UpdateCollections 555 | System.String 556 | 557 | 558 | 559 | http://reportserver.domain.local/ReportServer 560 | -------------------------------------------------------------------------------- /SourceFiles/UpdatesSummary.sql: -------------------------------------------------------------------------------- 1 | ---SCRIPTVERSION: 20220404 2 | 3 | ----------------------------------------------------------------------------------------------------------------------- 4 | ---- Disclaimer 5 | ---- 6 | ---- This sample script is not supported under any Microsoft standard support program or service. This sample 7 | ---- script is provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties 8 | ---- including, without limitation, any implied warranties of merchantability or of fitness for a particular 9 | ---- purpose. The entire risk arising out of the use or performance of this sample script and documentation 10 | ---- remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, 11 | ---- production, or delivery of this script be liable for any damages whatsoever (including, without limitation, 12 | ---- damages for loss of business profits, business interruption, loss of business information, or other 13 | ---- pecuniary loss) arising out of the use of or inability to use this sample script or documentation, even 14 | ---- if Microsoft has been advised of the possibility of such damages. 15 | ----------------------------------------------------------------------------------------------------------------------- 16 | ---- Changelog: 17 | ---- 2022-04-04: Changed the way deployed updates are shown. "MissingUpdatesApproved" will not show updates for excluded deployments anymore. Makes it more consistant with the rest of the report 18 | ---- Added systems domain name to the list 19 | ---- Removed unnecessary where clause: "where (QFE.Description0 = 'Security Update' or QFE.Description0 is null)" 20 | ---- Added Parameter @MonthIndex to be able to set the report one month back. Only possible if updates are delayed for one month and not deployed within the month of their release date 21 | ---- Added Paremeter to be able exclude future deployments based on starttime or deadline, exclude available and diabled deployments 22 | ---- 2022-03-02: Fixed @SecondTuesdayOfMonth and change logic for pulling the current and last rollups. Based on pull request 11 23 | ---- 2021-11-18: Added "Cumulative Update for Microsoft server operating system" string for server 2022 updates. 24 | ---- 2021-08-01: Changed multi value parameter handling to use CTEs due to performance issues with some environments. 25 | ---- Also changed overall compliance to be only compliant in case the last rollup has been installed and changed name from "UpdateAssignmentCompliance" to "OverallComplianceState" 26 | ---- Changed name from "UpdateStatusCompliant" to "UpdateAssignmentCompliant" to be more accurate with the wording 27 | ---- Removed condition "UPDATESTATES.UpdatesApprovedAndMissing = 0" of overall compliance state since it does not really work with the way we exclude deployments 28 | ---- Added option to exclude deployments with starttime in the future 29 | ---- Disabled update deployments will now be excluded from overall compliance state 30 | ---- 2021-06-14: Simplified RollupStatus and CurrentRollupStatus case when clauses 31 | ---- 2021-06-10: Added update install errors and changed query logic due to performance problems starting with 35k systems 32 | ---- 2020-11-30: Added "System Center Endpoint Protection" back to the exclusion list 33 | ---- 2020-11-24: Changed some descriptions, the QuickFixEngineering query due to missing date entries and the language problem from 20201103, 34 | ---- QuickFixEngineering query will also use the rollups as a reference if possible, simplified the current- and last-rollup queries and removed the limitation for Server 2008 35 | ---- 2020-11-03: Changed 'max(CASE WHEN (ISDATE(QFE.InstalledOn0) = 0' to 'max(CASE WHEN (LEN(QFE.InstalledOn0) > 10' due to language problems 36 | ---- 2020-11-03: Changed 'Windows Defender' to 'Microsoft Defender Antivirus' 37 | 38 | --- START just for testing in SQL directly 39 | Declare @CollectionID as varchar(max) = 'SMS00001' ---- semicolon seperated list of collectionIDs 40 | Declare @ExcludeProductList as varchar(max) = N'Microsoft Defender Antivirus;System Center Endpoint Protection'; ---- semicolon seperated list of update products 41 | Declare @MonthIndex as int = 0; -- 0 = current month, 1 = previous month 42 | 43 | -- Using a bitmask like parameter. Makes it possible to use a multivalue parameter instead of multiple parameters to filter out some deployments 44 | -- Using the same CTE function to convert parameter arraylist to CTE as with other multi value parameters due to perf issues on some SQL systems 45 | Declare @ExcludeDeplBitMask as varchar(20) = '8;16'---'2;4;8;16'; 46 | -- 2 = Deployments with starttime in the future will be excluded 47 | -- 4 = Deployments with deadline in the future will be excluded 48 | -- 8 = Available deployments will be excluded 49 | -- 16 = Disabled deployments will be excluded 50 | --- END just for testing in SQL directly 51 | 52 | ---- Extra variables to prevent variable performance issues in some SQL configurations 53 | Declare @CollectionIDList as varchar(max) = @CollectionID ---- semicolon seperated list of collectionIDs 54 | Declare @ExcludeProducts as varchar(max) = @ExcludeProductList ---- semicolon seperated list of update products 55 | Declare @MonthIndexInternal as int = @MonthIndex 56 | Declare @ExcludeDeplBitMaskInternal as varchar(20) = @ExcludeDeplBitMask 57 | 58 | -- using prefix like "2017-07" to filter for the security rollup of the past month 59 | DECLARE @LastRollupPrefix as char(7); 60 | DECLARE @CurrentRollupPrefix as char(7); 61 | DECLARE @SecondTuesdayOfMonth datetime; 62 | 63 | -- Calculate 2nd Tuesday of month to add the correct date string in case install date is missing in v_GS_QUICK_FIX_ENGINEERING 64 | -- Also used to fill the gap between the first day of a month and the next time a rollup will be released 65 | SET @SecondTuesdayOfMonth = (DATEADD(Month, DATEDIFF(Month, 0, GETDATE()), 0) + 6 + 7 - (DATEPART(Weekday, DATEADD(Month, DATEDIFF(Month, 0, GETDATE()), 0)) + (@@DateFirst + 3) + 7) %7) 66 | 67 | IF GETDATE() < @SecondTuesdayOfMonth 68 | BEGIN 69 | SET @LastRollupPrefix = (SELECT convert(char(7),DATEADD(MONTH,-2-@MonthIndexInternal,GETDATE()),126)) 70 | SET @CurrentRollupPrefix = (SELECT convert(char(7),DATEADD(MONTH,-1-@MonthIndexInternal,GETDATE()),126)) 71 | END 72 | ELSE 73 | BEGIN 74 | SET @LastRollupPrefix = (SELECT convert(char(7),DATEADD(MONTH,-1-@MonthIndexInternal,GETDATE()),126)) 75 | SET @CurrentRollupPrefix = (SELECT convert(char(7),DATEADD(MONTH,0-@MonthIndexInternal,GETDATE()),126)) 76 | END; 77 | 78 | -- PRE QUERIES 79 | -- Create table for collection IDs. Converting string based list into CTE to avoid any parameter performance issues with certain SQL configurations. 80 | WITH CTE_CollIDPieces 81 | AS 82 | ( 83 | SELECT 1 AS ID 84 | ,1 AS [StartString] 85 | ,Cast(CHARINDEX(';', @CollectionIDList,0) as int) AS StopString 86 | UNION ALL 87 | SELECT ID + 1 88 | ,StopString + 1 89 | ,Cast(CHARINDEX(';', @CollectionIDList, StopString + 1) as int) 90 | FROM CTE_CollIDPieces 91 | WHERE StopString > 0 92 | ) 93 | ,CTE_CollIDs 94 | AS 95 | ( 96 | SELECT (SUBSTRING(@CollectionIDList, StartString, 97 | CASE WHEN StopString > 0 THEN StopString - StartString 98 | ELSE LEN(@CollectionIDList) 99 | END)) AS CollectionID 100 | FROM CTE_CollIDPieces 101 | ) 102 | -- Create CTE for excluded products. Converting string based list into CTE to avoid any parameter performance issues with certain SQL configurations. 103 | ,CTE_ProductPieces 104 | AS 105 | ( 106 | SELECT 1 AS ID 107 | ,1 AS [StartString] 108 | ,Cast(CHARINDEX(';', @ExcludeProducts,0) as int) AS StopString 109 | UNION ALL 110 | SELECT ID + 1 111 | ,StopString + 1 112 | ,Cast(CHARINDEX(';', @ExcludeProducts, StopString + 1) as int) 113 | FROM CTE_ProductPieces 114 | WHERE StopString > 0 115 | ) 116 | ,CTE_Products 117 | AS 118 | ( 119 | SELECT (SUBSTRING(@ExcludeProducts, StartString, 120 | CASE WHEN StopString > 0 THEN StopString - StartString 121 | ELSE LEN(@ExcludeProducts) 122 | END)) AS Product 123 | FROM CTE_ProductPieces 124 | ), 125 | -- Using the same CTE function to convert parameter arraylist to CTE as with other multi value parameters due to perf issues on some SQL systems 126 | CTE_ExcludePieces 127 | AS 128 | ( 129 | SELECT 1 AS ID 130 | ,1 AS [StartString] 131 | ,Cast(CHARINDEX(';', @ExcludeDeplBitMaskInternal,0) as int) AS StopString 132 | UNION ALL 133 | SELECT ID + 1 134 | ,StopString + 1 135 | ,Cast(CHARINDEX(';', @ExcludeDeplBitMaskInternal, StopString + 1) as int) 136 | FROM CTE_ExcludePieces 137 | WHERE StopString > 0 138 | ) 139 | ,CTE_ExcludeIDs 140 | AS 141 | ( 142 | SELECT (SUBSTRING(@ExcludeDeplBitMaskInternal, StartString, 143 | CASE WHEN StopString > 0 THEN StopString - StartString 144 | ELSE LEN(@ExcludeDeplBitMaskInternal) 145 | END)) AS ExcludeID 146 | FROM CTE_ExcludePieces 147 | ), 148 | -- generate list of systems we are insterested in 149 | ResourceList (ResourceID) as 150 | ( 151 | Select Distinct ResourceID from v_FullCollectionMembership FCM 152 | inner join CTE_CollIDs on CTE_CollIDs.CollectionID = FCM.CollectionID 153 | ), 154 | -- List of deployments we might need to exclude 155 | ExcludedDeploymentsBaseList (AssignmentID, AssignmentType) as 156 | ( 157 | --- Getting a list of deployments/assignments based on different criteria to further limit the output later on 158 | -- parameter to be able to toggle to ex or include future deployments 159 | -- deployments with STARTTIME in the future 160 | select CIA.AssignmentID, AssignmentType = 2 161 | from v_CIAssignment cia 162 | where cia.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 163 | and cia.StartTime > GETDATE() 164 | -- deployments with DEADLINE in the future 165 | UNION ALL 166 | select CIA.AssignmentID, AssignmentType = 4 167 | from v_CIAssignment cia 168 | where cia.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 169 | and cia.EnforcementDeadline > GETDATE() 170 | -- availabe deployments 171 | UNION ALL 172 | select CIA.AssignmentID, AssignmentType = 8 173 | from v_CIAssignment cia 174 | where cia.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 175 | and cia.EnforcementDeadline is null 176 | -- disabled deployments 177 | UNION ALL 178 | select CIA.AssignmentID, AssignmentType = 16 179 | from v_CIAssignment cia 180 | where cia.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 181 | and cia.AssignmentEnabled = 0 182 | ), 183 | ExcludedDeployments (AssignmentID,AssignmentType, CI_ID) as 184 | ( 185 | -- Select just the deployments we need to exclude 186 | Select AssignmentID, AssignmentType, CI_ID = 0 from ExcludedDeploymentsBaseList 187 | where AssignmentType in (Select ExcludeID from CTE_ExcludeIDs) 188 | UNION ALL 189 | -- Defender Updates will be filtered out, because they will be updated more frequently 190 | -- Other producst can be filtered as well via @ExcludeProductList 191 | select CIA.AssignmentID, AssignmentType = 1, CIATOCI.CI_ID 192 | from v_CIAssignment CIA 193 | inner join v_CIAssignmentToCI CIATOCI on CIATOCI.AssignmentID = CIA.AssignmentID 194 | inner join v_CICategoryInfo CICI on CICI.Ci_ID = CIATOCI.CI_ID 195 | inner join CTE_Products PR on PR.Product = CICI.CategoryInstanceName 196 | ), 197 | -- list of cumulative updates of the current and the past month 198 | CumulativeUpdates (ArticleID,CI_ID, Latest) as 199 | ( 200 | Select 'KB' + updi.ArticleID as Article, CI_ID, Latest = 0 from v_updateinfo updi where (updi.Title like @LastRollupPrefix + ' Cumulative Update for Windows%' or updi.Title like @LastRollupPrefix + ' Security Monthly Quality Rollup%' or updi.Title like @LastRollupPrefix + ' Cumulative Update for Microsoft server operating system%') 201 | UNION ALL 202 | Select 'KB' + updi.ArticleID as Article, CI_ID, Latest = 1 from v_updateinfo updi where (updi.Title like @CurrentRollupPrefix + ' Cumulative Update for Windows%' or updi.Title like @CurrentRollupPrefix + ' Security Monthly Quality Rollup%' or updi.Title like @CurrentRollupPrefix + ' Cumulative Update for Microsoft server operating system%') 203 | ) 204 | 205 | 206 | --MAIN QUERY 207 | select [Name] = VRS.Name0 208 | ,[ResourceID] = VRS.ResourceID 209 | ,[Counter] = 1 -- a counter to make it easier to count in reporting services 210 | ,[Domain] = VRS.Resource_Domain_OR_Workgr0 211 | ,[PendingReboot] = BGBL.ClientState 212 | ,[OSType] = GOS.Caption0 213 | ,[OSBuild] = BGBL.DeviceOSBuild 214 | ,[ClientVersion] = VRS.Client_Version0 215 | ,[WSUSVersion] = USS.LastWUAVersion 216 | ,[DefenderPattern] = AHS.AntivirusSignatureVersion 217 | ,[DefenderPatternAge] = AHS.AntivirusSignatureAge 218 | ,[WSUSScanError] = USS.LastErrorCode 219 | ,[DaysSinceLastOnline] = ISNULL((DateDiff(DAY,BGBL.LastPolicyRequest,GETDATE())),999) 220 | ,[DaysSinceLastAADSLogon] = ISNULL((DateDiff(DAY,VRS.Last_Logon_Timestamp0,GETDATE())), 999) 221 | ,[DaysSinceLastBoot] = ISNULL((DateDiff(DAY,GOS.LastBootUpTime0,GETDATE())), 999) 222 | ,[DaysSinceLastUpdateInstall] = ISNULL((DateDiff(DAY,UPDINSTDATE.LastInstallTime,GETDATE())), 999) 223 | ,[MonthSinceLastOnline] = ISNULL((DateDiff(MONTH,BGBL.LastPolicyRequest,GETDATE())), 999) 224 | ,[MonthSinceLastOnlineABC] = case when ISNULL((DateDiff(MONTH,BGBL.LastPolicyRequest,GETDATE())), 999) = 0 then 'A' 225 | when ISNULL((DateDiff(MONTH,BGBL.LastPolicyRequest,GETDATE())), 999) = 1 then 'B' else 'C' end 226 | ,[MonthSinceLastAADSLogon] = ISNULL((DateDiff(MONTH,VRS.Last_Logon_Timestamp0,GETDATE())), 999) 227 | ,[MonthSinceLastAADSLogonABC] = case when ISNULL((DateDiff(MONTH,VRS.Last_Logon_Timestamp0,GETDATE())), 999) = 0 then 'A' 228 | when ISNULL((DateDiff(MONTH,VRS.Last_Logon_Timestamp0,GETDATE())), 999) = 1 then 'B' else 'C' end 229 | ,[MonthSinceLastBoot] = ISNULL((DateDiff(MONTH,GOS.LastBootUpTime0,GETDATE())), 999) 230 | ,[MonthSinceLastBootABC] = case when ISNULL((DateDiff(MONTH,GOS.LastBootUpTime0,GETDATE())), 999) = 0 then 'A' 231 | when ISNULL((DateDiff(MONTH,GOS.LastBootUpTime0,GETDATE())), 999) = 1 then 'B' else 'C' end 232 | ,[MonthSinceLastUpdateInstall] = ISNULL((DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE())), 999) 233 | ,[MonthSinceLastUpdateInstallABC] = case when ISNULL((DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE())), 999) = 0 then 'A' 234 | when ISNULL((DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE())), 999) = 1 then 'B' else 'C' end 235 | ,[MonthSinceLastUpdateScan] = ISNULL((DateDiff(MONTH,USS.LastScanTime,GETDATE())), 999) 236 | ---- custom compliance state based on deploymentstatus, update status, last installdate and last rollup state. Last update install needs to be at least in the past month 237 | ---- 20210801: Changed compliance to contain the last rollup 238 | ,[OverallComplianceState] = Case when AssignmentStatus.Compliant = AssignmentStatus.AssignmentSum and (DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE()) <= 1+@MonthIndexInternal) and (LASTROLLUPSTAT.Status = 3 or CURRROLLUPSTAT.Status = 3) then 1 else 0 end 239 | ---- 20210801: Original UpdateAssignmentCompliance query without last rollup state and with UPDATESTATES.UpdatesApprovedAndMissing = 0 240 | ---- Query does not work anymore because of the way we filter future and not enabled update deployments and the changed name to "OverallComplianceState" 241 | --,[UpdateAssignmentCompliance] = Case when AssignmentStatus.Compliant = AssignmentStatus.AssignmentSum and UPDATESTATES.UpdatesApprovedAndMissing = 0 and (DateDiff(MONTH,UPDINSTDATE.LastInstallTime,GETDATE()) <= 1) then 1 else 0 end 242 | ---- "UpdateStatusCompliant" is now called "UpdateAssignmentCompliant" 243 | ,[UpdateAssignmentCompliant] = AssignmentStatus.Compliant 244 | ,[UpdateAssignmentSum] = AssignmentStatus.AssignmentSum 245 | ,[UpdateAssignmentNonCompliant] = AssignmentStatus.AssignmentSum - AssignmentStatus.Compliant 246 | --,[UpdateStatusNonCompliant] = AssignmentStatus.NonCompliant 247 | --,[UpdateStatusUnknown] = case when AssignmentStatus.Unknown is null then 999 248 | --when (AssignmentStatus.Compliant is null and AssignmentStatus.NonCompliant is null and AssignmentStatus.Failed is null and AssignmentStatus.Pending is null) then 999 249 | --else AssignmentStatus.Unknown end ---some are unknown, because the state message seems to be missing 250 | --,[UpdateStatusFailed] = AssignmentStatus.Failed 251 | --,[UpdateStatusPending] = AssignmentStatus.Pending 252 | ,[MissingUpdatesAll] = UPDATESTATES.UpdatesMissingAll 253 | ,[MissingUpdatesApproved] = UPDATESTATES.UpdatesApprovedAndMissing 254 | ,[UpdatesApprovedAll] = UPDATESTATES.UpdatesApproved 255 | ,[UpdatesApprovedMissingAndError] = UPDATESTATES.UpdatesApprovedAndMissingAndError 256 | ---- show status of reference security rollup update 257 | ---- if current rollup ist installed, the last one doesn't matter and will be set to installed as well 258 | ,[LastRollupStatus] = case when LASTROLLUPSTAT.Status = 3 or CURRROLLUPSTAT.Status = 3 then 3 else 2 end 259 | ,[CurrentRollupStatus] = case when CURRROLLUPSTAT.Status = 3 then 3 else 2 end 260 | ---- add a list of all the collections having a software update or all deployments maintenance window 261 | --,[UpdateMWs] = STUFF((select ';' + Description 262 | -- from v_FullCollectionMembership TX0 263 | -- inner join v_ServiceWindow TX1 on TX1.CollectionID = TX0.CollectionID 264 | -- where TX1.ServiceWindowType in (1,4) and TX0.ResourceID = VRS.ResourceID FOR XML PATH('')), 1, 1, '') 265 | ---- add a list of all collections with update deployments to see gaps 266 | ,[UpdateCollections] = STUFF((select '; ' + CIA.CollectionName 267 | from v_FullCollectionMembership FCM 268 | inner join v_CIAssignment CIA on CIA.CollectionID = FCM.CollectionID 269 | where CIA.AssignmentType in (1,5) and FCM.ResourceID = VRS.ResourceID 270 | group by FCM.ResourceID, CIA.CollectionName FOR XML PATH('')), 1, 1, '') 271 | from v_R_System VRS 272 | inner join ResourceList on ResourceList.ResourceID = VRS.ResourceID 273 | ----- Join update states 274 | left join ( 275 | Select ucs.ResourceID 276 | ,[UpdatesApproved] = sum(case when AssignedUpdates.CI_ID is not null then 1 else 0 end) 277 | ,[UpdatesApprovedAndMissing] = sum(case when AssignedUpdates.CI_ID is not null and ucs.Status = 2 then 1 else 0 end) 278 | ,[UpdatesMissingAll] = sum(case when ucs.Status = 2 then 1 else 0 end) 279 | ,[UpdatesApprovedAndMissingAndError] = sum(case when AssignedUpdates.CI_ID is not null and ucs.Status = 2 and (ucs.LastErrorCode is not null and ucs.LastErrorCode !=0) then 1 else 0 end) 280 | from v_UpdateComplianceStatus ucs 281 | inner join v_UpdateCIs upd on upd.CI_ID = ucs.CI_ID 282 | inner join ResourceList on ResourceList.ResourceID = ucs.ResourceID 283 | left join ( 284 | -- updates deployed to the system 285 | select ATM.ResourceID, CATC.CI_ID 286 | from v_CIAssignmentTargetedMachines ATM 287 | inner join v_CIAssignmentToCI CATC on CATC.AssignmentID = ATM.AssignmentID 288 | inner join ResourceList on ResourceList.ResourceID = ATM.ResourceID 289 | inner join v_CIAssignment CIA on CIA.AssignmentID = CATC.AssignmentID 290 | where CIA.AssignmentType in (1,5) -- 1 = updates, 5 = update groups 291 | and CIA.AssignmentID not in (Select AssignmentID from ExcludedDeployments) -- Exclude some deployments based on parameter settings 292 | group by ATM.ResourceID, CATC.CI_ID 293 | ) as AssignedUpdates on UCS.ResourceID = AssignedUpdates.ResourceID and AssignedUpdates.CI_ID = UCS.CI_ID 294 | where upd.IsHidden = 0 and upd.CIType_ID in (1,8) --- 1 = update, 8 = update bundle, 9 = update group 295 | and ucs.CI_ID not in (Select CI_ID from ExcludedDeployments) -- exclude Defender and SCEP from statistic 296 | group by ucs.ResourceID 297 | ) UPDATESTATES on UPDATESTATES.ResourceID = VRS.ResourceID 298 | --- join last Rollup as reference 299 | left join ( 300 | select UCS.ResourceID, 301 | max(UCS.Status) as Status 302 | from v_UpdateComplianceStatus UCS 303 | inner join ResourceList on ResourceList.ResourceID = ucs.ResourceID 304 | inner join CumulativeUpdates CUU on CUU.CI_ID = UCS.CI_ID and CUU.Latest = 0 305 | group by UCS.ResourceID 306 | ) as LASTROLLUPSTAT on LASTROLLUPSTAT.ResourceID = VRS.ResourceID 307 | --- join current Rollup as reference 308 | left join ( 309 | select UCS.ResourceID, 310 | max(UCS.Status) as Status 311 | from v_UpdateComplianceStatus UCS 312 | inner join ResourceList on ResourceList.ResourceID = ucs.ResourceID 313 | inner join CumulativeUpdates CUU on CUU.CI_ID = UCS.CI_ID and CUU.Latest = 1 314 | group by UCS.ResourceID 315 | ) as CURRROLLUPSTAT on CURRROLLUPSTAT.ResourceID = VRS.ResourceID 316 | --- Join OS Information 317 | left join v_GS_OPERATING_SYSTEM GOS on GOS.ResourceID = VRS.ResourceID 318 | --- Join Client Health status 319 | --left join v_CH_ClientSummary CHCS on CHCS.ResourceID = VRS.ResourceID 320 | --- Join WSUS Client Info 321 | left join v_UpdateScanStatus USS on USS.ResourceID = VRS.ResourceID 322 | --- Join Antimalware Info 323 | left join v_GS_AntimalwareHealthStatus AHS on AHS.ResourceID = VRS.ResourceID 324 | --- join update compliance status (for overall compliance, failed and pending status) 325 | left join ( 326 | SELECT uas.ResourceID, 327 | count(uas.ResourceID) as AssignmentSum, 328 | --max(uas.LastComplianceMessageTime) as LastComplianceMessageTime, 329 | sum(CASE WHEN (IsCompliant = 1) THEN 1 ELSE 0 END) AS Compliant 330 | --sum(CASE WHEN (IsCompliant = 0) THEN 1 ELSE 0 END) AS NonCompliant, 331 | --sum(CASE WHEN (IsCompliant is null) THEN 1 ELSE 0 END) AS Unknown, 332 | --sum(CASE WHEN (IsCompliant = 0) AND LastEnforcementMessageID in (6,9) THEN 1 ELSE 0 END) AS Failed, 333 | --sum(CASE WHEN (IsCompliant = 0) AND LastEnforcementMessageID not in (0,6,9) THEN 1 ELSE 0 END) AS Pending 334 | FROM v_UpdateAssignmentStatus uas 335 | inner join ResourceList on ResourceList.ResourceID = uas.ResourceID 336 | where UAS.AssignmentID not in (Select AssignmentID from ExcludedDeployments) -- exclude defender and scep deployments from compliants as well as other deployments if selected 337 | group by uas.ResourceID 338 | ) as AssignmentStatus on AssignmentStatus.ResourceID = VRS.ResourceID 339 | ---- join last update installdate for security updates just as a reference to find problematic clients not installing security updates at all 340 | ---- need to convert datetime for older OS from 64bit binary to datetime 341 | left join ( 342 | select qfe.ResourceID 343 | --- CUU.CI_ID is not null means the current rollup is installed and we can use that installdate as reference 344 | ,LastInstallTime = max(CASE WHEN CUU.CI_ID is not null then 345 | ( 346 | --- the current rollup is installed, but do we have a valid install date? In some cases the installdate seems to be mising. For some Win10 1803 systems for example. 347 | --- "-05" impossible date, since the update could not be released the 5th day of the month (normally). That should indicate the missing date info and give us enough info about the state of the system 348 | --CASE WHEN qfe.InstalledOn0 = '' THEN @UpdatePrefix + '-05 00:00:00' ELSE TRY_CONVERT(datetime,qfe.InstalledOn0,101) END 349 | CASE WHEN qfe.InstalledOn0 = '' THEN @CurrentRollupPrefix + '-05 00:00:00' ELSE TRY_CONVERT(datetime,qfe.InstalledOn0,101) END 350 | ) 351 | else --CUU.CI_ID IS null 352 | ( 353 | --- due to some older systems sending datetime as binary like this: 01cc31160e1c4bac and since there is no cumulative update for such systems "CUU.CI_ID is null" should always be valid 354 | --- Found three different date strings so far: MM/dd/yyyy or yyyMMdd or binary 355 | CASE WHEN (LEN(QFE.InstalledOn0) > 10) 356 | THEN CAST((TRY_CONVERT(BIGINT,TRY_CONVERT(VARBINARY(64), '0x' + QFE.InstalledOn0,1)) / 864000000000.0 - 109207) AS DATETIME) 357 | ELSE TRY_CONVERT(datetime,qfe.InstalledOn0,101) 358 | END 359 | ) END) 360 | from v_GS_QUICK_FIX_ENGINEERING QFE 361 | inner join ResourceList on ResourceList.ResourceID = QFE.ResourceID 362 | left join CumulativeUpdates CUU on CUU.ArticleID = QFE.HotFixID0 363 | --where (QFE.Description0 = 'Security Update' or QFE.Description0 is null) 364 | group by qfe.ResourceID 365 | ) UPDINSTDATE on UPDINSTDATE.ResourceID = VRS.ResourceID 366 | ---- join Pending Reboot Info 367 | --left join BGB_LiveData BGBL on BGBL.ResourceID = VRS.ResourceID ---- <- no direct rights assigned and no other view available, using v_CombinedDeviceResources instead 368 | left join v_CombinedDeviceResources BGBL on BGBL.MachineID = VRS.ResourceID 369 | 370 | ---- fix for SQL compat level problem 371 | ---- https://support.microsoft.com/en-us/help/3196320/sql-query-times-out-or-console-slow-on-certain-configuration-manager-d 372 | ---- Force legacy cardinality for SQL server versions before 2016 SP1 (SQL version less than 13.0.4001.0) 373 | --OPTION (QUERYTRACEON 9481) 374 | ---- Force legacy cardinality for SQL server versions 2016 SP1 and higher (SQL version equal or greater than 13.0.4001.0) 375 | --OPTION (USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION')) --------------------------------------------------------------------------------