09/18/2008

SnTT, kinda: Interesting replication problem, and I wanted to get your ideas

QuickImage Category   
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

This is a technical post of sorts, and even though I'm not "showing" a solution or trick, I am "showing" a problem I encountered with the intention to use the "grid computing" power of our collective knowledge and experiences. Also keep in mind that I want to keep this as generic as possible, so that I can get an "untainted" set of opinions. Here's the situation.

John Coolidge (of the MS Constants database fame) and I were at a customer site, and there was a problem with replication of design elements. After some design elements were copied into the database programmatically (via published APIs, nothing tricky or experimental - the UNID was preserved via this process, also a part of the API), they wouldn't replicate at all, anywhere, until you edited the elements manually in Domino Designer. Also there is no replication error thrown at all.

Incidentally this technique has worked at many, many, many customer sites, so there's nothing wrong with the process we're using; this is the first customer for which the following problem as occurred.

All servers are in the same location, same time zone.

OK, back to the problem at hand. In order to rectify the problem we tried:
  • Clearing replication history of both the source and target replicas
  • Testing this technique on different databases
  • Compacting, Fixing, Updating, etc. - all the stuff you'd normally do to fix a corrupted db
  • And probably a few other things that I'm just not remembering - but I'm sure I'll remember when you ask about it

None of this work - the problem persisted. However, I had a sneaking suspicion what the problem may be, and after some investigation we had an idea as to why this was happening.

Now for those of you not familiar with replication, I think it is important for you to understand exactly how it works before moving on; therefore let me give you a quick primer on it, so that you have the same base knowledge from which I am working.

Replication Background NOTE: You can find the gorpy details of all of this stuff I am talking about here, but I will pick out the salient points for this discussion. First, let me explain about the Originator ID (OID).

Originator ID (OID)
The OID is comprised of:
  • Universal ID (UNID) which (as you know) is the unique identifier for a note (remember, a "note" can be either a document [data] or design element [design]) in a database
  • the sequence date (SD), which is a hex version of a date/time stamp
  • the sequence number (SN), which is a hex number representing the number of times the note has been saved

The SD and the SN are updated every time the note is "dirtied" (i.e. a change is made to the note) and saved. The UNID never changes after a note is created and saved.

Take a look at the graphic below:

SN-SD-Example.png

This happens to be the properties box for this document. The first part that is highlighted, in blue, is the Sequence Date (SD); the second part that is highlighted, in red, is the Sequence Number (SN). So, when I took that screen capture I had saved this document once (the SN always begins at 1).

The replicator uses the OID to determine if two replicas of the same note are the same, or if one (or both) of them has been changed. Let's talk about that a bit more.

Replication - How it Works
Here's an explanation from the aforementioned document from Lotus on how the replicator uses the OID for replicating notes, since it explains it better than I probably would.

NOTE: In the explanation below the term "Sequence Time" is used instead of "Sequence Date". I was always taught Sequence Date (because of the SD abbreviation), so that's what I use.
The UNID, the OID, and the Replicator

The Universal Note ID (the first half of the Originator ID) uniquely identifies all versions and all copies of the same note. Two notes are replica copies of each other if they share the same UNID. Therefore, different versions and all replica copies of the same note have the same UNID. A corollary of this rule is that one database must not contain two notes with the same UNID. If the replicator finds two notes with the same UNID in the same database, it generates an error message in the log and does not replicate the document.

The full Originator ID, on the other hand, uniquely identifies one particular version of a note. In other words, all replica copies of the same version of a note have the same OID. However, a modified version of a replica copy of a particular note will have a different OID, because Domino and Notes increment the sequence number when a note is edited and also sets the sequence time to the timedate when the sequence number was incremented. Therefore, when one replica copy of a note remains unchanged but another copy is edited and modified, the UNIDs of the two notes remain the same but the sequence number and sequence times (and therefore the OIDs) are different.

The Domino replicator uses the UNID to match the notes in one database with their respective replica copies in other databases. For example, if database A is replicating with database B, and database A contains a note with a particular UNID but database B does not, the replicator creates a copy of that note and add it to database B.

If database A contains a note with a particular UNID and database B contains a note with the same UNID, the replicator concludes that these two notes are replica copies of one another. In this case, the replicator goes on to examine the sequence number and sequence time of the two notes. If the sequence number and sequence time are the same for both notes, then the replicator concludes that the two notes are up to date with one another, and no action is required. On the other hand, if either the sequence number or the sequence time -- or both -- differ between two notes, the replicator must decide which one is more recent and update the older note with the most recent version.

If one note has been updated but the other note has not, the sequence number of the first note will be greater than that of the second. The replicator handles this case by overwriting the second note with the first, bringing the two databases into synchronization.
**Emphasis mine

Now, we could go on to explain field-level replication, replication conflicts, and so on, but we don't need to. Why not? Because we're talking about design elements, and with design elements the newest one wins.

Or so I thought.

My Problem - the Nitty Gritty
Based upon the replication explanation and how the OID is used, we can make the following simplistic assumptions about the various conditions for replication. In my examples below I am going to use regular date/time and decimal expressions to represent the SN and SD. Assume that I'm talking about replicas of the same design note.
Design Note 1 (SD : SN) Design Note 2 (SD : SN) Results and Comments
13 June 2008 : 03 13 June 2008 : 05 SD are the same, note 2's SN is greater, so note 2 wins
15 June 2008 : 12 13 June 2008 : 05 note 1's SD is greater, so note 1 wins
**
**
So far, so good. But when investigating this problem we had, here's the situation we found...
13 June 2008 : 12 25 Jun 2008 : 04 Notice that note 1's SD is lower than note 2's, but the SN is higher


As I stated before, the third example is what we found in our investigation. If everything is working correctly, this shouldn't be possible - to have a SD lower but the SN higher between two notes. I am not sure how this happened, and the customer had no idea either. We asked about maybe there being a server time issue at some point, but there was no such problem noted by the customer.

Now, we're not sure that this is the cause of the problem - but it is our best guess. The only part of this explanation that bothers me is that with design element notes I always thought "last one in wins", period - so if that is the case, shouldn't note 2 win in the example listed above? My guess is that the replicator still evaluates the entire thing - SD and SN - even with design elements; it just doesn't create a replication/save conflict, it simply picks the "winner" and ensures that it is in both replicas. But in this case, it couldn't make a determination as to which note should win, so the replicator did the equivalent "huh??", threw its hands in the air, and ignored the problem.

However, if this is the case, shouldn't a replication error be thrown?

Conclusion
The way to resolve this problem is to create new replicas of the databases from the most recent replica, in order to get the SD and SN numbers in sync again. This fixes the problem, and life is good again.

Well, there you have it. I would love to hear your theories, answer your questions, and generally discuss this weird anomaly to see if we can figure out why this happened. In any case maybe this post will help others in the future if they encounter the same problem.

So share your ideas, and let's discuss.

Rock

08/28/2008

Teamstudio Profiler and Notes Agent Profiler, compared

Category  
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

Recently a friend of mine who read my GetNextDocument VS GetNthDocument post and mentioned to me that there is a profiler feature in Domino Designer, and he asked why anyone would need Teamstudio Profiler. I actually didn't know that Designer had a profiler feature built in, so I had to explore.

It turnns out that the Profiler feature in Domino Designer allows you to profile agents and web services. You simply enable the profiler feature on the "key" tab in the properties box of the agent; and if you wish to access the profile after you run the agent you go to Agent/View Profile Results in Designer and it will display the profile results document.

Comparison of Domino Profiler and Teamstudio Profiler
After I experimented with Domino Profiler, I decided to demonstrate this feature in one of the agents written for the previous post, and then display the results here. I then ran Teamstudio Profiler against the same agent. Here's the results...

TS-Profiler_vs_N-Profiler_2.png

The Domino Profiler gives only basic information about the code that executed, based on the methods and properties that are called from Domino classes. No other lines are measured, so you can't get a line-by-line breakdown of the agent's performance. It simply gives you a basic measurement of the performance of the agent.

TS-Profiler_vs_N-Profiler_1.png

Now take a look at the Teamstudio Profiler results, by comparison. It gives a great deal more information about every line of your agent, which allows you to determine where your bottlenecks are in your code, especially in subs and functions you wrote in your code. The visual represntation of the percentage amount of time each line took also makes it very easy to quickly identify which lines are slowest. The upper area displays each major functional area - that is if you used common best practices and logically organized your code by subs and functions.

Additional Thoughts
There are upsides and downsides to every tool you encounter. In the case of performance profiling tools, you need to determine how much it is worth to you to optimize your new development as much as possible, or how much it is worth to you to troubleshoot existing apps to find speed bottlenecks - and then choose the tool that best suits your needs. For example, when considering the differences between Domino Profiler and Teamstudio Profiler, consider the following...
  • Domino Profiler is free; Teamstudio Profiler is not.
  • Domino Profiler only works on agents and Web services; Teamstudio Profiler will profile any design element with LotusScript in it
  • Teamstudio Profiler has many features and options, while Domino Profiler is very bare-bones.
  • Domino Profiler must be enabled through the Domino Designer, and it signs the agent with the user's name. Teamstudio Profiler can be run from the Notes client, and is completely nonintrusive.
These are just a few of the many differences between the two tools. You need to determine what your needs are, and then make sure you have the most appropriate tool for your needs.

One other thing to keep in mind is that ALL profiling tools cause the profiled code to run slower - but this is OK, because profiler tools are used to demonstrate relative performance, not sheer speed; for instance you run a profiler tool once to get a baseline, and then you make some changes and run it again to see if it made any (relative) difference. Once you are done with your profiling efforts, make sure you "turn off" your profiling tool (Teamstudio Profiler turns off after each run); in the case of Domino Profiler you must go into the Agent Properties and disable it there.

Conclusion
I hope you didn't mind the comparison to Teamstudio Profiler again, but it is a good way to show how a "built in" tool like Domino Profiler compares to a commercial product (or even a shareware/freeware product) that profiles code. Domino Profiler reminds me of Domino Design Synopsis - it is a basic tool, and really falls down in many areas that cause it to be a fairly disappointing tool. There are many other commercial and shareware/freeware tools out there that do a much, much better job than Design Synopsis (such as Teamstudio Analyzer!), and the same holds true for Domino Profiler as well.

Rock

08/19/2008

GetNthDocument vs GetNextDocument, revisited

Category  
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

NOTE: I had begun writing this last Thursday evening - and for some strange reason my Notes 8.5 client decided to NSD - and the autosave didn't save my document, even though it was enabled. Crud. So, I had begun writing it again and the NSD happened a few more times, and I discovered that the autosave feature in 8.5 beta may have a bug. I finally began saving very often on my own, and I am now finally posting this. The funny thing is that Bob Balaban - the guy who originally created this cause for debate about GetNextDocument vs GetNthDocument (thanks Bob!) - has also written a post about them as well. I guess we worked together so long that we think alike - sometimes . In any case I have written this without looking at his post, so that my post and results are not tarnished. Once I am done, Bob and I have agreed to read and comment on each other's posts appropriately. **Rock

Over the years there has been a great deal of angst and debate about two fundamental methods that are a part of the NotesDocument class in LotusScript - NotesDocument.GetNextDocument and NotesDocument.GetNthDocument. In fact a colleague of mine, John Kingsley, posted about them recently - and his belief was that GetNthDocument isn't as bad as everyone thinks it is; but how can this be? We have been taught over the years that GetNthDocument is evil. Like, "oh crud, we must go find every instance of GetNthDocument and rip it out immediately!!" evil - as if it would corrupt everyone and everything it touches. Even I have preached that over the years (ok, maybe not that strong - I have always said to run a little test and see which is faster, and by how much, and then make your decisions accordingly); and to make sure I was saying the right thing I even asked the man himself, Bob Balaban about it. He gave me the whens and wherefores of when GetNthDocument would actually save you any time; and his final words of wisdom on GetNthDocument are so memorable that I would never forget them, and have even used them in presentations as well:
The cases where GetNthDocument is faster than GetNextDocument are so remote that the best rule-of-thumb is to always use GetNextDocument. Even in the cases where GetNthDocument is faster, GetNextDocument is only negligibly slower, so just use GetNextDocument all the time.

You know, I wish I had never created the GetNthDocument method.

Well, those are pretty damning words for GetNthDocument. Then my colleague comes along and says that GetNthDocument isn't that bad - and he uses one of our products to validate this in his tests. Huh?

So, I decided to do some tests of my own. Now I am using our product as well. It is called Teamstudio Profiler. It allows you to determine the performance of every line of your LotusScript code, and is a nifty tool. I wish I had known about it before joining Teamstudio, it would have saved me a ton of time in many situtations.

NOTE: This is not a commerical for Teamstudio Profiler. I am not going to walk through the features, give you a demo, or even show you how it works. I have simply used Profiler for my tests, and have taken screenshots of it for the purposes of my tests and this post. If you want to learn more about Profiler, let me know.

Background
So, I decided to do some tests of my own. I performed my test using an agent that has identical code for all tests; the only thing that changes is the lines of code for GetNextDocument and GetNthDocument. I even leave the counter code in there so that the performance should be identical.

I wanted my test to be as real-world as possible, so instead of using dummy data I decided to use an actual, huge database. I wanted to use a database that contains real docs, real data, attachments, etc. - everything you'd find in a real db; so I am using a local replica of the Lotus Software Knowledgebase. It is pretty darn big - about 29,700 docs at last count. Now, I didn't want my tests to access all the docs, because it would take a long time, quite frankly. Instead I make all tests jump out of the loop at 15,000 documents, so that is the maximum number of documents processed in any given test.

The actual GetNext/GetNth looping code is in a sub called ProcessDocuments, to make it easier to see what's going on. Also, it is more real world - if you are structuring your code correctly you'll be using Subs and Functions as well.

I have saved my agents off as LSS files, and if you are interested in using them for your own tests, let me know and I'll send them to you or post them.

Now that you know a little of the background, let's get on with the tests.

Round 1: Using a NotesDocumentCollection obtained from Notesdatabase.AllDocuments
This round uses the NotesDatabase.AllDocuments property of the Lotus Software KBase. I also access some items in this test - let's see what happens. GetNextDocument
GetNextDoc_1.png

This agent took 70.02 seconds to run, and the ProcessDocs sub took 69.95 seconds of that. Not bad for 15,000 documents. But what's going in inside ProcessDocs?

GetNextDoc_2.png

As you can see, the bulk of the time is taken up by two lines - 10 and 11. Line 10 is a print statement - and this is important to keep in mind when optimizing your code. Anything that interacts with the UI is going to be expensive - and it is even more expensive if you print inside a loop! So, don't use print statements in iterative loops unless you absolutely have to; or if you really want to, set it up so it only prints every once in awhile.

Line 11 is the first of two lines that access items in each document. This line uses a NotesDocument.HasItem method to determine if the item ("Subject") is included in the document, and then if it is it access it (using NotesDocument.GetItemValue) as a part of the test. But notice that line 12 accesses an item ("Form") without the HasItem check - and it is amazingly faster than line 11. So, what does that mean? Does it mean that NotesDocument.HasItem is really expensive? It would appear so - but keep that in mind as you read on.

Anyway, back to the test.

Line 15 is the line we're really lookng for - it is the NotesDocument.GetNextDocument line. It processed the 15,000 documents very efficiently; it took 2.36 seconds to process the documents.

So how does that compare to GetNthDocument?

GetNthDocument
GetNthDoc_1.png The GetNth version of this code took over twice as long to process the same documents - 146.07 seconds! - while the ProcessDocs sub took 145.93 seconds. Let's take a look at the breakdown of the ProcessDocs sub.

GetNthDoc_2.png

Once again lines 10 and 11 took a decent amount of time, but take a look at line 15 - the GetNthDocument line. It took an astonishing 76.39 seconds alone! That's over 74 seconds slower than the GetNextDocument linem (line 15 also)! That means that, in this case (i.e. using the NotesDatabase.AllDocuments property's collection) GetNextDocument is much, much faster than GetNth.

But how would these two methods perform from a collection built using NotesView.GetAllDocumentsByKey?

Round 2: Using a NotesDocumentCollection obtained from NotesView.GetAllDocumentsByKey
I began using the AllDocuments property because it is easily accessible, and it is a NotesDocumentCollection containing a lot of documents; however most developers wouldn't use AllDocuments (and probably shouldn't in most cases), so I wanted to build some tests that were based on more real-world uses.

This test is using the NotesView.GetAllDocumentsByKey. I used the ProductLookup view in the Lotus Software KBase, and I set the key to "Lotus Domino" and the exact parameter to "False". This resulted in a document collection of 7,497 documents, which is sufficiently large enough for our testing. Since each round is simply a test of GetNextDocument vs GetNthDocument, I reasoned that it didn't mind that the collections weren't the same - we're not comparing rounds, only each method against each other in the round.

Given all of this, let's take a look at the GetNextDocument method.

GetNextDocument
GetNextDoc_1.1.png

Remember that this round is operating at roughly have the number of documents, so the time reflects this change. This time the GetNextMethod version took 40.25 seconds to process 7,497 dsocuments overall, with the ProcessDocs sub taking 38.92 seconds.

GetNextDoc_2.1.png

Notice that I took out the conditional item access statement - I did this to speed up the overall processing time. However I did leave the simple item acess line and the Print statement.

While we're looking at this, take a look at the Round 1 ProcessDocs image, line 12 (the Form item access). In that run the simple item access took a paltry 1.57 seconds for 15,000 documents, while the same line in Round 2 took 30.55 seconds to process roughly half the number of documents! Why is that? I am guessing that in both cases the document object itself is not open in memory until you access it. In Round 2 the Form item access line is the first line that actually accesses the document itself, causing it to run much slower. But what does this mean? That will be covered later on.

Line 14 (the GetNextDocument line) was fast as usual at 1.23 seconds. This means that when you're looking to optimize your code, GetNextDocument is almost always not going to be your problem.

So, how does GetNthDocument stack up in Round 2?

GetNthDocument
GetNthDoc_1.1.png

The GetNthDocument version of round 2 took 48.22 seconds to complete, while the ProcessDocs method took 46.90 to complete. This is around 6 seconds slower to process the same 7,497 documents. That's much closer than in Round 1, even taking into account that this is processing a smaller set of documents. Let's take a look inside ProcessedDocs.

GetNthDoc_2.1.png

For some reason the Print statement and Form item access lines took less time this time around; remember that code can run slower between each run for a variety of reasons, including system load, memory access, disk access, and so on - and that's the case in this instance. However, we're still here to look at the GetNthDocument line and compare it to the GetNextDocument line. The GetNthDocument line took 20 seconds to run, compared to GetNextDocument's time of 1.23 seconds. Wow. And this is on a run where, by looking at other lines, this run seems to be running faster than the GetNextDocument run - which means that if it had the same run parameters as GetNext, it might have run even slower!

Round 3: NotesView.AllEntries (NotesViewEntryCollection)
Many developers have discovered the speed benefits of using NotesViewEntryCollections vs NotesDocumentCollections whenever possible. As you know, accessing a document's items through a view column is much faster than opening the document itself. When I give talks about ways to organize your code I recommend using programmatic views to access documents, instead of using user-accessible views because user-accessible views are subject to change based on the whims of the user; programmatic views belong soley to the developer, and are much more dependable than user views. But how does this related to NotesViewEntryCollections, etc.?

If you can add columns to programmatic views that contain the data you need to access, then you can retrieve that data without needing to open the document itself - which we've shown to be an expensive proposition. Another additional benefit is that NotesViewEntryCollections are in view-order, where NotesDocumentCollections are in NoteID order. Pretty useful, huh?

So, let's take a look at the NotesViewEntryCollection in the context of GetNextEntries vs GetNthEntries.

Like before, there are some small code changes here as well. Since I want to show how much faster acessing item values via a NotesViewEntry is compared to NotesDocument, I added a line that simply pulls a column value from the Entry doc.

Round 3 uses a hidden view in the Lotus Software KBase called "Main Docs by Number". I am processing the collection found in the NotesView.AllEntries property, which is a collection containing all entries from this view (which in this case is only documents). Since this view contains over 29,700 documents the loop still exits at 15,000 documents.


GetNextEntry_1.png

This version of the code ran in 22.81 seconds, with the ProcessDocs sub taking up 18.11 seconds. As stated earlier, this document is processing 15,000 documents, so that's not a bad time.

GetNextEntry_2.png

First, notice that the column value access (line 11) is really fast - it accessed 15,000 values in 1.2 seconds! Then take a look at the GetNextEntry line (line 13) - it processed 15,000 entries in 15.31 seconds! That's impressive, when compared to NotesDocumentCollection access times.

GetNthEntry
GetNthEntry_1.png

This version of the code ran in 23.67 seconds, which is only around 1.1 seconds slower than the GetNextEntry version. The ProcessDocs sub ran in 18.93 seconds, which is less than a second slower. Wow! Let's take a look at the code in ProcessDocs to see what's going on.

GetNthEntry_2.png

The column value access line is about the same as the GetNext run - 1.26 seconds - which is expected. The line we really are concerned with, however, is the GetNthEntry line. It ran in 16 seconds, which is also less than a second difference. This experiment then begs the question:

Why so close?

I believe (and I may be wrong - I'll have to check with Bob B.) - that these results are very close because we're working with a NotesViewEntryCollection, and it simply contains the rows from the view with a little extra stuff (such as the NoteID of the doc). It is not designed to know much about the underlying document; it is designed to provide fast and ordered access to the contents of the view it represents.

When you are looping through a NotesDocumentCollection using GetNextDocument, the LotusScript engine has to find the next document by using the NoteID - but since the NotesDocumentCollection is in NoteID order, finding a doc by NoteID, this is easy. When using the GetNthDocument method against a collection, it is much harder. Since the documents are in NoteID order in the collection, finding the "nth" document in the collection is painful, because it has to start at the top of the collection and count until it finds the desired document - every time.

The NotesViewEntryCollection entries, however, are aready in "sort" order, so it makes it much faster for both GetNextEntry and GetNthEntry to find the entry since they're already in order. This also explains why GetNextEntry and GetNthEntry are virtually identical in their processing times.

Conclusion
Well, it appears that Bob's rule-of-thumb is probably right - it is a very good idea to use GetNextDocument in virtually every situation. It will almost certainly be faster than GetNthDocument, and in the rare situations where GetNthDocument might be faster, it is so close it isn't worth thinking about it.

Now, I am NOT saying that you should never use GetNextDocument; there are probably quite a few situations where GetNthDocument makes perfect sense. However I am saying that you should know why you're using it, and have a very good reason for doing so.

I did not test GetNextDocument and GetNthDocument against a NotesView object, and for that I apologize. I thought that this test was already long enough, so I stopped here. If you would like to see those tests as well, let me know.

I hope this proves useful, and I would love to hear your feedback.

NOTE: I read Bob's post about this, and I have stated something wrong in this post. I have stated (a lot) that NotesDocumentCollections are in "NoteID order". Well, they're not. According to Bob:
IF (you have a DocumentCollection object -- doesn't matter whether it's LotusScript, Java, COM, whatever language binding), AND (your DocumentCollection object is an UNsorted list -- i.e., the result of a Database.AllDocuments, or Database.Search operation, NOT an FTSearch) THEN iterating that collection using the DocumentCollection.GetFirst/GetNext pattern is MUCH (i.e., A LOT) faster than iterating using DocumentCollection.GetNth(index).

I think that in a NotesDocumentCollection it must know what the next and previous documents are in the collection, relative to the current document, because that's why GetNext/GetPrev get really confused if you delete the current doc. This also explains why getting the next/previous document is really fast.

But, to restate: NotesDocumentCollections are UNSORTED; please forgive my inaccuracies up above. **Rock


Enjoy!

Rock

02/15/2007

Wireless network security - two simple suggestions for the n00bs

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 



I recently was informed (in an internal forum called Iris Office Notes) about a Symantec security article highlighting the latest way that insecure home routers are being exploited. The article is called Drive-By Pharming: How Clicking on a Link Can Cost You Dearly. From the article:

The attackers create a Web page that includes malicious JavaScript code. When the Web page is viewed, this code, running in the context of your Web browser, uses a technique known as ‘Cross Site Request Forgery’ and logs into your local home broadband router. Now, most such routers require a password for logging in. However, most people never change this password from the original factory default. Upon successful login, the JavaScript code changes the router’s settings. One simple, but devastating, change is to the user’s DNS server settings.


This article brought some basic tips to mind - tips that I routinely suggest to friends and family (non-geeks, of course) for setting up their wireless home networks. Since this topic brought these suggestions to mind again, and since I needed a SnTT topic for today, I thought I would write them down - and this way I can point my family to this post in the future when asked about wireless home networks

There are a variety of ways your (wireless) router can be exploited - this is just the latest. Of course, wireless routers are much less secure by their very nature. It is fertile ground for hackers, phishers, etc. - and this is not because wireless routers are particularly insecure, it is mainly because neophyte users have no idea how to take advantage of the security measures built into the router. To avoid these potential vulnerabilities there are a couple of steps you should take, at a minimum, when setting up a wireless network.

Step 1: Change the friggin' password!
Every geek - and I mean EVERY geek - who has an interest in wireless networks knows the default password to every router on the market, or can simply Google it to find it (don't believe me? Take a look here.) This means that a router that is set up out of the box without any changes is wide open - for access AND configuration - to anyone who wants to access it. Change your password - and just like any other password, make sure it is one that is not easily guessed.

Step 2: Use wireless encryption
When wireless networking was first beginning to make inroads into the consumer market there was really only one type of wireless encryption available - WEP, or Wirless Equivalent Privacy (BTW, no one knows what WEP stands for - it's just called WEP, so don't bother memorizing it) In the early days WEP was difficult to understand and a pain to set up, especially for the folks who wanted to use your network. You usually needed to know some really long hex string as the key, and it was just difficult for the average user - so most consumers avoided it. Also, to make matters worse, the WEP protocol was easily cracked by "Wardrivers".

Then things got much better - WPA (Wi-fi Protected Access) came along, which greatly improved upon WEP in a couple of areas. First, it was much more secure (read: harder to crack) than WEP; and second it only required a "passphrase" to access a WPA-secured network instead of some incomprehensible (by humans) hex key. This means that if you wanted to, you could make your passphrase something like "Wee willie winkie", which is much easier to remember than 37925A56C3411655B090AA5D. This means that WPA is easy to set up and is easy to use. The ONLY drawback to WPA is that older (read: more than a few of years old) wireless equipment may not support WPA, only WEP; the nice thing is that virtually all of this equipment can be updated with firmware patches to support WPA.

Now, these steps barely scratch the surface of the variety of methods you can use to make your network even more secure - things like MAC filtering, additional DMZ firewalls, and so on. However, if you take these basic steps to secure your environment you will have a wireless router and network that is more secure than most home networks in use today.

Hopefully this will help those of you who are considering a wireless home network but know nothing about it; and for those of you who are wiley veterans of wireless networking, please share other simple tips with the class!

Enjoy!

Rock
**It's not the voices in my head that bother me, it's the voices in your head that do.

02/08/2007

SnTT: Blogsphere 2.x: A way to turn off comments on older entries

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 



I am using Blogsphere 2.5.6 as my blogging template. By default I allow comments to all my postings - I love the interchange with my readers - but I have decided that any post after about 6 months old should have commenting disabled. I still get the occasional "real" comment to really old posts, but by-and-large the comments are receive on older posts tend to be simply spammers that are able to bypass my normal anti-spam prevention.

The problem is that Blogsphere 2.5.6 doesn't contain a feature to disable comments after a certain time period - you either decide to disable comments from the beginning, or if you disable comments later on it will "hide" the existing comments from the site. Not what I wanted. I want to disable the comments, but leave the existing comments available for viewing.

NOTE: This feature is available in the upcoming Blogsphere 3.0. In fact, Blogsphere has undergone a complete reworking, from the ground up, to make it faster, easier to extend, and easier to implement - you should check it out when it is released.

So, since Blogsphere is simply a Notes database, I modified the code to support what I want - and I thought I would share in case any other Blogsphere user is interested. I could have made this fancier, but this suits my needs. Feel free to expand upon this, and share with the class.

The Design
I wanted to be able to change the value of "Allow Comments" on a blog entry and have it a) remove the "add comment" block from the post on the web site, and b) show any existing comments if they exist - and if they don't then don't show the "No comments - be the first!" text on the page. I also wanted an agent that allows me to select one or more blog entries and set the "Allow Comments" option to No.

The Code
Blogsphere is simply a series of subforms that display, based on blog configuration. On the $$ViewTemplate For D6plinks form there is a series of computed text lines inside the "StoryBlockOutline" div. The fourth computed text line is the line that loads the SubFormDisplayComments subform which displays existing comments, and the next line is the line that loads the SubformEnterComments subform that displayes the text area for entering comments.

The SubformEnterComments subform computed text is simply:

@If(kwdAllowComments = "1";"SubformEnterComments";"");


This line loads the SubformEnterComments subform if the Allow Comments keyword (kwdAllowComments) is set to "1" (Yes). This is what I want, and is simple. No need to change anything here.

The SubFormDisplayComments subform computed text is:

@If(kwdAllowComments = "1";"SubFormDisplayComments";"");


As stated earlier, I want the SubFormDisplayComments subform to show if either the Allow Comments keyword is set to Yes ("1"), or if there are any responses to the entry - regardless of the Allow Comments setting. Therefore I changed the computed text formula to the following:

@If(@If(!@IsNumber(ResponseCount); 0; ResponseCount) > 0 | kwdAllowComments = "1";"SubFormDisplayComments";"");


This formula first checks to see if the ResponseCount item is a number - if it is not it provides a 0 value for the formula. Then the code compares the value of the ResponseCount item, and if it is greater than zero OR if the Allow Comments choice is set to Yes ("1"), then display the SubFormDisplayComments subform; otherwise don't display anything. This formula does exactly what I want.

The next small thing to do is to write a one-line agent to set the kwdAllowComments item to "0" in all selected documents. This formula is also simple:

FIELD kwdAllowComments := "0";


I named this agent "Admin\Disable Comments on Selected" so that it shows up in my Admin action choices. It works great.

Conclusion
This is a simple modification to the Blogsphere 2.x template to allow you to easily disable comments on existing entries and have it continue to display responses if present. Remember, this feature (and so much more) is a part of the upcoming Blogsphere 3.0, so if you want you can wait until then. However, if you desire this functionality now, this should provide it to you.

Note: I want to thank Declan Lynch for quickly telling me where to find the correct line in the form, saving me a ton of searching. Thanks Dec - and thanks for your continued inspired work on Blogsphere.

Rock
**If you ever drop your car keys in molten lava, let them go, because, man, they're gone.

11/16/2006

Show n Tell Thursday: getElementHandle function

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 



UPDATE: I have updated this function to a more efficient version. Please check out this post for the details.

This is a little function I use that I thought I had blogged about before, but I couldn't find a full blog entry of mine that explained this function. The getElementHandle function is used to get a NotesDocument handle to a design element. Remember, all design elements are simply "notes", or documents, underneath the hood. They have special properties and items in them that define them as design elements, but in a general sense they are simply documents. Knowing this opens up many intruiging and powerful possibilities to the creative - ways to manipulate design elements in newfound ways, new ways to access, modify, or store information, and much more.

getElementHandle(db As NotesDatabase, etype As String, ename As String) As NotesDocument has the following parameters:
  • db - a NotesDatabase handle to the database contained the desired element
  • etype - a string containing the type of the element you want; if not in the following list, or blank, then all design elements are searched
    • acl
    • actions
    • agent
    • dbscript
    • dcr
    • document
    • folder
    • form
    • frameset
    • about
    • help
    • using
    • icon
    • image
    • javaresource (applet resource)
    • misccode
    • miscformat
    • miscindex
    • navigator
    • outline
    • page
    • profile
    • replicationformula
    • scriptlibrary
    • sharedfield
    • stylesheet
    • subform
    • view
  • ename - name of the desired element; this can be the name or an alias of the element

The function builds a NotesNoteCollection based on the type of design element (etype) you are searching for, then searches the collection for the desired element by name. Once that element is found, the element is returned as a NotesDocument object.

Now that you understand the basics and parameters of the function, let's take a look at the function itself...

08/26/2006

(SnTT) NaSS Installation Toolkit, Part III: getElementType function

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


Note: This is Part III of a series.

  • Part I can be found here
  • Part II can be found here


In the last installment I explained the Generate Design Element Docs agent, and we looked at how it works overall. This installment is diving into two particular functions used in that agent - the getElementType and getUseCases functions.

getElementType function

The first function, getElementType is a generic function I created (with tremendous help from Henry Newberry and Bob Balaban) that will take a look at design element and determine what it is. In MOST design elements there is a special item called $Flags. This item contains one-character flags that indicate a myriad of things about an element - does it have prohibit design refresh/replace flag set, is it formula or lotusscript, etc. However, most elements DO NOT have a flag that says simply "hey, I'm a form!" or "hey, I'm a view!". In fact, those two elements may not even HAVE a $Flags item! Therefore determining exactly what a design element is can be a tricky proposition, which makes this function a bit more involved.

Let's take a look at the function itself, then we'll dive into some of the design decisions I made.

NOTE: The in-line CSS version of this code was too big for the HTML conversion routine in Blogsphere - it was over 32K. So, I had to post it as a graphic so you could see it all. So, to make sure you get the code it is a part of the DesignFlags.txt file available in the Downloads area.


getElementType.gif

The first thing you may notice that is weird is the colon (":") that I use at the end of many lines. That is an OLD VB syntax that allows you to string together two lines of code on one line. I could have used the traditional format of wrapping those with an End If, but there were so many I thought that this convention lent itself to easier reading.

The beginning of this code checks the flags for obvious ones that define what the element is. If a flag is found, then that flag is returned and the function exits (what's why I use the colon). Notice that the flags are in a certain order, too. If it is an "easy" flag, where the flag defines the element outright, then I use the colon/Exit function call. If, however, the flag is one of the ones used for a view, then there is a bit more work to be done.

For a view there is no single flag that defines it. A view can have one of many flags, or more than one flag. Since there can be more than one flag for a view, I have to use a type of "fall-through" code whereby the flag result will be the last flag I encounter. In this case I didn't add code to figure out all permutations of views (calendar, outline, private, public, shared-private on first use (SPOFU) stored on server, SPOFU stored on desktop, etc.), only the ones I care about - but you could easily add those additional values if you wanted.

After views I check for agent flags. Agents are even trickier than views, because they can be multiple languages, they can be scheduled or triggered, and so on. I have a small if statement that first determines the language of the agent, then it determines if the agent is scheduled or run-as-user. I concatenate these values together to build my agent description.

The next set of If statements is used to check for a Form. There is no single flag that says, "hey, I'm a form"; instead there are certain flags that only forms have, or a form may not have ANY flags. I check for the form-only flags (C, D, V) and set the results appropriately.

If there is no flag, then I have to do a bit more detective work. I check for a couple of items that are either form or view specific. For forms, I check for $FIELDS or $$FORMSCRIPT. For views I check for $VIEWFORMAT.

At this point if I haven't determined some result set, then I have no idea what I am dealing with - and I simply return *UNKOWN*. To test this I ran it over and over until I didn't get an *UNKOWN* anymore - and if I did, then I determined what element was unknown and I looked for some distinguishing characteristic I could use to determine what it is.

Before we get off of this topic and move onto the next function, I want to answer that question that's burning in your mind right now - "This is all great, Rock, but how the hell do I KNOW what all the flags are? Is this some voodoo, ancient geek wisdom you have?" Of course not, silly rabbit. I got Bob to give me the relevant part from the file stdnames.h, which is used in C API programming. In order to make it easier for you, I have created a simple text file that contains the relevant design flag information as well as the help about design flags from the C API Help. You can download this text file from here, which is in my Downloads area.

Conclusion
I think this function is pretty kewl, and really opens up a bunch of possibilities for deconstructing and documenting your code. It can easily be expanded to support more design element types - and if you do so, please share with the class so we can all benefit, ok? Additionally I would love to hear of any uses you find for this function - and I think you'll find quite a few.

Rock
**You know, I spent a fortune on deodorant before I realized that people didn't like me anyway.

08/16/2006

(SnTT) NaSS Installation Toolkit, Part II: The Generate Design Element Docs agent

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


(Note: This is Part II of a series. Part I can be found here.)


In Part I I introduced you to the Notes access for SAP solutions (NaSS) Installation Toolkit, which is a part of the upcoming Notes 7.0.2 release. In this second part I am going to explore the Design Element Doc feature, and how the Design Element Docs are automatically generated.

Overview
The Installation Toolkit contains a set of documents known as Design Element Docs. These documents represent every design element that is a part of the various featuresets contained in NaSS. This is very useful to quickly determine what elements are contained in each featureset, and also what elements are contained in each template. Both modified elements (i.e. elements that are modified from an original template design element) and elements that are brand new are documented as Design Element Docs.

There are multiple views that are used to categorize and sort NaSS design elements, such as the By Featureset view displayed below:
By Featureset view

Additional views include By NaSS Name, By Template, and By Type.

The Design Element Docs contained in these views are displayed as below:
Design Element Doc

The information catalogued in the Design Element Doc includes:
  • Element type - the design element type, such as Form, View, etc.
  • Template name - the filename of the template containing the element
  • Element name - the name of the design element
  • Alias(es) - list of alias(es) for the design element
  • Featureset(s) - the featureset(s) that use this element; this can also be "All Featuresets" or "Original Design Element"
  • Modified date - the date the design element was last updated
  • New/Modified - whether this is an original design element for NaSS, or it is a modified element from the template
  • NaSS name - the special designation for the element that is used to identify and catalogue it in NaSS; this is stored in the Comments for the element
The NaSS Name has a special format so that it is easily parsed. The parts of this name are as follows:
NaSS.ElementName.NotesVersion.FeaturesetDesignator

  • NaSS - indicates it is a NaSS element
  • ElementName - a name to easily identify what the element is; usually based on the element name, but not always
  • NotesVersion - the version of Notes this element is for; for instance for Notes 7.0.2 this would be 702
  • FeaturesetDesignator - indicates the featureset(s) that use this element; one character for each featureset, such as T for Time Reporting, V for Vacation/Leave Request, and so on


Now that you understand how we catalogue and identify a design element, let's take a look at the code used to generate these documents...

08/10/2006

(SnTT) NaSS Installation Toolkit - Part I, Introductions

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


For the next few Show-n-Tell Thursday posts I am going to spend some time introducing you to a new feature shipping with Notes access for SAP solutions (NaSS) - the Installation Toolkit and the Template Builder. There are some interesting technologies being used in this new feature, and I thought it would be fertile ground for at least a few SnTT posts. Today's post will simply be an introduction to the Installation Toolkit - what it is, why it is, and how it works from an end-user perspective.

Background
Notes access for SAP solutions is a new feature introduced after Notes 7.0.1 shipped. It is a template-based feature that contains five featuresets (or scenarios, or use cases - I use these terms interchangeably) in the 7.0.1 code:
  • Contact Management
  • Time Reporting
  • Vacation/Leave Request
  • Report Sheduling
  • Workflow

These five scenarios are contained in two templates - a modified version of the Mail 7 template, and a modified version of the Personal Address Book (7) template. So far, so good.

In 7.0.2 it gets more interesting, because there are three additional use cases:
  • HR Self Service for Managers
  • HR Self Service for Employees
  • SAP Business Activity/Calendar Scheduling

In the past when new template based features were shipped with Notes, it wasn't a big deal because there was a 1:1 relationship between the featureset and the template (e.g. Discussion template, Teamroom, etc.). But now we have a situation where it is a (>1):1 relationship - there is more than one featureset in a given template for NaSS.

This then begs the question - What if I don't want to deploy all of the use cases?

In 7.0.1 you have to follow a series of steps, outlined in the help file, that walk an experienced developer through the process of identifying and manually removing the design elements for the undesired use case.

But in 7.0.2 we wanted to make that process easier for our customers - and I responded to that desire with the concept of the Installation Toolkit , and within that toolkit the Template Builder wizard.

To begin, let's take a look at the Installation Toolkit...

08/03/2006

SnTT (yeah, I remembered ;-) ): Couple of Notes frameset tips

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


During my time in the code-monkey-cave, I discovered a couple of very painful things when working with framesets in Notes (I emphasize Notes because when I mention framesets people inevitably miss the fact that I mean framesets in Notes, and assume I mean frames on the web. Silly readers - but not you, of course - it is those other silly readers. But I digress...)

Problem 1: Single clicks when I don't want single clicks!
I have a form that displays in the right frame of a frameset - call this frame "MainFrame". In that form is a tabbed table with 4 tabs. On the third and fourth tab there is an embedded view. I use a bit of trickery to cause those documents in the embedded views to be open in small dialog boxes, not in the main frameset. So the main doc stays open in the frameset, and the embedded view docs open in dialog boxes. As a side note, this doc is never viewed in a view - there is an outline entry that launches this doc into the frame, and that is the only way through the UI to get to this doc (hence there is only one of this doc in the db). Got the picture?

The problem is that when you navigate to one of the embedded view tabs, the first document in the view opens automatically!! No clicking at all! If you switch tabs a few times it quits doing that, but then whenever you single-click on a document in an embedded view it launches it. This is NOT intended behavior.

Cause and Solution
Well, I talked it over with John Head, and we found the problem - I had set the "Default target for links in frame" property to itself ("MainFrame") for this frame. This seems to cause the problem I was experiencing. Changing this property to nothing fixed the problem. John said that in general you should never set a default target frame to itself, and based on this evidence I think that is good advice.

Time to move on to problem #2...

Problem 2: Prompting to save when I open an embedded view doc?
OK, picture the same form/frame/embedded view/dialog scenario as above. Now we're placing the doc into edit mode, and it is still in the frame ("MainFrame"). All is well so far. We click to one of the embedded view tabs - since we fixed the problem in #1, it works fine. Now we double-click an embedded view doc to open it (remember it opens as a dialog box) - and we're prompted to save the current document. Huh?

Cause
This one actually makes sense, once you think about it carefully. You have a document being displayed in a frame, in a frameset. That frameset is in a Notes window tab (those things across the top of the notes client showing all the crap you have open). When you change the state of the document to edit mode, it is still in a frame. The frame can only have one document open at a time - it doesn't open new Notes window tabs like non-framed dbs. When you double-click a document, the Notes client assumes that it is going to open the document into the same frame; but you've got that doc in edit mode, and you have "clicked" something to "dirty" the document, so it must close the current document to show the new doc. Therefore it needs ask us to save the document before it can continue and open the new doc. And even though we're opening the new doc into a dialog box, it doesn't know that at the time - it (the client) thinks it has to replace the current doc with the new doc, so it needs to get you to save or dismiss it to open the new doc. Make sense?

And before you ask - that prompt to save occurs before the Initialize event of the embedded doc form fires, so there really is no clean way to do something like set SaveOptions = "0" temporarily.

Solution
My solution was the cleanest way I could find to get around the situation. When the user places the document into edit mode I force the edited version of the document to be open outside of the frameset, in its own Notes window tab. To accomplish this I did the following:

I placed the following code in the QueryModeChange events of the form:
If Source.EditMode = False Then Continue = False Call ws.SetTargetFrame("") Call ws.EditDocument(True, thisdoc,,,False, False) End If

This intercepts the editing of the document and forces it into a new Notes window tab by setting the TargetFrame = "". Also notice that the newInstance parameter (the last parameter) of the Call thisuidoc.EditDocument method is set to False. By setting this to false, the user cannot create multiple edit-mode instances of the document; if he tries, it will simply switch focuse to the current edit-mode version.

Now, as I mentioned earlier this doc is never viewed in a view - so I know that when the user sees this doc, it is always in read mode at first. The doc is created by an automated process (not composed by the user), and is access through an outline entry item that sets the doc into the frame. If, however, you wanted to use this technique in your app you can also place that code snippet into the QueryOpen event of your form as well, and it should work fine.

Conclusion
Hope this helps someone later on who struggles with the same issues with framesets in Notes that I had. These were a PITA to chase down, and by putting this out into the geek community hopefully I can save others the same pain.

Rock
**All good things arrive to them that wait -- and don't die in the meantime. -- Mark Twain

06/14/2006

SnTT (a day early): Problem with NotesSession.AddressBooks, and a workaround

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

During my development of Notes access for SAP solutions I found a problem with the NotesSession.AddressBooks property. I have SPRed it (no update), but I wanted to make you aware of it and give you a way to code around it (in case you find this problem in your code). Here's a description of the problem.

The Problem
If you are using a Location document that lists a home server (i.e. NOT disconnected), and your Notes client is unable to reach that server, then the NotesSession.AddressBooks property is EMPTY. This is unexpected; if the server is unavailable in any situation, I would expect the AddressBooks property to be populated with the local address book at a minimum.

To reproduce:
1. Create a location for yourself that points to a server (Server tab, home server) you are not currently able to access (or a "dummy" entry).
2. Add the following code to an agent (target == none)
Dim s As New NotesSession Dim abooks As Variant Dim pernab As NotesDatabase abooks = s.AddressBooks If Isarray(abooks) Then Msgbox "NotesSession.AddressBooks has " & Ubound(abooks) + 1 & " elements",,"Kewl" Else Msgbox "NotesSession.AddressBooks is NOT an array - it is empty",,"Bummer" End If

RESULT: If you have an invalid/unavailable server listed for your home server, then AddressBooks will not be an array, and will be empty. If you do have a valid server as a home server, you'll have at least 2 elments in AddressBooks (the server NAB and your personal NAB).

Now, let's take a look at the workaround.

The Workaround
Basically the workaround requires a bit of defensive coding - the code below is an example of this. Now, you can easily wrap this in a function (maybe called getPersonalAddressBook() or something similar) and make it a part of your LotusScript utilities, but I'll leave that as an exercise for the reader

NOTE: This is a code snippet - assume Option Declare and all variables are Dimmed:
If Isarray(s.AddressBooks) Then Forall db In s.AddressBooks If db.isPrivateAddressBook Then Set pernab = db Call pernab.Open("", "") Exit Forall End If End Forall Else ' try to get the address book from the Notes.ini names param Set pernab = s.GetDatabase("", s.GetEnvironmentString("NAMES", True)) If Not(pernab.IsOpen) Then ' not found that way, so try it with a manual setting Set pernab = s.GetDatabase("", "names.nsf") End If End If ' if it still isn't set here, then give up If Not(pernab.IsOpen) Then Error 1002, "Unable to open Personal Address Book"

This code is used to get the Personal Address Book for the user. It first checks to see if AddressBooks is valid; if so, use the traditional method of checking db.isPrivateAddressBook property. If it is not an array, then try the NAMES value from the Notes.ini; if that doesn't work, make a last-ditch effort by getting the NAMES.NSF from the root data dir. If all fails, throw an error.

Hope you find this useful! Incidentally I'll be in Westford tomorrow and Friday, so that's why I wanted to get my Show-n-Tell Thursday post in early for this week.

Enjoy!

Rock
**If all the cars in the United States were placed end to end, it would probably be Labor Day Weekend. --Doug Larson

06/07/2006

Update on my $KeepPrivate posting: Chris Linfoot...

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


Chris Linfoot posted a nice little elaboration/corollary to my recent Show-n-Tell Thursday $KeepPrivate post. He provides a scenario where $KeepPrivate makes sense. As he states...

It is sometimes necessary to send an email to all or a large subset of all users, warning about some new security issue. This should very rarely be called upon - you should certainly not email all users just because there's a new variant of Sobig (1) circulating - but it cannot be ruled out entirely.


More details at the entry, so go read it. Good stuff.

Rock
**There are two major products that came out of Berkeley: LSD and UNIX. We don't believe this to be a coincidence. - Jeremy S. Anderson

06/01/2006

SnTT: Prevent copying/printing of emails - Another pet peeve of mine

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


Last week I told you about my biggest pet peeve with emails - Return Receipts. I gave you some nice code that can be used to identify and remove return receipts at your leisure. This week I'll cover another email pet peeve of mine - emails that have been tagged to prevent copy/print/forward/etc. This is almost always done by someone who thinks you'll forward the email around - and it usually contains something incriminating. It is a pet peeve of mine that someone doesn't trust me enough to be discreet. If you want to send me something and you think I won't know to keep something private, then either tell me explicitly in the email "HEY, don't share this with anyone", or better yet DON'T SEND THE EMAIL. Everyone should operate under the assumption that EVERYTHING you put down in an email is subject to being shared with the world at some point. If you are considering sending something that sensitive via email then I suggest you don't, and talk to the person on the phone or in person instead. Now let's explore this feature and how it works - and how you can disable it.

$KeepPrivate
The field under the hood that prevents copying/printing/forwarding of an email is called $KeepPrivate. This item can get set in a document in a few ways:
  • Enable the "Prevent Copying" option in Delivery Options of an email, as shown below:
    Prevent copying
  • Set the "Disable printing/forwarding/copying to the clipboard" property in the Form properties box:
    Disable printing/copying/etc.
  • Here's one that bites people in the butt sometimes - if you have someone listed in the ACL, and they do NOT have "Replicate or copy documents" selected for their ACL entry, then this field will be set for docs they create:
    ACL - Replicate or copy docs
  • Of course you can maually set the $KeepPrivate item to "1" programmatically, like through an agent
The "Replicate or copy documents" option has caused many people problems in the past, for a variety of issues (search the KBase for more information). For this particular topic, if this option is NOT checked for a user, then the $KeepPrivate field item set to a value of "1" will be added to all documents created by that user. If you don't know this, then troubleshooting this problem is a big PITA. Hopefully this will help you avoid this pain.

I discovered one interesting bit of information while testing for this blog entry - there is another value besides "1" and "0" for $KeepPrivate. Under certain circumstances $KeepPrivate will be set to a value of "2". I talked with my colleague Andre Guirard and he discovered that the value "2" is the same as "1" except that "2" allows the document to be resent. I found this "2" value when I created a new memo and then saved it as a draft - the value is set to "2". This makes sense, because this allows me to edit the document again and send it without needing to reset the "Prevent Copying" feature. The $KeepPrivate value will get set to "1" as it is sent.

Disabling $KeepPrivate
So now you've have an email that someone has sent you, and they have $KeepPrivate marked on the email. You don't particularly like this person, and you want to make sure their boss knows what they sent. Simply run the following one-line agent against the email and you will be able to copy, forward, or print this email to your heart's content:

FIELD $KeepPrivate := @DeleteField

You could also set up something similar to what I set up for ReturnReceipt = an icon in your Inbox and an action with the above code to remove the tag. I use the "secret agent man" icon (number 163) for KeepPrivate notification, but you can choose your own.

Additionally Chris Toohey has blogged about this in the past (way back in 2003) as well - he gives you some nifty code to place in a smarticon, if you wish. You can read his suggestion here.

Conclusion
Hope you enjoyed this little tour of $KeepPrivate - what it is, how it works, and how you can remove it. Remember, $KeepPrivate is NOT a security feature (as stressed in this technote), and if you really don't want someone mishandling your information, then don't send it in an email.



Rock
**Why does a round pizza come in a square box?

05/25/2006

SnTT: Return Receipts - a pet peeve of mine

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


I have a couple of pet peeves when it comes to email, and I want to cover one of them today - and what I do about it. I really hate receiving emails with Return Receipt enabled. I dislike Return Receipts because I feel like the person believes I won't follow up in a timely manner - to me return receipts are generally rude. Now I realize that sometimes they are helpful, such as when you are having mail delivery problems and you want to ensure delivery; but in general I think they are rude. So, I remove the thing that irritates me - and if it irritates you, I'll show you how alleviate it too.

Removing Return Receipts
I have modified my Inbox to support the detection and removal of Return Receipts in a simple fashion. When I receive an email with a Return Receipt I see the following icon in my inbox:

Bomb icon indicates return receipt
The bomb icon indicates return receipt has been enabled for this email

To remove the return receipt, I simply select the email and choose Tools/Remove Return Receipt..., as shown below:

Remove Return Receipt action
The action Remove Return Receipt removes the return receipt flag before I open the email

Here's how you can add the same functionality to your inbox as well.

First, let me recommend that you make these changes to the TEMPLATE from which your mail file inherits its design. If you attempt to change the Inbox folder directly in your mail file it will "break" the automatic delivery of mail into your inbox. If you make the change to the template, then refresh the design, it continues to work as expected.

To add the bomb icon to the inbox folder follow these simple directions:
  • Edit the Inbox folder in Domino Designer
  • Select the 12th column, which is the small one next to the Subject column
  • change the first line from

    @If(@TextToNumber(@Version) > 122;

    to

    @If(@IsAvailable(ReturnReceipt) & ReturnReceipt = "1"; 91; @TextToNumber(@Version) > 122;

To add the Remove Return Receipt action follow these simple directions:
  • Go to the Actions pane in the Inbox folder design in Designer
  • Expand the Tools action
  • Select the "Calendar Cleanup" subaction, and right-click it
  • Select "Create Action...". The new action should appear right below the Calendar Cleanup action
  • Name the new action "Remove Return Receipt..."
  • Add the following formula to the action formula:

    FIELD ReturnReceipt := @DeleteField

  • Save and close the Inbox folder
  • Refresh the design of your mail file to move your changes over

That's it! Now, there are many ways to do this - and if you would like to share, I'd love to hear how you handle this in your own mail file.

Enjoy!

Rock
**Wouldn't it be nice if whenever we messed up our life we could simply press Ctrl Alt Delete and start all over?

05/11/2006

SnTT: A couple of quick Date suggestions

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


No, you lonely geeks, not dates as in "someone to go out with me", but dates as in date/time stuff in Notes/Domino. I have been working a great deal lately with the calendaring and scheduling system in Notes mail, and along the way I came up with some nice ways of accompishing simple tasks for date/time manipulation - and I thought I'd share.

Suggestion 1: Setting a "nice" default value for a time field
When creating time fields on a Notes form many people place @Now in the default value event. This is fine, but it has the unfortunate side effect of giving you a default value that typically ends in a number other than 0 or 5. When this happens it has a couple of side effects:

1) if you also have an ending time field and a duration display, it may make the duration display look wierd (like "2.23 hours" or something).
2) it makes the little "helper" widget harder to use, because the lowest granularity you can get on it is 0 or 5 minutes - for instance you can carefully go from 1:10 to 1:15, but not 1:13.

So, I think it is nicer to have the default value end in the next five minute increment. Say, if the current time is 3:13 then I want the default value to be 3:15. I developed a nice, small formula that does this:

curdate := @Now;
@Adjust(curdate; 0;0;0;0; 5-@Modulo(@Minute(curdate); 5); 0)


This formula captures the current date/time, and then uses @Adjust to adjust the minutes the appropriate amount to get to the next 5 minute interval. The formula that computes how many minutes to adjust the time is the really pretty part of this solution - using @Modulo. For you non-math geeks out there, the modulo of an expression is the remainder of a division expression. So, in my previous example, if the time is 3:13 The formula would look something like this, working from the inside-out:

5 - @Modulo(@Minute(curdate); 5)
== 5 - @Modulo(13; 5)
== 5 - (13/5)
== 5 - (2, modulo 3)
== 5 - 3
== 2


Which means that the value will be adjusted 2 minutes ahead, making 3:13 into 3:15 - which is exactly what I want. Incidentally, this will work if you want other increments as well, such as quarter-hour. Simply replace the 5s in the formula with 15s.

Suggestion 2: Quick and dirty ways to compute number of days between two dates
Awhile back I need to quickly compute the number of days between two dates. I didn't need to know weekends or anything, just simply the number of days. Here's the quick-n-dirty way to do that in LotusScript:
numdays = Abs(Cint(Datevalue(date1.DateOnly) - Datevalue(date2.DateOnly)))

Where date1 and date2 are NotesDateTime objects containing the desired dates.

The beauty of this code is it doesn't matter which date is first, because of the Abs function, which returns the absolute value.

You can do the same thing in @Formulas as well. Now, if you need the business days between two dates, don't forget you can use the handy-dandy @BusinessDays function to accomplish that; the code below is for simply determining the total number of days between two dates.

numdays := @Abs((date1 - date2)/86400) + 1

Where date1 and date2 are date/time values in Formula language

You need to divide by 86400 to convert from seconds to days.

Conclusion
I know it isn't much, but these are the types of formulas that you need on occasion and sometimes it is simpler to Google for them rather than developing them yourself.

Do you have a similar quick-n-dirty date/time trick of your own? Please share!

Rock
**I always wanted to be somebody, but I should have been more specific.

05/03/2006

Quick tech post - updated DetachFile function

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


First, let me apologize for not posting the last few days. I have been extremely busy with my project, but I am nearing the end of the effort (woohoo!) I spent the last few days knocking out SPRs, and I feel really good about what we've accomplished - stay tuned for more info on that front.

Today, however, I want to share some code that I meant to post last week as my Show-n-tell Thursday post - but simply didn't have the time. I took what I mentioned in I found about attachment names in rich text items from last month (the fact that the internal name could be different than the "displayed" name, which could break code using NotesRichTextItem.getEmbeddedObject), and integrated it into my detachFile function - and here's the results.

First, here's a brief explanation of the DetachFile function to explain what it does and the parameter it takes.

Function detachFile(doc As NotesDocument, rtname As String, fname As String, fpath As String) As Boolean
   -- Purpose: Detaches a file to the specified location
   ** doc == the document containing the attachment
   ** rtname == the name of the rich text item containing the attachment
   ** fname == name of the file to detach. OPTIONAL. if blank, gets the first attachment in the rtitem
   ** fpath == where you want to place the detached file. OPTIONAL. if blank, detaches the file to a subdir (LotusGeekTEMP) under the windows TEMP dir. also, the full path to the file is returned on this parameter


Now that you understand what the detachFile function is supposed to do and what parameters it takes, let's take a look at the code itself...

04/20/2006

SnT2: Programmatic tables for hiding other complex tables

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


Today's tip may be another one of those "duh" posts, but that's ok - this will hopefully help some of our newer Domino developers out there at a minimum, so in that light it should prove useful.

Here's the scenario: you're working on a fairly complex Notes client form. It is broken up into tables, and those tables are sometimes nested into other tables - for formatting, etc. Inside one of those "inner" tables you have multiple rows and columns, and inside those you have fields, graphics, etc. Much of that has formula-based hide-whens on it - "if this field is that value, hide these" - that kind of stuff. Everything's working nicely, and then a new requirement emerges (or a forgotten requirement resurfaces):

You need to hide the entire "sub" table when certain conditions exist - with all those formula hide-whens, etc.

Selecting the entire table and setting a global hide-when won't work - it will mess up your existing hide-whens you've spent all that time perfecting. So, what to do?

Programmatic tables to the rescue.

Programmatic tables. Remember those? They're tables that allow you to hide rows based on a field value. They are most often used in such things as dialog boxes, because you can easily create "Back" and "Next" style dialogs with them. Well, they will also work in this case.

Here's an example. I am working with the Calendar Entry form, adding a new section for the project on which I am developing.

Calendar Entry - with Time Recording

You can see my new section in the graphic above. When the user checks the "Report Time" checkbox, the rest of the table "expands" (appears). When I first added my new section, I thought it would be available for all Calendar Entry types. Well, turns out that when anything other than Meeting or Appointment are chosen, it needs to disappear, like below:

Calendar Entry - no Time Recording

So, you see my dilemma. Now, let me show you how to do this without needing to touch all of your hide-whens, using programmatic tables.

04/13/2006

SnT2: Quick one today: Faster tests for empty strings

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


I was fumbling around for an entry for Show-n-Tell Thursday today, and decided that I could enter a small one today since I did a big one last week. I wasn't sure what I would put out for today, and then Bob "LotusScript Daddy" Balaban gave me a great one during the course of our Codefest today - and it was one I never knew.

Bob informed me that when you test for an empty string, to use Len instead of testing for an empty string. So, if you've been using this:
If somestr = "" then .... End if

You should be using:
If len(somestr) = 0 then .... End If

According to Bob, he has tested this and it turns out that using Len to test for an empty string (i.e. Len(somestr) = 0) is 4-5 times faster than testing directly for an empty string (i.e. somestr = ""). Bob explains that it is faster because the length of a string is stored with the string struct in LotusScript, so all you're checking is that length value, and it doesn't have to compute anything. If you do the other way (i.e. somestr = "") then the interpreter actually has to do a compare of the two strings in memory, which is extra work.

So, for all you LotusScript speed monkeys out there, here's another way to tweak some extra cycles out of your code.

And many thanks to Bob for his insight.

Rock
**When cryptography is outlawed, bayl bhgynjf jvyy unir cevinpl. -- Anonymous

04/06/2006

SnT2: Keyword "More..." Feature, Part II - How it Works

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


(ED NOTE: Part 1 of this post was made yesterday. Please read it before continuing - you can access it here.)

When we left yesterday's post I had explained the basics of the fields involved in this feature - I explained how the fields get the "default", or cached values, and I explained how I am able to set a new value into those fields using the _NEWVAL and _LIST fields for each keyword. Today I'm going to explain how the More... feature itself works.

Before we dive into the code I recommend you download the "Keyword More Button Example" database and related files from here and follow along at home. Got it? Good. Before you can actually test this technique you need to perform a little bit of setup. In order to simulate pulling data from an external source I have included a small Excel spreadsheet with some dummy names and addresses in it. I set up an ODBC connection to this spreadsheet, and I use LS:DO to simulate pulling this data from an external source. Simply place the spreadseet somewhere where you know where it is on your hard drive, and then set up an ODBC user data source in Windows (the ODBC setup interface is found in the Administrative Tools area on WinXP Pro). Make sure the data source is named "Customer2Example" as shown in the graphic below.

ODBC setup for Customer2 Example

Once you've got that set up you're ready to move on.

As I explained yesterday, when the user chooses "More..." in either the CompanyName or ContactName keywords she is presented with a dialog box containing all choices from our external source (the Customer2.xls file via ODBC in this case), similar to the graphic below.

CompanyName - More... list.jpg

This is accomplished by placing code in the onChange event of the keyword field. Yes, you read right, the onChange event - in the Notes client you can place LotusScript or JavaScript code in the onChange event and it will execute. If you simply place code into this event without doing anything else, however, you will find that it does not actually execute until the user leaves the field -similar to the way the Exiting event works. We are fortunate that there is a field property available to us on the Advanced (i.e. "propellerhead") tab - the "Run Exiting/onChange events after value change" property, as shown below:

CompanyNameAdvanced Properties

This property will cause the onChange event to fire when we expect it to - when the value in the event is selected.

If you look at the onChange event for both the CompanyName and ContactName fields you'll find that they both contain the same single line of code:

Call handleFldMore()

This function resides in the LS.KeywordMoreExample.BE.6 script library. Let's take a look at the "meat" of the subroutine.

04/05/2006

Show-n-Tell Thursday Preview: Keyword "More..." Feature, Part I - An Introduction

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


The Show-n-Tell Thursday article I am presenting tomorrow demonstrates a new technique for displaying keywords. I wanted to use today's post to lay out the background, and then tomorrow's post will actually dive into the technique and code. Let me begin by laying out the scenario.

I have a Notes client application that contains multiple keyword fields. Some of the keyword fields are "cascaded", or once the "first" keyword of the sequence is chosen there are one or more subsequent keywords in the sequence whose choices are dependent upon the first keyword. In this particular case the keywords are "self-generating" - the choices provided for the keywords are chosen from previously created documents and the choices therein.

So, let's begin with a simplistic example. There are two keywords on the form - CompanyName and ContactName. The ContactName choices are based on the chosen company, and to prevent the user from attempting to choose the ContactName before the CompanyName is chosen I've entered the following in the new Input Enabled event of the ContactName field:

CompanyName != ""

The Input Enabled event is a formula event in the same vein as the Default Value, Input Translation, and Input Validation events. If the supplied formula evaluates to @True, then the field is enabled; if it is @False, the field is greyed out and is inaccessible.

Earlier I indicated that the choices are based on values stored in previously created, existing documents. Normally when keyword values are supplied this way the keyword property "Allow values not in list"; however this presents problems, most of which revolve around consistency of data. For example let's say there's a country field on a form; for the United States you may have values such as US, USA, United States, United States of America, etc. - that means no consistency.

But don't fret - we're not going to use this property after all.

In this case the existing documents are providing the "default" list of choices, like a local cache of chosen values; the full list actually resides in another, external data source. When the user Clicks on the keyword helper button she is presented with a list of values, and at the bottom of the list is a choice of "More...", as shown in the figure below.

CompanyName - More...

As you can see the cached values from the existing documents are listed, as well as More.... If More... is chosen then the user is presented with a list from the external source as shown below:

CompanyName - More... list choices

Once the user makes a choice it is set into the field:

CompanyName - More... choice set into field

The ContactName field works the same way, except that the choices are based on the CompanyName field value. When the user first clicks the ContactName field, the choices presented are from the existing documents (the "cache") with the same company name; if the More... choice is selected then the user is presented all available choices for the given company from the external data source.

But we don't have "Allow values not on this list" selected in the keyword field - so how can we set the value to a value not on the list? We have to make the list itself contain the new value - and here's how.

The choices formula for the CompanyName keyword field is the following:

@Unique("" : CompanyName_LIST) : "More..." And hidden at the top of the form are two fields: CompanyName_LIST and CompanyName_NEWVAL. The CompanyName_NEWVAL field is above the CompanyName_LIST field, and is a computed (C) text field set to itself.

The CompanyName_LIST field is a multivalue computed-for-display (CFD) field with the following formula:

list := @DbColumn("" : "nocache"; ""; "KeywordMoreExamples.all"; 1);
list := @If(@IsError(list); CompanyName_NEWVAL; list : CompanyName_NEWVAL);
@Sort(@Trim(@Unique(list)))


So when a value is chosen using the More... feature it is set into the CompanyName_NEWVAL field, which in turn is added to the CompanyName_LIST field, thereby making it available for selection (or setting) in the CompanyName keyword field.

Likewise the ContactName field is fed by two hidden fields - ContactName_NEWVAL and ContactName_LIST. The ContactName choices works identically to the CompanyName choice technique described above, except that the ContactName_LIST formula is a @DbLookup instead of a @DbColumn, as shown below:

list := @DbLookup("" : "nocache"; ""; "KeywordMoreExamples.all"; CompanyName; 2);
list := @If(@IsError(list); ContactName_NEWVAL; list : ContactName_NEWVAL);
@Sort(@Trim(@Unique(list)))


So, there's a brief introduction into the Keyword More... feature. This information sets up the basics for the technique; tomorrow I'll dive into the More... feature itself, and explain how it works.

Stay tuned...

Rock
**My whole career can be defined as 1 part being in the right place at the right time, and 2 parts realizing that I was in the right place at the right time. -- Andrew Pollack

03/30/2006

SnT2: LotusScript Password Generator

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


A regular need for developers and administrators alike is the need to generate complex, unique passwords for things like automated registration applications. Since it is a fairly regular occurance, I found a little VB password generator function written by a guy named Andrew Ells-O'Brien. Since LotusScript is very similar to VB/VBA, it was pretty simple to convert - the biggest item to convert was the error trapping, since it is handled differently between VB/VBA and LotusScript.

The result is listed below. I'll add some comments to it afterward...
Function passwordGenerator(Byval lngLength As Long) As String ' Description: Generate a random password of 'user input' length ' Parameters : lngLength - the length of the password to be generated ' Returns : String - Randomly generated password ' Created : 08/21/1999 Andrew Ells-O'Brien (andrew@ellsobrien@msn.com) ' Updated : Rocky Oliver, Sapphire Oak Technologies (rock@sapphireoak.com) ' made it work in LotusScript On Error Goto errHandler Dim iChr As Integer Dim c As Long Dim strResult As String Dim iAsc As String Randomize Timer For c = 1 To lngLength ' Randomly decide what set of ASCII chars we will use iAsc = Int(3 * Rnd + 1) 'Randomly pick a char from the random set Select Case iAsc Case 1 iChr = Int((Asc("Z") - Asc("A") + 1) * Rnd + Asc("A")) Case 2 iChr = Int((Asc("z") - Asc("a") + 1) * Rnd + Asc("a")) Case 3 iChr = Int((Asc("9") - Asc("0") + 1) * Rnd + Asc("0")) Case Else Error 1000, "PasswordGenerator has a problem." End Select strResult = strResult & Chr(iChr) Next c PasswordGenerator = strResult getOut: Exit Function errHandler: On Error Goto 0 Error Error$ & "(" & Err & ") [in " & Lsi_info(2) & "]" PasswordGenerator = "" Resume getOut End Function

The length of the desired password is passed as the lone parameter to the function (lngLength). This function uses the Randomize statement to generate random numbers - one for each character used in the password being generated. Randomize is "seeded" with a number which initializes the Random number generator so that it can be used with the Rnd function. In this case the Randomize statement is seeded with a Timer value.

Three ASCII character sets are used to generate the password - upper cas letters (e.g. A), lower case letters (e.g. a), and numbers (e.g. 0). As the function cycles through each password character it first randomly picks one of the three character sets, and then picks a random character within that set. Once the character is picked, it is appended to the strResult variable until the entire password is generated, and then that is returned on the function.

This is a handy little function to add to your LotusScript utilities, because when you need it you don't want to spend the time writing it.

Enjoy!

Rock
**Just drive the damn chariot and i guarantee you will win. -- Cecile B. DeMille to Charlton Heston when filming 'Ben Hur'

03/24/2006

Show-n-Tell Thursday (late): Quick-n-Dirty way to build a LS Web Services client

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


Web Services offers a great way to integrate heterogeneous systems in a predictable, straightforward, and loosely-coupled format. However many Domino developers are intimidated by both the complexity of the XML formats used for Web Services (WSDL and SOAP), and also because the talk is mainly of consuming Web Services in Java, which has a nice set of tools for doing so. But I'm here to tell you that Web Services (and XML in general) is not Java only, and it can be used by the average Domino developer to build robust, integrated systems in LotusScript - you just need some help, and this article intends to give you a bit of that help.

What we're going to do is "cheat" - we're going to use VBA to create a set of classes to consume our desired Web Service, and then we're going to move it over to LotusScript. Here is a list of steps we're going to take:
  • Get the Office Web Services Toolkit
  • Find the WSDL link for your Web Service
  • Load it in VBA using Tools/Web References…
  • Test it
  • Copy/Paste into LotusScript
  • Convert from VBA to LotusScript

The first step you need to take is to get the right Web Services Toolkit for your MS Office installation. It can be a bit difficult to find on Microsoft's site, since they are heavily pushing the .NET way to do everything - so to make it easier here are a couple of links to help you out.

OfficeXP Web Services Toolkit 2.0
Office 2003 Web Services Toolkit 2.01

Download the appropriate toolkit and install it - then we can move on. Got it? Good.

Next, you need to find the Web Service Description Language (WSDL) url of the Web Service you wish to consume. WSDL is simply a standard for describing what a Web Service is making available - functions, properties, etc. It is very analagous to the way COM works, where a COM object is "self-publishing".

If you're in a large organization you may have a Universal Description, Discovery, and Integration (UDDI) directory available. This directory can be searched to discover Web Services available in your organization. If one isn't available you'll have to contact the administrator of the back-end you're trying to work with, and ask them for the URL.

If, however, you're learning about this stuff right now and simply need a Web Service to "play with", then a great source for publicly available Web Services is xmethods.net. For this article I am using a fun little Web Service that I found there that converts strings to Morse code. The url for the WSDL of this web service is:

http://www.regomnet.de/morse.asmx?WSDL

Once you have the WSDL URL to your Web Service, we can move to the next step, which is working with VBA to build our Web Service client class.

03/16/2006

SnT2: Launch First Attachment Revisited

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

Last year I wrote an entry about some code I wrote that replaced the Notes feature known as "launch first attachment". I explained the technique I used to Bob Balaban the other day, and he thought it was another "righteous hack", and he suggested I use it for a Show-n-Tell Thursday entry. I remembered I had written it up before, but since that original writeup I have enhanced it a bit, and I have also created an example database to demonstrate it; therefore I think it is time to cover it again.

When designing a form you have the ability to set its launch properties - you can decide what happens when the form is used to open a document. One of the choices available is "First Attachment", as shown in the figure below:

form launch properties

When First Attachment is chosen then the first attachment in the document is opened instead of the form when a document using the form is opened. If the document is opened in Edit mode from the view (such as by selecting the document and hitting CTRL-E) then the document is opened with the form in edit mode (handy for changing the attachment, or changing other field values, etc.). This works great for applications that contain such things as Excel spreadsheets, reports, etc. - things where the "important" information is contained in the attachment, and the database is simply a repository. However, there is a bug with this feature - and it is a bug that has been around for quite some time.

The problem is that certain programs, such as MS Excel and Word, launch but don't maintain focus; while others, such as MS Powerpoint, do launch with focus. The real aggravation with this bug is for users who use Notes open full-screen, and have their taskbars hidden. In this scenario the user will double-click the document to open the attachment, and to them, nothing happens. So, they double-click it again. And again. And then they eventually call support to ask what's going on, only to find out they have opened the same attachment 35 times in the background, all because the attachment didn't have focus when opened (and therefore the user didn't know it was opening). This problem has been around for quite some time (as noted in Technote 1091159), with no plans to fix. But there is a way to give your users the convenience of this feature without the annoying focus problem - and here's how.

First, I recommend you download the example database from here. Got it? Good.

03/13/2006

Show-n-Tell: Another Graphic - RSS!

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


In my previous post where I debuted the Show-n-Tell Thursday button (Show-n-Tell Thursday button) there was a response from Rich Schwartz mentioning that it may be nice to have an RSS button for Show-n-Tell as well. So, based on Rich's suggestion, here's my version: Show-n-Tell RSS button.

Enjoy! Rock
**A true friend is there when you need them. Which is why I prefer enemies, because they pretty much leave you the hell alone. -- Scott Roeben

03/10/2006

Did someone ask for a Show-n-Tell Thursday button?

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


I was catching up on blog reading and found that Ray "Lion King" Bilyk asked for a Show-n-Tell Thursday button. Well, I remembered that I had found this nifty button maker site, so I went there and created one. I then took it into Paint Shop Pro and made things fit better, and this is what I came up with:

SNTT button

Feel free to use this - or make a better one, as long as you share with the class.

Have a great weekend, folks. Two of my daughers, Kelsey and Robin, are in a horse show this weekend, so I am excited. This is our horse's first show (King, ridden by Kelsey), and my youngest daughter's first show (Robin, riding Scarlet). It is going to be fun.

Rock
**If you dance with a grizzly bear, you had better let him lead.

03/09/2006

Yet another SnT2 logo - this time for the ladies...

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

Kathleen McGivney and Susan Bulloch asked me about a Show-n-Tell Thursday graphic that isn't as "boy-centric" - they wanted one for the ladies out there. They suggested the classic Marilyn Monroe over the air grate pic. I sent the request to Scott Good, who did the original graphic - but while I am waiting for him to do his magic I thought I would give it a shot myself. Here is the result:

Show-n-Tell Thrusday graphic for the ladies

Feel free to use this, at least until Scott comes up with a better one...

Rock
**Stop day dreaming about success. Go out and obtain it.

03/09/2006

SnT2: Introducing Quick Merge

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 


This week's installment of Show-n-Tell Thursday introduces you to some example code I have used for many years in my COM/OLE sessions. Previous incarnations of this code was used to demonstrate how you can use COM with Microsoft Word to create a simple merge document from your Personal Name and Address Book (NAB). The design is simple:
  • User selects one or more contacts (i.e. Person documents) in the Personal NAB
  • User runs the Quick Merge agent
  • Quick Merge does some magic
  • User is presented with a blank MS Word document, and depending on the version of Word, has either the Merge Wizard or Merge Toolbar available

Now, I used previous incarnations of this example code to graphically demonstrate that Microsoft has no clue about backwards compatability, and will go about changing parts of the object model wholesale without so much as a warning, much less a migration path. The example given with the code is that the merge object model and interface completely changed between MS Word 2000 and later versions. The merge code in MS Word 2000 will not run at all in later versions.

In the past I had two different versions of this code - one version that worked with MS Word 2000 that I wrote, and one that worked with MS Word XP and MS Word 2003 that was written by John Head. Now John and I have different code writing styles, and I wanted to have a consolidated example; so for this example I have taken these two versions of the code and have combined them into a single agent called Quick Merge. The new Quick Merge agent automatically detects the version and runs the appropriate type of code.

The example code below is not the full agent - I am merely highlighting some of the more important parts of the code. Please download the full example code from my downloads area here.

Have the code now so you can follow along at home? Good - let's begin...

03/03/2006

SnT2: Couple of LotusScript/C API Tips - MakeSureDirectoryPathExists and GetShortPathName

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 



ED NOTE: I know Show-n-Tell Thursday was yesterday, but I had already posted twice yesterday and I wanted to have something for today. So sue me ;) Additionally, I submitted these tips to Lotus Advisor for their Advisor Tips feature, so you may see them printed there as well at some point. **Rock

Here are two tips for LotusScript developers. Both are very useful when working with files and directories, and both will lead you, gentle developer, into the wild and wooly world of C API calls from LotusScript.

Tip #1: MakeSureDirectoryPathExists
How many times have you needed to either prompt a user for a directory path, or have had to construct a path on your own - only to find that one or more of the directories on the path don't exist? Most LotusScript developers will write some type of custom function that explodes the string and checks each directory on the path - not clean, and not fun.

Well, here's an alternative that's clean and fun - MakeSureDirectoryPathExists.

MakeSureDirectoryPathExists is a function contained in all Win32 installs, from Windows NT 3.1 or Windows 95 to the current versions. It is contained in the IMAGEHLP.DLL, and can be declared in your Declarations like this:
Declare Function MakeSureDirectoryPathExists Lib "imagehlp.dll" (Byval lpPath As String) As Long

Using this function is easy. Simply pass the function the path you want to verify, and it will return a nonzero value if successful. Here's a small code example:
Dim mypath as string, retval as Long mypath = "C:\downloads\some program directory\some subdirectory\" retval = MakeSureDirectoryPathExists(mypath)

That's it! No onto tip #2...

Tip #2: GetShortPath
Awhile back I developed a little signature block creation wizard for Notes that prompts the user for some information and creates a custom HTML signature block for them, including a graphic. If the user chooses to add a graphic I need to create an HTML IMG tag that points to the graphic on the user's local hard drive. In order to make sure the link to the image file works correctly I need to convert it from the "long" way of listing a file path (spaces, etc.) to the "short", or 8.3 version. So, I needed to convert it from this:

C:\Documents and Settings\Rock\My Documents\Sig Wizard Files\mygraphic.jpg

to this:

C:\DOCUME~1\ROCK\MYDOCU~1\SIGWIZ~1\TEST\MYPICT~1\MYGRAP~1.JPG

Once again the C API comes to the rescue. The function GetShortPathName is a function contained in all Win32 installs, from Windows NT 3.1 or Windows 95 to the current versions. It is contained in the KERNEL32.DLL, and can be declared in your Declarations like this:
Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" _ (Byval lpszLongPath As String, Byval lpszShortPath As String, Byval lBuffer As Long) As Long
  1. The lpszLongPath variable contains the long version of the path
  2. The lpszShortPath variable contains the short version of the path when it is returned
  3. The lBuffer is the string buffer size needed

However, most of the time you simply want to give it the long path and get the short one back. So, to make it that easy I wrap the GetShortPathName function into my own LotusScript function called GetShortPath, like this:
Function GetShortPath(strLongPath As String) As String Dim retval As Long, strShortPath As String On Error Goto errHandler strPath = String$(165, 0) ' create buffer to hold return value retval = GetShortPathName(strLongPath, strShortPath, 164) If retval = 0 Then GetShortPath = Left$(strShortPath, retval) 'remove all trailing chr(0)'s getOut: Exit Function errHandler: On Error Goto 0 GetShortPath = "" Error Err, Error$ & " [in " & Lsi_info(2) & ", line: " & Erl & "]" Resume getOut End Function

GetShortPathName returns the length of the short path if successful, and 0 if it fails. So I can check the return value, and if it is greater than zero I can use the value to strip off the extra spaces from the string buffer. I then return the short path on the function.

Conclusion
I know that I have discussed these functions before, but they have always been in the context of other blog entries. I thought it would be useful (especially for future Google searches) to break them out into a separate entry.

I hope this is useful to you.

Enjoy!

Rock
**Reality is a place I like to visit from time to time.

Technorati tags: ,

02/23/2006

SnT2: Computing Strings @Length: LMBCS, Unicode and Other Stuff Of Which You May Have Never Thought (Much)

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 



Technorati tags: ,

ED Note: I would like to welcome the first guest blogger ever on LotusGeek.com - and I could think of no one more appropriate that Bob Balaban, and no better time than Show-n-Tell Thursday. Bob doesn't feel he "has enough material" to do his own blog on a regular basis, so I have encouraged (ok prodded and cajoled) him to write for LotusGeek.com instead - and hopefully the blogging bug will bite him when he sees the responses here, and we can finally convince him to do his own thing. Until then, he can write here any time **Rock

Ok, folks, Rocky has been after me for a long time to "guest blog" on LotusGeek.com. We were talking the other day about one of my favorite software topics (really!), multibyte character sets and what it means to compute their "length". It turned out that Rocky didn't know some of the basics (believe me, I was shocked too; but to be fair, this is fairly deep and subtle stuff), so I thought it might be a good topic for a short post.

To start, let me reference a rather full discussion of this whole topic (it took over 3 hours to present it when I first did it in 2001 for a customer):

Multi-Language Character Sets (by Bob Balaban)

It explains in lots of fascinating (to geeks like me, anyway) detail how character sets really work, the difference between LMBCS and Unicode, between ASCII and EBCDIC, and so on (and on, and on). This presentation is good background if you really care about some of the issues I am only addressing very briefly here in this posting.

To begin: What does it mean to get the "length of a string"? It could mean any of three things:
  1. The size the string occupies on the screen when drawn there by some software
  2. The number of characters in the string
  3. The number of bytes of memory that the string represents inside some program

Clearly the first meaning is very different from the other two. But (and what might not be so clear), numbers 2 and 3 are also very different (most of the time)!

We're not going to talk about meaning number 1 today (maybe another time), except to say that the way the string looks on a screen depends not only on the content of the string, but on the "font" used to render it (Arial, Times New Roman, etc.), the size of the font (10, 12, 14 point), and various other potential assigned attributes (bold, strikethrough, etc.).

The differences between meanings number 2 and 3 are not always so obvious to people (even software geeks). Naturally, if every "character" in a string is represented in software/memory by a single byte, then the "length" of a string is always equal to the number of bytes of memory needed to hold it (plus, maybe, one extra byte for a zero to mark the end of the string, as is common in programming languages such as C and its derivatives). Here's the basic thing to remember: ALL "character sets" represent an assignment of each character to some numeric value, known as a "code point". This is how computer software can represent stuff that humans originally invented only to be written down on clay, stone, papyrus, whatever - we turn it into numbers.

If we're dealing with the ASCII character set (American Standard Code For Information Interchange), then we use 7 bits to represent each of 127 different characters (digits, letters, some punctuation, space, and so on). Since a byte is 8 bits, the entire ASCII character set only uses half the available number space that can be represented by an entire byte. The character at slot zero is special ("null"), leaving really 126 (ASCII was originally a six-bit table, but 64 characters wasn't enough so the extra bit was assigned in the mid-1960s, giving rise to the Civil Rights Movement, anti-war protests, and The Beatles).

But what if you live in one of the many countries around the world where the written language requires more than 127 individual characters? Japanese, Chinese and Korean come to mind. In these written languages, which are not alphabetic, each character represents a whole word, which in turn means that we need thousands of them to be available in order to faithfully represent those languages in computer software and storage. So, what to do? The answer is a bit complex in its implications: use more than one byte to represent a character! If we allow 2 bytes per character (16 bits), we have a table with (2^16)-1, or 65,535 slots (zero is still special). But that's STILL not always enough! If we normally need 30,000 characters (a useful subset, let's say) from each of Chinese, Japanese and Korean, then we still can't fit them all into one table (and of course if we're trying to put everything into one table, we also need to consider Cyrillic, Thai, Urdu, Hindi and lots of other languages that don't use the Roman alphabet, not to mention language specific punctuation and accent marks!). So, even in systems like Unicode, which attempt to unify everything, 16 bits might not be enough, and you might end up with multiple 16-bit code points representing a single character.

02/16/2006

SnT2: Signature File Wizard, Take Two

QuickImage Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

Awhile back I posted about my littleSignature File Wizard that I had created. Basically it allows you to mail a self-contained feature to a Notes mail user, and they can go through some steps and create a nice little signature block. After Lotusphere I dug that code up and updated it with the "Yellow is the New Black" theme and one of the logos floating around. Well, eventually this little thing made its way all the way to Surjit Chana (yes, that Surjit - "sick and tired of the Microsoft FUD Bull#@$^!" Surjit, VP of Marketing). He liked it a lot, and asked me for some minor changes. Always wanting to undercommit and overdeliver, I gave him back a pretty robust little tool that he can send out - and he has, a lot.

So, I thought it would be nice to share this tool with you. I have made some modifications so that it isn't "IBM-centric", but overall I think it is pretty neat. Here's what the result looks like:

Sig Wizard Example

You can choose from one of four graphics, or you can select a file from your local hard drive. Additionally you can optionally add your cell phone number. And since some people don't like to use the My Documents directory, you can even choose where your signature files are stored.

Here is a screenshot that demonstrates how I package and send out the Signature File Wizard to people via Notes mail.

Signature Wizard Screenshot

I provide a brief introduction, the button, and pictures of all of the graphics the user can choose from - since I can't display them during the wizard. I also include all the necessary files - the graphics files and the LG_sig.htm file - so that the button code can find the files.

Now that you know what it looks like, let's take a look at the code in the button...

02/13/2006

Announcing Show n Tell Thursdays

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

I was having a discussion with Bruce over the weekend - and we both agreed that we, as a community, seemed to have "lost our way" a bit with all the defensive arguing lately. We feel it is time to get back on track - sharing, collaborating, and discussing the kickass apps and techniques we're developing every day. So without further ado, Bruce and I want to announce a new "tradition" we hope to start:

Show n Tell Thursdays (BTW, thanks to Bob Balaban for coming up with the name ;) )

Show n Tell Thursdays are going to be every Thursday - and on this day we are all going to share some app or some technique that we've developed. And I want everyone to participate - even if you don't normally do "geek" type posts. This also includes Admins - you admin weenies have some great techniques, tips, tricks, or apps you use to do your job better, and we could all learn from you. So, every Thursday we'll all have something to look forward to - reading about a plethora of new apps and techniques that our fellow Lotus Geeks have developed. And from this we increase the communal knowledge pool of which we all drink, and we'll all be better for it.

So today is your heads-up. The first Show n Tell Thursday is this Thursday. You have a couple of days to get a post together. I'll be posting, as will Bruce and many others - and we want you to contribute too. I can't wait to see what you come up with!

A few other things...
First, we want you to participate even if you're NOT a blogger. Simply prepare your entry and send it to one of the participating bloggers - we'll post it for you and give you proper attribution.
Second, we need a kewl graphic for this - and Scott Good has come to the rescue! Here's the graphic for our blogs:

ShownTellThurs.jpg

Third, we need a Technorati category - I have no idea how to do that, but Paul Mooney said Greyhawk may know about that - someone get that going for us.
Fourth, I have a suggestion - if you're going to post code, I recommend you use Joe Litton's LotusScript to HTML Conversion page. Use inline-style, and you can copy/paste that into your entry.

Get those thinking caps on, and I'll see you Thursday!

Updates:
Here's an updated graphic with a bow-tie (from Newbs) - for those of you who are so inclined... ;)

ShownTellThursBowTie.jpg

Paul Mooney has blogged about Show-n-Tell Thursday as well. Check it out here.

Wild Bill Buchan has blogged about it too, here.

Add Greyhawk to the list of folks blogging about Show-n-Tell Thursday. His entry is here.

And Keith Nolen (Yellerdog.net) gives a hint about his upcoming post here.

Rock
**Everything should be made as simple as possible, but no simpler.

Meet Rocky

Rock - February 2010
Rocky Oliver
If you see me at a conference, please stop me and say hi!

Calendar

Search

Categories

Proudly Employed By

Wofkflow Studios

LOTUS GEEK gear

Social Networking


Add to Technorati Favorites

View Rocky Oliver's profile on LinkedIn

Rocky  Oliver

LotusGeek Blog Roll

Why display a blog roll when Planet Lotus does it so much better?

Dilbert

Buy my book!

Blog Buttons

Atheist - Unitarian - Humanist

Poker Players Alliance