A recent appeal from a reader for a PowerShell script to send a welcome message to new people joining an Office 365 tenant forced me to think about the best solution. The issue isn’t finding a script to do the job because there’s plenty of scripts published in places like the TechNet Gallery (here’s an example).
The issue boils down to deciding how to identify new users. Three approaches are available to find new users.
Use the Get-MsOlUser cmdlet from the Microsoft Online Services module (which is what the script pointed to above does).
Use the Get-AzureADUser cmdlet from the Azure Active Directory module.
My preferred approach is the last. It’s the simplest and most straightforward. Let’s see why.
Microsoft Online Services
The basic steps in this approach are:
Run Get-MsOlUser to return a set of accounts from Azure Active Directory that have been created in the recent past.
Calculate an email address for the user from the proxy addresses for the account.
Call Send-Message to send the welcome email.
One immediate issue that we face with Get-MsOlUser is to only select user accounts. Azure Active Directory includes all sorts of accounts that we don’t want to send welcome messages to, like the accounts created for shared and room mailboxes or those created for guest accounts. This snippet finds user accounts created in the last 7 days. The check for licensed accounts is an effective filter to focus on user accounts. In general, guest accounts and shared accounts don’t need Office 365 licenses.
# Find Office 365 user accounts created in the last 7 days
$CheckDate = (Get-Date).AddDays(-7)
$Users = Get-MsolUser -All | Where-Object {$_.WhenCreated -ge $CheckDate -and $_.isLicensed -eq $True }
After finding the user accounts, we can select an email address from the ProxyAddresses property. The primary SMTP address is marked with a prefix of “SMTP:,” but you can send to any proxy address registered for an account and Exchange Online will deliver the message. We can therefore do something like:
# Get Email address from the first proxy address in a user account
$EmailAddress = $User.ProxyAddresses[0].Split(":")[1]
Alternatively, if your organization assigns User Principal Names to accounts that match their email addresses, you can send the message to the address stored in the UserPrincipalName property.
Azure Active Directory
I prefer to use the Azure Active Directory module whenever possible because code written with this module is likely to last longer. Microsoft hasn’t said when they will deprecate the Microsoft Online Services module, but I assume this will be done in the future because it doesn’t make sense to have two modules that essentially do the same job. For these cmdlets, we need to:
Run Get-AzureADUser to return a set of accounts.
Check the creation date of each account to see if it’s been created recently.
Get the address for each matching account and send the email.
The command to fetch the set of accounts is as follows. We initially filter on UserType to exclude guest accounts. If we did no more, the set of objects would include accounts for shared and resource mailboxes, so we include an extra filter against ProvisionedPlans. This property holds details of the Office 365 plans assigned to the account and is empty when the account has no licenses.
# Find set of Azure Active Directory accounts
$Users = (Get-AzureADUser -All $True | ? {$_.UserType -eq "Member" -and $_.ProvisionedPlans -ne $Null} | Select ObjectId, DisplayName, UserPrincipalName, ProxyAddresses)
Get-AzureADUser doesn’t return a WhenCreated property for an account (it’s included in the ExtensionProperty property), so we need to call the Get-AzureADUserExtension cmdlet to extract the creation date.
# Calculate the age of the account
CreationDate = (Get-AzureADUserExtension -ObjectId $User.ObjectId).Get_Item("createdDateTime")
$AccountAge = ($CreationDate | New-TimeSpan).Days
After figuring out how old the account is, we decide whether to send the email and can use the same method to find an address for the message.
Exchange Online Cmdlets
Exchange Online has its own directory (EXODS) synchronized with Azure Active Directory. EXODS holds information for all mail-enabled objects. Given that we want to send email to new users, it’s reasonable to say that we can:
Run the Get-Mailbox or Get-ExoMailbox cmdlets to find recently-added mailboxes.
Send a message to those mailboxes.
Finding our mailboxes is easy and we can do some extra processing as we create the set of objects. This command applies a server-side filter (for better performance) based on the mailbox creation date (i.e. WhenMailboxCreated is used instead of WhenCreated). User mailboxes are selected if they were created in the last seven days.
# Get list of mailboxes created recently
[string]$Checkdate = (Get-Date).AddDays(-7)
$Users = (Get-ExoMailbox -Filter "WhenMailboxCreated -gt '$CheckDate'" -RecipientTypeDetails UserMailbox -ResultSize Unlimited -Properties WhenMailboxCreated | Select WhenMailboxCreated, DisplayName, UserPrincipalName, PrimarySmtpAddress)
The Get-Mailbox cmdlet returns a lot of properties, so we only select those that we need to process. In this case, we use Get-ExoMailbox, so we need to request that the WhenMailboxCreated property is returned because it is not in the minimum property set.
Being able to use a server-side filter to select a set of mailboxes is not only convenient; it’s also best in terms of performance, especially when a tenant supports large numbers of accounts.
Sending The Welcome Message
Now that we’ve figured out how to find the people we want to welcome, we can proceed to send the welcome message. I wrote a script to define some variables for HTML formatting and then assemble the elements of a HTML messages from details of each user. The script is shown below.
# Date to Check for new accounts created in the last 7 days
[string]$CheckDate = (Get-Date).AddDays(-7)
# Make sure that we have valid credentials
If (-not $O365Cred) { #Make sure we have credentials
$O365Cred = (Get-Credential)}
# Message is from the logged in account
$MsgFrom = $O365Cred.UserName ; $SmtpServer = "smtp.office365.com" ; $SmtpPort = '587'
# Define some variables for the message
#HTML header with styles
$htmlhead="
<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>"
#Header for the message
$HtmlBody = "
<h1>Welcome to Our Company</h1>
<p><strong>Generated:</strong> $(Get-Date -Format g)</p>
<h2><u>We're Pleased to Have You Here</u></h2>"
# Find all mailboxes created in the target period
$Users = (Get-ExoMailbox -Filter "WhenMailboxCreated -gt '$CheckDate'" -RecipientTypeDetails UserMailbox -ResultSize Unlimited -Properties WhenMailboxCreated | Select WhenMailboxCreated, DisplayName, UserPrincipalName, PrimarySmtpAddress)
ForEach ($User in $Users) {
$EmailRecipient = $User.PrimarySmtpAddress
Write-Host "Sending welcome email to" $User.DisplayName
$htmlHeaderUser = "<h2>New User " + $User.DisplayName + "</h2>"
$htmlline1 = "<p><b>Welcome to Office 365</b></p>"
$htmlline2 = "<p>You can open Office 365 by clicking <a href="http://www.portal.office.com">here</a> </p>"
$htmlline3 = "<p>Have a great time and be sure to call the help desk if you need assistance.</p>"
$htmlbody = $htmlheaderUser + $htmlline1 + $htmlline2 + $htmlline3 + "<p>"
$HtmlMsg = "" + $HtmlHead + $HtmlBody
# Construct the message parameters and send it off...
$MsgParam = @{
To = $EmailRecipient
From = $MsgFrom
Subject = "A Hundred Thousand Welcomes"
Body = $HtmlMsg
SmtpServer = $SmtpServer
Port = $SmtpPort
Credential = $O365Cred }
Send-MailMessage @msgParam -UseSSL -BodyAsHTML}
An example of a welcome message is shown in Figure 1. It’s easy to tweak the script to add appropriate text for your organization, including links, graphics, and other elements.
Figure 1: An example welcome message sent to a new mailbox
What is evident from this experience is that multiple ways exist to find out a list of new accounts/mailboxes. The number of PowerShell modules available for Office 365 sometimes makes it difficult to decide what the right tool is for any particular job. Some experimentation and testing helps to understand the strengths and weaknesses of each approach. In this case, if you want to send a welcome message to new Office 365 accounts, my recommendation is to use the Exchange Online cmdlets.
This is a great example of the in-depth consideration the Office 365 for IT Pros team gives to topics we cover in the book as well as those that never end up being covered. Our dedication (or obsession) benefits everyone!
I know this is an old post, but had a question around the credential prompt you get at the end of the script. We’d like to run this on a scheduled task and how would I go about adjusting the script so I didn’t get that prompt?
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
I know this is an old post, but had a question around the credential prompt you get at the end of the script. We’d like to run this on a scheduled task and how would I go about adjusting the script so I didn’t get that prompt?
Run the script as a scheduled task in Azure Automation? https://practical365.com/use-azure-automation-exchange-online/