SCCM Windows Update Compliance Reports Using Latest SUG

I've recently been working on some Window update compliance reports which dynamically retrieve compliance data for the latest SUG (Software Update Group) created on Patch Tuesday. This makes it easy for administrators and management to keep track of compliance without the need to find and select the latest SUG.

The SUG is deployed to two collections on the evening of patch Tuesday, one containing servers and the other workstations. We have a multi-tenant environment so the report splits these systems up by customer.

The compliance data is presented using pie charts on an SSRS report. I'll update this post at a later date with how I formatted these, for now I'll just provide the SQL we use to report on our server estate.


Prerequisites:

- Make sure your ADR is configured to create a new SUG each time it's triggered.
- Our ADR triggers on patch Tuesday (2nd Tuesday of the month).
- Create a separate database (Mine is called 'CM_Runtime') to store SQL functions. I wasn't too keen on defining functions on the CM database.

Create Functions:

To scope the report to a SUG created on the most recent patch Tuesday, we need to save some stored procedures ('PCC_SEC_TUESDAY'  and  'PCC_LAST_SEC_TUESDAY') in a runtime database which calculate the most recent patch Tuesday date based on the date the report is run using the following query:  SELECT [dbo].[PCC_LAST_SEC_TUESDAY](getdate())

Run the SQL statements below on the database created in prerequisite steps.

PCC_SEC_TUESDAY returns the 2nd Tuesday of the month that any date is in (@date would be the parameter.
================================================

CREATE FUNCTION PCC_SEC_TUESDAY
(
@p_date datetime
)
RETURNS DATETIME
AS
BEGIN
DECLARE @firstday AS DATETIME;
DECLARE @lastDay  AS DATETIME;
DECLARE @sectue   AS DATETIME;
--
DECLARE @today AS DATETIME -- for testing
SET @today = @p_date;


SET @firstday = DATEADD(MONTH, DATEDIFF(MONTH, 0, @today), 0)
SET @lastday = DATEADD(DAY, -1, DATEADD(MONTH, 1, @firstday));


--SELECT @today AS [Today], @firstday AS FirstDayOfMonth, @lastday AS LastDayOfMonth

WITH MyDates AS
(
    SELECT @firstday AS MyDate, DATENAME(DW, @firstday) AS NameOfDay
    UNION ALL
    SELECT DATEADD(DAY, 1, MyDate) AS MyDate, DATENAME(DW, DATEADD(DAY, 1, MyDate)) AS NameOfDay
    FROM MyDates
    WHERE DATEADD(DAY, 1, MyDate)<@lastDay
)
SELECT @sectue = Mydate 
FROM (
    SELECT ROW_NUMBER() OVER(PARTITION BY NameOfDay ORDER BY MyDate) AS RowNo, *
    FROM MyDates
    ) AS T
WHERE (RowNo=2 ) AND NameOfDay = 'Tuesday';
    RETURN @sectue;
END

================================================
PCC_LAST_SEC_TUESDAY
a) Works out the 2nd Tuesday of the month for the current date
b) If this is before or after the current date then return the 2nd Tuesday of the previous month
================================================

CREATE FUNCTION PCC_LAST_SEC_TUESDAY
(
@p_date datetime
)
RETURNS DATETIME
BEGIN
   DECLARE @lastsectue DATETIME;
   -- Get the 2nd Tuesday for the month of the date given
   DECLARE @thissectue DATETIME;
   SET @thissectue = dbo.PCC_SEC_TUESDAY(@p_date);
   -- is this date after the imput date
   IF @thissectue <= @p_date
   BEGIN
      SET @lastsectue = @thissectue;
   END;
   ELSE
   BEGIN
      -- previous months 
  DECLARE @monthago DATETIME
  SET @monthago = DATEADD(MONTH, -1, @p_date);
  SET @lastsectue = dbo.PCC_SEC_TUESDAY(@monthago);
END;
--
RETURN @lastsectue;
END;


Report SQL Query

The query to use in the SSRS report is below, the following strings will need editing to reflect your SCCM set up.

Customer A Device Collection ,'Customer B Device Collection'. - Used to split servers up by customer.
Server Software Updates  -- Name of the SUG deployment.
SUM | Server Software Updates - Name of collection the SUG is deployed to.
ADR | Server Software Updates - Name of the ADR used to create the SUG deployment.



declare  @collname  table(name  varchar(100));
declare  @title varchar(max);
declare @collid  table(id  varchar(100));
declare  @assignmentid table(id  varchar(100));
insert into @collname   values( 'Customer A Device Collection'),('Customer B Device Collection')
insert into @assignmentid   SELECT AssignmentID FROM v_CIAssignment WHERE 
CreationTime >= SELECT [CM_Runtime].[dbo].[PCC_LAST_SEC_TUESDAY](getdate()) 
and
CollectionName like 'SUM | Server Software Updates%' 
and
AssignmentName like '%Server Software Updates%' 
insert into @collid SELECT CollectionID from dbo.v_Collection where name in (select name from @collname)    -- Get Collection ID's from Collection names -- 
set @title = (SELECT Title
  FROM v_AuthListInfo WHERE 
  CI_UniqueID = (SELECT TOP 1 CI_UniqueID FROM v_AuthListInfo WHERE Title like 'ADR | Server Software Updates%' ORDER BY DateCreated DESC));
SELECT
@title AS SUG,
Count(StateID) AS Total,
Count(CASE When StateID = '1' OR StateID = '4' THEN 'OK' END) AS OK,
Count(CASE When StateID = '0' THEN 'Unknown' END) AS Unknown,
Count(CASE When StateID = 5 or StateID = 7 or StateID = 8 or StateID = 10 THEN 'Warning' END) AS Warning,
Count(CASE When StateID = 2 or StateID = 6 or StateID = 9 THEn 'Critical' END) AS Critical,
(SELECT name from dbo.v_Collection where CollectionID = v_fullcollectionmembership.collectionID) AS Collection
                FROM v_AssignmentState_Combined
INNER JOIN
v_R_System ON v_AssignmentState_Combined.ResourceID = v_R_System.ResourceID INNER JOIN
v_fullCollectionMembership on v_R_System.ResourceID = V_FullCollectionMembership.ResourceID
                WHERE 
v_R_System.ResourceID = v_AssignmentState_Combined.ResourceID AND
v_AssignmentState_Combined.AssignmentID in (SELECT id from @assignmentid)
AND
v_fullcollectionmembership.collectionID in (select id from @collid)
GROUP BY v_fullcollectionmembership.collectionID




Move WSUS content when running multiple SCCM SUPs, shared content and database

Recently I needed to relocated the WSUS content directory for an SCCM site which was running multiple Software Update Points with a shared content directory and SUSDB cross forest. It was pretty straight forward, but it needed a methodical approach to be successful.

High Level Steps:

1. Create a new directory and share.
2. Grant the appropriate permissions.
3. Stop WSUS on cross forest SUPs (all but one).
4. Run Wsusutil MoveContent command.
5. Alter registry and IIS on cross forest SUPs.
6. Start WSUS instances.

Detailed Steps:


1. Create Directory and Share

Create a directory and share for WSUS content, this could be on a file server or local. But it needs to be accessible by all SUPs over SMB using a UNC path. It should mimic the current content location in terms of permissions and network access.

2. Grant Permissions

SUP's will connect to the share using the computer accounts, so at least a one way inbound trust needs to exist beforehand. Grant the computer accounts for all SUPs 'Full Control' for the share and for NTFS.

The user account being used to run the wsusutil movecontent command will also need full control.

3. Stop WSUS on SUPs.

Stop the WSUS service and website on all SUP's except the SUP currently being used to synchronize with Microsoft Update (the WSUS master front end server).

Stop-Website "WSUS Administration"
Stop-Service WsusService -Force

4. Run WSUSUtil MoveContent command

On the currently active SUP, open an elevated command prompt and run the following command from c:\program files\update services\tools

Wsusutil.exe movecontent \\FQDN\SHARE\ c:\dir\wsuslog.log

This will move the content from the current directory to the share on server \\FQDN and output a log file to c:\dir\wsuslog.log. It's obvious, but make sure you use the fully qualified domain name and it's resolvable cross forest.

5. Alter registry and IIS on other SUPs

You could run the wsusutil moveconten command on each of the other SUPs with the -skipcopy flag. But I find it just as easy to alter the registry and IIS.

Update the 'contentdir' string in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Update Services\Server\Setup\  to reflect the share path  (i.e. \\FQDN\SHARE\ )

Open IIS, expand WSUS Administration, click on 'content' and select 'advanced settings'
Enter the full path to the wsuscontent folder on the share. i.e. \\FQDN\SHARE\wsuscontent\

6. Start WSUS on all instances

Once the registry entries and IIS has been updated, start the WSUS service and websites on all sups.

Start-Website "WSUS Administration"
Start-Service WsusService

About Me

My photo
Senior Consultant at CDW UK specialising in Microsoft workspace and cloud technologies.