narrow default width wide
colour style colour style colour style colour style

Automating Lotus Notes client configuration - Part 1

As I've mentioned in other articles, I'm currently stuck at a client using Lotus Notes. I think Domino is a fine backend mail system/collaboration server but--to be frank--the client blows. I think Lotus/IBM must still be pining for the days of the 3000 line CONFIG.SYS that OS/2 had because they are still using INI files for most of the client configuration. And don't even get me started on the fact that so much data is stored in NSFs on the client instead of on the Domino server.

We did decide to go ahead and setup Notes so all of the client data is out on network file shares. This is for a few reasons:

  1. All of the users' data gets backed up.
  2. Roaming support - a user can logon to any workstation and access their mail
  3. We can setup the users without actually visiting their workstation

One of the many process improvements that we've been able to make is automating the client configuration. I wrote a vbscript that basically does the following to automate the configuration:

  • Prompts for the user to setup
  • Performs a LDAP query against a LDAP-enabled Domino server to get info about that user
  • Finds the user in Active Directory so we can figure out what their file server is
  • Creates the folder structure on the network for the user (\Lotus\Notes\Data)
  • Builds a NOTES.INI file customized for that user
  • Copies the user's ID file to their Data folder
  • Copies a client configuration file with default settings

For this article we are just going to go over the process of querying Domino via LDAP to get the user properties so we can setup all of the files that the client needs in order to set itself up without any user input. I plan to do another article that will go into more detail on how we are querying Active Directory in order to make sure we are creating the files in the right place. (We are using XML to store information that is specific to each of our locations/departments.)

First, the pre-work that we had to do.

  1. Run Lotus Notes in MultiUser mode.
  2. We had to create a Domino account that has access to the user attributes that we need to query for. The only attributes that we really care about for a client setup are: mailserver and mailfile
  3. Make sure that every user's ID file gets put out on a network share somewhere so we know where to copy it from. Our Notes team already does this so this wasn't a big deal. I just had to set some permissions on the folders to make sure that everyone who might run the setup script had access to get to the files.

So let's get started on the code. For now I'm going to leave out all of the Dim statements and scripting objects that are setup at the beginning of the script. Don't worry, I'll post the code in its entirety.

Here's the first bit--prompting for the user to setup. Nothing too complicated:

If strUID = "" Then
 'Inputbox if no ID already Set

 strInputboxTitle  = "Enter Username"
 strInputboxMessage  = "Enter the User ID to setup Lotus Notes For:"
 strUID = InputBox(strInputboxMessage, strInputboxTitle)
 If strUID = "" Then
  Wscript.Echo "No username entered - Process Cancelled"
 End If

End If

I am working on some improvements to this script that will allow us to feed the users from a text file so we can setup batches of users at one time. Just need to work a few kinks out of a couple processes that are currently in place first....

Next, I am setting a bunch of variables that are specific to my client. This is what you will need to modify for your environment:

strLDAPServer  = "SERVER"
strLdapExePath  = "
strLdapUser  = strQuote & "CN=ACCOUNT,OU=IC,O=MYCOMPANY" & strQuote
strLdapPassword = "PASSWORD"
strLdapFilter = "(&(uid=" & strUID & ")(mailserver=*))"
strLdapProps = "mailserver mailfile"
strXMLPath     = "\\SERVER\CommonShare
strSetupSourceFolder  = "\\SERVER\CommonShare
\AdminScripts\NotesSetup" 'Source for setup.txt
strIDSourceFolder  = "
strNotesDataPath   = "\Application Data\LOTUS\Notes\Data"
strLdapCmd = strQuote & strLdapExePath & strQuote & " -v -D " & strLdapUser & " -w " & strLdapPassword & " -h " & strLDAPServer & " " & strQuote & strLdapFilter & strQuote & " " & strLdapProps
'strTextToDisplay = strTextToDisplay & VbCrLf & VbCrLf & strLDAPCmd 
'WScript.Echo strLdapCmd
arrLdapProps  = Split(strLdapProps, " ")

Here's a breakdown of the variables and what they are used for:

  • strLdapServer - This is the name of the Domino LDAP server that the script queries against
  • strLdapExePath - This points to the location of ldapsearch.exe. This is the program that the script shells out to in order to query Domino. I got it from I tried to do a LDAP query using ADO within the vbscript itself but I couldn't get it to authenticate properly and return the attributes I was looking for.
  • strLdapUser - This is the distinguishedName of the Domino user we are using to query against Domino with.
  • strLdapPassword - The password for that Domino account
  • strLdapFilter - This is the filter that we are using in the LDAP query to retrieve the proper user. I added “mailserver=*“ to the filter because my client has some issues with duplicate account names on some accounts that are setup to access our web portal but don't actually use e-mail. This makes sure we get the right account.
  • strLdapProps - Just a list of the attributes we need to retrieve from Domino.
  • strXMLPath - Ignore this for now. I'll get into it in the next article.
  • strSetupSourceFolder - This is the location of the generic client configuration file.
  • strIDSourceFolder - The location of all of the ID files.
  • strNotesDataPath - The folder structure on the network that all of the files will get copied into.
  • strLdapCmd -The command that we are going to shell out and run in order to query Domino.
  • arrLdapProp - This just puts all of the attributes we are looking for into an array for use later on.

Now, let's get some data! I created a subroutine named RunLdapSearch for this:

Sub RunLdapSearch

 Dim strOutput, strOutputTmp, arrOutput, strOutputErr, strOutputLine, intExitCode, strCurrValue, i
 'Run LDAPSearch command and get output
 Set oExec = WshShell.Exec(strLdapCmd)
 Set oStdOut = oExec.StdOut
 Set oStdErr = oExec.StdErr
 Do While oExec.Status = 0
  WScript.Sleep 100
  'Have to get stdOut in chunks so the buffer doesn't overflow
  strOutputTmp = strOutputTmp & oStdOut.ReadAll
 intExitCode = oExec.ExitCode
 'strOutput = oStdOut.ReadAll
 strOutput = strOutputTmp
 strOutputErr = oStdErr.ReadAll
 'Make sure no errors were returned
 If intExitCode = 0 Then
  'WScript.Echo strOutput
  'Build array of each line in the output
  arrOutput = Split(strOutput, VbCrLf)
  For Each strOutputLine In arrOutput
   'Process each line in the output and look for the values we are looking for
   'Go through all props that are being retrieved
   For i = LBound(arrLdapProps) To UBound(arrLdapProps)
    'WScript.Echo arrLdapProps(i) & vbTab & strOutputLine
    'Add to dict if line contains prop we are looking For
    'If InStr(lcase(strOutputLine), arrLdapProps(i) & "=") Then 'Old LDAPSEARCH format
    If InStr(lcase(strOutputLine), arrLdapProps(i) & ": ") Then
     strCurrValue = Mid(strOutputLine, Len(arrLdapProps(i)) + 3)
     'oDict.Add arrLdapProps(i), strOutputLine
     If oDict.Exists(arrLdapProps(i)) = False Then
      oDict.Add arrLdapProps(i), strCurrValue
     End If
    End If
   'Add line with 'matches' to the dictionary
   'If InStr(lcase(strOutputLine), "matches") Then oDict.Add "matches", strOutputLine 'Old LDAPSEARCH format
   If InStr(lcase(strOutputLine), "# numentries:") Then oDict.Add "matches", strOutputLine
  'LDAPSearch command got errors
  WScript.Echo "ERROR: EXITCODE: " & intExitCode & VbCrLf & "Errors: " & strOutputErr & VbCrLf & "Output: " & strOutput
 End If

End Sub

First, we create the wshshell.exec object so we can shell out to run ldapsearch.exe and retrieve the output from it. We store all of the output in a variable named strOutput. Next, we check the exitcode from ldapsearch.exe. If it returned anything other than 0 (success), we popup and error and exit the program.

Here's an example of the output from ldapsearch.exe:

ldap_initialize( ldap://stldiradm01 )
filter: (&(uid=mbroad)(mailserver=*))
requesting: mailserver mailfile
# extended LDIF
# LDAPv3
# base with scope sub
# filter: (&(uid=mbroad)(mailserver=*))
# requesting: mailserver mailfile

# Matt Broadstock, IC, SSMHC
dn: CN=Matt Broadstock,OU=IC,O=SSMHC
mailserver: CN=stlmail03,O=SSMHC
mailfile: mail\mbroad

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

So now we need to split up the output into an array delimited by vbcrlf so we can process each line individually.  I decided to use INSTR to look for the name of each attribute with a colon after it. (i.e. “mailserver:“). So now we just loop through each line and look for our attributes. Once we find one, we store it in a dictionary object. Dictionaries allow us to store our data in an indexed list. In this case we are storing the attribute name as our dictionary 'key' and the data that we found as the 'item'. You will also notice we are stripping off the colon and white space by using MID.

Next, I am calling a sub that takes the items in the dictionary object and puts the data into some variables we can use later on when we build the Notes.ini for this user.

Sub GetProps

 'Get the props that we want from the dictionary
 If oDict.Exists("mailserver") Then strMailServer = oDict.Item("mailserver")
 If oDict.Exists("mailfile") Then strMailFile = oDict.Item("mailfile")
 If oDict.Exists("matches") Then strMatchCount = oDict.Item("matches")
 ' If oDict.Exists("mail") Then WScript.Echo oDict.Item("mail")

End Sub

Now that we have the data from our LDAP query, we can actually setup the Notes client. The following subroutine takes the information that we now have and uses it to create a string that contains everything we are going to put into the Notes.ini for this user.

Sub BuildNotesIniString
 'Setup the string to put into the Notes.ini
 strMailFile = strMailFile & ".nsf"
 strMailServer = Replace(strMailServer, ",", "/")

 strNotesIni = strNotesIni & "[Notes]"           & VbCrLf
 strNotesIni = strNotesIni & "MailFile="  & strMailFile      & VbCrLf
 strNotesIni = strNotesIni & "MailServer="  & strMailServer      & VbCrLf
 strNotesIni = strNotesIni & "KeyFilename=" & strUID & ".id"     & VbCrLf
 strNotesIni = strNotesIni & "ConfigFile=" & "m:\notes\data\setup.txt"   & VbCrLf
 strNotesIni = strNotesIni & "Directory=m:\Notes\Data"       & VbCrLf & VbCrLf

'  Adding the port info here doesn't seem to help...
'  strNotesIni = strNotesIni & "Ports=TCPIP"          & VbCrLf
'  strNotesIni = strNotesIni & "DisabledPorts=LAN0,COM1,SPX,COM2,COM3,COM4,COM5" & VbCrLf
'  strNotesIni = strNotesIni & "WWWDSP_SYNC_BROWSERCACHE=0"      & VbCrLf
'  strNotesIni = strNotesIni & "WWWDSP_PREFETCH_OBJECT=0"       & VbCrLf & VbCrLf
'  strNotesIni = strNotesIni & "LAN0=NETBIOS,0,15,0,,12288,"      & VbCrLf
'  strNotesIni = strNotesIni & "COM1=XPC,1,15,0,,12288,"       & VbCrLf & VbCrLf

End Sub

The next part of our production script does some AD lookups and reads through an XML file to help figure out where on the network we need to put the Notes files for this user. I think we'll leave this part out for now--I plan to do another article on this at some point. So, let's assume that we now know the network location that we need to setup the client files at. Let's go ahead and create the Notes.ini. I created a sub for this as well:

Sub CreateNotesIni
 Dim strNotesIniFolder, oNotesIniFile
 'Create Notes.ini-create folder structure first
 strNotesIniFolder = Left(strNotesIniFile, InStrRev(strNotesIniFile, "\")-1)
 If objFSO.FolderExists(strNotesIniFolder) = False Then
 End If
 Set oNotesIniFile = objFSO.CreateTextFile(strNotesIniFile, True)
 Set oNotesIniFile = Nothing
End Sub

The first thing this sub does is call a separate function that creates the folder structure for this user. I used two generic functions that I wrote that have come in handy in a lot of scripts (listed below). After the folders have been created, it just goes ahead and creates the file and writes the strNotesIni string to it.

Function CreateFolderTree(strFolderPath)
 'Function will create every folder to a specified path-Just parses the string to find each folder needed
 Dim arrFolderPath, intFolderCreate, i, strCurrPath

 arrFolderPath = Split(strFolderPath,"\")
 If Left(strFolderPath, 2) = "\\" Then intFolderCreate = 3 : strCurrPath = "\\"
 For i = LBound(arrFolderPath) To UBound(arrFolderPath)
  'WScript.Echo i & vbTab & arrFolderPath(i)
  If i > 0 Then
   strCurrPath = strCurrPath & "\" & arrFolderPath(i)
   strCurrPath = arrFolderPath(i)
  End If
  If i > intFolderCreate Then MakeFolder(strCurrPath)

End Function

Function MakeFolder(strMakeFolderPath)
 'Function creates a single folder

 On Error Resume Next
 MakeFolder = True
 strMakeFolderPath = UCase(strMakeFolderPath)
 If objFSO.FolderExists(strMakeFolderPath) True Then
  If Err.Number 0 Then 
   MakeFolder = False
  End If
 End If
 On Error Goto 0

End Function

Ok, the hard part is done. We just need to copy the generic client configuration file and the user's ID file to their Data folder. At my client, all of the ID files are out on one network share but each department has a separate subfolder for their users' ID files. So I used a folder recursion routine to go through each subfolder to find the ID file we are looking for. Once we've found it, we just copy it and the generic setup file to their Data folder.

Function CopyOtherFiles

 Dim blnFoundIDFile
 blnFoundIDFile = FindIDFile
 If blnFoundIDFile = True Then
  'Copy Setup.txt and Notes ID file-move to Sub
  'strIDSource  = strIDSourceFolder & "\" & strUID & ".id"
  strIDDest   = strUserSystemFolder & strNotesDataPath & "\" & strUID & ".id"
  strSetupSource = strSetupSourceFolder & "\Setup.txt"
  strSetupDest = strUserSystemFolder & strNotesDataPath & "\Setup.txt"
  objFSO.CopyFile strIDSource, strIDDest, True
  objFSO.CopyFile strSetupSource, strSetupDest, True
  strTextToDisplay = strTextToDisplay & VbCrLf & VbCrLf & "Copied Setup.Txt and Notes ID file..."
  'WScript.Echo "Copied files..."
  CopyOtherFiles = True
  strTextToDisplay = strTextToDisplay & VbCrLf & VbCrLf & "ID File not found-couldn't copy files..."
  'WScript.Echo "ID File not found-couldn't copy files..."
  CopyOtherFiles = False
 End If

End Function

Function FindIDFile

 strTextToDisplay = strTextToDisplay & VbCrLf & VbCrLf & "Finding ID File"
 'WScript.Echo "finding ID"

 'If objFSO.FileExists(oIDSourceFolder & "\" &

 Dim oIDSourceFolder
 Set oIDSourceFolder = objFSO.GetFolder(strIDSourceFolder)

 Dim starttime : starttime = Timer
 'WScript.Echo Now
 'strID2 = Recurse(oIDSourceFolder, strUID & ".id")
 Recurse oIDSourceFolder, strUID & ".id"
 strTextToDisplay = strTextToDisplay & VbCrLf & VbCrLf & "ID File: " & strIDSource & VbCrLf & _
  Now & vbTab & Int(Timer - starttime) & " seconds"
 'WScript.Echo "ID File: " & strIDSource & VbCrLf & Now & vbTab & Int(Timer - starttime) & " seconds"
 If strIDSource "" Then
  'WScript.Echo "FOUND IT"
  FindIDFile = True
  FindIDFile = False
 End If

End Function

Function Recurse(oFolder, strFile)

 On Error Resume Next
 dim subfolders,files,folder,file

 If strIDSource "" Then Exit Function

 Set subfolders = oFolder.SubFolders
 Set files = oFolder.files

 For Each file in files
  If lcase( = LCase(strFile) Then 
   strIDSource = file.path
   Exit Function
  End If

 'Recurse all of the subfolders.
 For Each folder in subfolders
  Recurse folder, strFile

 Set subfolders = Nothing
 Set files = Nothing
 On Error Goto 0

End Function

Ta da! All of the files that the user needs to run Notes are now setup. You still need to make sure that the registry setting for each user under “HKEY_CURRENT_USER\Software\Lotus\Notes\6.0\[NotesIniPath]” gets set to point to their Notes.ini. Sounds like a topic for another article. Hopefully this isn't too hard to follow. I'm just starting out posting articles and I'm trying to find a format that works. If you have any feedback, please let me know!

Here's a link to the full code that we are using in production. It does quite a bit more than the snippets I included in this article.