Introduction
One of the things I like the most about SCCM is that it is very flexible and extensible. I am constantly amazed at all the things it can be used for in an environment.
Recently, I was asked by a client to create an SCCM web report that would display the Percent of uptime for a group of servers within the last month. Currently, this is partially a manual process for this customer, so they were really eager to get this automated and working.
I delivered a multi-part solution to them that will satisfy this need. It was kind of fun to create, so I figured I would share how it works with you.
Part 1 – Calculating uptime on a given server
I decided to use a PowerShell script to gather the data from each server locally. I’ve included the script below in the post, but I’ll step through the process here also.
Note: This will not work if your servers keep less than one months events in the System Event Log
1) Determine the Month and Year that was just completed (the one before now).
2) Capture the following events from the System Event Log that meet any of following criteria (within the previous month):
- List current server uptime (the server’s uptime is xxxxxx seconds)
- Indicate that the server is being shut down, and whether it was planned or unplanned
- Indicate the server started back up again
I used the event information I collected to build out a hash table in PowerShell that lists the Startup Time, Shutdown Time, and Shutdown Type of each session.
Once built, the data looked something like the following:
Startup Time | Shutdown Time | Shutdown Type |
4/1/2016 12:00:00 AM | 4/2/2016 12:30:00 AM | None |
4/2/2016 12:30:00 AM | 4/13/2016 11:31:00 PM | Planned |
4/13/2016 11:33:00 PM | 4/25/2016 1:30:00 PM | UnPlanned |
4/25/2016 1:33:00 PM | 4/29/2016 11:31:00 PM | Planned |
4/29/2016 1:33:00 PM | 5/1/2016 12:00:00 AM | None |
The two rows with Shutdown Type of “None” were added to fill the gaps in time, since the server was up when the month started and ended.
Next, I went through the hash table and added the number of seconds between the “Startup Time” and “Shutdown” time on each line. This became the total “Uptime” for the server.
To get downtime, I looked at any shutdown with a type of “Planned” or “UnPlanned”. I took the time of shutdown and determined the number of seconds until the server next came back up. I used these to build the total “Planned Downtime” and “UnPlanned Downtime”.
My customer wanted the data to reflect a percent of the month, so I calculated the number of seconds between 4/1/2016 12:00:00 AM and 5/1/2016 12:00:00 AM. Then I divided the number of seconds for “Uptime”, “Planned Downtime” and “Unplanned Downtime” to get the percentage for each.
To store the data, I had the script create a registry key under HKLM\Software\ModelTechnology\Uptime named with the month and year like so “2016-04”. Into that key, I inserted values for the monthly Uptime, Planned Downtime, and UnPlanned Downtime in seconds and percentage. I also added the date the script was run in case we needed to verify that.
The script can be deployed as a package to all servers (it will not return any results on a workstation). It should be set to run the 1st of each month, to ensure that there is enough history in the System Event Log.
Part 2 – Importing the Data into SCCM
Once the servers were generating the data and storing it locally, the next step was to get it into SCCM where it could be used. In order to do this, I added two new classes to SCCM Hardware Inventory. I modeled them off of the “Add Remove Programs” classes that were already there, since I wanted to capture any sub keys under the “Uptime” registry key that were added in the future. This would allow the history of the server’s uptime to be viewed if desired.
In order to extend Hardware Inventory, I created two MOF files. The first one was added to the end of the Configuration.mof file in “Program Files/Microsoft Configuration Manager/inboxes/clifiles.src/hwinv” folder. This caused SCCM to create the database tables and views that were needed to store the client information.
[ dynamic,
provider("RegProv"),
ClassContext("local|HKEY_LOCAL_MACHINE\\Software\\ModelTechnologies\\Uptime")
]
class Win32Reg_Uptime
{
[key]
string Month;
[PropertyContext("DeviceName")]
string DeviceName;
[PropertyContext("LastCaptureDate")]
string LastCaptureDate;
[PropertyContext("PeriodEnd") ]
string PeriodEnd;
[PropertyContext("PeriodStart") ]
string PeriodStart;
[PropertyContext("PlannedDownTimePercent") ]
string PlannedDownTimePercent;
[PropertyContext("PlannedDowntimeSeconds") ]
string PlannedDowntimeSeconds;
[PropertyContext("TotalSeconds") ]
string TotalSeconds;
[PropertyContext("UnPlannedDownTimePercent") ]
string UnPlannedDownTimePercent;
[PropertyContext("UnPlannedDowntimeSeconds") ]
string UnPlannedDowntimeSeconds;
[PropertyContext("UptimePercent") ]
string UptimePercent;
[PropertyContext("UptimeSeconds")]
string UptimeSeconds;
};
[ dynamic,
provider("RegProv"),
ClassContext("local|HKEY_LOCAL_MACHINE\\Software\\ModelTechnologies\\Uptime")
]
class Win32Reg_Uptime64
{
[key]
string Month;
[PropertyContext("DeviceName")]
string DeviceName;
[PropertyContext("LastCaptureDate")]
string LastCaptureDate;
[PropertyContext("PeriodEnd") ]
string PeriodEnd;
[PropertyContext("PeriodStart") ]
string PeriodStart;
[PropertyContext("PlannedDownTimePercent") ]
string PlannedDownTimePercent;
[PropertyContext("PlannedDowntimeSeconds") ]
string PlannedDowntimeSeconds;
[PropertyContext("TotalSeconds") ]
string TotalSeconds;
[PropertyContext("UnPlannedDownTimePercent") ]
string UnPlannedDownTimePercent;
[PropertyContext("UnPlannedDowntimeSeconds") ]
string UnPlannedDowntimeSeconds;
[PropertyContext("UptimePercent") ]
string UptimePercent;
[PropertyContext("UptimeSeconds")]
string UptimeSeconds;
};
The second MOF file was imported from inside the SCCM console. To do this, you open up the “Default Client Settings”, and under “Hardware Inventory” choose “Select Classes”.
Click the “Import” button and select your 2nd MOF file which will instruct the client on how to provide the data to SCCM.
[ SMS_Report (TRUE),
SMS_Group_Name ("Uptime"),
SMS_Class_ID ("MICROSOFT|UPTIME|1.0"),
Namespace ("\\\\\\\\localhost\\\\root\\\\cimv2"),
SMS_Context_1 ("__ProviderArchitecture=32|SInt32"),
SMS_Context_2 ("__RequiredArchitecture=true|Boolean") ]
class Win32Reg_Uptime : SMS_Class_Template
{
[ SMS_Report (TRUE), key ]
String Month;
[ SMS_Report (TRUE) ]
String DeviceName;
[ SMS_Report (TRUE) ]
String LastCaptureDate;
[ SMS_Report (TRUE) ]
String PeriodEnd;
[ SMS_Report (TRUE) ]
String PeriodStart;
[ SMS_Report (TRUE) ]
String PlannedDownTimePercent;
[ SMS_Report (TRUE) ]
String PlannedDowntimeSeconds;
[ SMS_Report (TRUE) ]
String TotalSeconds;
[ SMS_Report (TRUE) ]
String UnPlannedDownTimePercent;
[ SMS_Report (TRUE) ]
String UnPlannedDowntimeSeconds;
[ SMS_Report (TRUE) ]
String UptimePercent;
[ SMS_Report (TRUE) ]
String UptimeSeconds;
};
[ SMS_Report (TRUE),
SMS_Group_Name ("Uptime (64)"),
SMS_Class_ID ("MICROSOFT|UPTIME_64|1.0"),
Namespace ("\\\\\\\\localhost\\\\root\\\\cimv2"),
SMS_Context_1 ("__ProviderArchitecture=64|SInt32"),
SMS_Context_2 ("__RequiredArchitecture=true|Boolean") ]
class Win32Reg_Uptime64 : SMS_Class_Template
{
[ SMS_Report (TRUE), key ]
String Month;
[ SMS_Report (TRUE) ]
String DeviceName;
[ SMS_Report (TRUE) ]
String LastCaptureDate;
[ SMS_Report (TRUE) ]
String PeriodEnd;
[ SMS_Report (TRUE) ]
String PeriodStart;
[ SMS_Report (TRUE) ]
String PlannedDownTimePercent;
[ SMS_Report (TRUE) ]
String PlannedDowntimeSeconds;
[ SMS_Report (TRUE) ]
String TotalSeconds;
[ SMS_Report (TRUE) ]
String UnPlannedDownTimePercent;
[ SMS_Report (TRUE) ]
String UnPlannedDowntimeSeconds;
[ SMS_Report (TRUE) ]
String UptimePercent;
[ SMS_Report (TRUE) ]
String UptimeSeconds;
};
Once the MOF was imported, I enabled the new class in Client Settings.
Next, I forced a hardware inventory on one of my clients and was able to see the data in Resource Explorer.
Part 3 – Viewing the Data via SCCM Web Report
I created a custom Web report via Report Manager 3.0 to show the data that was captured for a given month on all servers. Once it was done, I imported it into the SCCM Reporting site and VOILA!. Now uptime for a server can be viewed by month going forward.
Here is a screenshot of the web report:
So there you have it! Now you can track monthly server uptime via SCCM.
As promised, here is the PowerShell script:
If(Test-Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion"){
#Check if this device is a server. If not, do nothing and exit.
$OSVersion = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion" -Name ProductName
If($OSVersion.ProductName -like "*Server*"){
#Determine the Month and Year for last month
$($(Get-Date).AddMonths(-1)).Month
$ReportMonth = $($(Get-Date).AddMonths(-1)).Month
$ReportYear = $($(Get-Date).AddMonths(-1)).Year
#Set the Start and End Periods to be last month.
$Start = "$ReportMonth/1/$ReportYear 00:00:00 AM"
$End = "$($ReportMonth + 1)/1/$ReportYear 00:00:00 AM"
#Get Events from System Log that contain the data we want, in the timeperiod we want.
$Events = Get-EventLog -LogName System -Source "EventLog","User32" -After $Start -Before $End
$EventList = $Events
$EventOutput = @()
$Sessions = @()
#Look at each event we captured and Build a list of them with the info we need
Foreach ($event in $EventList) {
$MessageType = ""
$MessageDetail = ""
If ($Event.Message -notlike "*attempt*failed*") {
If ($Event.Source -eq "User32") {
If ($Event.Message -like "*(Planned)*") {
$MessageType = "Shutdown"
$MessageDetail = "Planned"
}
else
{
$MessageType = "Shutdown"
$MessageDetail = "UnPlanned"
}
}
If ($Event.Message -like "*uptime*") {
$MessageType = "Uptime"
$MessageDetail = ($Event.MEssage.Replace("The system uptime is ","")).Replace(" seconds.","")
}
If ($Event.MEssage -like "The Event Log service was started*") {
$MessageType = "Startup"
$MessageDetail = "Startup"
}
#Write the Event Data to a custom object so we can manipulate further to get Session Data.
$ParsedEvent = New-Object -TypeName PSObject
Add-Member -InputObject $ParsedEvent -MemberType NoteProperty -Name "Time" -Value $Event.TimeGenerated
Add-Member -InputObject $ParsedEvent -MemberType NoteProperty -Name "Index" -Value $Event.Index
Add-Member -InputObject $ParsedEvent -MemberType NoteProperty -Name "MessageType" -Value $MessageType
Add-Member -InputObject $ParsedEvent -MemberType NoteProperty -Name "MessageDetail" -Value $MessageDetail
Add-Member -InputObject $ParsedEvent -MemberType NoteProperty -Name "Message" -Value $Event.Message
$EventOutput += $ParsedEvent
}
}
$EventOutput = $EventOutput |Sort-Object -Property Time
#For any uptime events, calculate the Date/time the system would have started.
#Then write it to a custom sessions object.
$n=0
Foreach ($Item in $EventOutput) {
If ($Item.MessageType -eq "Uptime") {
$Uptime = $Item.MessageDetail
if($n = 0) {
$Startup = $Start
}else
{
$Startup = $Item.Time.AddSeconds(-1 * $Item.MessageDetail)
}
$CanWrite = $true
}
If ($Item.MessageType -eq "Shutdown" -and $CanWrite -eq $true) {
$ShutdownTime = $Item.Time
$EventName = $Item.MessageDetail
$Session = New-Object -TypeName PSObject
Add-Member -InputObject $Session -MemberType NoteProperty -Name "Startup" -VAlue $Startup
Add-Member -InputObject $Session -MemberType NoteProperty -Name "Shutdown" -Value $ShutdownTime
Add-Member -InputObject $Session -MemberType NoteProperty -Name "Type" -Value $EventName
$Sessions += $Session
$CanWrite = $false
$Sessions[0].Startup = $Start
}
$n += 1
}
#Add a session record for the start and end of the month so we account for the entire month
$Session = New-Object -TypeName PSObject
Add-Member -InputObject $session -MemberType NoteProperty -Name "Startup" -Value $Startup
Add-Member -InputObject $Session -MemberType NoteProperty -Name "Shutdown" -Value $End
Add-Member -InputObject $Session -MemberType NoteProperty -Name "Type" -Value "None"
$Sessions += $Session
$first = $true
$i = 0
$uptime = 0
$planned = 0
$unplanned = 0
#For each session calculate the total seconds in uptime, downtime
Foreach ($Line in $Sessions) {
If ($i -ne 0) {
If ($Sessions[$i-1].Type -eq "Planned") {
$Planned += New-TimeSpan -Start $Sessions[$i-1].Shutdown -End $Line.Startup
}
elseif ($Sessions[$i-1].Type -eq "UnPlanned") {
$UnPlanned += New-TimeSpan -Start $Sessions[$i-1].Shutdown -End $Line.Startup
}
}
$Uptime += New-timeSpan -Start $Line.Startup -End $Line.Shutdown
$i += 1
}
$Total = New-TimeSpan -Start $Start -End $End
$PctUptime = ($Uptime.TotalSeconds / $Total.TotalSeconds) * 100.00
$PctPlanned = ($Planned.TotalSeconds / $Total.TotalSeconds) * 100.00
$PctUnPlanned = ($UnPlanned.TotalSeconds / $Total.TotalSeconds) * 100.00
If ($Uptime.TotalSeconds -eq $null) {$Uptime = 0} else {$Uptime = $Uptime.TotalSeconds}
If ($Planned.TotalSeconds -eq $null) {$Planned = 0} else {$Planned = $Planned.TotalSeconds}
If ($UnPlanned.TotalSeconds -eq $null) {$UnPlanned = "0"} else {$Unplanned = $Unplanned.TotalSeconds}
#Create the reporting object we will use to hold the results
$RptObject = New-Object -TypeName PSObject
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "DeviceName" -VAlue $ENV:Computername
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "Uptime(Seconds)" -VAlue $Uptime
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "PlannedDownTime(Seconds)" -VAlue $Planned
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "UnPlannedDownTime(Seconds)" -VAlue $UnPlanned
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "Total(Seconds)" -VAlue $Total.TotalSeconds
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "Uptime(Percent)" -VAlue $PctUptime
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "PlannedDowntime(Percent)" -VAlue $PctPlanned
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "UnPlannedDowntime(Percent)" -VAlue $PctUnPlanned
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "PeriodStart" -VAlue $Start
Add-Member -InputObject $RptObject -MemberType NoteProperty -Name "PeriodEnd" -VAlue $End
#Write the results to the registry
Set-Location HKLM:
If((Test-Path .\Software\ModelTechnologies)){
}
else{
New-Item -Path .\Software\ModelTechnologies
}
If((Test-Path .\Software\ModelTechnologies\Uptime)){
}
else{
New-Item -Path .\Software\ModelTechnologies\Uptime
}
If((Test-Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth)){
}
else{
New-Item -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth
}
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name DeviceName -Value $RptObject.DeviceName
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name UptimeSeconds -Value $RptObject."Uptime(Seconds)"
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name PlannedDowntimeSeconds -Value $RptObject."PlannedDownTime(Seconds)"
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name UnPlannedDowntimeSeconds -Value $RptObject."UnPlannedDownTime(Seconds)"
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name TotalSeconds -Value $RptObject."Total(Seconds)"
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name UptimePercent -Value $RptObject."Uptime(Percent)"
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name PlannedDownTimePercent -Value $RptObject."PlannedDownTime(Percent)"
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name UnPlannedDownTimePercent -Value $RptObject."UnPlannedDownTime(Percent)"
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name PeriodStart -Value $RptObject.PeriodStart
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name PeriodEnd -Value $RptObject.PeriodEnd
Set-ItemProperty -Path .\Software\ModelTechnologies\Uptime\$ReportYear-$ReportMonth -Name LastCaptureDate -Value $(Get-Date).DateTime
}
else {
Exit
}
}
else{
}