Tag Archives: active directory

An alternative logon script

Logon scripts can over time get complex and messy. Here’s an example of an alternative script that is a “one for all” script that can be used across a whole enterprise. The script uses group membership and the location of the user account in AD to work out which drive mappings and welcome window to give to the user.

Let’s assume there are two sites in the company to keep it simple, one in London and another in New York.

The OU structure is as follows:

John wants the following drives mapped:

P: drive mapped to \\nyserver1\Public
M: drive mapped to \\nyserver2\IT

Sarah wants the following drives mapped:

P: drive mapped to \\nyserver1\Public
M: drive mapped to \\lonserver2\IT

We create three groups:

A group named DriveMapping-nyserver1-Public and add both Sarah and John
The group description is set as: P,\\nyserver1.acme.corp\public
A group named DriveMapping-nyserver2-IT and add John
The group description is set as: M,\\nyserver2.acme.corp\IT
A group named DriveMapping-lonserver2-IT and add Sarah
The group description is set as: M,\\lonserver2.acme.corp\IT

On \\acme.corp\netlogon\images we save an image for each of the sites, London and New York: London.jpg and New York.jpg. These are used in the logon welcome window. There’s also one named default.jpg in case the script can’t work out which site they’re from.

Now this is how the script works:

  • When the user logs in the script runs
  • The script works out who that user is (their logon name)
  • The script queries active directory and looks at their user account
  • It looks at the location of the username in active directory and works out what site they belong to, i.e. Sarah is in London
  • It uses this information to select the correct welcome screen image
  • The welcome screen is then shown with this site-specific image
  • It then proceeds to start mapping drives. From the user account it gets the list of groups
  • If the group name they’re a member of matches the name “DriveMappings-*” then it looks at the description of the group and uses that information to map the drive
  • It does this for each group that the user is a member of
  • Once completed it waits 20 seconds and then closes automatically

The benefits of doing it this way?

  1. You don’t need to edit the logon script to map a new drive for a user. Either add them to an existing group or create a new one with the correct name (DriveMapping-lonserver1-IT for instance) and the description field filled out.
  2. It’s easy to add and remove users to groups to make sure they get the drives mapped they want
  3. You can see, just by looking at their group membership what drives they are getting mapped.
  4. You can create one logon script and give it to everyone in the company!
  5. It still works when you’re connecting remotely from offsite

Here’s the script:

'================================================================================
'=                                                                                =
'= NAME:             logon.vbs                                                    =
'=                                                                                =
'= DESCRIPTION:        Map user drives based on group membership                    =
'=                                                                                =
'= VERSION HISTORY:                                                                =
'=                                                                                =
'=    1.0        02/06/2011    Stuart Jordan    Script rewritten with specific per        =
'=                                        drive groups only used for mapping        =
'=                                                                                =
'================================================================================

'Don't prompt if there are any errors. Users don't want to see these!
On Error Resume Next

Dim WshNetwork, adsPath,strUser, strMappedDrives
Const ADS_READONLY_SERVER = 4
'This is the start path from which we look for the object in LDAP
strLDAPPath="dc=acme,dc=corp"
strDomainName="ACME"

'This is the drive letter we're using for the user's homedrive
strHomedriveDriveLetter="H"
'This just contains a list of drives mapped to be outputted to the screen
strMappedDrives="Drives mapped: "
'This is how long the IE window stays open in seconds after all the drives are mapped
intCloseWait=20

'This records the time at the start of the script
'We'll check at the end and then output the difference
startTime=Timer()

'Quit the logon script if we've logged onto a server
if isServer(".") then
    wscript.quit
end if

'Get the Username of the person logging in
Set WSHNetwork = WScript.CreateObject("WScript.Network")
strUser = ""
While strUser = ""
    strUser = UCase(WSHNetwork.UserName)
Wend

'Get the user object from Active Directory.
adsPath = "WinNT://" & strDomainName & "/" & strUser
Set dso = GetObject("WinNT:")
Set objUser = dso.OpenDSObject(adsPath,"","", ADS_READONLY_SERVER)

'Set the logon script image as the one that matches the user's
'Home site, e.g. London. We get this from the OU the user account
'is in. Move the user account and this will change.
strLogonScriptImage=getHomeSiteFromUserName(strUser) & ".jpg"

'Create a new internet explorer object
set InternetExplorer = new IE
'Create the Internet explorer window
InternetExplorer.Initialize
'Write something in the first line below the image
InternetExplorer.SetMessageLine1 "You are logged on as " & strDomainName & "\" & strUser

'For each group the user is a member of
For Each Group In objUser.groups
    'Check to see if this group is a drive mapping group
    if string_compare("DriveMappings-",Group.Name) then
        'If it is then lets process it.

        'The description field is of the form A,\\server\share
        'where A is the drive letter, server and share the destination
        'mapping. Split creates an array using a separator, in this case
        'a comma.
        arrDescField=split(Group.Description,",")

        'The first cell contains the drive letter to be mapped
        strTargetDrive=arrDescField(0) & ":"

        'The second cell contains the share to be mapped
        strTargetPath=arrDescField(1)

        'Check to see whether the drive specified is out homedrive letter (i.e. U)
        if string_compare(strHomedriveDriveLetter,strTargetDrive) then
            'Treat U drive as a special case as it's specific to the user logging in
            InternetExplorer.SetMessageLine3 "Drive " & strTargetDrive & " will be mapped to " & strTargetPath & strUser
            'Actually map the drive using the information we collected
            MapDrive strTargetDrive,strTargetPath & "\" & strUser
        'If it's not then process as normal
        else
            InternetExplorer.SetMessageLine3 "Drive " & strTargetDrive & " will be mapped to " & strTargetPath
            'Actually map the drive using the information we collected
            MapDrive strTargetDrive,strTargetPath
        end if
        InternetExplorer.SetMessageLine2 strMappedDrives
    end if
Next

'Get the time at this point
EndTime=Timer()
'Work out the durating between EndTime and StartTime in milliseconds
Duration=int((EndTime-StartTime)*1000)
'Output a completion status on line 3. Note the double minus in calculating the duration.
'This is so that we always round the value up so we don't return 0 seconds. Neat huh?
InternetExplorer.SetMessageLine3 "Completed in " & -int(-Duration/1000) & " seconds."

'This bit waits for a set period before closing the window automatically
do while intCloseWait > 0
    'A countdown appears on messageline4. When it reaches 0 seconds the "do while" exits
    InternetExplorer.SetMessageLine4 "This screen will close automatically in " & intCloseWait & " seconds."
    wscript.sleep 1000
    intCloseWait = intCloseWait - 1
loop

'Now exit IE
InternetExplorer.quit
'And clear our InternetExplorer object
set InternetExplorer=nothing
'Quit the script
wscript.quit

'==========================================================================
'=    Subs and functions

'This class object is used to create and manipulate our Internet Explorer window
class IE
    'These are our variables but since they're private we can only change them
    'by calling PUBLIC functions within the class
    private m_MessageLine1,m_MessageLine2,m_MessageLine3,m_MessageLine4
    private m_IE, m_objDocument

    'These public functions allow us to update the m_messageline1 variable which contains the
    'output in the IE window on messageline1
    public function SetMessageLine1(strNewMessageText)
        m_MessageLine1=strNewMessageText
        UpdateMessageLine1()
    end function

    public function SetMessageLine2(strNewMessageText)
        m_MessageLine2=strNewMessageText
        UpdateMessageLine2()
    end function

    public function SetMessageLine3(strNewMessageText)
        m_MessageLine3=strNewMessageText
        UpdateMessageLine3()
    end function

    public function SetMessageLine4(strNewMessageText)
        m_MessageLine4=strNewMessageText
        UpdateMessageLine4()
    end function

    'Private functions to update the variables
    private function UpdateMessageLine1()
        m_IE.document.all.MessageLine1.innerHTML=m_MessageLine1
    end function

    private function UpdateMessageLine2()
        m_IE.document.all.MessageLine2.innerHTML=m_MessageLine2
    end function

    private function UpdateMessageLine3()
        m_IE.document.all.MessageLine3.innerHTML=m_MessageLine3
    end function

    private function UpdateMessageLine4()
        m_IE.document.all.MessageLine4.innerHTML=m_MessageLine4
    end function

    'This is a public sub that creates the IE window inside which the HTML page will be shown
    public Sub initialize()
      On Error Resume Next
      Set m_IE = CreateObject("InternetExplorer.Application")
      With m_IE
        'These are the attributes of the window created by IE.
        .navigate "about:blank"
        .visible= True
        .resizable=0
        'Height of the IE window
        .height=543
        'Width of the IE window
        .width=620
        .toolbar=0
        .statusbar=0
        .menubar=0
        .visible=1
        'Position these number of pixels in from the top left of the screen
        .Left=20
        .Top=20
        .document.parentWindow.opener = "me"
      End With
      Do while m_IE.Busy
        ' wait for page to load
        Wscript.Sleep 100
      Loop
      'Call the private sub to load the html content in the window
      loadHTML()
    End Sub

    'This sub forms the HTML page shown in the IE window shown during logon
    private sub loadHTML()
        Set m_objDocument = m_IE.document
        m_objDocument.Open
        m_objDocument.Writeln "<html>"
        m_objDocument.Writeln "<body SCROLL='no' onclick=self.close() leftmargin='0' marginheight='0' marginwidth='0' topmargin='0'>"
        m_objDocument.Writeln "<table width='620' border='0' cellspacing='0' cellpadding='0'>"
        m_objDocument.Writeln "<tr>"
        'This is the image that's loaded in the IE window. Note that it loads an image that is particular to the site that
        'the user account is found in (or more strictly the branch OU)
        m_objDocument.Writeln "<td><img src='file://acme.corp/netlogon/images/" & strLogonScriptImage & "' alt='' height='457' width='620' border='0'></td>"
        m_objDocument.Writeln "</tr>"
        m_objDocument.Writeln "</table>"
        m_objDocument.Writeln "<table width='620' border='0' cellspacing='0' cellpadding='0'>"
        'These are the message lines referenced above when were outputting text to the IE window
        m_objDocument.Writeln "<tr><td><font face='Verdana' size='1' ID='MessageLine1'></font></td></tr>"
        m_objDocument.Writeln "<tr><td><font face='Verdana' size='1' ID='MessageLine2'></font></td></tr>"
        m_objDocument.Writeln "<tr><td><font face='Verdana' size='1' ID='MessageLine3'></font></td></tr>"
        m_objDocument.Writeln "<tr><td><font face='Verdana' color='blue' size='1' ID='MessageLine4'></font></td></tr>"
        m_objDocument.Writeln "</table>"
        m_objDocument.Writeln "</html>"
        m_objDocument.Close 
    end sub

    public sub quit()
        m_IE.quit
    end sub
end class

'Actually maps the network drives
Sub MapDrive(strDrive,strShare)
    On Error Resume Next
    WSHNetwork.MapNetworkDrive strDrive, strShare
    If Err.Number Then
        'wscript.echo Err.Number & " so removing drive first"
        err.clear
        WSHNetwork.RemoveNetworkDrive strDrive
        WSHNetwork.MapNetworkDrive strDrive, strShare
        If Err.Number Then
            'wscript.echo "Removing drive still failed with error " & Err.Number & ":" & Err.Description
        end if
    End If
    strMappedDrives = strMappedDrives & strDrive & " "
End Sub

'Checks to see if the machine we're running the logon script on is a "server" because
'we don't really want to map drives automatically on servers.
function isServer(strtmpComputer) 
    boolReturn=0
    on error resume next
    ' WMI connection to Root CIM
    Set objWMIService = GetObject("winmgmts:\\" & strtmpComputer & "\root\cimv2")
    'Get list of items (there is only one in this case)
    Set colItems = objWMIService.ExecQuery("Select DomainRole from Win32_ComputerSystem")
    for each objComputer in colItems
        'This returns a number depending on the role in the domain
        intRole=objComputer.DomainRole
        '0 (0x0) standalone Workstation
        '1 (0x1) Member Workstation
        '2 (0x2) Standalone Server
        '3 (0x3) Member Server
        '4 (0x4) Backup Domain Controller
        '5 (0x5) Primary Domain Controller
    next
    'If the system is 2,3,4 or 5 then it is a server. Otherwise we'll assume it's a workstation.
    select case intRole
        case 2,3,4,5
            boolReturn=1
        case else
            boolreturn=0
    end select

    isServer = boolReturn

end function

'This is used to check is a string is found inside another
'It's a bit like strcmp but works for regex and where
'the expression is found at the start of the string.
private function string_compare(expression,targetstring)
    set oreg= new regexp
    oReg.pattern=expression
    oReg.IgnoreCase = TRUE
    if ("" = expression OR "" = targetstring) then
        boolSearchResult=0
    end if
    if oReg.test (targetstring) then
        boolSearchResult=1
    else
        boolSearchResult=0
    end if
    string_compare=boolSearchResult
end function

'Do a LDAP lookup to find the location of the user account
'in active directory. Used to find the home site of the 
'particular user.
function getCanonicalNameFromUsername(strSamAccountName)
    dim strReturn,arrCN

    strSamAccountName=LCase(strSamAccountName)
    'Setup for the LDAP connection
    Set objConnection = CreateObject("ADODB.Connection")
    objConnection.Open "Provider=ADsDSOObject;"
    Set objCommand = CreateObject("ADODB.Command")
    objCommand.ActiveConnection = objConnection

    'Let's find the person
    strType="User"
    objCommand.CommandText = "<LDAP://" & strLDAPPath & ">;(&(objectCategory=" & strType & ")" & _
                       "(samAccountName=" & strSamAccountName & "));canonicalName;subtree"  
    'Run the query
    Set objRecordSet = objCommand.Execute

    if objRecordset.RecordCount = 1 then
        'For some reason the canonicalName object is returned as an array
        'with the first array cell containing the actual canonicalName
        arrCN=objRecordset.Fields("canonicalName")
        'Ubound is the reference of the last cell in the array (zero in this case)
        'but added for completeness
        strReturn=arrCN(Ubound(arrCN))
        'wscript.echo "Found " & strSamAccountName & " at " & strReturn
    else 
        wscript.echo "Failed to find " & strSamAccountName
    end if

    getCanonicalNameFromUsername=strReturn
end function

'Uses the canonicalName to work out the home site of the user so
'that the correct image can be shown in the logon script
'window
function getHomeSiteFromUserName(strtmpUsername)
    dim strReturn
    'The site name is contained in the third set of / slashes
    'acme.corp/Europe/London/OU1/OU2/username

    'So let's split the canonicalName using the /'s with an
    'level per cell
    arrCN=split(getCanonicalNameFromUsername(strtmpUsername),"/")
    if Ubound(arrCN) > 1 then
        strReturn=arrCN(2)
    else
        'Let's default to default if we can't work out what site the
        'user account is in
        strReturn="default"
    end if
    getHomeSiteFromUserName=strReturn
end function

Download (rename to a zip)

Tagged , , , ,