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

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