MS365, PowerShell and… MFA…

PUBLISHED ON MAY 22, 2020 — 1000 words — MFA , MS365 , POWERSHELL , SECURITY


Before we start: I hate PowerShell’s lack of consistency for error handling so I did not bother to handle all error since this is only a code snippet.

I wanted to check how one can set and force MFA authentication right from the start. While there is no real practical use for this, it was a good exercice.

Nota: All names where generated using Fake Name Generator so don’t bother sending me GDPR notices. As lawyers say: This is a work of fiction. Names, characters, business, events and incidents are the products of the author’s imagination. Any resemblance to actual persons, living or dead, or actual events is purely coincidental.

For my example we need to create a csv file (we could use a SQL database or anything else, but csv it is…).

DisplayName, MailNickName, UserPrincipalName, Password, Group, Done, Mobile
"Denise Berthelette", "dbe", "denise.berthelette@opium.io", "ShittyPassword001;", "Marketing", 0,"+33 712345678"
"Robert Chesnay", "rch", "robert.chesnay@opium.io", "ShittyPassword002;", "Sales", 0,"+33 712345679"
"Jacqueline Couturier", "jco", "jacqueline.couturier@opium.io", "ShittyPassword003;", "Accounting", 0, "+33 712345670"

We first start the script with the usual parameters definition and modules check (once again, this is a code snippet and not intended for production…)

 param (
    [switch]$delete = $False,
    [switch]$mfa = $False
 )


## --- Module Checks

if ((Get-Module -ListAvailable -Name MSOnline) -And (Get-Module -ListAvailable -Name AzureADPreview)) {
    Import-Module MSOnline
    Import-Module AzureADPreview
} 
else {
    Write-Host "Please install the following modules: MSOnline & AzureADPreview"
    exit 1
}

clear-host
Write-Host @"
--------------------------
AZure AD User Utils by jme
--------------------------
use -delete to remove users
use -mfa to activate SMS OTP during account creation

"@

write-host "-- Verifying Azure AD authentication" -ForegroundColor Cyan

try {  
    $session = Get-AzureADCurrentSessionInfo -ErrorVariable error
    
} 
catch {
    write-host "-- No Connection to Azure AD : launching Connect-AzureAD" -ForegroundColor Red
    Connect-AzureAD -TenantDomain "opium.onmicrosoft.com" -ErrorVariable myError# TODO: use AzADServicePrincipal -ea 0
    Connect-MsolService -ea 0
}
We import the csv file into $user
try {
    write-host "-- Importing users.csv where the 'done' field is not 1" -ForegroundColor Cyan
    $users = import-csv "users.csv" | where-object {$_.Done -eq "0"}
    write-host "-- Initialisation of the AzureAD password model" -ForegroundColor Cyan
    $PasswordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile
}

catch { Write-Host "Fucked-up the initialisation procedure :(" -ForegroundColor Red
        Exit 1
        }

if  (!$delete) {
    Write-host "-- Creating users" -ForegroundColor Yellow
} else { 
    Write-host "-- DELETING USERS" -ForegroundColor Yellow
}
And then we parse each user…
ForEach ($user in $users) {
    $_DisplayName = $($user.DisplayName)
    $_MailNickName = $($user.MailNickName)
    $_UserPrincipalName = $($user.UserPrincipalName)
    $PasswordProfile.Password = $($user.Password)
    $_Group = $($user.Group)
    $_Done = $($user.Done)
    $_Mobile = $($user.Mobile)

    if  (!$delete) {
        try {

                # ---------------- CREATING USER
                
                $groupID = Get-AzureADGroup -SearchString $_Group

                try {$test = [bool] (Get-AzureADUser -ObjectId $_UserPrincipalName -ea 0)} catch {$test = $false} #pwshell = caca...

                if (-Not $test) {
                    write-host " - Creating user "  $_DisplayName -ForegroundColor Green
                    $createdUSer = New-AzureADUser -AccountEnabled $True -DisplayName $_DisplayName -PasswordProfile $PasswordProfile -MailNickName $_MailNickName -UserPrincipalName $_UserPrincipalName -Mobile $_Mobile
                    Add-AzureADGroupMember -ObjectID $groupID.ObjectID -RefObjectId $createdUser.ObjectID
                } else {
                    write-host " - exists, skipping..." -ForegroundColor Red
                }#if
The important thing to note if you are using SMS OTP (eventhough that’s deprecated) is that while the populated mobile phone number (-Mobile $_Mobile) is different from the SMS FMA number (-MobilePhone $_Mobile) That’s where the MSOnline module enters into play. While it is deprecated, AzureAD (even in preview) does not have the facility to manage MFA parameters. Once again, because it is a code snippet, I did put all methods. However, you can find more information on Microsoft’s website.
                if ($mfa) {
            # ---------------- ENABLING MFA
                    $test = $False
                    while (-Not ($test = [bool] (Get-MsolUser -UserPrincipalName $_UserPrincipalName -ea 0))) { 
                        Write-Host "." -NoNewline 
                        Start-Sleep -s 1
                    }#while
The above loop is needed in order to wait for the user creation synchronisation.
                    write-host "Activating MFA for " $_UserPrincipalName
                    ## -- 
                    $APP = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationMethod
                    $APP.IsDefault = $false
                    $APP.MethodType = "PhoneAppNotification"

                    $OTP = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationMethod
                    $OTP.IsDefault = $false
                    $OTP.MethodType = "PhoneAppOTP"                    

                    $SMS = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationMethod
                    $SMS.IsDefault = $true
                    $SMS.MethodType = "OneWaySMS"
                    
                    $Phone = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationMethod
                    $Phone.IsDefault = $false
                    $Phone.MethodType = "TwoWayVoiceMobile"
                    $PrePopulate = @($SMS, $Phone) #We do not set OTP or APP as they are not setup
                    Set-MsolUser -UserPrincipalName $_UserPrincipalName -MobilePhone $_Mobile -StrongAuthenticationMethods $PrePopulate -ea 0
            }#mfa
        }#try
        catch { 
            Write-Host "  - Error Creating user" $_DisplayName -ForegroundColor Red
        }#catch
        if (!$error) {
            Write-Host "Creation OK" -ForegroundColor DarkGreen
            }
    } #if NOT delete
    
    if ($delete) {
        try {$test = [bool] (Get-AzureADUser -ObjectId $_UserPrincipalName -ea 0)} catch {$test = $false}
        if ($test) {
        try {

            write-host " - Deleting user "  $_DisplayName -ForegroundColor Yellow
            Remove-AzureADUser -ObjectId $_UserPrincipalName -ea 0
        } catch { 
            Write-Host "  - Error DELETING user" $_DisplayName -ForegroundColor Red
        }
    } else { write-host " - User $_DisplayName not found : SKIPPING" -ForegroundColor Red}

    } #if delete

} #Foreach 

Exit 0
Upon exection (./NewUser.ps1 -mfa) we get the following output (as stated, each dot is a 1 second wait event loop)
--------------------------
AZure AD User Utils by jme
--------------------------
use -delete to remove users
use -mfa to activate SMS OTP during account creation
 
-- Verifying Azure AD authentication
-- Importing users.csv where the 'done' field is not 1
-- Initialisation of the AzureAD password model
-- Creating users
 - Creating user  Denise Berthelette
.......Activating MFA for  denise.berthelette@opium.io
Creation OK
 - Creating user  Robert Chesnay
............Activating MFA for  robert.chesna@opium.io
Creation OK
 - Creating user  Jacqueline Couturier
...............Activating MFA for  jacqueline.couturier@opium.io
Creation OK
PS C:\Users\jme\Documents\WindowsPowerShell>

So what?

The issue with setting the MFA on behalf of the user is that it more or less kills Microsoft’s nice onboarding journey. My recommendation is to set a strong random password upon user creation and force the user to change it on first connection. They will be forced to do the full MFA onboarding (I sill recommend deactivating SMS and callback (unless Microsft releases their in-house voicecallback with PIN code method for the general public)).
Anyways, the futur is now Microsoft Graph which is good news for me since I fucking hate PowerShell… :).