I have been working with a number of customers over the years that come from the world of Lotus Notes. And one of the areas they often complain about with regards to Outlook is the calendar printing options. There are certain things you just cannot do in Outlook from the printing perspective that leaves them wont for more.
So, I have been working over the years on this add-in. This has actually gone through a few iterations – the current version 1.2.0.9 is the most recent and most fully-featured version.
The full source code is on GitHub, here. It is totally open source and free to use, modify, etc. Here is what it can do:
Printing calendars not available in Outlook by default.
The ability to create your own custom calendar
The ability to combine calendars for multiple people at once:
Displaying only overlapping schedules on the same calendar
Displaying all meetings including overlapping meeting
The ability to export in daily, weekly, by-weekly, tri-weekly or monthly formats.
The exact details on customization, installation and usage are all covered in the user guide, here.
A customer I was working with wanted help developing a fully on-premises Outlook web add-in. By this, they wanted no part of it to reach out to the Internet (Azure or Office 365). They wanted:
To connect to their internal Exchange server
An internal IIS website
And no references to the Internet (including the Office.js).
This is the topology we are trying to achieve:
If you have developed an Office Web Add-in lately, you find it is inherently biased to the Internet. Even the samples and solutions provided assume Office 365/Exchange and Azure websites. In a default, new Visual Studio solution, the links to the Office.js libraries and stylesheets are all pointing to the web. And, so as you might expect, there are some challenges to getting it to work on-premises only.
This posting covers what you must do to get such a solution to work, including getting past some pitfalls.
First, you have to download the Office.js files locally. And especially for Outlook because the Office.js files that are provided by default in your solution folder (“for offline debugging” as part of VS2015U3 or earlier) are missing some features to work with specific builds of Outlook 2013 and Outlook 2016. You will run into some strange “type” missing and “Office not defined” errors if you forget this step.
Once you have downloaded the Office.JS files, you will delete all the files under the Scripts\Office\1 folder and copy in the contents you downloaded in step 1.
Comment out that line and add the following two lines:
[code language=”html”]
<!– <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript></script> –>
<script src="../../Scripts/Office/MicrosoftAjax.js" type="text/javascript"></script>;
<script src="../../Scripts/Office/1.1/office.js" type="text/javascript"></script>;
[/code]
Once you have developed your solution, you must setup your IIS server. In general here is what you must do:
The IIS Server must have ASP.NET installed, it must have .NET4 installed and you must have the Web Application role enabled.
Open IIS Manager
Create a site, and figure the folder path
Convert the site to an Application
Apply an SSL certificate that is already trusted on all your client computers or that has a root certificate authority that is trusted on all your client computers. If you browse to your site using HTTPS and you get a RED warning about an untrusted site, then the certificate is not trusted or properly setup.
Next, and this was a major issue to troubleshoot, your Exchange Web Services certificate cannot be expired. If it is, any EWS call you make will return “succeeded” but will be blank – missing data. Digging into the logs you might find an error: “ErrorInvalidClientAccessTokenRequest” The Microsoft Exchange Server Auth Certificate that is used for OAuth needs to be updated. To do this you have to be logged into the Exchange server as administrator:
Run this cmdlet to identify the thumbprint of the certificate being used for OAUTH:Get-AuthConfig | FT currentcertificate*
Run this cmdlet to identify the thumbprint of certificates used for other Exchange servers (IIS, SMTP, etc.):Get-ExchangeCertificate | Fl *thumb*
Run these cmdlets to configure Exchange to use the valid certificate (copy/paste the thumbprint from the Get-ExchangeCertificate output):$today = Get-Date
Set-AuthConfig -NewCertificateThumbprint newthumbprint -NewCertificateEffectiveDate $today -Force
Run this cmdlet to make sure the changes are published to the environmentSet-AuthConfig -PublishCertificate
Run this cmdlet to verify the certificate thumbprintGet-AuthConfig | FT currentcertificate*,previouscert*
Now you can deploy your solution to IIS, update your manifest to point to the IIS server (do not forget the HTTPS) and then install it on the Exchange Server Control Panel (ECP) under the Organization / Add-ins option as a mandatory add-in.
Finally, and this last bit is important: IF USING OUTLOOK 2013 or OUTLOOK 2016, YOU MUST BE LOGGED INTO WINDOWS ON THE SAME DOMAIN AS YOUR EMAIL. I know, I know… for some folks this sucks. I have reported this to our product team and they are looking into it. If you are not logged into the same domain controller as your email address is registered, you will not see the advertised add-in. It will load in Outlook Web Access (OWA), but will not appear in Outlook 2013/2016. The exact cause of this problem is unknown, but hopefully it will be addressed in a future version of the product (Exchange or Outlook or both).
Setting up for 100% on-premises is difficult, but it CAN be done. There are a lot of steps, but if you follow the above prescription, you should get it to work. In time, I hope to see this process get easier. But in an online world where Microsoft Office 365 and Azure are main focus, “old fashioned” on-premises solutions are going to require a little more elbow grease.
NOTE: This entry was contributed to by Arthel Bibbens (MSFT) / Exchange PFE. You can follow his posts on this topic here:
I recently worked with a developer to deploy an Office add-in within an Exchange 2013 on-premises environment. This project highlighted a capability of Exchange and Outlook that is a huge shift in the way mail add-ins are developed, deployed, and maintained. Let’s take a look at the key components of Exchange 2013 that support this…
It is sometimes tough to determine what is happening in a production environment and you need to get logging information from the add-in to see what is happening. How can you do that?
One way is to build a console.log() option into your add-in that looks for a Debug flag in the manifest. So, you will create two manifest, one that enabled Debugging and another than disables it. More on that in a bit. To start, here is the basic class I created in order to handle this:
[code lang=”javascript” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
/*!
* logger JavaScript Library v1.0.1
* http://davecra.com
*
* Copyright David E. Craig and other contributors
* Released under the MIT license
* https://tldrlegal.com/license/mit-license
*
* Date: 2016-08-09T12:00EST
*/
var console = (function () {
"use strict";
var console = {};
console.initialize = function () {
///
<summary>
/// Add a textarea/console to the bottom of the page and then setup the logger
/// </summary>
/// <param name="DebugMode" type="Boolean">If debug mode enabled – we show the console for logging</param>
var debugMode = getParameterByName("Debug") == "true";
if (debugMode) {
// add the console to the screen
$("body").append("<textarea id=’log’ style=’width:100%’ cols=’2000′ rows=’7′ wrap=’off’></textarea>");
$("body").append("<button id=’saveLog’>Copy Log to Clipboard</button>");
$("#saveLog").click(function () {
var field = $("#log");
field.select();
document.execCommand("copy");
});
}
console.log = function (msg) {
///
<summary>
/// GLOBAL: Logs to the textarea on the page
/// </summary>
/// <param name="msg" type="string">The message to log</param>
if (debugMode) {
var d = new Date(Date.now());
var current = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
var data = $("#log").val();
$("#log").val(current + " – " + msg + "\r\n" + data);
}
};
}
function getParameterByName(name) {
///
<summary>
/// Get a parameter form the URL
/// </summary>
/// <param name="name" type="String">Name of the parameter to get from the query string</param>
/// <returns type="String">Value of the paramater</returns>
var url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return ”;
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
return console;
})();
[/code]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
To enable logging you will add the following code to your initialize:
[code language=”javascript”]
console.initialize();
console.log("Started…");
[/code]
Once initialized this will add a TEXTAREA to the bottom on the page where the log entries will be loaded. Additionally, it will place a “Copy To Clipboard” button at the bottom that when clicked will copy the contents of the TEXTAREA to the clipboard so that they can be forwarded to you as needed.
Once implemented and initialized, you can add a console.log() anywhere you want in your code to add an entry to the log. Now, how do you turn this on. What this is doing in initialize is to see if the debug flag is set in the Query String of the SourceLocation setting in the Manifest. To turn on debugging, you change the following line as such:
That is it. From this you will be able to share two manifests with your users/administrators. The first one will be your default production manifest and the second one can be loaded if you need debugging information from the add-in.
Recently while preparing an internal Chalk Talk on Office Web Add-in Development, a co-worker presented me with two links I had not seen before and I wanted to share them with everyone:
The EWSEditor tool has been around a while and is managed/developed by some of my co-workers that sit on the Outlook Developer Support Team. As I have been developing more and more Office Web Add-ins for Outlook, I have found knowing and using EWS to be a very important skill.
EWSEditor helps in this regard. It is a very powerful, full featured EWS test bed. To get started you download this and extract the contents of the ZIP to a folder. From there you launch it and from the File menu, click Next Exchange Service enter in your email address and then select password and click Ok. Then viola, you are connected:
Once connected, you can start browsing your mailbox using EWS. To get to your Inbox, for example, you select TopOfInformationStore and then select Inbox:
From there you can go to different folders and look at the items, properties, and values stored in your mailbox. It is quite handy to understand how these things are structured and stored.
Next, you can click Tools, EWS Post and test your EWS skills. What I did was entered in my information to connect to my server and filled it in as such:
And when I hit run, I got a response you see above. It is that simple. And there are also lots of examples as well. If you click Load Example you will see a lot of XML SOAP requests you can test with:
First, let’s discuss the architecture and from an Outlook/Exchange perspective:
First, you load your manifest on the Exchange server. The manifest is simply an XML file that contains pointers to your web site (on the IIS server).
When you load Outlook Web Access and click the add-ins button, the Add-ins pane will appear and each application manifest you have loaded will appear in the list.
When you click on one of the add-is, the task pane will load (in Orange) and your site (located on the IIS Server) will populate in the pane.
The problem arises when you have but ONE Exchange server or ONE developer account for development and test. Developers want to be able to debug the code they are working with which typically loads from the local instance of IIS Express (localhost). But testers need to be able to work with the latest release build to test.
So, how do you do this?
The key is in the Manifest file. If you open the Manifest file, you will see the <Id> field. This is the most important piece, but there are other areas you should/need to update as well. What you will essentially have is two copies of your manifest file:
The first file will use the default ID provided in the project, it should have a name like Developer Release and it will point to your localhost (this is default with the setup of a new Web Add-in).
The real key is having different ID’s and publishing them separately. In Visual Studio the developers will have everything set as shown above (essentially leave everything as default). When they are ready to drop a build to the testers:
Right-click on the Website project and click Publish. Follow the steps to publish the site to the Testers server.
Right-click on the Manifest project and click Publish. Click the “Package the add-in” button.
In the resulting dialog, enter the URL to the Testers site, This will ONLY update the URL, but not change the ID.
Open the <manifest>.xml file in Notepad and then change the fields as shown above:
Modify the ID
Change the name so that you can identify which one is which
Verify the URL is correct.
At this point you are ready to go. And you can create MULTIPLE versions of your manifest. If for example you need one for Testers, one for Developer and then another for Pilot and yet another for experimental testing (each pointing to different IIS instances, sites or even to the Cloud (Azure). You can create as many manifests as you need this way, have them all show up in the Add-ins task pane allowing the testers/users to select the one they wish to work with.
A requirement in an Outlook Web Add-in I am working on required the ability to send an email to an alias with another email as an attachment. I found this a bit challenging as the makeEwsRequestAsync() function limits us to only a handful of EWS functions (EWS operations that add-ins support). This means I was unable to use a lot of the samples I found on the web. However, with the UpdateItem method, I found a way.
First, let me say, I have been asked – why is easyEWS.js not on GitHub. Well, now it is:
Next, so I updated the functionality in easeEws via two function:
easyEws.getMailItemMimeContent – we use this function to get the MIME content of a specified mail item by the ID.
easyEws.sendPlainTextEmailWithAttachment – this function, although a tad more complicated, will create a very simple plain text email and add an attachment send it and save it to the drafts folder.
Here are the two new updates:
[code lang=”javascript” collapse=”true” title=”click to expand if the docs.com embedding below is not visible.”]
// PUBLIC: creates a new emails message with a single attachment and sends it
// RETURNS: ‘success’ is compelted successfully
easyEws.sendPlainTextEmailWithAttachment = function (subject, body, to, attachmentName, attachmentMime, successCallback, errorCallback) {
var soap = ‘<m:CreateItem MessageDisposition="SendAndSaveCopy">’ +
‘ <m:Items>’ +
‘ <t:Message>’ +
‘ <t:Subject>’ + subject + ‘</t:Subject>’ +
‘ <t:Body BodyType="Text">’ + body + ‘</t:Body>’ +
‘ <t:Attachments>’ +
‘ <t:ItemAttachment>’ +
‘ <t:Name>’ + attachmentName + ‘</t:Name>’ +
‘ <t:IsInline>false</t:IsInline>’ +
‘ <t:Message>’ +
‘ <t:MimeContent CharacterSet="UTF-8">’ + attachmentMime + ‘</t:MimeContent>’ +
‘ </t:Message>’ +
‘ </t:ItemAttachment>’ +
‘ </t:Attachments>’ +
‘ <t:ToRecipients><t:Mailbox><t:EmailAddress>’ + to + ‘</t:EmailAddress></t:Mailbox></t:ToRecipients>’ +
‘ </t:Message>’ +
‘ </m:Items>’ +
‘</m:CreateItem>’;
soap = getSoapHeader(soap);
// make the EWS call
asyncEws(soap, function (xmlDoc) {
// Get the required response, and if it’s NoError then all has succeeded, so tell the user.
// Otherwise, tell them what the problem was. (E.G. Recipient email addresses might have been
// entered incorrectly — try it and see for yourself what happens!!)
var result = xmlDoc.getElementsByTagName("ResponseCode")[0].textContent;
if (result == "NoError") {
successCallback(result);
}
else {
successCallback("The following error code was recieved: " + result);
}
}, function (errorDetails) {
if (errorCallback != null)
errorCallback(errorDetails);
});
};
// PUBLIC: gets the mail item as raw MIME data
// RETURNS: the entire email message as a MIME Base64 string
easyEws.getMailItemMimeContent = function (mailItemId, successCallback, errorCallback) {
var soap =
‘<m:GetItem>’ +
‘ <m:ItemShape>’ +
‘ <t:BaseShape>IdOnly</t:BaseShape>’ +
‘ <t:IncludeMimeContent>true</t:IncludeMimeContent>’ +
‘ </m:ItemShape>’ +
‘ <m:ItemIds>’ +
‘ <t:ItemId Id="’ + mailItemId + ‘"/>’ +
‘ </m:ItemIds>’ +
‘</m:GetItem>’;
soap = getSoapHeader(soap);
// make the EWS call
asyncEws(soap, function (xmlDoc) {
//var content = xmlDoc.getElementsByTagName("MimeContent")[0].textContent;
successCallback(xmlDoc);
}, function (errorDetails) {
if (errorCallback != null)
errorCallback(errorDetails);
});
};
[/code]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Once added, you can use EasyEWS.js on your projects. In my particular project, I used the following code, which does the following:
Gets the current mail item (entirely) as a base64 encoded string. This is the full MIME representation of the message.
It then creates a new mail item and sends it with a comment from the user to the email address specified with an attachment (which is the base64 string we got in the first step).
This is all done with EWS and because of easyEWS.js, it is done in very few lines of code:
[code lang=”javascript” collapse=”true” title=”click to expand if the docs.com embedding below is not visible.”]
// Loads the form items to attach to events
function loadForm() {
$("#forward-button").click(function () {
getCurrentMessage();
});
}
// This function handles the click event of the sendNow button.
// It retrieves the current mail item, so that we can get its itemId property
// ans also get the MIME content
// It also retrieves the mailbox, so that we can make an EWS request
// to get more properties of the item.
function getCurrentMessage() {
var item = Office.context.mailbox.item;
itemId = item.itemId;
mailbox = Office.context.mailbox;
try{
easyEws.getMailItemMimeContent(itemId, sendMessageCallback, showErrorCallback);
} catch (error) {
showNotification("Unspecified error.", err.Message);
}
}
// This function is the callback for the getMailItemMimeContent method
// in the getCurrentMessage function.
// In brief, it first checks for an error repsonse, but if all is OK
// t:ItemId element.
// Recieves: mail message content as a Base64 MIME string
function sendMessageCallback(content) {
var toAddress = "bob@contoso.com";
var comment = $("#forward-comment").val();
if (comment == null || comment == ”) {
comment = "[user provided no comment]";
}
try{
easyEws.sendPlainTextEmailWithAttachment("Message with Item Attachment",
comment,
toAddress,
"Email Attachment",
content,
successCallback,
showErrorCallback);
}
catch (error) {
showNotification("Unspecified error.", err.Message);
}
}
// This function is the callback for the easyEws sendPlainTextEmailWithAttachment
// Recieves: a message that the result was successful.
function successCallback(result) {
showNotification("Success", result);
}
// This function will display errors that occur
// we use this as a callback for errors in easyEws
function showErrorCallback(error) {
showNotification("Error", error);// .error.message);
}
[/code]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
I recently watched a video by my colleague Michael Zlatkovsky in which he demonstrates the changes to the OfficeJS Libraries. And in this video he proposed a nifty little trick I have been using ever since to refresh the task pane app without having to stop and reload the solution (which saves minutes each time you need to stop debugging). What you do is place a refresh button at the bottom of your task pane HTML, like this:
I have found Web Service Controllers in Office Add-ins to be quite useful. There are a number of reasons you might want to keep functions of your add-in on a server, including obfuscation, complex calculation, data intense, service mashups, and much more. However, every time you want to use your Web Ser4vice from the JavaScript interface, there is a lot of code associated with the AJAX call you have to make. This begs for simplicity and that is what I have done. In my App.js project, I added a simple makeAjaxCall function that takes the command you want to invoke, the parameters you want to pass it (as an array) a callback when the call is complete and a callback if there was an error. Here is the core code for the makeAjaxCall() method:
[code lang=”javascript” collapse=”true” title=”click to expand if the docs.com embedding below is not visible.”]
// Helper function to call the web service controller
app.makeAjaxCall = function (command, params, callback, error) {
var dataToPassToService = {
Command: command,
Params: params
};
$.ajax({
url: ‘../../api/WebService’,
type: ‘POST’,
data: JSON.stringify(dataToPassToService),
contentType: ‘application/json;charset=utf-8’
}).done(function (data) {
callback(data);
}).fail(function (status) {
error(status);
})
};
[/code]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
[code lang=”javascript” collapse=”true” title=”click to expand if the docs.com embedding below is not visible.”]
/// <summary>
/// CORE SERVICE FUNCTION
/// This function will take a web request which will contain the command the caller wants
/// to initate and then the paramaters needed for that call. We enter a SELECT statement
/// below to determine the command given and we then call the proper helper function
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost()]
public WebServiceResponse WebService(WebServiceRequest request)
{
WebServiceResponse response = null;
switch(request.Command)
{
case "DoFunctionA":
return functionA(request.Params);
case "DoFunctionB":
return functionB(request.Params);
case "DoFunctionC":
return functionC(request.Params);
case "DoFunctionD":
return functionD(request.Params);
}
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
[code language=”javascript”]
app.makeAjaxCall("DoFunctionA", ["Value1", "Value2"], function (data) {
var result = $.parseJSON(data.Message);
// do something with the retuirned result here…
});
[/code]
For more information on how to create a Web Service controller, there is a great blog post from a colleague of mine, Michael Zlatkovsky on how to do this:
If you have done any work with Outlook Add-ins using the Office JavaScript API’s, you might have found a nifty function that allows you to poll the Exchange Server using EWS calls. The function: makeEwsRequestAsync(). However, this function is not the easiest thing to use. You have to formulate an EWS SOAP message that you send to the service. Getting those correct, writing the code for them, and processing the results are a real beast. But, even worse is finding exactly how to formulate the SOAP message from the existing documentation. It is something I personally did NOT look forward to as I was working on my customers solutions.
My frustration is your benefit (I hope). I created a JavaScript class called easyEws, that makes certain calls very easy. I posted the project on GitHub (previously, I had posted this on CodePlex):