Update-UserMFA.ps1


<#PSScriptInfo
 
.VERSION 0.7
 
.GUID 499fcb19-e02b-4f3e-80b8-1013c2fce966
 
.AUTHOR jmcarthur@roundrocktexas.gov
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
Modified to allow for enable/disable only - removed option to set enforced
Removed logic for 3rd button in _Invoke-SelectionBox due to reducing available options
 
.PRIVATEDATA
 
#>
 

#Requires -Module AzureAD
#Requires -Module MSOnline






<#
 
.DESCRIPTION
 Script utilizing WPF forms to update MFA status of specified user.
  
.PARAMETER test
 Switch that enables generic testing of error handling while updating
 MFA settings for a given user. This flag will simulate an error by
 throwing a simple exception. The $testing flag is then set to false,
 and the Update-MFA function is called recursively with the user as a
 parameter to allow retrying the operation.
#>
 
Param
(
    [Parameter(HelpMessage="Flag to test failures while updating MFA")]
    [switch]$test
)
$ErrorActionPreference = "SilentlyContinue"
$WarningPreference = "SilentlyContinue"
Add-Type -AssemblyName PresentationFramework
Add-Type �assemblyName PresentationCore
Add-Type �assemblyName WindowsBase

Function _Invoke-InputBox {

    [cmdletbinding(DefaultParameterSetName="plain")]
    [OutputType([system.string],ParameterSetName='plain')]
    [OutputType([system.security.securestring],ParameterSetName='secure')]

    Param(
        [Parameter(ParameterSetName="secure")]
        [Parameter(HelpMessage = "Enter the title for the input box. No more than 25 characters.",
        ParameterSetName="plain")]        

        [ValidateNotNullorEmpty()]
        [ValidateScript({$_.length -le 25})]
        [string]$Title = "Update MFA Status",

        [Parameter(ParameterSetName="secure")]        
        [Parameter(HelpMessage = "Enter a prompt. No more than 50 characters.",ParameterSetName="plain")]
        [ValidateNotNullorEmpty()]
        [ValidateScript({$_.length -le 50})]
        [string]$Prompt = "Target user's email address:",
        
        [Parameter(HelpMessage = "Use to mask the entry and return a secure string.",
        ParameterSetName="secure")]
        [switch]$AsSecureString
    )

    if ($PSEdition -eq 'Core') {
        Write-Warning "Sorry. This command will not run on PowerShell Core."
        #bail out
        Return
    }

    Write-Verbose "In _Invoke-InputBox"

    #remove the variable because it might get cached in the ISE or VS Code
    Remove-Variable -Name myInput -Scope script -ErrorAction SilentlyContinue

    $form = New-Object System.Windows.Window
    $stack = New-object System.Windows.Controls.StackPanel

    #define what it looks like
    $form.Title = $title
    $form.Height = 150
    $form.Width = 350

    $label = New-Object System.Windows.Controls.Label
    $label.Content = " $Prompt"
    $label.HorizontalAlignment = "left"
    $stack.AddChild($label)

    if ($AsSecureString) {
        $inputbox = New-Object System.Windows.Controls.PasswordBox
    }
    else {
        $inputbox = New-Object System.Windows.Controls.TextBox
    }

    $inputbox.Width = 300
    $inputbox.HorizontalAlignment = "center"

    $stack.AddChild($inputbox)

    $space = new-object System.Windows.Controls.Label
    $space.Height = 10
    $stack.AddChild($space)

    $btn = New-Object System.Windows.Controls.Button
    $btn.Content = "_OK"

    $btn.Width = 65
    $btn.HorizontalAlignment = "center"
    $btn.VerticalAlignment = "bottom"

    #add an event handler
    $btn.Add_click( {
            if ($AsSecureString) {
                $script:myInput = $inputbox.SecurePassword
            }
            else {
                $script:myInput = $inputbox.text
            }
            $form.Close()
        })
    $btn.IsDefault = $true

    $stack.AddChild($btn)
    $space2 = new-object System.Windows.Controls.Label
    $space2.Height = 10
    $stack.AddChild($space2)

    $btn2 = New-Object System.Windows.Controls.Button
    $btn2.Content = "_Cancel"

    $btn2.Width = 65
    $btn2.HorizontalAlignment = "center"
    $btn2.VerticalAlignment = "bottom"

    #add an event handler
    $btn2.Add_click( {
            $form.Close()
        })

    $stack.AddChild($btn2)

    #add the stack to the form
    $form.AddChild($stack)

    #show the form
    $inputbox.Focus() | Out-Null
    $form.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen
    
    $form.ShowDialog() | out-null

    Write-Verbose ("myInput: {0}" -f $script:myInput)
    #write the result from the input box back to the pipeline
    $script:myInput
}

Function _Invoke-SelectionBox {


    Param(
    [Parameter(Mandatory=$true)]$user
    )

    if ($PSEdition -eq 'Core') {
        Write-Warning "Sorry. This command will not run on PowerShell Core."
        #bail out
        Return
    }

    #remove the variable because it might get cached in the ISE or VS Code
    Remove-Variable -Name myInput -Scope script -ErrorAction SilentlyContinue

    #check that $user has necessary properties to continue
    if (-not ($user.DisplayName))
    {
        Write-Warning "The provided user does not have the required properties."
        Return
    }
    elseif  (-not $user.StrongAuthenticationRequirements)
    {
        # this is a new user who's MFA has never been configured
        $name = $user.DisplayName.ToString()
        $str = $($name + "'s MFA has not been enabled.")
        $notEnabled = $true
    }
    else
    {
        $name = $user.DisplayName.ToString()
        $state = $user.StrongAuthenticationRequirements.State.ToString()
        $str = $($name + "'s MFA status is " + $state)
    }

    $form = New-Object System.Windows.Window
    $stack = New-object System.Windows.Controls.StackPanel

    #define what it looks like
    $form.Title = "Update MFA Status"
    $form.Height = 150
    $form.Width = 350

    $label = New-Object System.Windows.Controls.Label
    $label.Content = " $str"
    $label.HorizontalAlignment = "left"
    $stack.AddChild($label)
    
    # build options and outputs based on MFA state
    $enabledEnforced = @("Enabled","Enforced")
    if ($enabledEnforced -contains $state) 
    {
        $opt1 = "Disabled"
        $opt2 = "Cancel"
    } 
    elseif ( ($state -eq "Disabled") -or ($notEnabled) )
    {
        $opt1 = "Enabled"
        $opt2 = "Cancel"
    }

    #region BUTTON1
    $space = new-object System.Windows.Controls.Label
    $space.Height = 10
    $stack.AddChild($space)

    $btn = New-Object System.Windows.Controls.Button
    $btn.Content = $opt1

    $btn.Width = 65
    $btn.HorizontalAlignment = "center"
    $btn.VerticalAlignment = "bottom"

    #add an event handler
    $btn.Add_click( {
            $script:myInput = $opt1
            $form.Close()
        })

    $stack.AddChild($btn)
    #endregion

    #region BUTTON2
    $space2 = new-object System.Windows.Controls.Label
    $space2.Height = 10
    $stack.AddChild($space2)
    
    $btn2 = New-Object System.Windows.Controls.Button
    $btn2.Content = $opt2

    $btn2.Width = 65
    $btn2.HorizontalAlignment = "center"
    $btn2.VerticalAlignment = "bottom"

    #add an event handler
    $btn2.Add_click( {
            $script:myInput = $opt2
            $form.Close()
        })

    $stack.AddChild($btn2)
    #endregion
    
    #add the stack to the form
    $form.AddChild($stack)

    #show the form
    #$inputbox.Focus() | Out-Null
    $form.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen

    $form.ShowDialog() | out-null

    #write the result from the input box back to the pipeline
    $script:myInput
}


Function _Check-MsolSession
{
    Get-MsolDomain
    return $(if ($?) { $true } else { $false })
}

Function _Update-MFA
{
    Param
    (
        [Parameter(Mandatory=$false)]$user
    )

    Write-Verbose ("Testing? {0}" -f [bool]$script:testing)
    Write-Verbose ("User passed? {0}" -f [bool]$PSBoundParameters.ContainsKey('user'))

    $user = if ($PSBoundParameters.ContainsKey('user')) {$user} else {Write-Verbose "Calling _Invoke-InputBox" ;_Invoke-InputBox}
    #$user = Invoke-InputBox

    if ($user -eq $null)
    {
        Write-Verbose "User cancelled"
        return
    }

    if (-not (_Check-MsolSession))
    {
        Connect-MsolService
    }
    
    try
    {
        $ms_user = Get-MsolUser -UserPrincipalName $user -EA Stop
    }
    catch
    {
        $retry = [System.Windows.MessageBox]::Show($_.Exception.Message,'Error fetching user','Ok','Error')
        Write-Verbose $retry
        # recursively call this function to start over without prompting
        if ($retry -eq 'Ok') {_Update-MFA}
    }
    $name = $ms_user.DisplayName

    $update = _Invoke-SelectionBox $ms_user
    Write-Verbose "Update: $update"

    if ($update)
    {
        Write-Verbose ("Setting MFA to {0} for {1}" -f $update,$name)
        if ($update -ne "Disabled")
        {
            $auth = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
            $auth.RelyingParty = "*"
            #$auth.RememberDevicesNotIssuedBefore = (Get-Date)
            $auth.State = $update
        }
        else # 'Disabled
        {
            $auth = @()
        }
        
        try
        {
            if ($testing)
            {
                throw "Test catching update MFA errors"
                $script:testing = $false
            }
            else
            {
                Set-MsolUser -UserPrincipalName $user -StrongAuthenticationRequirements $auth
            }
        }
        catch
        {
            $msgBoxAction = [System.Windows.MessageBox]::Show($_.Exception.Message,'Error updating MFA - Retry?','YesNo','Error')
            if ($msgBoxAction -eq 'Yes')
            {
                _Update-MFA -user $user
            }
        }
    }
}

Function Run
{
    [bool]$continue = $true
    while ($continue)
    {
        _Update-MFA
        $continueQuery = [System.Windows.MessageBox]::Show('Process another user?','Operation Successful','YesNo','Question')
        if ($continueQuery -eq 'No')
        {
            $continue = $false
        }
    }
}
#region execute
$script:testing = if ($PSBoundParameters.ContainsKey('test')) {$true} else {$false}
Run
#endregion