Phased Deployment of Azure Conditional Access Multi-factor Authentication (MFA) using PowerShell

I recently had a client that needed to deploy Azure MFA, controlled by a conditional access policy, to around 30,000 users. Now, obviously, from a technical point of view this is quite straightforward, however because of the large number of users, and the fact that user communications and support needed to be managed, I wanted this to be done in a controlled way. I didn’t want to overload the helpdesk, or cause business problems for the users, due to unexpected issues.

I therefore wrote 3 scripts that would allow us to easily manage the deployment and ensure that things progressed in a tightly controlled way.

The process that was to be followed was this.

  1. Create the Conditional Access policy and assign to an MFA Deployment group.
  2. Identify all the affected users and gather their User Principal Names into multiple batches. Create files with, perhaps 500 users in each file. This ensures that users are enabled for MFA in small, manageable batches.
  3. Communications are sent to the user to tell them to register their additional authentication methods, together with the URL to the correct web page (aka.ms/MFASetup *). The users will automatically be prompted to register additional authentication methods when they first login, once they are enabled for MFA, however many users tend to ignore this if they can, so its better to try and get them to register before they need to. This saves issues building up with the helpdesk if they encounter problems after waiting until the last minute to register additional authentication methods.
  4. Run the CheckVerificationStatus.ps1 script. This script reads the UPNs from the Input File and lists each UPN together with a list of their registered Strong Authentication Methods. If nothing is listed for a user then they have not registered additional authentication methods for MFA. Communications could then be sent to those users to request that they register.
  5. Run the EnableMFA.ps1 script that takes the UPN names from a file, ensures that sufficient EMS licenses exist, provisions the user with an EMS license, then adds the user to the MFA deployment group.
  6. If irresolvable problems occur, run the DisableMFA.ps1 script which will back-out MFA for all users in the batch, or create a list of users to be backed out and run DisableMFA using that file.

* The aka.ms/MFASetup URL will send each user to the Additional Security Verification page for their own ID. If they haven’t registered additional security verification previously they will be prompted to configure it. If they have configured additional security verification previously they will be shown the page below.

The input files that provide the UPN list are simply TXT files that list each UPN on it’s own line. Try to ensure that there are no blank lines at the end of the file, otherwise you will see the following error in the PowerShell console for each blank line. It doesn’t affect how the scripts work though. The output file is simply a log of what the script has done.

Get-MsolUser : Cannot bind argument to parameter 'UserPrincipalName' because it is an empty string.
At line:10 char:52
+     $CurrentUser = Get-MsolUser -UserPrincipalName $UPN
+                                                    ~~~~
 
    + CategoryInfo          : InvalidData: (:) [Get-MsolUser], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.Online.Administration.Automation.GetUser

CheckVerificationStatus.ps1

Import-Module -Name Msonline
Connect-MsolService

$InputFile = "C:\Data\UpnList.txt" # List of UPNs
$OutputFile = "C:\Data\Output.txt"
 # Output file

# Read in list of users who will be enabled for MFA
$UPNs = Get-Content -Path $InputFile

ForEach ($UPN in $UPNs)
    {
    $CurrentUser = Get-MsolUser -UserPrincipalName $UPN
      
    If ($CurrentUser.StrongAuthenticationmethods -eq "")
        {
        $NewLine = $UPN + "`t" + "nul" >> $OutputFile
        }
    Else
        {
        $NewLine = $UPN + "`t" + $CurrentUser.StrongAuthenticationmethods.MethodType >> $OutputFile
        }
    }

After prompting for Azure credentials this script takes input from the $InputFile and outputs tab separated data to the $OutputFile. This file can be easily opened in Excel for data manipulation. An example of the output is shown below, showing the Strong Authentication Methods registered for each user. Users with no methods listed have not registered any additional security methods

graham.higginson@xxxxxx.com	OneWaySMS TwoWayVoiceMobile
joe.bloggs@xxxxxx.com	PhoneAppOTP PhoneAppNotification OneWaySMS TwoWayVoiceMobile
jim.booth@xxxxxx.com	
scott.devlin@xxxxxx.com	
andy.knowles@xxxxxx.com	
simon.jay@xxxxxx.com	
john.bonham@xxxxxx.com	

EnableMFA.ps1

Import-Module -Name MsolService
Connect-MsolService

$InputFile = "C:\Data\UpnList.txt"
 # List of UPNs
$OutputFile = "C:\Data\Output.txt"
 # Output File
$License = "xxxxx:EMS"
 # Name of license
$GroupID = "4adcacb1-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
 # GUID of the MFA Deployment group


# Read in list of users who will be enabled for MFA
$UPNs = Get-Content -Path $InputFile


# Identify how many available EMS licenses exist
$Licenses = Get-AzureADSubscribedSku | where {$_.SkuPartNumber -eq 'EMS'}
$PrepaidLicenses = $Licenses.PrePaidUnits.Enabled
$ConsumedLicenses = $Licenses.ConsumedUnits
$AvailableLicenses = $PrepaidLicenses - $ConsumedLicenses

If ($UPNs.count -lt $AvailableLicenses)
    {
     Write-Warning "Sufficient licenses are available"
     Write-Warning "Script continuing..."
    }
Else
    {
     Write-Warning "Insufficient licenses are available"
     Write-Warning "Script stopping!!"
     Break
    }

# Add EMS license to user and add to MFA group
ForEach ($UPN in $UPNs)
    {
    $CurrentUser = Get-MsolUser -UserPrincipalName $UPN
    If ($CurrentUser.Licenses.AccountSkuId -Contains $License)
        {
        Write-Host $UPN "already has an EMS license assigned" >>$OutputFile
        }
    Else
        {
        Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses $License
        If ($Error[0])
            {
            Write-Host "Unable to add license to" $UPN >>$OutputFile
            $Error.Clear
            }
        Else
            {
            Write-Host "Successfully added license to" $UPN >>$OutputFile
            }
        }

    Add-MsolGroupMember -GroupObjectId $GroupID -GroupMemberType User -GroupMemberObjectId $CurrentUser.ObjectId
    If ($Error[0])
        {
        Write-Host "Unable to add" $UPN "to the MFA group" >>$OutputFile
        $Error.Clear
        }
        Else
            {
            Write-Host "Successfully added" $UPN "to MFA group" >>$OutputFile
            }
    }

After prompting for Azure credentials this script reads in the list of user UPNs and checks that enough licenses are available for the number of users to be enabled for MFA. If there aren’t enough licenses the script will exit. Otherwise the script will assign and EMS license to each user, unless they already have a license assigned. The script then adds each user to the specified MFA Deployment group.

DisableMFA.ps1

Import-Module -Name MsolService
Connect-MsolService


$InputFile = "C:\Data\UpnList.txt"
 # List of UPNs
$OutputFile = "C:\Data\Output.txt"
 # Output File
 
$License = "xxxxx:EMS"
 # Name of license
$GroupID = "4adcacb1-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
 # GUID of the MFA Deployment group

# Read in list of users who will be enabled for MFA
$UPNs = Get-Content -Path $InputFile


ForEach ($UPN in $UPNs)
    {
    $CurrentUser = Get-MsolUser -UserPrincipalName $UPN
    Remove-MsolGroupMember -GroupObjectId $GroupID -GroupMemberType User -GroupMemberObjectId $CurrentUser.ObjectId
    If ($Error[0])
        {
        Write-Host "Unable to remove " $UPN " from MFA group" >>$OutputFile
        $Error.Clear
        }
    Else
        {
        Write-Host "Successfully removed " $UPN " from MFA group" >>$OutputFile
        }
    
    Set-MsolUserLicense -UserPrincipalName $UPN -RemoveLicenses $License
    
    If ($Error[0])
        {
        Write-Host "Unable to remove license from " $UPN >>$OutputFile
        $Error.Clear
        }
    Else
        {
        Write-Host "Successfully removed license from " $UPN >>$OutputFile
        }
    $NewLine = $UPN + "`t" + "Removed from Group" >> $OutputFile
    }

After prompting for Azure credentials this script reads in the list of UPNs and then attempts to remove the user from the MFA Deployment group. It then attempts to remove the EMS license from the user.

*** You may want to comment out the license removal if many of your users already have EMS licenses assigned prior to rolling out MFA. ***

This solution was designed for a very particular use case, and may not be a perfect fit for every requirement, but I hope it provides food for thought. Feel free to take the bits that work for you, to produce your own solution.