Published: Trello Taskpane for Word

I have done it! I have published my very first Office Web Add-in to the Microsoft Store. After years and years of blogging about it, I wrote an add-in for something I needed. It is true that necessity is the mother of all invention.

The add-in is called the Trello Taskpane for Word. It is a simple add-in that connects Word to your Trello boards, so that you can insert information you have collected in Trello into your documents. I do research on the web for various topics and send snippets and pages to Trello cards where I organize and refine them. Then I found myself copy/pasting the information from Trello into Word getting lost on the task switching. Now, I can remain in Word, select the cards I want, and insert them without ever leaving Word. How’s that!

It was also a fun exercise for me to combine the two types of extensions I have found to enjoy writing (Office and Trello). More importantly, it is a useful tool that I think might benefit other writers that use Trello, or writers looking for a new way to keep their notecards and thoughts in a more digital, accessible from anywhere type of format.

Have fun and let me know what you think!

5entences Add-in Released

I have been spending some of my free time writing add-ins for Office and Power-Ups for Trello. I just completed two add-ins for Office. One is going through the process with AppSource and I will blog about it soon enough. The other cannot go though AppSource because it uses the Block On Send interface for Outlook. The add-in I just release is the 5entences Add-in for Outlook.

I have been inspired by various books I have read that all cross-pollinate the idea that we use e-mail all wrong or too much. Cal Newport’s “A World without Email” and Tim Ferris’ “4 Hour Workweek” are just two examples. And I believe it was Tim’s book that first touched on brevity in email. And thus, this website was born: five.sentenc.es.

From those books, and website, I had an idea. Why not add “training wheels” to the idea of email brevity? So, I wrote the 5entences Add-in for Outlook as a simple to use add-in that tries to help you keep your email responses brief and to the point.

It has been a fun side project as I was delving deeper into writing React code and using prebuilt React controls, so I wanted to share it with everyone. Maybe we can make email fun again.

Disabling your Web Add-in if you have an Equivalent COM/VSTO Add-in

In previous post, I described how you can add code to your VSTO/COM add-in to disable your Web Add-in loaded from maniferst via the Office Portal:
Removing Web Add-in Ribbon Customization in Outlook for Windows (theofficecontext.com)

Well with Outlook for Windows, Office 365, version 16.0.13728.36136, you can now disable your Web Add-in via Policy settings.

Download Administrative Template files (ADMX/ADML) and Office Customization Tool for Microsoft 365 Apps for enterprise, Office 2019, and Office 2016 from Official Microsoft Download Center

Here is the path in the policy editor for this setting:

Once in the policy editor, you would set the value. If your OfficeJS Web Add-in has a GUID of {c5bc7737-0d79-4302-9e73-2a614941e914} and the COM add-in is called “My Full Featured Outlook Add-in“, you would set the values like this:

Specifically, the registry key it puts in place is this:

PATH: HKCU\SOFTWARE\Policies\Microsoft\Office\16.0\Outlook\addins\equivalentcomaddin
KEY (string/REG_SZ): {OfficeJS GUID}
VALUE: Prog.ID

For example, if I have an Outlook Web Add-in called “My Outlook Add-in” and it has the following manifest entry:

<?xml version="1.0" encoding="UTF-8"?>
<OfficeApp 
          xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" 
          xmlns:mailappor="http://schemas.microsoft.com/office/mailappversionoverrides/1.0" 
          xsi:type="MailApp">
	<Id>c5bc7737-0d79-4302-9e73-2a614941e914</Id>

Let’s say I have a full VSTO add-in called “My Full Featured Outlook Add-in” and it is registered as the following:

If you were to look at the Policy key after it is set you would have an entry like this:

KEY: {c5bc7737-0d79-4302-9e73-2a614941e914}
VALUE: My Full Featured Outlook Add-in

If it were an older traditional COM Add-in, like the top item: AccessAddin.DC, then it would look like this:

KEY: {c5bc7737-0d79-4302-9e73-2a614941e914}
VALUE: AccessAddin.DC

That is all you have to do if you have the newest builds of Outlook for Windows.

NOTE: Coming later in the year, will be an option to add this entry to your Web Add-in Manifest file the way you can with Excel and Word. 
NOTE: This is all covered in the latest Office add-ins community call.

Office Open XML File Viewer

In case you did not know, Office files that end with an (x), like PPTX, XLSX and DOCX, or an (m), like PPTM, XLSM, DOCM are actually ZIP files. If you rename these files to .ZIP, you can open them up and see all the component that make up what is now an the Office Open XML File Specification.

Years ago I was working on a project where we were programmatically modifying Office documents using the Open XML Toolkit. I got sick and tired of renaming the files to .ZIP, looking inside, making corrections as needed, and then renaming the file back. So I created this tool. I used it – a lot, and then… Well, I forgot about it.

I recently started working on a project where I needed to quickly get into the contents of an Office file to see if the changes being made were correct. I also needed to do light editing of the internal XML parts at times. And I seemed to remember doing all this before. I searched and search and then I found this old project. I have been using it a couple weeks now and figured it might be a good thing to share with the community. So I published it on GitHub:

https://github.com/davecra/OpenXmlFileViewer

The details for install and usage are in the README.md. Once installed, you will have to register it for each file type. Once setup, you would right-click on the file, select Open With and then click OpenXmlFileViewer.

OpenXmlFileViewer on the PowerPoint (pptx) right-click, Open With menu

You will be able to browse the parts like you would files in File Explorer:

The Office Open XML File Viewer application window

Please let me know if you have any questions or issues.

OfficeJS: Get the current user’s Username

While working on a customers proof of concept, we determined that we needed to know who the current user opening the add-in is. In most scenarios where there is a Store Add-in, you have the user log in. But we are in a enterprise environment, have an embedded taskpane and did not want to nag the user every single time they opened the document.

Outside of the BETA API set, there actually is not a way to do this in Word, Excel and PowerPoint. In the current BETA API (soon to be released), is the new Single sign-on (SSO) API. Detailed here:

https://docs.microsoft.com/en-us/office/dev/add-ins/develop/sso-in-office-add-ins

I know, I know… you do not want to have to have the user sign-on and neither did we. But actually you do not need to. You make a call to getAccessToken() – after you have gone through the rather complex process of setting up your application in Azure AD – and in the returned token is the user information. Here is all you need for code to get to the username:

export async function getUserName() {
try {
let tokenData = await OfficeRuntime.auth.getAccessToken({ allowSignInPrompt: false, forMSGraphAccess: true });
var parts = tokenData.split(".");
var token = JSON.parse(atob(parts[1]));
return token.preferred_username;
}
catch (exception) {
console.log(exception.message);
}
}
view raw getUsername.js hosted with ❤ by GitHub

Office Web Add-in Debugging in VS Code

Another recent announcement that has me excited is the ability to debug Office Web Add-ins directly from VS Code. Before this recent announcement, it was a hit or miss proposition. There was Visual Studio 2019, that did a pretty good job. But I liked the lightweight simplicity of VS Code. Visual Studio 2019 seemed too too heavy-weight. It’s hard to put my finger on how or why, but I really enjoy VS Code for Web Add-in development so much better. Except for debugging…

So, to debug, I actually did most of my dev/test in the web versions of Office (Excel online, Outlook online, etc.). Then came the Edge Developer Tools Preview which helped debug task pane add-ins in the full clients. But that did not help with things like the On Send event in Outlook or other UI-less functions. So, it was a struggle at times.

Now that has all changed!!!

The process is a tad more complicated than I like, but it does work. Essentially, you need to:

  1. Run VS Code as administrator
  2. Install the extension in VS Code by pressing CTRL + SHIFT + X and searching for the “Microsoft Office Add-in Debugger”
  3. Add the following code to the .vscode\launch.json file to enable Office Debugging in your project. You will need to update line #7, and replace the uppercase HOST text with the host application for your Office add-in.
{ 
   "type": "office-addin", 
   "request": "attach", 
   "name": "Attach to Office Add-ins", 
   "port": 9222, 
   "trace": "verbose", 
   "url": "https://localhost:3000/taskpane.html?_host_Info=HOST$Win32$16.01$en-US$$$$0", "webRoot": "${workspaceFolder}", 
   "timeout": 45000 
}

NOTE: If you create a new Office project with Yeoman, you will not need to add this line, it will be part of the default template going forward.

  1. Press CTRL+SHIFT+D to then open the Debug pane, select Attach to Office Add-in option from the drop down list at the top of the pane, and press F5 to start debugging.
  2. You can now set breakpoints, see variable values, etc.
Attach to Office Add-ins menu option

Detecting Print in Outlook (VSTO/C#)

This is a common problem in Outlook. You might have tried to override the Ribbon settings for Print in Outlook to find that your code never gets run when the user clicks Print.

There is also not any events in the Outlook object model to detect Print either. So if you need to detect the user pressing the print button, you are out of luck.

While it is still not possible to detect the print button being pressed, you can at least detect when the user has selected the Print tab on the backstage.

The following code uses a background thread and a series of Windows API calls to FindWindow/FindWindowEx to detect when the Print tab on the backstage is opened:

[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
/// <summary>
/// Startup for Outlook Add-in
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
// we start by creating a background thread and look for a specific
// set of windows to appear, then we know the user clicked print
new Thread(() =>
{
while (true)
{
Thread.Sleep(1000);
CheckForPrint();
}
}).Start();
}
/// <summary>
/// Checks to see if the user has opened backstage and
/// selected the Print tab
/// </summary>
private void CheckForPrint()
{
try
{
// depending on whether we have an inspector active, or the explorer
// active we will need to get the caption to FindWindow
string LstrCaption = "";
if(Application.ActiveWindow() is Outlook.Inspector)
{
// Active inspector caption
LstrCaption = ((Outlook.Inspector)Application.ActiveWindow()).Caption;
}
else if(Application.ActiveWindow() is Outlook.Explorer)
{
// Active explorer caption
LstrCaption = ((Outlook.Explorer)Application.ActiveWindow()).Caption;
}
// get the window handle
IntPtr LintHostHandle = FindWindow(null, LstrCaption);
if (LintHostHandle == IntPtr.Zero) return; // if we cannot find it – nevermind
// create a list of windows to find (in reverse order)
// 4) rctrl_renwnd32 – is the print preview window
// 3) NetUICtrlNotifySink – is whole Print options and preview
// 2) NetUIHWND – is the the entire print tab
// 1) FullpageUIHost – is the backstage page
Stack<string> LobjWindowClasses = new Stack<string> (
new string[] { "rctrl_renwnd32", "NetUICtrlNotifySink", "NetUIHWND", "FullpageUIHost" });
// recursive call back to find each window in the stack.
// if all of them are found, then present a message to the user
if(FindWindowStack(LintHostHandle, LobjWindowClasses))
{
MessageBox.Show("You have clicked on the Print Tab in Outlook.");
}
}
catch { }
}
/// <summary>
/// RECURSIVE
/// This function will take the window classnames in the provided stack
/// and then find each one in order via recursive calls. If all of them
/// are found – we return true = found
/// </summary>
/// <param name="PintHandle"></param>
/// <param name="PobjStack"></param>
/// <returns></returns>
private bool FindWindowStack(IntPtr PintHandle, Stack<string> PobjStack)
{
try
{
// get the window with the classname being popped off the stack
IntPtr LintNewHandle = FindWindowEx(PintHandle, IntPtr.Zero, PobjStack.Pop(), "");
if(LintNewHandle != IntPtr.Zero && PobjStack.Count == 0)
{
return true; // found it
}
else if(LintNewHandle!= IntPtr.Zero)
{
// found a window, but the stack still has items, call next one
return FindWindowStack(LintNewHandle, PobjStack);
}
else
{
// did not find it
return false;
}
}
catch
{
// oops
return false;
}
}

easyEws v1.0.16 Published

I have made a few minor updates to easyEws. First, was a fix for distribution lists with an “&” in the name. The second is a few minor JSDoc updates for better linting. And finally, I changed a couple forEach loops to traditional for loops for performance reasons.

I have published the update to NPM and to my GitHub.

Please let me know if you have any issues or questions.

Same Add-in Running Everywhere

If you have followed my blog or the Microsoft Office Developer Blogs enough, you have heard over an over again how Office Web Add-Ins are truly cross-platform.

In working on a proof-of-concept I created a simplified Word Redactions add-in that is truly cross-platform. I have shared the entire project on my GitHub repository, so you can see how it works.

Here are the screenshots to prove it runs on every platform supported:

  • Windows
  • Mac
  • Web Browser
  • iPad

Migrating a VS2019 Office Web Add-in to VS Code

I find myself frequently converting Office Web Add-in projects I originally created in VS2019/2017/2015 over to VS Code. This is because VS Code has become my preferred development platform for everything Web based.

There is a trick to this and a few things you need to know:

  1. Your project in VS2019 (et. al.) has a completely different structure, so once you move things around, you need to change some references.
  2. If you use the Yeoman generator to create the shell of your new project, you need to know a little about webpack.

So, in the following sections we will cover what you need to do:

  • Copy over all the files
  • Update the references
  • Add web pack commands

Copy Files

  1. Run “yo office” and create a default taskpane add-in, with JavaScript and for Outlook. This will create your baseline project. You will open this project and delete these files:
    1. .\src\taskpanes\taskpane.html
    2. .\src\taskpanes\taskpane.js
    3. .\src\commands\commands.html
    4. .\src\commands\commands.js
    5. .\assets\*.* (delete everything in there)
  2. In your VS2019 project and then open your Manifest (highlighted in the image below). Select All and Copy everything from the manifest.
  1. Open your shell project in Visual Studio Code, open the manifest.xml and paste. Later we will update the references.
  2. Next, copy the VS2019 FunctionFile.html and FunctionFile.js files to the .\src\commands folder in VS Code, then rename both: commands.html, commands.js
  3. Next, copy your VS2019 taskpane.html and taskpane.js to the taskpanes folder in VS Code.
  4. If you are like me and also have dialogs, you can create a .\src\dialogs folder in VS Code and then copy the dialog.html and dialog.js from VS2019 to VS Code.
  5. Be sure to copy over any other supporting scripts you have. I created a folder called .\src\common in VS Code and copied those JS files from VS2019.
  6. Next, anything you have in the Content folder in VS2019 you need to copy to VS Code .\assets folder.

That is it for the copy part. Now for the manifest part.

Update the Manifest

At this point you need to update the manifest.

  1. Open the manifest.xml in VS Code and Press CTRL+H to open the Find/Replace dialog. Type in ~remoteAppUrl and replace it with https://localhost:3000. Then replace all instances.
  2. Next and this part is a bit more tricky, but you need to look at every single html pointer URL and verify that it points to the ROOT. Meaning if it was [~remoteAppUrl/Functions/FunctionFile.html] and then you did the Find/Replace, it became [https://localhost:3000/Functions/FunctionFile.html]. You need to update this to be just: https://localhost:3000/FunctionFile.html. This is because when you compile your project with Web Pack, it places everything in the root.
  3. Again, this is tricky, but every one of your base URL’s for images will need to be updated to point to the correct image in the ./assets folder.
  4. Once you have completed, you can press CTRL+~ to open the command console and type: npm run validate. This will tell you if there is anything wrong with your manifest.

Update the HTML files

Next you need to open up each of your HTML files: dialogs.html, commands.html (was called FunctionFile.html), and taskpane.html. etc. In each of these you will find a script that points to a respective JavaScript file and possibly other source files you copied over to the .\src\common folder. Remove all those script tags. Leave only the tags that point to online CDN’s. We will get to this in one of the following sections, but essentially Web Pack will put all the references in for you automatically at compile time.

Also, any images that you are referencing in your HTML with the <img> tag will need to be updated to point to the proper file in the ./assets folder.

Update the Commands.js (formally FunctionFile.js)

Now we need to get everything ready for Web Pack and that is where we start getting into some code. I will cover Web Pack in another post soon, but what it uses is a global namespace for the functions you call. So you MUST add this code to the bottom of the JS file:

/*************************************************/
/* REQUIRED BY WEB PACK – DO NOT DELETE */
/*************************************************/
function getGlobal() {
return typeof self !== "undefined"
? self
: typeof window !== "undefined"
? window
: typeof global !== "undefined"
? global
: undefined;
}
const g = getGlobal();
g.onSendEvent = onSendEvent;
g.onRibbonButtonClick = onRibbonButtonClick
/*************************************************/
/* REQUIRED BY WEB PACK – DO NOT DELETE */
/*************************************************/
Web Pack Required code

In your manifest you have specified the functions that will be called when the user clicks on a Ribbon button or Events that will fire such as OnSend in Outlook. Find those function and made sure they are added to the global namespace as shown.

Also, at the top of EVERY single JS file in your project you will need to add the following line:

/* global global, self, window, Office, console, myClass*/

When you start to edit these files in VS Code, there will be a whole lot of “red squigglies” for undefined items in the global space. For each of those that the syntax highlighter defines as such, you will add it to the global tag at the top of the file. Like this:

Undefined object

Update the Common JS Files

For the .\src\common files any function you want to be available in the global namespace, meaning you call it from one JS file to the next, you will want to add the same section above and make sure the function is specified.

NOTE: You will also want to do this for any global constants or vars that you want visible as well.

Update the Web Pack Config

Finally, we have to update the webpack.config.js. This is probably the most tricky part. What you need to do is locate the values for {entry} and make sure that every JS file in your project is represented:

entry: {
polyfill: "@babel/polyfill",
dialog: "./src/dialogs/dialog.js",
commands: "./src/commands/commands.js",
shared: "./src/common/shared.js",
common: "./src/common/common.js",
taskpane: "./src/taskpane/taspane.js"
},
view raw entry.js hosted with ❤ by GitHub
Web Pack {entry}

NOTE: This is critical to get correct and to include all JS file because if you do not do this, it will not work.

Now that you have all the entries in place you need to update the {plugins} section. You might need to create an extra module or two as well. By default you have one for taskpane and commands, but my example will show dialogs as well:

plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "taskpane.html",
template: "./src/dialogs/taskpane.html",
chunks: ["polyfill", "common", "shared", "taskpane"]
}),
new HtmlWebpackPlugin({
filename: "dialog.html",
template: "./src/dialogs/dialog.html",
chunks: ["polyfill", "common", "dialog"]
}),
new HtmlWebpackPlugin({
filename: "commands.html",
template: "./src/commands/commands.html",
chunks: ["polyfill", "shared", "commands"]
})
],
view raw plugins.js hosted with ❤ by GitHub
Web Pack {plugins}

Note how in each plug-in (the HTML file), there are “chunks.” These chunks are the references to each script tag that will be added at compile time. It is VERY important to make sure that you reference each file as expected.

Ready to Test

At this point you should be ready to test. If you manifest validated, you can now just press CTRL+~ to open the command console and type npm start.

If your solution successfully sideloads in Office, you should verify that your commands, events and taskpanes all function. If they do not here are some common issues:

  1. You did not properly reference your JS file in the webpack.config.js. Either defined as an {entry}, or as a chunk on a {plugin}.
  2. You did not add the required code at the bottom of the JS file for your functions to be defined in the global namespace, or you forgot to add one or more.
  3. Your manifest is not pointing to the right file. Remember, once Web Pack compiles your code, everything is in the root, except for the assets.

That is a lot, I am sure I missed a few things even though this post took me a while to write. If you have any issues with porting over your project and find there are some missing steps or something I can clarify more, please let me know.