Click here to Skip to main content
Click here to Skip to main content

When Can I Logon to Windows?

, 18 Sep 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
A brief explanation on how to interpret the 'logon hours' member of the USER_INFO structures

Introduction

On my Windows 7 machine, I happened upon the Time Limits dialog used by Parental Controls.

This intrigued me enough to wonder how this information might be obtained. I quickly found the NetUserGetInfo() function. This function populates one of nine different USER_INFO_x structures with various information about a particular user account on a server. However, each time I found an example of what the members of the structure looked like, they all treated the usriX_logon_hours member the same: as 21 separate bytes. Printing the other structure members formatted with %s or %d is fine, but I found nothing useful from looking at that member formatted with %x. Thus, my mission began...

Reading the Array

MSDN tells us that the usriX_logon_hours member is a 168-bit array (laid out from 0 to 167) with each bit representing an hour of the day. I set one of the user accounts on my computer to have the following allowed times: Sun 13-20; Mon-Thu, Sat 9-20; Fri 9-21. If you laid this array out so that it looked like a typical 24-hour week, you'd have the following (with the alternating shades of gray showing the byte boundaries).

0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00 18:00 19:00 20:00 21:00 22:00 23:00
Sun 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0
Mon 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
Tue 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
Wed 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
Thu 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
Fri 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0
Sat 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0

So, how to read the 21-byte array so that it looks something like the above? Reading from the array is simple enough, but we're dealing with bits not bytes. I found it easier to look at the array as a 1x168 bit array rather than a 1x21 byte or a 7x24 bit array. The first order of business, then, will be to "convert" the 21 bytes into 168 bits. I did this with something that looks like:

LPBYTE lpLogonHours = lpUserInfo->usri2_logon_hours;
int nBits[7 * 24];

for (int x = 0; x < 21; x++)
{
    // get bit 8, bit 7, bit 6, etc
    for (int y = 7; y >= 0; y--)
        nBits[z++] = (*lpLogonHours >> y) & 0x01;

    lpLogonHours++; 
}

This produces a 168-bit array, arranged in a familiar 7x24 table, that looks like:

0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00 18:00 19:00 20:00 21:00 22:00 23:00
Sun 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0
Mon 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
Tue 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
Wed 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
Thu 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
Fri 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
Sat 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1

Hmm, this does not look quite like what we are after. The bits appear to have some sense of uniformity, but some things don't quite line up. For example, I would have expected Sunday to contain seven consecutive 1s for the 13:00-20:00 hour slots.

MSDN tells us that the bits must be adjusted (i.e., shifted) according to our time zone. With UTC±0, for example, the first bit (of the first byte) is Sunday, 0:00 to 0:59; the second bit is Sunday, 1:00 to 1:59; and so on like above. Ok, but since I am in the UTC-6 timezone, the starting bit for me would be 6 (of the first byte). That would be my Sunday from 0:00 to 0:59; my Sunday from 1:00 to 1:59 would be bit 7; and so on. Also, since my Sunday started with bit 6, bits 0-5 are the last 6 hours of Saturday (this "wrapping" will become more apparent). The first 24 hours now look like:

Sat 18:00 19:00 20:00 21:00 22:00 23:00 Sun 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0

It changed, but does not really look any better. Let's see if there's anything else that needs tweaking.

Reading Each Byte in Reverse Order

Looking at the first table, I should see a 1 in the 18:00-19:00 slots for Saturday, but I'm seeing a 0 instead. I do, however, see two 1s at the other end of that byte. Also, I should see 1s in the 13:00-17:00 slots for Sunday (the last five hours), but I'm seeing them in the 10:00-14:00 slots instead (the first five hours). In both cases, it appears that the bits are backwards for each byte. When converting the bytes to bits, it seems maybe we should simply iterate the bits in the opposite order, like:

LPBYTE lpLogonHours = lpUserInfo->usri2_logon_hours;
int nBits[7 * 24];

for (int x = 0; x < 21; x++)
{
    // get bit 1, bit 2, bit 3, etc
    for (int y = 0; y < 8; y++)
        nBits[z++] = (*lpLogonHours >> y) & 0x01;

    lpLogonHours++; 
}

The table now looks like the following. Notice how since the Sunday 0:00-0:59 hour has been offset by 6 hours from the beginning of the array, all subsequent hours have been offset as well, with the last 6 hours of Saturday being wrapped back around to the beginning of the array.

Sat 18:00 19:00 20:00 21:00 22:00 23:00 Sun 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1

Sun 18:00 19:00 20:00 21:00 22:00 23:00 Mon 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

Mon 18:00 19:00 20:00 21:00 22:00 23:00 Tue 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

Tue 18:00 19:00 20:00 21:00 22:00 23:00 Wed 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

Wed 18:00 19:00 20:00 21:00 22:00 23:00 Thu 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

Thu 18:00 19:00 20:00 21:00 22:00 23:00 Fri 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

Fri 18:00 19:00 20:00 21:00 22:00 23:00 Sat 0:00 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

Much better!

Find the Right Starting Point in the Array

When we go to create the table at the top of this article, it's simply a matter of finding the right bit in the array to start reading from. By looking at each of the timezones and starting offsets in this table, we can see where we should start reading from, and we also might see a pattern:

If TZ is... Then array offset is...
-12 12
-11 11
-10 10
-9 9
-8 8
-7 7
-6 6
-5 5
-4 4
-3 3
-2 2
-1 1
0 0
1 167
2 166
3 165
4 164
5 163
6 162
7 161
8 160
9 159
10 158
11 157
12 156
13 155

The way of achieving the starting offset first requires us to know how many hours there are between Coordinated Universal Time (UTC) and local time.

TIME_ZONE_INFORMATION tzi;
GetTimeZoneInformation(&tzi);

int nOffset = tzi.Bias / -60;

Second, we then adjust that value forward or backward, like:

nOffset = (168 - nOffset) % 168;

Now that we know where to start reading the array from, we can simply iterate through all 168 bits. But what happens when we get to the end of the array but haven't read all 168 bits yet? Answer: Just wrap back around to 0. One way to do this is:

nOffset = nOffset + 1;
if (168 == nOffset)
    nOffset = 0;

Or if you're into brevity, a one-line solution would be:

nOffset = (nOffset + 1) % 168;

Epilogue

While I used hard-coded values like 24 and 168 in the code snippets above, it was just to make the text easier to read. In the accompanying sample, however, I used the #define macros found in the lmaccess.h file.

It was not my intention with this article to make a replacement for the Time Limits dialog used by Vista and Windows 7, nor was I interested in creating a full-blown "restriction" application. Several of those already exist. I simply wanted to show how to read the information. I leave "write" capabilities to the interested reader.

Enjoy!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

DavidCrow
Software Developer (Senior) Pinnacle Business Systems
United States United States

The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.
 
HTTP 404 - File not found
Internet Information Services

Comments and Discussions

 
GeneralOther Applications PinmemberBlake Miller21-Sep-10 10:04 
GeneralMy vote of 5 PinmemberBlake Miller21-Sep-10 10:03 
QuestionRe: My vote of 5 PinmvpDavidCrow21-Sep-10 10:14 
AnswerRe: My vote of 5 PinmemberBlake Miller21-Sep-10 10:20 
GeneralRe: My vote of 5 PinmvpDavidCrow21-Sep-10 10:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 18 Sep 2010
Article Copyright 2010 by DavidCrow
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid