public/entraid/Get-EidUserMfaPolicy.ps1

function Get-EidUserMfaPolicy
{
    <#
    .SYNOPSIS
        Get all user multi-factor authentication conditional access policies from Microsoft Entra ID.
    .DESCRIPTION
        Return users that are exempt from multi-factor authentication (not fully covered).
    .PARAMETER UserPrincipalName
        UserPrincipalName such as "user@domain.com" (without quotes).
        If not specified, all users are returned.
    .PARAMETER OnlyEnabled
        If specified, only enabled users are returned.
    .PARAMETER ExemptGroups
        Exempt groups to flag users in the report (users that are members of any of the specified groups will be flagged in the report as exempt from MFA).
    .EXAMPLE
        Get-EidUserMfaPolicy;
    #>

    [cmdletbinding()]
    [OutputType([array])]
    param
    (
        # UserPrincipalName.
        [Parameter(Mandatory = $false, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ $_ | ForEach-Object { Test-EmailAddress -InputObject $_ } })]
        [string[]]$UserPrincipalName,

        # Only enabled users.
        [Parameter(Mandatory = $false, Position = 1, ValueFromPipelineByPropertyName = $true)]
        [switch]$OnlyEnabled,

        # Exempt groups to flag users in the report (users that are members of any of the specified groups will be flagged in the report as exempt from MFA).
        [Parameter(Mandatory = $false)]
        [string[]]$ExemptGroups
    )

    begin
    {
        # Write to log.
        $customProgress = Write-CustomProgress -Activity $MyInvocation.MyCommand.Name -CurrentOperation 'Retrieving user(s) that are exempted from MFA for conditional access policies in Microsoft Entra ID';

        # Get all conditional access policies that require MFA.
        $conditionalAccessPolicies = Get-EidConditionalAccessMfaPolicy;

        # Entra properties to get.
        $entraUserProperties = @('Id', 'UserPrincipalName', 'DisplayName', 'AccountEnabled', 'onPremisesSyncEnabled', 'UserType', 'PasswordPolicies', 'SignInActivity', 'SignInSessionsValidFromDateTime', 'mail', 'proxyAddresses');

        # If only enabled users should be returned.
        if ($true -eq $OnlyEnabled)
        {
            # Write to log.
            Write-CustomLog -Message 'Only enabled users will be returned' -Level 'Verbose';

            # Get Entra (enabled) users.
            $entraUsers = (Get-EntraUser -Filter 'AccountEnabled eq true' -Property $entraUserProperties -All);
        }
        # Else get all users.
        else
        {
            # Write to log.
            Write-CustomLog -Message 'Both enabled and disabled users will be returned' -Level 'Verbose';

            # Get all Entra users.
            $entraUsers = (Get-EntraUser -Property $entraUserProperties -All);
        }

        # Object for storing exempt group members.
        $exemptGroupMembers = @();

        # Object array to store users.
        $result = @();
    }
    process
    {
        # Foreach exempt group.
        foreach ($exemptGroup in $ExemptGroups)
        {
            # Get group ID by display name.
            $exemptGroupIds = (Get-MgGroup -Filter ("displayName eq '{0}'" -f $exemptGroup) -Select Id).Id;

            # Write to log.
            Write-CustomLog -Message ("Found {0} exempt group(s) with display name '{1}'" -f $exemptGroupIds.Count, $exemptGroup) -Level 'Verbose';

            # For each group ID.
            foreach ($exemptGroupId in $exemptGroupIds)
            {
                # Get group members.
                $exemptGroupMembers += Get-EidGroupTransitiveMember -Id $exemptGroupId;
            }
        }

        # Foreach Entra user.
        foreach ($entraUser in $entraUsers)
        {
            # Protected by conditional access policy.
            [bool]$isProtectedByMFA = $true;

            # If UserPrincipalName parameter is specified and the user is not in the list.
            if ($PSBoundParameters.ContainsKey('UserPrincipalName') -and
                $entraUser.UserPrincipalName -notin $UserPrincipalName)
            {
                # Write to log.
                Write-CustomLog -Message ("Skipping user '{0}', because it's not in the list of specified users" -f $entraUser.UserPrincipalName) -Level 'Verbose';

                # Set isProtectedByMFA to false.
                $isProtectedByMFA = $false;
            }

            # Object array to store policies that is targeting the user.
            $policiesTargetingUser = @();

            # If there is no conditional access policies that require multi-factor authentication.
            if ($conditionalAccessPolicies.Count -eq 0)
            {
                # Write to log.
                Write-CustomLog -Message ("No conditional access policies require multi-factor authentication, user '{0}' is NOT protected by MFA" -f $entraUser.UserPrincipalName) -Level 'Verbose';

                # Set isProtectedByMFA to false.
                $isProtectedByMFA = $false;
            }

            # Foreach conditional access policy that require multi-factor authentication.
            foreach ($conditionalAccessPolicy in $conditionalAccessPolicies)
            {
                # If the user is a guest user and the conditional access policy exclude one or more guest types.
                if ($entraUser.userType -eq 'Guest' -and $conditionalAccessPolicy.Users.ExcludeGuestsOrExternalUsersTypes.Count -gt 0)
                {
                    # Write to log.
                    Write-CustomLog -Message ("Guest user '{0}' is excluded from conditional access policy '{1}'" -f $entraUser.UserPrincipalName, $conditionalAccessPolicy.DisplayName) -Level 'Verbose';

                    # Set isProtectedByMFA to false.
                    $isProtectedByMFA = $false;
                }

                # If the user is targeted by the conditional access policy.
                if ($entraUser.UserPrincipalName -in $conditionalAccessPolicy.Users.TargetedUsers)
                {
                    # Write to log.
                    Write-CustomLog -Message ("User '{0}' is targeted by conditional access policy '{1}'" -f $entraUser.UserPrincipalName, $conditionalAccessPolicy.DisplayName) -Level 'Verbose';

                    # Add the conditional access policy to the object array.
                    $policiesTargetingUser += $conditionalAccessPolicy;
                }
                # Else if the user is a guest user and the conditional access policy don't exclude any guests.
                elseif ($entraUser.userType -eq 'Guest' -and $conditionalAccessPolicy.Users.ExcludeGuestsOrExternalUsersTypes.Count -eq 0)
                {
                    # Write to log.
                    Write-CustomLog -Message ("Guest user '{0}' is targeted by conditional access policy '{1}'" -f $entraUser.UserPrincipalName, $conditionalAccessPolicy.DisplayName) -Level 'Verbose';

                    # Add the conditional access policy to the object array.
                    $policiesTargetingUser += $conditionalAccessPolicy;
                }
                # Else user is not targeted by the conditional access policy.
                else
                {
                    # Write to log.
                    Write-CustomLog -Message ("User '{0}' is NOT targeted by conditional access policy '{1}'" -f $entraUser.UserPrincipalName, $conditionalAccessPolicy.DisplayName) -Level 'Verbose';

                    # Set isProtectedByMFA to false.
                    $isProtectedByMFA = $false;
                }
            }

            # Create custom object to store user information.
            $user = [PSCustomObject]@{
                Id                      = $entraUser.Id;
                UserPrincipalName       = $entraUser.UserPrincipalName;
                DisplayName             = $entraUser.DisplayName;
                AccountEnabled          = $entraUser.AccountEnabled;
                DirSyncEnabled          = $false;
                UserType                = '';
                PasswordPolicies        = $entraUser.PasswordPolicies;
                LastSuccessfulSignIn    = $entraUser.SignInActivity.lastSuccessfulSignInDateTime;
                ConditionalAccessPolicy = ($policiesTargetingUser).DisplayName;
                IsProtected             = $false;
                HasMailbox              = $false;
                MemberOfExemptGroup     = $false;
            };

            # If the user is not a guest.
            if ($entraUser.userType -ne 'Guest')
            {
                # If proxyAddresses property is set.
                if ($false -eq [string]::IsNullOrEmpty($entraUser.proxyAddresses))
                {
                    # Boolean to track SMTP address found.
                    [bool]$smtpAddressFound = $false;

                    # Foreach proxy address.
                    foreach ($proxyAddress in $entraUser.proxyAddresses)
                    {
                        # If SMTP address is found, break the loop.
                        if ($true -eq $smtpAddressFound)
                        {
                            # If SMTP address is found, break the loop.
                            break;
                        }

                        # If the proxy address starts with "SMTP:" (primary SMTP address).
                        if ($proxyAddress -like 'SMTP:*')
                        {
                            # Set SMTP address found to true.
                            $smtpAddressFound = $true;
                        }
                    }

                    # If the user has a SMTP address.
                    if ($true -eq $smtpAddressFound)
                    {
                        # Set Mailbox to true.
                        $user.HasMailbox = $true;
                    }
                }
            }

            # If the user is a guest.
            if ($entraUser.UserType -eq 'Guest')
            {
                # Set UserType to Guest.
                $user.UserType = 'Guest';
            }
            # Else if the user is a member.
            elseif ($entraUser.UserType -eq 'Member')
            {
                # If the user principal name dont have '#EXT#@'.
                if ($entraUser.UserPrincipalName -notlike '*#EXT#@*')
                {
                    # Set UserType to Member.
                    $user.UserType = 'Member';
                }
                # Else the user is external.
                else
                {
                    # Set UserType to External.
                    $user.UserType = 'External';
                }
            }

            # If the user is synchronized from on-premises Active Directory.
            if ($true -eq $entraUser.onPremisesSyncEnabled)
            {
                # Set DirSyncEnabled to true.
                $user.DirSyncEnabled = $true;
            }

            # If the user is protected by MFA.
            if ($policiesTargetingUser.Count -gt 0)
            {
                # Write to log.
                Write-CustomLog -Message ("User '{0}' is protected by MFA" -f $entraUser.UserPrincipalName) -Level 'Verbose';

                # Set IsProtected to true.
                $user.IsProtected = $true;
            }
            # Else user is not protected by MFA.
            else
            {
                # Write to log.
                Write-CustomLog -Message ("User '{0}' is NOT protected by MFA" -f $entraUser.UserPrincipalName) -Level 'Verbose';
            }

            # If the user is a member of any of the specified exempt groups.
            if ($entraUser.UserPrincipalName -in $exemptGroupMembers)
            {
                # Write to log.
                Write-CustomLog -Message ("User '{0}' is a member of an exempt group" -f $entraUser.UserPrincipalName) -Level 'Verbose';

                # Set MemberOfExemptGroup to true.
                $user.MemberOfExemptGroup = $true;
            }

            # Add the user to the object array.
            $result += $user;
        }
    }
    end
    {
        # Write to log.
        Write-CustomProgress @customProgress;

        # Return results.
        return $result;
    }
}