Outlook OnSend and Dialog Sample

As promised, I have created a GitHub repository for the sample I blogged about earlier. Here is the repository:

https://github.com/davecra/outlook-sample-1

This includes three items of interest:

  1. The OnSend event, implemented in the simplest of ways to demonstrate bare bones how to get it to work.
  2. The displayDialogAsync with the inline frame (displayInFrame) option. I will blog about this in the future as well.
  3. A project build by Yo Office and published per my blog post here.

What this sample add-in does is pops up a dialog box anytime you press Send on an email with a question: “Are you sure?” If you click Yes, it sends, if you click “no” is blocks the send. Here is what that dialog looks like:

dialog

If you have followed the steps from my Yo Office post, you will be able to open the project in VS Code, and from the terminal, launch it with:

npm start

Next, you will need to follow the steps to enable the OnSend policy in your account and then you will need to install/sideload the manifest. From there you will not see any appearance of the add-in at all, until you press the Send button.

NOTE: At the point I first tested it on June, 13th, 2017, there is a KNOWN ISSUE with the certificates created fro a “yo office” build. If you have not resolved the certificates issue you will be able to start the project in Node.js, but the add-in will fail to run. You need to perform the steps outlined here to correct it:

https://github.com/OfficeDev/generator-office/issues/244

EasyEWS: JavaScript Library Improved

I have been working on improving my various Outlook proof-of-concepts and nearly every one of them require using EWS to access data about items in Exchange, which are not exposed through the object model. As such I have been constantly improving easyEWS.js. I have just posted version 1.0.6 and also have provided a published CDN which you can now use to always get the latest version of the library. Here is the CDN:

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

Here is the latest version on GitHub:

https://github.com/davecra/easyEWS/blob/master/easyEws.js

Specifically, the improvements I have made are:

  1. Typed every var using JSDoc. See my post on that here. This should help with debugging any issues you have. And if you have issues with my library, please contact me.
  2. Updated the Error handling callbacks and the Debugging callback to include more information. Every function can be called with this pattern, and you will get more information now if something does wrong. Here is the essential pattern every function uses:

[code lang=”javascript” collapse=”false”]

easyEws.fn([x, [y],] function(z) {
// this is the success
}, function(error) {
// this is the failure – error
}, function(debug) {
// this is the debug information which contains:
// the soap message sent to the server
// the soap response from the server
// the server status message
// the server error message if any
}

[/code]

Yo Office

Ok, odd title, I know. For some developers “in the know,” they will understand what I am eluding to. So what does it mean? It is short for Yeoman. In their own words, here is what it does:

Yeoman helps you to kickstart new projects, prescribing best practices and tools to help you stay productive. To do so, we provide a generator ecosystem. A generator is basically a plugin that can be run with the `yo` command to scaffold complete projects or useful parts.

So, by “yo office”, I am telling you that Office Web Add-in templates can be delivered through this cross-platform command line tool. This means you do NOT need Visual Studio (or even Windows) to create a new Office Web Add-in project package. More importantly, these packages are setup through NodeJs Package Manager and are configured to use Node.js web server (which comes on MANY different platforms).

Now, if you are like me (a 20+ year VS Office Developer veteran), when I first heard this, it all sounds just like this:

Γράφω στα ελληνικά και αν καταλαβαίνετε αυτό, καλό για σας
FYI: That is Greek. wlEmoticon-hotsmile.png
So, I have a choice here and that is to make this a long blog post, or a short blog post and I dislike making long posts as much as you do reading them, so… In this case, here is the executive summary: The Microsoft Office Developer team is trying to make Office an accessible development platform no matter where you come from (experience wise) or what platform you use.
Now, for “super hip” developers living on the cusp of command-line based free developer tooling, put this in your MacBook:
yo office
For the rest of you…
To get this to work, you need to install Node JS from here. And next you need to install the prerequisites as per these steps. Once installed, you will open the Node JS command prompt (in Windows, from the Start menu, type NodeJS and it should appear as a choice) and perform these steps:
  1. Change directory (using cd) to the folder where you want the project to live.
  2. Type “yo office” and then answer the questions.
  3. In my case, I developed an Outlook add-in, with a manifest, using JavaScript, called “Outlook-Sample-1” and I chose JQuery as the framework. Here is the output:

yo.PNG

When it is done, it will create a folder called Outlook-Sample-1 and place a set of files in it that you can edit and the NodeJS configuration to run it from a standalone web server locally. I used Visual Studio Code (or VS Code, as it is known, which is a new multi-platform code editor from Microsoft that runs on Mac, Windows, and Linux), to edit the add-in.
NOTE: If you have configured everything in Visual Studio Code correctly, you can not only get NodeJS to interact from the “Terminal Window” you can also hook to GitHub to push/pull your projects. It is a whole other topic for a whole other blog post, but VS Code is a bare essentials utilitarian cross-platform code editing development tool.
Now, which files you need to edit and such are beyond the scope here, but if you have done some development in this area before, you can probably quickly figure it out. I might post more on this in the future. Stay tuned!

Ok, ok… I know, I know… I need to stop here and address the big huge hulking elephant in the room. YES, these are command line tools. Yes, DOS is still dead. No, it is. Really. Repeat after me, DOS “IS” DEAD. Now, just accept that this is the way it is, because this is the way it is. I know, it seems so… 1994… Neo called from the Matrix to assure you it is, indeed, 2017. wlEmoticon-surprisedsmile.png For my fellow Visual Studio, 20+ year developer gurus: “deep breaths.” Now, back to our regularly scheduled broadcast..


Here is a view of my project from VS Code, with the Terminal Window open…
vscode.PNG
Once I was done, I opened a NodeJS command prompt in the Terminal window and launched my project using the following command:
npm  start
Once you run this command, you will effectively start NodeJS web server at http://localhost:3000 which will be hosting your WebAdd-in. But you still need to side load your manifest. To do this, you will need to follow these steps (for Windows), here for Mac, here for Outlook. This gets all kinds of technical and deep into other areas that are potentially future blog posts, but the point is that you can then debug your add-in locally no matter which platform you are on.
Ok, Ok… back to the elephant
So, is this BETTER than Visual Studio Enterprise and the JavaScript debugger there and code assistance I get?
Well, this is more nuanced… I lean towards “no” as most of this stuff is too lightweight, especially given my experience. Especially without the debugging. But, I do see some advantages (besides the command line tools) that have its advantages for use on Windows:
  • It is available across multiple platforms – including Windows. VS Code, Node JS, and “yo office” are everywhere, anywhere, over there, under that, etc.. wlEmoticon-disappointedsmile.png
  • Out-of-the-box support for TypeScript. Which I have not blogged on a lot, but Michael Zlatkovsky has covered well in his book. TypeScript makes JSDoc seem like child’s play.
  • Out-of-the-box support for the Node Package Manager ecosystem. This is a huge repository of reusable code.
  • VS Code is a light weight editor that hooks into NPM, Node and Git really well. And while it does not debug, recent builds of Office have added the ability to hook to a debugger right from your taskpane. Which is only good if you have a taskpane, but it is a start.
  • Node provides auto-refresh (browser-sync), auto-compilation, and other goodness.

Again, this might look like a lot of Greek, but if you find your shop turning Greek, you can at least know some starter phrases to begin acquiring the knowledge you need to speak it.

 

So, why is any of this important to me? Especially if you are telling me:
“I use Visual Studio Enterprise (or Pro) and I am happy with it, why would you introduce me to this Greek language lesson.”
The answer comes in three points you can take away:
  • Office development is really multi-platform capable now. This correlates to the fact the Office Applications are also multi-platform. Write your add-in once, and it will run (or the future plan is, it will run) on every platform you can think of: Windows, Mac, iOS, Android, Linux (or essentially an browser or any platform with a browser that has HTML5 support).
  • You know more about the vision, commitments and goals of the core Microsoft Office teams. If you get nothing out of this other than Microsoft is committed to true multi-platform capabilities with Office Web Add-ins, you have come away with a major point. But there is also the knowledge on how Office development can be done no matter the developer background. And hey. You 20+ year VS Office Developer veterans: take this to heart and read into it, because these are the tea leaves; then look over to your mentee that is likely your kids age and <sigh>.
  • You can move to another platform and still take what you know with you. This means that “cool” MacBook you bought several years back and gave to your kiddo when they went off to college can actually do something more than read email and browse web pages. wlEmoticon-hotsmile.png Uh, I still strongly suggest you stick to Web Add-in development on Windows and Visual Studio Enterprise if you are already there… just saying…
So, with this, I hope I have sufficiently given you a vision of things to come, and maybe even given a few of you some new things to research and delve into. If you want more, the entire how-to with Yeoman, including a cool video, is on the following page:

The Most Anticipated OfficeJS Feature is Here

If you are an Outlook Web Access developer trying to create an add-in to help mitigate the accidental release of Personally Identifiable Information (PII), you have probably been looking for a way to stop the send of the message if it does not pass a test. The functionality has not been a part of the new OfficeJS object model, until now. However, there are several things you need to know in order to set this up. Here are the general steps:

  • Use PowerShell to create a policy to enable to the OWAOnSendAddinAllUserPolicy
  • Use PowerShell to set that policy for a mailbox or group of mailboxes
  • Update your manifest file to enable the functionality and specify the event function you want to call when the user presses send
  • Write your JavaScript to properly handle the event.

NOTE: There are several caveats to this functionality. These are: you CANNOT publish an app using this functionality to the Store; this will not work on Shared mailboxes; and this will not work from any other clients, just Outlook Web Access (for now).

Create the OWAOnSendAddinAllUserPolicy

To set the policy for a user account, you need to create a policy with it enabled (or edit the default or specific policy you have applied). First, you need these steps to connect to the Exchange server as an administrator and establish a session:

Next, you need to then use PowerShell to set/configure the policy and then apply it to an account.

Once you have done, this you can then start writing the functionality into your code.

Update the Manifest file

The manifest will require a new and additional version overrides node. You will then use the FunctionFile to define an Extension point in your code. This will be a function you call. If you are not familiar with Function Files, here is a link to how to create one:

Here is what you manifest will need to look like:

[code lang=”xml” collapse=”true” title=”click to expand if the embedding below is not visible.”]
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides&quot; xsi:type="VersionOverridesV1_0">
<!– On Send requires VersionOverridesV1_1 –>
<!– https://dev.office.com/docs/add-ins/outlook/outlook-on-send-addins –>
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1&quot; xsi:type="VersionOverridesV1_1">
<Requirements>
<bt:Sets DefaultMinVersion="1.5">
<bt:Set Name="Mailbox" />
</bt:Sets>
</Requirements>
<Hosts>
<Host xsi:type="MailHost">
<DesktopFormFactor>
<!– Location of the Functions that UI-less buttons can trigger (ExecuteFunction Actions). –>
<FunctionFile resid="functionFile" />
<ExtensionPoint xsi:type="Events">
<Event Type="ItemSend" FunctionExecution="synchronous" FunctionName="onSendEvent" />
</ExtensionPoint>

</DesktopFormFactor>
</Host>
</Hosts>

</VersionOverrides>
</VersionOverrides>
[/code]

<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides&quot; xsi:type="VersionOverridesV1_0">
<!-- On Send requires VersionOverridesV1_1 -->
<!-- https://dev.office.com/docs/add-ins/outlook/outlook-on-send-addins -->
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1&quot; xsi:type="VersionOverridesV1_1">
<Requirements>
<bt:Sets DefaultMinVersion="1.5">
<bt:Set Name="Mailbox" />
</bt:Sets>
</Requirements>
<Hosts>
<Host xsi:type="MailHost">
<DesktopFormFactor>
<!-- Location of the Functions that UI-less buttons can trigger (ExecuteFunction Actions). -->
<FunctionFile resid="functionFile" />
<ExtensionPoint xsi:type="Events">
<Event Type="ItemSend" FunctionExecution="synchronous" FunctionName="onSendEvent" />
</ExtensionPoint>
...
</DesktopFormFactor>
</Host>
</Hosts>
...
</VersionOverrides>
</VersionOverrides>
view raw OnSend.xml hosted with ❤ by GitHub

The important thing to note here is that your FunctionFile function for OnSend will need to be defined OUTSIDE the scope of the Office.initialize() function. It needs to be GLOBAL. So you simply place it in the root of your functionFile.js. However, you still need to define an Office.initialize() function in your FunctionFile and in there you probably will want to grab a reference to the mailbox, mailbox.item and the user maybe and place them in a global var as well. This way you will have access to those things in the Event method to be able to make call like body.getAsync() or subject.getAsync(), etc.

Here is how you define it:

[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
var sendEvent;
function onSendEvent(event) {
///
<summary>
/// OUTLOOK SEND EVENT
/// Entry point for Message onSend event
/// Per example: https://github.com/OfficeDev/Outlook-Add-in-On-Send/tree/master/outlook-add-in-on-send
/// Setup mailbox policy first:
/// 1) Get sessions and commands: https://technet.microsoft.com/en-us/library/jj984289(v=exchg.160).aspx
/// 2) Set policy and more info: https://dev.office.com/docs/add-ins/outlook/outlook-on-send-addins
/// </summary>

/// <param name="event" type="object">ItemSend event is automatically passed by on send code to the function
// specified in the manifest.</param>
sendEvent = event;

// do your work here… since it will likely be ASYNC, you will want to
// set the event handler to a global. When you are done, you will issue
// > sendEvent.completed({ allowEvent: true }); // to send
// > sendEvent.completed({ allowEvent: false }); // to block
doSomething.Async(values, asyncComplete);
}
[/code]

var sendEvent;
function onSendEvent(event) {
/// <summary>
/// OUTLOOK SEND EVENT
/// Entry point for Message onSend event
/// Per example: https://github.com/OfficeDev/Outlook-Add-in-On-Send/tree/master/outlook-add-in-on-send
/// Setup mailbox policy first:
/// 1) Get sessions and commands: https://technet.microsoft.com/en-us/library/jj984289(v=exchg.160).aspx
/// 2) Set policy and more info: https://dev.office.com/docs/add-ins/outlook/outlook-on-send-addins
/// </summary>
/// <param name="event" type="object">ItemSend event is automatically passed by on send code to the function
// specified in the manifest.</param>
sendEvent = event;
// do your work here... since it will likely be ASYNC, you will want to
// set the event handler to a global. When you are done, you will issue
// > sendEvent.completed({ allowEvent: true }); // to send
// > sendEvent.completed({ allowEvent: false }); // to block
doSomething.Async(values, asyncComplete);
}
view raw OnSend.js hosted with ❤ by GitHub

Then later in your code, you can either Cancel the send or allow the send event to occur, like this:

[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
function asyncCompelte() {
// procress the result
if(isOkToSend() == true) {
sendEvent.completed({ allowEvent: true }); // send it
} else {
sendEvent.completed({ allowEvent: false }); // block it
}
}
[/code]

function asyncCompelte() {
// procress the result
if(isOkToSend() == true) {
sendEvent.completed({ allowEvent: true }); // send it
} else {
sendEvent.completed({ allowEvent: false }); // block it
}
}

Once you have set the policy on the mailbox, modified your manifest and then added the event function to your FunctionFile, you are good to go. I will be working on a simple demo to place on GitHub in coming weeks and will post an update when complete.

UPDATE: Here it my post with the demo:
Outlook OnSend and Dialog Sample

But I wanted to get this information out here for now. In the meantime, you can use this demo as an example of how to implement this end-to-end:

Docs.com is Retiring

I went all out using Docs.com for my code starting last year. But, I just got notified that it is being retired this December. wlEmoticon-cryingface.png

However, effectively immediately, all the code pages I was using now display an error like this all over my site:

docs.PNG

This is VERY frustrating I know. wlEmoticon-sadsmile.png

I have decided I will be converting all my code blocks over to Gist. Unless anyone has a better idea for a WordPress site. P.S. I really do NOT want to use add-on’s either. Anyway, this may take me a while as I have a LOT of code content out there on Docs.com. So, please bear with me.

Also, please note, I (kind of) anticipated this. In all my code pages there is a link above every Docs.com embedding you can click to expand and to see the full code on my site:

link.PNG

Using Outlook.Items.Restrict()

Recently, I was working on an Outlook Visual Studio Tools for Office (VSTO) solution and encountered a problem with unexpected results with the Restrict() method. What I was doing was trying to locate all the appointments (meeting and recurrence) that occurred today. The Restrict command was returning items where the Start and End date properties that were from 2000, 2001, 2010, etc. I quickly deduced that it was also returning recurring items that were dated back to the first occurrence. Here is what my code looked like:

[code lang=”csharp” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
///
<summary>
/// EXTENSION METHOD
/// Get all the appointments between the given date times
/// </summary>

/// <param name="PobjItems"></param>
/// <param name="PobjStart"></param>
/// <param name="PobjEnd"></param>
/// <returns></returns>
public static List<Outlook.AppointmentItem> FindAppointments(this Outlook.Items PobjItems,
DateTime PobjStart,
DateTime PobjEnd)
{
try
{
List<Outlook.AppointmentItem> LobjReturn = new List<Outlook.AppointmentItem>();
string LstrCriteria = "[Start] >= \"" + PobjStart.ToString("g") + "\" AND [End] <= \"" + PobjEnd.ToString("g") + "\"";
// we want to find the oldest first. This way we do
// not get stuck on all day appointments that are conflicting
// with the current value.
PobjItems.Sort("[End]");
PobjItems.IncludeRecurrences = true;
PobjItems = PobjItems.Restrict(LstrCriteria);
object LobjItem = PobjItems.GetFirst();
// chcek the appointments or meetings against the
// the rules and set Skype status
Outlook.AppointmentItem LobjAppt = null;
do
{
if (LobjItem is Outlook.MeetingItem)
{
LobjAppt = ((Outlook.MeetingItem)LobjItem).GetAssociatedAppointment(false);
}
else
{
LobjAppt = (Outlook.AppointmentItem)LobjItem;
}
// as long as we are not null
if (LobjItem != null)
{
LobjReturn.Add(LobjAppt);
LobjItem = PobjItems.GetNext();
}
}
while (LobjItem != null);
// done
return LobjReturn;
}
catch (Exception PobjEx)
{
PobjEx.Log(false);
return null;
}
}
[/code]


/// <summary>
/// EXTENSION METHOD
/// Get all the appointments between the given date times
/// </summary>
/// <param name="PobjItems"></param>
/// <param name="PobjStart"></param>
/// <param name="PobjEnd"></param>
/// <returns></returns>
public static List<Outlook.AppointmentItem> FindAppointments(this Outlook.Items PobjItems,
DateTime PobjStart,
DateTime PobjEnd)
{
try
{
List<Outlook.AppointmentItem> LobjReturn = new List<Outlook.AppointmentItem>();
string LstrCriteria = "[Start] >= \"" + PobjStart.ToString("g") + "\" AND [End] <= \"" + PobjEnd.ToString("g") + "\"";
// we want to find the oldest first. This way we do
// not get stuck on all day appointments that are conflicting
// with the current value.
PobjItems.Sort("[End]");
PobjItems.IncludeRecurrences = true;
PobjItems = PobjItems.Restrict(LstrCriteria);
object LobjItem = PobjItems.GetFirst();
// chcek the appointments or meetings against the
// the rules and set Skype status
Outlook.AppointmentItem LobjAppt = null;
do
{
if (LobjItem is Outlook.MeetingItem)
{
LobjAppt = ((Outlook.MeetingItem)LobjItem).GetAssociatedAppointment(false);
}
else
{
LobjAppt = (Outlook.AppointmentItem)LobjItem;
}
// as long as we are not null
if (LobjItem != null)
{
LobjReturn.Add(LobjAppt);
LobjItem = PobjItems.GetNext();
}
}
while (LobjItem != null);
// done
return LobjReturn;
}
catch (Exception PobjEx)
{
PobjEx.Log(false);
return null;
}
}

view raw

broken.cs

hosted with ❤ by GitHub

What I created is an extension item off the Outlook.Items object that allows me to easily restrict them to appointments found between two given dates. This is actually a very useful extension method. If it worked the way I expected it to. What I found out is that in order to get recurring items that occur today which have todays date in the Start and End date properties, you MUST sort the results by the [Start] date. You cannot sort by the [End] date as I had done. Almost exactly the same, here is the fixed function:

[code lang=”csharp” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
///
<summary>
/// EXTENSION METHOD
/// Get all the appointments between the given date times
/// </summary>

/// <param name="PobjItems"></param>
/// <param name="PobjStart"></param>
/// <param name="PobjEnd"></param>
/// <returns></returns>
public static List<Outlook.AppointmentItem> FindAppointments(this Outlook.Items PobjItems,
DateTime PobjStart,
DateTime PobjEnd)
{
try
{
List<Outlook.AppointmentItem> LobjReturn = new List<Outlook.AppointmentItem>();
string LstrCriteria = "[Start] >= \"" + PobjStart.ToString("g") + "\" AND [End] <= \"" + PobjEnd.ToString("g") + "\"";
// FIX: we must use [Start] in the Sort to get the right recurrence
// for todays date…
PobjItems.Sort("[Start]");
PobjItems.IncludeRecurrences = true;
PobjItems = PobjItems.Restrict(LstrCriteria);
object LobjItem = PobjItems.GetFirst();
// chcek the appointments or meetings against the
// the rules and set Skype status
Outlook.AppointmentItem LobjAppt = null;
do
{
if (LobjItem is Outlook.MeetingItem)
{
LobjAppt = ((Outlook.MeetingItem)LobjItem).GetAssociatedAppointment(false);
}
else
{
LobjAppt = (Outlook.AppointmentItem)LobjItem;
}
// as long as we are not null
if (LobjItem != null)
{
LobjReturn.Add(LobjAppt);
LobjItem = PobjItems.GetNext();
}
}
while (LobjItem != null);
// done
return LobjReturn;
}
catch (Exception PobjEx)
{
PobjEx.Log(false);
return null;
}
}
[/code]


/// <summary>
/// EXTENSION METHOD
/// Get all the appointments between the given date times
/// </summary>
/// <param name="PobjItems"></param>
/// <param name="PobjStart"></param>
/// <param name="PobjEnd"></param>
/// <returns></returns>
public static List<Outlook.AppointmentItem> FindAppointments(this Outlook.Items PobjItems,
DateTime PobjStart,
DateTime PobjEnd)
{
try
{
List<Outlook.AppointmentItem> LobjReturn = new List<Outlook.AppointmentItem>();
string LstrCriteria = "[Start] >= \"" + PobjStart.ToString("g") + "\" AND [End] <= \"" + PobjEnd.ToString("g") + "\"";
// FIX: we must use [Start] in the Sort to get the right recurrence
// for todays date…
PobjItems.Sort("[Start]");
PobjItems.IncludeRecurrences = true;
PobjItems = PobjItems.Restrict(LstrCriteria);
object LobjItem = PobjItems.GetFirst();
// chcek the appointments or meetings against the
// the rules and set Skype status
Outlook.AppointmentItem LobjAppt = null;
do
{
if (LobjItem is Outlook.MeetingItem)
{
LobjAppt = ((Outlook.MeetingItem)LobjItem).GetAssociatedAppointment(false);
}
else
{
LobjAppt = (Outlook.AppointmentItem)LobjItem;
Debug.Print(" >>>>>>>>>>> " + LobjAppt.Subject);
}
// as long as we are not null
if (LobjItem != null)
{
LobjReturn.Add(LobjAppt);
LobjItem = PobjItems.GetNext();
}
}
while (LobjItem != null);
// done
return LobjReturn;
}
catch (Exception PobjEx)
{
PobjEx.Log(false);
return null;
}
}

OfficeJS: Create a (VERY) Basic Add-in for Excel

Recently, I was asked what is needed in order to create the most basic add-in you can. The least amount of files, work and effort. Technically, you only need two files:

  1. An HTML file with all your JavaScript inline and all the Office and JQuery libraries being pulled from a CDN.
  2. An XML Manifest file that defines the add-in.

You publish the HTML file to a web server, get the address to where it is published, update the manifest with that information and then install the XML file as an add-in. The XML file will define the add-in name/description and points to the HTML file for the code. When you launch the add-in, the task pane will open and the HTML page will be loaded in it. From there your JavaScript will execute and your add-in will come alive. It is THAT easy. 2 files.

To demonstrate this, I created a simple Excel add-in. Here is the code for the bare minimum manifest XML:

[code lang=”xml” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
<?xml version="1.0" encoding="UTF-8"?>
<OfficeApp
xmlns="http://schemas.microsoft.com/office/appforoffice/1.1&quot;
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot;
xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0&quot;
xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides&quot;
xsi:type="TaskPaneApp">
<Id>5226365f-62d0-435f-a4af-cc6a7bd753b2</Id>
<Version>1.0.0.1</Version>
<ProviderName>TheOfficeContext.com</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="BasicAddin" />
<Description DefaultValue="This is a basic addin."/>
<IconUrl DefaultValue="" />
<Hosts>
<Host Name="Workbook" />
</Hosts>
<DefaultSettings>
<SourceLocation DefaultValue="https://basicaddin.azurewebsites.net/home.html&quot; />
</DefaultSettings>
<Permissions>ReadWriteDocument</Permissions>
</OfficeApp>
[/code]


<?xml version="1.0" encoding="UTF-8"?>
<OfficeApp
xmlns="http://schemas.microsoft.com/office/appforoffice/1.1&quot;
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot;
xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0&quot;
xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides&quot;
xsi:type="TaskPaneApp">
<Id>5226365f-62d0-435f-a4af-cc6a7bd753b2</Id>
<Version>1.0.0.1</Version>
<ProviderName>TheOfficeContext.com</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="BasicAddin" />
<Description DefaultValue="This is a basic addin."/>
<IconUrl DefaultValue="" />
<Hosts>
<Host Name="Workbook" />
</Hosts>
<DefaultSettings>
<SourceLocation DefaultValue="https://basicaddin.azurewebsites.net/home.html&quot; />
</DefaultSettings>
<Permissions>ReadWriteDocument</Permissions>
</OfficeApp>

view raw

BasicAddin.xml

hosted with ❤ by GitHub

Here is the HTML file that is quite basic, I am not even using the Microsoft Fabric for the controls. This is a simple HTML page, period:

[code lang=”xml” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<title>Excel Basic Add-In</title>
<!–using JQuery CDN so we do not need to include–>
<script src="https://code.jquery.com/jquery-1.12.4.min.js&quot; type="text/javascript"></script>
&nbsp;&nbsp;&nbsp; <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js&quot; type="text/javascript"></script>
<script type="text/javascript">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (function () {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "use strict";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // The initialize function must be run each time a new page is loaded.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Office.initialize = function (reason) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(document).ready(function () {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Add a click event handler for the button.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(‘#simple-button’).click(function () {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; function (result) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (result.status === Office.AsyncResultStatus.Succeeded) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $("#banner-text").text(‘The selected text is: "’ + result.value + ‘"’);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $("#banner").show();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $("#banner-text").text(‘Error: ‘ + result.error.message);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $("#banner").show();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $("#banner-close").click(function () { $("#banner").hide(); });
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $("#banner").hide();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; })();
&nbsp;&nbsp;&nbsp; </script>
</head>
<body>
<div id="content-main">
<div class="padding">
<h1>Basic Addin</h1>
<div>Select a cell with text and then…</div>
<button id="simple-button">Click Here!</button></div>
</div>
<div id="banner" style="position:absolute;bottom: 0;">
<div id="banner-text"></div>
<button id="banner-close"> <i>X</i> </button></div>
</body>
</html>
[/code]


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<title>Excel Basic Add-In</title>
<!–using JQuery CDN so we do not need to include–>
<script src="https://code.jquery.com/jquery-1.12.4.min.js&quot; type="text/javascript"></script>
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js&quot; type="text/javascript"></script>
<script type="text/javascript">
(function () {
"use strict";
// The initialize function must be run each time a new page is loaded.
Office.initialize = function (reason) {
$(document).ready(function () {
// Add a click event handler for the button.
$('#simple-button').click(function () {
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
$("#banner-text").text('The selected text is: "' + result.value + '"');
$("#banner").show();
} else {
$("#banner-text").text('Error: ' + result.error.message);
$("#banner").show();
}
});
});
$("#banner-close").click(function () { $("#banner").hide(); });
$("#banner").hide();
});
}
})();
</script>
</head>
<body>
<div id="content-main">
<div class="padding">
<h1>Basic Addin</h1>
<div>Select a cell with text and then…</div>
<button id="simple-button">Click Here!</button>
</div>
</div>
<div id="banner" style="position:absolute;bottom: 0;">
<div id="banner-text"></div>
<button id="banner-close"> <i>X</i> </button>
</div>
</body>
</html>

view raw

Home.html

hosted with ❤ by GitHub

What this add-in does is very simple. It opens the task pane with a single button. Enter some text in a cell in Excel, select that cell, and then click the button. It gets the text and displays it in a popup at the bottom of the pane. I have published this add-in manifest here:

https://basicaddin.azurewebsites.net/basicaddin.xml

You can run it by using these steps:

  1. Open Excel Online. I first log into https://outlook.office365.com. Then I click on the menu button in the upper left and select Excel.
  2. I select to create a “New blank workbook.”
  3. On the Insert tab, I click Add-ins.
  4. In the upper right, I select “Upload My Add-in”
  5. I click Browse and in the Filename box, I put https://basicaddin.azurewebsites.net/basicaddin.xml and then click Open. This will download the add-in to the cache on your system and you will get a file name like “basicaddin[1].xml.”
  6. Click Upload.
  7. The add-in will load from my Azure site and you will see the button demonstrated above.

Now, to go ahead and answer a question I know are coming. How did I upload this Azure without Visual Studio publishing tools:

  1. I went to my Azure Protal (portal.azure.com).
  2. I clicked the +New button on the left side.
  3. I selected Web + Mobile, then Web App
  4. I filled in the required information: Name and resource group and clicked Create.
  5. After it was done, I followed these steps to get to the site via FTP: https://blogs.msdn.microsoft.com/kaushal/2014/08/01/microsoft-azure-web-site-connect-to-your-site-via-ftp-and-uploaddownload-files/
  6. I uploaded ONLY the HTML file.
  7. I  got the path to the location from the Azure portal:
    capture
  8. Next, I updated the XML manifest to point to https://basicaddin.azurewebsites.net/home.html
  9. Then I used the steps above to load it into Excel Online.

This is fairly simple from a web developer perspective. Now, if you are not a web developer (but a traditional Office developer), you are probably still on the fence on how/why you would want to go through all of this. Again, the primary thing I am demonstrating here is how you can build an add-in with as few files as possible. But, I want to reiterate how these new add-ins are truly cross-platform. Follow these steps on your iPad and you will know what I mean. It is incredible and this work really is not that difficult to perform. It might just be a tad time consuming in the beginning as you are getting used to the new interfaces.

If you have questions, please feel free to reach out to me.

OneNote: Do Your Tabs Disappear?

Recently, a customer I work with asked me to investigate why the tabs in their shared OneNote notebooks were disappearing. It is especially prevalent when they moved to OneNote 2016. The cause as it turned out was something called Windows Deduplication. At its simplest, it is a way to reduduplicate files and similar streams of data on a disk. It is a form of compression at its simplest which is to say it is a mechanism to allow you to stick more stuff on a computer or server disk. What we found was the deduplication was corrupting the OneNote index (toc) on the server and each time someone went to make changes to the OneNote file on the server. Tabs would start disappearing for other users when multiple users were editing the files. When we enabled the exception for OneNote files in the data deduplication feature and then moved the OneNote files from one folder to another and then back (which is required to de-optimize the files), the problem went away. To resolve this problem you will likely need your administrator involved:

  1. You will need to set an exception for the OneNote file extensions in Windows Server 2012 Data Deduplication.
    • Here are some details on configuring that with PowerShell. See the “Modifying Data Deduplication volume-wide settings” section.
    • You will need to exclude these extensions: *.one, *.onebin, *.onetoc2
  2. Next, have everyone close OneNote on their systems.
  3. Finally, you will need to “deoptimize” (or, and I love this, de-dedup) the files. To do this, you will create a folder somewhere in the shared folder, move all the files in the OneNote folder to that location, then move them back.

NOTE: Nothing needs to be done on the clients, they will just work the next time the users launch OneNote. You might notice the tabs will still initially appear to be missing, but they will re-index and come back. This might take a few minutes depending on how many tabs there are.

Once completed, you should find that your tabs are no longer disappearing.

Office.js: Jump on!

I am asked the following questions all the time by customers interested in making the jump to Office.js. Therefore, I am providing this FAQ as a primer which usually helps open the door to deeper discussions:

  • What is Office.js exactly?

Office.js is a new way to develop customizations (or add-ins) for Office that are cross-platform. It is developed in JavaScript, runs from a webserver and loads into a content pane or task pane in the document or application.

  • Cross-platform?

Yes. You can currently install or use an Office application (Word, Excel, PowerPoint and Outlook), on any of these platforms: Windows, Mac, iOS, Android (soon) or Web. There are some restrictions on what is supported and how much of the toolset is available per platform. See this article: Office Add-in host and platform availability

  • Is Visual Basic for Macros (VBA), Visual Studio Tools for Office (VSTO), COM Add-ins or Macros going away then?

No. Office.js is just another development platform for Office. There are no plans to deprecate any of the other programmability options for Office. Office.js was designed with the Cloud First, Mobile first vision at Microsoft and expands customizations across multiple platforms. It can be thought of as add-ins that you write once, run anywhere.

  • I have an existing COM add-in. Do I need convert it to Office.js?

No. Unless of course you want your add-in to work across multiple platforms. However, it is important to consider what your add-in is doing. The current application programming interfaces (APIs) for Office.js are more limited. While they are increasing in capability every day, they are still not on parity with COM, VBA or VSTO.

  • Can I convert my add-in in Visual Studio?

No. This is because the new Office.js developer platform is completely new. There are no conversion tools. If you want to make the move to Office.js, you will likely need to start from scratch. However, if you have properly separated your backend logic from application interfaces, you might be able to reuse some of your existing code as a Web Service.

  • How do determine if my existing COM add-in can be rewritten in Office.js?

This is harder to answer. The short answer is you try to create it in the new platform and see how much you can get to function. Longer answer, there are a couple of approaches:

  • You rewrite the entire thing in the new platform and where some things do not translate or work the same, you change the requirements to meet the new platform restrictions, such as changing the workflow, number of features or expected results.
  • You write as much as you can in the new platform and keep building on it as the platform continues to take shape. Whatever you cannot accomplish in Office.js you write as a VSTO add-in.
  • You provide a more limited Office.js version for the non-Windows based platforms you support but still use your COM or VSTO add-in on Windows.
  • How do I develop an Office.js add-in?

There are a number of editors you can use. These are web based so technically you can use any platform you want. I suggest using Visual Studio where you have templates the provide you with predefined templates for Word, Excel, PowerPoint and Outlook Web Add-ins. Also, the Publishing process is much simplified in my opinion with the Visual Studio tools.

  • How do Office Web Add-ins work?

Office Web Add-ins are web pages. You publish the web pages to a web server and the entire solution will load and run from there. As part of developing the add-in, you will create an XML file called a manifest. The Manifest in simplest terms will define the name of your add-in, a unique ID for your add-in, its version, a description, any ribbon commands and the URL to the website. In Office you can then add the manifest and this will register the add-in in your Office application and load the ribbon buttons defined. When the user clicks the ribbons the manifest will tell Office what URL to load and then the add-in will appear in a content pane or task pane.

  • What if I have more questions?

Please ask in the comments section and I will work to continue to expand this FAQ.

Office.js: A Face palm moment with Outlook Client in Compose Mode

I have been working with Outlook Web Add-ins  and Compose Message integration for Office Outlook Web Access for a while now. That is, Outlook add-ins that present themselves when the user is composing an email. This scenario is not seemingly as common. But one of my customers has been completely gung-ho about getting all of their COM add-ins converted to the new Office.js model. Some of their solutions will be able to make it there, and some will not. We are finding some roadblocks with mail compose apps.

One area that is currently failing is setting X-headers on an email item. I developed a JavaScript library to assist with this: easyEws. I blogged about it here. The one specific EWS SOAP call my customer wants to use from that library is this one:

updateEwsHeader: Updated the named x-header in the message.

This function works great in two conditions:

  • Outlook Web Access (or Office 365 Outlook Online), and
  • Outlook client – Online mode only

If you use Outlook client (Outlook 2016) in cached mode, this call will seem to work (because it does), but the x-headers will be missing when the user presses “Send.” The problem is that while the currently composed item is saved to the drafts folder and the custom x-headers are written to it via the EWS call (server side), the current Outlook Inspector/session is NOT updated. The cached Drafts folder may take (depending on network latency) anywhere from 1 to 3 minutes to be updated, but regardless the item in the current inspector session will NOT ever be updated. So when the user presses SEND, the item in the drafts folder will be overwritten with one that does not contain the custom x-headers and then sent.

There is no way around this limitation. And I have confirmed this with the Exchange and Office.js teams that this is a limitation of cached mode in Outlook. If you have a need for custom x-headers with an Outlook add-in your only option for now is to create (or keep your existing) COM/VSTO add-in.

If you are running into this limitation, please let me know by commenting below.