Back to script library
Entra / Microsoft 365 · Exchange Online

Send welcome message with calendar events

A script to send a welcome message to new user accounts, complete with an attachment to welcome the new account.

Connect & set up

Run these once per session. All scopes are read-only unless the script makes changes.

Connect-MgGraph -ClientId $AppId -TenantId $TenantId -CertificateThumbprint $Thumbprint

Run it

The main script. Copy it, or download the .ps1 and run it from your console.

param(
[string] $TenantId = "",
[string] $AppId = "",
[int] $LookbackDays = 7
)
Function Update-MessageRecipients {
[cmdletbinding()]
Param(
[array]$ListOfAddresses )
ForEach ($SMTPAddress in $ListOfAddresses) {
@{
emailAddress = @{address = $SMTPAddress}
}
}
}
Function Update-MessageAttachments {
[cmdletbinding()]
Param(
[array]$ListOfAttachments
)
[array]$MsgAttachments = $null
ForEach ($File in $ListOfAttachments) {
$ConvertedContent = [Convert]::ToBase64String([IO.File]::ReadAllBytes($File))
$FileExtension = [System.IO.Path]::GetExtension($File)
Switch ($FileExtension) {
".pdf" {
$ContentType = "application/pdf"
}
".docx" {
$ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
}
".xlsx" {
$ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}
".pptx" {
$ContentType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
}
".jpg" {
$ContentType = "image/jpeg"
}
".png" {
$ContentType = "image/png"
}
default {
$ContentType = "application/octet-stream"
}
}
$AttachmentDetails = @{
"@odata.type" = "#microsoft.graph.fileAttachment"
Name = $File
ContentType = $ContentType
ContentBytes = $ConvertedContent
}
$MsgAttachments += $AttachmentDetails
}
Return $MsgAttachments
}
# Start of processing
$Thumbprint = '0CF6CE3F3548FD73E7AC8CF20226ED447E125C71'
# Connect in app-only mode
Connect-MgGraph -ClientId $AppId -TenantId $TenantId -CertificateThumbprint $Thumbprint
# Runbook - connect using a managed identity. Make sure that the automation account has the
# necessary permissions and that the required modules like Microsoft.Graph.Authentication, Microsoft.Graph.Calendar,
# Microsoft.Graph.Users, and Microsoft.Graph.Users.Actions are added to the runbook environment
Connect-MgGraph -Identity
# We're going to look for user accounts created in the last week, but you can change this as needed
$WeekAgo = (Get-Date).AddDays(-$LookbackDays).toString("yyyy-MM-ddTHH:mm:ssZ")
# and the user accounts must be licensed for Exchange Online -
$ExoServicePlan1 = "9aaf7827-d63c-4b61-89c3-182f06f82e5c"
$ExoServicePlan2 = "efb87545-963c-4e0d-99df-69c6916d9eb0"
# Find the set of user accounts licensed for Exchange Online created in the last seven days
[array]$Users = Get-MgUser -Filter "(createddateTime ge $WeekAgo and userType eq 'Member') `
and (assignedPlans/any(c:c/servicePlanId eq $ExoServicePlan1 and capabilityStatus eq 'Enabled') `
or assignedPlans/any(c:c/servicePlanId eq $ExoServicePlan2 and capabilityStatus eq 'Enabled'))" `
-ConsistencyLevel eventual -CountVariable Test -All -PageSize 500 -Sort ('displayname') `
-Property Id, displayName, userprincipalName, assignedPlans, CreatedDateTime, mail
If (!$Users) {
Write-Host "No new users found. Exiting!"
break
} Else {
Write-Host "Found" $Test "new users created in the last week. Processing..."
}
# Message sender can be any user in the tenant.
$MsgFrom = 'Azure.Management.Account@office365itpros.com'
# Get Tenant name
$TenantName = (Get-MgOrganization).displayName
$MsgSubject = "A warm welcome to $($TenantName)"
# Define some variables used to construct the HTML content in the message body
#HTML header with styles
$HtmlHead="<html><style>BODY{font-family: Arial; font-size: 10pt;}
H1{font-size: 22px;}
H2{font-size: 18px; padding-top: 10px;}
H3{font-size: 16px; padding-top: 8px;}
</style>"
# Define attachments we're only using one here, which we fetch from a web site
# if you want to add more files, add the file names to the $AttachmentsList array.
# Obviously, change the file to something that makes sense for your organization.
$WebAttachmentFile = "https://office365itpros.com/wp-content/uploads/2022/02/WelcomeToOffice365ITPros.docx"
# fetch the content of the web file and store it in the downloads folder
$Attachment = ((New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path) +"\WelcomeNewEmployeeToOffice365itpros.docx"
Invoke-WebRequest -Uri $WebAttachmentFile -OutFile $Attachment
[array]$Attachments = $Attachment
#Content for the message - obviously, this is very customizable and should reflect what you want to say to new users
$HtmlBody = "<body>
<h1>Welcome to $($TenantName)</h1>
<p><strong>Generated:</strong> $(Get-Date -Format 'dd-MMM-yyyy')</p>
<h2><u>We're Pleased to Have You Here</u></h2>
<p><b>Welcome to your new Microsoft 365 account</b></p>
<p>You can open your account to access your email and documents by clicking <a href=http://www.portal.office.com>here</a> </p>
<p>Have a great time and be sure to call the help desk if you need assistance. And be sure to read all the great articles about Microsoft 365 published by our <a href=https://office365itpros.com>Microsoft 365 experts</a>.</p>"
# Determine if any calendar events for corporate events should be added to the message. We look for events in the next six weeks.
# If any events are found, create an .ics file and add it to the attachments list
$CalendarUser = Get-MgUser -UserId "Corporate.Events.Meetings@office365itpros.com"
Try {
$NoCorporateEvents = $False
$Calendar = Get-MgUserDefaultCalendar -UserId $CalendarUser.Id -ErrorAction Stop
} Catch {
Write-Host "Can't find calendar for corporate events" $CalendarUser " - skipping addition of calendar events to message attachments"
$NoCorporateEvents = $True
}
$StartDateTime = (Get-Date).ToString("yyyy-MM-dd")
$EndDateTime = (Get-Date).AddDays(42).ToString("yyyy-MM-dd")
Try {
[array]$Events = Get-MgUserCalendarView -UserId $CalendarUser.Id -CalendarId $Calendar.Id -all `
-EndDateTime $EndDateTime -StartDateTime $StartDateTime -ErrorAction Stop
} Catch {
Write-Host "Error fetching events from corporate events calendar - skipping addition of calendar events to message attachments"
$NoCorporateEvents = $True
}
# If we have some events, go and process them
If ($Events -and $NoCorporateEvents -eq $False) {
Write-Host "Found" $Events.Count "upcoming events in the corporate calendar. Adding users as meeting participants."
# Define array of new attendees to add as participants to the corporate events.
# In this example, we're just adding the new users as optional attendees, but you could change this as needed.
[array]$NewAttendees = @()
# Loop through the users we want to add to the event
ForEach ($User in $Users) {
# Create hash table containing attendee details
$NewAttendee = @{
EmailAddress = @{
Address = $User.Mail
Name = $User.displayName
}
Type = "optional" # or "required"
}
# Add the hash table to the array of new attendees
$NewAttendees += $NewAttendee
}
ForEach ($Meeting in $Events) {
# Get current attendees for the meeting
[array]$CurrentAttendees = $Meeting.Attendees
# Combine the arrays of new and existing attendees to create an updated list of attendees for the meeting
[array]$UpdatedAttendees = $CurrentAttendees + $NewAttendees
# Update the meeting to add the users as participants
Try {
$Status = Update-MgUserEvent -UserId $CalendarUser.Id -EventId $Meeting.Id -Attendees $UpdatedAttendees -ErrorAction Stop
} Catch {
Write-Host "Error updating meeting" $Meeting.Subject ":" $_.Exception.Message
}
}
}
# Create the attachments for the message
[array]$MsgAttachments = Update-MessageAttachments -ListOfAttachments $Attachments
# Loop through each user to generate and send the welcome message, customizing the content for each user.
ForEach ($user in $users) {
$ToRecipientList = @( $User.Mail )
[array]$MsgToRecipients = Update-MessageRecipients -ListOfAddresses $ToRecipientList
Write-Host "Sending welcome email to" $User.DisplayName
# Customize the message
$htmlHeaderUser = "<h2>A Huge Welcome to Our New User " + $User.DisplayName + "</h2>"
$HtmlMsg = "</body></html>" + $HtmlHead + $htmlHeaderUser + $HtmlBody + "<p>"
# Construct the message body
$MsgBody = @{
Content = "$($HtmlMsg)"
ContentType = 'html'
}
$Message = @{subject = $MsgSubject}
$Message += @{toRecipients = $MsgToRecipients}
$Message += @{ccRecipients = $MsgCcRecipients}
$Message += @{attachments = $MsgAttachments}
$Message += @{body = $MsgBody}
$Params = @{'message' = $Message}
$Params += @{'saveToSentItems' = $True}
$Params += @{'isDeliveryReceiptRequested' = $True}
Try {
Send-MgUserMail -UserId $MsgFrom -BodyParameter $Params -ErrorAction Stop
} Catch {
Write-Host "Error sending message to" $User.DisplayName ":" $_.Exception.Message
}
}
Write-Host "Processing complete. Sent welcome messages to" $Users.Count "new users created in the last week."

Parameters

ParameterDefaultNotes
-TenantId""Microsoft Entra tenant ID for app-only Graph authentication.
-AppId""Application (client) ID for the app registration used to connect.
-LookbackDays7How many days back to search for newly created mailboxes or recent activity.
Attribution