# SCCM – Parsing Collection Maintenance Windows from C-Sharp¶

## Introduction¶

This post details how to extract useful information from the SCCM Schedules format.

### Suggested Reading¶

Here are a few useful links to documentation regarding some of the activities being performed-

**This solution makes heavy use of Logical AND operators.**

**As well as heavy use of shift operators.**

## The Format¶

You can view the SCCM service windows by querying the view, “vSMS_ServiceWindow”

Here is an example query for retrieving collections along with their service windows.

```
select
c.CollectionID
,c.SiteID
,c.CollectionName
,c.CollectionType
,c.ServiceWindowsCount
,c.LimitToCollectionID
,c.LimitToCollectionName
,c.ObjectPath
,sw.Name
,sw.ServiceWindowType
,sw.Description
,sw.Enabled
,sw.Schedules
from v_Collections c
JOIN vSMS_ServiceWindow sw on sw.SiteID = c.SiteID
ORDER BY c.LimitToCollectionName
```

When you query this- you will get a lot of common information about your collections, their names, collection IDs, types, etc.

The piece I want to focus on here, is extracting out the actual schedules, which are stored in the “Schedules” column of the results from the above query.

At a glance, it would appear this column is complete gibberish, containing values such as below-

```
00629CC0101A2000
00A29CC0101A2000
02A29CC0101A2000
02E29CC0101A2000
02A29CC0101D2000
02E29CC0101D2000
00229CC0101D2000
00629CC0101D2000
```

However, this is in fact, 64bits of data, stored as a hexadecimal string.

## Reversing the format¶

Using data from both PART 1 of this post, as well as a bunch of testing collections I created- I came up with this diagram showing the relationships of the data in that column.

For the time being- I will be focused on “Weekly” windows, which are service-windows which repeats on the specified day of the week, every week.

I will note, the format of the above data will change, depending on the recurrence type, stored in bits 20- 22.

Low Bit | High Bit | N Bits | Data | Recurrence Type |
---|---|---|---|---|

1 | 1 | 1 | Is Date GMT/CST | |

4 | 8 | 3 | Recur Every N Days | 2 = Daily |

14 | 16 | 3 | Recur Every N Weeks | 3 = Weekly |

16 | 18 | 3 | Day Of Week | 3 = Weekly |

10 | 12 | 3 | Week Order | 4 = Monthly by WeekDay |

13 | 16 | 4 | Recur Every N Months | 4 = Monthly by WeekDay |

17 | 19 | 3 | Day Of Week | 4 = Monthly by WeekDay |

11 | 14 | 4 | Recur Every N Months | 5 = Monthly By Date |

15 | 19 | 5 | Recur Every N Days | 5 = Monthly By Date |

7 | 9 | 3 | Offset Days | 6 = Monthly By Weekday Offset |

10 | 12 | 3 | Week Order | 6 = Monthly By Weekday Offset |

13 | 16 | 4 | Recur Every N Months | 6 = Monthly By Weekday Offset |

17 | 19 | 3 | Day Of Week | 6 = Monthly By Weekday Offset |

20 | 22 | 3 | Recurrence Type | |

23 | 27 | 5 | Duration (Mins) | |

28 | 32 | 5 | Duration (Hours) | |

33 | 38 | 6 | Duration (???) | Unsure- of what this is. |

39 | 44 | 6 | Date – Year | |

45 | 48 | 4 | Date – Month | |

49 | 53 | 5 | Date – Day | |

54 | 58 | 5 | Date – Hour | |

59 | 64 | 6 | Date – Minute |

### Converting the HEX string into a ulong for processing.¶

```
if (!ulong.TryParse(Hex, System.Globalization.NumberStyles.HexNumber, provider: null, out ulong Result))
throw new Exception("Invalid hex provided. Unable to parse.");
```

While there is nothing special about converting a string containing HEX, into a number- there is one important thing to note here- I am parsing the value as ulong, instead of long.

The reason behind using ulong- all of the values are unsigned. If we parse as long instead, this will make it harder to manipulate the data later on, as c# will assume all of the resulting arithmetic results in signed numbers.

If you don’t know the difference between signed / unsigned-

Signed data types leverage the most significant bit, to determine if the value is negative or positive. This allows them to hold a negative value, but, sacrifices half of the maximum value.

Unsigned- uses all of the bits for numeric data.

### Common Fields¶

Bit 1, corresponds to “IS GMT / CST”, and is common for all types.

Bits 23-38 contains the duration, which is common for all recurrence types. I am unsure of what bits 33-38 stores…

Bits 39 – 64 contains the effective “Start Date” which is common and shared for all recurrence types. Parsing out this data is pretty easy.

Used the table above- we shift the data the specified number of bits, and do a bitwise AND to only include the number of bits notated.

```
var Flags = (Result >> 19) & 0x7; //Recurrence Type Flags - 3 bits.
model.Recurrance = (ParsedSchedule.RecurranceType)Flags;
var R_Duration_Mins = (Result >> 22) & 0x1F; //Duration Mins - 5 bits.
var R_Duration_Hours = (Result >> 27) & 0x1F; //Duration Hours- 5 bits.
model.Duration = new TimeSpan((int)R_Duration_Hours, (int)R_Duration_Mins, 0);
var IsGMT = (Result & 0x1) == 1; //First bit, specifies if this schedule is GMT, or Local.
var Duration = (Result >> 32) & 0x3F; // 6 bits. Not, sure exactly what this field's purpose is.
var Year = ((Result >> 38) & 0x3F) + 1970; // 6 bits + 1970
var Month = (Result >> 44) & 0xF; // 1 byte
var Day = (Result >> 48) & 0x1F; // 5 bits
var Hour = (Result >> 53) & 0x1F; // 5 bits
var Minute = (Result >> 58); // Remaining 6 bits. (Operating on a 64-bit ulong, no need to mask the rest)
model.StartTime = new DateTime((int)Year, (int)Month, (int)Day, (int)Hour, (int)Minute, 0, IsGMT ? DateTimeKind.Utc : DateTimeKind.Local);
```

The final cast to int is needed, otherwise, you would receive a syntax error because you cannot create a datetime with ulongs.

I will note, I am not using checked when casting, as…. well. It’s impossible to overflow an int with only 6 bits….

### Recurrence Type¶

The Recurrence type, stored in bits 20-22, will determine the data stored in bits 2-19.

From my research, I have found these possible values:

##### None = 1¶

This service windows is not recurring, and will only occur once on the date provided.

##### Daily = 2¶

This schedule will repeat every N Days.

##### Weekly = 3¶

This schedule, will repeat on the specified day of the week, every week.

##### Monthly By Week Day = 4¶

This Schedule will occur every month, on a given weekday.

##### Monthly, With Offset = 6¶

This is the same as “Monthly by week day”, but, with the “Offset” checkbox checked.

This came with SCCM 2207

From Microsoft-

### Parsing out Recurrence-Specific Fields.¶

```
switch (model.Recurrance)
{
case ParsedSchedule.RecurranceType.None:
break;
case ParsedSchedule.RecurranceType.Daily:
model.RecurEveryNDays = (int)((Result >> 3) & 0x1F); //5 Bits
break;
case ParsedSchedule.RecurranceType.Weekly:
model.DayOfWeek = (DayOfWeek)(((Result >> 16) & 0x7) - 1); // 3 bits.
model.RecurEveryNWeeks = (int)((Result >> 13) & 0x7); // 3 bits
break;
case ParsedSchedule.RecurranceType.Monthly_ByWeekday:
model.WeekOccurence = (ParsedSchedule.WeekOrder)((Result >> 9) & 0x7); // 3 bits
model.RecurEveryNMonths = (int)((Result >> 12) & 0xF); // 4 bits
model.DayOfWeek = (DayOfWeek)(((Result >> 16) & 0x7) - 1); // 3 bits
break;
case ParsedSchedule.RecurranceType.Monthly_ByDate:
model.RecurEveryNDays = (int)((Result >> 14) & 0x1F); // 5 bits
model.RecurEveryNMonths = (int)((Result >> 10) & 0xF); // 4 bits
break;
case ParsedSchedule.RecurranceType.Monthly_ByWeekDay_Offset:
model.DayOfWeek = (DayOfWeek)(((Result >> 16) & 0x7) - 1); // 3 bits.
model.OffsetDays = (int)((Result >> 6) & 0x7); // 3 bits.
model.WeekOccurence = (ParsedSchedule.WeekOrder)((Result >> 9) & 0x7); // 3 bits
model.RecurEveryNMonths = (int)((Result >> 12) & 0xF); // 4 bits
break;
}
```

About the only thing special to note here- SCCM stores Days of week, starting with Sunday = 1. .NET Starts the week on Sunday = 0. To compensate, we just subtract 1.

I will also note, for “WeekOccurence”, 0 corresponds to last, 1 = first, 2 = second, 3 = third, 4 = forth.

### How did I obtain this data?¶

Easier said than done!

While, I knew the general format from my previous article, and the pseudocode linked from it- I needed to parse out the specific offsets, and field types. As well, I wanted to leverage proper bitwise operations instead of relying on odd string/hex manipulation. So- a lot of this was trial and error.

The first step I did- was to generate a large number of dummy schedules, copy the hex, and I built a bunch of unit tests.

I knew the expected output, so, I provided the expected values. After which- most of the process was running unit tests, and using a bit of logic.

A lot of the work was done in Notepad++, looking at the binary values, and trying to determine what fits where.

By removing the “Known” bits, and only comparing the unknown bits- it makes it pretty easy to find what data, fits where, by comparing multiple variations of the data.

In the above example, it can be determined bits 17-19, corresponds to the day of the week.

Overall, it took me about one full working day to reverse engineer all of the formats.