Bulk Update Office 365 User’s Domain

Exported on 11-Nov-2021 11:02:20

Using Attune to update Office 365 Users' Domain/UserPrincipalName

This Blueprint is used for updating users' Domain/UserPrincipalName on an Office 365 Tenant.

A Tenant is an Organization, an Office 365 Tenant is an instance of Office 365 for an organization.

Pre-Blueprint Attune setup
  1. On the Inputs tab, create a Windows Node for the host you wish to run this Blueprint.
  2. On the Inputs tab, create a Windows Credentials to connect to the host you wish to run this Blueprint.
  3. On the Inputs tab, create a Text value to store the values below:
    • 0365UserName: This is the Username of the Global Administrator (DataType: String).
    • 0365Password: This is the Password of the Global Administrator (DataType: String).
    • 036Hashconfig: This holds an array of configurations (DataType: HashTable).

Two configurations can be used for this Blueprint:

  1. One with a CSV file holding specific users' that require an update and their new domain value.
  2. One without a CSV file, but the name of the old domain and new domain.
    • Note this update will affect all users on the tenant having the old domain specified

Below is a sample CSV:

UserPrincipalName
User001@contoso.com
User002@contoso.com
User003@contoso.com
User004@contoso.com
User005@contoso.com

Hash Table Configuration Syntax:


When a CSV File is used
@{'CSV'=$true;'NewDomain'="fabrikram.com";'CSVPath'="C:\Users\user\Desktop\ATTUNE";'CSVFileName'="UserEmail.csv"}

When a CSV File is not used
@{'CSV'=$false;'OldDomain'="contoso.com";'NewDomain'="fabrikram.com"}

Parameters & Descriptions

  • CSV: Specifies if a CSV is used in the configuration Accepts only $true or $false.

  • NewDomain: Holds the name of the users' new domain.

  • OldDomain: Holds the name of the users' old domain.

  • CSVPath: Holds the value of the CSV file path on the Attune Node.

  • CSVFileName: Holds the value of the CSV file name.


Blueprint Steps
  1. Check and Install the MSOnline Module
  2. Update the Office 365 Users' Domain

Parameters

Name Type Script Reference Default Value Comment
0365HashConfig Text 0365hashconfig @{'CSV'=$false;'OldDomain'="olddomain";'NewDomain'="newdomain"} This is a Hash Table configuration file for the Update Domain Blueprint
0365Password Text 0365password P@ssWorD This is the Global Admin Password for Office 365
0365UserName Text 0365username globaladmin@contoso.onmicrosoft.com This is the Global Admin Sign-in UserPrincipalName for Office 365
Attune Node Windows Server attuneNode This is an Attune Node
Attune Node Credential Windows OS Credential attuneNodeCredential This is an Attune Node Credential

1 - Install MSOnline Module

This step installs the MSOnline Module

The Blueprint first gets the Execution Policy of the current PowerShell session.

Then, checks if the Execution Policy is set to Unrestricted.

If it's not, it then sets the Execution Policy to Unrestricted for the current PowerShell session.

Next, it checks if the MsOnline module is installed.

If it's not installed, it then goes ahead to install the module.

The connection details have changed from the last step.

Login as user on node

  1. Connect via RDP
    mstsc /admin /v:Attune Node
  2. Login as user {Attune Node Credential}
  3. Then open a command prompt
This is a PowerShell Script make sure you run it with powershell.exe Click start menu, enter "powershell" in the search bar, then select the powersehll program
#Region for ExecutionPolicy
# Get Execution Policy of the current process
$Script:ProcessEP = Get-ExecutionPolicy -Scope Process

#Get the value of the Execution Policy and save it in the Variable
$Script:ValueProcessEP = ($Script:ProcessEP).value__

# Check if the Execution Policy of the process is set to Unrestricted
if ($Script:ValueProcessEP -eq 0) {

    # Write the message
    Write-Output "Execution Policy is already set to Unrestricted for the Process"
    # Check if the Execution Policy of the process is already set
}else{

    # Set the ExecutionPolicy of the Process to Unrestricted
    Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted -Force -Confirm:$false

    # Checks if the Execution Policy has been set
    if ((Get-ExecutionPolicy -Scope Process).value__ -eq 0) {

        # Write the message
        Write-Output "Execution Policy is now set to Unrestricted for the Process"
    }
}
#EndRegion for ExecutionPolicy 


#Region to Check if MSOnline Module is installed 
#Region if module is installed, update module if version is not up to Version "1.1.183.57"
if($null -ne (Get-InstalledModule -Name MSOnline -RequiredVersion "1.1.183.57" -ErrorVariable +Error365V -ErrorAction SilentlyContinue)) {

    # Get the MSOnline module installed and save it in a variable
    $Script:GetMSOnline = Get-InstalledModule -Name MSOnline -RequiredVersion "1.1.183.57" -ErrorVariable +Error365V -ErrorAction SilentlyContinue

    # echo the message
    Write-Output "MSOnline PowerShell Module exists ... checking ..."

    # Gets the build number for the MSOnline Module 
    $Script:MSOnlineBuild = ($Script:GetMSOnline).Version

    # Checks the build number to meet requirements 
    if($Script:MSOnlineBuild -like "*1.1.183.57*") {

        # Saves and converts Module version name to a variable
        $Script:OutVersion = ((($Script:GetMSOnline).Version)).tostring()

        # echo the message
        Write-Output "MSOnline Module Version $Script:OutVersion meets the minimum requirement."

    # Check if the build version is on 13
    }else{
        
        # echo the message
        Write-Output "MSOnline PowerShell Module is updated :)"
    }
#EndRegion if the module is installed, update module if the version is not up to Version "1.1.183.57"
#Region If the module is not installed, install it 
}else{

    # echo the message
    Write-Output "MSOnline PowerShell Module is not installed"
    
    # echo the message
    Write-Output "MSOnline PowerShell Module is installing..."
    
    Import-Module PowerShellGet
    
    # Install MSOnline Powershell Module 
    Install-Module -Name MSOnline -RequiredVersion "1.1.183.57" -Scope "CurrentUser" -AllowClobber:$true -Confirm:$false -Force

    # echo the message
    Write-Output "MSOnline PowerShell Module is installed :)"
}
#EndRegion If the module is not installed, install it
#EndRegion Check if MSOnline Module is installed

2 - Update Office 365 User's Domain

This step updates the domain of the office 365 users' primary email adddress/sign-in address

The Blueprint first gets the Execution Policy of the current PowerShell session.

Then, check if the Execution Policy is set to Unrestricted.

If it's not, it then sets the Execution Policy to Unrestricted for the current PowerShell session.

Next, the MsOnline module is imported to the current session.

Then the values below are set:

  1. UserName: This is the Global Admin Sign-in Userprincipalname corresponding to the 0365UserName set in the Inputs Tab.
  2. Password: This is the Global Admin Password corresponding to the 0365Password set in the Inputs Tab.
  3. HashConfig: This holds a hash table containing the configurations needed to to update the desired users' domain corresponding to the 0365Hashconfig set in the Inputs Tab.

Next, a connection to Office 365 is made.

Then it checks the hash table configuration to know what configuration to update the Office 365 users' domain.

This step has the following parameters

Name Script Reference Default Value
0365Password {0365password.value} P@ssWorD
0365UserName {0365username.value} globaladmin@contoso.onmicrosoft.com
0365HashConfig {0365hashconfig.value} @{'CSV'=$false;'OldDomain'="olddomain";'NewDomain'="newdomain"}

Login as user on node

  1. Connect via RDP
    mstsc /admin /v:Attune Node
  2. Login as user {Attune Node Credential}
  3. Then open a command prompt
This is a PowerShell Script make sure you run it with powershell.exe Click start menu, enter "powershell" in the search bar, then select the powersehll program
#Region for ExecutionPolicy
# Get Execution Policy of the current process
$Script:ProcessEP = Get-ExecutionPolicy -Scope Process

#Get the value of the Execution Policy and save it in the Variable
$Script:ValueProcessEP = ($Script:ProcessEP).value__

# Check if the Execution Policy of the process is set to Unrestricted
if ($Script:ValueProcessEP -eq 0) {

    # echo the message
    Write-Output "Execution Policy is already set to Unrestricted for the Process"
# Check if the Execution Policy of the process is already set
}else{

    # Set the ExecutionPolicy of the Process to Unrestricted
    Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted -Force -Confirm:$false

    # Checks if the Execution Policy has been set
    if ((Get-ExecutionPolicy -Scope Process).value__ -eq 0) {

        # echo the message
        Write-Output "Execution Policy is now set to Unrestricted for the Process"
    }
}
#EndRegion for ExecutionPolicy 



#Region ConnectTo0365
# Import Module for MSOnline
Import-Module -Name MSOnline

# Set the Global Admin Sign-in Userprincipalname
$Script:UserName = "{0365username.value}"

# Set the Global Admin Password
$Script:Password = "{0365password.value}"


#Region HashTable Configuration
$Script:HashConfig = {0365hashconfig.value}
#EndRegion HashTable Configuration


# Convert the password to secure string
$PasswordToSecureString = ConvertTo-SecureString $Password -AsPlainText -Force

# Saves UserName and Password for automation in the variable
$UserCredential = New-Object System.Management.Automation.PSCredential ($UserName, $PasswordToSecureString)

# Connects to the Office 365 (Azure Active Directory)
Connect-MsolService -Credential $UserCredential
#EndRegion ConnectTo0365


#Region Update-Domain Function
function Update-Domain {
    # Set the Cmdlet binding properties 
    [CmdletBinding(PositionalBinding = $false, SupportsShouldProcess = $false, ConfirmImpact = 'None')]
    param (
        # Set the Domain parameter
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $false,
            Position = 0,
            HelpMessage = "Enter a Verified Domain name on your Office 365 Tenant")]
        [Alias("Dn")]
        [String]
        $Domain,

        # Set the UserPrincipalName parameter
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [String]
        $UserPrincipalName,
        
        # Set the Force Parameter (switch)
        [Switch]$Force

    )  
    begin {
        # Set the divisor for the write-progress cmdlet
        [Int32]$Script:Divisor = 0

        # Set the incremental variable 
        [Int32]$Script:i = 0 
    }
    # process {
        
    # }
    end {

        # Creates a loop through all the input(UserPrincipalName) passed into the CMDLET
        foreach ($UserPrincipalNames in $input) {
            
            # If force switch is used
            if ($Force) { 

                #Region Get Prefix
                $Script:Symbol = $UserPrincipalNames.UserPrincipalName.IndexOf("@")

                $Script:Prefix = $UserPrincipalNames.UserPrincipalName.Substring(0, $Script:Symbol)

                $Script:NewUpn = ($Script:Prefix + "@" + $Domain)
                #EndRegion Get Prefix

                # Set write-progress incremental variable
                [Int32]$Script:i = [Int32]$Script:i + 1

                # Pause the scripts for 1 millisecond
                Start-Sleep -m 1

                try {
                    #Region Set New UserName
                    Set-MsolUserPrincipalName -UserPrincipalName $UserPrincipalNames.UserPrincipalName -NewUserPrincipalName $Script:NewUpn
                    #EndRegion Set New UserName

                    # Set the variable to the OldUpn of the User 
                    $Script:OldUpn = (($UserPrincipalNames).UserPrincipalName).tostring()

                    # Write out a message to screen regarding the change that has occurred
                    Write-Output `n "$Script:OldUpn has been changed to $Script:NewUpn"
                }
                catch {
                    # Echo out an error message
                    Write-Error ";( Error is above"
                    break #break the code
                }

            # IF Force Switch is not used 
            }else{
            
                # Write out the error message
                Write-Output "Force switch was not used"

            }
        }
    }
}
#EndRegion Update-Domain Function



#Region for setting update for Domain after checking if it is through a CSV File or not
function Set-Update{

    # To know what pattern of cmdlet to run if CSV is used 
    if ($Script:HashConfig['CSV'] -eq $true) {

        # Set the value of full path 
        $Script:FinalPath = $Script:HashConfig['CSVPath'] + "\" + $Script:HashConfig['CSVFileName']
    
        # Use CSV file to change the users in the domain
        Import-Csv -Path $Script:FinalPath | Update-Domain -Domain $Script:HashConfig['NewDomain'] -Force
    
    # To know what pattern of cmdlet to run if CSV is not used
    }elseif ($Script:HashConfig['CSV'] -eq $false) {

        # Save the domain with special test character to run a check
        $Script:ModOldDomain = "*@" + $Script:HashConfig['OldDomain'] + "*"

        # Check users' Domain for specified users
        Get-MsolUser | Where-Object { $_.UserPrincipalName -like $Script:ModOldDomain } | Update-Domain -Domain $Script:HashConfig['NewDomain'] -Force

    }
}
#EndRegion for setting update for Domain after checking if it is through a CSV File or not



#Region This function checks if the path is correct, if correct it runs the Set-Update function
function Get-PathandFile {

    # Save the value of the Csv path that is set in the configuration hashtable to the variable
    $Script:CSVPath = $Script:HashConfig['CSVPath']

    # Test Path & File segment (Path) (if it does not exist)
    if (!(Test-Path -Path $Script:HashConfig['CSVPath'])) {

        # Write out an error message if the path does not exist
        Write-Output "The File path $Script:CSVPath does not exist"

    # if it exists perform the below operation
    }else {
        
        # Write out a message if the file path exists
        Write-Output "The File path $Script:CSVPath exist..."

        # Save the value of the Csv filename that is set in the configuration hashtable to the variable
        $Script:CSVFileName = $Script:HashConfig['CSVFileName']

        # Get the particular file (if it does not exist)
        if (!(Get-ChildItem -Path $Script:HashConfig['CSVPath'] | Where-Object { $_.Name -like $Script:HashConfig['CSVFileName'] })) {
        
            # Writes out the message to the screen
            Write-Output "The File $Script:CSVFileName does not exist"
        
        # If the file exist 
        }else {

            # Writes out the message to the screen
            Write-Output $"The File $Script:CSVFileName exist"

            # Run the function Set-Update
            Set-Update

        }
    }
}
#EndRegion This function checks if the path is correct, if correct it runs the Set-Update function


#Region Check the configuration hashtable
# check the value of the CSV value if $True or $False entered
if ($Script:HashConfig['CSV'] -eq $true) {
    
    # Check the other parameters on the configuration file
    if ($Script:HashConfig['NewDomain'] -eq "" -or $null -eq $Script:HashConfig['NewDomain']`
    -or $Script:HashConfig['CSVPath'] -eq "" -or $Script:HashConfig['CSVFileName'] -eq ""`
    -or $null -eq $Script:HashConfig['CSVPath'] -or $null -eq $Script:HashConfig['CSVFileName']) {
        
        # Write out an error message
        Write-Output "Please check the configuration file"
    }else{
        
        # Run PathandFile Checker function 
        Get-PathandFile
    }
}elseif ($Script:HashConfig['CSV'] -eq $false) {

    if ($Script:HashConfig['OldDomain'] -eq "" -or $Script:HashConfig['NewDomain'] -eq ""`
    -or $null -eq $Script:HashConfig['OldDomain'] -or $null -eq $Script:HashConfig['NewDomain']) {
        
        # Write out an error message
        Write-Output "Please check the configuration file"
    }else {
        
        # Run the function Set-Update
        Set-Update
    }
}else {
    # Write out an error message
    Write-Output "Please check the data type of the value entered in the 'CSV' key in the HashTable Configuration 'ONLY datatype boolean Accepted!!'"
}   
#EndRegion Check the configuration hashtable