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:
- All of the users' data gets backed up.
- Roaming support - a user can logon to any workstation and access their mail
- 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.
- Run Lotus Notes in MultiUser mode.
- 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
- 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 SetstrInputboxTitle = "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"
WScript.Quit
End IfEnd 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 = "\\SERVER\CommonShare\AdminScripts\NotesSetup\ldapsearch.exe"
strLdapUser = strQuote & "CN=ACCOUNT,OU=IC,O=MYCOMPANY" & strQuote
strLdapPassword = "PASSWORD"
strLdapFilter = "(&(uid=" & strUID & ")(mailserver=*))"
strLdapProps = "mailserver mailfile"
strXMLPath = "\\SERVER\CommonShare\AdminScripts\EntityInfo.xml"
strSetupSourceFolder = "\\SERVER\CommonShare\AdminScripts\NotesSetup" 'Source for setup.txt
strIDSourceFolder = "\\stlidserver\notesids$"
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 www.openldap.org. 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
Loop
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
Next
'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
Next
Else
'LDAPSearch command got errors
WScript.Echo "ERROR: EXITCODE: " & intExitCode & VbCrLf & "Errors: " & strOutputErr & VbCrLf & "Output: " & strOutput
WScript.Quit
End IfEnd 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 & VbCrLfEnd 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
CreateFolderTree(strNotesIniFolder)
End If
Set oNotesIniFile = objFSO.CreateTextFile(strNotesIniFile, True)
oNotesIniFile.WriteLine(strNotesIni)
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)
Else
strCurrPath = arrFolderPath(i)
End If
If i > intFolderCreate Then MakeFolder(strCurrPath)
NextEnd Function
Function MakeFolder(strMakeFolderPath)
'Function creates a single folderOn Error Resume Next
MakeFolder = True
strMakeFolderPath = UCase(strMakeFolderPath)
If objFSO.FolderExists(strMakeFolderPath) True Then
objFSO.CreateFolder(strMakeFolderPath)
If Err.Number 0 Then
MakeFolder = False
Else
End If
End If
On Error Goto 0End 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
Else
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 IfEnd 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
Else
'WScript.Echo "COULDN'T FIND ID FILE"
FindIDFile = False
End IfEnd Function
Function Recurse(oFolder, strFile)
On Error Resume Next
dim subfolders,files,folder,fileIf strIDSource "" Then Exit Function
Set subfolders = oFolder.SubFolders
Set files = oFolder.filesFor Each file in files
If lcase(file.name) = LCase(strFile) Then
strIDSource = file.path
Exit Function
End If
Next'Recurse all of the subfolders.
For Each folder in subfolders
Recurse folder, strFile
NextSet subfolders = Nothing
Set files = Nothing
On Error Goto 0End 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.









