Rob van der Woude's Scripting Pages
Powered by GeSHi

Tips and Code Snippets for

Login Scripts

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 may be a viable option to use for login scripts, but remember it isn't installed by default on Windows XP and older versions, and even if installed, its settings for running scripts needs to be properly configured (not the default settings).
Besides, some major "breaking" changes were introduced in PowerShell 6 (e.g. Get-WmiObject was replaced by Get-CimInstance), so you may need to check the PowerShell version on the computer running the script, and supply code for PowerShell 2..5 as well as for 6..7.

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 and PowerShell snippets.
I will also assume that all workstations run Windows 2000 or a more recent Windows version.

For your convenience, you can hide or reveal the code snippets for any of these languages by using the buttons below.

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 may not be installed by default in these Windows versions
  c. an older version of Windows Script Host may 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 extension instead of .BAT
  e. A PowerShell script requires PowerShell to be installed, execution of PowerShell scripts to be allowed by the execution policy, and the script's code to be compatible with the PowerShell version installed.

1. Network Drives

Connect network drives

Batch: connect network drives

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:
)

KiXtart: connect network drives

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

PowerShell: connect network drives

try {
	New-SmbMapping -LocalPath 'G:' -RemotePath '\\CompanyServer\Dept'
}
catch {
	Write-Host 'Error mapping drive G:'
	Write-Host $_
}
 
try {
	New-SmbMapping -LocalPath 'H:' -RemotePath "\\CompanyServer\$Env:UserName"
}
catch {
	Write-Host 'Error mapping drive H:'
	Write-Host $_
}

VBScript: connect network drives

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.

KiXtart: connect drives with logging

; 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 RedirectOutput( "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

PowerShell: connect drives with logging

# It doesn't hurt to make sure the C:\temp folder exists
if ( !( Test-Path -Path 'C:\Temp' -PathType 'Container' ) ) {
	New-Item -Path 'C:\' -Name 'Temp' -ItemType 'directory'
}
 
# Delete an existing log file if necessary
if ( Test-Path -Path 'C:\Temp\login.log' -PathType 'Any' ) {
	Remove-Item -LiteralPath 'C:\Temp\login.log' -Force
}
 
# Start with a clean slate
$Error.Clear( )
 
# Map drive G: to the department share
try {
	New-SmbMapping -LocalPath 'G:' -RemotePath '\\CompanyServer\Dept'
}
catch {
	"Error mapping drive G:`n$_" | Out-File -FilePath 'C:\Temp\login.log' -Append
}
 
# Map drive H: to the user's home share
try {
	New-SmbMapping -LocalPath 'H:' -RemotePath "\\CompanyServer\$Env:UserName"
}
catch {
	"Error mapping drive H:`n$_" | Out-File -FilePath 'C:\Temp\login.log' -Append
}
 
# List all mappings
Get-SmbMapping | Out-File -FilePath 'C:\Temp\login.log' -Append
 
# Warn the user if (an) error(s) occurred
if ( $Error ) {
	$Msg  = "Errors occurred during login.`n"
	$Msg += "The errors are logged to be reviewed by the helpdesk staff.`n"
	$Msg += "Please notify the helpdesk."
	[void] [System.Windows.MessageBox]::Show( $Msg, "Login Error", "OK", "Warning" )
	$Host.SetShouldExit( -1 )
}

VBScript: connect drives with logging

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."
	MsgBox strMsg, "Login Error", 64
End If
 
Set objFSO     = Nothing
Set wshNetwork = Nothing

Often network drives are mapped based on group membership (or, for AD domains, on OU):

Batch: connect drives based on group membership

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!

KiXtart: connect drives based on group membership

If InGroup( "Marketing" )
	USE M: "\\CompanyServer\Marketing"
EndIf

PowerShell: connect drives based on group membership

# Local group
# Source: https://morgantechspace.com/2017/10/check-if-user-is-member-of-local-group-powershell.html
$groupObj = [ADSI]"WinNT://./Administrators,group"
$membersObj = @( $groupObj.psbase.Invoke( "Members" ) )
$members = ( $membersObj | ForEach-Object { $_.GetType( ).InvokeMember( 'Name', 'GetProperty', $null, $_, $null ) } )
If ( $members -contains $Env:UserName ) {
	New-SmbMapping -LocalPath 'T:' -RemotePath "\\CompanyServer\AdminTools"
}
 
# AD group, use "Import-Module ActiveDirectory" once
# Source: https://morgantechspace.com/2015/07/powershell-check-if-ad-user-is-member-of-group.html
$members = Get-ADGroupMember -Identity 'Marketing' -Recursive | Select -ExpandProperty Name
If ( $members -contains $Env:UserName ) {
	New-SmbMapping -LocalPath 'M:' -RemotePath "\\CompanyServer\Marketing"
}

VBScript: connect drives based on group membership

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
 
If blnMember Then
	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
End If

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.

Disconnect network drives

If users are allowed to map their own drives, you may want to consider disconnecting drives before mapping them:

Batch: reconnect drives

NET USE G: /DELETE /Y
NET USE G: \\CompanyServer\Dept /PERSISTENT:No

KiXtart: reconnect drives

USE G: /DELETE
USE G: "\\CompanyServer\Dept"

PowerShell: reconnect drives

Remove-SmbMapping -LocalPath 'G:' -Force
New-SmbMapping -LocalPath 'G:' -RemotePath "\\CompanyServer\Dept"

VBScript: reconnect drives

Set wshNetwork = CreateObject( "WScript.Network" )
wshNetwork.RemoveNetworkDrive "G:", True
wshNetwork.MapNetworkDrive    "G:", "\\CompanyServer\Dept"
Set wshNetwork = Nothing

2. Network Printers

Connect network printers

Batch: connect DOS style network printers

NET USE LPT1 \\Server\HPLJ4 /PERSISTENT:No
IF ERRORLEVEL 1 (
	ECHO Error connecting printer HP LaserJet 4
)

KiXtart: connect network printers

If Not AddPrinterConnection( "\\CompanyServer\LaserJet Marketing" ) = 0
	"Error @ERROR while trying to connect to LaserJet Marketing@CRLF"
EndIf

KiXtart: connect DOS style network printers

USE LPT1: "\\Server\HPLJ4"
If @ERROR <> 0
	"Error @ERROR while trying to connect to HPLJ4@CRLF"
EndIf

PowerShell: connect network printers

Add-Printer -ConnectionName "\\CompanyServer\LaserJet Marketing"

PowerShell: connect DOS style network printers

Add-Printer -ConnectionName "\\CompanyServer\LaserJet Marketing" -PortName "LPT1:"

VBScript: connect network printers

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

VBScript: connect DOS style network printers

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.

Disconnect network printers

Disconnecting network printers is much like disconnecting network drives:

Batch: disconnect DOS style printers

NET USE LPT1 /DELETE /Y
IF ERRORLEVEL 1 (
	ECHO Error disconnecting printer port LPT1
)

KiXtart: disconnect printers

If Not DelPrinterConnection( "\\CompanyServer\LaserJet Marketing" ) = 0
	"Error @ERROR while trying to drop LaserJet Marketing@CRLF"
EndIf

KiXtart: disconnect DOS style printers

USE LPT1: /DELETE
If @ERROR <> 0
	"Error @ERROR while trying to drop LPT1@CRLF"
EndIf

In PowerShell, removing printers should be straightforward, but tests on my own computer revealed that the commands for removal are not always reliable.
You may want to check afterwards if the printer is really removed.

PowerShell: disconnect network printers

$oldErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
Get-Printer -Name 'LaserJet Marketing' | Remove-Printer
# Check if removal succeeded
if ( Get-Printer -Name 'LaserJet marketing' ) {
	Write-Host "Failed to remove 'LaserJet Marketing' printer"
}
$ErrorActionPreference = $oldErrorActionPreference

PowerShell: disconnect DOS style printers

$oldErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
# Remove the printer first, then remove the printerport
Get-Printer -Name 'LaserJet Marketing' | Remove-Printer
Get-PrinterPort -Name 'LPT1:' | Remove-PrinterPort
$ErrorActionPreference = $oldErrorActionPreference

VBScript: disconnect all printer types

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

Set the default printer

Another useful function is SetDefaultPrinter( ) which, you may have guessed, sets the current user's default printer.
It is available in KiXtart, VBScript and in the Win32_Printer class in WMI.

In NT batch you can use WMIC to set the default printer.
This requires Windows XP Professional or later.
On older systems it could also be done by manipulating the registry, but that is not recommended.
You may also consider using prnmngr.vbs -t to set the default printer in a batch file.
prnmngr.vbs is located in Windows' System32 directory.

Batch: set default printer

WMIC Path Win32_Printer Where Name='HP LaserJet 4' Call SetDefaultPrinter
IF ERRORLEVEL 1 (
	ECHO Failed to make 'HP LaserJet 4' the default printer
)

KiXtart: set default printer

If SetDefaultPrinter ( "\\Server\HP LaserJet 4" ) <> 0
	Error @ERROR while trying to set the default printer to HP LaserJet 4@CRLF"
EndIf

PowerShell: set default printer

$Error.Clear( )
$OldProgressPreference = $ProgressPreference
$ProgressPreference = "SilentlyContinue"
# Cmdlet for WMI queries changed in PowerShell version 6
# Use 'ShareName' instead of 'Name' for network printers
if ( $PSVersionTable.PSVersion.Major -lt 6 ) {
	Get-WmiObject -Class Win32_Printer -Filter "Name='HP LaserJet 4'" | Invoke-WmiMethod -Name SetDefaultPrinter
} else {
	Get-CimInstance -ClassName Win32_Printer -Filter "Name='HP LaserJet 4'" | Invoke-CimMethod -Name 'SetDefaultPrinter'
}
$ProgressPreference = $OldProgressPreference
if ( $Error ) {
	Write-Host "Failed to make 'HP LaserJet 4' the default printer"
	Write-Host "Error: $_"
}

VBScript: set default printer

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

3. Log Computer Access

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:

  1. a single log file containing all login information of all users on all computers for every date
  2. a log file per user
  3. a log file per computer (less practical)
  4. a log file per date
  5. any combination of the options 2..4

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.

Batch: log computer access

:: Get current date in YYYYMMDD format if possible (XP Professional or later)
FOR /F "skip=1 tokens=1-3" %%A IN ('WMIC Path Win32_LocalTime Get Day^,Month^,Year /Format:Table') DO (
	SET /A Today = 10000 * %%C + 100 * %%B + %%A
)
IF ERRORLEVEL 1 SET Today=
:: In case WMIC did not get the "sorted" date we'll have to get an "unsorted" date in regional date format
IF "%Today%"=="" (
	REM Strip the leading day of the week from the date
	FOR %%A IN (%Date%) DO SET Today=%%A
	REM 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: In a mixed environment (i.e. several Windows versions, including older ones), to make absolutely sure the directories created will be "sortable", either force the date format using a group policy, or use one of the SortDate scripts to get today's date in YYYYMMDD format.

KiXtart: log computer access

; 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

PowerShell: log computer access

# Get today's date in YYYYMMDD format and time in HHmmss format
$Today = Get-Date -Format 'yyyyMMdd'
$Now   = Get-Date -Format 'HHmmss'
# Create the directory if it doesn't exist
if ( !( Test-Path "\\Server\Logs\$Today" -PathType Container ) ) {
	New-Item -Path "\\Server\Logs" -Name $Today -ItemType "directory"
}
# Log current computer access
"$Env:ComputerName,$Today,$Now\n" | Out-File -FilePath "\\Server\Logs\$Today\$Env:UserName.log" -Encoding ASCII

VBScript: log computer access

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 &amp; ".log" )
	Set objLog = .OpenTextFile( strLog, ForAppending, True, TristateFalse )
	objLog.WriteLine strComputer & "," & strToday & "," & strNow
	objLog.Close
	Set objLog = Nothing
End With
Set objFSO = Nothing

4. Log Computer Status

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?

Log IP and MAC addresses

Batch: log IP and MAC address (single adapter)

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 these properties to a separate line, it is recommended to combine all properties that need to be logged into a single line.

Batch: log IP and MAC addresses (single adapter, XP Pro SP2 or later)

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.

KiXtart: log IP and MAC addresses

; 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
 
; 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) 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.

PowerShell: log IP and MAC addresses

$MACAddress = ( Get-NetAdapter | Where-Object -Property Status -eq Up | Select-Object -First 1 ).MacAddress
$IPAddress  = ( Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias Ethernet | Select-Object -First 1 ).IPAddress
$Today      = Get-Date -Format 'yyyyMMdd'
# Append IP and MAC adresses to log file
"$IPAddress,$MACAddress\n" | Out-File -FilePath "\\Server\Logs\$Today\$Env:UserName.log" -Encoding ASCII -Append

VBScript: log IP and MAC addresses

' Query all network adapters that have a MAC address
strQuery = "SELECT * FROM Win32_NetworkAdapterConfiguration WHERE MACAddress &gt; ''"
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 objFSO = CreateObject( "Scripting.FileSystemObject" )
Set objLog = objFSO.OpenTextFile( strLog, ForAppending, True, TristateFalse )
objLog.WriteLine Mid( strIP, 2 ) & "," & Mid( strMAC, 2 )
objLog.Close
Set objLog = Nothing
Set objFSO = Nothing
Notes: (1) The variable 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.

Log AntiVirus status

Now let's get some more advanced status readings. How about, for example, the status of the AntiVirus software installed?

Batch: log AntiVirus status (Windows XP SP2/SP3 only)

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.

Batch: log AntiVirus status (Windows 7 and later)

WMIC.EXE /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get displayName,timestamp /Format:Table >> \\Server\Logs\%Today%\%UserName%.log

KiXtart: log AntiVirus status (Windows XP SP2/SP3 only)

; 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
 
; 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) 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.
  (2) This WMIC command requires Windows XP Professional SP2 or SP3. It will not work in Windows Vista and later.

KiXtart: log AntiVirus status (Windows 7 and later)

; 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
 
; Read the AV software status
$objWMISvc = GetObject( "winmgmts:{impersonationLevel=impersonate}!//./root/SecurityCenter2" )
$colItems  = $objWMISvc.ExecQuery( "SELECT * FROM AntiVirusProduct", "WQL", 48 )
For Each $objItem In $colItems
	$Msg = $objItem.displayName + "," + $objItem.timestamp
Next
; Log the result
If RedirectOutput( "\\Server\Logs\$Today\@USERID.log" ) = 0
	$Msg
	$RC = RedirectOutput( "" )
EndIf

PowerShell: log AntiVirus status (Windows 7 and later)

$Today = Get-Date -Format 'yyyyMMdd'
# Append AV program name(s) and timestamp(s) to log file
$AV1 = ( Get-WmiObject -Class AntiVirusProduct -Namespace 'root\SecurityCenter2' )
$AV2 = ( $AV1 | Format-Table -Property displayName,timestamp )
$AV2 | Out-File -FilePath "\\Server\Logs\$Today\$Env:UserName.log" -Encoding ASCII -Append
# The 3 lines above may be joined into a single line

VBScript: log AntiVirus status (Windows XP SP2/SP3 only)

' 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; variable 'strLog' and constant 'ForAppending' need to be set before
Set objFSO = CreateObject( "Scripting.FileSystemObject" )
Set objLog = objFSO.OpenTextFile( strLog, ForAppending, True, TristateFalse )
objLog.WriteLine strMsg
objLog.Close
Set objLog = Nothing
Set objFSO = 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.

VBScript: log AntiVirus status (Windows 7 and later)

' Query the AV status
Set objWMISvc = GetObject( "winmgmts:{impersonationLevel=impersonate}!//./root/SecurityCenter2" )
Set colItems  = objWMISvc.ExecQuery( "SELECT * FROM AntiVirusProduct" )
For Each objItem in colItems
	strMsg = strMsg & objItem.displayName & "," & objItem.versionNumber & vbCrLf
Next
Set colItems  = Nothing
Set objWMISvc = Nothing
' Log the result; variable 'strLog' and constant 'ForAppending' need to be set before
Set objFSO = CreateObject( "Scripting.FileSystemObject" )
Set objLog = objFSO.OpenTextFile( strLog, ForAppending, True, TristateFalse )
objLog.WriteLine strMsg
objLog.Close
Set objLog = Nothing
Set objFSO = Nothing

More Properties to Log

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.

5. Update user or computer settings

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:

6. Tips and best practices to prevent common mistakes

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:

 

KiXtart: abort if user is Administrator

; 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

PowerShell: abort if user is Administrator

# 'S-1-5-32-544' is the SID of the local 'Administrators' group.
# The groups 'Domain Admins' and 'Enterprise Admins' are members of the local
# 'Administrators' group if the computer is connected to an AD domain.
if ( [Security.Principal.WindowsIdentity]::GetCurrent( ).Groups -contains 'S-1-5-32-544' ) {
	Write-Error "This login script must NOT be executed by members of the Administrators group." -ErrorAction Stop
}

PowerShell: abort on Terminal Server

# S-1-5-13     = Terminal Server Users
# S-1-5-14     = Remote Interactive Logon
# S-1-5-32-555 = Remote Desktop Users
# See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab for a list of well-known SIDs
if ( [bool][Security.Principal.WindowsIdentity]::GetCurrent( ).Groups -match 'S-1-5-13' -or 'S-1-5-14' -or '1-5-32-555' ) {
	Write-Error "This login script must NOT be executed by Terminal Server or Remote Desktop users." -ErrorAction Stop
}
 

page last uploaded: 2021-11-24, 18:40