Autolaunch and Outlook on Windows

I have been updating my 5entences add-in install for use in the AppSource/App Center for Office, plus adding an auto-summarization capability via OpenAI. 😉 COMING SOON!!!!

Originally, this add-in was using the Dialog API inside the “original” OnSend event. This was a bit nifty as it presented the user with a blocking popup that would allow them to manage everything in one place. It told them, they had more than 5 sentences, and would suggest they go back and fix it, or go ahead and send it. But that type of event is NOT supported in the App Store.

Enter Smart Events, and with it specifically the OnMessageSend event. The adventure began…

I have an Office 365 account and installed Office 365 from my portal.office.com page, made sure I was updated, and everything was looking good, except that:

  • When I have my personal Outlook.com email address attached to Outlook, I cannot debug add-ins in Outlook full client on Windows.
  • Even when I did remove my Outlook.com email address, my events were still not firing.
  • Everything worked great in Office on the Web.

So, I proceeded with updating the code using Office on the web to debug, giving up on Outlook full client in the interim. Once I got everything working well in Outlook on the Web, I went back to Outlook on Windows and began to lose my hair.

I quickly discovered that my ES6 code in the command.js, were not working. As you dig into the documentation you find that the WebViewURL and the override for JSRuntime, sharing the same file become an issue. It turns out that even with the WebView2 control installed on my box, and even though I have the latest version of Office 365 full client installed, my JSRuntime code reverts to Trident+ (IE11). I refactored my code to:

  • stop using const, instead var everything.
  • stop using => arrow operators, reverting to full function()
  • stop using async/await, and built pyramids of doom
  • stopped using Promises() even

The reason I reverted, is because I pulled out all transpiling and polyfills because they SLOW down my add-in code, make it too large and it impacts my already overwhelmed server. I also like to remain as pure JavaScript ES6 as I can. I am a bit puritanical, I guess. 😆

But even with all that it did not work. So, line by line I went and found the first problem:

/**
* Ensures the Office.js library is loaded.
*/
Office.onReady((info) => {
/**
* Maps the event handler name specified in the manifest's LaunchEvent element to its JavaScript counterpart.
* This ensures support in Outlook on Windows.
*/
if (Office.context.platform === Office.PlatformType.PC || Office.context.platform == null) {
Office.actions.associate("onMessageSendHandler", onMessageSendHandler);
}
});
view raw onready.js hosted with ❤ by GitHub

That code seemed to work, but the add-in would just hang telling me “…it is taking too long… Try Again.” I resolved it by doing this:

/**
* Checks to see if we are running in Windows Outlook
* @returns {Boolean}
*/
function isPC() {
try {
if (Office.context.platform === Office.PlatformType.PC || Office.context.platform === null) {
return true;
} else {
return false;
}
} catch {
return false;
}
}
if (isPC() === true) {
Office.actions.associate("onMessageSendHandler", onMessageSendHandler);
} else {
Office.onReady(function () {});
// Everything below is for OTHER (non-PC) clients per older constructs. Not certain
// if any of this is needed except for COMMANDS (rather than EVENTS), so eventually
// if commands are added, we have this in place…
var g = getGlobal();
// The add-in command functions need to be available in global scope
g.onMessageSendHandler = onMessageSendHandler; // on send event
}
/**
* OnSend event triggered
* @param {Office.AddinCommands.Event} event
*/
function onMessageSendHandler(event) {
Office.onReady(function () {});
Office.context.mailbox.item.body.getAsync(Office.CoercionType.Text, { asyncContext: event }, function (asyncResult) {
var body = asyncResult.value;
var event = asyncResult.asyncContext;
// … more code here …
});
}
view raw commands.js hosted with ❤ by GitHub

Essentially, I had to put the associate at the root of the file. Without the PC check, this would bomb on Office online and the Mac, so I gated that with the function you see: IsPC(). And Office online needs the Office.onReady() in the root to work effectively, so you see that there. But putting Office.ready() in the root broken Outlook on Windows, but also ignoring it/not using it, gave me the same problem. I discovered that if I put it in the event activation itself, as you see, I was finally able to get into my event handler and execute and Office.context (line of code). However, deeper in my code, it was STILL failing.

Debugging my code further, I had a LOT of Regex to determine what was going on in the body of the email. Introduce Trident+ and IE11 (es5), and you realize that “look heads” and a lot of really cool stuff I have grown accustomed to do not work. So, I struggled and struggled, until I just sent to my friend ChatGPT and asked it to produce me IE11 compatible Regex for each of my Regex statements. And then viola, my add-in was fully functional on Windows and Office online.

I am still a hacker by nature – what works, works. And I have converted from Office Add-ins written in C# with Visual Studio Tools to Office to this OfficeJS paradigm. I spend a lot of time reading the documentation and I SWEAR it tells me that with my version of Office 365 and having the WebView2 control installed, I should not be reverting to IE11 (Trident+) for Smart Events:

Microsoft® Outlook® for Microsoft 365 MSO (Version 2308 Build 16.0.16731.20052) 64-bit

But maybe it is buried in there somewhere that on Windows/PC, it always uses IE11 for Smart Events. Either way, to make sure you support 99% of the market out there, I guess you need to write you code like this or use polyfills and transpile. Bottom line is I got it to work. If anyone else is having similar issues, hopefully this helps. If anyone has an alternative or can point to something I am doing wrong (other than using polyfills and transpile my code), I am open to suggestions.

Excel Send to Trello

Well, that was fast. My latest add-in is published. Excel Send to Trello.

As per my previous post, the thing I found most interesting was how Excel full client seems to fail if you configure your server .htaccess file to prevent caching. Well, I found out that my Outlook Send to Trello add-in actually had the same problem too. The Outlook client just happened to refresh this morning and my icon disappeared there too. In Office online it seems to work, but in the full client you cannot seem to force the client to NOT cache. I see my files all pulled down locally in the Wef folder and my concern is that when I update the add-in it will not go get the latest every time… I have actually seem and beat my head over this problem a few times. But the solution there for anyone working on a BETA site for example making changes and all of a sudden the full-client stops refreshing your updates, this is a good article to keep handy.

But when Wef strikes in production, I have found customers are not so excited to blow away this folder and find all their add-ins, preferences, stored cached credentials and other goodies for each and every add-in are gone. Ergo why I added the no-cahce to the .htacess. Oh well. 🙁

Also, just to share something else as I am delving more and more into publishing add-ins for real. As an Office Developer, in the traditional sense (aka boomer 😛, VBA/VSTO/COM), there are aspects of living in an HTML web world that I still learning (although this is an old one it comes up now and again because I forget something).

You have to worry about various attack vectors and sanitizing HTML strings. There are LOTS of libraries and solutions out there and Mozilla even has documented a possible standard supported in several browsers, but not all. It is a tricky thing because some sanitizers do too much or not enough, and then you also rely on a dependency which has now burned me more often than just owning things that might be a hundred lines of code for my own common library of goodies.

So, I have created my own based on various library implementations, and found the best option is to escape most of the stuff you find “injected” rather than remove it.

/**
* Sanitizes the string for possible malicious values
* @param {String} string
* @returns {String}
*/
static sanitizeString = (string) => {
try {
string = string.replace(/(javascript:|onerror)/gi, "");
string = string.replace(/undefined/gi, "");
string = string.replace(/<script/gi, "&lt;script");
string = string.replace(/<iframe/gi, "&lt;iframe");
string = string.replace(/<object/gi, "&lt;object");
string = string.replace(/<embed/gi, "&lt;embed");
string = string.replace(/<applet/gi, "&lt;applet");
string = string.replace(/<form/gi, "&lt;form");
string = string.replace(/<meta/gi, "&lt;meta");
string = string.replace(/<link/gi, "&lt;link");
string = string.replace(/<a\s/gi, "&lt;a ");
string = string.replace(/<img\s/gi, "&lt;img ");
string = string.replace(/="/gi, "&#x3D;&#39;");
string = string.replace(/='/gi, "&#x3D;&#39;");
string = string.replace(/=`/gi, "&#x3D;&#x60;");
string = string.replace(/\/>/gi, "&#x2F;&gt;");
return string;
} catch (e) {
return `[[SANITIZED STRING MALFORMED: ${e}]]`;
}
};

The important thing is that in the web world, anytime you take data from one service to another, or take input in a field, or grab input from some element on a page and insert it back into another element in your code, there is a hack waiting to happen if you do not sanitize.

New Excel Add-in & Side Loading

I recently published a new Excel Add-in called “Excel Send to Trello.” It is a pretty nifty add-in that will take an Excel sheet full of names, descriptions, and dates and build a bunch of Trello Cards on a specific list in Trello for you. It is just a start (v1.0) and has many features to come based on popularity.

But when I submitted the add-in to AppSource they came back to me with an issue that I was not seeing. Specifically, that the icon on the Ribbon was showing the default add-ins image and not my icon. I did not see this on my “beta” test site that I was using but had sent them a link to my manifest with was right to my production published version. The issue it turns out after a lot of testing was the .htaccess on my production site and the ONLY difference was this line:

<IfModule mod_headers.c>
    Header set Cache-Control "no-store, no-cache, must-revalidate"
</IfModule>

The problem it turns out is that Excel (full client) cannot read your icon without caching it. This works FINE in Excel Online. Oh well. So, I cannot prevent my add-in JS files from being cached which it turns out is a huge problem when I push an update it can take days/weeks for the Office application to expire its own cache. Unless someone know a way around this…

Anway, the way I discovered this was via sideloading the manifest pointing to my production site. And it also turns out I had forgotten how EASY Outlook makes sideloading. In Excel (full client) it is not that easy, because you do not have the option to point to your own manifest, except in Excel Online. Then I recall some work a colleague Marty Andren and I worked on many, many years ago, called the Web Add-in Side loader. And low and behold, it still works like a charm.

I was able to sideload in Excel using the command line tool, then I saw the problem. That is where I began the file-by-file comparison “Beta” to “Production” and found the difference mentioned above.

Anyway, it was an adventure, with a blast from the past! And keep an eye out, my new Excel Add-in will hopefully be published any day now.

Outlook Signature Add-in

I recently got a question about this add-in created as a sample by the Microsoft Office Developer Team. It is provided as a sample and is a challenging bit of code to follow. It all started because I was trying to provide some advice to someone who is new to the OfficeJS world (but not Office development in general). They wanted to build a cross-platform add-in for a cause. After tinkering with it for a few hours, I managed to get it to work. But then I looked it over and it was quite a bit of tinkering on my part.

In most cases I create Add-ins based on React, but because I have done a lot of Trello development of late, I have gotten into more of a pure ES6 vibe. The code provided in the add-in as I stated was hard to follow because I think I am losing my jQuery and ES5 JavaScript skills.

Anyway, as I said, I spent a few hours today and worked on getting it into ES6 format by moving most of the code into classes, removing redundant code, removing redundant HTML pages, consolidating CSS and adding a lot of additional JSDoc comments throughout the code.

Hopefully, this version is a bit easier to follow for those that develop in a more modern ES6 style.

NOTE: This looks and behaves exactly the same, but I have not thoroughly tested it. So, it might be a bit rough here and there. If you encounter issues please let me know. 😁

Determine MIME type from base64

In writing my new Outlook Add-in (Send to Trello), I got stuck on attachments. The first version did not include an attachments option because of two unique problems that compounded each other:

  1. The Office Add-in API no longer provides a Media Type/MIME Type with an attachment request. I am able to get the “blob()” from Office, but other than the file extension there is not a way to determine the type. But sometimes a file does not have an extension, or the extension is wrong, etc.
  2. The Trello API will not let you upload without supplying a MIME type, you cannot just give it a Base64 string as an attachment and let them figure it out.

So, I found out something interesting while researching a workaround. Most every base64 string of a specific file type starts with the same “prolog” of text. Using this, combined with the fallback of the file extensions, I was able to get attachments to work (for the attachment types supported by Trello). So, v1.02 will now include attachments.

Anway, as for the workaround I found, this might be ugly, but wanted to share it anyway:

/**
* Returns the data type based on the base64 string
* @param {String} base64String
* @param {String} fileName
* @returns {String}
*/
detectMimeType(base64String, fileName) {
var ext = fileName.substring(fileName.lastIndexOf(".") + 1);
if (ext === undefined || ext === null || ext === "") ext = "bin";
ext = ext.toLowerCase();
const signatures = {
JVBERi0: "application/pdf",
R0lGODdh: "image/gif",
R0lGODlh: "image/gif",
iVBORw0KGgo: "image/png",
TU0AK: "image/tiff",
"/9j/": "image/jpg",
UEs: "application/vnd.openxmlformats-officedocument.",
PK: "application/zip",
};
for (var s in signatures) {
if (base64String.indexOf(s) === 0) {
var x = signatures[s];
// if an office file format
if (ext.length > 3 && ext.substring(0, 3) === "ppt") {
x += "presentationml.presentation";
} else if (ext.length > 3 && ext.substring(0, 3) === "xls") {
x += "spreadsheetml.sheet";
} else if (ext.length > 3 && ext.substring(0, 3) === "doc") {
x += "wordprocessingml.document";
}
// return
return x;
}
}
// if we are here we can only go off the extensions
const extensions = {
xls: "application/vnd.ms-excel",
ppt: "application/vnd.ms-powerpoint",
doc: "application/msword",
xml: "text/xml",
mpeg: "audio/mpeg",
mpg: "audio/mpeg",
txt: "text/plain",
};
for (var e in extensions) {
if (ext.indexOf(e) === 0) {
var xx = extensions[e];
return xx;
}
}
// if we are here – not sure what type this is
return "unknown";
}

New Outlook Add-in: Send to Trello

I have been using Trello for a while now and one of the features I have found most useful is to take an email I received and turn it into a Kanban item on my backlog to address later. This allows me to archive the email but keeps it on my “Trello radar” as I work at my own pace through my personal backlog.

Recently, Trello removed their add-in from the Microsoft Office store. If you have the add-in installed, you will see this error:

Well, since they say necessity is the mother of all invention and I really had to fill the gap as it is part of my routine, I rolled my own. 🤓 To add a degree of difficulty, I wrote this in VS Code in Linux running in Windows Subsystem for Linux (WSL). See my previous post. It was a fun exercise as I am on vacation and using the time tom learn new things, engage in self-improvement and relax (coding is relaxing to me 🤓🤓🤓). In the end, I learned something and created something for everyone to enjoy.

Say hello to the recently published: Send to Trello Outlook Add-in.

Give it a try and let me know what you think.

Office.JS Q&A on stackoverflow

Recently I have become aware that many of our Office.JS folks at Microsoft spend time answering questions on stackoverflow. I want to call out that there is a specific office.js category where you can get your questions answered.

I hope to spend a little of my time there each week answering some questions as well. I have already asked on and got a swift response.

Automating Excel and Add-ins Don’t load

There is a common problem I see when automating Excel from an external .NET application:

You will launch Excel, open a workbook, perform work, and then try to close Excel and find that it remains in memory. You might even have code in your solution to try to re-use existing instance of Excel and find that it uses one of these “zombie” instances that do not have any of the add-ins loaded… and maybe an add-in your code relies on.

The issue is that Excel is not able to close because  the primary thread of your application is holding on to a COM reference. Even adding Marshal.ComReleaseObject() and GC.Collect() like this probably will not improve the situation either:


Marshal.ReleaseComObject(excelWorkbook);
Marshal.ReleaseComObject(excelApp);
excelApp = null;
excelWorkbook = null;
GC.Collect();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();

view raw

COMRelease.cs

hosted with ❤ by GitHub

From my experience, and from what I have gathered is that until the thread is stopped, the COM reference will be locked. Not sure where the issue lies (EXCEL, COM, or .NET), but I have found a workaround to this. What you have to do is terminate your thread. But because you do not want to terminate your application, you need to create a NEW thread. So the following pattern is what I have found works:


OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.Filter = "Excel Files|*.xls*";
DialogResult result = openFileDialog1.ShowDialog();
if (result == DialogResult.OK)
{
new Thread(() =>
{
Excel.Application excelApp = null;
Excel.Workbook excelWorkbook = null;
this.Invoke(new Action(() =>
{
try
{
excelApp = (Microsoft.Office.Interop.Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
}
catch
{
excelApp = new Excel.Application();
}
string workbookPath = openFileDialog1.FileName;
excelWorkbook = excelApp.Workbooks.Open(workbookPath);
excelApp.Visible = true;
}));
Marshal.ReleaseComObject(excelWorkbook);
Marshal.ReleaseComObject(excelApp);
excelApp = null;
excelWorkbook = null;
openFileDialog1 = null;
GC.Collect();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
}).Start();
}

 

 

JSDoc – Wow, I just learned something cool

As I start to delve deeper into the world of Office.JS and JavaScript, I am having some fits with how things are done and how things are implemented. Besides getting used to a whole new load of terms, finding out that there are more frameworks than there are developers (ok, maybe that is a small exaggeration), or that JavaScript is not “really” object-oriented (and that’s “ok”) is just nearly too much to handle.

Then there is one of my biggest complains with JavaScript is it use of types. You can get in a lot of trouble here and you can even break JavaScript from its root if you really prototype something wrong (see monkey patching). Additionally, scope can be an issue. And then there is Visual Studio 2015 doing it’s best to try to help you with types, but then it fails you. For example, you type “something(dot)” and you expect IntelliSense to come to the rescue – and low and behold, you get what I like to call: “The yellow bangs of hell.“(tm) wlEmoticon-hotsmile.png

yellow-bangs-of-hell.png

I have been coding with this problem thinking I am all alone in this world screaming every time an object, string, or number fails at IntelliSense. Then on an internal Microsoft email thread where the virtues of TypeScript and JavaScript were being contemplated, a whole world was opened before my eyes. Enter JSDoc. Turns out there is an entire specification around this. And even better – Visual Studio 2015 support it. And it is pretty easy to use. Plus type definition is just the tip of the iceberg, there is a LOT more for me to lesrn. But for now, here is an example of how to define a few common types I frequently use:

[code language=”javascript”]
/** @type {XMLDocument} */
var xmlDoc = $.parseXML(ewsResult.value);
/** @type {string} */
var theString;
/** @type {number} */
var myNumber;
[/code]

I have started to use this very recently in my proof of concepts I work on with my customers and I think I just increased my productivity by 50% (or more). Holy cow. wlEmoticon-winkingsmile.png

Great Office.js resource

As you might know (or I hope you do by now), Office Web Add-ins are the new thing. And if you have recently started a project in this new paradigm, you know that it is quite different from Office COM/VSTO/VBA based coding. There is a little bit of a learning curve and some of the new code patterns (namely async) make doing things “the old way” not quite possible. As such this can make getting your head around this programming model a tad tough. And then there is JavaScript too. wlEmoticon-disappointedsmile.png

One of my colleagues, Michael Zlatkovsky (a member of the Office.JS development team at Microsoft) is writing a book: Building Office Add-ins using Office.JS.

book-cover

 

I have had an opportunity to read this book and believe it to be a great resource to understanding the new model. Some of the best parts of the book are where he covers the concepts behind the model, its use of async (and why), how to get around some of the challenges of async with Promises (great chapter), the way to use the Office context objects with some great code patterns, and much, much more.

Michael’s companion website is a must to bookmark as well. Check it out: http://buildingofficeaddins.com.