I’m currently involved in a large-scale migration to Microsoft 365 in an organisation with over 18.000 employees. As each step in a migration takes time, automation is a must. Also to inform users that their OneDrive for Business migration has been completed and ready to use. Email templates and PowerShell can help to automate this.

The solution in this post is actually something I inherited from a project a few years back. I recently decided to rewrite it, as there are plenty of benefits in using automated personalised emails and it was applicable to the project that I’m currently working on.

Setting the stage

Let’s say you need to migrate the local files and folders for a lot of users from a network drive to OneDrive for Business for a prolonged period. So, think about frequent migrations using a standardized automated process.

Once the migration completes, you need to inform the users that their OneDrive for Business drive has been populated with their files and ready to use. The best way to do this at scale, is to use a personalised email notification message.


To do this with an attractive email message including corporate look and feel, you can use an HTML-file as a template.

As you can see in the example, the html-template contains several placeholders, recognizable by ~{placeholderName}~. This is basically the foundation of the personalized message. The template contains the static text and e.g. links to a support portal. The logo image (Contoso in the example), is embedded in the templates using base64 encoding. To generate the encoded image, upload the original image file to a popular conversion service like Base 64 Guru and include it in the HTML-template.

The HTML for the image would like something like this:

<img alt="Contoso logo" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAkACQAAD/4QAiRXhpZgAA

Please note that the example HTML has been truncated for display purposes. Usually the HTML would be a lot of lines.

Replacing placeholders

The logic behind the use of the HTML-template, is to replace the placeholders with actual data for each user. The input file (csv-format) with all required data would obviously be the same as for OneDrive provisioning and migration.

Adele VanceAdeleV@contoso.com\\srv-45-ad\homedrive\AdeleV{}
Bianca PetersBiancaP@contoso.com\\srv-45-ad\homedrive\BiancaP{}
Chris GreenChrisG@contoso.com\\srv-45-ad\homedrive\ChrisG{}

Once the csv-file is imported, the basic code in the script would be something like the following:

foreach ($row in $table) {
    #Define the replacement parameters
    $replaceParams = @{
        displayName  = $row.userPrincipalName
        sourceFolder = $row.sourceFolder
        url          = $row.url

    #Set the request parameters
    $requestParams = @{
        emailTemplatePath = "$templatesPath\OneDriveMigration_Anouncement.htm"
        toEmailAddress    = $row.userPrincipalName
        fromEmailAddress  = $migrationTeamEmail
        emailSubject      = 'Welcome to OneDrive for Business'
        smtpServer        = $smtpServer
        smtpServerPort    = $smtpServerPort
        smtpCredential    = $smtpCredential
        replaceParams     = $replaceParams

    #Send the email
    Send-FactoryEmail @requestParams


So, based on the input file data, you can create a $replaceParams splat including all elements to replace. This splat is than added to a $requestParams splat which includes all parameters for the ‘Send-FactoryEmail’ function. This also included the smtp server data to actually send the email.


The magic happens in the ‘Send-FactoryEmail’ function.

function Send-FactoryEmail {
        [Parameter(Mandatory = $true)]
        [Parameter(Mandatory = $true)]
        [Parameter(Mandatory = $true)]
        [Parameter(Mandatory = $true)]
        [Parameter(Mandatory = $true)]
        [Parameter(Mandatory = $true)]
        [Parameter(Mandatory = $false)]
        [Parameter(Mandatory = $true)]

    #Get template for email Body
    [string]$body = Get-Content -Path $emailTemplatePath

    #Replace variables with parameter data
    foreach ($replaceParam in $replaceParams.GetEnumerator()) {

        $body = $body.Replace("~$($replaceParam.Name)~", $replaceParam.Value)


    #Set the request parameters
    $requestParams = @{
        Subject    = $emailSubject
        Body       = $body
        BodyAsHtml = $true
        To         = $toEmailAddress
        From       = $fromEmailAddress
        SmtpServer = $smtpServer
        Port       = $smtpServerPort
        UseSsl     = $true

    #Add additional parameters based on input
    if ($smtpCredential) {

        $requestParams.Add('Credential', $smtpCredential)


    #Send the message
    Send-MailMessage @requestParams


The first part of the function is to import the HTML from the template as a string in to a variable called $body.

#Get template for email Body
[string]$body = Get-Content -Path $emailTemplatePath

Next, the replacement takes place for each of the provided replaceParams. Obviously, these have to match the placeholders in the template.

#Replace variables with parameter data
foreach ($replaceParam in $replaceParams.GetEnumerator()) {

    $body = $body.Replace("~$($replaceParam.Name)~", $replaceParam.Value)


Notice, that the replace wraps the replaceParam names in ‘~’-characters as a naming convention for the placeholders in the template.

After the replacement completes, the body of the email has the personalised elements similar to a mail-merge in Microsoft Word.

Last, but not least, the email message needs to be send. Using the email and smtp server data, the ‘Send-Message’ function is called and the email is send out.


Automation can save time, especially in migrations at scale. And communication is a key part of a successful migration project. HTML templates ensure a professional look and feel of the message. The Communications department of your organisation can provide the foundational template to ensure compliance with corporate identity and the replacement parameters do the rest.

And it’s flexible through the function approach: multiple template files and replace parameters can support different scenarios in which people in the organisation need to be informed or called to action.