Login scripts can be used for many purposes:
You are completely free to choose any scripting language for your login script... you may even use an executable as your login "script".
Keep in mind, however, that login scripts tend to be long-lived: they may survive multiple OS and hardware updates in your domain.
Batch commands are often broken in OS updates.
This makes batch files less suitable candidates for login script.
Besides, because of being long-lived, several "generations" of administrators may have to edit and maintain the login script's code, so both the scripting language and the script itself need to be well documented -- another reason not to choose for batch files as login scripts.
Many companies do use a batch file as their login script, but in most cases this batch file serves only as a "wrapper" to start he "real" login script, e.g. @KIX32.EXE login.kix or @CSCRIPT.EXE //NoLogo login.vbs or @REGINA.EXE login.rex or @PERL.EXE login.pl.
PowerShell is not yet a viable option to use for login scripts: it requires Windows XP or later, even then it isn't installed by default, and even if installed, its settings for running scripts needs to be properly configured (not the default settings).
On this page, the main focus will be on login script snippets in KiXtart (version 4.60) and VBScript (WSH version 5.6 or 5.7), with only a limited number of (NT) batch snippets.
I will also assume that all workstations run Windows 2000 or a more recent Windows version.
| Note: | Many of the snippets can be used on older Windows versions too, but keep in mind that: | |
| a. | many variables won't have a value till after the logon process is finished; some will never have a value at all | |
| b. | WMI is not installed by default in these Windows versions | |
| c. | an older version of Windows Script Host will be installed by default | |
| d. | NT batch files will not run correctly on Windows 9*/ME; if you do use an NT batch file as login script, use a .CMD exension instead of .BAT | |
NET USE G: \\CompanyServer\Dept /PERSISTENT:No
IF ERRORLEVEL 1 (
ECHO Error mapping drive G:
)
NET USE H: \\CompanyServer\%UserName% /PERSISTENT:No
IF ERRORLEVEL 1 (
ECHO Error mapping drive H:
)
USE G: "\\CompanyServer\Dept"
If @ERROR <> 0
"Error @ERROR mapping drive G:@CRLF"
EndIf
USE H: "\\CompanyServer\@HOMESHR"
If @ERROR <> 0
"Error @ERROR mapping drive H:@CRLF"
EndIf
Set wshNetwork = CreateObject( "WScript.Network" )
On Error Resume Next
With wshNetwork
.MapNetworkDrive "G:", "\\CompanyServer\Dept"
If Err Then
WScript.Echo "Error " & Err & " mapping drive G:"
WScript.Echo "(" & Err.Description & ")"
End If
.MapNetworkDrive "H:", "\\CompanyServer\" & .UserName
If Err Then
WScript.Echo "Error " & Err & " mapping drive H:"
WScript.Echo "(" & Err.Description & ")"
End If
End With
On Error Goto 0
Set wshNetwork = Nothing
Instead of "annoying" the user with the details of mapping drives, consider logging error messages and the results to a log file on the local computer.
In case of errors, set a variable named Error and, at the end of the login script, display a message telling the user to contact the helpdesk.
; It doesn't hurt to make sure the C:\temp folder exists
MD "C:\temp"
; Redirect messages to a log file, display
; a message dialog if redirection fails
If RedirectOutpu( "C:\temp\login.log", 1 ) <> 0
$Msg = "Error logging the results.@CRLF"
$Msg = $Msg + "Please notify the helpdesk.@CRLF"
$Msg = $Msg + "For now, results will be displayed on screen."
$RC = MessageBox( $Msg, "Log File Error", 64, 300 )
EndIf
$Error = 0
; Map drive G: to the department share
USE G: "\\CompanyServer\Dept"
If @ERROR <> 0
"Error @ERROR while trying to map drive G:@CRLF"
$Error = $Error + 1
EndIf
; Map drive H: to the user's home share
USE H: "\\CompanyServer\@HOMESHR"
If @ERROR <> 0
"Error @ERROR while trying to map drive H: to the homedir@CRLF"
$Error = $Error + 1
EndIf
; List all mappings
USE List
; End redirection
$RC = RedirectOutput( "" )
; Warn the user if (an) error(s) occurred
If $Error > 0
$Msg = "$Error error(s) occurred during login.@CRLF"
$Msg = $Msg + "The errors are logged to be "
$Msg = $Msg + "reviewed by the helpdesk staff.@CRLF"
$Msg = $Msg + "Please notify the helpdesk.@CRLF"
$RC = MessageBox( $Msg, "Login Error", 64 )
EndIf
Set wshNetwork = CreateObject( "WScript.Network" )
Set objFSO = CreateObject( "Scripting.FileSystemObject" )
' It doesn't hurt to make sure the C:\temp folder exists
If Not objFSO.FolderExists( "C:\temp" ) Then
Set objTempFolder = objFSO.CreateFolder( "C:\temp" )
Set objTempFolder = Nothing
End If
On Error Resume Next
' Open a log file, display a message dialog in case of error
Set objLogFile = objFSO.CreateTextFile( "C:\temp\login.log", True, False )
If Err Then
strMsg = "Error logging the results." & vbCrLf _
& "Please notify the helpdesk." & vbCrLf _
& "For now, results will be displayed on screen."
MsgBox strMsg, "Log File Error", 64
End If
intError = 0
With wshNetwork
' Map drive G: to the department share
.MapNetworkDrive "G:", "\\CompanyServer\Dept"
If Err Then
objLogFile.WriteLine "Error " & Err & " mapping drive G:"
objLogFile.WriteLine "(" & Err.Description & ")"
intError = intError + 1
End If
' Map drive H: to the user's home share
.MapNetworkDrive "H:", "\\CompanyServer\" & .UserName
If Err Then
objLogFile.WriteLine "Error " & Err & " mapping drive H:"
objLogFile.WriteLine "(" & Err.Description & ")"
intError = intError + 1
End If
End With
On Error Goto 0
' List all drive mappings
With wshNetwork.EnumNetworkDrives
For i = 0 To .Count - 2 Step 2
objLogFile.WriteLine .Item(i) & " " & .Item(i+1)
Next
End With
' Close the log file
objLogFile.Close
Set objLogFile = Nothing
' Warn the user if (an) error(s) occurred
If intError > 0 Then
strMsg = intError & " error(s) occurred during login." & vbCrLf _
& "The errors are logged to be reviewed " _
& "by the helpdesk staff." & vbCrLf _
& "Please notify the helpdesk."
MessageBox strMsg, "Login Error", 64
EndIf
End If
Set objFSO = Nothing
Set wshNetwork = Nothing
Often network drives are mapped based on group membership (or, for AD domains, on OU):
NET GROUP Marketing /DOMAIN | FINDSTR /R /I /B /C:"%UserName%$" >NUL
IF NOT ERRORLEVEL 1 (
NET USE G: \\Server\Marketing /PERSISTENT:No
)
| Note: | Though this will usually work, it may fail if ampersands, carets, percent or dollar signs are used in group or user names. Not recommended! |
If InGroup( "Marketing" )
USE M: "\\CompanyServer\Marketing"
EndIf
In VBScript this is a little more complicated, though hard-coding the domain name would simplify things:
strGroup = "Marketing"
blnMember = False
Set objSysInfo = CreateObject( "WinNTSystemInfo" )
strUserName = objSysInfo.UserName
strDomain = objSysInfo.DomainName
Set objSysInfo = Nothing
Set objUser = GetObject( "WinNT://" & strDomain & "/" & strUserName )
Set colGroups = objUser.Groups
For Each objGroup in colGroups
If LCase( objGroup.Name ) = LCase( strGroup ) Then
blnMember = True
End If
Next
Set colGroups = Nothing
set objUser = Nothing
The code shown is for NT as well as AD groups, and even for local groups on computers in a workgroup.
For AD domains, use ADSI.
If users are allowed to map their own drives, you may want to consider disconnecting drives before mapping them:
NET USE G: /DELETE /Y
NET USE G: \\CompanyServer\Dept /PERSISTENT:No
USE G: /DELETE
USE G: "\\CompanyServer\Dept"
wshNetwork.RemoveNetworkDrive "G:", True
wshNetwork.MapNetworkDrive "G:", "\\CompanyServer\Dept"
NET USE LPT1 \\Server\HPLJ4 /PERSISTENT:No
IF ERRORLEVEL 1 (
ECHO Error connecting printer HP LaserJet 4
)
If Not AddPrinterConnection( "\\CompanyServer\LaserJet Marketing" ) = 0
"Error @ERROR while trying to connect to LaserJet Marketing@CRLF"
EndIf
USE LPT1: "\\Server\HPLJ4"
If @ERROR <> 0
"Error @ERROR while trying to connect to HPLJ4@CRLF"
EndIf
Set wshNetwork = CreateObject( "WScript.Network" )
On Error Resume Next
wshnetwork.AddWindowsPrinterConnection "\\CompanyServer\LaserJet Marketing"
If Err Then
WScript.Echo "Error " & Err.Number & " while trying to connect to LaserJet Marketing"
WScript.Echo "(" & Err.Description & ")"
End If
On Error Goto 0
Set wshNetwork = Nothing
Set wshNetwork = CreateObject( "WScript.Network" )
On Error Resume Next
wshNetwork.AddPrinterConnection "LPT1", "\\Server\HPLJ4", False
If Err Then
WScript.Echo "Error " & Err.Number & " while trying to connect to HPLJ4"
WScript.Echo "(" & Err.Description & ")"
End If
On Error Goto 0
Set wshNetwork = Nothing
Like network drives, printer connections will usually depend on OU or group membership.
The same techniques discussed for network drives apply for network printers too.
Disconnecting network printers is much like disconnecting network drives:
NET USE LPT1 /DELETE /Y
IF ERRORLEVEL 1 (
ECHO Error disconnecting printer port LPT1
)
If Not DelPrinterConnection( "\\CompanyServer\LaserJet Marketing" ) = 0
"Error @ERROR while trying to drop LaserJet Marketing@CRLF"
EndIf
USE LPT1: /DELETE
If @ERROR <> 0
"Error @ERROR while trying to drop LPT1@CRLF"
EndIf
Set wshNetwork = CreateObject( "WScript.Network" )
On Error Resume Next
wshnetwork.RemovePrinterConnection "\\CompanyServer\LaserJet Marketing", True, False
If Err Then
WScript.Echo "Error " & Err.Number & " while trying to drop LaserJet Marketing"
WScript.Echo "(" & Err.Description & ")"
End If
On Error Goto 0
Set wshNetwork = Nothing
Another useful function is SetDefaultPrinter( ) which, you may have guessed, sets the current user's default printer.
It is available in KiXtart as well as in VBScript.
In NT batch there is no simple way to set the default printer.
It could be done by manipulating the registry, but this isn't recommended.
You may want to consider using prnmngr.vbs -t to set the default printer in a batch file.
Prnmngr.vbs is located in Windows' System32 directory.
If SetDefaultPrinter ( "\\Server\HP LaserJet 4" ) <> 0
"Error @ERROR while trying to set the default printer to HP LaserJet 4@CRLF"
EndIf
Set wshNetwork = CreateObject( "WScript.Network" )
On Error Resume Next
wshnetwork.SetDefaultPrinter "\\Server\HP LaserJet 4"
If Err Then
WScript.Echo "Error " & Err.Number & " while trying to make HP LaserJet 4 the default printer"
WScript.Echo "(" & Err.Description & ")"
End If
On Error Goto 0
Set wshNetwork = Nothing
Though auditing is the preferred way to log access to computers, it does have one disadvantage: you can check on the computer who accessed it and when, but not the other way around.
So what do we do if we want to know which computers were accessed by a particular user?
To efficiently search this information, we need to store it in a central location, we don't want to access each computer's security event log separately.
And how are we going to collect this information?
Since the login script is forced to run each time a user logs in, it is perfectly suited to log each (interactive) access to any computer in the domain.
There are several options:
Depending on the number of users (and logins) I would recommend using a log file per date, or per user per date.
The log files need to be stored in directories per date, on a server where all Authenticated Users have Write permissions.
The directory per date can be created by a scheduled task on the server, but it may be easier and safer to let login script check if it exists and create it if not.
So let's have a look at some code to create a log file per user per day.
In the following code, a log file with the user name is created/used, and the computer name, current date and current time are logged.
If you want to use a single "common" log file for all users per day, make sure you also log the current user name.
:: Strip the leading day of the week from the date
FOR %%A IN (%Date%) DO SET Today=%%A
:: Remove the date delimiters
SET Today=%Today:/=%
SET Today=%Today:-=%
:: Create a directory for today if it does not exist
IF NOT EXIST \\Server\Logs\%Today% MD \\Server\Logs\%Today%
:: Log the computer name and the date and time in a file with the user's name
>> \\Server\Logs\%Today%\%UserName%.log ECHO %ComputerName%,%Date%,%Time%
| Note: | The directories created are not "sortable", i.e. their names depend on the date format used on the computer running the login script. Either force the date format using a group policy, or use one of the SortDate scripts to get today's date in YYYYMMDD format. |
; Get the current date in YYYYMMDD format
$Today = "@YEAR" + Right( "0@MONTHNO", 2 ) + Right( "0@MDAYNO", 2 )
; Create the directory if it doesn't exist
If Exist( "\\Server\Logs\$Today\*.*" ) = 0
MD "\\Server\Logs\$Today"
EndIf
; Log current computer access
If RedirectOutput( "\\Server\Logs\$Today\@USERID.log" ) = 0
"@WKSTA,@DATE,@TIME@CRLF"
$RC = RedirectOutput( "" )
EndIf
Const ForAppending = 8
Const TristateFalse = 0
' Get today's date in YYYYMMDD format and time in HHmmss format
strToday = CStr( 10000 * Year( Now ) + 100 * Month( Now ) + Day( Now ) )
lngNow = 1000000 + 10000 * Hour( Now ) + 100 * Minute( Now ) + Second( Now )
strNow = Right( CStr( lngNow ), 6 )
' Get the current user and computer names
Set wshNetwork = CreateObject( "WScript.Network" )
strUser = wshNetwork.UserName
strComputer = wshNetwork.ComputerName
Set wshNetwork = Nothing
' Create the directory if it doesn't exist
Set objFSO = CreateObject( "Scripting.FileSystemObject" )
With objFSO
strFolder = .BuildPath( "\\Server\Logs", strToday )
If Not .FolderExists( strFolder ) Then
.CreateFolder strFolder
End If
strLog = .BuildPath( strFolder, strUser & ".log" )
Set objLog = .OpenTextFile( strLog, ForAppending, True, TristateFalse )
objLog.WriteLine strComputer & "," & strToday & "," & strNow
objLog.Close
Set objLog = Nothing
End With
Set objFSO = Nothing
Besides the computer name, user name and time of login, you can choose from a long list of properties to add to the login log.
How about logging the IP and MAC addresses?
FOR /F "tokens=1,2 delims=:" %%A IN ('IPCONFIG /ALL ˆ| FIND "Address"') DO (
FOR /F "tokens=1,2" %%C IN ("%%~A") DO FOR %%E IN (%%~B) DO SET %%C%%D=%%E
)
>> \\Server\Logs\%Today%\%UserName%.log ECHO.%IPAddress%,%PhysicalAddress:-=%
| Notes: | (1) | Though this code snippet will usually work, it depends too much on the Windows language and version to be reliable. Use only in an environment with identical Windows installations. |
| (2) | The variable Today should be set before running the code displayed above. |
|
| (3) | Instead of writing the AntiVirus status to a separate line, it is recommended to combine all properties that need to be logged into a single line. |
SETLOCAL ENABLEDELAYEDEXPANSION
SET WMIPath=Path Win32_NetworkAdapter
SET WMIQuery=WHERE "AdapterType LIKE 'Ethernet%%' AND MACAddress>'' AND NOT PNPDeviceID LIKE 'ROOT\\%%'"
FOR /F "tokens=*" %%A IN ('WMIC %WMIPath% %WMIQuery% Get MACAddress /Format:List ^| FIND "="') DO SET %%A
SET WMIPath=Path Win32_NetworkAdapterConfiguration
SET WMIQuery=WHERE "MACAddress='%%MACAddress%%'"
FOR /F "tokens=*" %%A IN ('WMIC %WMIPath% %WMIQuery% Get IPAddress /Format:List ^| FIND "="') DO (
FOR /F "tokens=2 delims==" %%B IN ("%%~A") DO (
IF NOT "%%~B"=="" (
FOR /F "tokens=1 delims={}" %%C IN ("%%~B") DO (
SET IPAddress=!IPAddress!,%%~C
)
)
)
)
>> \\Server\Logs\%Today%\%UserName%.log ECHO.%IPAddress:~1%,%MACAddress::=%
ENDLOCAL
| Notes: | (1) | This code snippet requires Windows XP Professional SP2 or later. |
| (2) | The variable Today should be set before running the code displayed above. |
|
| (3) | Instead of writing these properties to a separate line, it is recommended to combine all properties that need to be logged into a single line. |
; Read the first IP address
$IP = Join( Split( @IPAddress0, " " ), "" )
; Check if there are more, and join them all using semicolons
For $i = 1 To 3
$RC = Execute( "If @@IPAddress$i > '' $$IP = $$IP + Chr(59) + Join( Split( @@IPAddress$i, ' ' ), '' )" )
Next
; Log the results
If RedirectOutput( "\\Server\Logs\$Today\@USERID.log" ) = 0
"$IP,@ADDRESS@CRLF"
$RC = RedirectOutput( "" )
EndIf
| Notes: | (1) | The variable $Today needs to be set before running the code displayed above. |
| (2) | Instead of writing these properties to a separate line, it is recommended to combine all properties that need to be logged into a single line. |
' Query all network adapters that have a MAC address
strQuery = "SELECT * FROM Win32_NetworkAdapterConfiguration WHERE MACAddress > ''"
Set objWMIService = GetObject( "winmgmts://./root/CIMV2" )
Set colItems = objWMIService.ExecQuery( strQuery, "WQL", 48 )
For Each objItem In colItems
If IsArray( objItem.IPAddress ) Then
strIP = strIP & ";" & Join( objItem.IPAddress, ";" )
strMAC = strMAC & ";" & Replace( objItem.MACAddress, ":", "" )
End If
Next
Set colItems = Nothing
Set objWMIService = Nothing
' Log the result
Set objLog = .OpenTextFile( strLog, ForAppending, True, TristateFalse )
objLog.WriteLine Mid( strIP, 2 ) & "," & Mid( strMAC, 2 )
objLog.Close
Set objLog = Nothing
| Notes: | (1) | The variables objFSO and strLog and the constant ForAppending need to be set before running the code snippet displayed above. |
| (2) | Instead of writing these properties to a separate line, it is recommended to combine all properties that need to be logged into a single line. |
Now let's get some more advanced status readings. How about, for example, the status of the AntiVirus software installed?
SET NameSpace=/Namespace:\\root\SecurityCenter
SET AVPath=Path AntiVirusProduct
SET AVProperties=displayNameˆˆ,onAccessScanningEnabledˆˆ,productUptoDateˆˆ,versionNumber
FOR /F "tokens=*" %%A IN ('WMIC %NameSpace% %AVPath% Get %AVProperties% /Format:List ˆ| FIND "="') DO (>NUL SET %%A)
>> \\Server\Logs\%Today%\%UserName%.log ECHO.%displayName%,%versionNumber%,%onAccessScanningEnabled%,%productUptoDate%
| Notes: | (1) | The first 3 lines, setting environment variables, are used to limit the length of the WMIC command line. You are free to integrate them directly into the WMIC command. If you do, replace each set of double carets by a single caret. |
| (2) | The variable Today should be set before running the code displayed above. |
|
| (3) | Instead of writing the AntiVirus status to a separate line, it is recommended to combine all properties that need to be logged into a single line. | |
| (4) | This WMIC command requires Windows XP Professional SP2 or SP3. It will not work in Windows Vista and later. |
; Read the AV software status
$objWMISvc = GetObject( "winmgmts:{impersonationLevel=impersonate}!//./root/SecurityCenter" )
$colItems = $objWMISvc.ExecQuery( "SELECT * FROM AntiVirusProduct", "WQL", 48 )
For Each $objItem In $colItems
$Msg = $objItem.displayName + "," + $objItem.versionNumber
If $objItem.onAccessScanningEnabled = 0
$Msg = $Msg + ",FALSE,"
Else
$Msg = $Msg + ",TRUE,"
EndIf
If $objItem.productUptoDate = 0
$Msg = $Msg + "FALSE@CRLF"
Else
$Msg = $Msg + "TRUE@CRLF"
EndIf
Next
; Log the result
If RedirectOutput( "\\Server\Logs\$Today\@USERID.log" ) = 0
$Msg
$RC = RedirectOutput( "" )
EndIf
| Notes: | (1) | The variable $Today needs to be set before running the code displayed above. |
| (2) | Instead of writing the AntiVirus status to a separate line, it is recommended to combine all properties that need to be logged into a single line. | |
| (3) | This WMIC command requires Windows XP Professional SP2 or SP3. It will not work in Windows Vista and later. |
' Query the AV status
Set objWMISvc = GetObject( "winmgmts:{impersonationLevel=impersonate}!//./root/SecurityCenter" )
Set colItems = objWMISvc.ExecQuery( "SELECT * FROM AntiVirusProduct" )
For Each objItem in colItems
With objItem
strMsg = .displayName & "," & .versionNumber
If .onAccessScanningEnabled Then
strMsg = strMsg & ",TRUE,"
Else
strMsg = strMsg & ",FALSE,"
End If
If .productUptoDate Then
strMsg = strMsg & "TRUE"
Else
strMsg = strMsg & "FALSE"
End If
End With
Next
Set colItems = Nothing
Set objWMISvc = Nothing
' Log the result
Set objLog = .OpenTextFile( strLog, ForAppending, True, TristateFalse )
objLog.WriteLine strMsg
objLog.Close
Set objLog = Nothing
| Notes: | (1) | The variables objFSO and strLog and the constant ForAppending need to be set before running the code snippet displayed above. |
| (2) | Instead of writing the AntiVirus status to a separate line, it is recommended to combine all properties that need to be logged into a single line. | |
| (3) | This WMIC command requires Windows XP Professional SP2 or SP3. It will not work in Windows Vista and later. |
Besides the status of the AntiVirus software, there are more properties that can be useful to log, like the computer's last reboot, hardware properties like CPU type or amount of physical memory, local printers...
Well, you get the idea.
Browse the script samples on this site, or other sites, for more details.
| Warning: | Useful as this may be, you need to limit the number of logged properties, or the login process may take way too much time. Hardware properties could be logged in separate files per computer and limited to one log per week, for example. |
Of course, when you can use login scripts to check settings, why not use it to correct or modify settings?
Many settings can be managed using group policies, but sometimes it may be easier to use an addition to the login script.
Make sure these modifications:
The most common mistake I've seen in login scripts is bloating: too many small additions that add up to a script that takes a quarter of an hour or even more to run.
How many hours of lost productivity every day are acceptable?
Some basic guidelines:
; Get the current date in YYYYMMDD format
$Today = "@YEAR" + Right( "0@MONTHNO", 2 ) + Right( "0@MDAYNO", 2 )
; Create the directory if it doesn't exist
If Exist( "\\Server\Logs\$Today\*.*" ) = 0
MD "\\Server\Logs\$Today"
EndIf
; Log current computer access
If RedirectOutput( "\\Server\Logs\$Today\@USERID.log" ) = 0
"@WKSTA,@USERID,@DATE,@TIME,@PRIV@CRLF"
$RC = RedirectOutput( "" )
EndIf
; Administrators should quit now
If @PRIV = "ADMIN"
Quit 1
EndIf
| Note: | This code won't discriminate between local Administrators or Domain Admins. |
| page last uploaded: 1 May 2013, 13:31 |