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. |
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
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 $_ }
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 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
# 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 ) }
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):
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
# 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" }
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.
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
Remove-SmbMapping -LocalPath 'G:' -Force New-SmbMapping -LocalPath 'G:' -RemotePath "\\CompanyServer\Dept"
Set wshNetwork = CreateObject( "WScript.Network" ) wshNetwork.RemoveNetworkDrive "G:", True wshNetwork.MapNetworkDrive "G:", "\\CompanyServer\Dept" Set wshNetwork = Nothing
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
Add-Printer -ConnectionName "\\CompanyServer\LaserJet Marketing"
Add-Printer -ConnectionName "\\CompanyServer\LaserJet Marketing" -PortName "LPT1:"
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
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.
$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
$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
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, 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.
WMIC Path Win32_Printer Where Name='HP LaserJet 4' Call SetDefaultPrinter IF ERRORLEVEL 1 ( ECHO Failed to make 'HP LaserJet 4' the default printer )
If SetDefaultPrinter ( "\\Server\HP LaserJet 4" ) <> 0 Error @ERROR while trying to set the default printer to HP LaserJet 4@CRLF" EndIf
$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: $_" }
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.
:: 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. |
; 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
# 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
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 these properties 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. |
; 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. |
$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
' 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 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. |
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. |
WMIC.EXE /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get displayName,timestamp /Format:Table >> \\Server\Logs\%Today%\%UserName%.log
; 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. |
; 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
$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
' 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. |
' 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
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
# '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 }
# 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 modified: 2021-11-25; loaded in 0.0863 seconds