Reputation: 1
Is it possible to get all the AD user's logon hours and output it into an excel spreadsheet. So something like:
Name | Logon Hours |
---|---|
Bob | 9am-6pm |
Jane | 8am-5pm |
Chris | 9am-6pm |
So far I have Get-AdUser -Filter * -Properties LogonHours | ft Name, LogonHours. However the output is all in binary.
Upvotes: 0
Views: 2629
Reputation: 174690
However the output is all in binary.
That happens to be the way AD stores it. The logonHours
attribute value consists of 21 bytes, each byte covering an 8-hour window, starting from midnight on Sunday.
As TheMadTechnician notes, converting the byte values to binary/base-2 strings might be the simplest way of accessing each "hour".
With this in mind, we could make a nifty little helper class that can translate the byte array into meaningful logon hour information:
class LogonHourAdapter
{
hidden
[string[]]$_days
LogonHourAdapter([byte[]]$logonHours)
{
if($logonHours.Count -ne 21){
throw [System.ArgumentException]::new('logonHours', "Expected byte array of length 21.")
}
$this._days = [string[]]::new(7)
for($i = 0; $i -lt 7; $i++){
$offset = $i * 3
$this._days[$i] = ($logonHours[$offset..($offset+2)]|ForEach-Object{[convert]::ToString($_,2).PadLeft(8, '0')})-join''
}
}
[bool]
IsAllowedToLogonDuring([DayOfWeek]$day, [int]$hour)
{
if($hour -ge 24){
throw [System.ArgumentOutOfRangeException]::new('hour')
}
return $this._days[$day][$hour] -eq '1'
}
[int[]]
GetLogonHoursOn([DayOfWeek]$day){
$hours = 0..23 |? {$this._days[$day][$_] -eq '1'}
return $hours -as [int[]]
}
}
Now we don't need to worry about parsing the binary data, we just pass the attribute value to an instance of the class:
PS ~> $bob = Get-ADUser Bob -Properties logonHours
PS ~> $bobHours = [LogonHourAdapter]::new($bob.logonHours)
PS ~> $bobHours.IsAllowedToLogonDuring('Monday', 9) # Bob is allowed to logon after 9am
True
PS ~> $bobHours.IsAllowedToLogonDuring('Monday', 6) # But not earlier!
False
Next, we'll want to track "ranges" - contiguous periods throughout each day where the user is allowed to logon.
For each day, simply "walk" through each hour from 0 through 23 and start tracking a new range everytime there's a break:
# Prepare our logon hour adapter
$lha = [LogonHourAdapter]::new($bob.logonHours)
# Extract ranges for each day of the week
$ranges = foreach($day in -split 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'){
$currRange = $null
0..23|%{
if($lha.IsAllowedToLogonDuring($day, $_)){
if(-not $currRange){
$currRange = [pscustomobject]@{ Day = [DayOfWeek]$day; From = $_; To = $null}
}
$currRange.To = $_ + 1
}else{
if($currRange){
$currRange
$currRange = $null
}
}
}
if($currRange){
$currRange
}
}
Now we just need to group them together so we can express "8-16 (Monday, Tuesday, Friday)" instead of "8-16 (Monday), 8-16 (Tuesday), 8-16 (Wednesday) ... etc.":
$label = $ranges |Group-Object From,To |Sort Count -Descending |ForEach-Object {
'{0:00}-{1:00} ({2})' -f $_.Group[0].From,$_.Group[0].To,(($_.Group.Day |Sort) -join', ')
}
And $label
now contains a more human readable version of Bob's logonHours:
PS ~> $label
08-16 (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
Upvotes: 1