Consuming Microsoft Exchange Web Services (EWS)

Cecil

19+ years progress programming and still learning.
Hi

Has anybody successfully be able to integrate with Microsoft Exchange Web Services (EWS)?

I see that Bruce Gruenbaum has hooking into EWS via a custom built JAVA service to act like a proxy agent between OpenEdge Client and Exchange Server. However this was written in OpenEdge 10.2B. We are now in the 11.2/3 arena and thing have improved regarding SSL, SOAP and complex XML data types which was originally lacking in 10.2B.

So far I successfully connected to my Exchange Server 2010 and pulled down the WDSL file from Openedge 11.2 and that's it. I have no idea what to do next. I read and skipped through the Microsoft SDK manual for EWS, but it was a useful as a chocolate tea pot.

Should I continue in vain trying to integrate EWS into OpenEdge 11.2 or should I just give up and put it into the "too hard basket"?

See Bruce Gruenbaum blog:
http://www.thesoftwaregorilla.com/2...vices-example-part-1-introduction-and-set-up/
 

Metsakohin

New Member
Hi.

I made the account just to ask, if You had any success. Business side wants to integrate Outlook callendar into out CRM, which is written in progress and after reading about it from the net, it seems a hard nut to crack. If You managed to integrate with EWS then any help would be really good.
 

Cecil

19+ years progress programming and still learning.
Hi.

I made the account just to ask, if You had any success. Business side wants to integrate Outlook callendar into out CRM, which is written in progress and after reading about it from the net, it seems a hard nut to crack. If You managed to integrate with EWS then any help would be really good.

Hi and welcome to Progress Talk forum.

Regarding integrating with EWS, I found it to hard and complicated to continue with this project. One of the major hurdle was to communicating with EWS as it need to use NTLM user authentication (which progress does not natively supports). I had the idea to use cURL command line tool handle the HTTP communications. However I found the Microsoft SDK documentation far too complicated to digest and in the end I just gave up.

I looked at the possibility using JAVA projects (google: "JAVA EWS") to act as a gateway between ABL and EWS. But due to my lacking in JAVA skills this was also a dead end for me, but still a valid avenue.
 

Cecil

19+ years progress programming and still learning.
Hey Metsakohin,

I've been thinking more about consuming EWS from the ABL. I started to investigate how I could manually send the SOAP request via the command line tool (cURL) and I have been successful in creating a calendar event under my Exchange Account (in fact it work first time). So as a proof of concept I think I might be able start creating a ABL library that can interact with Exchange EWS. This is a major project and I would not have anything developed soon.

He is what I did:

Manually create a SOAP request XML file call CreateItemCalendar.XML
Code:
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
  <soap:Body>
    <CreateItem xmlns="http://schemas.microsoft.com/exchange/services/2006/messages"
                xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
                SendMeetingInvitations="SendToAllAndSaveCopy" >
      <SavedItemFolderId>
        <t:DistinguishedFolderId Id="calendar"/>
      </SavedItemFolderId>
      <Items>
        <t:CalendarItem xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
          <Subject>Planning Meeting</Subject>
          <Body BodyType="Text">Plan the agenda for next week's meeting.</Body>
          <ReminderIsSet>true</ReminderIsSet>
          <ReminderMinutesBeforeStart>60</ReminderMinutesBeforeStart>
          <Start>2014-01-31T14:00:00</Start>
          <End>2014-01-31T15:00:00</End>
          <IsAllDayEvent>false</IsAllDayEvent>
          <LegacyFreeBusyStatus>Busy</LegacyFreeBusyStatus>
          <Location>Conference Room 721</Location>
        </t:CalendarItem>
      </Items>
    </CreateItem>
  </soap:Body>
</soap:Envelope>

Then form the command Line I executed the cURL command:
Code:
 curl  -u name@domain.com:passwordgoeshere -L https://my.exchangeserver.com/EWS/exchange.asmx -d "@CreateItemCalendar.xml" -H "Content-Type:text/xml" > CreateItemCalendar-responce.xml

Note: You might need to also include the --ntlm parameter to curl if this does not work for you first time. For some reason my hosted exchange service is using basic authentication rather than NTML..

Let us know how well you got on.
 

Cecil

19+ years progress programming and still learning.
Last night I quickly created a ABL program which consumes Microsoft's Exchange Web Services SOAP API to just retrieve the root folders as what you can see (and not see) in outlook.

This code is working for me because my Hosted Exchange provider is using BASIC HTTP authentication rather then NTLM authentication. NTLM Authentication is not available in the ABL.

For the code to work you will need to download the following files from your Exchange server as save them to your working directory: Services.wsdl , messages.xsd and types.xsd.

Example:
https://owa.hostedexchange.com/EWS/Services.wsdl
https://owa.hostedexchange.com/EWS/messages.xsd
https://owa.hostedexchange.com/EWS/types.xsd

The Services.wsdl needs to be amended to include a extra element fragment. This needs to be inserted after the '</wsdl:binding>' and before '</wsdl:definitions>'.

Code:
<wsdl:service name="ExchangeServices">
    <wsdl:port name="ExchangeServicePort" binding="tns:ExchangeServiceBinding">
        <soap:address location="https://owa.hostedexchange.com/EWS/Exchange.asmx"/>
    </wsdl:port>
</wsdl:service>

Code:
DEFINE TEMP-TABLE ttFolder NO-UNDO
    FIELD displayName AS CHARACTER LABEL 'Display Name' format "x(32)"
    INDEX idxDisplayName
    displayName.

FUNCTION ExchangeService RETURNS HANDLE
    (INPUT pchUsername AS CHARACTER,
     INPUT pchPassword AS CHARACTER):

    DEFINE VARIABLE hWebService              AS HANDLE NO-UNDO.
    DEFINE VARIABLE hExchangeServicePortType AS HANDLE NO-UNDO.
    DEFINE VARIABLE chEWSSOAPConnect         AS CHARACTER   NO-UNDO.            

    CREATE SERVER hWebService.

    chEWSSOAPConnect = SUBSTITUTE("-WSDL 'Services.wsdl' -Service ExchangeServices -ServiceNamespace http://schemas.microsoft.com/exchange/services/2006/messages -SOAPEndpointUserid &1 -SOAPEndpointPassword &2",
                                  pchUsername,
                                 pchPassword).

    hWebService:CONNECT(chEWSSOAPConnect) NO-ERROR.

    IF NOT ERROR-STATUS:ERROR THEN
        RUN ExchangeServicePortType SET hExchangeServicePortType ON hWebService.
    ELSE
        MESSAGE ERROR-STATUS:GET-MESSAGE(1)
            VIEW-AS ALERT-BOX ERROR.

    /** NOT YET NEEDED **/
    /** hExchangeServicePortType:SET-CALLBACK-PROCEDURE("REQUEST-HEADER", "ReqHandler").**/

    RETURN hExchangeServicePortType.
END FUNCTION.

PROCEDURE ReqHandler:

    /* Define procedure parameters */
   DEFINE OUTPUT PARAMETER hHeader AS HANDLE.
   DEFINE INPUT PARAMETER cNamespace AS CHARACTER.
   DEFINE INPUT PARAMETER cLocalNS AS CHARACTER.
   DEFINE OUTPUT PARAMETER lDeleteOnDone AS LOGICAL.

   /** Not Yet Used**/
   /** EWS has option into allow additional SOAP header to be instered if required.**/

   RETURN.
END PROCEDURE.

FUNCTION FINDFOLDERREQUEST RETURNS LONGCHAR
    (INPUT chDistinguishedFolderId AS CHARACTER ):
                                                                                                                   
    /** Find Folder XML Request **/
    DEFINE VARIABLE LCREQUEST   AS LONGCHAR NO-UNDO.
    DEFINE VARIABLE hnSAXWriter AS HANDLE   NO-UNDO.

    CREATE SAX-WRITER hnSAXWriter.

    hnSAXWriter:FRAGMENT  = TRUE.
    hnSAXWriter:FORMATTED = TRUE.
    hnSAXWriter:STRICT    = FALSE.
    hnSAXWriter:SET-OUTPUT-DESTINATION('longchar':U , LCREQUEST).

    hnSAXWriter:START-DOCUMENT().
    hnSAXWriter:START-ELEMENT('FindFolder':U,'http://schemas.microsoft.com/exchange/services/2006/messages':U).
    hnSAXWriter:DECLARE-NAMESPACE('http://schemas.microsoft.com/exchange/services/2006/types':U,'t':U).

    hnSAXWriter:INSERT-ATTRIBUTE('Traversal':U,'Shallow':U).
    hnSAXWriter:START-ELEMENT('FolderShape':U).
    hnSAXWriter:WRITE-DATA-ELEMENT('t:BaseShape':U, 'Default':U ).
    hnSAXWriter:END-ELEMENT('FolderShape':U).
    hnSAXWriter:START-ELEMENT('ParentFolderIds':U).
    hnSAXWriter:WRITE-EMPTY-ELEMENT ('t:DistinguishedFolderId':U).
    hnSAXWriter:INSERT-ATTRIBUTE('Id':U, chDistinguishedFolderId).
    hnSAXWriter:END-ELEMENT('ParentFolderIds':U).
    hnSAXWriter:END-ELEMENT('FindFolder':U).
    hnSAXWriter:END-DOCUMENT().

    DELETE OBJECT hnSAXWriter.
    RETURN LCREQUEST.
END FUNCTION.

PROCEDURE FindFolderResponce:
    DEFINE INPUT PARAMETER plcFindFolderResult AS LONGCHAR NO-UNDO.

    DEFINE VARIABLE hnSAXReader AS HANDLE   NO-UNDO.
    /** Read the Responce...**/
    CREATE SAX-READER hnSAXReader.

    hnSAXReader:ADD-SCHEMA-LOCATION("http://schemas.microsoft.com/exchange/services/2006/messages", "messages.xsd").
    hnSAXReader:ADD-SCHEMA-LOCATION("http://schemas.microsoft.com/exchange/services/2006/types", "types.xsd").
    hnSAXReader:HANDLER = THIS-PROCEDURE.
    hnSAXReader:SET-INPUT-SOURCE('Longchar',plcFindFolderResult).
    hnSAXReader:SAX-PARSE( ).

    /* message string(request) SKIP */
    /*     STRING(FindFolderResult) */
    /*     view-as alert-box info.  */
    RETURN.
END PROCEDURE.

DEFINE VARIABLE LCELEMENTVALUE AS LONGCHAR   NO-UNDO.

/** SAX Override Procedure. **/
PROCEDURE characters:

    DEFINE INPUT PARAMETER pmCharArray   AS MEMPTR NO-UNDO.
    DEFINE INPUT PARAMETER piArrayLength AS INTEGER NO-UNDO.

    LCELEMENTVALUE = LCELEMENTVALUE + GET-STRING(pmCharArray,1,piArrayLength ).

    /* COPY-LOB FROM OBJECT pmCharArray FOR piArrayLength TO OBJECT LCELEMENTVALUE APPEND. */

    RETURN.
END PROCEDURE.

/** SAX Override Procedure. **/
PROCEDURE startElement:
    DEFINE INPUT PARAMETER pcNamespaceURI AS CHARACTER NO-UNDO.
    DEFINE INPUT PARAMETER pcLocalName    AS CHARACTER NO-UNDO.
    DEFINE INPUT PARAMETER pcQName        AS CHARACTER NO-UNDO.
    DEFINE INPUT PARAMETER phAttributes   AS HANDLE NO-UNDO.

    LCELEMENTVALUE = ''.

    RETURN.
END PROCEDURE.

/** SAX Override Procedure. **/
PROCEDURE endElement:
    DEFINE INPUT PARAMETER pcNamespaceURI AS CHARACTER NO-UNDO.
    DEFINE INPUT PARAMETER pcLocalName    AS CHARACTER NO-UNDO.
    DEFINE INPUT PARAMETER pcQName        AS CHARACTER NO-UNDO.

    IF pcLocalName EQ 'DisplayName':U THEN
    DO:
        CREATE ttFolder.
        ASSIGN
            ttFolder.displayName = LCELEMENTVALUE.
    END.
                                                                                             
    RETURN.
END PROCEDURE.

/** MAIN-BLOCK **/

DEFINE VARIABLE lcFindFolderRequest      AS LONGCHAR NO-UNDO.
DEFINE VARIABLE lcFindFolderResult       AS LONGCHAR NO-UNDO.
DEFINE VARIABLE hExchangeServicePortType AS HANDLE      NO-UNDO.

hExchangeServicePortType = ExchangeService(INPUT 'your emaill address goes here',
                                           INPUT 'YOUR PASSWORD GOES HERE').

lcFindFoldeRREQUEST = FINDFOLDERREQUEST(INPUT 'msgfolderroot':U ).

/** DEBUG MESSAGE **/
/** message STRING(lcFindFoldeRREQUEST) view-as alert-box info. **/

/** Execute the SOAP call..**/
RUN FindFolder IN hExchangeServicePortType(INPUT lcFindFoldeRREQUEST,
                                           OUTPUT lcFindFolderResult).

RUN FindFolderResponce(INPUT lcFindFolderResult).

FOR each ttFolder:
    DISPLAY ttFolder.
END.
 

Cecil

19+ years progress programming and still learning.
If anyone is interested, I've created a Proof-of-Concept Class Object to consume Exchange Web Services (Exchange 2010/2013). This should also work in Linux as it's written in pure ABL.

Currently it has the following methods:
  • List your calendar events (also other mail boxes which you have permissions to access).
  • Create a calendar appointment/meeting requests.
  • Delete calendar event and moves it to your Deleted Items or deletes it permanently.
To do:
  • Update an existing calendar event i.e. 'Subject, Start/End times, Location,Body etc'.
  • Fetch a list of changes which have been made from Outlook or Mobile Device.
  • Better error handling.
  • Better code re-factoring as the code could become huge.
  • Add CRUD to Tasks, Contacts, Email (To be able to send emails with attachments).
Also in theory this should also work with Office 365 as it using BASIC Authentication over SSL rather than NTLM.
 

Cecil

19+ years progress programming and still learning.
Sneak Peak of the code for calling my Exchange Web Service class objects from the ABL:

Code:
/** procedure Create New Calendar Event. */
DEFINE VARIABLE obEWS AS CLASS EWS NO-UNDO.
DEFINE VARIABLE chEventID AS CHARACTER   NO-UNDO.

obEWS = NEW EWS("name@randomdomain.com", "Y0urP455w0rd").

obEWS:Calendar():Subject('This is a test...').
obEWS:Calendar():StartTime( DATETIME(TODAY,MTIME ) ). /* NOW */

/** Optional properties which can be assigned.. PLUS More.**/
obEWS:Calendar():Location('My Office').
obEWS:Calendar():Body('<p>Example of my first Calendar Event.</p>','HTML').

MESSAGE
    "Do you want to continue in creating a new calender event?" SKIP(1)
    obEWS:Calendar():Subject SKIP
   STRING( obEWS:Calendar():StartTIME ,"99/99/9999 HH:MM" ) '-'
   STRING( obEWS:Calendar():EndTIME ,"99/99/9999 HH:MM" ) 
    VIEW-AS ALERT-BOX QUESTION
    BUTTONS YES-NO-CANCEL
    TITLE "Create Calendar Event"
    UPDATE lgContine AS LOGICAL.


IF lgContine THEN
DO:

    chCalendarEventID = obEWS:Calendar():AddCalendarEvent().

    MESSAGE chCalendarEventID
        VIEW-AS ALERT-BOX INFO
        TITLE "Calendar Event Created" .

END.
ELSE
    MESSAGE "No calendar event created"
        VIEW-AS alert-box info
        TITLE "Calendar Event - User Declined" .
 

juanmorales

New Member
Hi.
Cecil, i'm triying to make a api to sendmail trough office 365. I'd look to your first code and try something like that, but i'm little stock.
First I tried your first example and i can't get trough the call to CreateItem, it keep's teeling me that the number of parameters passed are not the same.

So, i've install OpenEdge 11.3 so i can't try to load directly the dll provided from microsoft for the EWS Managed API, but i'm not able to load de dll so i can use the class.

In your second piece of code, i've seen that you work from the class. How did you managed to get that working?

Thanks in Advance.
 

Cecil

19+ years progress programming and still learning.
Hi.
Cecil, i'm triying to make a api to sendmail trough office 365. I'd look to your first code and try something like that, but i'm little stock.
First I tried your first example and i can't get trough the call to CreateItem, it keep's teeling me that the number of parameters passed are not the same.

So, i've install OpenEdge 11.3 so i can't try to load directly the dll provided from microsoft for the EWS Managed API, but i'm not able to load de dll so i can use the class.

In your second piece of code, i've seen that you work from the class. How did you managed to get that working?

Thanks in Advance.

I'm a little bit busy at the moment and the project has been put on hold. I need to spend some extra time on this to get the code working again and publish it on github. Can you attach the WSDL file from Office 365. It might be different to the version I'm developing against and that is why you are getting the miss-matching parameters.
 

juanmorales

New Member
Here's the services.wsdl. I've change the file extension for the upload.
Thanks.
 

Attachments

  • services.wsdl.txt
    158.9 KB · Views: 18

medu

Member
We already have that working for office365/exchange for the following: calendar, tasks, contacts, emails and attachments (those actually can be added to practically any item in exchange)... 'one drive' (sharepoint) support for upload/download is planed to be added although authentication there is a bit more complicated.

If interested drop me a line, and no it's not on github or sourceforge this time :)
 

juanmorales

New Member
We already have that working for office365/exchange for the following: calendar, tasks, contacts, emails and attachments (those actually can be added to practically any item in exchange)... 'one drive' (sharepoint) support for upload/download is planed to be added although authentication there is a bit more complicated.

If interested drop me a line, and no it's not on github or sourceforge this time :)

I've managed to get through the mismatched parameters, but now it's trowing an error for the SSL authentication, in Bruce Gruenbaum blog, I read something about openedge and ssl, and that's we're i'm stuck right now.

How did you managed to get it work? It's via ews managed api or throug web services?
 

medu

Member
How did you managed to get it work? It's via ews managed api or throug web services?
It's web-services only, to pass the SSL error you might want to add '-nohostverify' to the connection string but for office365 you need to import certificates first (look-up certutil -import).
 

juanmorales

New Member
I've managed to pass the ssl error, now it's showing this error:

SOAP faultstring es: The request failed schema validation:
The element 'CreateItem' in namespace 'http://schemas.microsoft.com/exchange/services/2006/messages' has invalid child element 'Body' in namespace
'http://schemas.xmlsoap.org/soap/envelope/'.
List of possible elements expected: 'SavedItemFolderId, Items' in namespace 'http://schemas.microsoft.com/exchange/services/2006/messages'. (11506)

The thing it's that i create the xml based on the example provided by microsoft, but it's not working. Any ideas?
 

Cecil

19+ years progress programming and still learning.
Here's the services.wsdl. I've change the file extension for the upload.
Thanks.
I had a quick file diff between my WDSL and yours and they are wildly different surprisingly. This is going to take more time to resolve which I just don't have a the moment.
 
Top