Login Scripts - Best Practices, Part 3
Novell Cool Solutions: Feature
By David Lange
Digg This -
Posted: 25 Jan 2006
Login scripts are the lost art of the IT community. They can be a very efficient means to deliver important information to your users and manage local PCs. Unfortunately, many organizations find their scripts cumbersome and difficult to navigate.Login scripts should be simple to manage. They should be easily viewed and intuitive to any IT staffer with a little programming background. In fact, if you treat them like code and create modular script that can be referenced in subordinate containers, you'll find you can easily manage your scripts and make changes to accommodate user needs much more efficiently.
You should get three valuable tips from reading this article:
- Where to find instructions on building your scripts.
- How to modularize your login script for easy management and execution.
- How to help eDirectory deliver your modular scripts without slowing down the login process.
Resources for Building Scripts
Hopefully, you already found and read Novell's Login Scripts Guide:
I wouldn't be surprised if you haven't read this yet since it's not very easy to find. Don't worry that the URL says "linux_client" - this guide is all about Windows, too.
Now that you've found it, print yourself a copy and read it. Make sure you read the sections in chapter 3 on login script conventions and identifier variables. I'll wait here until you're done. Please try not to spend too much time playing with that FIRE PHASERS command ...
While not specifically related to login scripts, many of you will find the following TID helpful, especially if you also use ZENworks. It lists additional variables that represent common directories on a Windows workstation:
Order of Execution
Before showing you my method, it's important that you understand some basic concepts about script execution. Even after you've read the above documents, these are important concepts that bear repeating. When a user logs in, the Novell client reads the directory to determine what script commands should be executed on behalf of this user. Those commands execute in this order:
- Container script - the script defined in the user's container object (but not the parent container).
- Profile script - defined by a profile object which can be anywhere in the tree, but is referenced in the user object.
- User script - defined in the user object. If no user script is defined, then default script is executed.
But what if I want to be more dynamic? Maybe certain conditions should lead to different commands, a different script, or stopping the execution of script altogether.
Also, what if all my users in 38 different containers should always get the same commands? Perhaps a corporate-wide announcement should be displayed, and it gets changed every week. Do I really have to change the announcement in 38 different containers?
Thankfully, there are several commands that can be used to change the execution of a script. Here are the most important ones:
- INCLUDE. This command makes the modular script concept possible. It is the basis for the rest of this document. You can create script in a profile object, or even a file, and call it from another script by using this command.
- IF*THEN*ELSE*END. This standard set of logic commands can look at variables and make branching decisions within script.
- GOTO. Again, this is standard programming fare to change the order of execution within a script.
- PROFILE. This command can be used in a container (or included modular) script to change the profile script a user will receive.
- EXIT. This command can be used to stop all subsequent script execution.
Traditionally, you would create script in profile objects to be referenced by user objects. However, you can also create profile objects that are NOT referenced by user objects. These seemingly un-referenced profile objects are perfect for holding "modules" of script. The profile objects essentially become script modules that can be hierarchically called by container script, or another module, using the INCLUDE command.
Suppose you want to execute the following commands for all users in all 38 containers for the school district where you work:
WRITE "Good %GREETING_TIME" WRITE "You are logged in to the MyCity School District Network" WRITE "from the %FILE_SERVER file server." WRITE "Your user context is %LOGIN_CONTEXT" WRITE "You are using NetWare client %NETWARE_REQUESTER" WRITE "Your workstation's operating system is %OS version %WINVER" WRITE "Your DOS shell version is %SHELL_TYPE" WRITE "You authenticated as user %LOGIN_NAME ( %FULL_NAME )." WRITE "Your password expires in %PASSWORD_EXPIRES days." WRITE "Network address: %NETWORK_ADDRESS" WRITE "Workstation node address: %P_STATION" WRITE "Workstation connection number: %STATION" WRITE "Machine type: %MACHINE" SET USER="%LOGIN_NAME" SET ADDRESS="%P_STATION" << 5 SET P_ADDRESS="%P_STATION" WRITE "The winter holidays are Dec 15 to Jan 7"
If you create this script in 38 different container objects, you must also edit it in 38 different container objects every time a new holiday rolls around.
Instead, create a profile object called "greeting.scripts.applications.district" and put your greeting script in this object. (There's a reason I put this profile object in this container - stay tuned.) Now, each of your container scripts could contain just one command to call this script from the profile object:
And every time you need to change your greeting script you do so only once.
That's the basic concept.
Now, here are the general rules that I apply to all my containers and scripts:
1. Every container has a profile object named "myscript" with script commands that should be executed by its container and any subordinate containers.
2. Every "myscript" profile object "includes" its parent container's "myscript" object before executing its own script, which ensures:
- All the "myscript" scripts are called hierarchically so the top level (district, in the example below) myscript is executed first.
- All the "myscript" objects are executed before the container script.
- Only the users who are actually in the local container get the container script, not the subordinates.
3. Script commands that should be executed only by the immediate users of a container are in the container object.
4. Script commands that should be executed by the users of a container and all subordinate containers are in the "myscript" profile object at that container level.
5. When a section of script serves a special purpose or becomes too complex to manage within the container or "myscript" object, it is broken out into its own module and "included".
- Each ou container has a profile object named "myscript".
- Each ou container has script that calls its own contained myscript profile object.
- Each myscript profile object starts by calling it's parent container's myscript profile object .
-.o=District -.ou=HR -.ou=Admin -.ou=IT (Very important users go here!) -.ou=Health (Nurses are here) -.ou=CampusA (Teachers and staff from campus A) -.ou=Students.CampusA (Students from campus A) -.ou=CampusB -.ou=Students.CampusB -.ou=Applications -.ou=Scripts.Applications
Here are some example scripts that may be helpful.
Profile object: myscript.scripts.applications.district
REM This is the top-level myscript object. All the subordinate ou's myscript objects ultimately call this object through hierarchical "INCLUDE" commands. REM This script simply calls all the general purpose scripts that everyone should get. REM They were modularized because they served a special purpose or became too complex to manage in this profile object. Include .greeting.scripts.applications.district (see above for content) Include .checkIP.scripts.applications.district Include .drivemaps.scripts.appllications.district
Profile object: checkIP.scripts.applications.district
IF "%NETWORK_ADDRESS" < "AC1E1DFF" AND "%NETWORK_ADDRESS" > "AC1E1D00" THEN SET mysubnet="29" SET myZENserver="172.30.29.1" ELSE IF "%NETWORK_ADDRESS" < "AC1E1EFF" AND "%NETWORK_ADDRESS" > "AC1E1E00" THEN SET mysubnet="30" SET myZENserver="172.30.30.1" ELSE SET mysubnet="31" SET myZENserver="172.30.31.1" END END REM This sets a couple of DOS variables that I can use in ZEN applications or batch commands. REM Why do we check this at the root script level instead of just setting it at the container script level? REM Because if the nurse (ou=health) logs into a CampusA workstation on Monday and Wednesday REM And a CampusB workstation on Tuesday and Thursday, REM I want that person to get ZEN apps from the closest server, not the one at the district office.
Profile object: drivemaps.scripts.applications.district
REM We all know we're not supposed to use cross-container groups, but we all know we do. REM This script module sets up any district-wide shared drives. IF MEMBER OF ".project1.applications.district" THEN MAP ROOT G:=Server1/DATA:\GROUPS\Project1 END IF MEMBER OF " principals.applications.district " THEN MAP ROOT P:=Server1/DATA:\GROUPS\Principals END
Container object: HR.district
WRITE " " WRITE "Attention all HR users:" WRITE "The personnel file combination is now 3486" REM Obviously, only those in HR should get this script! REM But everyone in HR should also get the general script.
Container object: CampusA.district
Include .myscript.CampusA.district MAP ROOT H:=ServerA/DATA:\Staff\%USER_NAME% IF MEMBER OF ".english" THEN MAP ROOT K:=ServerA/DATA:\GROUPS\English END IF MEMBER OF ".math" THEN MAP ROOT K:=ServerA/DATA:\GROUPS\Math END IF MEMBER OF ".science" THEN MAP ROOT K:=ServerA/DATA:\GROUPS\Science END WRITE " " WRITE "Attention all staff:" WRITE "One of our students has head lice." WRITE "Please let the nurse know if any of your" WRITE "students repeatedly scratch their heads" WRITE "as it may be spreading." REM Of course, e-mail would be a more efficient way of getting this message out. REM But the point is the students won't get this script because they're in a different container.
Container object: Students.CampusA.district
NODEFAULT Include .myscript.CampusA.district MAP ROOT H:=ServerA/DATA:\Student\%USER_NAME% WRITE " " WRITE "Attention students: " WRITE "The nurse will be here every Monday and Wednesday" WRITE "Please stop by if you have any problems you need help with."
Profile object: .myscript.CampusA.district
REM Everyone on campus gets this script because both containers include it. REM Because this script includes the district script, everyone also gets that. Include myscript.scripts.applications.district IF MEMBER OF ".StuGov" THEN MAP ROOT L:=ServerA/DATA:\GROUPS\StuGov REM It is conceivable that both students and faculty advisors should have access to this drive. END WRITE " " WRITE "Attention CampusA Warriors:" WRITE "The school carnival is Feb 3." WRITE "Hope to see you there!"
A student in CampusA will get this script:
- Myscript.scripts.applications.district (modular profile script object - which calls 3 other profile script modules)
- Myscript.CampusA.district (modular profile script object)
- Students.CampusA.district (container script)
followed by any profile and user script assigned to this user.
And it will execute like this:
[.greeting.scripts.applications.district] WRITE "Good %GREETING_TIME" WRITE "You are logged in to the MyCity School District Network" WRITE "from the %FILE_SERVER file server." WRITE "Your user context is %LOGIN_CONTEXT" WRITE "You are using NetWare client %NETWARE_REQUESTER" WRITE "Your workstation's operating system is %OS version %WINVER" WRITE "Your DOS shell version is %SHELL_TYPE" WRITE "You authenticated as user %LOGIN_NAME ( %FULL_NAME )." WRITE "Your password expires in %PASSWORD_EXPIRES days." WRITE "Network address: %NETWORK_ADDRESS" WRITE "Workstation node address: %P_STATION" WRITE "Workstation connection number: %STATION" WRITE "Machine type: %MACHINE" SET USER="%LOGIN_NAME" SET ADDRESS="%P_STATION" << 5 SET P_ADDRESS="%P_STATION" WRITE "The winter holidays are Dec 15 to Jan 7" [checkIP.scripts.applications.district] IF "%NETWORK_ADDRESS" < "AC1E1DFF" AND "%NETWORK_ADDRESS" > "AC1E1D00" THEN SET mysubnet="29" SET myZENserver="172.30.29.1" ELSE IF "%NETWORK_ADDRESS" < "AC1E1EFF" AND "%NETWORK_ADDRESS" > "AC1E1E00" THEN SET mysubnet="30" SET myZENserver="172.30.30.1" ELSE SET mysubnet="31" SET myZENserver="172.30.31.1" END END [.drivemaps.scripts.applications.district] IF MEMBER OF "project1.applications.district" THEN MAP ROOT G:=Server1/DATA:\GROUPS\Project1 END IF MEMBER OF " principals.applications.district " THEN MAP ROOT P:=Server1/DATA:\GROUPS\Principals END [.myscript.CampusA.district] IF MEMBER OF ".StuGov" THEN MAP ROOT L:=ServerA/DATA:\GROUPS\StuGov REM It is conceivable that both students and faculty advisors should have access to this drive. END WRITE " " WRITE "Attention CampusA Warriors:" WRITE "The school carnival is Feb 3." WRITE "Hope to see you there!" [Students.CampusA.district] MAP ROOT H:=ServerA/DATA:\Student\%USER_NAME% WRITE " " WRITE "Attention students: " WRITE "The nurse will be here every Monday and Wednesday" WRITE "Please stop by if you have any problems she can help you with."
Teachers from the same campus will get the same script, except the final portion of script. Instead of getting Students.CampusA.district (container script), teachers and staff at CampusA will get container script from CampusA.district which will do the following:
- Map their H drives from the staff directory
- Map their shared K drives, depending on which department they're in
- Display a teacher-specific announcement
In this example, all the principals from all over the district will map the same P drive, and any staff member involved with project1, regardless of which campus they come from, will get the same G drive.
Keeping the Execution Efficient
As promised, this final tip may help speed up the execution of your scripts. I'm making the assumption that you have a tree with partitions stored on each remote (campus) server. For example, ou=CampusA defines a partition that is stored on ServerA which is physically located at CampusA.
You do this to prevent unnecessary traffic from traversing your WAN so that every time a user in CampusB logs in, the CampusA server doesn't have to hear about it and change the user object's LastLoginTime attribute. This is all well and good - except that whenever someone in CampusA needs to reference an object that's not in the local replica on ServerA, the client traverses the WAN to walk the tree and find the object, leading to slow login times.
Notice that in my example tree I have an OU called Applications. This OU is also a partition that is replicated to all servers. That way, when a user in CampusA needs to reference an object in the applications container, there's a copy on the local ServerA server.
If you place all your commonly used reference objects like ZEN applications, cross-container groups, and modular profile scripts in this container, then all servers have a local copy to work with instead of traversing the WAN. This should speed up your script execution, as well as ZEN refresh time.
Please send any questions or comments to me at:
Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com