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

easyEws Updated to v1.0.17

There is one major change and one fix in this update:

  1. The library no longer has any dependencies on jQuery. As such all parsing is done with DOMParser and all loops are traditional (for, versus $.each()).

    NOTE: This is a fairly major change as it touches the core asyncEws call, which is at the very core over every call in the library. If you have an issue with this build, please point to the previous build of the library directly. See more below…
  2. The sendPlainTextEmailWithAttachment() function was fixed to submit parameters as a Object versus individually.

The GitHub repository has all previous versions. For example, to access the primary CDN, go to:

https://cdn.jsdelivr.net/gh/davecra/easyEws/easyEws.js

If you need to access a specific version (like the last one):

https://cdn.jsdelivr.net/gh/davecra/easyEws/easyEws1.0.16.js

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

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

Script Lab for Outlook. Yeah!

OMG! This is a day I have been waiting for.

I do a LOT of Outlook Web Add-in development. A lot. I have made the move from Visual Studio to Visual Studio Code, from C# to JavaScript and have not looked back. However, there are a few things that make development in this new area difficult and that is rapid prototyping.

For C#, I could always go into VBA and see how the object model would behave when I needed to test a hypothesis. I was able to use VBA as a laboratory for ideas before I codified them into solid C# code in a VSTO Add-in. But there was no such laboratory in the JavaScript web add-in world, until now…

…Script Lab for Outlook has been released. And the best part, it is available in Outlook on Windows, Outlook on Mac and Outlook on the web!

Script Lab in Outlook for Windows
Script Lab for OWA

If you do a lot of Outlook development this will be a godsend. You will now be able to go into Script Lab and test your code ideas before you add them to your more complex add-in.

Open Outlook, go into the Office Store and type “Script Lab” and Script Lab for Outlook should come up in your list. Select and install it. Then open a message in Outlook and you should see the Script Lab items on the Home tab.

Happy coding!

Communication between Office Web Add-ins

Sometimes you have multiple add-ins and you need to facilitate communication between them. For example, a common scenario I have heard is that you have:

  • A Content Add-in that displays something like a graph or an organization chart.
  • A Taskpane app that allows you to manipulate settings, upload and download data from a backend web service.

You need to be able to facilitate communication between the two so that when updates happen to one add-in, the other receives those updates. I recently worked on a proof of concept that helped prove how this can be done.

The solution is to use the Document as a communication medium. In the particular case we used CustomXMLParts in the document. Here is how it would work:

  • One add-in would need to send an update to the other, so it would write a CustomXMLPart with a specific namespace and a “context” (basically, I am the TaskPane communicating) to the document.
  • Both add-ins will have a window.setInterval() thread running to check the documents for CustomXMLParts in that given namespace.
  • The timer on the Content Add-in would fire, find the new customXMLPart from the taskpane, read the contents and then update itself as needed and finally, delete the CustomXMLPart.

Here is the code for the Content Add-in to look for the message from the TaskPane:

Office.initialize = function(reason) {
// background thread checker
window.setInterval(() => { checkForPart(); }, 1000);
}
const ns = "http://pfe.microsoft.com/excelpoc/1.0";
const xml = "<message xmlns='http://pfe.microsoft.com/excelpoc/1.0'>&quot; +
"<sentby>[who]</sentby>" +
"<info>[data]</info>" +
"</message>";
const from_tp = "TASKPANE ADD-IN";
function checkForPart() {
Excel.run(function(context) {
/**@type {Excel.CustomXmlPartScopedCollection} */
var customXmlParts = context.workbook.customXmlParts.getByNamespace(ns);
customXmlParts.load();
return context.sync().then(function () {
if(customXmlParts.items.length > 0) {
/**@type {OfficeExtension.ClientResult<string>} */
var xmlData = customXmlParts.items[0].getXml();
context.sync().then(function() {
/**@type {DOMParser} */
var parser = new window.DOMParser();
/**@type {Document} */
var xmlDoc = parser.parseFromString(xmlData.value, "text/xml");
/**@type {Element} */
var who = xmlDoc.getElementsByTagName("sentby")[0];
/**@type {Element} */
var data = xmlDoc.getElementsByTagName("info")[0];
document.getElementById("message").innerText = who.innerHTML;
if(who.innerHTML == from_tp) {
// write tot he pane
var dt = new Date();
var currentTime = pad(dt.getHours(),2) + ":" + pad(dt.getMinutes(),2) + ":" + pad(dt.getSeconds(),2);
// update a DIV on the page
document.getElementById("message").innerHTML = "<p>Message sent on " +
currentTime +
" by " + who.innerHTML +
" and the message is " + data.innerHTML;
// now we delete the part
customXmlParts.items[0].delete();
return context.sync();
}
});
}
});
});
}

Next, here is the code in the Task Pane Add-in that will send the message for the content add-in to read:

const ns = "http://pfe.microsoft.com/excelpoc/1.0&quot;;
const xml = "<message xmlns='http://pfe.microsoft.com/excelpoc/1.0'>&quot; +
"<sentby>[who]</sentby>" +
"<info>[data]</info>" +
"</message>";
const from_tp = "TASKPANE ADD-IN";
function sendMessage() {
Excel.run(function(context) {
var data = xml.replace("[who]", from_tp).replace("[data]", "This message is coming from the taskpane.");
const customXmlPart = context.workbook.customXmlParts.add(data);
customXmlPart.load();
return context.sync().then();
});
}

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.