Operating system used: macOs Catalina Version 10.15.5

Python Version: Python3

What is an Exchange Web Service?

So before we even begin, what exactly is the Exchange Web Service (EWS) ?  Well in short, EWS is a Microsoft API that allows you to connect to Exchange Online which stores all the data used in Microsoft applications but more specifically Outlook. It allows developers to extract data such as the mailbox, calendar, tasks, and etc which in turn allows them to use this data in their own applications. This API proves to be very useful and powerful because it allows Outlook to be easily integrated into anything.

What is the difference between PowerShell vs Python?

Exchange Web Services is mainly built to be used by PowerShell but many developers love the flexibility and the wide range of tasks that can be done using Python so they look to use Python to access EWS instead.

Like I said Python is a very powerful language because its a general-purpose language that can be used for many purposes ranging from web development to machine learning. Meanwhile, PowerShell is a scripting language mainly used for task automation.

Despite the differences when we look at the two languages, they show a lot of similarity since they are both object-oriented. So knowing one or the other will allow you to easily transition into using the other.

So let's first compare the two languages:

Installing modules and libraries:

#Python3:

pip install <module_name>
#PowerShell:

Install-Module -Name <module_name>

Importing modules and libraries:

#Python3:

 import <module_name>
#PowerShell

Import-Module <module_name>

The element that makes Python3 so powerful is it's access to extensive libraries and modules that can do just about anything. So here we can see how both Python and PowerShell are able to easily install and import modules/libraries.


Extensions:
Python: .py / PowerShell: .ps1 / PowerShell Module: .psm1
These are the extensions for Python and PowerShell files respectively.

Basic code:

hello_variable = 'Hello Word'
print(hello_variable)

if 'H' in hello_variable:
    print('H Found')

for i in hello_variable:
    print(i)
$hello_variable = 'Hello Word'
Write-Output $hello_variable

if($hello_variable -like "H*"){
  Write-Output 'H Found'
}

for ($i -in $hello_variable){
  Write-Output $hello_variable
}

So here we can see two short snippets of code showing the similarities between Python and PowerShell.


Variables: So on the first line of each snippet we can see how both languages declare their variables. It's almost identical except for the fact that PowerShell uses '$' in front of their variable names.


Printing/outputting: On the second line of each snippet, we see how both languages print/output. Again, very similar in the format except for different syntax.

If statements: So the if statement shown in both snippets checks whether there is an H in the variable and if there is then print out a message. Here we can see that the two languages start to differ a bit because Python's syntax has the variable to check on the right where as PowerShell has it on the left side.

For Loop: Now going back to this for loop, the syntax is almost identical once again but these loops do different things with the Python loop printing out each letter in the variable and the PowerShell for loop printing out the variable a certain amount of times.

By comparing these two snippets of code, we can see how PowerShell and Python are so similar in what they can do but differs in the syntax. Therefore, we will easily be able to connect to the EWS using Python even though PowerShell is the norm.

Connecting to Exchange Web Services using PowerShell:

First we will need to install PowerShell and the EWS Managed API. We can use brew to install PowerShell:

#Use brew to install powershell:

brew cask install powershell

#Use this command to check if PowerShell was downloaded successfully:

pwsh

Then you can install the EWS Managed API from the Microsoft website.

function Connect-EWS {
    param(
        [parameter(Mandatory = $True)]
        [PSCredential]
        $Credential,
        [parameter(Mandatory = $True)]
        [string]
        $Domain,
        [parameter(Mandatory = $True)]
        [string]
        $AutoDiscoverUrl
        
    )

    # First import the DLL, else none of the EWS classes will be available

    Try{
        Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
    }
    Catch{
        Write-Error "Error importing C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
        Write-Error $_.Exception
        Break
    }
    
    # Set the domain in networkcredential - don't know if this is the way to do it but it works.
    $Credential.GetNetworkCredential().Domain = $Domain

    # Create ExchangeCredential object from regular [PSCredential]
    $ExchangeCredential = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($Credential.Username, $Credential.GetNetworkCredential().Password, $Credential.GetNetworkCredential().Domain)
    
    # Create the ExchangeService object
    $ExchangeService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService 
    $ExchangeService.UseDefaultCredentials = $true 
    
    # Add the ExchangeCredential to the ExchangeService
    $ExchangeService.Credentials = $ExchangeCredential
    
    # {$true} is needed if it's office 365 with a redirect on the autodiscovery. I think there's a lot more secure ways to check if
    # a redirection is correct.
    $ExchangeService.AutodiscoverUrl($AutoDiscoverUrl, {$true})
    
    # Return the object
    return $ExchangeService
}
Source: https://automativity.com/Using-the-EWS-API-with-Powershell/

Heres a basic function that can be used to connect to the EWS with PowerShell. As you can see here, it takes in credential and domain parameters to create a connection to the EWS. It returns a $ExchangeService object which will be used to bind the different services like calendar, inbox, etc. Then from there we can call on endpoints of those services to retrieve data such as getting all your messages in the inbox.

Now let's see how we do it in Python.

How to connect to an Exchange Web Service using Python3?

Once again since EWS was built to be used with PowerShell in order for us to connect to it using Python, we will need the help of a library called exchangelib.

So first we need to install the library with pip:

#regular install
pip install exchangelib

#kerberos support
pip install exchangelib[kerberos]

#sspi support
pip install exchangelib[sspi]

#both sspi and kerberos support
pip install exchangelib[complete]

So I just used the regular install but you can use the other installs if you want kerberos or sspi support which is a a network authentication protocol and a
win32 api used for security related operations which we won't be getting into in this article.

So here's how I first attempted to connect to the EWS:

from exchangelib import DELEGATE, Credentials, Account 

credentials = Credentials(
    username = 'MYWINDOMAIN\\myusername', #or myusername@email.com
    password = 'password'
)

test_account = Account(
    primary_smtp_address = 'myusername@email.com',
    credentials = credentials,
    autodiscover = True,
    access_type = DELEGATE
)

# Print first 100 inbox messages in reverse order
for item in test_account.inbox.all().order_by('-datetime_received')[:100]:
    print(item.subject, item.body, item.attachments)

So first we imported DELEGATE, Account, Credentials from the exchangelib library.

DELEGATE is an access type that is used when the primary account holder who is authorized to perform actions on the account.

Credentials is a function used to define the credentials of the account. As you can see from the code snippet that credentials takes in a username and the password. The user can be given in two formats. One being MYWINDOMAIN\myusername with MYWINDOMAIN being your windows domain name and myusername being the username of your Outlook 365 account. Then there are some servers that will just allow the username to be the primary smtp address which is the primary email that you are grabbing data or performing actions from.

So I used the defined the username as my primary smtp address and then the password as the password of my Outlook 365 account.

Account is a class used to instantiate the account that we will be grabbing data from. Here we pass in 4 arguments. One being the primary smtp address. The second being the credentials that we defined earlier. Third is a boolean declaring autodiscover meaning that we want exchangelib to automatically match us to the correct Exchange server. The final argument is the access type which we pass in DELEGATE which we discussed about above.

This is basically all we needed to do to connect to the EWS. In order to test the connection, we have a short snippet of code that will print out the first 100 inbox messages in reverse order.

Though running this code produce an error for me:

exchangelib.errors.AutoDiscoverFailed: All steps in the autodiscover protocol failed

I found that this occurs because the autodiscover protocol is actually very fragile and some servers will not support this exchangelib server so a way to get around this is to directly connect to the server by creating a new configuration.

from exchangelib import DELEGATE, Account, Credentials, Configuration

credentials = Credentials(
    username = 'myusername@email.com', #or myusername
    password = 'password'
)

config = Configuration(server='outlook.office365.com', credentials=credentials)

test_account = Account(
    primary_smtp_address = 'myusername@email.com',
    config = config,
    autodiscover = False,
    access_type = DELEGATE
)
# Print first 100 inbox messages in reverse order
for item in test_account.inbox.all().order_by('-datetime_received')[:100]:
    print(item.subject, item.body, item.attachments)

So we can manually specify the server name by using the Configuration function. First we need to import it and then we will pass on 2 arguments to the function. The first one being the server name and then the second being the credentials that we defined earlier.

So to find the server name we first login to outlook 365 > gear icon on the top right > view all outlook settings > choose mail on the left hand side > click sync email > copy the server name under pop or imap settings.

Select gear icon on the top right of outlook.
Select view all outlook settings.
Select mail > sync email > POP/IMAP Setting > Server name.

With this configuration we can finally connected to the EWS without any errors.

In this article, we can see how we can use Python with exchangelib to connect to the Exchange Web Service. We've also learned how similar PowerShell and Python are so similar therefore we further understand how Python is able to connect to the EWS even though normally we'd have to use PowerShell.

In the next article, we will explore more of what we can do with Python and the exchangelib with EWS.


Wilson Song, Software Engineer Intern at HacWare. HacWare measures risky behaviors and automates security awareness to combat business email compromised attacks. HacWare is backed by TechStars NYC and CyberNYC.

Learn more about HacWare at www.hacware.com.