Free Tool: Analyze ACL in Notes Application/Database

Yesterday my network admin asked me if I could write a simple tool that could provide him with with a spreadsheet of what users had access to a certain database, and through what groups and roles. A couple of hours later I had created an agent that analyze the ACL and identify the users who can access it. The result is presented as a CSV file. I am sharing the code below. It is pretty straight forward. As you can see, I am using lists to hold the data for easy export later to CSV. Run the code with the Lotusscript debugger turned on, and put a breakpoint before the CSV export starts, and you can see how the data is stored in the lists. The function ExpandGroups() is called recursively to drill down, if the group contains additional groups. This function also use a lookup into a custom view, (LookupPeople), that we have in our corporate NAB, I am sure you can modify this code with something that works for you. Enjoy! As always, use the code on your own risk, no warranties, etc. %REM Agent Export ACL Info to CSV Created Nov 14, 2011 by Karl-Henry Martinsson/Deep-South Description: Read ACl for specified database and create a CSV file with info about each user's access (roles, groups, delete access, access level). %END REM Option Public Option Declare Dim nab As NotesDatabase Type RowData role As String group As String username As String deletedoc As String level As String levelno As Integer End Type Class GroupData Public roles List As String Public Sub New() End Sub End Class Class PersonData Public accesslevel As Integer Public roles List As String Public deletedoc As boolean Public accessthrough List As String Public Sub New() me.deletedoc = False End Sub Public Sub SetAccessLevel(level As Integer) If me.Accesslevel<level Then me.AccessLevel = level End If End Sub Public Function GetAccessLevelText() Select Case me.AccessLevel Case 0 : GetAccessLevelText = "No Access" Case 1 : GetAccessLevelText = "Depositor" Case 2 : GetAccessLevelText = "Reader" Case 3 : GetAccessLevelText = "Author" Case 4 : GetAccessLevelText = "Editor" Case 5 : GetAccessLevelText = "Designer" Case 6 : GetAccessLevelText = "Manager" End Select End Function End Class Class RoleData Public groups List As String Public Sub New() End Sub End Class Sub Initialize Dim ws As New NotesUIWorkspace Dim session As New NotesSession Dim db As NotesDatabase Dim pview As NotesView Dim pdoc As NotesDocument Dim acl As NotesACL Dim entry As NotesACLEntry Dim person List As PersonData Dim group List As GroupData Dim role List As RoleData Dim users As Variant Dim row List As RowData Dim cnt As Long Dim groupname As String Dim filename As String Dim rowstr As String Dim dbname As String Dim servername As String servername = InputBox$("Enter server for database:","Select Server") If servername = "" Then Exit Sub End If dbname = InputBox$("Enter full path of database:","Select Database") If dbname = "" Then Exit Sub End If set nab = New NotesDatabase(servername,"names.nsf") Set db…

3 Comments

Dynamic tables in classic Notes

Using Xpages, you can do many neat things in Notes. But I am still doing all my development in classic Notes, despite being on Notes/Domino 8.5.2. The reason is simple. Memory. There is just not enough memory to run the standard client in our Citrix environment, so all users are using the basic client. And Xpages is not working in the basic client. So how do I solve issues like multiple documents tied to a main document, being displayed inline in the main document? Well, I solved this a long time ago by using a rich text field and building the document list on the fly. I am sure many other Notes developers use this technique. I know it was discussed in a session at Lotusphere several years ago, perhaps as long ago as 7-8 years ago. So this is of course not anything I came up with myself. In this post I just want to share how I am using this, and perhaps this can help someone. The database (download it here) is very simple. It contains three (3) forms and two (2) views. In addition I have some icon as image resources, but that is just to make things pretty.   Forms The 'MainDoc' form is the main document, being displayed in the view 'By Last Change'. It contains a few fields: 'Title' - a text field where the user can enter a title/description 'Data' - a rich text field where the rendered table rows will be displayed 'ParentUNID' - a hidden computed-when-composed field where the UniversalID of the document is stored 'EntryCount' - a hidden text field used to count the number of entries (to display in the views) 'LastEntry' and 'LastEntryDate' - Hidden text fields to keep track of the last person who edded an entry The form also contains some regular action buttons ('Save', 'Edit', 'Close'), as well as 'Add entry...', an action button with two lines of Formula language: @Command([FileSave]); @Command([Compose];"Entry") The QueryOpen event, which executes before the form is opened in the front-end and displayed to the user, contains some Lotusscript code. This event let us modify the document in the backend, which is where rich text fields can be updated. The code - which I will post at the end of this article - is very simple, it is just over 50 lines of code.   The 'Entry' form is where the entries are created. It is set to let fields inherit values from the document it is created from, and contains a couple of fields, some action buttons and some Lotusscript code as follows: 'ParentUNID' - editable, default value is "ParentUNID", to inherit the value from MainDoc 'Created' and 'Creator' - computed-when-composed to store date/time and user name of creator 'ItemDate', 'Issue' and 'Action' - user data fields. The 'Entry' form contains the following Lotusscript code: (Declarations) Dim callinguidoc As NotesUIDocument Dim callingdoc As NotesDocument Sub Initialize Dim ws As New NotesUIWorkspace Set callinguidoc = ws.CurrentDocument If Not callinguidoc Is Nothing Then Set callingdoc = callinguidoc.Document End If End Sub Sub Terminate Dim ws As New NotesUIWorkspace…

7 Comments

Import CSV from Excel into Notes documents

The other day there was a post on LinkedIn regarding importing Excel data into Notes documents. Someone suggested to save into Access format, and then export from there intosome 1-2-3formatthat Notes can read. I suggested to save the Excel spreadsheet as a CSV file, and then import it. So I decided to write a small generic importer. I built a class called "csvFile", which I put in a script library called "Class.ImportCSV". Below is the code for the actual import agent. It creates a new csvFile object, which load all the CSV data into an array in memory. Each array element is in turn a class, containing a list of entries. This is because you can not create arrays of arrays or lists, they have to be in another object/class. If you know the row number and column label (the first row in the CSV file will be considered the column labels), you can address the value like this: csvfile.row(r).entry("ColumnLabel"). Option Public Option Declare Use "Class.ImportCSV" Sub Initialize ' *** Import CSV file and create matching documents in Notes ' *** By Karl-Henry Martinsson, April 8, 2010 Dim session As New NotesSession Dim db As NotesDatabase Dim doc As NotesDocument Dim csvfile As csvFile Dim rowcnt As Long Dim r As Long Set db = session.CurrentDatabase Set csvfile = New csvFile("c:\Book1.csv") rowcnt = Ubound(csvfile.row) + 1 ' *** Loop through the rows and create a new document for each For r = Lbound(csvfile.row) To Ubound(csvfile.row) If (r+1 Mod 10) = 0 Then ' Update status bar every 10 documents Print "Importing " & r+1 & " of " & rowcnt End If Set doc = New NotesDocument(db) Call doc.ReplaceItemValue("Form", "MyFormName") ' *** Loop though entries for the row and populate corresponding fields in doc Forall e In csvfile.row(r).entry Call doc.ReplaceItemValue(Listtag(e), e) End Forall Call doc.Save(True,False) Next End Sub Here is the script library. Simply create a new script library, call it "Class.ImportCSV" and paste the code into it's Declaration section: ' *** Created by Karl-Henry Martinsson on 2010-04-08 ' *** Email: TexasSwede@gmail.com ' *** Blog: http://blog.texasswede.com ' *** ---------------------------------------------------------- ' *** You are free to modify and edit this code, but please keep ' *** all comments intact, and publish any changes you make so ' *** the Lotus community can benefit. You are allowed to use ' *** this code in commercial/closed source products, but are ' *** encouraged to share your modifications. ' *** Disclaimer: Use this code at your own risk. No warranties ' *** what so ever. Don't run code you don't know what it does. ' *** ---------------------------------------------------------- Class RowData Public entry List As String End Class Class csvFile Public row() As RowData ' Storing the rows in the imported CSV file Public column List As String ' List containing column labels Private fileno As Integer ' File number Public Sub new(filename As String) Dim temprow As String Dim temparr As Variant Dim fixedarr() As String Dim i As Integer Dim flagQuoted As Integer fileno…

5 Comments

Agent Logging – Budget Style

At my work, we had several issues/needs related to agents in different Domino databases. I tried to come up with a quick and lean solution, that gave us the functionality we needed, nothing more and nothing less. Yes, I am aware of Julian Robichaux's excellent OpenLog. But we had some needs that I did not feel were addressed in that project. What I wanted was a database where the agents could be documented, and also where the log data was stored. So I created a Agent Log database where one document is stored per agent. The form let me document the actions of the agent, as well as what server it is running on, in what database, when it is triggered, etc. When the document is opened, I also read the last time the agent was run and if the agent is enabled or not. This is a sample document for an agent. It is scheduled to run at 11am every day:   This is what the actual database looks like. I plan to release the database on OpenNTF.org when I get the time.   Nothing complicated this far. The next step was to create a small script library, doing the actual logging. I created a script library called Class.AgentLog with the following code: Public Const THREAD_TICKS=6 Public Const THREAD_TICKS_PER_SEC=7 Class AgentLog Private session As NotesSession Private logdb As NotesDatabase Private agentdoc As NotesDocument ' Main doc in Agent Log db Private logdoc As NotesDocument Private running As Integer Private tstart As Long ' Ticks at start Private tend As Long ' Ticks at end Private tps As Long ' Ticks per second (system dependant) Public Sub New( agentname As String ) Dim view As NotesView Dim key As String running = True tstart = GetThreadInfo( THREAD_TICKS ) tps = GetThreadInfo( THREAD_TICKS_PER_SEC ) Set session = New NotesSession Set logdb = New NotesDatabase( session.CurrentDatabase.Server, "IT/AgentLog.nsf" ) Set view = logdb.GetView( "(LookupAgent)" ) If agentname = "" Then agentname = session.CurrentAgent.Name End If key = session.CurrentDatabase.FilePath & "~" & agentname Set agentdoc = view.GetDocumentByKey( key ) If agentdoc Is Nothing Then Print "AgentLog: Failed to locate agent document for " & agentname & "." Exit Sub End If Set logdoc = New NotesDocument( logdb ) logdoc.Form = "LogItem" Call logdoc.MakeResponse( agentdoc ) Call logdoc.ReplaceItemValue("AgentName", agentname ) Call logdoc.ReplaceItemValue("StartTime", Now() ) If session.IsOnServer = True Then Call logdoc.ReplaceItemValue("RunBy", session.CurrentDatabase.Server ) Else Call logdoc.ReplaceItemValue("RunBy", session.CommonUserName ) End If End Sub Public Sub Finish() running = False End Sub Public Sub Terminate() Dim seconds As Integer If logdoc Is Nothing Then Exit Sub End If tend = GetThreadInfo( THREAD_TICKS ) seconds = (tend - tstart) / tps Call logdoc.ReplaceItemValue("EndTime", Now() ) Call logdoc.ReplaceItemValue("Seconds", seconds ) If running = True Then ' Check if not terminated gracefully Call logdoc.ReplaceItemValue("Terminated", "Yes" ) End If Call logdoc.Save(True,True) End Sub End Class All code goes in the (Declarations) section of the script library. There are a few things to notice. I actually make each log item a child…

0 Comments

Object Oriented Lotusscript for beginners – Part 2

In my previous post I wrote about whyI use object oriented Lotusscript. Let's look at how it can be used in a real-life application. Background At my work I developed a system to handle insurance claims. Each claim can have one or more claimants, people or parties that have either BI (bodily injury) or PD (property damage) claims related to an accident. Each claimant get a reserve setup, an amount of money the adjuster think it will cost to settle the claimant. There are two reserves for each claimant, one for loss payments and one for expense payments. The latter can be payments for police reports, field adjusters, lawyer fees, etc while loss payments are the actual damages (payments to body shops, medical payments, etc). When payments are made, the reserve amounts are reduced, until reaching zero. No more payments can be done then until the reserve is increased. Each adjuster have a limit to how large reserve he or she can set, higher reserve must be approved by a manager. Data storage When the claim system was first put in place, all reserves and payments werestored in the Notes database. They were then (manually) transferred into a backend system built in Visual FoxPro. But after a few years, a COM object (dsClaimLink) was developed and the Notes database is now sending all financial transaction into the backend, and retrieving financial information the same way when needed. Claim information is stored in the Notes database, as is claimant information. Some claimant information. a sub-set of the data stored in Notes,is sent to the backend as well. Original design Initially I built a large number of functions and subroutines, organized in different script libraries based on functionality.This actually worked really good, and the code was fairly easy to maintain, modify and expand. When the financial transactions were moved to the backend, I just had to modify the function GetAvailableAmount() to call the backend instead of looking up the amount in the Notes database. But it was still not very flexible, andI had some code that was duplicated in many places (most of it related to calling the COM object). So about two years ago, I started refactoring my code, both to make it faster and easier to maintain, by using object oriented Lotuscript. Example Beloware examples of the code in the script library Class.ClaimData class. This is not my exact production code, I have removed a number of lines to make the example more clear. The ClaimData class (described in next posting)contains an array of claimants, each of those an object. Each claimant object in turn contains an object containing the different amounts (loss reserve/loss payments, expense reserve/expense payments, recovery amount, etc). First, let's look at the AmountData object. Class AmountData Public lr As Currency ' Loss Reserve Public er As Currency ' Expense Reserve Public lp As Currency ' Loss Payments Public ep As Currency ' Expense Payments Public slp As Currency ' Supplemental Loss Payments Public sep As Currency…

0 Comments

ProgressBar class for Lotusscript

Brian Moore wrote about the problem with users thinking Notes applications are slow, due to no visible feedback: "People seem to be totally happy at letting something take bleeding ages just so long as there is a little moving object to distract them from the clock." This is very true, and I addressed this in one of my applications a while back by implementing a progress bar. Suddenly, when the users got feedback on what was happening, all complaints about it being slow stopped, despite it taking the same amount of time... I built a progress bar class. Here it is, as well as a small code sample showing how to call it. Note that this is a Win32 only solution.   Option Public Option Declare ' ***** Declarations for (undocumented) progress bar in Notes ***** Private Const NPB_TWOLINE% = 1 Private Const NPB_STATUSBAR% = 32 Declare Private Function NEMProgressBegin Lib "nnotesws.dll" ( Byval wFlags As Integer ) As Long Declare Private Sub NEMProgressDeltaPos Lib "nnotesws.dll" ( Byval hwnd As Long, Byval dwIncrement As Long ) Declare Private Sub NEMProgressEnd Lib "nnotesws.dll" ( Byval hwnd As Long ) Declare Private Sub NEMProgressSetBarPos Lib "nnotesws.dll" ( Byval hwnd As Long, Byval dwPos As Long) Declare Private Sub NEMProgressSetBarRange Lib "nnotesws.dll" ( Byval hwnd As Long, Byval dwMax As Long ) Declare Private Sub NEMProgressSetText Lib "nnotesws.dll" ( Byval hwnd As Long, Byval pcszLine1 As String, Byval pcszLine2 As String ) Class ProgressBar Private hidden As Integer Private hwnd As Long Private value As Long Private title As String Private textline1 As String Private textline2 As String Private maxvalue As Long Public Sub New(range As Long, initialtitle As String) Dim session As New NotesSession maxvalue = Clng(range) title = initialtitle textline1 = "" textline2 = "" If session.IsOnServer Then ' Check if code is running on server (scheduled agent) hidden=True Else hidden=False End If If hidden = True Then Exit Sub End If hwnd = NEMProgressBegin(NPB_TWOLINE) Call SetRange(maxvalue) NemProgressSetText hwnd, title, textline1 & Chr$(13) & textline2 End Sub Sub Increase() If hidden = True Then Exit Sub End If If hwnd <> 0 Then If value < maxvalue Then value = value + 1 NEMProgressSetBarPos hwnd, value End If End If End Sub Sub Decrease() If hidden = True Then Exit Sub End If If hwnd <> 0 Then If value > 0 Then value = value - 1 NEMProgressSetBarPos hwnd, value End If End If End Sub Sub Close If hidden = True Then Exit Sub End If If hwnd <> 0 Then NEMProgressEnd hwnd hwnd = 0 title = "" value = 0 End If End Sub Sub SetRange(value As Long) If hidden = True Then Exit Sub End If If hwnd <> 0 Then maxvalue = value NEMProgressSetBarRange hwnd, maxvalue End If End Sub Sub SetValue(value As Long) If hidden = True Then Exit Sub End If If hwnd 0 Then NEMProgressSetBarPos hwnd, value End If End Sub Sub SetText(message As String) If textline2="" Then Call PrintText() End If…

1 Comment

End of content

No more pages to load