Link to home
Start Free TrialLog in
Avatar of teztickles
tezticklesFlag for Australia

asked on

How to restrict domain users to a single session

Hi all,

I'm the network administrator of a school (W2K3 + XP) and I'm trying to find a way to make it so the students can only log on to one computer at a time so that if any of their accounts are disabled for whatever reason they can't just 'share' the logon of a friend.

From searching through the Experts Exchange forums I've learnt that this is a common request with the most common solution being to use Microsoft's LimitLogon. My school is a part of a national network and as such I do not have sufficient priveleges to alter the forest schema and as Limitlogon prepares/alters the forest schema this approach is unfortunately not an option.

I've been applying a simple logon/logoff batch file script via Group Policy to create a text file when the user logs on and delete it when they log off. If a file corresponding to their username exists when they log on it logs them off. The problem with this is that for it to work the students need full read/write/delete access to this folder and they now know they can just delete their file from this folder to have multiple sessions again.

I was looking some sort of database entry/checking system as opposed to the file creation. I currently am using a VB script to log all users logons and logoffs into a MS Access database and was looking use a similar approach for putting usernames into a table when they log in, removing it when logout and checking and logging them out if their name already exists within the database.

I have done some coding in the past with VB and PHP but i'm a little rusty so any help would be appreciated.

Alternatively, if anyone knows of a different to this approach I would be interested in hearing it!

Thanks for reading,

Terry
Avatar of Mike Kline
Mike Kline
Flag of United States of America image

Yeah what you have found is on the money, no easy way to limit concurrent logins.  Easy to restrict to certain machines but that is not what you want.

Nothing really great, this was actually discussed on an MVP distro last summer summer.  One of the guys said:

"...Some companies are doing this by restricting the share of the home-folder to one session and check in the logon-script whether the home-drive is available  if not logout ..."


Another solution is smart cards so that you have to have a smart card to login.  May not be practical for a k-12 though (for the younger kids) and it is expensive to setup at first.

Limitlogon has never been great.

Thanks
Mike
just a few thoughts on possibe solutions:

1)  What if you use MS SQL instead of MS Access (very similar syntax, to both), and then enable XP Command shell in SQL, then have a trigger to create and delete files in a folder on the server when a user logs on or off.  Then you can leave your logon scripts essentially the same, but only have them check if the file exists, that only requires having reed access to the directory.


2)  OR  Better Yet, why not have the students wite a file to one directory where they have write access, and have an automated process MOVE the file to another directory where they only have read access for the logon script to read from.  Then have a log-off script whcih will write a new file to a different directory, you monitor that directory and delete the script if the file appears (since they should not have any access to log off scripts this shoudl work okay)  Or you could instead have a recurring task to check the computer to see if the user is still logged on there (using NET View "\\ComputerName") and delete if they log off.

3)  OH!  How about THIS:

make TWO scheduled tasks (since I doubt any students are administrators they can't edit scheduled tasks or stop the service!)  The scheduled tasks will both run as a special user, it will be the only user with the permissions to write/delete on the folder, and you will be the only one to know it's user name and password.

The 1st scheduled task will be used with the "ON LOGON" start setting, and will create the file by running as the special user.

Your logoff script will run the Second task using SCHTasks, and that task will delete the file, and will log off the current user -- just in case someone figure out how to run it (Using Shutdown /L /F /T 0  -- this says to log off the user forcefully closing applications without asking, and has a delay of 0 seconds--)

I think option 3 is 'best' because it requires the least 'new' work , and therefore fastest time to implementation, and also should be pretty easy to implement, you just need to use SCHTasks to kick off the log-off script by name, and you can also use SCHTasks to mass create the tasks by using a batch file to loop through all the computer names.
Avatar of teztickles

ASKER

Thanks for the ideas guys. It's a high school that I'm at so 7-12 (and some of them know their way around a computer).

mkline71, smart cards are a little out of my league in terms of funding, it's pretty much just me and my own devices though the single home share session you mentioned is interesting. I'll have to have a play with that and see what I can come up with.

I managed to get the database logon/logoff scripts doing what I want them to do, I just have to put them into the system and test them out to see how they go. I like your second and third ideas though QCubed, something I hadn't thought about. I'd also read a bit about running scripts as administrator which I guess would allow file creation in a folder that is only shared to those with administrator privileges, no?

Somethings to think about but I'll certainly try some of those ideas.

Ill leave this open for a little while to see if anyone else has any decent ideas and to post back my results.

Thanks again,

tez
The beauty of the 3rd Idea is that the user would be a Standard Domain User, which may bot need admin privliges on the client machine at all, if you only create and delete the file, it will NOT need those priviledges tgo actually run.  It would only have to have admin priviledges on the machines where the task is run if you include my Idea to have the removal process force the student to log-off (I think that is for the best!  So I will assume that method & that therefore the user needs to be an administrator on all client machines), and on your servers you would only need to have the user given full controll on the share and the security settings of the folder it needs to write to.

  The task's properties would hold the UN and the PW, but the PW is encrypted by default.  So  if you gain access to the scheduled tasks control panel/mmc you can only change the password, not read it, .

Schedulaed tasks itself will deny access to it from regular users except to run a task.

I assume you already run Services as users and in doing so you follow your own set of best practices.  This user would be treated similarly.  Name it with a good name like "SLogonSVC" or "ForcedSingleLogonService".

  You would only make the user a Domain user, and, you should by now, have (and if you don't it's time yoi made one) a group that contains local admins on all student machines, so you can add the user to that group, and have it there-for included in the local admins group on all student machines.

 Then to be thorough you can lock the user down completely in terms of other network access priviledges, for say Remote desktop sessions, etc, it would be denied.  You can also give the user a logon script that will write a text file to show where the user was attempted to be used to log onto a machine, and what date and time it occured, and then the next step in the logon script can be to logoff the user.

Another script on that server can then check the folder for such files periodically, and email them to you using BLAT, if you like.  Though all of this sounds like it's best left to future iterations.

I'm glad you like my Idea, I like to think outside the box like that, when an Idea is a little off the wall and gets some attention it really makes me feal good.

LMK, if there is anything I can help you with.   If I think of a better solution I will post back as well.

-Ben
Hi guys, I'm not sure if this will work, but I will test out a possible solution to this issue, by limiting the "machines" they can log on to in the userWorkstations attribute at Logon.

I will test something tomorrow and see what I can come up with.

Regards,

Rob.
Thanks Rob, will be interested.

Still haven't tried out your idea yet Ben, fiddling around with the database idea at the moment and as much as it works I think its always gonna have a 'hole' somewhere. As I'm using a database, files don't need to be created or destroyed so the students don't have delete privileges anymore. I've password protected the database so even if they do go into that folder they won't be able to open the database to delete their record BUT to have the script running on a passworded database I need to have the database password in plain text in the actual logon script. There's always a catch!! I think that's the reason I like yours so much Ben, I like the idea of having the data not shared therefore not accessible.

I'm going to put it into effect tomorrow (along with a few other lockdowns) and see how it goes. I have another log file in effect at the moment so hopefully Ill be able to tell if there's any shenanigans going on.

tez
OK, so, what I have found is that a normal user cannot modify their own userWorkstations attribute.  This setting in the GUI is on the Account tab, Logon To.  What the below script does is restrict logons to a specific computer, which would be the one they log on to at the first attempt.  It does this by binding to the user object with domain admin privileges.  Then, when that user logs off, the userWorkstations attribute is cleared, in the same way, so that they can log on to any computer again.

One particular caveat to this approach is that it restricts the logon to one, and only computer at a time.  You cannot allow more than one concurrent logon, to say 3, because you cannot forsee the computer name that the account may be used on, and wildcards do not work.

Another caveat that applies to all of these techniques, and even CConnect, is that if the logoff process does not occur normally (if the computer shut down unexpectedly), the attribute isn't cleared, which means an Admin must go into the account and tick "Log On To All Computers" again.

A possible way around this: If you know that all users will be logged off at say 11pm, or you have a process to shut down all computers at 11pm, you could run a scheduled task on the server at 11:30pm or so to clear the userWorkstations for all users.

Now, as for not providing your admin credentials to prying eyes, keep the VBS in a secure folder, without read access for users, and use this tool, VBS2EXE:
http://www.f2ko.de/English/v2e/index.php

but I will have to check whether the EXE can accept command line parameters, because the script needs to be run at Logon with a parameter of
Logon

and run again at Logoff with a parameter of
Logoff

I'll check that out later....

Also, one plus to this approach, is that while it only allows for one concurrent logon, when a second attempt is made, logon is actually refused by the domain, and the user isn't logged in, then logged off, like the other approaches.

Regards,

Rob.
strMode = WScript.Arguments.Item(0)
If Trim(LCase(strMode)) = "logon" Or Trim(LCase(strMode)) = "logoff" Then
	Set objNetwork = CreateObject("WScript.Network")
	Set objADSysInfo = CreateObject("ADSystemInfo")
	Set objShell = CreateObject("WScript.Shell")
	strDC = Mid(objShell.ExpandEnvironmentStrings("%LOGONSERVER%"), 3)
	strUserDN = objADSysInfo.UserName
	
	Const ADS_SECURE_AUTHENTICATION = 1
	Const ADS_SERVER_BIND = 512
	strUser = "domain\administrator"
	strPassword = "password"
	Set objLDAP = GetObject("LDAP:")
	
	' Bind to the user using the admin credentials
	Set objUser = objLDAP.OpenDSObject("LDAP://" & strDC & "/" & strUserDN, strUser, strPassword, ADS_SECURE_AUTHENTICATION + ADS_SERVER_BIND)
	' userWorkstations is of type String when one or more computers are entered,
	' otherwise the type is Empty
	If LCase(strMode) = "logon" Then
		objUser.userWorkstations = objNetwork.ComputerName
	ElseIf LCase(strMode) = "logoff" Then
		Const ADS_PROPERTY_CLEAR = 1
		objUser.PutEx ADS_PROPERTY_CLEAR, "userWorkstations", 0
	End If
	objUser.SetInfo
End If

Open in new window

OK, the EXE didn't seem to accept the WScript.Argument.Item(0) line, so didn't work at all.  You can use the below two scripts, the first at Logon, and the second at Logoff, to do the same job though.

Regards,

Rob.
Set objNetwork = CreateObject("WScript.Network")
Set objADSysInfo = CreateObject("ADSystemInfo")
Set objShell = CreateObject("WScript.Shell")
strDC = Mid(objShell.ExpandEnvironmentStrings("%LOGONSERVER%"), 3)
strUserDN = objADSysInfo.UserName

Const ADS_SECURE_AUTHENTICATION = 1
Const ADS_SERVER_BIND = 512
strUser = "domain\administrator"
strPassword = "password"
Set objLDAP = GetObject("LDAP:")

' Bind to the user using the admin credentials
Set objUser = objLDAP.OpenDSObject("LDAP://" & strDC & "/" & strUserDN, strUser, strPassword, ADS_SECURE_AUTHENTICATION + ADS_SERVER_BIND)
' userWorkstations is of type String when one or more computers are entered,
' otherwise the type is Empty
objUser.userWorkstations = objNetwork.ComputerName
objUser.SetInfo

Open in new window

Logoff code
Set objNetwork = CreateObject("WScript.Network")
Set objADSysInfo = CreateObject("ADSystemInfo")
Set objShell = CreateObject("WScript.Shell")
strDC = Mid(objShell.ExpandEnvironmentStrings("%LOGONSERVER%"), 3)
strUserDN = objADSysInfo.UserName

Const ADS_SECURE_AUTHENTICATION = 1
Const ADS_SERVER_BIND = 512
strUser = "domain\administrator"
strPassword = "password"
Set objLDAP = GetObject("LDAP:")

' Bind to the user using the admin credentials
Set objUser = objLDAP.OpenDSObject("LDAP://" & strDC & "/" & strUserDN, strUser, strPassword, ADS_SECURE_AUTHENTICATION + ADS_SERVER_BIND)
' userWorkstations is of type String when one or more computers are entered,
' otherwise the type is Empty
Const ADS_PROPERTY_CLEAR = 1
objUser.PutEx ADS_PROPERTY_CLEAR, "userWorkstations", 0
objUser.SetInfo

Open in new window

Rob,

Just tested out your scripts, they work perfectly! It's actually the kinda thing I was looking for cause I've never liked how the logon scripts I'd been using allowed the user to actually log in to the system before the scripts ran and then logged them out. There's only two problems I have with it.

The first is having the administrator password there in plain text. I have no doubt that that will get discovered just sitting there like that.

The second is the message that is displayed when the user goes to log on to another machine. "Your account is configured to prevent you from using this computer' gives the impression that they have been barred from that particular machine as opposed to being denied concurrent sessions. While I do now plan on looking to see if there is anyway to change that message, have you tried changing it?

But other than those little gripes, it's an awesome script!
Hi. I'm glad it worked for you. It worked for me pretty well.  As far as the admin credentials, I did mention that you can convert the VBS to an EXE, by keeping the VBS in a secure folder, without read access for users, and use this tool, VBS2EXE:
http://www.f2ko.de/English/v2e/index.php

I'm not sure if this will ever be *impossible* to decompile, but it shouldn't be too bad.  I cannot see any threads with Process Explorer that lead you to the raw VBS code.

Or....one thing I've always wondered.....how secure is the Parameters section when you set a Logon Script?  I know that parameters would be passed to the script in plain text, but you'd need a pretty good sniffer to trap that, wouldn't you?  What you could do, is change

strUser = "domain\administrator"
strPassword = "password"

to

strUser = WScript.Arguments.Item(0)
strPassword = WScript.Arguments.Item(1)

in each script, and then not use the EXE (I don't think the arguments work from this tool).

Now, as for the Windows message....unfortunately, that's not customisable.  It's a standard windows message.

I was also thinking....maybe I could expand this to multiple concurrent sessions by writing the computer names to another AD property, and then checking for an existing session, but this means that logon would have to occur, then log off if required.....a step backwards.....

Regards,

Rob.
Sorry, forgot to mention, if you were to use:
strUser = WScript.Arguments.Item(0)
strPassword = WScript.Arguments.Item(1)

then the Logon Script would be:
Name: LimitSessions.vbs
Parameters: domain\username password

Regards,

Rob.
Sorry Rob,

I thought you were saying that VBS to EXE tool didn't work at all but you just meant for that first script. I'll give it a go, and I'll take my chances with the students decompiling it, and the packet sniffing.

I can live without changing the error message, I had a look yo see if there was any way but found if it is at all possible noone knows how! I'll just leave it as is, I'm sure if I pass the message around to staff its meaning will soon leak down to the students.

I'll let you know how it works out.

tez
Cool...I have just posted a new question in the AD zone to see if anyone security minded knows whether "parameters" are secure (enough) to use as well, so I'll let know if anything fruitful comes of that.

Regards,

Rob.
Hi, just letting you know that in my question:
https://www.experts-exchange.com/questions/26440105/How-secure-are-Logon-Script-parameters-passed-to-a-script-in-Active-Directory.html

the general consensus is that is parameters passed from Group Policy to scripts are sufficiently secure from prying eyes, so if you wanted to use

strUser = WScript.Arguments.Item(0)
strPassword = WScript.Arguments.Item(1)

and pass those parameters at the login script assignment, that would be fine.

Regards,

Rob.
@Tez & @Rob,

     You could use the scheduled task manager with an "on logon" scheduled task to run the first script instead of using running it as part of the regular logon script.

     That would allow you to run as a specific user, and hide the UN/PW it needs to use as well. ^^

-Q
Well (I think this is what is going on anyway),

After changing the scripts to exe files then calling them from a seperate vbs script the logon script works fine, locks the user to that computer however the logoff exe doesn't appear to be unlocking them. The exe and vbs file combination works fine, I've tested it while logged in to a profile and it unlocks the user but it just doesn't seem to be working as a log off script. I'm assuming it has something to do with an exe file trying to run while the computer logging off.

Rob, I'll try and implement your parameter passing technique and see how that fairs but I might need a little support there, see how I go.

Thanks again!

tez
Hmmm, maybe what's happening there is that the environment variable %LOGONSERVER% is not available (or destroyed) by the time the logoff script runs.

How many DCs do you have?  How many sites do you have?  If there's only one, or they all replicate immediately (ie. they're in the same site) then you should get away with changing
strDC = Mid(objShell.ExpandEnvironmentStrings("%LOGONSERVER%"), 3)

to
strDC = "YourDomainController"

That might work better.....test that out and see if my suspicion is correct....

Regards,

Rob.
Yeah, only one site, one DC, so I'll try it out and post back.

Or I'll just forget to post and put it all in one!

Tried that out and still the same result, all works fine during a user session but doesn't work as a log off script.
So you tried hardcoding your DC with
strDC = "YourDomainController"

and that still didn't work at logoff?

That's annoying....try the below code for Logoff.  It will hopefully write a log to c:\Temp\LogoffLog.txt that might give us a clue as to what's happening.

Regards,

Rob.
Set objNetwork = CreateObject("WScript.Network")
Set objADSysInfo = CreateObject("ADSystemInfo")
Set objShell = CreateObject("WScript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objLog = objFSO.CreateTextFile("C:\Temp\LogoffLog.txt", True)

strDC = Mid(objShell.ExpandEnvironmentStrings("%LOGONSERVER%"), 3)
objLog.WriteLine Now & " - DC is " & strDC
strUserDN = objADSysInfo.UserName
objLog.WriteLine Now & " - User DN is " & strUserDN

Const ADS_SECURE_AUTHENTICATION = 1
Const ADS_SERVER_BIND = 512
strUser = "domain\administrator"
strPassword = "password"
Set objLDAP = GetObject("LDAP:")

' Bind to the user using the admin credentials
On Error Resume Next
Set objUser = objLDAP.OpenDSObject("LDAP://" & strDC & "/" & strUserDN, strUser, strPassword, ADS_SECURE_AUTHENTICATION + ADS_SERVER_BIND)
If Err.Number = 0 Then
	objLog.WriteLine Now & " - bound to user object."
	' userWorkstations is of type String when one or more computers are entered,
	' otherwise the type is Empty
	Const ADS_PROPERTY_CLEAR = 1
	objUser.PutEx ADS_PROPERTY_CLEAR, "userWorkstations", 0
	objUser.SetInfo
Else
	objLog.WriteLine Now & " - Could not bind to user object. Error " & Err.Number & ": " & Err.Description
End If
objLog.Close

Open in new window

Hi Rob,

Sorry for not getting back straight away,  busy day, didn't even get to play with it today.

Yeah putting the domain controller name in instead of 'strDC = Mid(objShell.ExpandEnvironmentStrings("%LOGONSERVER%"), 3)' had the same result, still didn't run on logoff.

I'll hopefully get a bit of free time tomorrow to try out that code. I'll post back the contents of the text file after I've run it.

Thanks heaps for your help too Rob, I really do appreciate it.

tez
Ok, ran your code at logoff and got the text file below. The STUDENTNAME and SCHOOLNAME entries were actually displaying the correct details, I just changed them to protect the school/student but aside from that, everything looks in order, everything appears to be displaying as it should.

I ran your script as both a .vbs file and as an .exe file called from a .vbs file. Again, the exe file didn't appear to run at logoff (or at least no text file was created).

I tried using your parameter technique too, setting strUser = WScript.Arguments.Item(0) and strPassword = WScript.Arguments.Item(1) but it errors out on me when it runs, line 1 char 1, which I assume is something I'm doing and not your code as you mentioned if I use those parameters that the Logon Script would be Name: LimitSessions.vbs and parameters: domain\username password and I'm not exactly sure what you mean by that.
9/7/2010 8:49:26 AM - DC is 8906F1
9/7/2010 8:49:26 AM - User DN is CN=STUDENTNAME,OU=AdminTesting,OU=Students,OU=School,DC=SCHOOLNAME,DC=schools,DC=nsw,DC=edu,DC=au
9/7/2010 8:49:26 AM - bound to user object.

Open in new window

OK, I'll add a bit more logging after the "bound to user object".  We need to see whether it manages to successfully clear the userWorkstations attribute.

Rob.
Set objNetwork = CreateObject("WScript.Network")
Set objADSysInfo = CreateObject("ADSystemInfo")
Set objShell = CreateObject("WScript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objLog = objFSO.CreateTextFile("C:\Temp\LogoffLog.txt", True)

strDC = Mid(objShell.ExpandEnvironmentStrings("%LOGONSERVER%"), 3)
objLog.WriteLine Now & " - DC is " & strDC
strUserDN = objADSysInfo.UserName
objLog.WriteLine Now & " - User DN is " & strUserDN

Const ADS_SECURE_AUTHENTICATION = 1
Const ADS_SERVER_BIND = 512
strUser = "domain\administrator"
strPassword = "password"
Set objLDAP = GetObject("LDAP:")

' Bind to the user using the admin credentials
On Error Resume Next
Set objUser = objLDAP.OpenDSObject("LDAP://" & strDC & "/" & strUserDN, strUser, strPassword, ADS_SECURE_AUTHENTICATION + ADS_SERVER_BIND)
If Err.Number = 0 Then
	objLog.WriteLine Now & " - bound to user object."
	' userWorkstations is of type String when one or more computers are entered,
	' otherwise the type is Empty
	Const ADS_PROPERTY_CLEAR = 1
	objUser.PutEx ADS_PROPERTY_CLEAR, "userWorkstations", 0
	objUser.SetInfo
	If Err.Number = 0 Then
		objLog.WriteLine Now & " - cleared the userWorkstations attribute."
	Else
		objLog.WriteLine Now & " - error clearing the userWorkstations attribute. Error " & Err.Number & ": " & Err.Description
		Err.Clear
	End If
Else
	objLog.WriteLine Now & " - Could not bind to user object. Error " & Err.Number & ": " & Err.Description
End If
objLog.Close

Open in new window

Hey Rob,

That code just produces the same output as the last script with the addition of the bottom line.

9/7/2010 10:28:58 AM - DC is 8906F1
9/7/2010 10:28:58 AM - User DN is CN=STUDENTNAME,OU=AdminTesting,OU=Students,OU=School,DC=SCHOOLNAME,DC=schools,DC=nsw,DC=edu,DC=au
9/7/2010 10:28:58 AM - bound to user object.
9/7/2010 10:28:58 AM - cleared the userWorkstations attribute.

As with before, when it's being run as a vbs it runs fine at logoff but running it as an exe at logoff doesn't. I don't think it's the script that's the problem, it works beautifully, it just seems incapable of running the exe file at logoff.
ASKER CERTIFIED SOLUTION
Avatar of RobSampson
RobSampson
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Sorry about that Rob, I didn't wanna seem like I was getting impatient or anything with your help by saying "that's not what I mean". Yeah the script works fine, was just that hiccup with the exe.

And I see what you mean now about the name/parameters, I wasn't sure whereabouts you were telling me to put them.

I'll give it a go and post back the results. Fingers crossed!!
Sure, no problem.  I haven't tested it at logoff myself (yet), so hopefully it works this time.  :-)

Regards,

Rob.
That works a treat Rob :D

I've done away with the exe's now and am just using the parameter passing on both the logon and logoff scripts and it all seems to be working fine, locks them on log on, unlocks them on log off. I'll test it out a little before I implement it but that seems to be working perfectly!

I know I've already said it but thanks again for your all your help Rob (and Q and others). Not only have a I learnt a couple of things in the process but hopefully this will help someone out in the future if they're in the same predicament as I was in.
Sure.  It makes sense to use the parameters instead of the EXE, it would be a bit more double handling otherwise.

Thanks for the grade.

Regards,

Rob.
You're more than welcome, you can help me out anytime ;)