Friday, September 07, 2018

Getting FreeBusy information in the Graph API using the getSchedule action and putting it to use in PowerShell

One of the recent additions to the Graph API for Exchange in Office365 has been getSchedule action . What this action allows to do is get the FreeBusy information from one or more mailboxes in your Office365 environment. If your new to Exchange (or for those that have forgotten) FreeBusy information is essential what is used to produce the Schedule Assistant in Outlook and equivalent in OWA

The above is built by Outlook using the EWS GetUserAvaliblity operation which is what the getSchedule action in the Graph is the equivalent of. GetUserAvaliblity has been in use in Outlook since Exchange 2007 where we went from using Public Folders to hold the freebusy information in previous versions to the Public Folder free utopia we have today. In the preceding years features such as Suggested Meeting times have been added to this EWS operation this feature is available in the Graph via the Graph in the findmeeting action .

If you are using the Graph API
getSchedule is a  good a solution for solving one of the more frequently asked questions of how you can get the availability of multiple meeting rooms without needing to query every Meeting Rooms calendars (or a simular question for a group of users).


There are some limits to be aware of when querying FreeBusy data which is you can query up to 62 days worth of FreeBusy information (eg the period between the Start and EndTimes). The other limit you should adhere to is no more the 100 Mailboxes per request to keep below the throttling limits.

Making the query

The REST request you need to make to get the FreeBusy information should look something like the following

    "schedules":  [
    "endTime":  {
                    "dateTime":  "2018-09-08T10:21:49",
                    "timeZone":  "AUS Eastern Standard Time"
    "startTime":  {
                      "dateTime":  "2018-09-07T10:21:49",
                      "timeZone":  "AUS Eastern Standard Time"
    "availabilityViewInterval":  15

Time zones are an issue that usually trips up a lot of people, especially if your dealing with mailboxes in multiple locations. Its important to note the timezone specified in the query only relates to the query period your using. The timezone that you get back in the results will depend on the request header Prefer: outlook.timezone in the Request. So if your setting the TimeZone in the query and your getting back a different unexpected TimeZone In the results you need to look at the Prefer: outlook.timezone that's being used (or the absence of that header will mean you will get UTC back).

The availabilityViewInterval affects the timeslots that come back  in the availabilityView generally you would use either 15,30 or 60 minutes but you can have a value as high as 1440 (which represents 1 day) and as low a 6 minutes. If you set the availabilityViewInterval to 1440 then just having one appointment in a day will mean that day will appear as Busy.


The results you get back from this action is first the availabilityView which depending on the interval you specified will be splices of availability between then Start and End Times you specified. eg

Also if you have the FreeBusy,Subject and location permission on the Target calendars (or higher) you will get an array of calendar appointments (as long as they aren't private) back for each Target Mailbox. eg

In EWS you also got back the HexEntryId of the appointment object meaning you could then retrieve more information for a specific Appointment without first doing a search which seems to be missing in the Graph operation at the moment. Lets hope that comes back as its an important function and the workaround is messy and will cause a lot of development pain for people 🙏🙏.

Using this in PowerShell

I've added the ability to use this action to my Exch-Rest PowerShell library which is what the above screen shots where produced using this module is available from the PowerShell Gallery and GitHub

The Module has the following Cmdlet


this takes an array of Mailboxes you want to run it against so to use it first create a blank array

$Mailboxes = @()

then populate that with the Mailboxes you want to run against eg

$Mailboxes += ""
$Mailboxes += ""

Then run the cmdlet with the Start and EndTime and availability period you want by default these aren't need and it will return the freebusy information for the next 24 hours in 15 minutes intervals eg

Get-EXRSchedule -Mailboxes $mailboxes 

However usually you would want to run something like the following

$StartTime = [DateTime]::Parse((Get-Date).ToString("yyyy-MM-dd HH:00:00"))
$EndTime = [DateTime]::Parse((Get-Date).AddHours(6).ToString("yyyy-MM-dd HH:00:00"))
Get-EXRSchedule -Mailboxes $mailboxes -Start $StartTime -EndTime $Endtime -availabilityViewInterval 15

Which would get the freebusy information for the next 6 hours in 15 minute increments

The Module is useful for getting the data so lets look at a few fun ways you can use it. eg if you want to have the results stored in a collection you could export as CSV or Graph you could use something like the following

$rptCollection = @()
$StartTime = [DateTime]::Parse((Get-Date).ToString("yyyy-MM-dd HH:00:00"))
$EndTime = [DateTime]::Parse((Get-Date).AddHours(6).ToString("yyyy-MM-dd HH:00:00"))
$avResults = Get-EXRSchedule -Mailboxes $mailboxes -Start $StartTime -EndTime $Endtime -availabilityViewInterval 15
foreach($fbResult in $avResults){
 $CurrentTime = $StartTime 
 $fbResult.availabilityView.ToCharArray() | ForEach-Object{
  $rptobj = "" | select Time,User,FreeBusyStatus
  $rptobj.Time = $CurrentTime.ToString("HH:mm")
  $rptobj.User = $fbResult.scheduleId
     0 {$rptobj.FreeBusyStatus = "Free"}
     1 {$rptobj.FreeBusyStatus = "Tentative"}
     2 {$rptobj.FreeBusyStatus = "Busy"}
     3 {$rptobj.FreeBusyStatus = "Out of Office"}
     4 {$rptobj.FreeBusyStatus = "Working Elsewhere"}
   $rptCollection += $rptobj
  $CurrentTime = $CurrentTime.AddMinutes(15)
return $rptCollection 

Or we can create a FreeBusy board using this information that would look like the following

Where I'm using both the avaiblitiyview data to create the Table and also the calendar details so when you hover over one of the busy table cells it will show the subject of that meeting. I've put the code up for the freebusy board here .

Hire me - If you would like to do something similar to this or anything else you see on my blog I'm currently available to help with any Office365, Exchange or Active Directory related development work or scripting, please contact me at too big or small).

Friday, August 24, 2018

Using Guest Access in PowerShell to access Unified Groups and Teams in Office365

This is a continuation somewhat of my previous post on using a generic oAuth script to access EWS but in this post I'm going to look at how you can authenticate as a Guest user to a tenant you have been granted Guest access to and then access the resources like a Unified Office365 Group or Teams( because Teams uses Groups) via the Graph API.

To Access another tenant as a Guest (considering that Guest Access is enabled and you have accepted the invitation and logged in at least once with that user) you need to generate an Access Token against the Target tenants endpoints. To do this you can discover what a particular tenants Authentication and Token endpoints are by making a request like the following

$RequestURL = "{0}/.well-known/openid-configuration" -f $TargetDomain
$Response = Invoke-WebRequest -Uri  $RequestURL
$JsonResponse = ConvertFrom-Json  $Response.Content

This will return a result like the following

Out of this result the Authorization Endpoint and the Token_endpoint are what you need to make a successful authentication against the Target Tenant to get a guest access token.

For technical completeness the above step is not necessarily needed as the endpoint should also support you using the domain name as per . But like many things in IT there are many ways of doing the same things.

I've posted up a script that first does the above the request and then will authenticate against the Target Tenant and return an Access Token that can then be used to access the Graph. I've created a test cmdlet called Test-GuestAuth that will enumerate the unified Groups the guest is a member of and get the first two conversation items  in each of the unified Groups, I've put the code up for this here

The script is on GitHub here

To use this script use something like the following

Test-GuestAuth -UserName -TargetDomain

For the clientId you would need to use in oAuth that clientId has to have been consented to by the Target Tenant admins, in the above script I've used the default Office clientId which should be enough to get access to Group conversation etc.

Friday, August 10, 2018

Dependency free Generic EWS oAuth PowerShell example for Office365

Microsoft recently posted an update that will affect those people who use EWS in applications and script against Office365 

While 2020 is a few years away what this means is that if you are using Basic Authentication in your EWS scripts or applications that on the 13th October 2020 your app will stop working. Given the amount of time you have and the changes required to support oAuth nobody should really be caught out by this but procrastination and people not understanding legacy applications will mean I'm sure this date won't pass without any infamy.

Within PowerShell scripts you have two options to generate the oAuth tokens you need to keep you script working. One is to use a dependency library like ADAL to do it which Ingo posted a really good write up for the other is just create some of your own script code to do the Authentication and managed of the Tokens. As part of my Exch-Rest library because I wanted to make this dependency free I wrote my own routines for Getting and Renewing OAuth tokens needed which can also be used for EWS . So in this post I've separated those out and included them into a simple header script that can be used to do oAuth against Office365.

EWS Managed API and oAuth

If you using the EWS Managed API in your scripts which the majority of people do it contains code already to add the correct Bearer headers in for Oauth if you use  the OAuthCredentials class

        $OAuthCredentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials((ConvertFrom-SecureStringCustom -SecureToken $Script:Token.access_token))
        $service.Credentials = $OAuthCredentials

If your using the ADAL library be aware while its correct to say it does have a TokenCache and code to refresh the tokens once they expire this won't work with the EWS Managed API. As you can see from the code above because you only pass in the Access_Token (as a String) into this class it doesn't do any active management of the Token from that point. This means if you just pass in the AccessToken from whatever method you used to generated it if your script runs for more then one hour your code will fail at the 60 minute point. Unfortunately the EWS Managed API doesn't have a CallBack to use where you could link in the ADAL libraries refresh function (or your own code to manage the token refresh) to check before making a request. So one important modification you should make to your code if before it makes an EWS call (eg Bind,Load,findItems etc) you will need to add in code to check for expired tokens. The ADAL has the acquiretokensilentasync method for this (this will return the token from cache or renew if necessary), in the code in my example I have the Invoke-ValidateToken function which does the renewal after 50 minutes to avoid token expiration.

Application registrations

Its always a good idea to create your own application registration as this gives you the ultimate control over what rights a script or application will have in your environment (although EWS really only has one grant that will work which is full Mailbox Access). But creating you own ApplicationId's allows you  track the use of the ApplicationId and potential audit the miss use of a particular application more effectively. For scripts using the Native application type and the Out of Band redirect(urn:ietf:wg:oauth:2.0:oob) is generally a good idea as you won't have an registered endpoint online to redirect the Authentication to once its complete. There are some good walk throughs to do this shows the newer portal screens.

Last word on security

While oAuth is a great improvement in security over basic Authentication its not the panacea in itself for the InfoSec issues the IT industry faces as a whole. So you should see implementing oAuth as a step in the right direction but be careful and always treat access tokens like you would usernames and passwords (eg don't store them in plain text) and look at always strengthening your authentication with measures like Multi-Factor Authentication. 

The script includes a Test function which just binds to the Inbox so you use it like

Import-Module .\GenericOauthEWS.ps1 -Force

Test-EWSConnection -MailboxName


Like any script you get off the Internet you should always do your own testing but consider these points of failure for Oauth tokens that you wouldn't have to have previously considered with Basic Auth

  • If things run for more the 1 hour does my token renew correctly
  • If the AccessToken suddenly becomes Invalid for X Reason and I get a 401 error in my code will it handle the reauth (using the RefreshToken). (for the second part you need to build in an exception handler in you code for 401 error when using oAuth)
Regional Azure Endpoints

This script is hardcoded to use the common (Production) Azure Authentication endpoint that the majority of Office365 Tenants would use however some regions like China,Germany and US Government have their own dedicated endpoint so you would need to change the URL in this case see . There are ways of discovering the Endpoint dynamically which I'll included in future updates of the script.

Thursday, July 19, 2018

Expanded Folder Age and Attachment reporting for Exchange / Office 365 Mailboxes with EWS and Powershell

Folder Item numbers, size's and Attachment Item Numbers, types and sizes are one of the Mailbox statistics that is useful when your trying to look at how and why a mailbox is growing or why your mailbox performance might be declining if you have a large number of folder items. With the recent increases in Mailbox size quotas  in Office365 it can be easy to become quite indifferent to how quickly your mailbox might be growing. For example if you have 100GB quota the lazy mindset (which I admit to being a part of) can be to just keep everything you get via mail (or stack it and rack it). If your users are not able to take advantage of cloudy attachments (or the users refuse to change their use habits) the nature of expanding file sizes means a sharper growth curve for Folder Items and Attachments.

The traditional way of tracking Mailbox sizes is to use Get-MailboxStatistics and Get-MailboxFolderStatistics for which there are many good scripts around that give a good overview of what is in a Mailbox or Folder. For a deeper dive into what  is in messages in a particular folder where you are seeing growth then using a Mailbox API like EWS (or REST) can be useful. In the past I've posted a few scripts for doing Attachment and Item level statistics which where in need of some updating which is what I've done with this post

Script Approach 

For this script I've used EWS as it is universal across OnPremise and Office365 from Exchange 2010 up. I do have two different versions of this script however because Office365 and Exchange 2016 allows you to have reference attachments (cloudy attachments) so some slightly different code is needed and the latest compiled version of the EWS Managed API which is only available from GitHub is needed so statistics can be compiled successfully for these type of Attachments. For the Item level processing side of the script it will process every item in a folder firstly using the EWS FindItems operation which will return a minimal results set of Items and Sizes and then for those items with Attachments another batch operation is used to get the metadata for the attachments which will include the Attachment name (which we can then derive the attachment type from using the file extension name) and the size of the attachment. If its a cloudy attachment then this is processed separately. I haven't included any code to call out to OneDrive to get the filesize for the cloudy attachments but its possible to do this with some extra code (generally you would want to do this asynchronously while processing the Exchange attachments).

The Results the script produces is the following html report

The script writes its result sets out in JSON, I've then used the Tabulator JavaScript library which can turn JSON datasets into well formatted tables and offer sorting options so its quite easy to create a more user friendly results set then just a plain CSV file which I usually use. Currently the script will report on just one particular folder as I envisaged that it would be best used in conjunction with other scripts (that utilize Get-MailboxStatistics and Get-MailboxFolderStatistics eg take your pick from ) where you identify a particular Mailbox or  folder you want to investigate more thoroughly as this script can take some time to run. I also have two defined modes where you can process Inline Attachments (which are primary just image files) or skip the processing of Inline attachments which will make the script run a lot faster because of the number of Messages that have inline attachments.

Some Examples of running the script on the Inbox folder without processing Inline attachments

Import-Module .\AttachmentStats.ps1
Invoke-MailboxFolderAttachmentReport -MailboxName gscales@dataru -FolderPath \Inbox -Verbose

Or with processing Inline attachments

Import-Module .\AttachmentStats.ps1
Invoke-MailboxFolderAttachmentReport -MailboxName gscales@dataru -FolderPath \Inbox -IncludeInline -Verbose
The report will be saved to the same path as the script module is located.

The regular version of the script can be found on GitHub

The cloudy version of the script can be found on GitHub here 

The cloudy version will require the following EWS Managed API version to be located in the same directory as the script module.

The Cloudy version will produce a separate table of cloudy attachment stats eg

Hire me - If you would like to do something similar to this or anything else you see on my blog I'm currently available to help with any Office365, Exchange or Active Directory related development work or scripting, please contact me at too big or small).

Wednesday, June 27, 2018

Building a Microsoft Teams Tab Application that uses the Graph API and Exchange OOF data

Over the years I've created a few different versions of In/Out boards like this that have used Exchange FreeBusy information to present the In/Out status for Users. In this post I am going to look at how you can create the same thing in Microsoft Teams using a Team's tab application that will call some Microsoft Graph endpoints that will first get the members of a particular Team, then there OOF MailTip to determine if they have an AutoResponse set and finally get there userphoto. The end results is a board that looks like this

Team's Tab Applications

Team's tab apps are very simular to the Add in's framework used across other Office365 products that utilize JavaScript and html to provide a consistent approach across desktop and web clients. However because teams is fairly new the underlying client SDK isn't as feature rich or mature as you would find for example with Exchange/Outlook. Some examples of this is there is no way to get the members of a Team using the client SDK (v1.0). Also the Authentication methods to get the token to use against other workloads is not as seamless and in some cases relies on Auth popups that can be blocked by web browsers.

Getting Started

To get started with developing and using your own custom tab applications you first need to enable side loading of Apps in the Office365 Admin portal ref .The important part is "Sideloading is how you add an app to Teams by uploading a zip file directly to a team. Sideloading lets you test an app as it's being developed. It also lets you build an app for internal use only and share it with your team without submitting it to the Teams app catalog in the Office Store. "

You also will need somewhere to host your pages a good free solution is GitHub pages  or Azure if you have a subscription or credits via MSDN etc.

An Application registration is needed which is used in both the Teams manifest and it allows you to assign what rights your Team app will have to other workloads in the Graph API. To Create a app registration see . This sample app uses a v1 registration


Because this app accesses various graph endpoints different oauth Grants need to be allowed eg
Admin Consent 

Once you have your application registration setup with the correct user grants for what you plan to access because some of these permissions may require Administrative Consent you will need to consent to its use within your tenant. The easiest way to do this is in a Web Browser, eg post the following where you replace the Client_Id with the application Id from the registration you just created.  

Installing a Custom tab app

Once you have met all the prerequisites you are now ready to upload the App into teams and enable it on your desired channel. To do this you need to use the "upload a custom app" link which is available when you click ManageTeam-Apps tab see

(Note if you don't see  the "upload a custom app" check that you have side loading of apps enabled in your tenant config)

What you upload here is a Zip file containing your manifest file and two images that you manifest refers to for eg
   "icons": {
    "outline": "Outline32.png",
    "color": "Colour192.png"

For this sample this is located in

These icons are what is used in the UI to represent your application.

How the code works

Because the code uses the Graph API to get the Team Members, AutoResponse Mailtip and Profile picture the first thing it must do is get a Token to use against the Graph endpoint. With this sample it uses the JavaScript ADAL which allows you to do a silent Authentication if a cached token is already available otherwise a popup is used to authenticate the user (in most cases the user doesn't need to enter anything the popup will showup and be able to use the currently logged on creds). The Authentication flow through tab applications is explained in detail here .

Once a Token has been acquired the requests to the Graph are relatively straight forward, for performance reasons the requests are preformed asynchronously so the app can be as responsive to the user as possible.


Because this was proof of concept sample its not optimized for performance, eg for things like getting the user photo if you had a very large group then this could potentially take quite a while to get and render all these images (eg you need to start implementing paging and caching things in blob storage to improve performance and user response).

All the code for this sample is available in GitHub

Its also hosted in GitHub pages if you have a development tenant and want to test it (you will need to first use the admin consent URL listed above).

This is just a sample to show what you can do within the teams client to bring in data from other workloads. Other things you could do with this is provide a reporting interface directly within a Teams channels for the data provided by the Graph endpoint who's functionality is constantly growing.

Hire me - If you would like to do something similar to this or anything else you see on my blog I'm currently available to help with any Office365, Exchange or Active Directory related development work or scripting please contact me at too big or small).

Tuesday, June 19, 2018

Using Message and Adaptive Cards with Powershell in Email and Microsoft Teams in Office365

Cards are "a user-interface (UI) container for short or related pieces of information ref" which there are currently two formats for, MessageCards and AdativeCards . The underlying data format is a JSON object that describes the content to be rendered by a supporting client. Eg here is a basic sample

So they are a simple way of presenting information in consistent manner which  then makes deploying that same UI experience across multiple client forms factors (mobile,web,desktop) a lot easier.  Cards also allow you to define actions, so then the cards themselves can become a small app or part of an integration piece of a Line of Business workflow etc. Actionable Cards in Outlook have the ability to link with an Outlook Addin's see which starts to really extend what you can use cards for. This means your no longer just stuck in the limited feature set of Cards but you can use the richness available in the Outlook Addin framework.

Email Security  

Cards rely on JSON (JavaScript Object Notation) and JavaScript to provide the functionality that they do. Messaging people that have been around for a while would know that JavaScript (and other active Content) in Email bodies hasn't been allowed for a very long time because of the risk of malicious code execution. Therefor senders of Actionable cards must be registered with Microsoft and Approved using the following portal . If you just want to send Actionable messages within your own tenant you would registered your App within your tenant but your Actionable Messages would not have any context outside of your tenant (or other peoples Actionable Messages within your own).

Client Support 

 If you are a student of Technology cards are a good study into cloud cadences and just how quickly standards can come and go now but also how hard it can be to get all your software wheels turning to keep pace. For example MessageCards are now considered legacy however as of this post June 2018 both the Outlook desktop  and the Team's clients don't support adaptive cards in their current general releases.  Eg for Adaptive Cards support in Teams you will need the developer preview and in the Outlook Desktop client you need to be on the Monthly Channel with version 1085. This is neither good nor bad but if you find Adaptive Cards the exact thing you need to solve your problem you should be aware of client support status before you start your planning. 


For this post I've created both a MessageCard PowerShell sample that can use be used in either Outlook or Teams and a Adaptive Cards sample that can be used primary in OWA at the moment. The to produce just the MessageCard can be found and the Adaptive Card

Microsoft Teams - In/Out - Out of Office Teams Message card

This script will produce the following output to a Teams Channel using a incoming WebHook to receive the Messages

This example uses my Exch-REST module available from the PowerShell Gallery and GitHub . This contains all the code to first get the Group associated with a particular team, get the members of that groups mail Addresses, make the Graph Mail tips request and then package the results as JSON message which can then be posted to a WebHook.

To Setup a WebHook for the script to post to on a Team see

 So lets look at how this all fits together

Connect-EXRMailbox -mailbox
$GroupName = "A Team" $Group = Get-EXRModernGroups -GroupName $GroupName $Members = Get-EXRGroupMembers -GroupId $
The above code first gets the ModernGroup which is associated with the Team you are working with using the Teams DisplayName. It then uses the GroupId that is returned to make a request to Get the Members of that group.

$OOFMailboxCol = @() $mtHash = @{} foreach($Member in $Members){     if(![String]::IsNullOrEmpty($Member.mail)){         $OOFMailboxCol += $Member.mail         $mtHash.Add($Member.mail,$Member.displayName)     }  }
This code creates a collection of Mailboxes to use the Get-EXRMailTips operation and also creates a hashtable to allow us to map the email address in the Mailtips response back to the DisplayName because by default MailTips only gives you back the EmailAddress.

$Mailtips = Get-EXRMailTips -Mailboxes $OOFMailboxCol -tips "automaticReplies"
$FactsColl = ($Mailtips | select @{Name='EmailAddress';Expression={$mtHash[$_.emailAddress.address]}},@{Name='OOF Message';Expression={$val =($_.automaticReplies.message -replace '<[^>]+>','').Trim();if([String]::IsNullOrEmpty($val)){"In"}else{"Out : " + $val}}})
$WebhookAddress = ""
Invoke-WebRequest -Uri $WebhookAddress -Method Post -ContentType 'Application/Json' -Body (New-EXRMessageCard -Facts $FactsColl -Summary "Team OOF Status" -Title "Team OOF Status")
The first line of the above code makes the MailTips request for the addresses that are passed into it. The next line builds a Collection of Facts which is a Messagecard element that display a two column table. I've used expressions to clean up the results as the MailTips response is a HTML value that needs to be formatted back to text for the card and I've added some In/Out prefixes based on the response. The last line of code is what submits the Message card to the configured webhook address in the $WebhookAddress variable.

The Full Sourcecode for this script can be found

Adaptive Card to show the Office365 Service health status


As I mentioned previously if your going to send an Adaptive Card as an email you need to be registered in the portal and include the originator Id you receive in your adaptive Card. For testing what you can do is send an Adaptive Card to yourself (from yourself) and without the originator property being set and it will appear okay. But if you where to send it to someone else the content will be stripped from the message.


One of the other things that you can get from the endpoint that I talked about in this post is the Service Health information about each of the different workloads in Office365. In this example I use the Management API to produce the following adaptive card example eg

Adaptive Cards have a lot more formatting options for example you can have a column set and use different font colors. To make the script as dynamic as possible I do the color switching using a hashtable. This example uses my Exch-REST module available from the PowerShell Gallery and GitHub  . The script does the following

$To = "" Connect-EXRMailbox -MailboxName "" Connect-EXRManagementAPI -UserName "" $StatusArray = Get-EXRMCurrentStatus | Select-Object WorkLoad,Statusdisplayname,StatusTime 
In the above code it makes a connection to the Mailbox and ManagementAPI and then builds an collection for the service health information.

$ColorSwitchHash = @{} $ColorSwitchHash.Add("Service degradation","attention") $ColorSwitchHash.Add("Normal service","good")
This code sets up a HashTable that will be used to display a different color for the service status column based on its status. Note the Adaptive Cards support a subset of the MarkDown format so attention (red), good (green) etc.

$Card = New-EXRAdaptiveCard -Columns $StatusArray -ColorSwitchColumnNumber 1 -ColorSwitchHashTable $ColorSwitchHash -ColorSwitchDefault warning Send-EXRAdaptiveCard -To $To -Subject "Office 365 Service Status" -AdaptiveCard $Card
The last part of the code generates the AdatpiveCard, the ColorSwitchColumnNumber is a zero based array to match with the collection that is passed in. What Send-EXRAdaptiveCard does is wraps the JSON from the Card in the following script which is necessary when you send a message.

<script type="application/adaptivecard+json"></script>

Hire me - I'm currently available to help with any Exchange\Office365 related development work or scripting please contact me at (nothing too big or small). I'd also be interested in hearing from any companies that want to sponsor open source projects around Exchange and Office365 development.