≡ Menu

Computer Uptime Compliance Policy

We have a new company requirement for workstations to have an uptime of no more than “N” days, and to force a reboot when machines are non-compliant. To enforce this, I decided the best way to evaluate computer uptime was with a compliance script.

The Compliance Script

  1. Create a new compliance item named Computer Uptime, and select the Supported Platforms appropriate for your environment.
  2. On the Settings tab, click New… and add a setting:
    Name: Uptime Days
    Setting type: Script
    Data type: Integer
  3. Under the Discovery Script section, click Add Script… and add the following Windows Powershell script:
    [math]::truncate((gwmi Win32_PerfFormattedData_PerfOS_System).SystemUpTime/60/60/24)
  4. Under Compliance Rules, add a new compliance rule:
    Name: Need Reboot
    Selected Setting: Computer Uptime \ Uptime Days
    Must comply with the following rule: Value returned is less than N (N is the integer of days for maximum uptime)
  5. Save the Compliance Item.
  6. Create a new Compliance Baseline named Computer Uptime, and add the CI to it.
  7. Note the CI ID NNNNN of the new Compliance Baseline you created – for the baseline, not the CI!  If you cannot see the CI ID, then add the CI ID column (while in Configuration Baselines, right click the columns, and check CI ID).

The Reboot Package

I could have simply used shutdown.exe to force a reboot of the machine, or initiated it using a PowerShell command as a remediation script. However, since our end users are used to seeing the Config Manager restart dialog for Software Updates and other installs that require a restart, I decided to keep their experience the same.

  1. Create a new package named Reboot Computer.  No source files will be needed. Save it.
  2. Create a new program:
    Name: Reboot
    Command line: cmd /c
    Run: Hidden
    Program can run: Whether or not a user is logged on
    After Running: Configuration Manager restarts computer

The Target Collection

Now we need a target collection of non-compliant machines to get the reboot deployment.

  1. Create a new collection named Non-compliant Computer Uptime.
  2. Add a membership rule query, with two criteria:
    Compliance Item Compliance State.CIID is equal to NNNNN (from the baseline you created above)
    Configuration Item Compliance State.Compliance State Name is equal to “Non-Compliant”
  3. Evaluation should be a few times a day (every few hours), or set to use incremental updates if you [sparingly] use those in your environment.

The Deployments

  1. Deploy the Computer Uptime Compliance Baseline to the machines that need to comply with the uptime policy.  Set the evaluation to run every four hours, and no need to remediate since you are doing that outside of the compliance item.
  2. Deploy the Reboot program to your Non-compliant Computer Uptime collection:
    Required deployment
    Recurring installation (occurs every 1 day)
    Rerun behavior: Always Rerun
    If you are using maintenance windows, consider checking to perform both the installation and the reboot outside of maintenance windows.

Sit back and wait for the evaluations to happen. Once a machine is up at least N days, then it will end up in the target collection, get the deployment to reboot, and be removed from the collection after re-evaluating again. Users that are logged in will get the familiar restart warning message before the restart is enforced.

Some things to consider for the choices to make on evaluation frequencies, compliance rule values, and deployment settings:

  • Whether or not maintenance windows exist in the environment
  • The Computer Restart options set in Client Settings

Get a SQL Query from Status Message Viewer

I needed to run a T-SQL query based off of what I was viewing in Status Message Viewer — in my particular case, it was from SMS_POLICY_PROVIDER. I was poking around, and clicked on View –> Query Information. I got something like the following:

Select * from SMS_StatusMessage as stat
left outer join SMS_StatMsgAttributes as att
on stat.recordid = att.recordid
left outer join SMS_StatMsgInsStrings as ins on stat.recordid = ins.recordid
AND (stat.Time>='2014/08/18 16:14:15.000')
AND (SiteCode="ABC")
AND (MachineName="P01.ABC.COM")
order by stat.Time Desc

This is a WQL query. To convert from WQL to T-SQL, you typically can change the following to make it work in T-SQL:

  1. Change the table names from SMS_* to v_*
  2. Search and replace double quotes to single quotes.

The above example will look like this:

Select * from v_StatusMessage as stat
left outer join v_StatMsgAttributes as att
on stat.recordid = att.recordid
left outer join v_StatMsgInsStrings as ins
on stat.recordid = ins.recordid
AND (stat.Time>='2014/08/18 16:14:15.000')
AND (SiteCode='ABC')
AND (MachineName='P01.ABC.COM')
order by stat.Time Desc

Put that in SQL Server Management Studio and successfully query your database. Now, this will give you a LOT more rows than what you see in Status Message Viewer. This is because you are pulling information from the joined tables for the related status message attributes and insert strings, and the Status Message Viewer cleans that up for your viewing pleasure.  Note that the additional info is certainly important, as it contains the parameters for the packages you need (Package ID, Name, Deployment ID, or whatever).  Also note that your script is now “dated” since the time is hardcoded. If you just want to view the base records from the last hour, to confirm you are looking at the same amount of records in SQL that you are also viewing in Status Message Viewer from the last hour, then change it to:

Select distinct stat.* from v_StatusMessage as stat
left outer join v_StatMsgAttributes as att
on stat.recordid = att.recordid
left outer join v_StatMsgInsStrings as ins on stat.recordid = ins.recordid
AND (stat.Time>= DATEADD(HOUR, -1, SYSDATETIME() ) )
AND (SiteCode='ABC')
AND (MachineName='P01.ABC.COM')
order by stat.Time Desc

IMPORTANT NOTE: This assumes that SYSDATETIME() matches your existing time. You may need to do a SELECT SYSDATETIME() to see if it actually matches your time, and adjust the DATEADD function accordingly. There is probably a more accurate way to convert based on time zone. Adjust for your particular situation.

Now, to get to the information I needed.  I wanted to calculate the number of policy updates (MessageID = 5101) that were occuring per package within the last hour, as we were experiencing a corruption issue that was causing the policy provider to go nuts on specific packages, causing frequent policy updates. With a little T-SQL magic, I was able to get the results I needed:

Select distinct stat.RecordID,
(insPid.InsStrValue + ' - ' + insN.InsStrValue) as Pkg
into #tmpRecords
from v_StatusMessage as stat
left outer join v_StatMsgAttributes as att on stat.recordid = att.recordid
left outer join v_StatMsgInsStrings as insN on stat.recordid = insN.recordid
left outer join v_StatMsgInsStrings as insPid on stat.recordid = insPid.recordid

AND (stat.Time>= DATEADD(HOUR, -1, SYSDATETIME() ) )
AND (SiteCode='ABC')
AND (MachineName='P01.ABC.COM')
and stat.MessageID = 5101
and insN.InsStrIndex = 0
and insPid.InsStrIndex = 1;

select Pkg, count(*) as Total
from #tmpRecords
group by Pkg
order by count(*) desc;
drop table #tmpRecords;



Ignore mod_rewrite redirect for subfolder with AuthType Basic

I had a few admin PHP pages in a custom subfolder for a customer, protected with the AuthUser directive in the .htaccess file.  However, when I migrated their main site from Zen Cart to Magento (moving the Magento folder to the root), the subfolder kept throwing 404 errors from Magento.

I knew which line was doing it, but the final redirect should have been ignoring real files and folders:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule .* index.php [L]

Finally I stumbled across the answer, which was not on the main .htaccess folder. In the .htaccess file in the protected subfolder, I needed to add a single line, which probably should have been there the whole time:

ErrorDocument 401 "Unauthorized Access"

The problem was fixed!