Author Archives: David Navarre

About David Navarre

I'm an amateur historian and wine lover. I study both with hopes of turning my passion for them into a career.

Copying a Sharepoint file using Flow

For one of the applications that we’re moving from Notes to the Microsoft platform, I need to copy documents from one Sharepoint site to another. There are two instances in which this task will be called: when a document gets approved for publication and when a project needs a new set of documents.

In Notes, I had a handle on how to do this. For the approved documents, my agent would send them via email to the destination database as a mail-in database. For creating a new set of documents, I would simply copy the main database and give it a new name. Needless to say, I liked my Notes solution a lot, but technology and user requirements are constantly changing.

This is a relatively simple thing, but I am again lost in the code. As in the past. I want to share my experience, so you can learn from it as well. Here’s what my copy file task looks like in Flow:

Copy File Image

So, we start with the assumption that there is a document, thisItem, being processed here, then have to direct Flow where to find it and provide the destination for the document. The trick was making sure to get Folder Path before File name with extension. I tried without using the path, thinking that Sharepoint docs would always be in the same directory (which they aren’t, of course!)

The other good news, beyond realizing I can do something in a different environment is that there actually is some “code” to look at. It’s not all just drag-and-drop, pretty pictures.

{
    "inputs": {
        "host": {
            "connection": {
                "name": "@parameters('$connections')['shared_sharepointonline']['connectionId']"
            }
        },
        "method": "post",
        "body": {
            "sourceFileId": "@{triggerBody()?['{Path}']}@{triggerBody()?['{FilenameWithExtension}']}",
            "destinationDataset": "https://mysite.sharepoint.com/sites/teams/oimt/TAMIS",
            "destinationFolderPath": "/Documents",
            "nameConflictBehavior": 1
        },
        "path": "/datasets/@{encodeURIComponent(encodeURIComponent('https://mysite.sharepoint.com/sites/Policy/PolicyPortal'))}/copyFileAsync",
        "authentication": "@parameters('$authentication')"
    },
    "metadata": {
        "flowSystemMetadata": {
            "swaggerOperationId": "CopyFileAsync"
        }
    }
}

It’s a small step, but one I’m happy to have accomplished and privileged to share.

Advertisement
Categories: Flow, Xpages | Tags: , , | Leave a comment

Lt Gorman, you need this software!

How can I be grumpy at a tech conference? Well, it’s not one of my familiar conferences. That one’s next week, but I’m not going. We’re moving off of Notes, though a part of me continues to bleed yellow. So, I’ve been stomping around, feeling a little grumpy about having to give up my familiar technology and attending conferences with my friends.

The best thing about technology conference is… technology! So, I saw a session with the relatively boring title of “Real Time Data Analytics to the Edge”. OK, I admit, tossing in the word “Analytics” is what caught my eye, because it’s a buzzword. When I look at horse racing, I dive deeply into the data and use analytics to help me understand the possible outcomes and their likelihood. The description sold it a bit better:

Speed of command and safety is a top priority for governments. Delivering on the intelligent edge is made possible by Microsoft Azure. The “edge” is where the operational action lives and is ever present at the very skin of an organization. It’s the convergence of compute, connectivity, and cloud. The intelligent edge is about enabling operations, collaboration, and IoT sensor fusion amongst responders and far forward commanders yet leverages centralized services to augment decision making where the decision space is measured in minutes and in worst case scenarios, seconds. Blueforce Development and Dejero will show how sensor fusion, cognitive services, and mobile connectivity put the power of actionable intelligence in the hands of the frontline users, powered by Microsoft Azure, in real-time.

This still doesn’t convey the coolness of the technology involved.

Go back with me, if you will, to Aliens and sit down with Lieutenant Gorman, commanding his squad of Colonial Marines. Each of your Marines is wearing cameras and sensors to feed back information to their commander. Unfortunately, there’s too much data for him to handle and his lack of experience contributes to his failure. Nonetheless, what cool tech! He can see what any of his Marines can see. He can monitor all of their vitals. He should even be able to pick up movement around them from the sensors.

What BlueForce does is take all that data and make it manageable. I remember taking a course in management way back in the day and among the best ideas was “management by exception”. I use this all the time in software development – I want the system to only notify me when something’s broken. I also like to get occasional messages that just remind me that the system is running (since it might not be able to send a notice if a whole lot is broken!) BlueForce lets the user, in this case our neophyte Lieutenant, take the feeds he wants and arrange them the way he wants to know what’s going on.

In the “It’s a small world” category, the CEO of BlueForce Development is Michael Helfrich, who was Director of Product Development for Knowledge Management at Lotus. So, we were able to share stories about Raven, which I was never able to get any of my customers to buy, since no one could afford $100,000 in 1999!

Among the feeds was the Virtual Surveillance Partner (VSP) by 6-Watch. It uses a combination of Lidar and video to perform threat detection (helping sort out what’s important and what’s not) and provide audio cues about those threats. There are far more applications to this set of tools than just providing our Colonial Marine Lieutenant with real-time threat assessment and asset management, but I suspect anyone reading this is going to hop onto their website to see more about it. (Think “law enforcement” and “forensic evidence”.)

So, the good Lieutenant would have been able to put his VSP up on the screen, select which video feeds matter, perhaps enabling alerts for various vital stats feeds from his Marines and figure out how best to command the situation. After all, he had 38 drops (simulated) to practice it.

20190205_140111.jpg

The command center screens that Lt Gorman would be able to drag-and-drop to configure his feeds to best command those Colonial Marines

It’s some pretty cool stuff and the President of 6-Watch, Eric Gahagan, took some time to chat with me as I was looking at their system in action on the conference floor. Eric spent a long time with the Massachusetts State Police, so the implementation of their products is near and dear to his heart.

20190205_135931.jpg

The Lidar for 6-Watch is atop that tripod. The crate below is just for packing it up for shipping. So, it’s somewhat portable – a Special Forces team could easily mount it on a vehicle.

As much as I struggle with being an “outgoing introvert”, meeting the people who are behind the technologies and making connections has always been the great benefit of going to tech conferences. I can’t say that I’ll ever personally have a use for the technology, but it’s pretty cool.

Categories: Bleed Yellow, Conferences, Security | Tags: , , , , , , , , , , , | 1 Comment

Get excited about learning all over again! #IBMThink #IBMChampion

Do you remember when learning was exciting? If it isn’t anymore, you need to start listening to John Jardin. He’s got a session coming up at IBM Think and the excitement explodes from his blog. If you haven’t registered yet, DO IT NOW!

See you in Las Vegas, March 19-22.

Read about John’s session — http://bleedingcode.com/will-presenting-ibm-think-las-vegas/

p.s. Ask me for a discount code!

Categories: Conferences, IBM Champion | Tags: , | Leave a comment

Proud to be an #IBMChampion

In one of the many jobs that I held, I remember someone telling me that they weren’t sharing knowledge of something because being the only person who knew that gave them job security. I’ve always felt that was foolish. If you’re the only person who knows something, you get stuck doing that same stupid, repetitive task whenever it needs doing. I can think of nothing more boring for a developer than hoarding knowledge. I don’t ever want to have to do the same thing twice – unless doing it the second time is showing someone else how to do it or demonstrating the way things “used to get done” before I automated the task.

So, it’s no surprise that I’ve always wanted to share whatever knowledge I’ve acquired. Sometimes, the knowledge I’ve got to share is so esoteric that people’s eyes glaze over and they ask me to stop talking. (Like the time I was about to talk for 30 minutes about Miljenko Grgich’s life story and how that related to the wine I’d poured, when my best man, Stu Shelton intervened…) Fortunately, in the IBM ICS community, among committed “Lotus” Notes professionals, any short or long blog post, knowledge sharing session or roundtables veritably demands the sharing of esoterica.

Now, here’s the thing. It’s not only encouraged – it is REWARDED.

I really wasn’t good at sharing outside my own work groups until about 2013. I was at Lotusphere (whatever it was called that year) and Marky Roden saw me singing karaoke. As horrid a singer as I am, I believe his thinking had to be ‘anyone who can risk that kind of humiliation can be a conference speaker’. He’d seen me answering questions on Stack Overflow (I’d gotten over a few hundred reputation points and he was shocked that he didn’t know who I was – no one did!) So, he told me I needed to become a speaker.

I’ve worked on that, but not made the breakthrough to the big time. I’ve blogged over the years, helped out with the DC Lotus Users Group (presented once!), participated in more IBM calls/sessions and gotten very involved in MWLUG. I even was on a panel at Connect 2017, talking to executives about technology trends. Then, when Richard Moy asked, I jumped at the chance to be the local host for MWLUG 2017, here in Alexandria, VA.

Nonetheless, I still have felt like I wasn’t doing all that much. I was living with The Imposter Syndrome. So, when time for IBM Champion nominations came round, I would nominate a few people and then cringe at the idea of nominating myself. I’d been nominated by others, but couldn’t get myself to fill out the paperwork. Howard Greenberg was the first to push me toward this. In 2016, Kathy Brown nominated me and pushed me to nominate myself. I wilted when looking at the paperwork, even though it was something I really wanted.

This year, I finally filled out the paperwork, to go with nominations from a few community members and, to my shock, I didn’t need to write a “it was an honor to be nominated” speech while talking about the great stable of Champions out there. I’m swollen with pride. I was named an IBM Champion.

I’ll get some swag. I’ll get a chance to participate more fully in our community. I’ll have a chance for some recognition at the various conferences. I’m excited about it and vowing to put even more effort into our community.

I want to thank everyone who has encouraged me along the way and those who played a particular role in this process. I hope that I can be your humble and obedient servant, sharing knowledge and helping build our community.

Categories: Conferences, IBM Champion | Tags: , , , , , , , , , , | 2 Comments

A more direct way of creating an Outlook mail item from the Notes client

The best thing about being in software development is learning something new every day. The most humiliating thing in software development is learning that you did something the hard way when there’s a far easier way to do it. That happens most days as well!

So, I was explaining to my manager, Tami Fries, and one of our business users that my method for opening an Outlook message for the end users was a little sloppy, in that it would open two browser tabs (or windows, depending on configuration) before opening the Outlook message. I wondered in the back of my mind if it had to be done that way. They agreed that my helpful message on one of the tabs and titling the tab ‘Close Me’ would probably not generate any confusion. Nonetheless, I wondered.

I remembered vaguely that when I was creating Excel spreadsheets from Notes and silently emailing them in the background from the FAA to Office Depot, that I had to add an extra line of code to make Excel not appear as an active window. So, I looked at the documentation again and found that all I had to do was use the Display method of the MailItem object.

Here’s the code fragment that generates the Outlook message and displays it for further editing. (Note that I used br without the angled brackets in the HTML, since it was reading the HTML and inserting blank lines into the code snippet, but you’ll want those when you drop this into your LotusScript.)

...
Dim outlook As Variant
Dim message As Variant
Dim itemType As Integer

Set outlook = CreateObject("Outlook.Application")

If ( outlook Is Nothing ) Then
	Call ws.Urlopen(shareViaMailToURL(subject, doc))
Else
	body = "Here is a link to " & subject
	body = "Your approval has been requested for changes made using " & changeOrderNumber (0) & " in " & thisdb.Title
	body = body & "br br"// replace with the angled bracketed HTML for two line breaks
	body = body & "Please review these changes and approve, provide comments, or request more time to review within five business days of this notification. Otherwise, the change will be considered approved as per DAI policy."
	body = body & "br br"// replace with the angled bracketed HTML for two line breaks
	body = body & "The pending change approval form and links to draft documents can be found here: "
	body = body & "br br"// replace with the angled bracketed HTML for two line breaks
	body = body & "Notes:///" & thisdb.ReplicaID & "/0/" & doc.UniversalID

	' 0 is mail item '
	itemType = 0
	Set message = outlook.CreateItem(itemType)
	message.Subject = subject
	message.HTMLBody = body
	message.Display
End If
...

The shareViaMailToURL is intended to run if the user doesn’t have Outlook, so that it will use the previously supplied code to open the mailto URL using the code in that earlier post.

So incredibly simple in either method.

Categories: Old Notes, Utilities | Tags: , , , | Leave a comment

Sending an automated mail using Outlook from a Notes agent

One of the greatest hurdles we’ve had in our transition from being a Notes mail shop to an Outlook mail shop remaining a Notes application shop is automated emails. When someone clicks a button to approve a document in a workflow, I want the system to quietly notify the pertinent parties without bothering the user.

Before I arrived, none of our applications did that. Every time someone wanted an email to accompany the request for approval, an email was opened in the Notes client using formula language. The code did assign the recipients and add text with a doclink, but the user had to click ‘Send’ for the message to go onward. This did allow them to add explanatory text and choose to route the message to a different approver if someone was not available, so it did have advantages. However, not every message ought to sent that way and we can keep the user inside our application by popping up dialogs to customize or select different recipients, or… whatever flexibility we’d like to provide.

So, a number of the applications I’ve designed, or modified from the original, send notices via Notes mail, generated quietly in the background by LotusScript. 2016 rolls around and our organization makes a strategic move away from Notes mail and into Outlook. I spent a year thinking there was no way for me to keep those notifications without dancing around about Notes mail files and replication. The other day, I wondered if I could use OLE to create the Outlook messages and send them without the user needed to intervene. Shockingly, it didn’t even take me a day to figure out how to do it.

The hardest part was figuring out that passing in 0 for the item type needed to specifically be an integer, not just a zero. If figured this out when I created a task using 3. Since it understood that 3 was an integer, I just created itemType as an integer and then 0 really was 0. (Got the clue from someone’s commented code on Stack Overflow, of course)

Here’s the list of Outlook items you can create, with links so you can examine the events, properties and methods:

Name Value Description
olAppointmentItem 1 An AppointmentItem object.
olContactItem 2 A ContactItem object.
olDistributionListItem 7 A DistListItem object.
olJournalItem 4 A JournalItem object.
olMailItem 0 A MailItem object.
olNoteItem 5 A NoteItem object.
olPostItem 6 A PostItem object.
olTaskItem 3 A TaskItem object.
%REM
	Agent Send Outlook Mail
	Created Sep 12, 2017 by David Navarre
	Description: This Agent creates and sends an Outlook mail message in the background
%END REM
Option Public
Option Declare

Sub Initialize
	Dim outlook As Variant
	Dim message As Variant
	Dim itemType As Integer
	Dim reason As String

	On Error GoTo errorhandler

	Set outlook = CreateObject("Outlook.Application")
	' 0 is mail item '
	itemType = 0
	Set message = outlook.CreateItem(itemType)
	message.To = "David_Navarre@company.com"
	message.Subject = "This is my test"
	message.HTMLBody = "<b>Body bolded</b> and some not"
	message.Send

exiting:
	Exit Sub
errorhandler:' report all errors in a messagebox '
	reason = "Error #" & CStr (Err) & " (" & Error & ") on line " & CStr (Erl)
	MessageBox reason, 16, "Error"
	Resume exiting
End Sub

The most amusing thing to me is that I got my initial clue from a post made way back in 2006. Sometimes, I feel like I’m starting all over again. I guess that’s a good thing, because learning new stuff was always fun (this was too!) and not as intimidating as it sometimes seems when I think about having to learn something new. It’s been nice to be in the upper branches of the knowledge tree, but the truth is that what always separated me from other folks was not what I already knew, but how fast I could learn something new.

Categories: Old Notes, Utilities | Tags: , , , , | 4 Comments

Breweries and tap rooms for #MWLUG

OK, so we’ve discussed restaurants nearby, but MWLUG includes so many beer lovers that I know some of you will be eager to venture further afield for interesting beer. None of these are in walking distance.

What we already know

In Friday’s post, I mentioned the Cap City Brewing Company in Shirlington as a viable dinner option in the midst of a good restaurant scene. Shirlington is either a short Uber/Lyft ride from the hotel or just across the footbridge out the back of Wednesday night’s brewery tour at New District Brewing.

Where everyone knows your name

OK, at Fat City Kitchen, they won’t already know YOUR name, but they know mine. There are a dozen taps, mostly microbrews (the others are Guiness and Yuengling, IIRC) and probably 80 other beers by the bottle or can. Matt can mix anything you can think of (I go for the ‘City 75’, which is similar to the French 75, while Melissa digs the Grapefruit Collins), Farrah and the rest of the staff keep you smiling with full glasses. The food is excellent and we love about half a mile from it.

Where I can get some beers to bring to the hotel

Our favorite wine shop, Unwined, is just across the highway off King Street. Besides the excellent wine selection, they have a large selection of beers, including a growler filling station. Yes, bring your own growler in and fill it up, or buy a growler there. According to the link , they’ve got a saison and 3 IPAs on tap right now. Another place where mentioning that you know Dave and Melissa will make them smile!

Where else could we have gone for a brewery tour

Another place that’s near my house (stumbling distance?) is Port City Brewing Company. Way back in the day, Alexandria was actually nicknamed “the Port City” because so much trade came in over it’s docks. Then the 19th century started…. The brewery was named Small Brewery of the Year in 2015, so likely worthy of a pilgrimage. I love their Porter, though it’s not exactly an August beer. The tap list is less heavily weighted towards IPAs than it was a few years ago, but they’ve got 11 beers on it.

Arlington, please!

Some of the fellows that I’ve played softball and baseball with are into beer as much as I’m into wine. They had a happy hour at Crafthouse over in Arlington and the draft beer selection was very nice.

Maybe not yet

Earlier this year, Portner Brewhouse opened about a mile from our house. We went during the ‘soft opening’ and loved the food. The brewery inside seems to have been a gimmick or something that they thought “couldn’t be that hard”. Sitting next to a fellow who sells brewing equipment, he let us know that what he could see wasn’t the top grade equipment. No, that’s too polite. He said that he thought they did it on the cheap. He heartily recommended New District, so we’re on the right path for Wednesday night.

Final thoughts

Of course, there are other options further afield, but I generally haven’t been there, so wouldn’t have useful input. The best beer spot in the DC area closed recently. The Brickskellar in Dupont Circle had hundreds of beers on their menu and they even had live music on the weekends. Feel free to questions about certain parts of town, as I’ve been here for 27 years and used to go everywhere in Arlington, Alexandria and DC.

I’ll be out of town before MWLUG, returning on Monday night. If you’re interested in some assistance with getting beer and wine from Unwined or beer from Port City (they do have Sixtels, which are only 41 pints), I may be able to help. Otherwise, or for evening adventures, getting to any of these should be easy via Uber, Lyft, or some other conveyance.

Categories: Conferences | Tags: , | Leave a comment

So where can I go for dinner near #MWLUG?

For MWLUG 2017, we’re in my lovely city of Alexandria, Virginia, just outside Washington, DC. Some folks will come early, some will stay late, some will choose to dine out while they’re here. So, I wanted to offer some mild advice to make your search for a good meal easier.

Man, I want it to be close

Well, the easiest is right in the hotel. I’ve heard good things about Finn & Porter, but I’ve not eaten there. I’m sure everyone will stop in for a drink, so while you consider your options elsewhere, check the menu. Remember to tip your bartenders and waitresses well! The next closest is one I’ve been to very often. Well, a few times a year. Clyde’s is on the way home from my mother-in-law’s house, so we have stopped for a drink on the way home occasionally. The food is outstanding. If you like oysters, they always have a good list. There are good wines available and the bartenders mix up some fine cocktails. You can walk to and from Clyde’s (0.4 miles).

I really wish I hadn’t left home

Yes, there is a TGI Friday’s and I have heard they do wear the appropriate amount of flair. It’s a mile, so you could walk.

Beer, beer and more beer

I really like Cap City Brewing in Shirlington. Get yourself some pretzels and enjoy a few cold ones. Cap City opened early on in the micro-brew era, so it may not be ‘different’ enough if that’s important to you. On the other hand, longevity does indicate a certain level of quality. Take an Uber over to Shirlington, or wander over after our time at New District Brewing. (Half a mile from New District, 10 minute drive from the hotel to Shirlington).

I want to open a cheese shop

Nearby in Shirlington, Cheesetique started as a cheese shop that had some wines and snacks. Then, they morphed into a cheese and wine shop where you could have a meal. Now, it is really a good restaurant with amazing cheese and pretty good wine.

Everything seems to be headed to Shirlington, but I want something different

If you’re seeking something other than beer and wine as the draw, Shirlington has many restaurants these days. Busboys & Poets will always fill you up with delicious comfort food and also has some literature or might even have a poetry reading. Carlyle is an old standby – a little pricey for me. We’ve loved eating at every location of Cafe Pizzaiolo, including the one that they had right in our neighborhood for a few years. Guapo’s is great for Mexican food. There are a bunch of other restaurants, so it’s a good area to choose — keeping in mind it is just a half mile from Wednesday night’s brewery.

It has to be French, bien sur!

Two choices if time, distance and money aren’t real priorities: Le Diplomate in DC or La Cote d’Or in Falls Church. Great for me, because the wine lists are very good (longer and more expensive downtown, shorter and more reasonable in Fall Church) and the food is unbeatable.

Other ideas

You could head to Crystal City or Pentagon Row, which are near the Crystal City and Pentagon Row metro stops. Another good choice is the Clarendon areas in Arlington, which is very walkable once you’re there. Old Town Alexandria has dozens of restaurants and true nightlife.

Hope these ideas help!

Categories: Conferences | Tags: | 1 Comment

Creating a meeting notice in Outlook from the Notes client

Another task in our migration to Outlook as the mail client is creating Outlook calendar entries and meeting notices directly from the Notes client. Fortunately, it’s been two years since I wrote about how to do this in the UI in Notes, so I don’t feel like that was wasted time. I was exciting to solve the problem and… oddly enough, solving this one was fun as well. It helped that creating an iCal entry is far simpler than the gyrations we had to go through to create one in Notes. As noted previously, there  aren’t a whole lot of required values to generate in order to have an ICS file that you can open in the UI as a meeting notice/calendar invite.

BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20170622T211500
DTEND:20170622T221500
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN="Required Person/Company";RSVP=TRUE:mailto:Required_Person@company.com
ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:external_person@SecondCompany.com
SUMMARY:2017 Q2 QPR: Agribusiness Competitiveness Enhancement via file
UID:AC1804D765C782CD8525814500073F3720170620T104326
END:VEVENT
END:VCALENDAR

Now, keep in mind that this iCal file is a mere fragment. If you sent that file to someone, they get the same behaviour you get — it thinks they’re the meeting organizer and doesn’t save it to their calendar unless they send the ‘update’. The key parameter we leave off is that we don’t set METHOD, since setting that to PUBLISH or REQUEST proved problematic in the Outlook client. If we leave it off, Outlook will allow us to treat it like a brand new calendar entry we’ve created, except that the send button will say ‘Send Update’.

So, let’s review those values in our fragment…

Objects

First, the calendar and event objects are encapsulated. Nothing fancy there.

BEGIN:VCALENDAR
BEGIN:VEVENT
END:VEVENT
END:VCALENDAR

Meeting times

Then we have our start and end times, formatted with date first (YYYYMMDD) then a separator (T) and then the time (HHMMSS). You can include time zone information, but we’re creating this in Outlook and allowing the UI to finish everything for us. So, if the user wants to change the time zone, they can do that in Outlook.

DTSTART:20170622T211500
DTEND:20170622T221500

Attendees

The one required value for our needs in the attendees is the mailto value. Without that, it won’t know who to send the invite to and it simply ignores any other item in that list.

ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED;CN="Meeting Chair/Company";RSVP=TRUE:mailto:Meeting_Chair@company.com
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN="Required Person/Company";RSVP=TRUE:mailto:Required_Person@company.com
ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:external_person@SecondCompany.com

ROLE is not required and can be CHAIR, REQ-PARTICIPANT (required participant), OPT-PARTICIPANT (optional participant) or even NON-PARTICIPANT (for FYI only).

PARTSTAT is not required. There are several values for an attendee in a VEVENT for their participant status, but we’re only concerned with two. Either “NEEDS-ACTION” for attendees that we don’t know when we create the meeting notice whether they’ve agreed to attend or “ACCEPTED” which we’d typically only use for the person creating the meeting notice.

CN is, of course, familiar to us as Notes developers, but it applies here to whatever will be displayed as the attendee name. In my experience, Outlook can parse the abbreviated name and display just the attendee’s common name. That might be our Outlook configuration, but I would assume it’s common.

RSVP would be either true or false, indicating whether you want a response from the attendee. In my case, we always want it from the attendees, other than the current user.

Title and description

I got fooled by this one. In my sample ICS files, I thought there was just an odd carriage return, but the DESCRIPTION value is basically the body or details of the event, while the SUMMARY is what appears in the subject line for the meeting.

SUMMARY:2017 Q2 QPR: Agribusiness Competitiveness Enhancement via file

Meeting ID

I’m guessing that Outlook computes the unique meeting ID itself, but in my code, I generate from the Notes document’s unique ID and then, in order to ensure that subsequent meetings concerning the same document get different IDs, I’m appending a creation time-stamp.

UID:AC1804D765C782CD8525814500073F3720170620T104326

So, the agent I wrote that generates the new meeting notice is pretty straight-forward. The getEmailAddress function was described and detailed in a prior blog post and my Utilities script library only provides the logging functions here. Like my mailto agent, this one relies on the creation of a file in the Notes data directory and opening it using a browser.

The agent

First, you can look over the main part of the agent…

%REM
	Agent (Send Calendar Invites)
	Created Jun 20, 2017 by David Navarre/DAI
	Description: This Agent creates a calendar invite, listing participants and optional participants
%END REM
Option Public
Option Declare
Use "Utilities"

Dim session As NotesSession
Sub Initialize
	Dim ws As New NotesUIWorkspace
'	Dim thisdb As NotesDatabase declared in Utilities script library '
	Dim uidoc As NotesUIDocument
	Dim qprdoc As NotesDocument
	Dim history As NotesRichTextItem
	Dim chairName As NotesName
	Dim recipientName As NotesName
	Dim projectName As Variant
	Dim fiscalYearAndQuarter As Variant
	Dim participants As Variant
	Dim participantsOptional As Variant
	Dim subject As String
	Dim answer As Variant
	Dim reason As String
	Dim unid As String
	
	On Error GoTo errorhandler
	
	set session = New NotesSession
	Call StartAgentLogging ( session )

	If ( openAddressBooks () ) Then
		agentLog.Logaction("Address books opened")
	End If

	Set thisdb = session.CurrentDatabase
	Set uidoc = ws.CurrentDocument

	reason = "This will create a meeting invite for you to send to participants."
	If uidoc.Editmode Then
		reason = reason + Chr$(10) + "The QPR will switch to read-only mode."
		reason = reason + Chr$(10) + "If you close and re-open it, you can edit it again."
	End If
	reason = reason + Chr$(10) + "Continue?"
	answer = ws.Prompt ( PROMPT_YESNO, "Continue?", reason )
	If answer = 0 Then
		Exit Sub
	End If	
	
	If uidoc.Editmode Then
		Call uidoc.Save()
		uidoc.Editmode = False
		Set qprDoc = uidoc.Document
		unid = qprDoc.Universalid
		Call uidoc.Close(True)
		Set qprDoc = thisdb.Getdocumentbyunid(unid)
		Set uidoc = ws.Editdocument(False, qprDoc, True)
	Else
		Set qprDoc = uidoc.Document
	End If
		
	Dim fileName As String
	Dim dataDirectoryPath As String
	Dim url As String
	Dim fileNumber As Integer
	
	fileNumber = 1
	
	dataDirectoryPath = session.Getenvironmentstring("Directory", True)
	fileName = dataDirectoryPath & "\QPRInvite.ics"
	
	Open fileName For Output As fileNumber
	
	Print # fileNumber, {BEGIN:VCALENDAR}
	Print # fileNumber, {BEGIN:VEVENT}
	Print # fileNumber, {DTSTART:} & getMeetingTime ( "Start", qprDoc ) '20170620T211500
	Print # fileNumber, {DTEND:}  & getMeetingTime ( "End", qprDoc ) '20170620T221500

	' Chair '
	Set chairName = New NotesName ( session.Effectiveusername )
	' when you send the invite from Outlook, it makes you the chair '
	' this line is here to show how you would format an attendee line for the chair '
	' Print # fileNumber, {ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED;CN="} & chairName.Abbreviated & {";RSVP=TRUE:mailto:} & getEmailAddress ( chairName.Abbreviated ) '
	' Required participants '
	participants = qprDoc.Getitemvalue ( "Participants" )
	ForAll entry In participants
		Set recipientName = New NotesName ( entry )
		If Not ( chairName.Abbreviated = recipientName.Abbreviated ) Then
			Print # fileNumber, {ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN="} & recipientName.Abbreviated & {";RSVP=TRUE:mailto:} & getEmailAddress ( recipientName.Abbreviated )
		End If
	End ForAll
	' Optional participants '
	participantsOptional = qprDoc.Getitemvalue ( "ParticipantsOptional" )
	ForAll entry In participantsOptional
		Set recipientName = New NotesName ( entry )
		Print # fileNumber, {ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN="} & recipientName.Abbreviated & {";RSVP=TRUE:mailto:} & getEmailAddress ( recipientName.Abbreviated )
	End ForAll

	projectName = qprDoc.Getitemvalue("ProjectName") 
	fiscalYearAndQuarter = qprDoc.Getitemvalue("FiscalYearAndQuarter") 
	subject = fiscalYearAndQuarter(0) & " QPR: " & projectName (0)
	Print # fileNumber, {DESCRIPTION:} & subject ' this is the body of the message
	Print # fileNumber, {SUMMARY:} & subject ' this is the meeting name

	' assign a unique ID to meeting using the unid of the document with the current date-time appended '
	' in case user creates multiple meetings for the same QPR '
	Print # fileNumber, {UID:} & qprdoc.Universalid & getMeetingTime ( "Now", qprDoc ) 

	Print # fileNumber, {END:VEVENT}
	Print # fileNumber, {END:VCALENDAR}
	
	Close # fileNumber
	
	url = "file:///" & fileName
	Call ws.Urlopen(url)
	
	Set history = qprDoc.Getfirstitem("History")
	Call history.Appendtext(Now & " - Meeting notice created by " & session.Commonusername)
	Call history.Addnewline(1, True)
	Call qprDoc.Replaceitemvalue("NoticeFlag", 1)
	Call qprDoc.Save(True, False)

exiting:
	Exit Sub
errorhandler:' report all errors in a messagebox '
	reason = "Error #" & CStr (Err) & " (" & Error & ") on line " & CStr (Erl)
	MessageBox reason, 16, "Error"
	Call agentLog.LogAction ( reason )
	Resume exiting ' transfers control to the exiting label	
End Sub

getMeetingTime

The getMeetingTime function just returns the requested date-time in the format YYYYMMDDTHHMMSS, so it can be included in the creation of the ICS file.

%REM
	Function getMeetingTime
	Description: This Function returns a string in the format YYYYMMDDTHHMMSS
		If it is the start time, the values from the source document are used 		-- 20170622T211500
		If it is the end time, it is adjusted one hour later					 	-- 20170622T221500
		If it is the "Now" time, it returns a string for the current date and time	-- 20170620T094326
%END REM
Function getMeetingTime ( startOrEnd As String, qprDoc As NotesDocument ) As String
	Dim thisNotesDateTime As NotesDateTime
	Dim qprDate As Variant
	Dim qprTime As Variant
	Dim timeString, dateString As Variant
	Dim reason As String	

	On Error Goto errorhandler

	Set qprDate = qprDoc.Getfirstitem("QPRDate")
	Set qprTime = qprDoc.Getfirstitem("QPRTime")
	dateString = qprDate.Text
	timeString = qprTime.Text
	Set thisNotesDateTime = New NotesDateTime ( dateString & " " & timeString )
	Select Case startOrEnd
		Case "End"
			Call thisNotesDateTime.AdjustHour (1)
		Case "Now"
			Set thisNotesDateTime = New NotesDateTime ( Now )
		Case else	
			' keep thisNotesDateTime as set on the source document '
	End Select
	dateString = thisNotesDateTime.DateOnly
	timeString = thisNotesDateTime.TimeOnly
	getMeetingTime = CStr ( Year ( dateString ) )
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Month ( dateString ) ), 2 )
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Day ( dateString ) ), 2 )
	getMeetingTime = getMeetingTime & "T"
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Hour ( timeString ) ), 2 )
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Minute ( timeString ) ), 2 )
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Second ( timeString ) ), 2 )

exiting:
	Call agentLog.LogAction ( "-------" ) 
	Call agentLog.LogAction ( "-------" ) 
	Exit Function
errorhandler:' report all errors in a messagebox '
	reason = "Function getMeetingTime: "
	reason = reason & "Error #" & Cstr (Err) & " (" & Error & ") on line " & Cstr (Erl)
	Messagebox reason, 16, "Error"
	Call agentLog.LogAction ( reason )
	Resume exiting
End Function

While this did take me a few days to sort out, I’m pretty happy with the result. Our configuration has users sharing one “migration” mail file, so that users who are already on Outlook still retain a mail file and can send email. Unfortunately, that means any email from them that we create in the UI is going to have values pointing back to the “migration” mail file. I spent my first few days on this trying to spoof the mail.box by changing Principal, ReplyTo, $InetAddress and Chair when sending via Notes calendaring. While changing Chair did make it appear to come from the current user, it always displayed the email address from the “migration” mail file. It might have been getting caught in our spam filter on the way to Outlook, as my test user on Notes was still receiving the notices. Nonetheless, by switching to using Outlook as the UI, it not only took away that problem, but was far simpler and future-proofed my application. As I look at these tools I’ve created in LotusScript to generate mail messages and calendar entries, I know that it’s but a short step to doing them in server-side Javascript or maybe in Java.

There is hope for the Notes gurus of old. We just have to keep learning!

iCal RFC (documentation?)

Categories: Old Notes, Utilities | Tags: , , , , , | Leave a comment

Getting email addresses from the Notes address book

As we work to get our Notes applications functioning smoothly with our Outlook mail, I’m finding ways to keep the close binding between Notes applications and the user’s mail. It’s so much easier to click a button to generate an email associated to a particular Notes document than to copy-paste a document link.

We’ve got a Notes form for the Quarterly Project Report. Each quarter, various key members on the project are supposed to have a call to review the project. The form itself is ponderous, having something like 700 fields, but (using hide-whens) can be distilled down to a manageable number for the meeting’s agenda. The Notes names of the expected participants are computed from other documents within the database, though the fields are editable. In switching from using Notes mail to generate the meeting notice to Outlook, I ended up switching to using iCal.

It turns out that iCal is a far simpler way to initiate the meeting notice in the UI. All I need is something like this in an ICS file to have it open in my Outlook client as a meeting notice for me to send (as an update, but more on that in another post)

BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20170622T211500
DTEND:20170622T221500
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN="David Navarre/Company";RSVP=TRUE:mailto:David_Navarre@company.com
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN="Another Person/Company";RSVP=TRUE:mailto:Another_Person@company.com
ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:external_person@SecondCompany.com
DESCRIPTION: 
SUMMARY:2017 Q2 QPR: Agribusiness Competitiveness Enhancement via file
UID:AC1804D765C782CD8525814500073F37
END:VEVENT
END:VCALENDAR

One of the hurdles is that we need to have email addresses as well as names for all of the attendees. If you leave the mailto blank or put the Notes name there, it ignores it. Since our address book is still available in Notes and contains everyone’s email address, I thought I’d just google how to do it. Sadly, it wasn’t out there, so I took a few minutes and modified some script in the help documentation (Examples: AddressBooks property) to create a function to do the lookup.

%REM
	Function getEmailAddress
	Description: This Function returns a string that is the email address from the address books

	IMPORTANT: all address books are stored in the NotesDatabase list, addressBookList, which must be called BEFORE calling the getEmailAddress function

%END REM
Function getEmailAddress ( recipientName As String ) As String
	Dim view As NotesView
	Dim doc As NotesDocument
	Dim internetAddress As Variant
	Dim found As Boolean
	Dim reason As String	

	On Error Goto errorhandler

	' if already an internet address, just return that value '
	If ( InStr ( recipientName, "@" ) ) Then
		getEmailAddress = recipientName
		Exit function
	End If

	getEmailAddress = ""
	found = False
	
	ForAll addressBook In addressBookList
		' all address books are stored in the NotesDatabase list, addressBookList, which must be called BEFORE calling the getEmailAddress function '
		' check every Domino Directory, until found '
		If ( addressBook.IsPublicAddressBook ) And ( Not found ) Then
			' look up name in the VIMPeople view of address book '
			Set view = addressBook.GetView( "($VIMPeople)" )
			If not ( view Is Nothing ) Then
				Set doc = view.GetDocumentByKey( recipientName )
				' if person is found, get their internet addrress and stop '
				If Not ( doc Is Nothing ) Then
					internetAddress = doc.Getitemvalue("InternetAddress")
					If ( internetAddress (0) <> "" ) Then
						getEmailAddress = internetAddress (0)
						found = True
						Exit ForAll
					End If
				End If
			End If 
		End If
	End ForAll
	' if found is still False, the person was not found '
	If Not found Then
		MessageBox ( "Unable to locate " & recipientName & " in the address book, using " & recipientName & " as their email addresss" )
		getEmailAddress = recipientName
	End If

exiting:
	Exit Function
errorhandler:' report all errors in a messagebox '
	reason = "Function getEmailAddress: "
	reason = reason & "Error #" & Cstr (Err) & " (" & Error & ") on line " & Cstr (Erl)
	Messagebox reason, 16, "Error"
	Resume exiting
End Function

Hope someone else finds this useful….

Addendum

Dim addressBookList List As NotesDatabase

As Ben pointed out, re-opening every address book every time you want the email address is incredibly inefficient. So, declare a global variable for the address books and use the following function to open them. The code in getEmailAddress now reflects this….

%REM
	Function openAddressBooks
	Description: This Function assigns all address books to a NotesDatabase list and opens them
%END REM
Function openAddressBooks ( ) As Boolean
	Dim reason As String	

	On Error Goto errorhandler

	openAddressBooks = False
	
	ForAll addressBook In session.Addressbooks
		' open every Domino Directory '
		If ( addressBook.IsPublicAddressBook ) Then
			Set addressBookList (addressBook.FileName) = addressBook
			Call addressBookList (addressBook.FileName).Open( "", "" )
		End If
	End ForAll
	
	openAddressBooks = True

exiting:
	Call agentLog.LogAction ( "-------" ) 
	Call agentLog.LogAction ( "-------" ) 
	Exit Function
errorhandler:' report all errors in a messagebox '
	reason = "Error #" & Cstr (Err) & " (" & Error & ") on line " & Cstr (Erl)
	Messagebox reason, 16, "Error"
	Call agentLog.LogAction ( reason )
	Resume exiting
End Function
Categories: Old Notes, Utilities | Tags: , , , , , , | 3 Comments

Blog at WordPress.com.

%d bloggers like this: