Introduction
This article will describe the process of using gSOAP to connect to the Exchange Web Service. All functionalities in EWS are not defined in this article, but reading it should give you a solid basis of using C++ and gSOAP to access EWS operations.
gSOAP is a toolkit that simplifies development with SOAP/XML web services by creating SOAP/XML data bindings for C and C++. With gSOAP, calling SOAP web services is extremely easy because the toolkit auto generates C/C++ code to handle the SOAP messages that are sent and received. This eleminates the developer's need to write complicated code to parse large XML messages.
EWS is a web service that is used to access emails in a pre existing email account. It is a SOAP API, so it is the perfect example to show just how useful gSOAP can be.
The code attached to this article was written in equal parts by myself and Ning Xie.
Installation
gSOAP
Check this page to see how to download and install gSOAP on your system.
OpenSSL
You will need OpenSSL in order to connect gSOAP with EWS. You can see how to install it on their website.
Outlook email account
You will need an Outlook email account in order to test your EWS client. You can create an account on this page if you don't already have one.
Generating C++ Bindings from the EWS WSDL with gSOAP
Obtaining the .wsdl file
In order for the wsdl2h tool(described in the next step) to run, you need the wsdl file of the web service you wish to create a header file for. You will need to download three files: Services.wsdl
, messages.xsd
, and types.xsd
. In order to download these files, you need to to know your exchange server.
The format of your exchange server should look something like these examples: exchange.your_company.com, exchange.your_shcool.edu.
To download Services.wsdl
, messages.xsd
, and types.xsd
, enter these links into your web browser:
https:
https:
https:
When you visit these links, you will be prompted to enter a username and password. Use your Exchange email credentials, and you can view and download the files.
Make sure to save the files in the same directory. Also, you might need to modify Services.wsdl to make sure that the locations for messages.xsd
and types.xsd
are not attached to absolute paths (see below).
<wsdl:types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:import namespace="http://schemas.microsoft.com/exchange/services/2006/messages" schemaLocation="messages.xsd"/>
<xs:schema/>
<wsdl:types/>
and that schemaLocation="types.xsd" is used in message.xsd without path:
<xs:import namespace="http://schemas.microsoft.com/exchange/services/2006/types" schemaLocation="types.xsd"/>
Step 1
In order to generate the EWS header file, run the wsdl2h
tool with the EWS WSDL file as well as the desired title of you header file (we'll use service.h). You will need the file typemap.dat
in your working directory in order for the tool to work.
Note: Your typemap.dat file only needs the following two lines to work correctly with this example:
ewsmsg = "http://schemas.microsoft.com/exchange/services/2006/messages"
ewstype = "http://schemas.microsoft.com/exchange/services/2006/types"
wsdl2h -o service.h -u Services.wsdl
Step 2
Now we will use soapcpp2
to generate the data bindings that will connect our code to the service operations. Make sure that stdsoap2.h
and stdsoap2.cpp
are either in either your working directory or the path to the gsoap folder is included in the following command.
soapcpp2 -j -CL -I /path/to/gSOAP/import service.h
Option -j produces C++ proxy classes while -CL indicates that we will only create the client side. You will need the path to the gSOAP import file for the #import statements in service.h
. Several files will be generated.
Connecting gSOAP with EWS
The following examples will show you how to find an item in your email account and delete that specific item programatically. We will be using the generated proxy classes to do so.
Example 1: SendItem service operation(Sending an email from the Drafts folder)
Before we begin, open up your Outlook email account. Create an email and list yourself as the recipient. You may set the subject and message if you want to, but it is not necessary. Now, abandon this email so that it is saved within the Drafts folder. This is the email that we will send in the next step. In order to send the email, we will have to know its Id and Change Key. In order to get this information, we will run a service operation defined in the tar file attached to this article: testClient_FindItem.cpp
. This code will look in your Drafts folder and return all of the item Ids. To get your item's Id and Change Key, run the FindItem operation from the command line and pass in your username and password like this(I am using clang):
clang++ -Wno-undefined-bool-conversion -o fi -DWITH_OPENSSL testClient_FindItem.cpp soapC.cpp soapExchangeServiceBindingProxy.cpp /Path/to/stdsoap2.cpp -I. /path/to/gSOAP/gsoap/custom/duration.c -lssl -lcrypto
./fi emailusername@outlook.com email_password
Note: there is a more detailed explanation of compilation at the end of this step.
The Id and Change Key of your draft will be printed to your screen.
Note: When testing the EWS client I prefer to handle emails found in the Drafts folder because it generally holds much less content than the other folders. You can continue using whatever email location you prefer,but the code you will use in order to find the email's Id is set to look in the Drafts folder.
Now that we have a pre existing email to send, we can begin coding. The following code can be found in testClient_SendItem.cpp
in the tar file attached to this article.
We must include the namespace mapping file as well as the generated proxy header file so that gSOAP's generated code can help us access the service operations.
#include "ExchangeServiceBinding.nsmap"
#include "soapExchangeServiceBindingProxy.h"
#include "soapH.h"
#include <iostream>
#include <string>
Next, we will write our code so that we can pass in username and password parameters from the command line and handle the security measures of logging into your email accout. Notice this is also where the ewsBinding proxy variable is declared. This is what will allow us to call the SendItem function so that our draft is sent to the inbox!
static const char *userid;
static const char *passwd;
int main(int argc, char **argv)
{
if(argc>=3)
{
userid = argv[1];
passwd = argv[2];
}
ExchangeServiceBindingProxy ewsBinding("https://outlook.office365.com/EWS/Exchange.asmx");
soap_init1(ewsBinding.soap, SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_XML_INDENT | SOAP_XML_NOTYPE);
ewsBinding.soap->userid = userid;
ewsBinding.soap->passwd = passwd;
soap_ssl_init();
if(soap_ssl_client_context(ewsBinding.soap,SOAP_SSL_DEFAULT | SOAP_SSL_SKIP_HOST_CHECK,NULL,NULL,"cacerts.pem",NULL,NULL))
{
soap_print_fault(ewsBinding.soap, stderr);
exit(1);
}
_ewstype__RequestServerVersion serVer;
enum ewstype__ExchangeVersionType exchangeVer = ewstype__ExchangeVersionType__Exchange2010_USCORESP2;
serVer.Version = exchangeVer;
ewsBinding.soap_header(NULL, NULL, NULL, &serVer,NULL,NULL);
Now we will set all of the information needed for the request we send to the web service. Remeber to set itemtosend.Id
equal to the Id returned from FindItem and change
equal to the Change Key returned from FindItem.
ewstype__NonEmptyArrayOfBaseItemIdsType itemArray;
itemArray.__size_NonEmptyArrayOfBaseItemIdsType = 1;
__ewstype__union_NonEmptyArrayOfBaseItemIdsType member;
ewstype__ItemIdType itemtosend;
itemtosend.Id = "";
std::string change = "";
itemtosend.ChangeKey = &change;
member.ItemId = &itemtosend;
itemArray.__union_NonEmptyArrayOfBaseItemIdsType = &member;
ewsmsg__SendItemType request;
__ewsmsg__SendItemResponse response;
request.ItemIds = &itemArray;
request.SaveItemToFolder = true;
Now it is time to call the service and handle our results.
if(ewsBinding.SendItem(&request, response)==SOAP_OK)
{
ewsmsg__ResponseMessageType* iirmt
= response.ewsmsg__SendItemResponse->ResponseMessages->__union_ArrayOfResponseMessagesType->SendItemResponseMessage;
if(iirmt != NULL)
{
if(iirmt->ResponseClass == ewstype__ResponseClassType__Error)
std::cout << *iirmt->__ResponseMessageType_sequence->MessageText <<std::endl;
else
std::cout << "Message Sent!";
}
}
else
ewsBinding.soap_stream_fault(std::cerr);
ewsBinding.destroy();
}
This will send the SOAP request in the variable request
and send the SOAP response message back in response
. Calling ewsBinding.destroy()
deletes all of the managed data.
Here is testClient_SendItem.cpp in its entirety:
#include "ExchangeServiceBinding.nsmap"
#include "soapExchangeServiceBindingProxy.h"
#include "soapH.h"
#include <iostream>
#include <string>
static const char *userid;
static const char *passwd;
int main(int argc, char **argv)
{
if(argc>=3)
{
userid = argv[1];
passwd = argv[2];
}
ExchangeServiceBindingProxy ewsBinding("https://outlook.office365.com/EWS/Exchange.asmx");
soap_init1(ewsBinding.soap, SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_XML_INDENT | SOAP_XML_NOTYPE);
ewsBinding.soap->userid = userid;
ewsBinding.soap->passwd = passwd;
soap_ssl_init();
if(soap_ssl_client_context(ewsBinding.soap,SOAP_SSL_DEFAULT | SOAP_SSL_SKIP_HOST_CHECK,NULL,NULL,"cacerts.pem",NULL,NULL))
{
soap_print_fault(ewsBinding.soap, stderr);
exit(1);
}
_ewstype__RequestServerVersion serVer;
enum ewstype__ExchangeVersionType exchangeVer = ewstype__ExchangeVersionType__Exchange2010_USCORESP2;
serVer.Version = exchangeVer;
ewsBinding.soap_header(NULL, NULL, NULL, &serVer,NULL,NULL);
ewstype__NonEmptyArrayOfBaseItemIdsType itemArray;
itemArray.__size_NonEmptyArrayOfBaseItemIdsType = 1;
__ewstype__union_NonEmptyArrayOfBaseItemIdsType member;
ewstype__ItemIdType itemtosend;
itemtosend.Id = "AAMkAGQ0MDg4MjMwLTYyZjEtNDc1Zi1hNWQwLWY4ZGFiODIyY2ZiOABGAAAAAADQJ5K0osuKQatv50kfuGI4BwALbeOJ+btaT4I3Qf8SQ+rtAAAAAAEQAAALbeOJ+btaT4I3Qf8SQ+rtAAKA2ysiAAA=";
std::string change = "CQAAABYAAAALbeOJ+btaT4I3Qf8SQ+rtAAKBBRvI";
itemtosend.ChangeKey = &change;
member.ItemId = &itemtosend;
itemArray.__union_NonEmptyArrayOfBaseItemIdsType = &member;
ewsmsg__SendItemType request;
__ewsmsg__SendItemResponse response;
request.ItemIds = &itemArray;
request.SaveItemToFolder = true;
if(ewsBinding.SendItem(&request, response)==SOAP_OK)
{
ewsmsg__ResponseMessageType* iirmt
= response.ewsmsg__SendItemResponse->ResponseMessages->__union_ArrayOfResponseMessagesType->SendItemResponseMessage;
if(iirmt != NULL)
{
if(iirmt->ResponseClass == ewstype__ResponseClassType__Error)
std::cout << *iirmt->__ResponseMessageType_sequence->MessageText <<std::endl;
else
std::cout << "Message Sent!";
}
}
else
ewsBinding.soap_stream_fault(std::cerr);
ewsBinding.destroy();
}
Compiling the SendItem Example
We must compile with OPENSSL to ensure that our credentials are transmitted securely. This requires the OpenSSL and OpenSSL crypto libraries.
You will need to include the paths to the stdsoap2.cpp
and duration.c
files in the gSOAP library.
Similarly to the FindItem compilation, you will need to pass username and password arguments to the executable.
clang++ -Wno-undefined-bool-conversion -o si -DWITH_OPENSSL testClient_SendItem.cpp soapC.cpp soapExchangeServiceBindingProxy.cpp /Path/to/stdsoap2.cpp -I. /path/to/gSOAP/gsoap/custom/duration.c -lssl -lcrypto
./si emailusername@outlook.com email_password
When yout run the SendItem operation, you should recieve a new email in your inbox!
Example 2: DeleteItem service operation
As I mentioned before, I prefer to work from the Drafts folder for simplicity, so we will be deleting an email in the Drafts folder. If you wish to delete from the inbox or other locations, you can easily modify the code in testClient_FindItem.cpp
to search in other folders (set the variable dfit
to ewstype__DistinguishedFolderIdNameType__inbox
on line 80 to search in the inbox).
Like we did before, create a draft in your email account, and then run the FindItem service operation to get the item's Id. The Change Key is not needed for the DeleteItem service operation.
clang++ -Wno-undefined-bool-conversion -o fi -DWITH_OPENSSL testClient_FindItem.cpp soapC.cpp soapExchangeServiceBindingProxy.cpp /Path/to/stdsoap2.cpp -I. /path/to/gSOAP/gsoap/custom/duration.c -lssl -lcrypto
./fi emailusername@outlook.com email_password
Now that we have a pre existing email to send, we can begin coding. The following code can be found in testClient_DeleteItem.cpp
in the tar file attached to this article.
We must include the namespace mapping file as well as the generated proxy header file so that gSOAP's generated code can help us access the service operations.
#include "ExchangeServiceBinding.nsmap"
#include "soapExchangeServiceBindingProxy.h"
#include "soapH.h"
#include <iostream>
#include <string>
Now we set up the credentials so we can log into the email account as well as create the proxy we will use to call the web service and receive the results.
static const char *userid;
static const char *passwd;
int main(int argc, char **argv)
{
if(argc>=3)
{
userid = argv[1];
passwd = argv[2];
}
ExchangeServiceBindingProxy ewsBinding("https://outlook.office365.com/EWS/Exchange.asmx");
ewsmsg__DeleteItemType request;
__ewsmsg__DeleteItemResponse response;
soap_init1(ewsBinding.soap, SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_XML_INDENT | SOAP_XML_NOTYPE);
ewsBinding.soap->userid = userid;
ewsBinding.soap->passwd = passwd;
soap_ssl_init();
if(soap_ssl_client_context(ewsBinding.soap,SOAP_SSL_DEFAULT | SOAP_SSL_SKIP_HOST_CHECK,NULL,NULL,"cacerts.pem",NULL,NULL))
{
soap_print_fault(ewsBinding.soap, stderr);
exit(1);
}
_ewstype__RequestServerVersion serVer;
enum ewstype__ExchangeVersionType exchangeVer = ewstype__ExchangeVersionType__Exchange2010_USCORESP2;
serVer.Version = exchangeVer;
ewsBinding.soap_header(NULL, NULL, NULL, &serVer,NULL,NULL);
It is now time to set all of the attributes the DeleteItem service needs to do its job. Don't forget to set item.Id
to the item Id that the FindItem service returned.
ewstype__ItemIdType item;
item.Id = "";
__ewstype__union_NonEmptyArrayOfBaseItemIdsType member;
member.ItemId = &item;
ewstype__NonEmptyArrayOfBaseItemIdsType itemArray;
itemArray.__size_NonEmptyArrayOfBaseItemIdsType = 1;
itemArray.__union_NonEmptyArrayOfBaseItemIdsType = &member;
request.ItemIds = &itemArray;
request.DeleteType = ewstype__DisposalType__SoftDelete;
ewstype__AffectedTaskOccurrencesType ATOType = ewstype__AffectedTaskOccurrencesType__SpecifiedOccurrenceOnly;
request.AffectedTaskOccurrences = &ATOType;
ewstype__CalendarItemCreateOrDeleteOperationType CalType = ewstype__CalendarItemCreateOrDeleteOperationType__SendOnlyToAll;
request.SendMeetingCancellations = &CalType;
Now all that's left to do is call the web service!
if(ewsBinding.DeleteItem(&request, response)==SOAP_OK)
{
ewsmsg__ResponseMessageType *iirmt
= response.ewsmsg__DeleteItemResponse->ResponseMessages->__union_ArrayOfResponseMessagesType->DeleteItemResponseMessage;
if(iirmt != NULL)
{
if (iirmt->ResponseClass == ewstype__ResponseClassType__Error)
std::cout << *iirmt->__ResponseMessageType_sequence->MessageText <<std::endl;
else
std::cout << "Message deleted!";
}
}
else
ewsBinding.soap_stream_fault(std::cerr);
ewsBinding.destroy();
}
Again, don't forget to call ewsBinding.destroy()
to clean up all managed data!
Here is testClient_DeleteItem.cpp
in its entirety:
#include "ExchangeServiceBinding.nsmap"
#include "soapExchangeServiceBindingProxy.h"
#include "soapH.h"
#include <iostream>
#include <string>
static const char *userid;
static const char *passwd;
int main(int argc, char **argv)
{
if(argc>=3)
{
userid = argv[1];
passwd = argv[2];
}
ExchangeServiceBindingProxy ewsBinding("https://outlook.office365.com/EWS/Exchange.asmx");
ewsmsg__DeleteItemType request;
__ewsmsg__DeleteItemResponse response;
soap_init1(ewsBinding.soap, SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_XML_INDENT | SOAP_XML_NOTYPE);
ewsBinding.soap->userid = userid;
ewsBinding.soap->passwd = passwd;
soap_ssl_init();
if(soap_ssl_client_context(ewsBinding.soap,SOAP_SSL_DEFAULT | SOAP_SSL_SKIP_HOST_CHECK,NULL,NULL,"cacerts.pem",NULL,NULL))
{
soap_print_fault(ewsBinding.soap, stderr);
exit(1);
}
_ewstype__RequestServerVersion serVer;
enum ewstype__ExchangeVersionType exchangeVer = ewstype__ExchangeVersionType__Exchange2010_USCORESP2;
serVer.Version = exchangeVer;
ewsBinding.soap_header(NULL, NULL, NULL, &serVer,NULL,NULL);
ewstype__ItemIdType item;
item.Id = "AAMkAGQ0MDg4MjMwLTYyZjEtNDc1Zi1hNWQwLWY4ZGFiODIyY2ZiOABGAAAAAADQJ5K0osuKQatv50kfuGI4BwALbeOJ+btaT4I3Qf8SQ+rtAAAAAAEQAAALbeOJ+btaT4I3Qf8SQ+rtAAKA2yshAAA=";
__ewstype__union_NonEmptyArrayOfBaseItemIdsType member;
member.ItemId = &item;
ewstype__NonEmptyArrayOfBaseItemIdsType itemArray;
itemArray.__size_NonEmptyArrayOfBaseItemIdsType = 1;
itemArray.__union_NonEmptyArrayOfBaseItemIdsType = &member;
request.ItemIds = &itemArray;
request.DeleteType = ewstype__DisposalType__SoftDelete;
ewstype__AffectedTaskOccurrencesType ATOType = ewstype__AffectedTaskOccurrencesType__SpecifiedOccurrenceOnly;
request.AffectedTaskOccurrences = &ATOType;
ewstype__CalendarItemCreateOrDeleteOperationType CalType = ewstype__CalendarItemCreateOrDeleteOperationType__SendOnlyToAll;
request.SendMeetingCancellations = &CalType;
if(ewsBinding.DeleteItem(&request, response)==SOAP_OK)
{
ewsmsg__ResponseMessageType *iirmt
= response.ewsmsg__DeleteItemResponse->ResponseMessages->__union_ArrayOfResponseMessagesType->DeleteItemResponseMessage;
if(iirmt != NULL)
{
if (iirmt->ResponseClass == ewstype__ResponseClassType__Error)
std::cout << *iirmt->__ResponseMessageType_sequence->MessageText <<std::endl;
else
std::cout << "Message deleted!";
}
}
else
ewsBinding.soap_stream_fault(std::cerr);
ewsBinding.destroy();
}
Compiling the DeleteItem example
We must compile with OPENSSL to ensure that our credentials are transmitted securely. This requires the OpenSSL and OpenSSL crypto libraries.
You will need to include the paths to the stdsoap2.cpp
and duration.c
files in the gSOAP library.
Similarly to the FindItem compilation, you will need to pass username and password arguments to the executable.
clang++ -Wno-undefined-bool-conversion -o di -DWITH_OPENSSL testClient_DeleteItem.cpp soapC.cpp soapExchangeServiceBindingProxy.cpp /Path/to/stdsoap2.cpp -I. /path/to/gSOAP/gsoap/custom/duration.c -lssl -lcrypto
./di emailusername@outlook.com email_password
With the soft delete option, the email will not be sent to your delete folder. If you would only like to move the item to the delete folder, use testClient_MoveItem.cpp
(included in the tar attached to this article). When you compile and run this example, your email should dissapear from the drafts folder.
More Examples
Now that you have familiarized yourself with connecting gSOAP and EWS to make a client, you can work through the other examples in the tar file on your own! The files included in the tar are as follows:
testClient_CreateAttachment.cpp
testClient_DeleteAttachment.cpp
testClient_CreateFolder.cpp
testClient_DeleteFolder.cpp
testClient_FindFolder.cpp
testClient_MoveFolder.cpp
testClient_CreateItem.cpp
testClient_DeleteItem.cpp(described in this article)
testClient_FindItem.cpp
testClient_SendItem.cpp(described in this article)
testClient_MoveItem.cpp
testClient_PwdExpirationDate.cpp
If you want to learn about other service operations, this page will be very useful. If you wanted to learn about CreateItem, you can search CreateItemtype Class in the search bar and a resut of CreateItemtype Class (ExchangeWebServices) will show. If you click on that result, exampe code is provided which shows which attributes need to be set in order to call the service operation (code is in C#). This search works when you enter and function in the format of Function_Nametype Class.
If you have any questions feel free to leave a comment!