tag:blogger.com,1999:blog-71234502024-03-07T15:24:21.703+11:00Glen's Exchange and Office 365 Dev BlogPushing the Envelope in Messaging and Office 365 DevelopmentGlen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comBlogger550125tag:blogger.com,1999:blog-7123450.post-24820036705924871792022-08-08T17:03:00.000+10:002022-08-08T17:03:47.665+10:00Important change to EWS access to Microsoft Teams dataMicrosoft have annouced they will be restricting access to Microsoft Teams data via EWS from the 30th September see <a href="https://devblogs.microsoft.com/microsoft365dev/restricted-access-to-microsoft-teams-data-via-ews-starts-september-30-2022/">https://devblogs.microsoft.com/microsoft365dev/restricted-access-to-microsoft-teams-data-via-ews-starts-september-30-2022/</a> . If you are using EWS to access any teams data you will need to move to the Graph API's <a href="https://docs.microsoft.com/en-us/microsoftteams/export-teams-content">https://docs.microsoft.com/en-us/microsoftteams/export-teams-content</a> one thing to note is "Microsoft Teams APIs in Microsoft Graph that access sensitive data are considered protected APIs" so there are some extra steps and time you need to access this data and be aware of the potential need to pay for that access eg "On July 5th 2022, metered consumption is active, and will be billed through an Azure subscription" ref <a href="https://practical365.com/teams-export-graph-apis/.">https://practical365.com/teams-export-graph-apis/.</a>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-66626294177850710252022-01-19T15:54:00.001+11:002022-01-19T15:54:02.867+11:00Using MSAL in the EWS Managed API and doing auto token expiration and renewal in Delegate and Client Credential Azure oAuth Flows<p>With the full depreciation of Basic Authentication around the corner I've put together a Github doc to show one implementation of using MSAL with the EWS Managed API that supports both Hybrid Modern Authentication and token caching and refresh. It can be found <a href="https://github.com/gscales/EWS-BasicToOAuth-Info/blob/main/EWA%20Managed%20API%20MASL%20Token%20Refresh.md">https://github.com/gscales/EWS-BasicToOAuth-Info/blob/main/EWA%20Managed%20API%20MASL%20Token%20Refresh.md</a></p><p><br /></p>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-69223214315243314482021-09-17T16:09:00.000+10:002021-09-17T16:09:54.726+10:00Sending a Message with a Large attachment using the Microsoft Graph and Powershell<p>Sending a message with a large attachment used to be the least worst option when it came to shipping data between people. In the Modern workplace where document and file sharing options are a lot better sending email with a large attachment can tends now to be the worst option especially when it comes to version control, expiration etc. However if you do find yourself with the need to send an email with large attachments (larger then 4MB) and you want to make use of the Microsft Graph API then it requires you use a different method from just sending the message in one post eg <a href="https://docs.microsoft.com/en-us/graph/outlook-large-attachments?tabs=http">https://docs.microsoft.com/en-us/graph/outlook-large-attachments?tabs=http</a></p><p>This does make sending a Message a lot more complex eg if you have a Message less the 4MB you can just make one REST Post to send that message. If you have a large attachment you then have a multi step process eg</p><p></p><ol style="text-align: left;"><li>Create a Draft message where you set all the other message property eg Subject,To,Body</li><li>Take the MessageId(graph item id) returned in Step 1 and Create an Upload Session</li><li>Read the attachment and upload the attachment in chunks until you have uploaded the entire file</li><li>Send the Draft Message</li></ol><div>Eg this is what it looks like in term of REST requests</div><br /><script src="https://gist.github.com/gscales/f97586fe105e3c3316bd8517990e8656.js"></script><p></p><p><br />I've put together a Powershell sample and posted it on GitHub <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/SendMessageWithLargeAttachment.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/SendMessageWithLargeAttachment.ps1</a> that performs the above to run this use something like</p><p>Invoke-SendMessageWithLargeAttachment -MailboxName gscales@....com -filePath "C:\temp\rates.pdf" -verbose -uploadChunkSize 400000 -Subject "Test Message 1234" -body "Rgds Glen" -To glenscales@yahoo.com</p><p>The chunk size you use is up to you a larger chunk size will generally mean a faster upload but if the Network is more unreliable a smaller chunk is better</p><p><br /></p><p><br /></p><p><br /></p>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-31302611707960886532021-08-24T09:45:00.000+10:002021-08-24T09:45:53.409+10:00Sending a MimeMessage via the Microsoft Graph using the Graph SDK, MimeKit and MSAL<p>One of the new features added to the Microsoft Graph recently was the ability to create and <a href="https://docs.microsoft.com/en-us/graph/outlook-send-mime-message">send Mime Messages</a> (you have been able to get Message as Mime for a while). This is useful in a number of different scenarios especially when trying to create a Message with inline Images which has historically been hard to do with both the Graph and EWS (if you don't use MIME). It also opens up using SMIME for encryption and a more easy migration path for sending using SMTP in some apps.</p><p><a href="https://github.com/jstedfast/MimeKit">MimeKit</a> is a great open source library for parsing and creating MIME messages so it offers a really easy solution for tackling this issue. The current documentation on Send message via MIME lacks any real sample so I've put together a quick console app that use MSAL, MIME kit and the Graph SDK to send a Message via MIME. As the current Graph SDK also doesn't support sending via MIME either there is a workaround for this in the future my guess is this will be supported.</p><p><br /><script src="https://gist.github.com/gscales/02b32a66dfb0b11792b8a2d852188086.js"></script></p><p><br /></p>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-73686672344428316442021-07-19T14:20:00.002+10:002021-07-19T14:20:51.289+10:00Migrating Exchange Web Services (EWS) Directory and Recipient resolution code to the Microsoft Graph<p> One of the more complex things to migrate in EWS when migrating to the Graph API is any directory access code that uses one of the following EWS operations</p><p></p><ul style="text-align: left;"><li>FindPeople</li><li>ResolveName</li><li>ExpandGroup (ExpandDL)</li></ul><div>or if your using OnPrem you maybe using System.DirectoryServices to do direct LDAP requests of Active Directory.</div><div><br /></div><div>With the Microsoft Graph API these Directory based mail operations don't exist, because you have full access to the underlying AzureAD, so in theory everything should be achievable without these type of operations. For the most part this is correct where is starts to get a little grayer is around this like Address Lists and Exchange recipient types mostly because the Graph doesn't expose the following underlying Active Directory properties</div><p></p><ul style="background-color: white; border: 0px; color: #444444; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.7; list-style: outside square; margin: 0px 0px 24px; padding: 0px; vertical-align: baseline;"><li style="border: 0px; margin: 0px 0px 0px 36px; padding: 0px; vertical-align: baseline;"><em style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"><strong style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">msExchRecipientDisplayType</strong></em></li><li style="border: 0px; margin: 0px 0px 0px 36px; padding: 0px; vertical-align: baseline;"><em style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"><strong style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">msExchRecipientTypeDetails</strong></em></li><li style="border: 0px; margin: 0px 0px 0px 36px; padding: 0px; vertical-align: baseline;"><em style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"><strong style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">msExchRemoteRecipientType</strong></em></li></ul><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">so this can be a limitation if your migrating from LDAP code and some FindPeople implementations if your searching based on AddressList.</span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>Microsoft Graph Users Endpoint and the People API</b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b><br /></b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">The two main endpoints you will use when migrating your directory based code is the Users endpoint which allows you to enumerate and query the underlying users in Azure AD. The People API allows you to query both the Users, Mailbox Contacts, Directory and other related contacts information (and a little bit more). The third endpoint you might use is Contacts if you just want to search the contacts of the local mailbox but I'm not going to cover this in this post also the Groups Endpoint should be used for group operations (expanddl)</span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>Permission and uses </b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">For the People API there are two permissions that are usable but if you need to be able to search all users in your organization (regardless of their relevance to the underlying user) you need to make sure that you have the People.Read.All permission that requires Admin Consent. For the Users Endpoint depending on the level of detail you need user.readbasic.all is an option but you may find you need user.read.all.</span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>Sample Scripts</b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b><br /></b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">For this post I've created a number of sample scripts for the user endpoint i have </span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><a href="goog_1634370449"><br /></a></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/EnumerateUsers.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/EnumerateUsers.ps1</a></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><br /></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;">(users Delegate Authentication) </span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><br /></span></div><a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/EnumerateUsersSpAuth.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/EnumerateUsersSpAuth.ps1</a><div><br /></div>(Uses Service Principal Authentication)<div><br /></div><div><a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/PeopleModule.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/PeopleModule.ps1</a><br /><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><b style="color: #444444; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 14px;">Users API Advanced Queries</b></div><div><b style="color: #444444; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 14px;"><br /></b></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">The Microsoft Graph API recently introduced an Advanced query engine that allows you to make more complex queries of Azure AD <a href="https://docs.microsoft.com/en-us/graph/aad-advanced-queries">https://docs.microsoft.com/en-us/graph/aad-advanced-queries</a> . To use this advanced query the Request string needs to modified and a additional header </span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace" style="background-color: #f2f2f2; color: #171717; font-size: 14px; white-space: pre;">ConsistencyLevel: eventual</span></div><div><span face="SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace" style="background-color: #f2f2f2; color: #171717; font-size: 14px; white-space: pre;"><br /></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">needs to be added to the Graph request, this is supported in the sample script for using the -AdvancedQuery switch</span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>People API Search Scope</b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b><br /></b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">As well as the permission scope in the People API the scope it will search can be affected by an additional header called </span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace" style="background-color: #f2f2f2; color: #171717; font-size: 14px; white-space: pre;">"</span><span class="hljs-attribute" face="SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace" style="background-color: #f2f2f2; box-sizing: inherit; color: #006881; font-size: 14px; outline-color: inherit; white-space: pre;">X-PeopleQuery-QuerySources</span><span face="SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace" style="background-color: #f2f2f2; color: #171717; font-size: 14px; white-space: pre;">: Mailbox,Directoryā€¯</span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">By default the People API will only search the Mailbox so it's important to use this header when you want to search the Directory or even limit your search to the Directory (and don't search the mailbox). In the sample script using the -DirectoryOnly switch will limit the search to the directory only. One thing to note if you don't have the People.Read.All permission and you use a Directory only search scope it will only return those contacts relevant to the user rather then rather then any user in the directory.</span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>Enumerating or browsing users (Find People)</b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b><br /></b></span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;">Address books are a pretty standard feature of any email client, in EWS you may have used the FindPeople operation to search or browse the users from an Addressbook. The good thing about findpeople is that it gave you the ability to use Exchange Address Lists. The Graph doesn't have that direct ability so if your trying to browse using Address lists using one of the Graph endpoints to try and mimic the GAL you need to try to reproduce that filter in your code (which you may not be able to do). When you enumerate objects in the Graph API you page those objects in pages of up to 999 objects at a time. An example of a straight page of all the users in the underlying directory is</span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;">Get-AzureUsersFromGraph -MailboxName gscales@datarumble.com</span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;">Using the People API you can search across all Exchange Address Lists (rather then browse within a particular address lists like FindPeople could). Because this is a search rather then an enumeration like the Users endpoint you can use a workaround such as search for "smtp:" as the search string to return all users (this was a workaround for EWS as well it was limited to 100 users in EWS I haven't tested if there is the same limit in the Graph and its not documented, the differences is the Microsoft Graph results are paged where resolveName wasn't, paging is also a little weird in the People API and the documentation is lacking at the moment and doesn't explain how it works (or it has bugs at the moment) (eg Directory queries look like there is a limitation to 150 where Mailbox queries aren't limited ) If anybody can give clarity around this let me know and I'll correct this.</span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;">Invoke-SearchPeople -MailboxName gscales@datarumble.com -SearchString 'smtp:' -DirectoryOnly</span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;">I've added a Graph 101 Binder page for enumerating users in the directory which has more example for the users interface <a href="https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Users/Enumerate%20and%20Search%20for%20users%20in%20Azure.md">https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Users/Enumerate%20and%20Search%20for%20users%20in%20Azure.md</a></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b><br /></b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>ResolveName</b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b><br /></b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">In EWS the ResolveName operation has a number of different uses in the conventional sense it would be used to do ANR (ambiguous name resolution) when someone is sending email. In an Application it maybe used to do any of the following </span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>Get the contact information for a user that you only have the SMTP Address for</b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">If the target users are located in AzureAD you could use the Users Endpoint in Azure to search for the user based on one of their proxyaddress so it could be a Primary or secondary address of the object. eg</span></span></div></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">Get-AzureUsersFromGraph -MailboxName gscales@doamin.com -filter "proxyAddresses/any(x:x eq 'smtp:info@domain.com')"</span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;">Which generates a Request to Graph like</span><pre style="background: rgb(255, 255, 255); color: black;"><span style="color: #e34adc;">https:</span><span style="color: dimgrey;">//graph.microsoft.com/v1.0/users?$top=999&$filter=proxyAddresses/any(x:x%20eq%20'smtp:</span><span style="color: #7144c4;">info@users.com</span><span style="color: dimgrey;">')</span>
</pre></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;">If the Contact your trying to locate is in the Mailbox or if its in either the Mailbox or the Directory then you can use the People API to search both scopes. Note this is a Search so you may need to filter the results more then you would in the Users example with is doing an exact match</span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;">Invoke-SearchPeople -MailboxName gscales@datarumble.com -SearchString 'smtp:info@domain.com' -DirectoryOnly</span></span></div><div><b style="color: #444444; font-size: 14px;"><br /></b></div><div><b style="color: #444444; font-size: 14px;">Find All users from a particular domain (eg all guest users from domain)</b></div><div></div><br />Get-AzureUsersFromGraph -MailboxName gscales@datarumble.com -filter "endsWith(mail,'@msgdevelop.com')" -AdvancedQuery<div><br /></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444; font-size: 14px;">Which generates a Request to Graph like (notice this is an Advanced Query)</span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><div style="color: black; font-size: medium;"><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><pre style="background: rgb(255, 255, 255); color: black;"><span style="color: #e34adc;">https:</span><span style="color: dimgrey;">//graph.microsoft.com/v1.0/users?$top=999&$filter=endsWith(mail,'@msgdevelop.com')&$Count=true</span>
</pre><div><span style="color: dimgrey;"><br /></span></div></span></div></span></span></div><div><b style="color: #444444; font-size: 14px;">Search based on part of a DisplayName </b></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b><br /></b></span></span></div><div><span style="color: #444444;"><span style="font-size: 14px;">For the closest ResolveName behavior use the People API however the Users Endpoint in Graph can also be used here are some examples</span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b><br /></b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>Users Endpoint</b></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif"><span style="color: #444444; font-size: 14px;"> Get-AzureUsersFromGraph -MailboxName gscales@datarumble.com -filter "startswith(displayName,'glen')" -AdvancedQuery</span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><br /></span></span></div><div><span face="Ubuntu, Helvetica, Arial, sans-serif" style="color: #444444;"><span style="font-size: 14px;"><b>People API</b><br /></span></span><div><br /></div><div>Invoke-SearchPeople -MailboxName gscales@datarumble.com -SearchString 'glen' -DirectoryOnly</div><div><br /></div><div><b>Resolving a Legacy ExchangeDN or EX Address</b></div><br />This is something I've used a lot especially when your migrating email or in other instances where all you have is the LegacyDN or Exchange Native Address of the User. You can resolve these addresses using the People API and a DirectoryOnly query eg<div><br /></div><div>Invoke-SearchPeople -MailboxName gscales@datarumble.com -SearchString '/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=a7d0ec70c15e4132a8a2cfb840c75a66-gscales' -DirectoryOnly</div><div><br /></div><div><b>Expand Group</b></div><div><br /><div>I haven't actually included this in any of my samples as there is a Groups Endpoint in the Microsoft Graph that you should use for this which is documented well at<span style="color: #444444;"><span style="font-size: 14px;"> <a href="https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0">https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0</a></span></span></div></div></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-14193289020170437782021-06-22T11:14:00.001+10:002021-06-22T11:14:38.034+10:00Using Batching to improve the speed of Contact creation in the Microsoft Graph<p>There's been a few contact creation scripts popup recently for the Graph API like <a href="https://practical365.com/create-org-contacts-powershell-graph">this</a> as well as a few questions on the forums around this topic lately. None of these examples and questions are taking advantage of using batching in the Microsoft Graph which will give you a significant uplift in performance vs the single request method when creating larger numbers of items and also help you a little around throttling.</p><p>I've added a new post to my <a href="https://gscales.github.io/Graph-Powershell-101-Binder/">Graph 101 binder</a> on GitHub that includes an example of doing a CSV Contact import using batching and Service Principal Authentication <a href="https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Contacts/Batch%20Importing%20Contacts.md">https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Contacts/Batch%20Importing%20Contacts.md</a> </p><p>If your interested in a EWS version that can use larger batches (eg 60-100 contacts per request) I've also include an example on GitHub for this <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/BatchContactCreationEWS.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/BatchContactCreationEWS.ps1</a></p>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-1972007657452976292021-06-08T12:02:00.005+10:002021-06-08T12:10:54.026+10:00Using Out of Office / automaticRepliesSetting with the Microsoft Graph with Service Principal Authentication<p>Out of Office (or automaticRepliesSetting) can be used for a vast number of different applications. For example in this <a href="https://gsexdev.blogspot.com/2018/06/building-microsoft-teams-tab.html">Teams In/Out board</a> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXMX8ak6Xg47-Ar8SaCxugJIiQMNdCfcbP8BQ4RTN7n5Ztpi9EkMiLYFwheKfYItYas8KCcsV8aayuAtVyo2tKiibPEIoVaQam8z4ctox9dSqAmQ-BigMt3leKLhFiAOaPPMDTGg/s885/teamsboard.JPG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="389" data-original-width="885" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXMX8ak6Xg47-Ar8SaCxugJIiQMNdCfcbP8BQ4RTN7n5Ztpi9EkMiLYFwheKfYItYas8KCcsV8aayuAtVyo2tKiibPEIoVaQam8z4ctox9dSqAmQ-BigMt3leKLhFiAOaPPMDTGg/w640-h282/teamsboard.JPG" width="640" /></a></div><br /><p>With the Microsoft Graph API there are two ways that can be used to get the automaticRepliesSetting either via the Mailbox setting Endpoint eg</p><p><a href="https://docs.microsoft.com/en-us/graph/api/resources/automaticrepliessetting?view=graph-rest-1.0">https://docs.microsoft.com/en-us/graph/api/resources/automaticrepliessetting?view=graph-rest-1.0</a></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7rxT8YOdJFhvaFF6NllaoPxvWhnKh50CzUF3G1YSD3kGduSC7HEHg2wqIRGjjIYoSuRfPI-t-r6qtKH93GOpAegrYpbChX-XvgDlqRsEnYSu5pgsUJEs-p_tDh-u2c8QRjsnyrQ/s1211/mbset1.PNG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="562" data-original-width="1211" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7rxT8YOdJFhvaFF6NllaoPxvWhnKh50CzUF3G1YSD3kGduSC7HEHg2wqIRGjjIYoSuRfPI-t-r6qtKH93GOpAegrYpbChX-XvgDlqRsEnYSu5pgsUJEs-p_tDh-u2c8QRjsnyrQ/w640-h298/mbset1.PNG" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><br /></div>Or you can use MailTips which was the method i used in the Teams Apps eg<div><br /></div><div><a href="https://docs.microsoft.com/en-us/graph/api/user-getmailtips?view=graph-rest-1.0&tabs=http">https://docs.microsoft.com/en-us/graph/api/user-getmailtips?view=graph-rest-1.0&tabs=http</a></div><div><br /></div><div>eg</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFQeHv_mDN_Yunq2cgs4qSFrG3eN1oyWdfSpDk6bUNyhQ0vgZPHI4TigC5uY_6oAsbFGGWiurjWSKe9GVSHDNLzBCARy97gX5QWWCE603H_8p9LuovJf2pK1f6yME3RUAZe-M4TA/s1176/ar2.PNG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="675" data-original-width="1176" height="368" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFQeHv_mDN_Yunq2cgs4qSFrG3eN1oyWdfSpDk6bUNyhQ0vgZPHI4TigC5uY_6oAsbFGGWiurjWSKe9GVSHDNLzBCARy97gX5QWWCE603H_8p9LuovJf2pK1f6yME3RUAZe-M4TA/w640-h368/ar2.PNG" width="640" /></a></div><div><br /></div>When it comes to setting the OOF you must use the Mailboxsettings endpoint<div><br /></div><div>What is better ? for getting the OOF settings on a large number of users getmailtips because you can request up to 100 users in one request while if your batching Mailboxsetting you can only have a max of 20 user in a single batch.</div><div><br /></div><div><b>Permission and Authentication </b></div><div><b><br /></b></div><div>One consideration for the Mailboxsettings endpoint is there is no ability to use Delegate permissions to access the Mailbox settings of a user other then that of the credentials (unlike EWS where you could use delegation). So for most use cases (eg daemon apps or other automation back-ends) you will need to use <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals">Service Principal authentication</a> and Application permissions. This can open up some security concerns if the application in question is only required to work on a subset of Mailbox make sure that you employ <a href="https://docs.microsoft.com/en-us/graph/auth-limit-mailbox-access">scoping </a><br /><div><br /></div><div><b>Using Service Principal Authentication in Power Shell</b></div><div><br /></div><div>I don't yet have a sample for doing this in my<a href="https://gscales.github.io/Graph-Powershell-101-Binder/"> Graph 101 series</a> but its actually less complicated then doing an authentication code type auth because all your code needs to do is create a JWT token from a locally stored SSL certificate (and sign it). Here is some sample code for doing this</div><div><br /><script src="https://gist.github.com/gscales/12a4dd21a03c66fa4072b03337ac4fad.js"></script></div><div><br />So to use this function you first need to acquire the local certificate which could be either stored in the Local Certificates Store or in a PFX file with a password. eg</div><div><br /></div><div>$certificate = Get-ChildItem -Path Cert:LocalMachine\MY\FCE7328421DDDB4CC8EA59C51B6F156765272CFA</div><div><br /></div><div>or from a PFX file</div><div><br /></div><div>$certificate = Get-PfxCertificate -FilePath C:\temp\certo.pfx</div><div><br /></div><div>Once you have the certificate you can then request the token like</div><div><br /></div><div>Get-AccessTokenForGraphFromCertificate -Certificate $Certificate -ClientId e6fd6f09-9c63-4b30-877a-3520ee1e1e9a -TenantDomain domain.com</div><div><br /></div><div><b>Samples for Getting the AutoReplySettings using Service Principal Authentication</b></div><div><br /></div><div>For this post I've put together a sample of Getting the AutoReply Setting for One user and batch of users via the Mailbox Setting Endpoint using certificate Authentication. And also a sample or using MailTips to do the same.</div><div><br /></div><div>The script for this is available on Github here <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/AutoReplySettings.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/AutoReplySettings.ps1</a></div><div><br /></div><div><br /><p><br /></p></div></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-67193827767962364762021-04-22T12:59:00.002+10:002021-04-22T12:59:40.265+10:00Using the Tag for external email messages received feature in the Microsoft Graph and Exchange Web Services<span style="font-size: medium;">The "Tag for external email messages received" feature was introduced into Office365 recently to help people better to identify mail that comes from external sender vs internal sender see <a href="https://www.blogger.com/u/1/#">this</a> for more info on this feature . <br /><br />What happens when you enable this feature is that for messages with External sender a new Mapi property is set on those messages eg</span><div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVCgxzmwgWHtYeKI0RPAbmFVduoVjT8XuKNhgUyisXA7hE7378xrzq15SvhDpFsWgLgosJlwEMrUBONF8DC2q5pbIwPyP59z3G7P3VgmLWFolmn5eg84VuI9T_SA-0lA3dYGRI2A/s926/isext.PNG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="297" data-original-width="926" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVCgxzmwgWHtYeKI0RPAbmFVduoVjT8XuKNhgUyisXA7hE7378xrzq15SvhDpFsWgLgosJlwEMrUBONF8DC2q5pbIwPyP59z3G7P3VgmLWFolmn5eg84VuI9T_SA-0lA3dYGRI2A/w640-h206/isext.PNG" width="640" /></a></div><br /><span style="font-size: medium;">For messages from internal senders the property doesn't appear to get set and if the feature isn't enabled in your tenant then you won't see this property either. You can negate the boolean value of the property which will turn off the external tag.</span></div><h2 style="text-align: left;"><span style="font-size: medium;">Using it in the Microsoft Graph API</span></h2><div><span style="font-size: medium;"><br /></span></div><div><span style="font-size: medium;">If you want to use it in the Microsoft Graph API when you retrieve messages you can include this as a SingleValueExtendedProperties eg for Retrieving and filtering message you can use</span><p cid="n8" class="md-end-block md-p md-focus" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><script src="https://gist.github.com/gscales/abe8e16ef4a6a05c37161551de1b1d6c.js"></script><br /></p><span style="font-size: medium;"><div><span style="font-size: medium;">I've included this in my <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/GetLastItemInInbox.ps1">Get LastEmail 101 graph sample</a> if you want to try this in a real mailbox eg you can do</span></div><div><span style="font-size: medium;"><br /></span></div><div><span style="font-size: medium;"> Get-LastEmail -InternalSender -MailboxName gscales@mailbox.com </span></div><div><span style="font-size: medium;"><br /></span></div><div><span style="font-size: medium;">Will get the Last mail from an Internal Sender</span></div><div><span style="font-size: medium;"><br /></span></div><div><span style="font-size: medium;"> Get-LastEmail -ExternalSender -MailboxName gscales@mailbox.com </span></div><div><span style="font-size: medium;"><br /></span></div><div><span style="font-size: medium;">Will get the Last mail from an External Sender</span></div><div><span style="font-size: medium;"><br /></span></div><div><span style="font-size: medium;">and you can use the -Focused or -Other switch to get the above email from the Focused or Other Inbox</span></div></span><h2 style="text-align: left;"><span style="font-size: medium;">Using it in Exchange Web Services</span></h2><span style="font-size: medium;"><br />In EWS you can use a SearchFilter eg<br /></span></div><div><span style="font-size: medium;"><script src="https://gist.github.com/gscales/53b60b022618e55c7eb14dde741b97ae.js"></script><br />This property is set-able so if you have an application where email is marked as external and you can't or don't want to whitelist the domain (so they no longer are marked external) you could modify this property value (on each applicable message) so they will no-longer be tagged as External for the user.</span></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-63420027170419429272021-03-18T14:07:00.000+11:002021-03-18T14:07:34.902+11:00Auditing Inbox rules (and looking for hidden rules) with EWS in OnPrem Exchange<p> After the events of the last weeks around the latest <a href="https://www.microsoft.com/security/blog/2021/03/02/hafnium-targeting-exchange-servers/">zero day vulnerabilities in Exchange </a> and once you've finished cleaning up any back doors that may have been left on servers its a good idea to review some other less known but established ways bad actors may hide persistent access within Mailboxes. One of these are Inbox Rules (but Mail Flow rules could also be used) and a more advanced method is the hidden Inbox rule exploit that was first talked about <a href="https://blog.compass-security.com/2018/09/hidden-inbox-rules-in-microsoft-exchange/">https://blog.compass-security.com/2018/09/hidden-inbox-rules-in-microsoft-exchange/</a> and I covered it in <a href="https://gsexdev.blogspot.com/2019/05/audting-inbox-rules-with-ews-and-graph.html">https://gsexdev.blogspot.com/2019/05/audting-inbox-rules-with-ews-and-graph.html</a> and somebody else <a href="https://mgreen27.github.io/posts/2019/06/09/O365HiddenRules.html">https://mgreen27.github.io/posts/2019/06/09/O365HiddenRules.html</a> there are a number of tools and techniques around detecting these types of rule but are all focused more toward Office365 as that was where at the time this exploit was being mostly employed. In my post at the time I modified the Microsoft script https://github.com/gscales/O365-InvestigationTooling/blob/master/Get-AllTenantRulesAndForms.ps1 so it would include the PidTagRuleMsgProvider property in its final report so you could easy spot any null, empty or custom values that would effectively hide rules from being enumerated via other more conventional methods. Because this tool was designed for o365 it can't easily be run against onPrem Exchange so I've put together a modified version that can be and posted it up here. <a href="https://github.com/gscales/Powershell-Scripts/blob/master/EWShiddenRuleEnum.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/EWShiddenRuleEnum.ps1</a> I've retained the funky Encoded compressed version of the EWS Managed API dll that the original used and extended some of the properties to include things like the display-name of the user and the last modified date time of the rule object. To use this script you need to have EWS Impersonation enabled for the user you either pass into the script or the user the script is running under. <a href="https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-configure-impersonation">https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-configure-impersonation</a></p><p>Mostly the changes to the script involved (other then cleanup) where around the remote power shell differences (this script will try to open a remote ps connection if one isn't already established) and passing in the hostname of the EWS endpoint.</p><p>To use this script first import the script module</p><p>Import-module ./EWShiddenRuleEnum.ps1</p><p>Then </p><p>Invoke-EnumerateAllInboxRules</p><p>parameters that can/must be used</p><p><b>EWSHostName</b> this is the EWS endpoint hostname the script doesn't use autodiscover so if your not running directly on the destination server you must pass this in</p><p><b>ExchangePSHostName</b> this is the servername that the Remote powershell session will be open against if your not running directly on the destination server you must pass this in</p><p><b>Credentials</b> are the credentials that will be used in EWS to connect and impersonate mailboxes, the remote powershell credentials used are the running users. (credentials can be entered in either UPN format or downlevel format domain\username and this can be environment dependent as to what works)</p><p><b>Filter </b>this allows you to filter the Get-Mailbox query with any valid filter you would normally use in the PowerShell eg Invoke-EnumerateAllInboxRules -filter '{servername -eq hostname}'</p><p>The script will produce a CSV report of rules and forms in the current directory, you can open this in Excel and apply filters to make it easy to read and interpret. </p><p>If you don't go down the path of rebuilding Exchange servers that where exploited make sure you also look at any Transport Agents you have installed and verify the assemblies, its a a lesser known method of persistent access but it has been used before <a href="https://www.bankinfosecurity.com/researchers-spies-exploit-microsoft-exchange-backdoor-a-12459">https://www.bankinfosecurity.com/researchers-spies-exploit-microsoft-exchange-backdoor-a-12459</a> to some success. </p><p><br /></p><p><br /></p><p><br /></p>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-13231268945766686202021-01-08T16:07:00.004+11:002021-01-08T16:07:58.845+11:00Using Shared Mailboxes in the Microsoft Graph API from PowerShell<p>I've created a few new Binder entries in GitHub for using Shared Mailboxes in the Graph API using PowerShell</p><div>The Binder index is <a href="https://gscales.github.io/Graph-Powershell-101-Binder/" style="box-shadow: none; box-sizing: border-box; color: #0366d6; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 14px; outline: none;">https://gscales.github.io/Graph-Powershell-101-Binder/</a> </div><div><br /></div><div>The topics covered are</div><div><br /></div><a href="https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Shared-Mailboxes/Accesing%20a%20Shared%20Mailbox%20Folder.md">Accessing a Shared Mailbox folder and its Items</a><div><br /></div><div><a href="https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Shared-Mailboxes/Sending%20a%20Mail%20from%20a%20Shared%20Mailbox.md">Sending an Email from a Shared Mailbox</a></div><div><br /></div><div>The script for these entries can be found <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/SharedMailboxOps.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/SharedMailboxOps.ps1</a></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-43151337395030024182020-11-20T16:14:00.001+11:002020-11-20T16:14:44.499+11:00Finding Emails in a Mail Folder older then a specific date using the Microsoft Graph and Powershell<p> </p><h1 cid="n0" class="md-end-block md-heading" mdtype="heading" style="border-bottom: 1px solid rgb(238, 238, 238); box-sizing: border-box; break-after: avoid-page; break-inside: avoid; color: #333333; cursor: text; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 2.25em; line-height: 1.2; margin-bottom: 1rem; margin-top: 1rem; orphans: 4; position: relative; white-space: pre-wrap;"><br /></h1><p cid="n2" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">If you are doing any archiving, clean-up or just searching for Messages that are older then a specific date you will need to make use of a filter on the receivedDateTime. </span></p><p cid="n3" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-pair-s " md-inline="strong" style="box-sizing: border-box;"><strong style="box-sizing: border-box;">The receivedDateTime property</strong></span></p><p cid="n4" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">This property represents when the Message was received expressed and stored in UTC time, this means when you query it you should also make sure your query value is in UTC. </span></p><p cid="n5" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">So for instance if I where looking for all the Email in the JunkEmail folder older then 30 days I could use</span></p><pre cid="n6" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">/v1.0/users('gscales@datarumble.com')/MailFolders('JunkEmail')/messages?$Top=10&$filter=receivedDateTime lt 2020-10-21T00:00:00Z</span></pre><p cid="n7" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">If the mailboxes are in a TimeZone other then UTC then first convert the actual date to the local date you want to include to UTC eg in Powershell something like</span></p><pre cid="n8" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">(Get-Date).adddays(-30).Date.ToUniversalTime().ToString("o")</span></pre><p cid="n9" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">This means if my timezone is +11 UTC my actual query time would look like</span></p><p cid="n10" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">2020-10-20T13:00:00.0000000Z based on an Actual day value of 2020-10-21T00:00:00.0000000+11:00</span></p><p cid="n11" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-pair-s " md-inline="strong" style="box-sizing: border-box;"><strong style="box-sizing: border-box;">Precision</strong></span></p><p cid="n12" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">The last thing to consider around receivedDateTime is precision, Exchange Server stores the Item with a precision down to the milliseconds in EWS you could adjust the precision of your queries down to the millisecond however the Graph API doesn't allow you to do this. So if your querying items in the Graph and using the GT or LT operators you may see them work more as GE and LE due to the loss in precision. While this sounds like a little thing it can be quite painful depending on what your trying to do.</span></p><p cid="n13" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">Orderby</span></p><p cid="n14" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">By default when you use a filter you will get the results back in ascending order so if you want to see the latest messages first make sure you use</span></p><pre cid="n15" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">OrderBy=receivedDateTime desc</span></pre><p cid="n16" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">if you add more fields into the Filter clause they will also need to be added to the OrderBy </span></p><p cid="n17" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">I've created a Powershell Script to go along with the doc call ItemAgeModule.ps1 available from </span><span class="md-link md-pair-s" md-inline="url" spellcheck="false" style="box-sizing: border-box; word-break: break-all;"><a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/ItemAgeModule.ps1" style="-webkit-user-drag: none; box-sizing: border-box; color: #4183c4; cursor: pointer;">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/ItemAgeModule.ps1</a></span></p><p cid="n18" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">Some Examples are get the last 10 email in the Inbox older then 30 days would look like</span></p><pre cid="n19" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">Get-EmailOlderThan -MailboxName gscales@domain.com -FolderName Inbox -OlderThanDays 30 -MessageCount 10</span></pre><p cid="n20" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">To get all the Email in the Junk-Email Folder older the 30 days use the following and it will page them back 100 items at a time</span></p><pre cid="n21" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">Get-EmailOlderThan -MailboxName gscales@domain.com -FolderName Inbox -OlderThanDays 30</span></pre><p cid="n22" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">To get all Email older then 30 days with a specific subject then you can use (note the OrderByExtraFields is needed which should have a comma seperated list of extra fields used in the filter)</span></p><pre cid="n23" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">Get-EmailOlderThan -MailboxName gscales@domain.com -FolderName Inbox -OlderThanDays 30 -filter "Subject eq 'test'" -OrderByExtraFields subject</span></pre><p cid="n24" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-pair-s " md-inline="strong" style="box-sizing: border-box;"><strong style="box-sizing: border-box;">Moving the Emails you find</strong></span></p><p cid="n25" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">As mentioned one of the main reasons you may have for finding messages of a specific age is to Archive, Move or Delete them. For this you need to use the move operation on the Item endpoint eg in graph this is basically a post like</span></p><pre cid="n26" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">https://graph.microsoft.com/v1.0/me/messages/AAK.....=/move</span></pre><p cid="n27" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">with the destination folder in the body of the POST (either a folderId or WellknownFolderName) eg</span></p><pre cid="n28" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">{ "destinationId": "archive" }</span></pre><p cid="n29" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">Would move messages to the mailbox's archive folder (not my in-place archive)</span></p><p cid="n30" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">So if I wanted to archive all the Messages in my Inbox from a particular sender that was older then 2 years I could use</span></p><pre cid="n31" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">Move-EmailOlderThan -MailboxName gscales@domain.com -FolderName Inbox -OlderThanDays 30 -filter "from/emailAddress/address eq 'user@domain.com'" -OrderByExtraFields "from/emailAddress/address" -Destination archive</span></pre><p cid="n32" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">This code makes use of batching and moves items in lots of 4 as not to go over the concurrent user quota</span></p><p cid="n33" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">Or if I wanted to soft delete items I could move them to the RecoverableItemsDeletions folder</span></p><p cid="n34" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">eg</span></p><pre cid="n35" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" mdtype="fences" spellcheck="false" style="background-attachment: inherit; background-clip: inherit; background-color: #f8f8f8; background-image: inherit; background-origin: inherit; background-position: inherit; background-repeat: inherit; background-size: inherit; border-radius: 3px; border: 1px solid rgb(231, 234, 237); box-sizing: border-box; break-inside: avoid; color: #333333; font-family: var(--monospace); font-size: 0.9em; margin-bottom: 15px; margin-top: 15px; overflow: visible; padding: 8px 4px 6px; position: relative !important; white-space: normal; width: inherit;"><span role="presentation" style="box-sizing: border-box; padding-right: 0.1px;">Move-EmailOlderThan -MailboxName gscales@domain.com -FolderName Inbox -OlderThanDays 30 -filter "from/emailAddress/address eq 'info@twitter.com'" -OrderByExtraFields "from/emailAddress/address" -Destination RecoverableItemsDeletions -verbose</span></pre><p cid="n36" class="md-end-block md-p" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">the script for this post can be found </span><span class="md-link md-pair-s" md-inline="url" spellcheck="false" style="box-sizing: border-box; word-break: break-all;"><a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/ItemAgeModule.ps1" style="-webkit-user-drag: none; box-sizing: border-box; color: #4183c4; cursor: pointer;">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/ItemAgeModule.ps1</a></span></p><p cid="n37" class="md-end-block md-p md-focus" mdtype="paragraph" style="box-sizing: border-box; color: #333333; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: inherit; margin: 0.8em 0px; orphans: 4; position: relative; white-space: pre-wrap;"><span class="md-meta-i-c md-link md-expand" md-inline="link" style="box-sizing: border-box;"><a href="https://gscales.github.io/Graph-Powershell-101-Binder/" spellcheck="false" style="-webkit-user-drag: none; box-sizing: border-box; color: #4183c4; cursor: pointer;"><span class="md-plain" md-inline="plain" style="box-sizing: border-box;">Back to 101 Graph PowerShell Binder Mini-Site</span></a></span></p>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-56682848419609709332020-11-06T12:54:00.004+11:002020-11-06T12:54:58.912+11:00Looking at raw Mailbox analytics data using EWS and a ChangeDiscovery script<p>Mailbox analytics is something Microsoft have been working on for a number of years and its seems to be something that has received a little more effort in these pandemic times. If you have ever looked at the Non_IPM_Subtree of your mailbox you will see a lot of data being stored in their from various apps and substrate processes. A while back i wrote a <a href="https://gsexdev.blogspot.com/2019/10/doing-mailbox-change-discovery-with-ews.html">ChangeDiscovery </a>script to allow me to dump out quick what changes where happening in a Mailbox in a short time frame (eg i wanted to see what happened to all the items in a Mailbox when i performed a specific task). If you run this script with a slightly longer time-frame (eg looking over a day) it picks up all the Items that are being written and created for the Mailbox insights processes and other substrate processes.</p><p>Most of these emails get written under the </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB9rk9bKuDdGt4p57GpullPb5NCevdCD6_97uKBzTRU5HIf43ZmewLI8HhvtVckIFhVCqO0iRhCBx6mwQe0Ry2-m7cE0eFhCovZPYBEe1_YWqnk0_gbcp-3DpoiRcHCeG7x1lbeQ/s432/appdata.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="292" data-original-width="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB9rk9bKuDdGt4p57GpullPb5NCevdCD6_97uKBzTRU5HIf43ZmewLI8HhvtVckIFhVCqO0iRhCBx6mwQe0Ry2-m7cE0eFhCovZPYBEe1_YWqnk0_gbcp-3DpoiRcHCeG7x1lbeQ/s320/appdata.PNG" width="320" /></a></div><p><br /></p><p>Usually if I then wanted to look at these type of items I would use OutlookSpy or MFCMapi to browse the raw MAPI properties on items to see if they where of interest. Given the number of these items now and the performance on MAPI in Online mode doing this is extremely tedious so I modified my change discovery script further to dump the RawJson and Data properties that most of the insight items use so you can then look at the whole bunch in Excel. eg the following two properties</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTxDdrDQN8pOwiKskyUjZkbj6gCLiMGKi9cO-j9xwzLLqav4Q03o9u1dc_lIoCmgpvJAGfX-bhewu26xVLThGC58lKKnl6YW2c_1wQP3XKO8cU9ojW1SJgJoLS4DJe_IJPofT06A/s639/dprops.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="65" data-original-width="639" height="66" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTxDdrDQN8pOwiKskyUjZkbj6gCLiMGKi9cO-j9xwzLLqav4Q03o9u1dc_lIoCmgpvJAGfX-bhewu26xVLThGC58lKKnl6YW2c_1wQP3XKO8cU9ojW1SJgJoLS4DJe_IJPofT06A/w640-h66/dprops.PNG" width="640" /></a></div><br /><p> So now i can dump basically everything that is happening in a Mailbox to a CSV in the last day and you can see some of this analytics data is pretty interesting </p><p>eg</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGfUH1zJ7GFoBSzb-J2vwnfgues1QeIusrHexE9PshVndFXodgH0DvtBRMHPCtbULN5xTVV2QOd7AhUr1PLGKjcsv1jsYHLWqRaXTNwRF5Q1wI7XuqnQlPOm7VJ8W-LwqPax2JqQ/s1123/rawdata.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="344" data-original-width="1123" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGfUH1zJ7GFoBSzb-J2vwnfgues1QeIusrHexE9PshVndFXodgH0DvtBRMHPCtbULN5xTVV2QOd7AhUr1PLGKjcsv1jsYHLWqRaXTNwRF5Q1wI7XuqnQlPOm7VJ8W-LwqPax2JqQ/w640-h196/rawdata.PNG" width="640" /></a></div><br /><p>and if you looked at the data from the ActivitiesDaily</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx7VqK1mGj5fgb8JWGu4xBtFG3l3mR2vVfLvN0S99djooRVUYiWAmcNwPjJf5edc8QtzMmGGJfo8HcboHmOwBhqfUTziSrLHS8jwgZbygTMpgCbl0VGJQr_Q4gofdyM4IwgXF4EA/s1081/dailyact.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="966" data-original-width="1081" height="572" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx7VqK1mGj5fgb8JWGu4xBtFG3l3mR2vVfLvN0S99djooRVUYiWAmcNwPjJf5edc8QtzMmGGJfo8HcboHmOwBhqfUTziSrLHS8jwgZbygTMpgCbl0VGJQr_Q4gofdyM4IwgXF4EA/w640-h572/dailyact.PNG" width="640" /></a></div><br /><p>This is just a quick snippet of data on one item but things like the hourly breakdown of the number of the Messages being read per hour by a user is something that is really interesting and not something you can determine just using the raw data in a Mailbox.</p><p>While this raw data may surface elsewhere in a number of different guises and formats that are a lot of specific things you could use it for (outside of the narrative Microsoft are using for this).</p><p>The Modified change discovery script is available on Github <a href="https://github.com/gscales/Powershell-Scripts/blob/master/ChangeDiscovery.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/ChangeDiscovery.ps1</a></p><p>To run the script and look back 12 hours and dump the RawJson properties use something like</p><p>Invoke-MailboxChangeDiscovery -MailboxName gscales@mailbox.com -disableImpersonation -getJsonMetaData -secondstolookback 43200</p><p>for 24 hours use</p><p>Invoke-MailboxChangeDiscovery -MailboxName gscales@mailbox.com -disableImpersonation -getJsonMetaData -secondstolookback 86400</p><p><br /></p><p><br /></p><p><br /></p>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-18924948375258238972020-10-23T15:57:00.003+11:002020-10-23T15:57:31.503+11:00Reporting on the Favorites Shortcut items in Outlook, OWA and Outlook Mobile using PowerShell and EWS<p>One of the email UI features that I find the most useful in Outlook on the Web and Outlook mobile is the People favorites feature which saves having to do a search for historical email from particular high use contacts. Favorites is a feature that has evolved especially in Outlook on the web and Outlook mobile eg People/Persona favorites and category favorites. The way this is implemented in the Mailbox is interesting eg People/Persona favorites get their own search folder under the favoritePersonas Folder in the Non_IPM_Subtree in a Mailbox eg</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjL3Q1ujXEu4ceIJBbDF70OXh9aqlbnw2j8pdPJdJW4BZ09ACb8xC5vTpJtWqSF5x_4P66hVE3A35b_FA7EH0AVMmyqwQDjFmxL7yN0tPERbvRuqKY6WO3P56Glyau6pQdXUuFpA/s523/ofv2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="420" data-original-width="523" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjL3Q1ujXEu4ceIJBbDF70OXh9aqlbnw2j8pdPJdJW4BZ09ACb8xC5vTpJtWqSF5x_4P66hVE3A35b_FA7EH0AVMmyqwQDjFmxL7yN0tPERbvRuqKY6WO3P56Glyau6pQdXUuFpA/s320/ofv2.PNG" width="320" /></a></div><br /><p>As well as a configuration object under the </p><p>\ApplicationDataRoot\32d4b5e5-7d33-4e7f-b073-f8cffbbb47a1\outlookfavorites eg</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxMZvdgYVxfbof93u06CZe9SmjXPJ3pl6txF9HkZphX1-QC2LWzLE08VsjDCrXVysa-QbQSui9K8lCFrXUly061h-jTtyOAA29ueKM_MAYkp5G-lu4paoIIDomaofZulgYKwVNXQ/s378/ofv1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="337" data-original-width="378" height="356" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxMZvdgYVxfbof93u06CZe9SmjXPJ3pl6txF9HkZphX1-QC2LWzLE08VsjDCrXVysa-QbQSui9K8lCFrXUly061h-jTtyOAA29ueKM_MAYkp5G-lu4paoIIDomaofZulgYKwVNXQ/w400-h356/ofv1.PNG" width="400" /></a></div><p><br /></p><p>The configuration object is of interest as this tells as a lot about what type of favorites are being created and used in a Mailbox. It also can serve in a custom app if you want to reproduce the same type of favorites folder tree (you will need to use EWS for this as the Graph API is unfortunately hamstrung for this type of application). On the favourites object is a Extended property call RawJson that contains all the information that is of interest eg.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsvTU8iS4-kANsDU2A0IDevdkeTimcAVSNCTx6wTN0-Of0saCflA1aXZNhhGM3v2bCjd1xJivPNxNDiHWB8cMBBAnMl_rZFWkcdFRVGUKrfk_xY0H5A4mMJkOcXnpu90Y9cU21HQ/s823/exprov3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="410" data-original-width="823" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsvTU8iS4-kANsDU2A0IDevdkeTimcAVSNCTx6wTN0-Of0saCflA1aXZNhhGM3v2bCjd1xJivPNxNDiHWB8cMBBAnMl_rZFWkcdFRVGUKrfk_xY0H5A4mMJkOcXnpu90Y9cU21HQ/w640-h318/exprov3.PNG" width="640" /></a></div><br /><p><br /></p><p>For this post I've created an EWS script that first finds the above folder and then gets all the Shortcut Items in that folder and retrieves the rawJson extended property on each of the ShortCut Items. It then does a little bit more data abstraction from the Json to make it into a report-able object. So for instance we can do a</p><p>Quick view of what Shortcuts are being used (which clients,Last-Visted(for groups)and type) eg</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy6V6zAXBfM9AgIjotdKVe-ZR4AkqWnxymn80Bx37QRt4mZsyx3Udogl-XU532yruw4JiYQXueMcccEQ8LR-WWuFsia6EVJ0xuOmTW3x7a1x-iJVvaQQS6NAp7IJ32cZyWp7okig/s795/ofv4.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="795" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy6V6zAXBfM9AgIjotdKVe-ZR4AkqWnxymn80Bx37QRt4mZsyx3Udogl-XU532yruw4JiYQXueMcccEQ8LR-WWuFsia6EVJ0xuOmTW3x7a1x-iJVvaQQS6NAp7IJ32cZyWp7okig/w640-h306/ofv4.PNG" width="640" /></a></div><br /><p>In the above Legacy = Outlook Desktop and Outlook Services is the Outlook Mobile client on Android</p><p>Just the Personal favorites</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrEEDMUtQejD_zb1Oj6ebxQuyhEGhkkruvrdtSlNLYteCDS1OmQIYsM7z2qHDmOT3bXAL2ajoUj8Oz5a9j5nN_nEFM9dct-A9hyphenhypheni8MyqnTxMwwbkwmenl6U_HZkGnCwejXGzakDA/s843/ofv5.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="698" data-original-width="843" height="530" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrEEDMUtQejD_zb1Oj6ebxQuyhEGhkkruvrdtSlNLYteCDS1OmQIYsM7z2qHDmOT3bXAL2ajoUj8Oz5a9j5nN_nEFM9dct-A9hyphenhypheni8MyqnTxMwwbkwmenl6U_HZkGnCwejXGzakDA/w640-h530/ofv5.PNG" width="640" /></a></div><br /><p>The script for this post is available on GitHub <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Favourites.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Favourites.ps1</a></p><p><br /></p>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-17459404018489851342020-10-01T16:02:00.005+10:002020-10-01T16:02:52.975+10:00How to access and restore deleted Items (Recoverable Items) in the Exchange Online Mailbox dumpster with the Microsoft Graph API and PowerShellAs the information on how to do this would cover multiple posts, I've bound this into a series of mini post docs in my GitHub Repo to try and make this subject a little easier to understand and hopefully navigate for most people. <div><br /></div><div>The Binder index is <a href="https://gscales.github.io/Graph-Powershell-101-Binder/" style="box-shadow: none; box-sizing: border-box; color: #0366d6; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 14px; outline: none;">https://gscales.github.io/Graph-Powershell-101-Binder/</a> </div><div><br /></div><div>The topics covered are</div><div><br /></div><div><a href="https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Mailbox-Dumpster/Getting%20the%20Recoverable%20Items%20(Dumpster%20v2)%20Folders.md">How you can access the Recoverable Items Folders (and get the size of these folders) </a></div><div><br /></div><div><a href="https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Mailbox-Dumpster/Getting%20the%20Recoverable%20Items%20in%20a%20Mailbox.md">How you can access and search for items in the Deletions and Purges Folders and also how you can Export an item to an Eml from that folder</a></div><div><br /></div><div><a href="https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Mailbox-Dumpster/Restoring%20a%20Items%20to%20where%20it%20was%20deleted%20from.md">How you can Restore a Deleted Item back to the folder it was deleted from (using the Last Active Parent FolderId)</a></div><div><br /></div><div>and the sample script is located </div><div><br /></div><div><a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/Dumpster.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/Dumpster.ps1</a></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-17247682330332614542020-09-17T17:20:00.003+10:002020-09-17T17:20:28.732+10:00Creating a Mailbox Search Folder based on a Message Category using the Microsoft Graph and Powershell<p>Searching on the Categories property of an Email can pose a challenge because this property is a Multi-valued String property (which aren't that common in email) eg in a Message the property may look like the following</p>
<p><img alt="image-20200917131803998" src="https://github.com/gscales/Graph-Powershell-101-Binder/raw/master/bin/Images/image-20200917131803998.png" /></p>
<p>So this needs to be queried in a different way then a normal String or single valued property in an Email would, where you could use a number of filter options (eg equal, contains,startswith). In EWS it was only possible to query this property using AQS because of the way SearchFilters translated to the underlying ROP based restrictions used by the Exchange Mailbox Store. In the Microsoft Graph the Linq format in Filters does translate more favourably so can be used eg the following simple query can find Messages in a folder based on a specific category</p>
<pre><code>https://graph.microsoft.com/v1.0/me/messages?filter=Categories/any(a:a+eq+'Green+Category')
</code></pre>
<p>you can create a SearchFolder based on this query which would search all folder in a Mailbox which would produce a SearchFolder Creation request like</p>
<pre><code>{
"@odata.type": "microsoft.graph.mailSearchFolder",
"displayName": "Green Email",
"includeNestedFolders": true,
"sourceFolderIds": ["MsgFolderRoot"],
"filterQuery": "Categories/any(a:a+eq+'Green+Category')"
}
</code></pre>
<p>Based on a previous post on <a href="https://github.com/gscales/Graph-Powershell-101-Binder/blob/master/Search/Creating%20a%20SearchFolder%20in%20a%20Mailbox.md">creating Search Folders</a> I have this script <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/SearchFolder.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/SearchFolder.ps1</a> which you can use to Create a Category Search folder as well as other SearchFolder CRUD operations eg</p>
<pre><code>Invoke-CreateCategorySearchFolder -MailboxName user@domain.onmicrosoft.com -SearchFolderName CategoryTest -CategoryName "Category 1"
</code></pre>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-51826685029571297522020-09-10T18:38:00.001+10:002020-09-10T18:43:41.235+10:00The MailboxConcurrency limit and using Batching in the Microsoft Graph API<p>If your getting an error such as Application is over its MailboxConcurrency limit while using the Microsoft Graph API this post may help you understand why.</p><p><b>Background </b></p><p>The Mailbox concurrency limit when your using the Graph API is 4 as per <a href="https://docs.microsoft.com/en-us/graph/throttling#outlook-service-limits?WT.mc_id=M365-MVP-10145">https://docs.microsoft.com/en-us/graph/throttling#outlook-service-limits</a> . This is evaluated for each app ID and mailbox combination so this means you can have different apps running under the same credentials and the poor behavior of one won't cause the other to be throttled. If you compared that to EWS you could have up to 27 concurrent connections but they are shared across all apps on a first come first served basis.</p><p><b>Batching</b></p>Batching in the Graph API is a way of combining multiple requests into a single HTTP request. Batching in the Exchange Mail API's EWS and MAPI has been around for a long time and its common, for email Apps to process large numbers of smaller items for a variety of reasons. Batching in the Graph is limited to a maximum of 20 items per batch.<div><br /></div><div><b>Problems</b></div><div><br /></div><div>When you make a batch request like the following against the Microsoft Graph to Get the child folders or particular root folders in a Mailbox (but it can be basically any other mailbox request)</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1UoUo2COGe9y_RzETTEbWzdY4S8yet8mH17cJU6TcDvvPL59aEwNx8eLxMuKIPs6guxG7scZV9Ax8AqgUJU97oWohI8KO-AeeHTzqM0txoM82DFCfV5JfD36HauE0OE4-2Uf_fw/s614/batchexample.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="614" data-original-width="558" height="781" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1UoUo2COGe9y_RzETTEbWzdY4S8yet8mH17cJU6TcDvvPL59aEwNx8eLxMuKIPs6guxG7scZV9Ax8AqgUJU97oWohI8KO-AeeHTzqM0txoM82DFCfV5JfD36HauE0OE4-2Uf_fw/w711-h781/batchexample.png" width="711" /></a></div><br /><div>If you have any more then 4 requests in the batch you will get a concurrency error for each request greater then 4. This goes back to the Mailbox concurrency limit being 4 by default, even though this is one request each of the requests in the batch gets executed asynchronous by default and this is what causes the limit to be exceeded. So the effective default batch limit for Mailboxes is 4.</div><div><br /></div><div>If you wanted to have all the requests in your 20 item batch fulfilled in the one request you could use the dependsOn header eg</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHnUKJVXBNF4cNBnV5-N6H4zeAE67jvWzK_-FLxQJNBpX9uLszCnD0ve3qy0Q2RKCGFwhcIbN1-vDwBfGsTXeeRXzimCrK-oODyA8X7sWzqL6M-lla8m3PFW4NpFzj0lVzlD4gnA/s584/batchexampledp.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="299" data-original-width="584" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHnUKJVXBNF4cNBnV5-N6H4zeAE67jvWzK_-FLxQJNBpX9uLszCnD0ve3qy0Q2RKCGFwhcIbN1-vDwBfGsTXeeRXzimCrK-oODyA8X7sWzqL6M-lla8m3PFW4NpFzj0lVzlD4gnA/w625-h320/batchexampledp.png" width="625" /></a></div><br /><div>This makes the request fully sequential meaning that only one connection is ever opened to the Exchange Mailbox.</div><div><br /></div><div>The problem with this is in a lot of real world scenarios its much slower eg enumerating all the folders in my mailbox using depends on with 20 Item batches took 14 seconds with 4 item batches async took 8. (note neither of these is good result vs a Deep Traversal in EWS but there is currently no alternative in the graph). The batches weren't optimized because of the random hierarchy in my Mailbox but it would make sense that 4 cloud threads is going to win over 1 sequential one even with the greater number of client requests. <br /><div><br /></div><div><b>So what should you do</b></div><div><ol style="text-align: left;"><li>test,test,test to see what works best for you</li><li>Make sure you always create a separate App registration for your apps (never reuse)</li><li>Think about your context, if there is a chance your going to have multiple instances of your app running at the same time using the same user think about your batching strategy .</li><li>Make sure you process the throttling responses, retrying your op at least once shouldn't be a big deal</li></ol></div><div><b>My 2 cents</b></div><div><b><br /></b></div><div>The way this API behaves in this scenario fails the pub test for me at the moment. If your using batching in EWS code then this isn't really the equivalent. eg someone spinning up 4 threads in EWS or MAPI to do the equivalent of what batching is doing in the Graph wouldn't be considered optimal (while they have tried to mitigate the need to use batching in the first place vs EWS and MAPI). The other side of the coin is its something you can exploit to gain some performance vs single op graph code.</div><div><b><br /></b></div><div><b> </b></div></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-13021959112081637932020-08-28T10:44:00.002+10:002020-08-28T10:44:43.100+10:00How to Approve and Reject Moderation Emails in Exchange Online with the Microsoft Graph API and Powershell<p>A while ago I published <a href="https://gsexdev.blogspot.com/2012/07/ews-managed-api-and-powershell-how-to.html">this </a>blog post about doing this using EWS and a few people have recently asked if it is also possible to do this with the Graph API(which it is) so I've decided to include this one in my Graph Basics series.</p><p><b>Moderation</b></p>Moderation is an Exchange feature that was introduced in Exchange 2010 that allows the Human control of mail flow to a distribution group or mailbox see <a href="https://docs.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/manage-message-approval">https://docs.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/manage-message-approval</a> for more detail.<div><br /></div><div><b>Moderation Approve/Reject Messages</b></div><div><br /></div><div>When a Message is requiring moderation an email is sent to one (or more) moderators requesting approval. In the Graph you can get these moderation messages by filtering on the MessageClass property. Because this isn't a first-class property like it was in EWS you need to use the singleValueExtendedProperties representation of the property. eg in the Graph a Request like this</div><div><br /></div><div><pre class="lang-cs prettyprint prettyprinted" style="border-radius: 3px; border: 0px; box-sizing: inherit; color: #242729; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-size: 13px; font-stretch: inherit; font-variant-east-asian: inherit; font-variant-numeric: inherit; line-height: inherit; margin-bottom: 1em; margin-top: 0px; max-height: 600px; overflow-wrap: normal; overflow: auto; padding: 12px 8px; vertical-align: baseline; width: auto;"><code style="border: 0px; box-sizing: inherit; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline; white-space: inherit;"><span class="pln" color="" style="border: 0px; box-sizing: inherit; font-family: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">https</span><span class="pun" color="" style="border: 0px; box-sizing: inherit; font-family: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">:</span><span class="com" color="" style="border: 0px; box-sizing: inherit; font-family: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">//graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages?
$filter=singleValueExtendedProperties/any(ep:ep/id eq 'String 0x001a' and ep/value eq 'IPM.Note.Microsoft.Approval.Request')</span></code><br /></pre>To Approve or Reject a Message that requires moderation you need to do a few different things to create the approval/rejection Message</div><div><br /></div><b>1. ItemClass</b>- Set the ItemClass on the Message your sending in the Graph use the singleValueExtendedProperties like<br /><br />Approval - <br /><br /><pre class="lang-cs prettyprint prettyprinted" style="border-radius: 3px; border: 0px; box-sizing: inherit; color: #242729; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-size: 13px; font-stretch: inherit; font-variant-east-asian: inherit; font-variant-numeric: inherit; line-height: inherit; margin-bottom: 1em; margin-top: 0px; max-height: 600px; overflow-wrap: normal; overflow: auto; padding: 12px 8px; vertical-align: baseline; width: auto;"><code style="border: 0px; box-sizing: inherit; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline; white-space: inherit;"><span class="pln" color="" style="border: 0px; box-sizing: inherit; font-family: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">{
id = "String 0x001A"
value = "IPM.Note.Microsoft.Approval.Reply.Aprove"
}</span></code></pre>Rejection -<br /><div><pre class="lang-cs prettyprint prettyprinted" style="border-radius: 3px; border: 0px; box-sizing: inherit; color: #242729; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-size: 13px; font-stretch: inherit; font-variant-east-asian: inherit; font-variant-numeric: inherit; line-height: inherit; margin-bottom: 1em; margin-top: 0px; max-height: 600px; overflow-wrap: normal; overflow: auto; padding: 12px 8px; vertical-align: baseline; width: auto;"><code style="border: 0px; box-sizing: inherit; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline; white-space: inherit;">{
id = "String 0x001A"
value = "IPM.Note.Microsoft.Approval.Reply.Reject"
}</code></pre><span face="" style="background-color: white; color: #660000;"><span style="color: black;"><b>2. Subject </b>- Use the Normalized subject from the approval Request email and then prepend Accept or Reject </span></span><span face="" style="background-color: white; color: #660000;"><span style="color: black;"><br /></span></span><br style="background-color: white; color: #292929; font-family: lora, serif; font-size: 20px;" /><span face="" style="background-color: white; color: #660000;"><span style="color: black;"><b>3. RecipientTo - </b>Needs to be set to the Microsoft Exchange Approval Assistant Address (Sender of the Moderation Message)</span></span><b style="background-color: white; color: #292929; font-family: lora, serif; font-size: 20px;"><span style="color: #660000;"><span style="color: black;"><br /></span></span></b><br style="background-color: white; color: #292929; font-family: lora, serif; font-size: 20px;" /><span face="" style="background-color: white; color: #660000;"><span style="color: black;"><b>4. VerbResponse - </b></span></span>This tells Exchange if you want to Approve or Reject the Message</div><div><span face="" style="background-color: white; color: #660000;"><span style="color: black;"><pre class="lang-cs prettyprint prettyprinted" style="border-radius: 3px; border: 0px; box-sizing: inherit; color: #242729; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-size: 13px; font-stretch: inherit; font-variant-east-asian: inherit; font-variant-numeric: inherit; line-height: inherit; margin-bottom: 1em; margin-top: 0px; max-height: 600px; overflow-wrap: normal; overflow: auto; padding: 12px 8px; vertical-align: baseline; width: auto;"><code style="border: 0px; box-sizing: inherit; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline; white-space: inherit;">{
id = "String {00062008-0000-0000-C000-000000000046} Id 0x8524"
value = "Approve"
}</code></pre></span></span></div><div><span face="" style="background-color: white; color: #660000;"><span style="color: black;"><b>5. <a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxomsg/b90b2c88-fe59-4fd9-a6a7-ec18833654d6">PidTagReportTag Property</a> -</b></span></span> This is a critical property you get from the Approval Request which is used to correlate the Approval Response.<br style="background-color: white; color: #292929; font-family: lora, serif; font-size: 20px;" /><pre class="lang-cs prettyprint prettyprinted" style="background-color: white; border-radius: 3px; border: 0px; box-sizing: inherit; color: #242729; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-size: 13px; font-stretch: inherit; font-variant-east-asian: inherit; font-variant-numeric: inherit; line-height: inherit; margin-bottom: 1em; margin-top: 0px; max-height: 600px; overflow-wrap: normal; overflow: auto; padding: 12px 8px; vertical-align: baseline; width: auto;"><code style="border: 0px; box-sizing: inherit; font-family: consolas, menlo, monaco, "lucida console", "liberation mono", "dejavu sans mono", "bitstream vera sans mono", "courier new", monospace, sans-serif; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline; white-space: inherit;">{
id = "Binary 0x31"
value = $ApprovalMail.singleValueExtendedProperties[0].value
}</code></pre><span style="background-color: white;">Once you have all that information you can put a message together in JSON and send that using the SendMail endpoint in the Microsoft Graph</span></div><div><span style="background-color: white;"><br /></span></div><div><span style="background-color: white;">I've put a full sample script for this on GitHub </span><a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/Moderation.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/Moderation.ps1</a></div><div><br /></div><div>Invoke-ApproveModerationRequest will approve the last moderation request in a Mailbox </div><div><br /></div><div>Invoke-RejectModerationRequest will reject the last moderation request in a Mailbox</div><div><br /></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-29223485110198101372020-08-18T16:16:00.000+10:002020-08-18T16:16:28.513+10:00Testing and Sending email via SMTP using Opportunistic TLS and oAuth in Office365 with PowerShellAs well as EWS and Remote PowerShell (RPS) other mail protocols POP3, IMAP and SMTP have had OAuth authentication enabled in Exchange Online (Official announcement <a href="https://www.blogger.com/u/1/#">here</a>). A while ago I created <a href="https://gsexdev.blogspot.com/2019/10/how-to-test-opportunistic-tls-over-smtp.html">this script</a> that used Opportunistic TLS to perform a Telnet style test against a SMTP server using SMTP AUTH. Now that oAuth authentication has been enabled in office365 I've updated this script to be able to use oAuth instead of SMTP Auth to test against Office365. I've also included a function to actually send a Message.<div><br /></div><div><b>Token Acquisition </b></div><div><b><br /></b></div><div>To Send a Mail using oAuth you first need to get an Access token from Azure AD there are plenty of ways of doing this in PowerShell. You could use a library like MSAL or ADAL (just google your favoured method) or use a library less approach which I've included with this script . Whatever way you do this you need to make sure that your application registration <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app">https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app</a> has the following permissions scope https://outlook.office.com/SMTP.Send . One thing to note is that Application permissions aren't supported at the moment so you need to use one of the Delegate Authentication flows (which means having a service account for SendAs other user scenarios).</div><div><br /></div><div><div><br /></div><div><b>Adding the SASL XOAUTH2 header</b></div><div><b><br /></b></div><div>This was really the only thing I needed to change in the initial script apart from adding in code to get the OAuth token. The SASL header looks like the following</div><div><br /></div><div><span face="" style="background-color: #fafafa; color: #171717; font-size: 14px; white-space: pre;">base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")</span></div><div><br /></div><div>The ^A character is a <a href="https://en.wikipedia.org/wiki/Control_character#:~:text=%22format%20effectors%22.-,In%20ASCII,the%20end%20of%20a%20string.">Control code</a> which relates to character 01 in the ASCII Character set which corresponds to SOH (Start of Heading).</div></div><div><br /></div><div><b>Testing out OAuth</b></div><div><b><br /></b></div><div><b>I've put the Script for this post here </b><a href="https://github.com/gscales/Powershell-Scripts/blob/master/TLS-SMTP-Oauth-Mod.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/TLS-SMTP-Oauth-Mod.ps1</a></div><div><b><br /></b></div><div>To test out oAuth against Office365 use something like the following</div><div><br /></div><span style="color: #444444; font-size: x-large;">Invoke-TestSMTPTLSwithOauth -ServerName smtp.office365.com -SendingAddress gscales@datarumble.com -To gscales@datarumble.com -ClientId {your AzureApp Registration Id} -RedirectURI {Your redirect URI}</span><div><br /></div><div>which should give you an output like</div><div> <div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaUcAcj0RaCxvpghBLjLRIMzwCKmSnV3s9Lzd88Tu4pjrjPzOAc-JSETGGjVF5XZqgujNiiOFfqObfFPbMknta6unOQYctYnkT3SdPIGjfLJ80NUVKKRdwmPNNw3GtrDHup8Ofyg/s932/oauthsend.JPG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="395" data-original-width="932" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaUcAcj0RaCxvpghBLjLRIMzwCKmSnV3s9Lzd88Tu4pjrjPzOAc-JSETGGjVF5XZqgujNiiOFfqObfFPbMknta6unOQYctYnkT3SdPIGjfLJ80NUVKKRdwmPNNw3GtrDHup8Ofyg/d/oauthsend.JPG" /></a></div></div><div><br /></div><div><b>Actually Sending a Message</b></div><div><b><br /></b></div><div>As well as the SMTP Mail Conversation Test function, I also included a function that would allow you to actually send an Email Message using SMTP,TLS and oAuth. As the System.Net.Mail.Message class is now obsolete (which also takes Send-MailMessage along with it in term of sending via oAuth) there is no way of easily sending a Message without a third party library like MailKit (which is actually a really good library and supports things like Dkim etc). To get the Message Send to work I used the System.Net.Mail.Message and then used reflection to substitute a MemoryStream into the Send function so I could get the Message from this class as a MimeStream. This stream can then be sent as part of the SMTP DATA verb (minus the X-Sender/X-Reciever Headers). As mentioned in the Token Acquisition if you want to send as another user you need to have the normal Exchange SendAS permission granted to the Delegate account you using. To Send a Message with an Attachment use something like</div><div><br /></div><span style="background-color: white; font-size: x-large;">Invoke-SendMessagewithOAuth -ServerName smtp.office365.com -SendingAddress jcool@</span><span style="background-color: white; font-size: x-large;">somedomain</span><span style="background-color: white; font-size: x-large;">.com -To gscales@somedomain.com -Subject "This is a Test Message" -Body "Test Body" -AttachmentFileName "c:\temp\olm.csv" -userName gscales@datarumble.com -ClientId {your AzureApp Registration Id} -RedirectURI {Your redirect URI}</span><div><br /></div><div>Which will produce a conversation like</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgN8jAqLaAkfyT71JrQrKaJ2DjFwHnc587qXtzZiHTL4L69yAcFS-XsUNOhHBLpIbed3ksJW7KXzsPbW4o6JLtKAve0zyd4pk99javA7Ewff0_1piasT31N_WK4nOwUkGhjvd96OA/s1047/sendasmessage.JPG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="715" data-original-width="1047" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgN8jAqLaAkfyT71JrQrKaJ2DjFwHnc587qXtzZiHTL4L69yAcFS-XsUNOhHBLpIbed3ksJW7KXzsPbW4o6JLtKAve0zyd4pk99javA7Ewff0_1piasT31N_WK4nOwUkGhjvd96OA/s640/sendasmessage.JPG" width="640" /></a></div><div><br /></div><div><br /></div><div><b>MailKit</b></div><div><b><br /></b></div><div>If your reading this post because you have existing code that needs to be converted to use oAuth then the library you want to use in either PowerShell or C# is <a href="https://github.com/jstedfast/MailKit">MailKit</a> . For Powershell I would check out the <a href="https://www.powershellgallery.com/packages/Mailozaurr/0.0.9">https://www.powershellgallery.com/packages/Mailozaurr/0.0.9</a> module that looks pretty good, with C# here is a simple example that use MSAL and MailKit</div><div><script src="https://gist.github.com/gscales/57216f70aef4b79ff2c0a0053152d5d0.js"></script><br /></div><div><br /></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-23152690369427128152020-07-31T12:00:00.000+10:002020-07-31T12:00:26.781+10:00Graph Basics Get the User Photo and save it to a file (and resize it) with PowerShellThis is part 2 of my Graph Basic's series and this post is born out of an actual need that I had over the last week which was to get a user photo from the Microsoft Graph and save it as a custom size and different image type. Like many things there are multiple ways of doing this but the Microsoft Graph <a href="https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0">GetPhoto endpoint</a> is pretty straight forward and delivers the image in one of the following formats 48x48, 64x64, 96x96, 120x120, 240x240, 360x360, 432x432, 504x504, and 648x648. Because I wanted to use the photo on a <a href="https://www.elgato.com/en/gaming/stream-deck">Elgato stream deck</a> this required the size be 72x72 so I needed some extra code to do the resize of the photo and change the format from a jpeg to png.<div><br /></div><div><b>Getting the user-photo from the Microsoft Graph </b></div><div><br /></div><div>Before you can get the user's photo from Microsoft Graph you need to make sure the application registration you are using has one of the following permissions</div><div><br /></div>User.Read, User.ReadBasic.All, User.Read.All, User.ReadWrite, User.ReadWrite.All<br /><br />Then after you have obtain the Token make a request to Graph like<div><br /></div><div>https://graph.microsoft.com/v1.0/users('user@domain.com')/photos/240x240/$value <br /><div><br /></div></div><div>The results of this Get Request will be a Jpeg image 240x240 for that user, if you use Invoke-WebRequest you can simply then just use the -Filename parameter to specify the output filename and that it.</div><div><br /></div><div>My function to download and resize the user photo looks like</div><div><br /><script src="https://gist.github.com/gscales/6ba458d3b5ae05e269d9f30457990a28.js"></script></div><div><br />So if you just want the user photo in the native format (which will be jpeg) use</div><div><br /></div><div>Get-GraphUserPhoto -MailboxName user@domain.com -Filename c:\temp\dnlPhoto.jpeg -PhotoSize 240x240</div><div><br /></div><div><div>If you want to get the user photo and resize it (72x72) and save it as a png</div><div><br /></div><div>Get-GraphUserPhoto -MailboxName user@domain.com -Filename c:\temp\dnlPhoto.png -PhotoSize 240x240 -ReSizeDimension 72 -ReSizeImageForamt Png</div></div><div><br /></div><div>I've put a download of the full script <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/PhotoOps.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/PhotoOps.ps1</a></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-17307954499099885312020-07-24T10:41:00.000+10:002020-07-24T10:41:33.732+10:00Modifying your Exchange Online PowerShell Managed Code to use oAuth and MSALWhile not as popular these days many .net developers may have in the past used Managed code to run Exchange Online PowerShell cmdlets to do things like assign Mailbox Permissions or run other EXO PowerShell Cmdlets to get reporting information where no other alternatives where available (or are still available). The majority of these code bases are most likely using basic authentication using something like<div><br /></div><script src="https://gist.github.com/gscales/7c3b82cec8059797b81b917bd45bcfa8.js"></script><div>
Or maybe some of the examples in <a href="https://docs.microsoft.com/en-us/exchange/client-developer/management/how-to-get-a-list-of-mail-users-by-using-the-exchange-management-shell">https://docs.microsoft.com/en-us/exchange/client-developer/management/how-to-get-a-list-of-mail-users-by-using-the-exchange-management-shell</a></div><div><br /></div><div>In this post I'm going to cover how to change your existing code, you might want to consider however making use of some of the new ExchangeV2 Powershell module functionality to improve performance and security . But to migrate existing code to use oAuth from Basic Authentication is relatively straight forward</div><div><ol style="text-align: left;"><li>You will need some code to do the Authentication, for this I'm going to use the <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-overview">MSAL library</a> because its both the recommended library from Microsoft and its easy to use. </li><li>You should create your own <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app">Azure App registration</a> and consent to it that has the Exchange.Manage Permissions eg</li></ol></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJ0k7W1EzqcV0UiZJ8uxKogkiYyPg1TYPxrnOpkmQU5LymGDqNtIgDoMHJfweAaxFxvjQ8ehP0KeB_sbyrCOllFvQrgneIKOF8TOTGIE56l6SF7K5y_4eIpAg3X6bNw0TW1Wn_rA/s851/exm.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="573" data-original-width="851" height="420" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJ0k7W1EzqcV0UiZJ8uxKogkiYyPg1TYPxrnOpkmQU5LymGDqNtIgDoMHJfweAaxFxvjQ8ehP0KeB_sbyrCOllFvQrgneIKOF8TOTGIE56l6SF7K5y_4eIpAg3X6bNw0TW1Wn_rA/w625-h420/exm.png" width="625" /></a></div><div><br /></div><div>(If you can't create your own app registration you can use the well-known ClientId from the V2 PowerShell Module which I've used in the below samples).</div><div><br /></div><div>Once you have your authentication code generating a Token you then use that as the Password in the PSCrednetial object you pass in the WSManConnectionInfo object. The one thing you need to change is the WSManConnection URI to include the parameters DelegatedOrg which should be set to your domain and add BasicAuthToOAuthConversion=true eg so your connection string should look like</div><blockquote><div>https://outlook.office365.com/powershell-liveid?DelegatedOrg=youdomain.onmicrosoft.com&BasicAuthToOAuthConversion=true</div></blockquote><div>eg an Interactive Auth sample to run Get-Mailbox would look like </div><div><br /><script src="https://gist.github.com/gscales/d9d49fcab122f4327035b98cabfd1b41.js"></script></div><div>If you need your code to run non-interactively with a set of credentials you can use the ROPC grant like</div><script src="https://gist.github.com/gscales/66cacbef64087a5c89587f1ce6a739fa.js"></script><div><br /></div><div> </div><div><br /></div><div><br /></div><div><br /></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-4167486763426868162020-07-07T12:56:00.001+10:002020-07-07T12:56:45.881+10:00Using 2 Authentication factors (for MFA) in an unattended PowerShell Script MFA (Multi Factor Authentication) is great at making the Authentication process more secure in Exchange Online but can be challenging in Automation scenarios. I originally wrote this code for something that I wanted to run unattended on a RasberryPi that was running PowerShell that i wanted to use MFA on and where i wanted to avoid going down the path of using the 90 day RefreshToken/device code method and I also didn't want to use App Authentication via Certificates or Client Secrets.<div><br /></div><div>Interestingly while i was writing this post Microsoft just announced Certificate based Modern Auth in Exchange Online PowerShell <a href="https://techcommunity.microsoft.com/t5/exchange-team-blog/modern-auth-and-unattended-scripts-in-exchange-online-powershell/ba-p/1497387">https://techcommunity.microsoft.com/t5/exchange-team-blog/modern-auth-and-unattended-scripts-in-exchange-online-powershell/ba-p/1497387</a> . This article also links to the Secure App Model <a href="https://docs.microsoft.com/en-us/powershell/partnercenter/multi-factor-auth?view=partnercenterps-3.0#exchange">https://docs.microsoft.com/en-us/powershell/partnercenter/multi-factor-auth?view=partnercenterps-3.0#exchange</a> which is the way Microsoft are recommending you handle MFA in unattended delegate scenarios so this is an alternative to this if you can't go down that path .</div><div><br /></div><div>One thing to note about any unattended method is that by there nature (eg because they aren't interactive) they can all potentially be unraveled by a bad actor if they make it passed the outer security of the machine where the script is running. Eg an unlocked machine or week logon security to that machine that would allow an attacker to access the stored RefreshToken/Certificate/SharedKey (no matter where they are stored or how they are encrypted). However the more factors you can put into your unattended process eg encrypting the stored cert/key etc the slower its going to be for anybody trying to unravel it. The gold standard would be an Secure Azure VM where the secrets themselves are in a KeyVault and use a managed identity but in the real world this is not always possible. In security terms you should always consider what the implications are when the security around your unattended process fails (and not if it fails). </div><div><br /></div><div><b>Factors</b></div><div><b><br /></b></div><div>The two factors in my script that I'm going to be using is firstly a Username and Password which is really the only Primary Authentication method that currently lends itself to be used unattended (eg FIDO and OATH requires some level of interactivity). The additional second factor I'm using is TOTP (Time based One Time passwords) which are relatively easy to generate as they are based on <a href="https://tools.ietf.org/html/rfc6238">https://tools.ietf.org/html/rfc6238</a> . Other people have already created a few Powershell functions for generating TOTP's so the one I decided to use was <a href="https://www.powershellgallery.com/packages/SecurityFever/2.2.0/Content/SecurityFever.psm1">https://www.powershellgallery.com/packages/SecurityFever/2.2.0/Content/SecurityFever.psm1</a> which just requires that you pass in the SharedSecret that you get when you add a (3rd Party Authenticator)to your account in Office365 as an additional authentication method . To do this you should select to add an Authenticator application as an additional authentication method and select the "Configure app without notifications" from the screen that shows the QRCode. You then should be presented with a secret (shared)key (if it has spaces in it you will need to remove these) which you then need to feed into the Get-TimeBasedOneTimePassword cmdlet which will provide the verification code to successfully add the authenticator eg</div><div><br /></div><div>Get-TimeBasedOneTimePassword -SharedSecret txxxxxxxxxxxx</div><div><br /></div><div>You then need to store this sharedsecret as securely as you can as you will use this each time you want to logon to generate the TOTP factor for the authentication. A few ways of storing this is firstly use an Azure key vault, if you have a Windows machine your running it on then consider the credential store which can be used easily via another Powershell module <a href="https://www.powershellgallery.com/packages/CredentialManager/2.0">https://www.powershellgallery.com/packages/CredentialManager/2.0</a> and there's a simple demo of using it <a href="https://github.com/pnp/PnP-PowerShell/wiki/How-to-use-the-Windows-Credential-Manager-to-ease-authentication-with-PnP-PowerShell">https://github.com/pnp/PnP-PowerShell/wiki/How-to-use-the-Windows-Credential-Manager-to-ease-authentication-with-PnP-PowerShell</a> . (I would suggest you put a second layer of encryption on top of this so whatever goes into the credential store is also encrypted)</div><div><br /></div><div><b>MFA Code</b></div><div><b><br /></b></div><div>So far I've been using a whole bunch of other peoples code but now comes to the bit i wrote which does the MFA authentication using OpenId connect <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc">https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc</a> and then gets an AccessToken once an authorization_code is acquired. This code is relatively straight forward it starts the normal browser based authentication flow maintaining the session using Invoke-WebRequest's -WebSession parameter and parses out the context, flow and canary tokens from the response which are needed in various parts of the process. For the additional authentication factor the TOTP is used.</div><div><br /></div><div><b>Example </b></div><div><b><br /></b></div><div>While my code was written for EWS primarily running on a Rasberry PI a more relevant sample is a Exchange Online PowerShell connection eg </div><div><br /><script src="https://gist.github.com/gscales/1b7973f6fe194804270c9934a887b705.js"></script></div><div>So this simple example uses the CredentialManger PS module to grab a ShareSecret from the credential store and generate a TOTP and then you have a normal set of PSCredentials and the "BasicAuthToOAuthConversion=true" query-string does the rest.</div><div><br /></div><div>A few things to remember is that Access Tokens are only good for 1 hour so if you expect your script to run for longer then this you will need to put in a method of refreshing the Token (or just generate a new access token). </div><div><br /></div><div>I've put the code for the AzureMFAOTPv2 module on GitHub here <a href="https://github.com/gscales/Powershell-Scripts/blob/master/AzureMFAOTPv2.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/AzureMFAOTPv2.ps1</a></div><div><br /></div><div> </div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><div><br /></div><div><br /></div></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-68634533074807117612020-06-11T20:43:00.001+10:002020-06-11T20:43:05.515+10:00Modifying your EWS WSDL Proxy Code for Modern Authentication This is a follow-on from my last post on <a href="https://gsexdev.blogspot.com/2020/06/modifying-your-ews-managed-api-code-to.html">Modifying your EWS Managed API code to use Hybrid Modern Authentication against OnPrem Mailboxes</a> . If instead of the EWS Managed API you are using EWS Proxy Code (generated from the EWS WSDL) and you want to migrate it to using Modern Authentication for Office365 and/or Hybrid here's a method you can use using the <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-overview">MSAL Authentication library</a>.<div><br /></div><div>Unlike the EWS Managed API the WSDL generated proxy classes and specifically the ExchangeServiceBinding class doesn't have any provision to use Token Credentials. One way of implementing this in .NET is to take advantage of Polymorphism and create a new class that is derived from the ExchangeServiceBinding class and then override the method GetWebResponse from this class (which is actually derived from the SoapHttpClientProtocol class which contains the actual method we are going to override <a href="https://docs.microsoft.com/en-us/dotnet/api/system.web.services.protocols.soaphttpclientprotocol.getwebrequest?view=netframework-4.8">https://docs.microsoft.com/en-us/dotnet/api/system.web.services.protocols.soaphttpclientprotocol.getwebrequest?view=netframework-4.8</a> )</div><div><br /></div><div>At the same time we can also add the X-AnchorMailbox header into the request which is also recommended for any Exchange Online requests you make. And because this method is called before every EWS Request we can place our Token Refresh code in there. In this example I'm using which uses the MSAL all you need to include is code that fetches the token from the TokenCache, this will trigger a Token Refresh if need or ultimately throw to Interaction if the Refresh Token isn't available. So here is a basic C# Console App that can do Hybrid/Modern Auth discover using the MSAL library. If you want the project files you can download them from <a href="https://github.com/gscales/gscales.github.io/blob/master/EWSWSDLoAuthExample.zip">here</a></div><div><br /><script src="https://gist.github.com/gscales/a946025d143436ec64a8cc4cb27693c0.js"></script></div><div><br /></div><div><br /></div><div> </div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-17705410477133004892020-06-02T11:14:00.001+10:002020-06-02T11:14:31.353+10:00Modifying your EWS Managed API code to use Hybrid Modern Authentication against OnPrem Mailboxes<div>In this post I'm going to look at what you need to do in your EWS Managed API code to support using Hybrid Modern Authentication where previously you've been using Basic or Integrated Authentication (both of which are susceptible to password spray attacks). If you don't know what <a href="https://docs.microsoft.com/en-us/office365/enterprise/hybrid-modern-auth-overview">Hybrid Modern Authentication </a> is put simply it brings to Exchange OnPrem email clients the security benefits of Modern Authentication offered by Azure AD to Office365 tenants. If your already using OAuth to connect to Office365 you have most of the work already done but you will still need logic to ensure you have the correct Audience set in your token when that code is used against an OnPrem Mailbox. </div><div><br /></div><div><b>Prerequisites </b></div><div><b><br /></b></div><div>You need to be using Hybrid Exchange or more specifically </div><div><br /></div><blockquote><div><span style="-webkit-text-stroke-width: 0px; background-color: white; color: #171717; display: inline; float: none; font-family: "segoe ui", segoeui, "segoe wp", "helvetica neue", helvetica, tahoma, arial, sans-serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; overflow-wrap: break-word; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;">Hybrid Office 365 tenant is configured in full hybrid configuration using Exchange Classic Hybrid Topology mode ref <a href="https://docs.microsoft.com/en-us/exchange/clients/outlook-for-ios-and-android/use-hybrid-modern-auth?view=exchserver-2019">https://docs.microsoft.com/en-us/exchange/clients/outlook-for-ios-and-android/use-hybrid-modern-auth?view=exchserver-2019 </a></span><b></b><i></i><u></u><sub></sub><sup></sup><strike></strike><br /></div></blockquote><div>And you need to configure Hybrid Modern Authentication <a href="https://docs.microsoft.com/en-us/office365/enterprise/configure-exchange-server-for-hybrid-modern-authentication">https://docs.microsoft.com/en-us/office365/enterprise/configure-exchange-server-for-hybrid-modern-authentication </a></div><div><br /></div><div>If you don't want to enable Hybrid Modern Authentication but still want to use oAuth in EWS you can do it and there is a good article by Ingo on how to do this <a href="https://practical365.com/exchange-server/configure-hybrid-modern-authentication-for-exchange-server/">https://practical365.com/exchange-server/configure-hybrid-modern-authentication-for-exchange-server/</a></div><div><br /></div><div><b><br /></b></div><div><b>Authentication - Acquiring the Token</b></div><div><b><br /></b></div><div>This is where you need to make the most changes in your current code as you will now need some logic that can be used to acquire the oAuth Tokens from AzureAD. The easiest way of doing this is to use one of the Authentication libraries from Microsoft either <a href="https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/">ADAL</a> (if you already have this implemented in your code) or preferably use the <a href="https://www.nuget.org/packages/Microsoft.Identity.Client/">MSAL</a> library. The difference between ADAL and MSAL is ADAL uses the v1 Azure oauth endpoint and MSAL uses the v2 there is a good description of the differences between the two endpoints <a href="https://nicolgit.github.io/AzureAD-Endopoint-V1-vs-V2-comparison/">https://nicolgit.github.io/AzureAD-Endopoint-V1-vs-V2-comparison/</a></div><div><b></b><br /></div><div><b>Getting the intended Audience value for you Token Request </b></div><div><b><br /></b></div>The audience of an oAuth token is the intended recipient of the token (or basically the resource its going to be used against) , in our Exchange EWS context this is the host-name part of the EWS External endpoint. In Office365 the EWS Endpoint will be https://outlook.office365.com/ews/exchange.asmx so the intended Audience of a token will be https://outlook.office365.com if you look at an Office365 token is jwt.io this is what you see eg<div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpyqQwPHZJXLNdll3zjpOoNPi6v60h1MGwyeHg7eip2ZL-F5fuReQhUgqsnFp6sAkzBS6WI06j9bPultqTPBhRZu151q8V__6WNn39hHlCSyWTF9YwPPJoSZTA6Q8bu4JCUW_UBQ/" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="706" data-original-width="611" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpyqQwPHZJXLNdll3zjpOoNPi6v60h1MGwyeHg7eip2ZL-F5fuReQhUgqsnFp6sAkzBS6WI06j9bPultqTPBhRZu151q8V__6WNn39hHlCSyWTF9YwPPJoSZTA6Q8bu4JCUW_UBQ/w554-h640/audpic.png" width="554" /></a></div><div><br /><div><br /></div><div>When your using Hybrid Modern Authentication the Audience value for your token will become the external EWS endpoint's host-name of your OnPrem server (generally what you have configured in get-webservicesvirtualdirectory)</div><div><br /></div><div>In the Authentication libraries this Audience is passed differently in</div><div><br /></div><div>ADAL v1 Azure Endpoint its passed as the resourceURL</div><div><br /></div><pre style="background: rgb(246, 248, 255); color: #000020;">String ResourceURL <span style="color: #308080;">=</span> <span style="color: maroon;">"</span><span style="color: maroon;">"</span>https<span style="color: #308080;">:</span><span style="color: #595979;">//outlook.office365.com";</span>
<span style="color: #200080; font-weight: bold;">var</span> AuthResults <span style="color: #308080;">=</span> AuthContext<span style="color: #308080;">.</span>AcquireTokenAsync<span style="color: #308080;">(</span>ResourceURL <span style="color: #308080;">,
</span> <span style="color: maroon;">"</span><span style="color: #1060b6;">xxxxx-52b3-4102-aeff-aad2292ab01c</span><span style="color: maroon;">"</span><span style="color: #308080;">,</span> <span style="color: #200080; font-weight: bold;">new</span> Uri<span style="color: #308080;">(</span><span style="color: maroon;">"</span><span style="color: #1060b6;">urn:ietf:wg:oauth:2.0:oob</span><span style="color: maroon;">"</span><span style="color: #308080;">)</span>,
<span style="background-color: transparent; color: #200080; font-weight: bold;">new</span> PlatformParameters<span style="background-color: transparent; color: #308080;">(</span>PromptBehavior<span style="background-color: transparent; color: #308080;">.</span>Always<span style="background-color: transparent; color: #308080;">)</span><span style="background-color: transparent; color: #308080;">)</span><span style="background-color: transparent; color: #308080;">.</span>Result<span style="background-color: transparent; color: #406080;">;</span><span style="background-color: transparent;"> </span></pre><div><br /></div><div>In MASL v2 Azure Endpoint its passed as part of the scope </div><div><br /></div><div><pre style="background: rgb(246, 248, 255); color: #000020;"><span style="color: #200080; font-weight: bold;">string</span> scope <span style="color: #308080;">=</span> <span style="color: maroon;">"</span><span style="color: #1060b6;">https://outlook.office365.com/EWS.AccessAsUser.All</span><span style="color: maroon;">"</span><span style="color: #406080;">;</span>
PublicClientApplicationBuilder pcaConfig <span style="color: #308080;">=
</span> PublicClientApplicationBuilder<span style="color: #308080;">.</span>Create<span style="color: #308080;">(</span>ClientId<span style="color: #308080;">)</span><span style="color: #308080;">.</span>WithAuthority<span style="color: #308080;">(</span>AadAuthorityAudience<span style="color: #308080;">.</span>AzureAdMultipleOrgs<span style="color: #308080;">)</span><span style="color: #406080;">;</span>
<span style="color: #200080; font-weight: bold;">var</span> IntToken <span style="color: #308080;">=</span> pcaConfig<span style="color: #308080;">.</span>Build<span style="color: #308080;">(</span><span style="color: #308080;">)</span><span style="color: #308080;">.</span>AcquireTokenInteractive<span style="color: #308080;">(
</span><span style="color: #200080; font-weight: bold;">new</span><span style="color: #308080;">[</span><span style="color: #308080;">]</span> <span style="color: #406080;">{</span> scope <span style="color: #406080;">}</span><span style="color: #308080;">)</span><span style="color: #308080;">.</span>ExecuteAsync<span style="color: #308080;">(</span><span style="color: #308080;">)</span><span style="color: #308080;">.</span>Result<span style="color: #406080;">;</span>
</pre></div><div>With Hybrid Modern Auth in the above examples outlook.office365.com would be replaced with the host name for your external EWS endpoint which you would obtain usually via Autodiscover</div><div><br /></div><div><b>AutoDiscover</b> </div><div><br /></div><div>Autodiscover in Exchange from Exchange 2007 has been there to help you basically discover the internal or external endpoint you need for whatever API your using. It is however an Authenticated Endpoint so when you remove Basic/Intergrated Authentication your code needs to be able to deal with this change. There are two ways you could go about this the first is generate a OAuth token first and use that to make the Authenticated traditional Auto-discover request. Or the second way is to use Autodiscover v2 (or Autodiscover json) which allows you to make an unauthenticated autodiscover requests to return the API endpoint you want. </div><div><br /></div><div>If your code targets predominately Office365 and Hybrid tenants then just switch to use Autodiscover v2, if you have a mix of Office365, Hybrid and OnPrem islands then you still need the legacy Basic/Integrated Auth method for these OnPrem clients. The Approach that I'm taking in this post is to first do a Realm discovery against Office365 to determine if a particular set of credentials is an Office365 or Hybrid account.If it is then a v2 Autodiscover request will be made against Office365 and if not fail back to the legacy code. This isn't 100% guaranteed to work for some OnPrem (especially pre Exchange 2016) and account combinations so my advice is you always make sure your try/catch autodiscover logic includes at least one legacy auto discover-attempt as a last fail back. And make sure you do some regression testing on your code change against Exchange 2013. </div><div><br /></div><div>For doing a simple JSON based Autodiscover against Office365 this can be done in a few lines with httpclient in c# </div></div><div><br /></div><div><pre style="background: rgb(246, 248, 255); color: #000020;">String MailboxName <span style="color: #308080;">=</span> <span style="color: maroon;">"</span><span style="color: #1060b6;">gscales@datarumble.com</span><span style="color: maroon;">"</span><span style="color: #406080;">;</span>
String EWSEndPoint <span style="color: #308080;">=</span> $<span style="color: maroon;">"</span><span style="color: #1060b6;">https://outlook.office365.com/autodiscover/autodiscover.json/v1.0/{MailboxName}?Protocol=EWS</span><span style="color: maroon;">"</span><span style="color: #406080;">;</span>
HttpClient httpClient <span style="color: #308080;">=</span> <span style="color: #200080; font-weight: bold;">new</span> HttpClient<span style="color: #308080;">(</span><span style="color: #308080;">)</span><span style="color: #406080;">;</span>
httpClient<span style="color: #308080;">.</span>DefaultRequestHeaders<span style="color: #308080;">.</span>UserAgent<span style="color: #308080;">.</span>ParseAdd<span style="color: #308080;">(</span><span style="color: maroon;">"</span><span style="color: #1060b6;">Mozilla/5.0 (compatible; AcmeInc/1.0)</span><span style="color: maroon;">"</span><span style="color: #308080;">)</span><span style="color: #406080;">;</span>
dynamic JsonResult <span style="color: #308080;">=</span> JsonConvert<span style="color: #308080;">.</span>DeserializeObject<span style="color: #308080;">(</span>httpClient<span style="color: #308080;">.</span>GetAsync<span style="color: #308080;">(</span>EWSEndPoint<span style="color: #308080;">)</span><span style="color: #308080;">.</span>Result<span style="color: #308080;">.</span>Content<span style="color: #308080;">.</span>ReadAsStringAsync<span style="color: #308080;">(</span><span style="color: #308080;">)</span>
<span style="color: #308080;">.</span>Result<span style="color: #308080;">)</span><span style="color: #406080;">;</span>
Console<span style="color: #308080;">.</span>WriteLine<span style="color: #308080;">(</span>JsonResult<span style="color: #308080;">.</span>Url<span style="color: #308080;">)</span><span style="color: #406080;">;</span>
</pre></div><div>Or in PowerShell you could do it as a one-liner</div><div><pre style="background: rgb(246, 248, 255); color: #000020;"><span style="color: #308080;">(</span>Invoke<span style="color: #308080;">-</span>WebRequest <span style="color: #308080;">-</span>Uri https<span style="color: #308080;">:</span><span style="color: #595979;">//outlook.office365.com/autodiscover/autodiscover.json/v1.0/</span><span style="color: #7144c4;">gscales@datarumble.com
?Protocol=EWS</span><span style="color: #595979;"> | ConvertFrom-Json).url</span>
</pre>When your submitting an Autodiscover request against Office365 if your mailbox is OnPrem and you have HMA configured you will get returned your OnPrem EWS endpoint. </div><div><br /></div><div><b>EWS Managed API</b> </div><div><br /></div><div>So what does this look like in the context of your EWS Managed API code, let first look at the traditional code path for Autodiscover</div><div><br /></div><div><pre style="background: rgb(246, 248, 255); color: #000020;">ExchangeService service <span style="color: #308080;">=</span> <span style="color: #200080; font-weight: bold;">new</span> ExchangeService<span style="color: #308080;">(</span>ExchangeVersion<span style="color: #308080;">.</span>Exchange2013<span style="color: #308080;">)</span><span style="color: #406080;">;</span>
service<span style="color: #308080;">.</span>Credentials <span style="color: #308080;">=</span> <span style="color: #200080; font-weight: bold;">new</span> WebCredentials<span style="color: #308080;">(</span><span style="color: maroon;">"</span><span style="color: #1060b6;">user1@contoso.com</span><span style="color: maroon;">"</span><span style="color: #308080;">,</span> <span style="color: maroon;">"</span><span style="color: #1060b6;">password</span><span style="color: maroon;">"</span><span style="color: #308080;">)</span><span style="color: #406080;">;</span>
service<span style="color: #308080;">.</span>AutodiscoverUrl<span style="color: #308080;">(</span><span style="color: maroon;">"</span><span style="color: #1060b6;">user1@contoso.com</span><span style="color: maroon;">"</span><span style="color: #308080;">,</span> RedirectionUrlValidationCallback<span style="color: #308080;">)</span><span style="color: #406080;">;</span>
</pre></div><div>Here is what it would look like use a Realm Discovery and then a Json Autodiscover and some MSAL code to do the Token Acquisition otherwise following the above logic.</div><div><br /><script src="https://gist.github.com/gscales/9c2eb16cecf3bdad2ef6501934648b06.js"></script>
</div><div>A PowerShell version of the same thing would look like</div><div><br /><script src="https://gist.github.com/gscales/fe2fb8cbcaa95218b903c43a44e7cb5c.js"></script></div><div>If you want a library free version that uses Invoke-WebRequest it would look like</div><div><br /><script src="https://gist.github.com/gscales/021becef1e16e34de43eea4b27615ec6.js"></script> </div><div><br /></div><div><b>Dealing with Token Refresh (Important)</b></div><div><b><br /></b></div><div>Access tokens by default in Azure are valid for 1 hour, so if your application is going to run for a long period of time or is persistent then you will need to manage token expiration and refresh. If your using one of the Authentication libraries then they can perform this for you automatically however they do rely on you calling their methods before you make any authenticated EWS call. Currently the EWS Managed API doesn't offer a callback to help you integrate easily with an Authentication library (for doing the Token Refresh management) so you will need to come up with your own method of doing this (eg a simple method to check the token before any operation could be used). Or you can modify the EWS Managed API source to integrate your own callback eg a good place to look is <span style="background-color: white; color: #6f42c1; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 12px; white-space: pre;">PrepareWebRequest </span>in <a href="https://github.com/OfficeDev/ews-managed-api/blob/70bde052e5f84b6fee3a678d2db5335dc2d72fc3/Credentials/OAuthCredentials.cs">https://github.com/OfficeDev/ews-managed-api/blob/70bde052e5f84b6fee3a678d2db5335dc2d72fc3/Credentials/OAuthCredentials.cs</a> . The good thing about modifying the source is that you fix the issue for any operation that you code will do now and into the future. </div><div><br /></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-815555273546766552020-05-14T21:32:00.001+10:002020-05-14T21:32:25.096+10:00Graph Mailbox Basics with PowerShell Part 1 FoldersI haven't done a basics series for a while but based on some of the questions I've been getting lately and the lack of some good Mailbox specific examples for basic but more complex tasks using the Graph against Exchange Online Mailboxes this seemed like a good series to write.<div><br /></div><div>For all the scripts in this series I'm not going to use any modules or other libraries so everything will be using Invoke-WebRequest and Invoke-RestMethod, while there is nothing wrong with using libraries or modules and a number of advantages in doing so it just keeps the examples as simple and easy to understand as they can be.</div><div><br /></div><div><b>Authentication</b> You can't have an article on the Graph without talking about authentication and we are now far from the past where all you needed was a simple username and password and you where off to the races. The basics of Authentication are is that first you will need an Azure App Registration (that has been consented to), there are many pages dedicated to how you can do this (<a href="https://www.thelazyadministrator.com/2019/07/22/connect-and-navigate-the-microsoft-graph-api-with-powershell/#Assigning_Permissions">this </a>is one of the better ones) so I'm not going to dwell too much on this. My simple template script has a function called Get-AccessTokenForGraph which takes a ClientId and RedirectURI and does an interactive login to get the Azure access token. With oAuth there are many other ways of authenticating so if this doesn't fit your needs you just need to plug your own code in the Get-AccessTokenForGraph function.</div><div><br /></div><div><b>Get-FolderFromPath</b></div><div><b><br /></b></div><div>With Exchange the locator (think file path as an analogy) you use to access a Folder programatically is its FolderId. Every Exchange API has it own interpretation of the FolderId starting with the Fid and PidTagEntryId in Mapi, EWS has the EWSid and Graph just has the Id (and the EMS gives a combination of Id's back depending on which cmdlet you use). With the Graph and EWS id's these id's contain the PidTagEntryId with a bunch of other flags that tell the service how to locate and open the folder. However most of the time us humans think of folders in terms of Paths eg if I have a Subfolder of the Inbox a more human reference would be \Inbox\subfolder (language differences aside). So one of the more common methods I use is the Get-FolderFromPath to get a folder (or just the folderid) so you can then work on the Items within that folder or the folder itself. So the method I've always used in EWS is to take the path you want to search for and split in based on the \ character and then do a number of shallow searches of the parent folders until you find the child folder you want. in the Graph this looks something like this</div><div><br /></div><div><pre style="text-align: left;"> $RequestURL = $EndPoint + "('$MailboxName')/MailFolders('MsgFolderRoot')/childfolders?"<br /> $fldArray = $FolderPath.Split("\")<br /> $PropList = @()<br /> $FolderSizeProp = Get-TaggedProperty -DataType "Long" -Id "0x66b3"<br /> $EntryId = Get-TaggedProperty -DataType "Binary" -Id "0xfff"<br /> $PropList += $FolderSizeProp <br /> $PropList += $EntryId<br /> $Props = Get-ExtendedPropList -PropertyList $PropList <br /> $RequestURL += "`$expand=SingleValueExtendedProperties(`$filter=" + $Props + ")"<br /> #Loop through the Split Array and do a Search for each level of folder <br /> for ($lint = 1; $lint -lt $fldArray.Length; $lint++) {<br /> #Perform search based on the displayname of each folder level<br /> $FolderName = $fldArray[$lint];<br /> $headers = @{<br /> 'Authorization' = "Bearer $AccessToken"<br /> 'AnchorMailbox' = "$MailboxName"<br /> }<br /> $RequestURL = $RequestURL += "`&`$filter=DisplayName eq '$FolderName'"<br /> $tfTargetFolder = (Invoke-RestMethod -Method Get -Uri $RequestURL -UserAgent "GraphBasicsPs101" -Headers $headers).value <br /> if ($tfTargetFolder.displayname -match $FolderName) {<br /> $folderId = $tfTargetFolder.Id.ToString()<br /> $RequestURL = $EndPoint + "('$MailboxName')/MailFolders('$folderId')/childfolders?"<br /> $RequestURL += "`$expand=SingleValueExtendedProperties(`$filter=" + $Props + ")"<br /> }<br /> else {<br /> throw ("Folder Not found")<br /> }<br /> }</pre>So for each folder Step I'm finding the intermediate folder using $filter=DisplayName eq '$FolderName'</div><div><br />To make the results more useful I've included a few extended properties that give me some extra information</div><div><br />The first is the FolderSize, which in Mapi is the PidTagMessageSizeExtended property on the folder </div><div><br />The second is the pidTagEntryId (PR_EntryId)property which I added in so I could easily convert this into the folderId format that is used in the Office365 compliance search eg in Office365 when you do a compliance search you have the ability of using the folderid:xxxx keyword in a Search to limit the search of a Mailbox to one particular folder in a Mailbox. There is a script in <a href="https://docs.microsoft.com/en-us/microsoft-365/compliance/use-content-search-for-targeted-collections?view=o365-worldwide">https://docs.microsoft.com/en-us/microsoft-365/compliance/use-content-search-for-targeted-collections?view=o365-worldwide</a> which uses the Get-MailboxFolderStatistics cmdlet which I found a little cumbersome so having a simple method like the above can return the id i need for the folder i want. eg this is what the end result looks like when you run the script</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR0JURewI4tPB6kHdg1IbI9tr3VMqGh9VD-7Dqlh1SQze_ycf2fWfsQAXKxRZSEL6W9Oe3gbSSS7qYmx7hyphenhyphenqKjap66ibjnNTjOFcD131AB9XA9_Fh9iFPu5ZRp4qGrc0yES6QRdw/" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="285" data-original-width="738" height="248" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR0JURewI4tPB6kHdg1IbI9tr3VMqGh9VD-7Dqlh1SQze_ycf2fWfsQAXKxRZSEL6W9Oe3gbSSS7qYmx7hyphenhyphenqKjap66ibjnNTjOFcD131AB9XA9_Fh9iFPu5ZRp4qGrc0yES6QRdw/w640-h248/Annotation+2020-05-14+210844.png" width="640" /></a></div><div><br /></div><div><br /></div><div>The REST request that is generated by the script looks like (if you want to try this in the graph explorer)</div><div><br /></div><pre style="text-align: left;">https://graph.microsoft.com/v1.0/users('gscales@datarumble.com')
/MailFolders('MsgFolderRoot')/childfolders?
$expand=SingleValueExtendedProperties($filter=(Id%20eq%20'Long%200x66b3')
%20or%20(Id%20eq%20'Binary%200xfff'))
&$filter=DisplayName%20eq%20'inbox'</pre>There are a bunch more things you can do with this type of query eg working with the retention tags on a folder. Or using the FolderId to then process the Items within that folder. The reason i started with this function is for me its always a jumping off point for starting working with mailbox data.<div><br /></div><div>The full script is available on GitHub <a href="https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/FolderOps.ps1">https://github.com/gscales/Powershell-Scripts/blob/master/Graph101/FolderOps.ps1</a><br /><pre><br /></pre><div><span style="background-color: white; color: #171717; font-family: "segoe ui", segoeui, "segoe wp", "helvetica neue", helvetica, tahoma, arial, sans-serif; font-size: 16px;"></span></div></div>Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.comtag:blogger.com,1999:blog-7123450.post-19898349694975405322020-04-22T21:27:00.002+10:002020-04-22T21:27:54.518+10:00Migrating your Mailbox searches in EWS to the Graph API Part 2 KQL and new search endpointsThis is part 2 of my blog post on migrating EWS Search to the Graph API, in this part I'm going to be looking at using KQL Searches and using the new Microsoft Search API (currently in Beta). The big advantage these type of searches have over using SearchFilters is that these type of searches use the content indexes which can improve the performance of searches when folder item counts get high. They also allow you to query the contents of Attachments which are indexed through ifilters on the server.<br />
<b></b><br />
<b>KQL queries on the Mailbox and Mailbox Folders</b><br />
<b><br /></b>
In EWS you have been able to use firstly AQS and now KQL in the FindItems operation from Exchange 2013 up. To migrate these searches to Microsoft Graph is pretty simple eg an EWS FindItem query to search for all messages with a pdf attachment<br />
<br />
<pre style="background: #f6f8ff; color: #000020;">FindItemsResults fiItems <span style="color: #308080;">=</span> service<span style="color: #308080;">.</span>FindItems<span style="color: #308080;">(</span>QueryFolder<span style="color: #308080;">,</span> <span style="color: maroon;">"</span><span style="color: #1060b6;">Attachmentnames:.pdf</span><span style="color: maroon;">"</span><span style="color: #308080;">,</span> iv<span style="color: #308080;">)</span><span style="color: #406080;">;</span>
</pre>
<br />
in the Graph you would use something like<br />
<br />
<pre style="background: #f6f8ff; color: #000020;"><span style="color: #e34adc;">https:</span><span style="color: #595979;">//graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages
?$search="attachmentnames:.pdf"</span>
</pre>
<br />
the slightly disappointing thing with the Graph is that you can't use count along with a search which when your doing statistical type queries eg say I wanted to know how many email that where received in 2019 had a pdf attachment makes this very painful to do in the Graph where in EWS it can be done with one call (its a real snowball that one).<br />
<br />
Searching the recipient fields like To and CC, in the forums you see some absolute clangers search filters that try to search the recipients and from fields of messages that can easily be done using the participants keyword which includes all the people fields in an email message. These fields are From, To, Cc. The one thing to be aware of is the following note on expansion in <a href="https://docs.microsoft.com/en-us/microsoft-365/compliance/keyword-queries-and-search-conditions?view=o365-worldwide">https://docs.microsoft.com/en-us/microsoft-365/compliance/keyword-queries-and-search-conditions?view=o365-worldwide</a> . So if you don't want expansion to happen you need to ensure you use the wildcard character after the participant your searching for. A simple participants query looks like<br />
<br />
<pre style="background: #f6f8ff; color: #000020;"><span style="color: #e34adc;">https:</span><span style="color: #595979;">//graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages?
$search="participants:Fred"</span>
</pre>
<b></b><i></i><u></u><sub></sub><sup></sup><strike></strike><br />
<b>Date range queries</b><br />
<br />
One of the good things about KQL with dates is that you can use reserved keywords like today,yesterday,this week eg<br />
<b></b><i></i><u></u><sub></sub><sup></sup><strike></strike><br />
<pre style="background: #f6f8ff; color: #000020;"><span style="color: #e34adc;">https:</span><span style="color: #595979;">//graph.microsoft.com/v1.0/me/mailFolders('Inbox')
/messages?$search="received:yesterday"</span>
</pre>
<b></b><i></i><u></u><sub></sub><sup></sup><strike></strike><br />
to get all the received sent between two dates you can use either<br />
<br />
<pre style="background: #f6f8ff; color: #000020;"><span style="color: #e34adc;">https:</span><span style="color: #595979;">//graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages?
$search="received:2019-01-01...2019-02-01"</span>
</pre>
<b></b><i></i><u></u><sub></sub><sup></sup><strike></strike><br />
or<br />
<br />
<pre style="background: #f6f8ff; color: #000020;"><span style="color: #e34adc;">https:</span><span style="color: #595979;">//graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages?
$search="(received>=2019-01-01 AND received<=2019-02-01)"</span>
</pre>
<br />
If you want to search the whole of the Mailbox using the graph eg if you have use the AllItems Search Folder in EWS to do a Search that spans all the MailFolders in a Mailbox in the Graph you just need to use the /Messages endpoint eg<br />
<br />
<pre style="background: rgb(246, 248, 255); color: #000020;"><span style="color: #e34adc;">https:</span><span style="color: #595979;">//graph.microsoft.com/v1.0/me/messages?
$search="(received>=2019-01-01 AND received<=2019-02-01)"</span>
</pre>
<br />
<br />
<b>New Search Methods</b><br />
<b><br /></b>
The traditional search methods in EWS give you the normal narrow refiner search outputs that most mail apps have been providing over the past 10-20 years. While these methods have improved over the years there hasn't been any real great leaps in functionality with Search. So the Microsoft Graph has been adding some newer endpoints that do allow a more modern approach to searching . The first is Microsoft Graph data connect which has been around for a while now and the Microsoft Search API which is still in Beta. As this article is about migrating EWS searches you probably wouldn't consider either of these for your traditional search migration as $filter and $search are going to meet those needs. However if you are looking at overhauling the search functionality in your application or you are building greenfield functionality then both of these new methods are worth consideration.<br />
<br />
<div style="font-size: 16px; font-variant-east-asian: normal; font-variant-numeric: normal;">
<span style="background-color: white; font-variant-east-asian: normal; font-variant-numeric: normal;"><b><a href="https://docs.microsoft.com/en-us/graph/data-connect-concept-overview">Microsoft Graph Data Connect</a></b></span></div>
<div style="font-size: 16px; font-variant-east-asian: normal; font-variant-numeric: normal;">
<i></i><u></u><sub></sub><sup></sup><strike></strike><br /></div>
<div style="font-size: 16px; font-variant-east-asian: normal; font-variant-numeric: normal;">
<b></b><i></i><u></u><sub></sub><sup></sup><strike></strike><b></b><a href="https://docs.microsoft.com/en-us/graph/data-connect-concept-overview">https://docs.microsoft.com/en-us/graph/data-connect-concept-overview</a></div>
<div style="font-size: 16px; font-variant-east-asian: normal; font-variant-numeric: normal;">
<br /></div>
<div style="font-size: 16px; font-variant-east-asian: normal; font-variant-numeric: normal;">
Graph Data connect is your go-to endpoint when you want to do any mass processing of Mailbox data. It solves that problem of having to crawl every item in a Mailbox when you want to do any data-mining type operations by basically providing an Azure dataset of this information for you. Data connect is great however it has a high entry level, first you need a Workplace analytics licence for every mailbox you wish to analyse and the costs can mount pretty quickly the larger the Mailbox count your dealing with. The other requirements is paying for the underlying Azure Storage etc that your dataset ends up consuming. I think it can be a bit of a shame that the licencing costs can lock a lot of developers out of using this feature as it really does provide a great way or working with Mail item data. And it leaves some having to resort to doing their own crawling of Mailbox data to avoid these costs (eg that licencing cost is a pretty hard sell for any startup looking to use this) </div>
<br />
<br />
<span style="background-color: white; color: black; display: inline; float: none; font-family: "times new roman"; font-size: 16px; font-style: normal; font-variant: normal; letter-spacing: normal; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;"><b>Microsoft Search API</b></span><br />
<span style="background-color: white; color: black; display: inline; float: none; font-family: "times new roman"; font-size: 16px; font-style: normal; font-variant: normal; letter-spacing: normal; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;"><b><br /></b></span>
<span style="background-color: white; color: black; display: inline; float: none; font-family: "times new roman"; font-size: 16px; font-style: normal; font-variant: normal; letter-spacing: normal; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;"><a href="https://docs.microsoft.com/en-us/graph/search-concept-overview">https://docs.microsoft.com/en-us/graph/search-concept-overview</a></span><br />
<i></i><u></u><sub></sub><sup></sup><strike></strike><br />
This is the newest way of searching mailbox data, while the underlying mechanism for doing mailbox searches is still KQL so its very similar to the $Search method described about, this API does enhance the search results with some more "Search Intelligence" like relevance bringing AI into the picture . One of the other main benefits of this endpoint is when you want to broaden your search to other Office365 workflows or even include your own custom data searches. So this really is the endpoint that will provide you with a modern search experience/workflow. Which is getting more critical due to the sheer amount of data we have (eg the datageddon). Its still in beta and is a little restricted at the moment eg<br />
<br />
<ul>
<li>It can't be used to search delegate Mailboxes so only the primary mailbox </li>
<li>It only returns the pageCount for items not the Total number of Items found in a search (to be fair $search does this as well which is really annoying)</li>
<li>Searches are scoped across the entire mailbox </li>
<li>Just Messages and Events are searchable at the moment</li>
</ul>
<br />
<br />
<br />
<br />
<br />
<br />Glen Scaleshttp://www.blogger.com/profile/07323836178158839483noreply@blogger.com