runbook-dynamicgroup-mfa.ps1

<#PSScriptInfo
.VERSION 0.4
.GUID 21da31c8-4f69-419d-8a3f-f16168b8f3ae
.AUTHOR Dominik Gilgen
.COMPANYNAME Dominik Gilgen (Personal)
.COPYRIGHT 2023 Dominik Gilgen. All rights reserved.
.LICENSEURI https://github.com/M365-Consultant/EntraID-MFA-DynamicGroup/blob/main/LICENSE
.PROJECTURI https://github.com/M365-Consultant/EntraID-MFA-DynamicGroup
.TAGS AzureAD EntraID MFA ConditionalAccess DynamicGroup Runbook
.RELEASENOTES
This script now supports email reporting, which requires the permission 'Mail.Send' and the Graph-Module Microsoft.Graph.Users.Actions.
Instead of variables it is now using parameters for the input.
#>


<#
 
.DESCRIPTION
 Azure Runbook - Dynamic Group - MFA State
  
 This script is designed for an Azure Runbook to assign users to two Azure AD groups based on their MFA capability (capable / non-capable).
 Before running the runbook, you need to set up an automation account with a managed identity.
 
 The managed identity requires the following Graph Permissions:
    - User.Read.All
    - Group.ReadWrite.All
    - UserAuthenticationMethod.Read.All
    - Mail.Send
 
 
 The script requires the following modules:
    - Microsoft.Graph.Authentication
    - Microsoft.Graph.Groups
    - Microsoft.Graph.Identity.SignIns
    - Microsoft.Graph.Users
    - Microsoft.Graph.Users.Actions
 
 There are a few parameters which must be set for a job run:
    - $groupid_capable -> The Object-ID of a EntraID (AzureAD) group where MFA capable uers's should be assigned
    - $groupid_noncapable -> The Object-ID of a EntraID (AzureAD) group where MFA NON-capable uers's should be assigned
    - $mailMode -> This controls the mail behavior. Enter the mode you want without using '
        'always' - sends a mail on every run
        'changes' - sends a mail only if there were any changes
        'disabled' - never send a mail
    - $mailSender -> The mail-alias from which the mail will be send (can be a user-account or a shared-mailbox)
    - $mailRecipients -> The recipient(s) of the mail (internal or external). If you want more than one recipient, you can separate them with the character ; in between.
 
#>
 

Param
(
  [Parameter (Mandatory= $true)]
  [String] $groupid_capable = "Enter Group-ID for MFA capable",
  [Parameter (Mandatory= $true)]
  [String] $groupid_noncapable = "Enter Group-ID for MFA non-capable",
  [Parameter (Mandatory= $false)]
  [String] $mailMode,
  [Parameter (Mandatory= $false)]
  [String] $mailSender,
  [Parameter (Mandatory= $false)]
  [String] $mailRecipients
)

#Connect to Microsoft Graph using a Managed Identity
Connect-MgGraph -Identity

#Preparing necessary variables
$users = get-mguser -All
$members_capable = Get-MgGroupMember -GroupId $groupid_capable -All
$members_noncapable = Get-MgGroupMember -GroupId $groupid_noncapable -All
$groupname_capable = Get-MgGroup -GroupId $groupid_capable
$groupname_noncapable = Get-MgGroup -GroupId $groupid_noncapable

#Preparing mail content
$mailContentHeader = "<p>The Azure runbook for dynamic group based on the user's MFA state has been executed.</p><p>Those are the changes from this run:</p>"
$mailContentChanges = "<ul>"

#Running the MFA state check for every user and assign them to correct groups
foreach ($user in $users) {
    $mfa = Get-MgUserAuthenticationMethod -UserId $user.UserPrincipalName -All | Where-Object {$_.AdditionalProperties."@odata.type" -ne "#microsoft.graph.passwordAuthenticationMethod"}
    if ($mfa.Count -gt 0) {
            if ($members_capable.Id -notcontains $user.Id){
                New-MgGroupMember -GroupId $groupid_capable -DirectoryObjectId $user.Id
                $output = $user.UserPrincipalName + " added to '" + $groupname_capable.DisplayName + "'. User-ID: " + $user.Id
                Write-Output $output
                $outputMail = "<li>" + $user.UserPrincipalName + " <font color='green'>added</font> to '" + $groupname_capable.DisplayName + "'. <font color='grey'>(User-ID: " + $user.Id + ")</font></li>"
                $mailContentChanges += $outputMail
            }
            if ($members_noncapable.Id -contains $user.Id){
                Remove-MgGroupMemberByRef -GroupId $groupid_noncapable -DirectoryObjectId $user.Id
                $output = $user.UserPrincipalName + " removed from '" + $groupname_noncapable.DisplayName + "'. User-ID: " + $user.Id
                $outputMail = "<li>" + $user.UserPrincipalName + " <font color='orange'>removed</font> from '" + $groupname_noncapable.DisplayName + "'. <font color='grey'>(User-ID: " + $user.Id + ")</font></li>"
                Write-Output $output
                $mailContentChanges += $outputMail
            }
    }
    else{
            if($members_noncapable.Id -notcontains $user.Id){ 
                New-MgGroupMember -GroupId $groupid_noncapable -DirectoryObjectId $user.Id
                $output = $user.UserPrincipalName + " added to '" + $groupname_noncapable.DisplayName + "'. User-ID: " + $user.Id
                $outputMail = "<li>" + $user.UserPrincipalName + " <font color='green'>added</font> to '" + $groupname_noncapable.DisplayName + "'. <font color='grey'>(User-ID: " + $user.Id + ")</font></li>"
                Write-Output $output
                $mailContentChanges += $outputMail
            }
            if($members_capable.Id -contains $user.Id){
                Remove-MgGroupMemberByRef -GroupId $groupid_capable -DirectoryObjectId $user.Id
                $output = $user.UserPrincipalName + " removed from '" + $groupname_capable.DisplayName + "' because this account has become non-capable! User-ID: " + $user.Id
                $outputMail = "<li><font color='red'><b>WARNING: </b></font>" + $user.UserPrincipalName + " <font color='orange'>removed</font> from '" + $groupname_capable.DisplayName + "' because this account has become non-capable! <font color='grey'>(User-ID: " + $user.Id + ")</font></li>"
                Write-Warning $output
                $mailContentChanges += $outputMail
            }
    }
}

if ($mailContentChanges -eq "<ul>"){
    $mailContentChanges = "<b>No changes made.</b>"
    Write-Output "No changes made."
}
else { $mailContentChanges += "</ul>" }

# Sendmail
function runbookSendMail {
    $mailRecipientsArray = $mailRecipients.Split(";")
    $mailSubject = "Azure Runbook Report: Dynamic Group MFA State"
    $mailContentFooter = "<br><br><p style='color: grey'>You can find additional details in the job history of this runbook.<br>Job finished at (UTC) " + (Get-Date).ToUniversalTime() + "<br>Job ID:"+ $PSPrivateMetadata.JobId.Guid + "</p>"
    $mailContent = $mailContentHeader + $mailContentChanges + $mailContentFooter

    $params = @{
            Message = @{
                Subject = $mailSubject
                Body = @{
                    ContentType = "html"
                    Content = $mailContent
                }
                ToRecipients = @(
                    foreach ($recipient in $mailRecipientsArray) {
                        @{
                            EmailAddress = @{
                                Address = $recipient
                            }
                        }
                    }
                )
            }
            SaveToSentItems = "false"
        }
        
    Send-MgUserMail -UserId $mailSender -BodyParameter $params
    Write-Output "Mail has been sent."
}

if ($mailMode -eq "always" -and $mailSender -and $mailRecipients) { runbookSendMail }
elseif ($mailMode -eq "changes" -and $mailSender -and $mailRecipients -and ($mailContentChanges -ne "<b>No changes made.</b>")) { runbookSendMail }
elseif ($mailMode -eq "changes" -and $mailSender -and $mailRecipients) { Write-Output "No mail sent, because there are no changes and mailmode is set to 'changes'." }
elseif ($mailMode -eq "disabled"){ Write-Output "Mail function is disabled." }
else { Write-Warning "Mail settings are missing or incorrect" }


#Disconnect from Microsoft Graph within Azure Automation
Disconnect-MgGraph