public/entraid/Send-EidUserMfaReport.ps1

function Send-EidUserMfaReport
{
    <#
    .SYNOPSIS
        Send Entra user MFA status report.
    .DESCRIPTION
        Collects Entra user MFA status and sends a report to the specified e-mail address.
    .PARAMETER EmailAddress
        E-mail address to send the report.
    .EXAMPLE
        Send-EidUserMfaReport -EmailAddress 'abc@contoso.com';
    #>

    [cmdletbinding()]
    [OutputType([void])]
    [ValidateScript({ $_ -match '' })]
    param
    (
        # E-mail address to send the report.
        [Parameter(Mandatory = $false)]
        [ValidateScript({ Test-EmailAddress -InputObject $_ })]
        [string]$EmailAddress = ((Get-EntraContext).Account)
    )

    begin
    {
        # Write to log.
        $customProgress = Write-CustomProgress -Activity $MyInvocation.MyCommand.Name -CurrentOperation 'Sending Entra user MFA status report';

        # Get Entra user MFA status.
        $users = Get-EidUserMfaPolicy;

        # Import header and footer.
        $header = Get-Content -Path (Join-Path -Path $Script:scriptPath -ChildPath 'private\assets\send-eidusermfareport\header.html');
        $footer = Get-Content -Path (Join-Path -Path $Script:scriptPath -ChildPath 'private\assets\send-eidusermfareport\footer.html');

        # HTML.
        [string]$html = '';

        # Update header with date.
        $header = $header -replace '##DATE##', (Get-Date -Format 'yyyy-MM-dd HH:mm:ss');

        # If security defaults is enabled.
        if ($true -eq (Get-EidSecurityDefaultsEnforcement))

        {
            # Update header with security defaults info.
            $header = $header -replace '##SecurityDefaultsInfo##', 'Microsoft 365 security default is enabled, so all users are protected by MFA. Disregard below findings.';
        }
        # Else security defaults is disabled.
        else
        {
            # Update header with security defaults info.
            $header = $header -replace '##SecurityDefaultsInfo##', 'Microsoft 365 security default is disabled, so some users might not be fully protected by MFA.';
        }

        # Add header.
        $html = $header | Out-String;

        # Temporary folder path.
        [string]$tempFolderPath = [System.IO.Path]::GetTempPath();

        # File name.
        [string]$outputFileName = ('Microsoft 365 User MFA Status Report - {0}.csv' -f (Get-Date -Format 'yyyy-MM-dd'));

        # Output file path.
        [string]$outputFilePath = Join-Path -Path $tempFolderPath -ChildPath $outputFileName;
    }
    process
    {
        # Counter for users.
        [int]$userCount = 0;

        # Foreach user.
        foreach ($user in $users)
        {
            # If the user is not protected by a conditional access policy requiring MFA.
            if ($true -eq $user.IsProtected)
            {
                # Continue to next user.
                continue;
            }

            # If the user is not a member.
            if ($user.UserType -ne 'Member')
            {
                # Continue to next user.
                continue;
            }

            # If the account is disabled.
            if ($false -eq $user.AccountEnabled)
            {
                # Continue to next user.
                continue;
            }

            # Add user to HTML.
            $html += '<tr>' | Out-String;
            $html += "<td>$($user.UserPrincipalName)</td>" | Out-String;
            $html += "<td>$($user.DisplayName)</td>" | Out-String;
            $html += "<td>$($user.UserType)</td>" | Out-String;
            $html += "<td>$($user.LastSuccessfulSignIn)</td>" | Out-String;
            $html += '</tr>' | Out-String;

            # Increment user counter.
            $userCount++;
        }

        # Update header with users count.
        $html = $html -replace '##UsersCount##', $userCount;

        # Add footer.
        $html += $footer | Out-String;

        # Write to log.
        Write-CustomLog -Message ('Found {0} MFA users for the MFA status report' -f $users.Count) -Level 'Verbose';
        Write-CustomLog -Message ("Exporting report to '{0}'" -f $outputFilePath) -Level 'Verbose';

        # Save status report as CSV.
        $null = $users | Select-Object -Property Id,
        UserPrincipalName,
        DisplayName,
        AccountEnabled,
        DirSyncEnabled,
        UserType,
        @{ Name = 'PasswordPolicies'; Expression = { $_.PasswordPolicies -join '|' } },
        LastSuccessfulSignIn,
        @{ Name = 'ConditionalAccessPolicy'; Expression = { $_.ConditionalAccessPolicy -join '|' } },
        IsProtected,
        Mailbox | Export-Csv -Path $outputFilePath -UseQuotes Always -Encoding utf8 -Delimiter ';' -Force;

        # Convert CSV to Base64.
        $attachment = [Convert]::ToBase64String([System.IO.File]::ReadAllBytes($outputFilePath));

        # Create parameters for sending the e-mail.
        $params = @{
            message         = @{
                subject      = ('Microsoft 365 User MFA Status Report - {0}' -f (Get-Date -Format 'yyyy-MM-dd'));
                body         = @{
                    contentType = 'HTML';
                    content     = $html;
                };
                toRecipients = @(
                    @{
                        emailAddress = @{
                            address = $EmailAddress;
                        };
                    }
                );
                attachments  = @(
                    @{
                        '@odata.type' = '#microsoft.graph.fileAttachment';
                        name          = $outputFileName;
                        contentType   = 'text/plain';
                        contentBytes  = $attachment;
                    }
                );
            };
            saveToSentItems = 'true';
        };

        # If users count is zero.
        if ($Users.Count -eq 0)
        {
            # Write to log.
            Write-CustomLog -Message 'No users found for the MFA status report, skipping e-mail sending' -Level Warning;
        }
        # Else send the e-mail.
        else
        {
            # Write to log.
            Write-CustomLog -Message ("Sending MFA status report to '{0}'" -f $EmailAddress) -Level Verbose;

            # A UPN can also be used as -UserId.
            $null = Send-MgUserMail `
                -UserId (Get-EntraContext).Account `
                -BodyParameter $params `
                -ErrorAction Stop;
        }
    }
    end
    {
        # Write to log.
        Write-CustomProgress @customProgress;
    }
}