Exchange Web Services (EWS)

Roger Roger 6,406 Reputation points
2024-10-04T20:44:30.2333333+00:00

Hi All,

I am using an Exchange 2016 hybrid environment. All my users have been migrated from on-prem to online. I have a requirement to forward meeting invites from a shared mailbox to users, and my input file changes daily. I need to schedule this script via Task Scheduler.I have downloaded the EWS Managed API from this URL: https://github.com/OfficeDev/ews-managed-api. Additionally, I have created an app registration in Azure with the delegated Microsoft Graph API permission EWS.AccessAsUser.All. Below script is not throwing any error but meeting invites are not getting forwarded.

New-ServicePrincipal -AppId "111111111111111" -ObjectId "ooooobbbbbbbbb" -DisplayName Test1
New-ManagementScope -Name "CRS" -RecipientRestrictionFilter "Department -eq 'somedept'"
New-ManagementRoleAssignment -App  "111111111111111" -Role " Application EWS.AccessAsApp" -CustomResourceScope "CRS"

=====================================================================

Start-Transcript -Path "c:\temp\transcript.txt"
$ApplicationId = "111111111111111"
$ApplicatoinSecret = "sseeccrreett"
$TenantId = "222222222222222222"

# Get an access token
$body = @{
    grant_type    = "client_credentials"
    client_id     = $ApplicationId
    client_secret = $ApplicatoinSecret
    scope         = "https://outlook.office365.com/.default"
}

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $body
$accessToken = $response.access_token

# Load EWS Managed API (Exchange 2016)
$EWSServicePath = 'C:\Temp\EWS\bin\Debug\Microsoft.Exchange.WebServices.dll'
Import-Module $EWSServicePath

# Connect to Exchange Online using EWS with OAuth
$ExchVer = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2016
$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchVer)
$Service.Url = "https://outlook.office365.com/EWS/Exchange.asmx"
$Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($accessToken)
 
# Define mailboxes and other parameters
$StartOU = "contoso.com/OU1"
$MoveCSV = ".\userlist.csv"
$Users = Import-Csv '.\userlist.csv'
$MeetingMBX = 'sharedmailbox@contoso.com'
$Items = 50

$DaysInTheFuture = 200
$Now = [System.DateTime]::Now
$Then = $Now.AddDays($DaysInTheFuture)

# Set up impersonation
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SMTPAddress, $MeetingMBX)

# Define calendar view
$folderid = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, $MeetingMBX)
$calendarFolder = [Microsoft.Exchange.WebServices.Data.CalendarFolder]::Bind($service, $folderid)
$calendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($Now, $Then)
$calendarView.MaxItemsReturned = $Items
$calendarView.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$findItemResults = $calendarFolder.FindAppointments($calendarView)

if ($findItemResults.Items.Count -gt 0) {
    $FutureMeetings = @()
    foreach ($CalItem in $findItemResults.Items) {
        if ($CalItem.IsMeeting -eq $True) {
            $FutureMeetings += $CalItem
        }
    }

if ($FutureMeetings) {
        $emaillist = @()
        foreach ($user in $Users) {
            $EmpType = $null
            $EmpType = Get-QADUser $user.UserPrincipalName -properties employeeType | select employeeType
            if (($user.RecipientTypeDetails -eq 'UserMailbox') -and (($EmpType.employeeType -eq 'emp1') -or ($EmpType.employeeType -eq 'emp2'))) {
                $emaillist += $user.emailaddress
            }
        }

if ($emaillist.Count -gt 0) {
            foreach ($item in $FutureMeetings) {
                [void]$item.Forward("Meeting invite forwarded.", $emaillist)
                "Date: $Now" | Out-File ".\FWReport.csv" -Append
                "Addresses: $emaillist" | Out-File ".\FWReport..csv" -Append
                "Subject: $item.Subject" | Out-File ".\FWReport..csv" -Append
                "Start: $item.Start" | Out-File ".\FWReport..csv" -Append
                "End: $item.End" | Out-File ".\FWReport..csv" -Append
                "Location: $item.Location" | Out-File ".\FWReport..csv" -Append
                "--------------------------------------------------------------" | Out-File ".\FWReport..csv" -Append
            }
        }
    }
}

Stop-Transcript

Microsoft Exchange Online
Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
12,268 questions
Microsoft Exchange Online Management
Microsoft Exchange Online Management
Microsoft Exchange Online: A Microsoft email and calendaring hosted service.Management: The act or process of organizing, handling, directing or controlling something.
4,578 questions
Exchange Server Management
Exchange Server Management
Exchange Server: A family of Microsoft client/server messaging and collaboration software.Management: The act or process of organizing, handling, directing or controlling something.
7,669 questions
Microsoft Exchange Hybrid Management
Microsoft Exchange Hybrid Management
Microsoft Exchange: Microsoft messaging and collaboration software.Hybrid Management: Organizing, handling, directing or controlling hybrid deployments.
2,136 questions
0 comments No comments
{count} votes

Accepted answer
  1. Jake Zhang-MSFT 6,615 Reputation points Microsoft Vendor
    2024-10-07T02:21:56.6366667+00:00

    Hi @Roger Roger ,

    Welcome to the Microsoft Q&A platform!

    It looks like you're on the right track with your script, but there are a few areas to check and adjust.Here are some specific suggestions:

    1. Ensure the path to the EWS Managed API DLL is correct and accessible:
    $EWSServicePath = 'C:\Temp\EWS\bin\Debug\Microsoft.Exchange.WebServices.dll'
    Import-Module $EWSServicePath
    
    1. Verify that the impersonation is correctly set up and that the shared mailbox allows impersonation:
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SMTPAddress, $MeetingMBX)
    
    1. Ensure the forwarding logic is correctly implemented and that the email addresses are valid:
    foreach ($item in $FutureMeetings) {
        $forwardMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service)
        $forwardMessage.Subject = "Fwd: " + $item.Subject
        $forwardMessage.Body = $item.Body
        foreach ($recipient in $emaillist) {
            $forwardMessage.ToRecipients.Add($recipient)
        }
        $forwardMessage.SendAndSaveCopy()
    }
    
    1. Ensure the logging is correctly implemented:
    "Date: $Now" | Out-File ".\FWReport.csv" -Append
    "Addresses: $emaillist" | Out-File ".\FWReport.csv" -Append
    "Subject: $item.Subject" | Out-File ".\FWReport.csv" -Append
    "Start: $item.Start" | Out-File ".\FWReport.csv" -Append
    "End: $item.End" | Out-File ".\FWReport.csv" -Append
    "Location: $item.Location" | Out-File ".\FWReport.csv" -Append
    "--------------------------------------------------------------" | Out-File ".\FWReport.csv" -Append
    
    1. Ensure the script is correctly scheduled in Task Scheduler with the necessary permissions.

    Please feel free to contact me for any updates. And if this helps, don't forget to mark it as an answer.

    Best,

    Jake Zhang

    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Roger Roger 6,406 Reputation points
    2024-10-06T05:38:01.6133333+00:00

    Any help would be appreciated

    0 comments No comments

  2. Roger Roger 6,406 Reputation points
    2024-10-09T06:14:44.0766667+00:00

    i am getting error at Get-ADUser -Filter

    Select-Object : A positional parameter cannot be found that accepts argument 'False'.

    
    Start-Transcript -Path "C:\temp\transcript.txt"
    $ApplicationId = "111111111111111"
    $ApplicatoinSecret = "sseeccrreett"
    $TenantId = "222222222222222222"
    # Get an access token
    $body = @{
        grant_type    = "client_credentials"
        client_id     = $ApplicationId
        client_secret = $$ApplicatoinSecret
        scope         = "https://outlook.office365.com/.default"
    }
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    $response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $body
    $accessToken = $response.access_token
    # Load EWS Managed API (Exchange 2016)
    $EWSServicePath = 'D:\EWS\bin\Debug\Microsoft.Exchange.WebServices.dll'
    Import-Module $EWSServicePath
     
    # Connect to Exchange Online using EWS with OAuth
    $ExchVer = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2016
    $Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchVer)
    $Service.Url = "https://outlook.office365.com/EWS/Exchange.asmx"
    $Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($accessToken)
     
    # Define mailboxes and other parameters
    $StartOU = "contoso.com/OU1"
    $MoveCSV = ".\userlist.csv"
    $Users = Import-Csv '.\userlist.csv'
    $MeetingMBX = 'sharedmailbox@contoso.com'
    $Items = 50
    $DaysInTheFuture = 200
    $Now = [System.DateTime]::Now
    $Then = [System.DateTime]::Now.AddDays($DaysInTheFuture)
     
    # Set up impersonation
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SMTPAddress, $MeetingMBX)
     
    # Define calendar view
    $folderid = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, $MeetingMBX)
    $calendarFolder = [Microsoft.Exchange.WebServices.Data.CalendarFolder]::Bind($service, $folderid)
    $calendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($Now, $Then)
    $calendarView.MaxItemsReturned = $Items
    $calendarView.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
    $findItemResults = $calendarFolder.FindAppointments($calendarView)
     
    if ($findItemResults.Items.Count -gt 0) {
        $FutureMeetings = @()
            foreach ($CalItem in $findItemResults.Items) {
            if ($CalItem.IsMeeting -eq $True) {
                $FutureMeetings += $CalItem
            }
        }
        if ($FutureMeetings.Count -gt 0) {
            $emaillist = @()
            #$upn = $user.UserPrincipalName
            foreach ($user in $Users) {
            $EmpType = $null
            $EmpType = Get-ADUser -Filter "UserPrincipalName -eq '$($user.UserPrincipalName)'" -Properties employeeType |
            Select-Object -ExpandProperty employeeType if (($user.RecipientTypeDetails -eq 'UserMailbox') -and (($EmpType -eq 'emp1') -or ($EmpType -eq 'emp2'))) { $emaillist += $user.emailaddress } }
              if ($emaillist.Count -gt 0) {
                foreach ($item in $FutureMeetings) {
                    [void]$item.Forward("Meeting invite forwarded.", $emaillist)
                    "Date: $Now" | Out-File ".\ForwardReport.csv" -Append
                    "Addresses: $emaillist" | Out-File ".\FWReport.csv" -Append
                    "Subject: $item.Subject" | Out-File ".\FWReport.csv" -Append
                    "Start: $item.Start" | Out-File ".\FWReport.csv" -Append
                    "End: $item.End" | Out-File ".\FWReport.csv" -Append
                    "Location: $item.Location" | Out-File ".\FWReport.csv" -Append
                    "--------------" | Out-File ".\FWReport.csv" -Append
                }
            }
        }
    }
    Stop-Transcript
    
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.