Quantcast
Channel: Glen's Exchange and Office 365 Dev Blog
Viewing all 241 articles
Browse latest View live

Enumerating Public Folders using EWS and reporting on Rules and Folder Stats

$
0
0
When people mention Public Folders in Exchange a quote from Mark Twain often comes to mind "the reports of my death are greatly exaggerated", one often thinks the same when people write such things about email.  While Groups in Office 365 offer a compelling alternative, Public Folders are still things Admins need to deal with and Migrate occasionally.

While the Exchange Management Shell cmdlets for reporting on Public Folders have improved a lot in Exchange 2013 you may still have the need from time to time to want to do some more in-depth analysis of what's happening in your Public Folders which is where EWS can come in very useful. The one big difference to reporting on folders in a Public Folder Tree vs doing the same thing in a Mailbox is that in a Public Folder tree you can't do a Deep Traversal. This means to report on Public Folders you need to enumerate the child folders of each parent folder with a separate EWS call. For very large Public Folder trees this can take a considerable amount of time.

The following Script does this enumeration for all the Public Folders (that the Mailbox that is running the script) has access to, it checks to see if there are any Rules that are Active or Disabled, and also produces a basic stats report of the Number of Items, FolderSize and also the PidTagLocalCommitTimeMax time which should give you the last time the items in the folder where last changed. eg it produces something like


With this base you can add-in whatever you want to do with each folder depending on your particular requirements.

I've put a copy of this script on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/PublicFolderMod.ps1

To Run the script you need to pass in a Mailbox this is used to connect to CAS server and then connect eg

Enum-PublicFolders -MailboxName mailbox@domain.com

Mining the clientinfo property in Messages in Exchange 2013 and Office365 with EWS

$
0
0
Tracking the bewildering array of clients and different Internet browsers that are being used to send email on Exchange can be an interesting challenge especially when your clients are in the cloud. One Mapi property that gets set on the SentItem in the senders Mailbox can help answer some of these questions where logs may not be available. The Named Mapi property clientinfo eg


So if you write a script that accesses this property on messages in the SentItems folder you can build a report of the different sending methods that are being used and also different properties such as the browser version from the agent string being stored eg something like this


I've put a script on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/GetMailUserAgentsv2.ps1 that can mine the data in the property correctly taking into account the ; used as a separator while also dealing with data enclosed in ( ) (it does this by just doing a character by character parse rather the splitting or using Regex). The script will separate the Primary ClientType and extra client information (eg browser agent string) from the clientinfo property.

The script has a few different options by default it will evaluate every message in the SentItems folder if you use the -days switch you can specify how many days to look back eg

Get-MailUserAgents -Mailbox user@domain -days 14

Will look at messages for the last 14 days, if you want to evaluate messages in the Inbox you can use the -Inbox switch which will change the target folder to the Inbox.

Conversation statistics with EWS

$
0
0
Conversations have been a hot area in the Messaging space recently with many different solutions jockeying for attention such as Yammer, Office365 Groups , Slack, HipChat, Skype for business and many other. Each solution offers a different method to communicate and thread different conversations in different ways  over different clients and communication platforms. Typically in Exchange conversations either take place in Mailboxes or Public folders while Groups are a now a newer offering.

When it comes to reporting, looking at conversations can offer some interesting insights as to when conversations are happening how many people are participating and where a Group or Channel based solution might provide some form of productivity gain or usefulness.

In EWS in Exchange 2010 and greator the findconversation operation allows you to query and group conversation threads in a Mailbox folder and you can then use the information returned about the conversations to get the individual conversation items in a Mailbox using the Batch EWS operations. 

To demonstrate some of this I've come up with a PowerShell module that does just that it first uses the FindCoversation operation to get the Messages from the Inbox of a Mailbox or other folders if you use the FolderPath switch.  The script enumerates all the available message from the conversation that it looks at (eg those with more then 2 participants and 2 messages). It then compiles statistics for each conversation about the

Number of messages, participants
Start and duration of the conversation thread in hours
The original sender of the thread and how many messages they sent
The loud mouth (the person who isn't the originate of the thread  who has responded the most) and number of Messages. from this person

I've put a copy of the module on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/EWSConversation.ps1 to run the script you pass in the Mailbox you want it to run against and period of days to look back eg

 Get-ConversationStats -MailboxName gscales@datarumble.com -Period 60

If you are moving conversations into a different folder in a Mailbox using a Rule eg from a Distribution list you can report on these folders using the FolderPath parameter eg

 Get-ConversationStats -MailboxName gscales@datarumble.com -Period 60 -FolderPath \Inbox\BlahGroup

EWS Basics : Enumerating all the Items in any folder in a Mailbox

$
0
0
I've decided to start a new series on the blog called EWS Basics where I'll post some reusable scripts that are fit for easy customization by those people unfamiliar with EWS. So the basic idea is to have a group of samples where most of the underlying EWS plumbing code is done and you can just plug in whatever customized action you want to do on particular Items

Enumerate all the Items in any folder in a Mailbox

A common task when you want to report on the Item content in a Folder or you want to perform some type of action on Items (Delete,Move,Copy etc) is you will want to enumerate all the items in a folder. One analogy is doing a dir on directory (just don't mention the M: drive!)

So what this sample does is lets you input the MailboxName (PrimarySMTP Address) and Path to the Folder in the mailbox where the Item are  you want to access and it will write back to the pipeline the Items it finds.

So to put that together in a sample say you want to enumerate the Items in the Clutter Folder and output the ReceivedDateTime and Subject you could use the following

Get-FolderItems -MailboxName gscales@domain.com -FolderPath \clutter | Select DateTimeReceived,Subject

If the folder you wanted to access was a subfolder of the Inbox you could use

Get-FolderItems -MailboxName gscales@domain.com -FolderPath \Inbox\Subfolder| Select DateTimeReceived,Subject

Another example of doing something a little more advanced is where you modify the script to produce statistics of the domains of the senders for email in a Mailbox folder. To do this you need to modify the part of the script that enumerates each of the Items so instead of writing the object to the pipeline it does some summarizations. I've added some lines in that uses a HashTable to compile the statistics on Email Domains and then writes out and sorts those values in the last line.

do{    
$fiItems = $service.FindItems($SubFolderId,$ivItemView)
Write-Host ("Processed " + $fiItems.Items.Count)
#[Void]$service.LoadPropertiesForItems($fiItems,$ItemPropset)
foreach($Itemin$fiItems.Items){
#Process Item
$EmailAddress = new-object System.Net.Mail.MailAddress($Item.Sender.Address)
if($rptCollection.ContainsKey($EmailAddress.Host))
{
$rptCollection[$EmailAddress.Host].NumberOfItems +=1
$rptCollection[$EmailAddress.Host].SizeOfItems = $Item.Size
}
else
{
$rptObj = "" | select Domain,NumberOfItems,SizeOfItems
$rptObj.Domain = $EmailAddress.Host
$rptObj.NumberOfItems = 1
$rptObj.SizeOfItems = $Item.Size
$rptCollection.Add($EmailAddress.Host,$rptObj)
}
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq$true)
Write-Output$rptCollection.Values | Sort-Object -Property NumberOfItems -Descending
}

I've posted this script on GitHub https://github.com/gscales/Powershell-Scripts/blob/master/EnumerateItemsInFolder.ps1


EWS Basics : Reporting on the Age of Items in a Folder

$
0
0
One of the more common questions I get is around some of the EWS Item Age scripts I've written in the past. If we take the script I introduced in the first Basics post with some easy modifications we can turn this script that already enumerates all the Items in a folder into a script that will report on age of the Items in a folder. So in this post I'm going to try to explain a little better the changes that you would need to make.

EWS Changes

The EWS changes that need to be made to the basics script are to the Propertyset that is used in the FindItems Operation. A PropertySet in EWS basically tells Exchange which properties you want to be returned. So to make this script as efficient as possible we want to restrict Exchange to just returning the Size and DateTime the messages where received or created. So these are the only changes that are needed for the EWS side

$ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000) 
$ItemPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly)
$ItemPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size)
$ItemPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived)
$ItemPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeCreated)
$ivItemView.PropertySet = $ItemPropset

Reporting

When your reporting on the age of Items your basically aggregating the part of the Date/Time a Message was received. To do the aggregation in this script I'm just using a Hashtable which there is a good description of at https://technet.microsoft.com/en-us/library/ee692803.aspx . The way I'm using them to aggregate the number of Messages and the size of the messages for the Year a Message was received in. To get the Year a message was received in the item enumeration section of the script I'm using this simple function

$dateVal = $null
if($Item.TryGetProperty([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived,[ref]$dateVal )-eq$false){
$dateVal = $Item.DateTimeCreated
}
What this does is loads the DateTimeReceived property into the $dataVal variable and if that property isn't available (eg the message maybe a draft) it uses the DateTime created property.

Once we have this value its just a matter of firstly using the Year property of the (DateTime) Class and then we aggregate this value with the Hashtable. In the Values collection of the HashTable I'm using a Custom object (see https://technet.microsoft.com/en-us/magazine/hh750381.aspx) that does the aggregation part.  eg

if($rptCollection.ContainsKey($dateVal.Year)){
$rptCollection[$dateVal.Year].TotalNumber += 1
$rptCollection[$dateVal.Year].TotalSize += [Int64]$Item.Size
}
else{
$rptObj = "" | Select Year,TotalNumber,TotalSize
$rptObj.TotalNumber = 1
$rptObj.Year = $dateVal.Year
$rptObj.TotalSize = [Int64]$Item.Size
$rptCollection.add($dateVal.Year,$rptObj)
}

At the end of this we can just write the HashTable Values collection back to output Pipeline using write-object taking advantage of the sort-object cmdlet to make sure the values are sorted descending by Year

Write-Output$rptCollection.Values | Sort-Object -Property Year -Descending

I've put a copy of this script on GitHub here https://github.com/gscales/Powershell-Scripts/edit/master/GetItemAgeFromItemsInFolder.ps1

An example of running this script would look like


EWS Bascis : Accessing Public Folders and Public Folder Content

$
0
0
As well as Mailboxes and Archives, Public Folders are another place you may want to use EWS to enumerate Items to do some enhanced reporting. Building on the Enumerate script from the last two post, this post will cover how to modify this script to enable access Public Folder and Public Folder Items.

EWS Changes

To change the Mailbox code we have to access public folders requires one big change in the Mailbox script we have the line

$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$SmtpAddress) 

in the FolderIdFromPath function. To access a Public folder we need to change this to

$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot) 

And that's pretty much it apart from renaming the function to PublicFolderIdFromPath this is all you have to do. One thing you can't do with Public Folders vs what you can do with Mailboxs and Archive folders is do a DeepTraversal of all the folders in a folder hierarchy. For the basic enumeration script this isn't a big deal because all the searches are done at a shallow level.

Exchange 2013 up

For Exchange 2013,2016 and Exchange Online there is one other change you should make to this script which is to add the X-AnchorHeader and X-PublicFolderMailbox headers to ensure routing happens correctly as per https://msdn.microsoft.com/en-us/library/office/dn818490(v=exchg.150).aspx and https://msdn.microsoft.com/en-us/library/office/dn818491(v=exchg.150).aspx. It gets a little complicated here but because the Hierarchy and content maybe located in a different Public Folder Mailboxes you have to use 2 different discovery mechanisms both the EWS Managed API discover like

$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1
$AutoDiscoverService = New-Object Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverService($ExchangeVersion);
$creds = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
$AutoDiscoverService.Credentials = $creds
$AutoDiscoverService.EnableScpLookup = $false;
$AutoDiscoverService.RedirectionUrlValidationCallback = {$true};
$AutoDiscoverService.PreAuthenticate = $true;
$AutoDiscoverService.KeepAlive = $false;
$gsp = $AutoDiscoverService.GetUserSettings($MailboxName,[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::AutoDiscoverSMTPAddress);

and a POX Autodiscover (plain old XML) method like

$AutoDiscoverRequest = [System.Net.HttpWebRequest]::Create($AutoDiscoverService.url.ToString().replace(".svc",".xml"));
$bytes = [System.Text.Encoding]::UTF8.GetBytes($auDisXML);
$AutoDiscoverRequest.ContentLength = $bytes.Length;
$AutoDiscoverRequest.ContentType = "text/xml";
$AutoDiscoverRequest.UserAgent = "Microsoft Office/16.0 (Windows NT 6.3; Microsoft Outlook 16.0.6001; Pro)";
$AutoDiscoverRequest.Headers.Add("Translate", "F");
$AutoDiscoverRequest.Method = "POST";
$AutoDiscoverRequest.Credentials = $creds;
$RequestStream = $AutoDiscoverRequest.GetRequestStream();
$RequestStream.Write($bytes, 0, $bytes.Length);
$RequestStream.Close();
$AutoDiscoverRequest.AllowAutoRedirect = $truee;
$Response = $AutoDiscoverRequest.GetResponse().GetResponseStream()
$sr = New-Object System.IO.StreamReader($Response)
[XML]$xmlReposne = $sr.ReadToEnd()
if($xmlReposne.Autodiscover.Response.User.AutoDiscoverSMTPAddress -ne$null)
{
write-host ("Public Folder Content Routing Information Header : " + $xmlReposne.Autodiscover.Response.User.AutoDiscoverSMTPAddress)
$service.HttpHeaders["X-AnchorMailbox"] = $xmlReposne.Autodiscover.Response.User.AutoDiscoverSMTPAddress
$service.HttpHeaders["X-PublicFolderMailbox"] = $xmlReposne.Autodiscover.Response.User.AutoDiscoverSMTPAddress
}

I've put a full copy of this enumerate script up on git hub https://github.com/gscales/Powershell-Scripts/blob/master/EnumerateItemsInPublicFolder.ps1

For one example of what you can do with it if we take the sample from the last post on ItemAge we can create an ItemAge script for public folder items. eg

https://github.com/gscales/Powershell-Scripts/blob/master/PublicFolderItemAge.ps1




EWS Basics : Create a Folder in a Mailbox or Public Folder

$
0
0
One of the more common questions on EWS I get is around how to create a folder so this post will cover the basics you need to know around creating folders using EWS in Mailboxes or Public Folders.

What you need to create a Folder

To create a folder you first need a unique name for the new folder eg if you going to create a folder called "SubFolder1" under the Inbox another folder with the name Subfolder1 can't already exists as a SubFolder of the Inbox (anywhere else in the Mailbox is okay) if it does you will get an error.  So when writing a script to create a folder its first good to includes a search to see if the folder already exists. eg

#Define Folder Veiw Really only want to return one object  
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
#Define a Search folder that is going to do a search based on the DisplayName of the folder
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$NewFolderName)
#Do the Search
$findFolderResults = $service.FindFolders($EWSParentFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -eq 0){
Write-host ("Folder Doesn't Exist")
$NewFolder.Save($EWSParentFolder.Id)
Write-host ("Folder Created")
}
else{
Write-error ("Folder already Exist with that Name")
}



The second piece of information you need is what type of folder your going to create, in Exchange  folders have a default ItemType property (in EWS this the FolderClass in Mapi its PR_CONTAINER_CLASS) . Eg when you create a folder is you specify the Folder Class of this Folder is for Appointment the Client will treat that folder as a Calender Folder. In EWS once this FolderClass is set the service will return a different FolderType for the following Folder

  • Mail Folders - IPF.Note - EWS Managed API Type Folder
  • Calendar Folders - IPF.Appointment - EWS Managed API Type CalendarFolder
  • Contact Folders - IPF.Contact - EWS Managed API Type ContactFolder
  • Task Folders - IPF.Task - EWS Managed API Type TaskFolder
In Exchange and Outlook there are other FolderType eg Notes, Journal etc but these will just be returned as Folder in EWS. Generally apart from CalendarFolder which has different Permissions types the Folders can be treated the same apart from the FolderClass property.

The last piece of information you need when creating a folder is you need know the ewsId of the Parent folder. If your creating a folder within a WellKnown Folder like the Inbox,Calendar,MailboxRoot etc then you can just use the FolderId overload eg for the Inbox

$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)   
$NewFolder.Save($folderid)

If this is not a WellKnownFolder or a PublicFolder then you need to first find the EWSFolderId, for this I generally recommend you great a path for the parent folder like \Inbox\ParentFolder and then use a Function like this to find the FolderId eg

function FolderIdFromPath{
param (
$FolderPath = "$( throw 'Folder Path is a mandatory Parameter' )",
$SmtpAddress = "$( throw 'Folder Path is a mandatory Parameter' )"
)
process{
## Find and Bind to Folder based on Path
#Define the path to search should be seperated with \
#Bind to the MSGFolder Root
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$SmtpAddress)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
#Split the Search path into an array
$fldArray = $FolderPath.Split("\")
#Loop through the Split Array and do a Search for each level of folder
for ($lint = 1;$lint-lt$fldArray.Length;$lint++) {
#Perform search based on the displayname of each folder level
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folderin$findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
"Error Folder Not Found"
$tfTargetFolder = $null
break
}
}
if($tfTargetFolder-ne$null){
return$tfTargetFolder.Id.UniqueId.ToString()
}
else{
throw "Folder not found"
}
}
}

or for Public Folders you need to use something like

function PublicFolderIdFromPath{
param (
[Parameter(Position=0, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service,
[Parameter(Position=1, Mandatory=$true)] [String]$FolderPath,
[Parameter(Position=2, Mandatory=$true)] [String]$SmtpAddress
)
process{
## Find and Bind to Folder based on Path
#Define the path to search should be seperated with \
#Bind to the MSGFolder Root
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$PR_REPLICA_LIST = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6698,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset.Add($PR_REPLICA_LIST)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset)
$PR_REPLICA_LIST_Value = $null
if($tfTargetFolder.TryGetProperty($PR_REPLICA_LIST,[ref]$PR_REPLICA_LIST_Value)){
$GuidAsString = [System.Text.Encoding]::ASCII.GetString($PR_REPLICA_LIST_Value, 0, 36);
$HeaderAddress = new-object System.Net.Mail.MailAddress($service.HttpHeaders["X-AnchorMailbox"])
$pfHeader = $GuidAsString + "@" + $HeaderAddress.Host
write-host ("Root Public Folder Routing Information Header : " + $pfHeader )
$service.HttpHeaders.Add("X-PublicFolderMailbox", $pfHeader)
}
#Split the Search path into an array
$fldArray = $FolderPath.Split("\")
#Loop through the Split Array and do a Search for each level of folder
for ($lint = 1;$lint-lt$fldArray.Length;$lint++) {
#Perform search based on the displayname of each folder level
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$fvFolderView.PropertySet = $psPropset
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folderin$findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
"Error Folder Not Found"
$tfTargetFolder = $null
break
}
}
if($tfTargetFolder-ne$null){
$PR_REPLICA_LIST_Value = $null
if($tfTargetFolder.TryGetProperty($PR_REPLICA_LIST,[ref]$PR_REPLICA_LIST_Value)){
$GuidAsString = [System.Text.Encoding]::ASCII.GetString($PR_REPLICA_LIST_Value, 0, 36);
$HeaderAddress = new-object System.Net.Mail.MailAddress($service.HttpHeaders["X-AnchorMailbox"])
$pfHeader = $GuidAsString + "@" + $HeaderAddress.Host
write-host ("Target Public Folder Routing Information Header : " + $pfHeader )
Get-PublicFolderContentRoutingHeader -service $service -Credentials $Credentials -MailboxName $SmtpAddress -pfAddress $pfHeader
}
return$tfTargetFolder.Id.UniqueId.ToString()
}
else{
throw "Folder not found"
}
}
}

I have a module that combines all these that I've blogged before herehttps://github.com/gscales/Powershell-Scripts/blob/master/CreateFolder.ps1 I've added a new cmdlet in this module to now allow you to create folder within a public folder eg

Create-PublicFolder -Mailboxname mailbox@domain.com -NewFolderName test -ParentPublicFolderPath '\Folder1\Folder2'

Showing the client that was used to create or update an Appointment in Exchange 2013/16 using EWS

$
0
0
Previously I posted an example on Mining the Client information on Sent Messages in EWS to report on what client is being used in this post . For Calendar appointments there is a simular but different property that gets set when you either create or update a Appointment eg


(This property is set by the Exchange Store based on what client is being used)

 I've put together a script that aggregates this property and produces a summary report like


Its also produces an export report of all the appointments that looks like the following so you can see at the appointment level what client is being used.


I've put this script up on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/ShowCalendarClients.ps1

EWS Basics: Message Flagging and Marking Messages Read/Unread

$
0
0
Messages that are stored in Exchange can be flagged in a number of ways to indicate different states to the user or as an effective method to prompt the user to action (or make them remember to do something).

Unread/Read Flags

 The most commonly used Flag in Exchange is the Unread/Read Message flag which is used so when a message arrives in Mailbox a user can track which messages they have read. In EWS this flag is surfaced in the API in the form of a Strongly type property isRead  https://msdn.microsoft.com/en-us/library/office/bb408987(v=exchg.150).aspx . (The underlying Store property that back this is PidTagMessageFlags property https://msdn.microsoft.com/en-us/library/ee160304(v=exchg.80).aspx  which is a bitwise flag representing many states of a message).

The other place the Unread Message flag is surfaced is on the Folder object in the guise of the Unread Item count. So in the below example we Bind to Inbox folder and we can look at the Total Number of Items in a Folder and the Total number of those that are unread

$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
Write-Host ("Total Message Count : " + $Inbox.TotalCount)
Write-Host ("Total Unread Message Count : " +$Inbox.UnreadCount)


Marking all Messages in a Folder Read

One of the new features added to EWS in 2013 was an operation that allows you to mark all messages as read in a folder. Also with the other important feature of being able to suppress Read Receipts when you do so. Eg the following is a function that will Mark all the Messages in a Folder as Read and Suppress Read Recipients.

$folderId = FolderIdFromPath -FolderPath $FolderPath -SmtpAddress $MailboxName
if($folderId-ne$null){
$Folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderId)
Write-Host ("Total Message Count : " + $Folder.TotalCount)
Write-Host ("Total Unread Message Count : " +$Folder.UnreadCount)
Write-Host ("Marking all messags a unread in folder")
$Folder.MarkAllItemsAsRead($true)
}

Marking just one message as Read

To mark just the current message your enumerating in a function as Read as you need to do is use the isRead Strongly typed property eg the mark the last message received in a Mailbox read

$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)
$fiItems = $service.FindItems($Inbox.Id,$ivItemView)
if($fiItems.Items.Count -eq 1)
{
$fiItems.Items[0].isRead = $true
$fiItems.Items[0].Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AutoResolve);
}

All these example are in the following GitHub Script https://github.com/gscales/Powershell-Scripts/blob/master/unReadModule.ps1

Flagging a Message for Follow-up

Flagging a Message for follow-up is a common task you might do it to remind you that a message needs some other actions on it after you have read it (eg you need to phone the sender etc). In 2013 some strongly type properties where added to help read and set the follow-up flags. However these don't cover all the possible flag properties (listed here) you might want to set when setting a followup in particular the followup Text (eg the custom option in Outlook). To cater for this you can use a combination of Extended property's and the strongly typed properties. Eg To flag the last message with a follow-up to call the sender in 1 hour you can use the following

$FlagRequest = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common, 0x8530,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
$PR_FLAG_ICON = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x1095,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)
$fiItems = $service.FindItems($Inbox.Id,$ivItemView)
if($fiItems.Items.Count -eq 1)
{
$fiItems.Items[0].Flag.FlagStatus = [Microsoft.Exchange.WebServices.Data.ItemFlagStatus]::Flagged;
$fiItems.Items[0].Flag.DueDate = $DueDate
$fiItems.Items[0].Flag.StartDate = (Get-Date)

$fiItems.Items[0].SetExtendedProperty($FlagRequest,$FollowupText)
$fiItems.Items[0].SetExtendedProperty($PR_FLAG_ICON,$Color)
$fiItems.Items[0].Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AutoResolve);
}

This example is covered in the following GitHub Script https://github.com/gscales/Powershell-Scripts/blob/master/flaggfollowup.ps1

Sender Flags are a more advanced Outlook feature that allows you to transmit a specific flag to a recipient with the message. There is no support in EWS for setting this type of flag but I have another workaround solution I've published before for this http://gsexdev.blogspot.com.au/2013/12/using-sender-flags-in-ews.html

Using the ExcludesBitmask SearchFiilter \ Restriction in EWS

$
0
0
Complex properties are one of the more challenging elements when it comes to developing with Exchange and bitwise properties are one of the more challenging property types you may need to deal with. The most common bitwise property in Exchange you may access/use is the PR_MessageFlags property https://msdn.microsoft.com/en-us/library/ee160304(v=exchg.80).aspx . If you look at this property on a Message that you received you might see something like


What the  ExcludesBitmask SearchFilter/Restriction in EWS allows you to do is create a search where you can specifically exclude messages because they have one of these bitmask values set. Eg you might want to exclude Draft messages from your search or maybe you want to exclude any messages that where SentBy you in the Inbox. You can also use this Search Filter with the Not filter which Negates the restriction so if I created a Searchfilter to excluded all messages with the MSGFlag_HasAttached then negated that with the Not filter I would then only get the message where the MSGFlag_HasAttached bit-wise is set.

Using this the EWS Managed API is pretty simple you first define the property you want to search on and then create a SearchFilter using this property and the Bitwise Value as the variable eg

ExtendedPropertyDefinition PR_MESSAGE_FLAGS = new ExtendedPropertyDefinition(0x0E07,MapiPropertyType.Integer);
SearchFilter ExcludeDraft = new SearchFilter.ExcludesBitmask(PR_MESSAGE_FLAGS, 8);

This creates a SearchFilter that excludes draft messages, If you want to Negate that you can use

SearchFilter JustDrafts = new SearchFilter.Not(ExcludeDraft);

Which would then create a filter that just retrieved draft messages.

In PowerShell you can write a function like the following

function Query-MessageFlag  {
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$false)] [string]$url,
[Parameter(Position=4, Mandatory=$true)] [Int32]$Flag,
[Parameter(Position=5, Mandatory=$true)] [string]$FolderPath,
[Parameter(Position=6, Mandatory=$false)] [switch]$Negate,
[Parameter(Position=7, Mandatory=$false)] [switch]$SummaryOnly
)
Begin
{
if($url){
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials -url $url
}
else{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
}
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$folderId = FolderIdFromPath -FolderPath $FolderPath -SmtpAddress $MailboxName
if($folderId-ne$null){
$Folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderId)
Write-Host ("Folder Name : " + $Folder.DisplayName)
$PR_MESSAGE_FLAGS = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E07,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+ExcludesBitmask($PR_MESSAGE_FLAGS, $Flag)
if($Negate.IsPresent){
$sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+Not($sfItemSearchFilter)
}
if(!$SummaryOnly.IsPresent){
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$fiItems = $null
do{
$fiItems = $service.FindItems($Folder.Id,$sfItemSearchFilter,$ivItemView)
#[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Itemin$fiItems.Items){
Write-Host($Item.DateTimeReceived.ToString() + " : " + $Item.Subject)
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq$true)
}
else
{
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)
$fiItems = $service.FindItems($Folder.Id,$sfItemSearchFilter,$ivItemView)
Write-Host ("Total Items : " + $fiItems.TotalCount)
}
}
}
}

The above function comes from the following script on Github https://github.com/gscales/Powershell-Scripts/blob/master/ExcludeQuery.ps1 demonstrates how to either get Items or a Summary count of those Items based on a bitwise flag exclusion or a negation of that exclusion. Again some examples may be in order

Example 1 Search for messages in the Inbox not sent from you using the mfFromMe bitwise flag

Query-MessageFlag -MailboxName user@domain.com -FolderPath '\Inbox' -Flag 32 -SummaryOnly

Example 2 Search for messages in the Inbox  sent from you using the mfFromMe bitwise flag (this is using a Negate filter)

Query-MessageFlag -MailboxName user@domain.com -FolderPath '\Inbox' -Flag 32 -SummaryOnly -Negate

Example 3 Search for messages with the Attach Flag set in the Inbox mfHasAttach

Query-MessageFlag -MailboxName user@domain.com -FolderPath '\Inbox' -Flag 16 -SummaryOnly -Negate

Example 4 Search for message with Read Recipient flag mfNotifyRead and Negate that

Query-MessageFlag -MailboxName user@domain.com -FolderPath '\Inbox' -Flag 256  -SummaryOnly -Negate

EWS Basics : Sending Messages using EWS

$
0
0
One of the most basic things you might want to do with EWS is Send a Message, while its may sound easy sending messages programmatically can be at times confusing.

But first lets take one step back for a second from EWS and look at the different ways you could send Email programmatically on Exchange.

SMTP : (Simple Mail Transfer Protocol) SMTP is the backbone protocol for Email and is how most Email is transferred between servers on the Internet. Its also the protocol POP and IMAP clients use to send email. Because SMTP is a protocol (meaning its a set of rules defined in a RFC) rather then an API to use it you need to use a library that will give you some objects that you can code against that will then generated the necessary communication that follows the protocol rules. Some example of these are CDOSys, and System.NET.Mail. From an Exchange technical point of view when you submit a message via SMTP your submitting it directly into the Transport Pipeline so it doesn't going through Mailbox Store.

Pickup and Replay Directories : These are special directories on the Transport server that you can place Messages into https://technet.microsoft.com/en-us/library/bb124230(v=exchg.150).aspx typically used by specialized applications like foreign connectors https://technet.microsoft.com/en-us/library/aa996779(v=exchg.150).aspx

Mailbox API's : These are the specific API's that Microsoft has made available to access a Mailbox which are MAPI, EWS Managed API and for Office365 the new REST api. (ActiveSync could also be used but is a more specialized protocol) . When you send a Message via one of these Mailbox API's you first have to talk to the Mailbox role server (via the CAS ) and the message is submitted to Exchange Store which will then send it through into the transport pipeline. As part of that process you may choose to save a copy of the message your sending into the sentItems folder of the user who is sending the message. As you are sending via the Store you can also assert the SendOnBehalf rights if you send a message on behalf of a delegate.

EWS : To Send a Message using EWS you use the SendItem operation https://msdn.microsoft.com/en-us/library/office/aa580238(v=exchg.150).aspx you can also use the CreateItem Operation and set the SendDisposition to Send or SendAndSaveCopy. Depending on if your sending a message with Attachments you may need to make multiple requests to the server to create a draft message and then add attachments. In the EWS Managed API this complexity is implemented in the API so you don't have to worry about it. eg the following is a basic function for sending a message using EWS in PowerShell.

functionSend-EWSMessage  {
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$false)] [string]$url,
[Parameter(Position=6, Mandatory=$true)] [String]$To,
[Parameter(Position=7, Mandatory=$true)] [String]$Subject,
[Parameter(Position=8, Mandatory=$true)] [String]$Body,
[Parameter(Position=9, Mandatory=$false)] [String]$Attachment
)
Begin
{
if($url){
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials -url $url
}
else{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
}
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems,$MailboxName)
$SentItems = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service
$EmailMessage.Subject = $Subject
#Add Recipients
$EmailMessage.ToRecipients.Add($To)
$EmailMessage.Body = New-Object Microsoft.Exchange.WebServices.Data.MessageBody
$EmailMessage.Body.BodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::HTML
$EmailMessage.Body.Text = "Body"
$EmailMessage.From = $MailboxName
if($Attachment)
{
$EmailMessage.Attachments.AddFileAttachment($Attachment)
}
$EmailMessage.SendAndSaveCopy($SentItems.Id)

}
}

One other way of sending a Message in EWS is you can use the MIMEContent that maybe generated by another library or maybe a message that was exported or saved eg

functionSend-MimeMessage {
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$false)] [String]$MimeMessage
)
Begin
{
if($url){
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials -url $url
}
else{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
}
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems,$MailboxName)
$EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service
[byte[]]$bdBinaryData1 = [System.IO.File]::ReadAllBytes($MimeMessage)
$EmailMessage.MimeContent = new-object Microsoft.Exchange.WebServices.Data.MimeContent("UTF-8", $bdBinaryData1)
$EmailMessage.SendAndSaveCopy($SentItems.Id)
}

}

Both of these functions come from the following github script https://github.com/gscales/Powershell-Scripts/blob/master/EWSSend.ps1

Sending Options : To Send a Message as another user (eg other then the current user being used to authenticate) you need to have been granted the SendAs permission on a Mailbox (which is a separate permission from the Mailbox rights granted via Add-MailboxPermission). The other option in EWS is that you can use EWS impersonation which would give the caller the same rights as the Mailbox owner,  the script I've posted on GitHub has an option to use EWS Impersonation.

Both of these example functions save a copy of the message being sent into the SentItemFolder of the Mailbox passed into the Function. This is controlled via the

$EmailMessage.SendAndSaveCopy($SentItems.Id)

line if you didn't want to save a copy to the SentItem folder you can use the Send Method instead eg

$EmailMessage.Send()

This will set the necessary MessageDispostion value in the SOAP request.

EWS only allows you to send using one body format eg you need to choose between HTML or Text if you do want to use the more advanced body types allowed in MIME look to use the MIME Content option to send a message. There is also an example of sending an encrypted message using the MIME content  https://blogs.msdn.microsoft.com/emeamsgdev/2015/08/10/ews-how-to-send-signed-email-using-the-ews-managed-api/

Using VBA or VB6 : If your still enjoying the retro Programing languages like VBA or VB6 its still possible to use EWS to send a message from your code by manually constructing the SOAP message involved. This maybe helpfully where your enviorment has been migrated to the cloud and your CDOsys code that was using  SMTP to send messages no longer works. As a work around you can use a simple function like this to do a Send using EWS in Office365 (or change the URL to you OnPrem EWS endpoint).

SubSendMessage(Subject AsString, Recipient AsString, Body AsString, User AsString, Password AsString)
Dim sReq AsString
Dim xmlMethod AsString
Dim XMLreq AsNew MSXML2.XMLHTTP60
Dim EWSEndPoint AsString
EWSEndPoint ="https://outlook.office365.com/EWS/Exchange.asmx"
sReq ="<?xml version=""1.0"" encoding=""UTF-8""?>"& vbCrLf
sReq = sReq &"<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:t=""http://schemas.microsoft.com/exchange/services/2006/types"">"& vbCrLf
sReq = sReq &"<soap:Header>"& vbCrLf
sReq = sReq &"<t:RequestServerVersion Version=""Exchange2010""/>"& vbCrLf
sReq = sReq &"</soap:Header>"& vbCrLf
sReq = sReq &"<soap:Body>"& vbCrLf
sReq = sReq &"<CreateItem MessageDisposition=""SendAndSaveCopy"" xmlns=""http://schemas.microsoft.com/exchange/services/2006/messages"">"& vbCrLf
sReq = sReq &"<SavedItemFolderId>"& vbCrLf
sReq = sReq &"<t:DistinguishedFolderId Id=""sentitems"" />"& vbCrLf
sReq = sReq &"</SavedItemFolderId>"& vbCrLf
sReq = sReq &"<Items>"& vbCrLf
sReq = sReq &"<t:Message>"& vbCrLf
sReq = sReq &"<t:ItemClass>IPM.Note</t:ItemClass>"& vbCrLf
sReq = sReq &"<t:Subject>"& Subject &"</t:Subject>"& vbCrLf
sReq = sReq &"<t:Body BodyType=""Text"">"& Body &"</t:Body>"& vbCrLf
sReq = sReq &"<t:ToRecipients>"& vbCrLf
sReq = sReq &"<t:Mailbox>"& vbCrLf
sReq = sReq &"<t:EmailAddress>"& Recipient &"</t:EmailAddress>"& vbCrLf
sReq = sReq &"</t:Mailbox>"& vbCrLf
sReq = sReq &"</t:ToRecipients>"& vbCrLf
sReq = sReq &"</t:Message>"& vbCrLf
sReq = sReq &"</Items>"& vbCrLf
sReq = sReq &"</CreateItem>"& vbCrLf
sReq = sReq &"</soap:Body>"& vbCrLf
sReq = sReq &"</soap:Envelope>"& vbCrLf
xmlMethod ="POST"
XMLreq.Open xmlMethod, EWSEndPoint, False, User, Password
XMLreq.setRequestHeader "Content-Type", "text/xml; charset=""UTF-8"""
XMLreq.setRequestHeader "Translate", "F"
XMLreq.setRequestHeader "User-Agent", "VBAEWSSender"
XMLreq.send sReq
If XMLreq.Status =200Then
' Message Sent okay
Else
' Something went Wrong
EndIf
EndSub

Sending a Message via EWS from an Arduino MKR1000

$
0
0
The Internet of Things is all the rage these days and one of the most popular prototyping platform for any electronic projects these days is the Arduino. The new MKR1000 Arduino board combines the functionality from the Zero board with a WifiSheild on a compact board and pretty cheap $35. It looks like




These boards are pretty cool and take you back in some ways to 80's with some of the programming challenges that come with memory restriction devices. Eg the MRK1000 has 32Kb of SRAM which is half that of Commodore 64 although it runs at 48Mhz vs the 1.33 Mhz the Commodore could manage (I know this isn't a fair technical comparison just trying provide a bit of contrast).

The nice thing with this board also is because it implements a cryptoauthentication chip it can do SSL communication which can be problem with low power boards because of the amount of processing power required for SSL. So at the bare bones this board can run code and logic that will allow HTTPS communication.

The following is a simple how to for sending an email from an Exchange Mailbox using EWS in Office365 from one of these devices using the following two libraries

https://github.com/arduino-libraries/ArduinoHttpClient  (used for the HTTPS communication)

https://github.com/agdl/Base64 (used to create the Base64 Authentication header)

Preparation

Before you can use the board to talk to Office365 you need to load the SSL certificates required for SSL communication. This may sound a little weird to most people as this process you would never have to worry about with most coding you would do. However because of the limited power/size of this unit it can only hold 10 certificates for WebSites you want to talk to and it has no ability to load them dynamically so you first need to copy the cert to the device. To do this you need to use the following sketch https://github.com/arduino-libraries/WiFi101-FirmwareUpdater and then use the upload certificates to Wifi in the Wifi101 firmware update which is in the Arduino IDE https://www.arduino.cc/en/Main/Software (eg just add the root certificate for outlook.office365.com see below)


To use the example sketch I've created you need to install the following Libraries in the Arduino IDE

https://github.com/arduino-libraries/ArduinoHttpClient  (used for the HTTPS communication)

https://github.com/agdl/Base64 (used to create the Base64 Authentication header)

Once you do that you ready to use the following Sketch the code is relativity simple, with EWS on Office365 it supports Basic and Oauth authentication as this is just for prototyping I've gone with basic auth which means you just need a Base64 library to do the encoding of the authentication header.

The code use the Wifi Sketch as a base which allows you to specify the SSID and password to use for your wifi in the following two variables

char ssid[] ="ssid"; // your network SSID (name)
char pass[] ="password";

then when you upload the sketch and connect to the serial port on the MRK1000 it will try to connect to the WIFI and then run the SendMessageEWS function.

I have the following variables in the sketch to hold the Office365 username and password and data for the message Subject, Body and To Recipients

//Office365 Credentials
String ExUserName ="user@domaiin.onmicrosoft.com";
String ExPassword ="passwprd";

//Message Details
String Auth = ExUserName +":"+ ExPassword;
String Subject ="Subject of the Message";
String To ="user@domain.com";
String Body ="Something happening in the Body";


The SendMessageEWS function first creates a variable that contains the EWS SOAP Message to send. (At this point you need to be careful of how big this string could get). The rest of the code makes a connection to the Office365 Service using the Arduino http client library that makes use of that Crypo chip for SSL. It then starts sending the headers necessary for the EWS POST and submits the POST String as the payload. I don't have anything written to parse the response but the raw output is witten back to the serial port so you can see if it was successfully or if something went wrong. I've put a copy of the Sketch here on GitHub https://github.com/gscales/Arduino-MRK1000/blob/master/EWS-Office365SendSample.ino the code looks like

#include <Base64.h>
#include <ArduinoHttpClient.h>

/*
This example creates a connection to Office365 and Send an Email
via SOAP

Uses code from the following examples

https://www.arduino.cc/en/Tutorial/WiFiWebClient


*/

#include <SPI.h>
#include <WiFi101.h>

char ssid[] ="ssid"; // your network SSID (name)
char pass[] ="password"; // your network password (use for WPA, or use as key for WEP)
int keyIndex =0; // your network key Index number (needed only for WEP)

//Office365 Credentials
String ExUserName ="user@domaiin.onmicrosoft.com";
String ExPassword ="passwprd";

//Message Details
String Auth = ExUserName +":"+ ExPassword;
String Subject ="Subject of the Message";
String To ="user@domain.com";
String Body ="Something happening in the Body";



int cCount =0;

int status = WL_IDLE_STATUS;
WiFiSSLClient client;

voidsetup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}

// check for the presence of the shield:
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println("WiFi shield not present");
// don't continue:
while (true);
}

// attempt to connect to Wifi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);

// wait 10 seconds for connection:
delay(10000);
}
Serial.println("Connected to wifi");
printWifiStatus();
SendMessageEWS(Auth,To,Subject,Body);

}


voidSendMessageEWS(String AuthString,String MessageTo, String MessageSubject, String MessageBody)
{
//Auth Header
char basicHeader[AuthString.length()+1];
AuthString.toCharArray(basicHeader,AuthString.length()+1);
int inputStringLength =sizeof(basicHeader);
int encodedLength = Base64.encodedLength(inputStringLength);
char encodedString[encodedLength];
Base64.encode(encodedString, basicHeader, inputStringLength);
String AuthHeader ="Basic ";

//EWS SOAP Request
String content ="<?xml version=\"1.0\" encoding=\"utf-8\"?>";
content +="<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">";
content +="<soap:Header>";
content +="<t:RequestServerVersion Version=\"Exchange2013_SP1\" />";
content +="</soap:Header>";
content +="<soap:Body>";
content +="<m:CreateItem MessageDisposition=\"SendAndSaveCopy\">";
content +="<m:SavedItemFolderId>";
content +="<t:DistinguishedFolderId Id=\"sentitems\" />";
content +="</m:SavedItemFolderId>";
content +="<m:Items>";
content +="<t:Message>";
content +="<t:Subject>"+ MessageSubject +"</t:Subject>";
content +="<t:Body BodyType=\"HTML\">"+ MessageBody +"</t:Body>";
content +="<t:ToRecipients>";
content +="<t:Mailbox>";
content +="<t:EmailAddress>"+ MessageTo +"</t:EmailAddress>";
content +="</t:Mailbox>";
content +="</t:ToRecipients>";
content +="</t:Message>";
content +="</m:Items>";
content +="</m:CreateItem>";
content +="</soap:Body>";
content +="</soap:Envelope>";


Serial.println("\nStarting connection to server...");
// if you get a connection, report back via serial:
if (client.connectSSL("outlook.office365.com", 443)) {
Serial.println("connected to server");
client.print("POST ");
client.print("https://outlook.office365.com/EWS/Exchange.asmx");
client.println(" HTTP/1.1");
client.print("Host: ");
client.println("outlook.office365.com");
client.print("Authorization: Basic ");
client.println(encodedString);
client.println("Connection: close");
client.print("Content-Type: ");
client.println("text/xml");
client.println("User-Agent: mrk1000Sender");
client.print("Content-Length: ");
client.println(content.length());
client.println();
client.println(content);
}

}

voidloop() {
// if there are incoming bytes available
// from the server, read them and print them:
while (client.available()) {
char c = client.read();
Serial.print(c);
}

// if the server's disconnected, stop the client:
if (!client.connected()) {
Serial.println();
Serial.println("disconnecting from server.");
client.stop();
// do nothing forevermore:
while (true);
}
}


voidprintWifiStatus() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());

// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);

// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}

Sending a Message in Exchange Online via REST from an Arduino MKR1000

$
0
0
This is part 2 of my MKR1000 article, in this previous post I looked at sending a Message via EWS using Basic Authentication.  In this Post I'll look at using the new Outlook REST API which requires using OAuth authentication to get an Access Token.

The prerequisites for this sketch are the same as in the other post with the addition of the ArduinoJson library https://github.com/bblanchon/ArduinoJson which is used to parse the Authentication Results to extract the Access Token. Also the SSL certificates for the login.windows.net  and outlook.office365.com need to be uploaded to the devices using the wifi101 Firmware updater.

To use Token Authentication you need to register an Application in Azure https://msdn.microsoft.com/en-us/office/office365/howto/add-common-consent-manually with the Mail.Send permission. The application should be a Native Client app that use the Out of Band Callback urn:ietf:wg:oauth:2.0:oob. You need to authorize it in you tenant (eg build a small app that can do that which will prompt for authorization). One that is done you then need to set that ClientId variable in the sketch

String ClientId = "8fe353d6-efa0-4b0f-aafb-ab7cf3a9b307";

I've put a copy of this Sketch up https://github.com/gscales/Arduino-MRK1000/blob/master/REST-Office365SendSample.ino the code looks like


#include <ArduinoJson.h>

#include <Base64.h>
#include <ArduinoHttpClient.h>

/*
This example creates a client object that connects and transfers
data using always SSL.

It is compatible with the methods normally related to plain
connections, like client.connect(host, port).

Written by Arturo Guadalupi
last revision November 2015

*/

#include <SPI.h>
#include <WiFi101.h>

char ssid[] ="SSOecure"; // your network SSID (name)
char pass[] ="pass@#"; // your network password (use for WPA, or use as key for WEP)
int keyIndex =0; // your network key Index number (needed only for WEP)
constsize_t MAX_CONTENT_SIZE =5120;

//Office365 Credentials
String ExUserName ="user@domain.com";
String ExPassword ="passw";
String ClientId ="8fe353d6-efa0-4b0f-aafb-ab7cf3a9b307";
//Message Details
String Auth = ExUserName +":"+ ExPassword;
String Subject ="Subject of the Message";
String To ="mailbox@domain.com";
String Body ="Something happening in the Body";
String Access_Token ="";

bool DebugResponse =false;

int cCount =0;

int status = WL_IDLE_STATUS;
// if you don't want to use DNS (and reduce your sketch size)
// use the numeric IP instead of the name for the server:

// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
WiFiSSLClient client;

voidsetup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}

// check for the presence of the shield:
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println("WiFi shield not present");
// don't continue:
while (true);
}

// attempt to connect to Wifi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);

// wait 10 seconds for connection:
delay(10000);
}
Serial.println("Connected to wifi");
printWifiStatus();
TokenAuth(ClientId,ExUserName,ExPassword);
if(Access_Token.length() >0){
Serial.println("Send Message");
SendRest(Access_Token,To,Subject,Body);
Serial.println("Done");
}

}

voidTokenAuth(String ClientId,String UserName, String Password)
{
char endOfHeaders[] ="\r\n\r\n";
char passwordCA[Password.length()+1];
Password.toCharArray(passwordCA,Password.length()+1);
String content ="resource=https%3A%2F%2Foutlook.office.com&client_id="+ ClientId +"&grant_type=password&username="+ ExUserName +"&password="+ URLEncode(passwordCA) +"&scope=openid";
Serial.println("\nStarting connection to server...");
if (client.connectSSL("login.windows.net", 443)) {
Serial.println("connected to server");
client.print("POST ");
client.println("https://login.windows.net/Common/oauth2/token HTTP/1.1");
client.println("Content-Type: application/x-www-form-urlencoded");
client.println("client-request-id: "+ ClientId);
client.println("return-client-request-id: true");
client.println("x-client-CPU: x32");
client.println("x-client-OS: Arduino");
client.println("Host: login.windows.net");
client.print("Content-Length: ");
client.println(content.length());
client.println("Expect: 100-continue");
client.println("");
client.println(content);
client.find(endOfHeaders);
bool ok = client.find(endOfHeaders);
if (!ok) {
Serial.println("No response or invalid response!");
}
else{
Serial.println("Request Okay");
}
char response[MAX_CONTENT_SIZE];
readAuthReponse(response, sizeof(response));
}
}

voidreadAuthReponse(char* content, size_t maxSize) {
size_t length = client.readBytes(content, maxSize);
content[length] =0;
Serial.println(content);
content[length] =0;
DynamicJsonBuffer jsonBuffer;
JsonObject&root = jsonBuffer.parseObject(content);
if (!root.success()) {
Serial.println("JSON parsing failed!");
}
else{
String token = root["access_token"];
Access_Token = token;
}

}


String URLEncode(constchar* msg)
{
constchar*hex ="0123456789abcdef";
String encodedMsg ="";

while (*msg!='\0'){
if( ('a'<=*msg &&*msg <='z')
|| ('A'<=*msg &&*msg <='Z')
|| ('0'<=*msg &&*msg <='9') ) {
encodedMsg +=*msg;
} else {
encodedMsg +='%';
encodedMsg += hex[*msg >>4];
encodedMsg += hex[*msg &15];
}
msg++;
}
return encodedMsg;
}


voidSendRest(String Bearer,String MessageTo, String MessageSubject, String MessageBody)
{
//DebugResponse = true;
char endOfHeaders[] ="\r\n\r\n";
String content ="{";
content +="\"Message\": {";
content +="\"Subject\": \""+ MessageSubject +"\",";
content +="\"Body\": {";
content +="\"ContentType\": \"Text\",";
content +="\"Content\": \""+ MessageBody +"\"";
content +=" },";
content +="\"ToRecipients\": [";
content +=" {";
content +="\"EmailAddress\": {";
content +="\"Address\": \""+ MessageTo +"\"";
content +=" }";
content +=" }";
content +=" ]";
content +=" },";
content +="\"SaveToSentItems\": \"false\"";
content +=" }";
Serial.println("\nStarting connection to server...");
// if you get a connection, report back via serial:
if (client.connectSSL("outlook.office365.com", 443)) {
Serial.println("connected to server");
client.print("POST ");
client.print("https://outlook.office365.com/api/v2.0/me/sendmail");
client.println(" HTTP/1.1");
client.print("Host: ");
client.println("outlook.office365.com");
client.print("Authorization: Bearer ");
client.println(Bearer);
client.println("Connection: close");
client.print("Content-Type: ");
client.println("application/json");
client.println("User-Agent: mrk1000Sender");
client.print("Content-Length: ");
client.println(content.length());
client.println();
client.println(content);
char okayString[] ="HTTP/1.1 202 Accepted";
bool ok = client.find(okayString);
if (!ok) {
Serial.println("Message Sent");
}
else{
Serial.println("Request Failed");
}
}

}

voidloop() {
// if there are incoming bytes available
// from the server, read them and print them:
if(DebugResponse){
while (client.available()) {
char c = client.read();
Serial.print(c);
}
}

// if the server's disconnected, stop the client:
if (!client.connected()) {
Serial.println();
Serial.println("disconnecting from server.");
client.stop();
// do nothing forevermore:
while (true);
}
}


voidprintWifiStatus() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());

// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);

// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}


How to Like\Unlike an Item using EWS in Exchange Online

$
0
0
Likes and Mentions are a new feature in Exchange Online (in OWA) that was introduced late last year in First Release for Office365. With the focused Inbox now being rolled out to replace clutter these are some of the new social user curation type features that could change the user experience (hopefully for the better) in the coming years. While none of these features are new to those people using other Social platforms like facebook, twitter etc they do offer a world of new possibilities to those that have a little imagination.

In this post I'm going to look at how you can Like an item using Exchange Web Services eg

Currently there is no real documentation on the use of Likes in any API or how they are delivered in Exchange Online so care should be taken as this may mean the feature is subject to change in any of the future service updates.

Versioning your Requests

To use likes fully you need to make sure you version your EWS requests (which involves setting the ServerRequestVersion in the SOAP header) to V2015_10_05 or higher.  The Like information is returned by Exchange as a Strongly Type property in EWS (LikeType). If you look at a Response that includes the Like information in the SOAP response you should see both Like and LikePreview returned eg

<t:Likes>
<t:Like>
<t:Id>150bb06c-1c9a-4ac2-8b55-8cf15854b555</t:Id>
<t:CreatedBy>
<t:Name>Glen Scales</t:Name>
<t:EmailAddress>gscales@datarumble.com</t:EmailAddress>
<t:ExternalObjectId>150bb06c-1c9a-4ac2-8b55-8cf15854b555</t:ExternalObjectId>
</t:CreatedBy>
<t:CreatedDateTime>2016-08-28T10:26:40.299Z</t:CreatedDateTime>
<t:ServerCreatedDateTime>2016-08-28T10:26:40.299Z</t:ServerCreatedDateTime>
</t:Like>
</t:Likes>
<t:LikesPreview>
<t:LikeCount>1</t:LikeCount>
<t:IsLiked>true</t:IsLiked>
<t:Likers>
<t:Name>Glen Scales</t:Name>
<t:EmailAddress>gscales@datarumble.com</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
<t:MailboxType>Mailbox</t:MailboxType>
<t:ExternalObjectId>150bb06c-1c9a-4ac2-8b55-8cf15854b555</t:ExternalObjectId>
</t:Likers>
</t:LikesPreview>
<t:AtAllMention>false</t:AtAllMention>

If you have the latest proxy objects from the Exchange Online WSDL then you should see the Likes and LikePreview property collections in the ItemType Class. In the latest EWS Managed API from github https://github.com/OfficeDev/ews-managed-api only the Likes class is currently available.

Liking an Item (Unsupported)

The EWS LikeItem operation currently has no definition in the Services.WSDL so liking an Item via EWS is currently unsupported. However you can still use the operation as long as you construct the request using Raw SOAP message eg a request to like or unlike and item using EWS look like

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<soap:Header>
<t:RequestServerVersion Version="V2015_10_05"/>
</soap:Header>
<soap:Body xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
<LikeItem>
<ItemId Id="AAM.." ChangeKey="CQA.."/>
<IsUnlike>false</IsUnlike>
</LikeItem>
</soap:Body>
</soap:Envelope>

As this is an unsupported operation if you do something like try to like the same item twice you will get a 500 error rather then a nice SOAP based error response. I've put together a scrip that allows you to search for an Item via subject and like or unlike it using EWS. I've put this up on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/EWSLIkeMessage.ps1

Search for Credit Card numbers in Address Book\Contact data using EWS

$
0
0
Information security and data breaches are a hot topic at the moment, there seems to be a constant stream of data breaches and vulnerabilities in different products being exploited on a daily basis. One topic that was brought up in the last few weeks has been Address Book data https://www.wired.com/2016/09/people-please-dont-store-private-data-address-book/ . Address books can be the proverbial open window on the house with bars on door and maybe not something that is commonly thought about. 

If you want to detect if people are using Address book to store confidential information it can be a challenge because this data isn't searchable via a conventional eDiscovery type search. But this is where a scripted enumeration and filtering approach can do the job.

I posted a Contacts Powershell module that consolidated a lot of EWS contacts function into one script last year so for this post I've extended this to include a Search that will enumerate all the contacts in a Mailbox's contacts folder and Search for Credit Card Number and Social Security Numbers being stored in any of the Phone number properties and email address properties. The script I've posted does some filtering to separate out the Host part of email address to test so for example if somebody puts the 12345678@fakedomain it will separate out 12345678 to test.

Searching for Credit Card Numbers

To Search for Credit card number you basically need two ingredients, the first is the luhn algorithm which is a Modulus 10 algorithm that will validate if a number sequence is a credit card number. Then you run a number of Regex patterns to determine the type of card and who issued it. The good thing is there are plenty libraries up on GitHub that will  already do this so there is no need to write any code for this. The one I decided to use was https://github.com/gustavofrizzo/CreditCardValidator

Searching for Social Security Numbers (or your own custom RegEx)

To Search for SSI I've used the Google Braintrust Regex of

$SSN_Regex = "^(?!000)([0-6]\d{2}|7([0-6]\d|7[012]))([ -]?)(?!00)\d\d\3(?!0000)\d{4}$"

I've posted up the script for this https://github.com/gscales/Powershell-Scripts/blob/master/EWSContacts/EWSContactFunctions.ps1 I've put a compiled version of the creditcard validation library I used which need to be in the same directory as the module here https://github.com/gscales/Powershell-Scripts/raw/master/EWSContacts/CreditCardValidator.dll

To Run the script you just use something like the following to produce a report of any hits in  a Mailbox. Note because of the Regexs used for the SSI and the fact that phone numbers can easily look like validate credit card numbers this script can produce a large number of false positives.

Search-ContactsForCCNumbers -MailboxName mailbox@domain.com | Export-csv -NoTypeInformation -Path c:\temp\CCrep.csv




EWS Basics Accessing and using Shared mailboxes

$
0
0
One of the most commonly asked and misunderstood things that people starting out using Exchange Web Services get wrong is accessing a Shared Mailbox or a Delegated Mailbox other then that of security principal (another way of saying credentials) you are authenticating with.

Autodiscover

One of the first confusion points is with Autodiscover, for people who aren't that familiar with Exchange its important to understand that all Autodisover does is gives you the endpoint to connect to for Exchange Web Services. Some people confuse using the following line

$service.AutodiscoverUrl("Mailbox@domain.com",{$true})

To mean all future EWS requests will go the mailbox you use here which isn't the case all this will do is return the most optimized endpoint for EWS request for that particular user.

Authentication

By default nobody has access to a Mailbox other then the owner of that mailbox, a common problem that people have is to believe they can use the admin account to access any users Mailbox content .  Access to a Mailbox needs to be granted via
  • Adding the user as a Delegate in Outlook or via the EWS Delegate operations
  • Giving the user full access using Add-MailboxPermission in the Exchange Management Shell
  • Grant EWS Impersonations rights on the Mailbox via the Application Impersonation RBAC role
  • Give Access to particular mailbox folder in Outlook or via Add-MailboxFolderPermssion
Accessing a Shared Mailboxes folder

To Access a Mailbox folder in EWS you need to know the EWSId of the folder, the one exception to this rule are the WellKnownFolders like the Inbox,Contacts,Calendar etc. With these WellKnowFolders you can tell EWS which folder you want in which mailbox without knowing the EWSId of that folder.

Eg to Access the Inbox in a Shared Mailbox you use the FolderId overload to define the folderId you want to access and then bind to that folder


$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName) 
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

If its a user created folder you want to access then this is where some complexity comes in, to access the folder you need to get the EWSId of that folder. The easiest way to do this would be to search for that folder within the target mailbox, however depending on what rights you have that this may or may not be a problem. But if you do have Full access or Impersonation rights to a Mailbox then to access a usercreated folder turn the folder you want to access into a path like \\Inbox\folder1\folder2 and you can then use a function like this


functionGet-FolderFromPath{
param (
[Parameter(Position=0, Mandatory=$true)] [string]$FolderPath,
[Parameter(Position=1, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=2, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service,
[Parameter(Position=3, Mandatory=$false)] [Microsoft.Exchange.WebServices.Data.PropertySet]$PropertySet
)
process{
## Find and Bind to Folder based on Path
#Define the path to search should be seperated with \
#Bind to the MSGFolder Root
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
#Split the Search path into an array
$fldArray = $FolderPath.Split("\")
#Loop through the Split Array and do a Search for each level of folder
for ($lint = 1;$lint-lt$fldArray.Length;$lint++) {
#Perform search based on the displayname of each folder level
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
if(![string]::IsNullOrEmpty($PropertySet)){
$fvFolderView.PropertySet = $PropertySet
}
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folderin$findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
Write-host ("Error Folder Not Found check path and try again")
$tfTargetFolder = $null
break
}
}
if($tfTargetFolder-ne$null){
return[Microsoft.Exchange.WebServices.Data.Folder]$tfTargetFolder
}
else{
throw ("Folder Not found")
}
}
}

Once you have the EWSId of the Folder you can use that in FindItems Operation or another other EWS operation that takes a FolderId to do what you want. For Example

Sending Email As a Shared Mailbox

To send a message as another user you need to first have either SendAS permissions to that Mailbox or Send on Behalf off (the latter will mean the message will be marked as Sent On Behalf)

The first thing in you code you want to do is bind to the SentItems folder of the Mailbox you want to send as eg

$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems,$MailboxName)   
$SentItems = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

Then when you create a message to Send set the From address to the Mailbox you want to SendAs and set the SentItems FolderId to the Target mailbox so a copy of what you will be sending will be saved to the SentItems folder of that mailbox eg


functionSend-EWSMessage  {
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$false)] [string]$url,
[Parameter(Position=6, Mandatory=$true)] [String]$To,
[Parameter(Position=7, Mandatory=$true)] [String]$Subject,
[Parameter(Position=8, Mandatory=$true)] [String]$Body,
[Parameter(Position=9, Mandatory=$false)] [String]$Attachment
)
Begin
{
if($url){
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials -url $url
}
else{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
}
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems,$MailboxName)
$SentItems = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service
$EmailMessage.Subject = $Subject
#Add Recipients
$EmailMessage.ToRecipients.Add($To)
$EmailMessage.Body = New-Object Microsoft.Exchange.WebServices.Data.MessageBody
$EmailMessage.Body.BodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::HTML
$EmailMessage.Body.Text = "Body"
$EmailMessage.From = $MailboxName
if($Attachment)
{
$EmailMessage.Attachments.AddFileAttachment($Attachment)
}
$EmailMessage.SendAndSaveCopy($SentItems.Id)

}
}




Using EWS to upload / set user photos in Exchange Online and 2016

$
0
0
Between Exchange 2013 and 2016 there where few new operations introduced into EWS, one operation that was introduced was the SetUserPhoto operation which pairs with the GetUserPhoto operation that was introduced in Exchange 2013.

What this operation does is allows you to set/upload a high resolution photo for a user to be used in Exchange and Skype for Business in Exchange Online or Exchange 2016. A little bit more about the high ressolution user photo is that when you set this it uploads this as an item in the Non_IPM_Root of the Mailbox (so it is not visible to the user) with a message class of IPM.UserPhoto if you where to look at a Mailbox with a Mapi Editor you can see the object that this creates. eg


If you look at the UserPhoto Object itself you can see the different size formats are stored ready to access in a number of different Binary Mapi properties eg


So what the SetUserPhoto operation does is handles creating this object and all the different photo formats that applications might require.

Currently there isn't anything in the EWS Managed API to take advantage of this new operation so to use this you can either use the WSDL Proxy Objects (generated against Exchange 2016 or Exchange Online) or just raw soap like the following.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<RequestServerVersionVersion="Exchange2016"xmlns="http://schemas.microsoft.com/exchange/services/2006/types"/>
</soap:Header>
<soap:Body>
<SetUserPhotoxmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
<Email>$MailboxName</Email>
<Content>$Content</Content>
<TypeRequested>UserPhoto</TypeRequested>
</SetUserPhoto>
</soap:Body>
</soap:Envelope>

I've put together a Powershell script that use the EWS Managed API to do the discovery and then uses raw soap to do the upload the photo.. I've put this script up on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/Upload-Photo.ps1

To use this script you use the cmdlet like

Set-PhotoEWS -MailboxName mailbox@domain -Photo c:\temp\photo1.jpg

The script itself looks like

functionConnect-Exchange{ 
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [string]$url
)
Begin
{
Load-EWSManagedAPI

## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013

## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)

## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials

#Credentials Option 1 using UPN for the windows Account
#$psCred = Get-Credential
$creds = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
$service.Credentials = $creds
#Credentials Option 2
#service.UseDefaultCredentials = $true
#$service.TraceEnabled = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates

Handle-SSL

## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use

#CAS URL Option 1 Autodiscover
if($url){
$uri=[system.URI]$url
$service.Url = $uri
}
else{
$service.AutodiscoverUrl($MailboxName,{$true})
}
Write-host ("Using CAS Server : " + $Service.url)

#CAS URL Option 2 Hardcoded

#$uri=[system.URI] "https://casservername/ews/exchange.asmx"
#$service.Url = $uri

## Optional section for Exchange Impersonation

#$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
if(!$service.URL){
throw "Error connecting to EWS"
}
else
{
return$service
}
}
}

function Load-EWSManagedAPI{
param(
)
Begin
{
## Load Managed API dll
###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
$EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
if (Test-Path$EWSDLL)
{
Import-Module$EWSDLL
}
else
{
"$(get-date -format yyyyMMddHHmmss):"
"This script requires the EWS Managed API 1.2 or later."
"Please download and install the current version of the EWS Managed API from"
"http://go.microsoft.com/fwlink/?LinkId=255472"
""
"Exiting Script."
#exit
}
}
}

function Handle-SSL{
param(
)
Begin
{
## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null

$TASource=@'
namespace Local.ToolkitExtensions.Net.CertificatePolicy{
public class TrustAll : System.Net.ICertificatePolicy {
public TrustAll() {
}
public bool CheckValidationResult(System.Net.ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Net.WebRequest req, int problem) {
return true;
}
}
}
'@
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly

## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll

## end code from http://poshcode.org/624

}
}


functionSet-PhotoEWS {
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$false)] [string]$url,
[Parameter(Position=4, Mandatory=$true)] [String]$Photo
)
Begin
{

if($url){
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials -url $url
}
else{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
}
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
[Byte[]]$FileContent = [System.IO.File]::ReadAllBytes($Photo);
$Base64Content = [System.Convert]::ToBase64String($FileContent);
$request = Get-SetPhotoRequest -MailboxName $MailboxName -Content $Base64Content
$SetPhotoRequest = [System.Net.HttpWebRequest]::Create($service.url.ToString());
$bytes = [System.Text.Encoding]::UTF8.GetBytes($request);
$SetPhotoRequest.ContentLength = $bytes.Length;
$SetPhotoRequest.ContentType = "text/xml";
$SetPhotoRequest.UserAgent = "EWS Photo upload";
$SetPhotoRequest.Headers.Add("Translate", "F");
$SetPhotoRequest.Method = "POST";
$SetPhotoRequest.Credentials = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
$RequestStream = $SetPhotoRequest.GetRequestStream();
$RequestStream.Write($bytes, 0, $bytes.Length);
$RequestStream.Close();
$SetPhotoRequest.AllowAutoRedirect = $true;
$Response = $SetPhotoRequest.GetResponse().GetResponseStream()
$sr = New-Object System.IO.StreamReader($Response)
[XML]$xmlReposne = $sr.ReadToEnd()
if($xmlReposne.Envelope.Body.SetUserPhotoResponse.ResponseClass -eq"Success"){
Write-Host("Photo Uploaded")
}
else
{
Write-Host("Upload failed")
Write-Host$sr.ReadToEnd()
}

}
}

functionGet-SetPhotoRequest
{
param(
[Parameter(Position=0, Mandatory=$true)] [String]$MailboxName,
[Parameter(Position=0, Mandatory=$true)] [String]$Content
)
Begin
{

$request = @"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<RequestServerVersion Version="Exchange2016" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap:Header>
<soap:Body>
<SetUserPhoto xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
<Email>$MailboxName</Email>
<Content>$Content</Content>
<TypeRequested>UserPhoto</TypeRequested>
</SetUserPhoto>
</soap:Body>
</soap:Envelope>
"@
return$request
}
}


Using the Birthday calendar in EWS in Exchange 2016 and Office365

$
0
0
One of the new features that has been added in Exchange 2016 and Office365 in OWA is a birthday calendar which is a dedicated calendar for the Birthday appointments that are associated with contacts. eg



Like many of the special folders in a Mailbox the folder name for this is localized so if your mailbox is set to use a different language this folder name should appear in your localized language. Unlike most of the special folders in Exchange there is no WellKnownFolder enumeration for the birthday calendar so if you want to open this folder you either need to search for it by name (as long as you know the localization of the Mailbox) or you can use the following extended property that should exist on the root mailbox folder.


So in EWS you can do something like the following to access the HexEntryId value from this extended property which you can then convert to a EWSId using the ConvertId operation and then you will be able to bind to the folder using that ID. eg


functionGet-BirthDayCalendar{
param (
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation )
process{

$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$BirthdayCalendarFolderEntryId = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,"BirthdayCalendarFolderEntryId",[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($BirthdayCalendarFolderEntryId)
$EWSRootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset)
$BirthdayCalendarFolderEntryIdValue = $null
$BirthdayCalendarFolderHexValue = $null
if($EWSRootFolder.TryGetProperty($BirthdayCalendarFolderEntryId,[ref]$BirthdayCalendarFolderEntryIdValue)){
$BirthdayFolderEWSId = new-object Microsoft.Exchange.WebServices.Data.FolderId((ConvertId -HexId ([System.BitConverter]::ToString($BirthdayCalendarFolderEntryIdValue).Replace("-",""))))
$BirthdayFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$BirthdayFolderEWSId);
return$BirthdayFolder
}
else
{
throw [System.IO.FileNotFoundException]"folder not found."
}
}
}

Enumerating through birthdays and working out a persons age :

Birthdays are stored as recurring appointments (as they do occur every year) so if you want to display all the birthdays in a calendar its better to use a CalendarView to expand any recurring appointments. To determine the Age of the person associated with the calendar appointment you can use the Birthdaylocal property which holds the actual date of birth (if it was entered). eg


If you then do a Time difference between that and the Start time of the appointment that will give you the Age of the person in days which you can then convert to years using some simple math. In code this looks like

functionGet-Birthdays{
param (
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation )
process{

$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$strtime = (Get-date).Year.ToString() + "0101"
$endtime = (Get-date).AddYears(1).Year.ToString() + "0101"
$StartDate = [datetime]::ParseExact($strtime,"yyyyMMdd",$null)
$EndDate = [datetime]::ParseExact($endtime,"yyyyMMdd",$null)
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$BirthdayCalendarFolderEntryId = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,"BirthdayCalendarFolderEntryId",[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($BirthdayCalendarFolderEntryId)
$EWSRootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset)
$BirthdayCalendarFolderEntryIdValue = $null
$BirthdayCalendarFolderHexValue = $null
if($EWSRootFolder.TryGetProperty($BirthdayCalendarFolderEntryId,[ref]$BirthdayCalendarFolderEntryIdValue)){
$BirthdayFolderEWSId = new-object Microsoft.Exchange.WebServices.Data.FolderId((ConvertId -HexId ([System.BitConverter]::ToString($BirthdayCalendarFolderEntryIdValue).Replace("-",""))))
$BirthdayFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$BirthdayFolderEWSId);
$CalendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($StartDate,$EndDate,1000)
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$BirthDayLocal = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Address,0x80DE, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::SystemTime)
$psPropset.Add($BirthDayLocal)
$CalendarView.PropertySet = $psPropset
$fiItems = $service.FindAppointments($BirthdayFolder.Id,$CalendarView)
foreach($Itemin$fiItems.Items){
$exportObj = "" | Select subject,StartTime,EndTime,DateOfBirth,Age
$exportObj.StartTime = $Item.Start
$exportObj.EndTime = $Item.End
$exportObj.Subject = $Item.Subject
$BirthDavLocalValue = $null
if($Item.TryGetProperty($BirthDayLocal,[ref]$BirthDavLocalValue)){
$exportObj.DateOfBirth = $BirthDavLocalValue
$exportObj.Age = [Math]::Truncate(($Item.Start $BirthDavLocalValue).TotalDays / 365);
}
Write-Output$exportObj
}
}
else
{
throw [System.IO.FileNotFoundException]"folder not found."
}
}

}

I've put a copy of this script up on github at https://github.com/gscales/Powershell-Scripts/blob/master/BirthDayCalendar.ps1

Showing the Recipient history from the Out of Office feature using EWS

$
0
0
One interesting thing I learnt this week from a mailing list that I knew how it worked but didn't know the detail of was the OOF history feature. This feature has been around for ages and its what Exchange uses to ensure you don't receive more then one copy of an OOF message when you send to a mailbox where the OOF status is enabled. According to this KB https://support.microsoft.com/en-us/help/3106609/out-of-office-oof-messages-are-sent-multiple-times-to-recipients this list has a limit of 10000 entries and can cause problems at times like any feature so it give some details on how to manually clear it.

The more interesting part for a developer is the property they mention PR_DELEGATED_BY_RULE (or PidTagDelegatedByRule https://msdn.microsoft.com/en-us/library/ee218716%28v=exchg.80%29.aspx). This property contains a list of all the Email Addresses that this Mailbox has sent an OOF message to while the OOF feature was enabled which is something you could do a few cool things with. Pulling on my Sherlock hat here that property name doesn't sound quite right even through the documentation link I posted confirms the property name and property tag are correct the datatype specified in the documentation is Boolean and in the KB its a Binary Stream. Reading a little a more into what that property is meant to do doesn't quite match its uses here on the FreeBusy Folder but a lack of clear documentation on the actually property means at this point lets write it off as some type of anomaly but the property itself is still of interest.

Unfortunately no documentation exists for the actual format of the datastream stored in this property, so we have to rely on the inspection method. Looking at the raw stream it looks like a serialized MAPI stream (eg just a bunch of MAPI properties stored in a binary stream) however its not like other serialized Mapi streams (eg the Autocomplete stream) where you have the normal EmailAddress Mapi properties etc. By inspection it looks more tokenized with the normal Mapi property types. Looking at one of the Email Address values there is prefix token of 3349C842  which appears to be a repeating token before other email address values (generally you expect the MAPI Tag here) followed by 0201 which is the normal MAPI property type for Binary props and followed by a something like 1400 which the length of the property value (stored as Hex) and then for example a value like 676C656E7363616C6573407961686F6F2E636F6D . So given that inspection value I can write a really dumb parser in PowerShell that parses out the Data Stream a Byte at a time in a forward manner find the Tokens for the email addresses which always seems to have a null terminator preceding them and then parse out the Email addresses which would give you a history list of the Email received and responded to while the OOF rule was enabled. Note without proper documentation writing a real parser isn't really feasible but from the little testing I've done the dumb parser seems to work okay. I've put a copy of an EWS script that grabs this property from a Mailbox's FreeBusy folder and then dumps and values from this prop into the PowerShell pipeline on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/ShowOOFRcpHistory.ps1 to use it use something like

Get-OOFRcpHistory -MailboxName user@datarumble.com

Using the Office365/Exchange 2016 REST API to access Mailbox data using PowerShell part 1

$
0
0
The Outlook REST API 's https://dev.outlook.com/ which are part of Office365 and Exchange 2016 is one of the ways new feature are being delivered for Mailbox clients which previously where delivered via EWS operations. They are also part of the Graph API https://graph.microsoft.io/en-us/docs which is Microsoft's envisioned unified data access API that has the ultimate goal of allowing you to access all your data endpoints via a single interface/endpoint.

In this series of posts I'm going to be looking at writing a PowerShell module that uses the REST API to access Mailbox data and some of the new Exchange features like Groups and the focused Inbox. To keep things simple and flexible I'm not going to use any helper libraries (like the ADAL library or the Outlook Services Client) which I hope will make the script as portable and easy to use as possible with the one downside of while making the code a little more complex  I'm going to use the System.Net.HttpClient classes for greater flexibility as apposed the native PowerShell Rest interfaces.

Getting started

Compared to EWS where there was very little up front configuration necessary to get going (eg in most case just supply a username and password) for the REST API's there is a little bit of configuration that needs to be done.
To use the new REST endpoint you need to use oAuth authentication which means instead of a username and password being included as a header which each request to the server like in Basic Authentication you use an Access Token which is only valid for an hour. A Refresh Token can be used to renew the Access Token when it expires. Tokens offer a big security advantage over using a UserName and Password but still should be treated as if they where a username and password in regards to storage and access as they can still be exploited in the same way. This is an extreme simplification of the oAuth, there is some good documentation sources but be careful of those that discuss Modern Authentication and the ADAL library as they tend to abstract away some the real technical side of understanding what's happening with Token Auth. Personally I like https://docs.microsoft.com/en-gb/azure/active-directory/develop/active-directory-authentication-scenarios and https://msdn.microsoft.com/en-us/office-365/get-started-with-office-365-management-apis as these look more at the underlying way the protocols work.

To use oAuth to authenticate you need to create an Application registration (which gives you the clientId) to use for your scripts or authorize somebody else's (which wouldn't be recommended).  There are plenty of good walk throughs on creating app registrations using the Azure console this one is quite good https://github.com/jasonjoh/office365-azure-guides/blob/master/RegisterAnAppInAzure.md . For scripting generally you want to create a Native App registration and use the Out of Band Call-back urn:ietf:wg:oauth:2.0:oob . One of the big advantages of using oAuth with the new REST interfaces is the authentication scopes which allow you to restrict an application/script to just being able to access the resources you want. Eg if this app is going to just access contacts data then you just enable the authentication scope that allows access to contacts data without allows access to any other Mailbox items.

Authenticating as a User or Application

In EWS and MAPI authentication is always done in the context of the User if you want to access a Mailbox other then that of security context you are using then Delegation would allow that or you could configure Application Impersonation using RBAC which means you could impersonate the owner of any mailbox you wanted to access. There is no Impersonation in the REST API but in Azure you can use what they term the Daemon or Server Application scenario or App-Only tokens which are documented  https://docs.microsoft.com/en-gb/azure/active-directory/develop/active-directory-authentication-scenarios#daemon-or-server-application-to-web-api and https://msdn.microsoft.com/en-us/office/office365/howto/building-service-apps-in-office-365 . In the current interaction of the module I don't cover this Authentication scenario but will in future posts and interactions.

What you get out of the app registration process is a ClientId to use in your script.

Down to coding

For the Authentication code in my module I've used the pretty cool  Show-AuthWindow function from https://foxdeploy.com/2015/11/02/using-powershell-and-oauth/ and https://blogs.technet.microsoft.com/ronba/2016/05/09/using-powershell-and-the-office-365-rest-api-with-oauth/ which does the job of presenting the Azure logon box, any user\tenant consents that are necessary and return back an Auth code that can then be used to get an Access Token. With my implementation I've put all the configurable variables into a separate function to call eg

functionGet-AppSettings(){
param(

)
Begin
{
$configObj = "" | select ResourceURL,ClientId,redirectUrl
$configObj.ResourceURL = "outlook.office.com"
$configObj.ClientId = "5471030d-f311-4c5d-91ef-74ca885463a7"
$configObj.redirectUrl = "urn:ietf:wg:oauth:2.0:oob"
return$configObj
}
}

This makes the ClientId, ResourceURL and redirect easy to configure

The rest of the code is pretty straight forward setting up and using the HTTPClient object to make the necessary REST GET's and POSTs. I've included a number of functions that use the MailboxSetting https://msdn.microsoft.com/office/office365/APi/mail-rest-operations#GetAllMailboxSettings you can break those down into Getting the Oof (or Automatic Replies), timezone etc. Because these are just simple http GET's the code to get the information and parse the JSON results is pretty simple. I've included a sample Get-ArchiveFolder function that demonstrates stacking requests to Get the new Archive Folder https://support.office.com/en-us/article/Archive-in-Outlook-2016-for-Windows-25f75777-3cdc-4c77-9783-5929c7b47028?ui=en-US&rs=en-US&ad=US which was introduced recently. To get the Id of an Archive Folder in a Mailbox you make a request to https://outlook.office.com/api/v2.0/Users('$MailboxName')/MailboxSettings/ArchiveFolder and then you use the result returned to get the Folder in question which might be useful if you tracking usage stats or want to copy an item into the archive.  I've put a copy of the module here (which is a work in progress) https://github.com/gscales/Powershell-Scripts/blob/master/RestHttpClientMod.ps1


Viewing all 241 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>