Public/Security.ps1

<#
.SYNOPSIS
    Finds SendAs audit records in the Office 365 audit log.
.DESCRIPTION
    This function searches the Office 365 audit log for SendAs events and enriches the data with information about the recipient type.
.PARAMETER StartDate
    The start date for the audit log search. Defaults to 90 days ago.
.PARAMETER EndDate
    The end date for the audit log search. Defaults to today.
.PARAMETER ResultSize
    The maximum number of audit records to return. Defaults to 2000.
.PARAMETER NoGridView
    If specified, the function will not display the results in a grid view.
.PARAMETER ExportPath
    If specified, the function will export the results to a CSV file at the given path.
.EXAMPLE
    Find-O365SendAsAuditRecord -ExportPath "C:\temp\SendAsAudit.csv"
.NOTES
    You must be connected to Exchange Online before running this function.
#>

function Find-O365SendAsAuditRecord {
    [CmdletBinding()]
    param(
        [datetime]$StartDate = (Get-Date).AddDays(-90),
        [datetime]$EndDate = (Get-Date).AddDays(1),
        [int]$ResultSize = 2000,
        [switch]$NoGridView,
        [string]$ExportPath
    )

    Write-Verbose "Populating Recipients Table..."
    $RecipientsTable = @{}
    try {
        $Recipients = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox
        $Recipients.ForEach({
            $RecipientsTable.Add([String]$_.PrimarySmtpAddress, $_.RecipientTypeDetails)
        })
        $GroupMailboxes = Get-Mailbox -ResultSize Unlimited -GroupMailbox
        $GroupMailboxes.ForEach({
            $RecipientsTable.Add([String]$_.PrimarySmtpAddress, $_.RecipientTypeDetails)
        })
    }
    catch {
        Write-Warning "Can't find recipients. Make sure you are connected to Exchange Online."
        return
    }

    Write-Verbose "Finding audit records for Send As operations..."
    $Records = (Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations "SendAs" -ResultSize $ResultSize)
    if ($Records.Count -eq 0) {
        Write-Verbose "No audit records for Send As found."
        return
    }

    Write-Verbose "Processing $($Records.Count) Send As audit records..."
    $Report = [System.Collections.Generic.List[Object]]::new()
    ForEach ($Rec in $Records) {
        $AuditData = ConvertFrom-Json $Rec.Auditdata
        $MailboxType = $RecipientsTable.Item($AuditData.MailboxOwnerUPN)
        if ($MailboxType -eq "GroupMailbox") { $Reason = "Group Mailbox Send" } else { $Reason = "Delegate Send As" }
        if ($AuditData.UserId -eq "S-1-5-18") { $UserId = "Service Account" } else { $UserId = $AuditData.UserId }
        $ReportLine = [PSCustomObject]@{ 
            TimeStamp   = Get-Date($AuditData.CreationTime) -format g
            SentBy      = $AuditData.MailboxOwnerUPN
            SentAs      = $AuditData.SendAsUserSmtp
            Subject     = $AuditData.Item.Subject
            User        = $AuditData.UserId
            Action      = $AuditData.Operation
            Reason      = $Reason
            UserType    = $AuditData.UserType
            LogonType   = $AuditData.LogonType
            ClientIP    = $AuditData.ClientIP
            MailboxType = $MailboxType
            ClientInfo  = $AuditData.ClientInfoString
            Status      = $AuditData.ResultStatus
        }
        $Report.Add($ReportLine)
    }

    $UserMailboxReport = $Report | Where-Object { $_.MailboxType -eq "UserMailbox" }

    if (-not $NoGridView) {
        $UserMailboxReport | Out-GridView
    }

    if ($ExportPath) {
        try {
            $Report | Export-Csv -NoTypeInformation -Path $ExportPath
            Write-Verbose "Report file saved to $ExportPath"
        }
        catch {
            Write-Warning "Failed to save report file to $ExportPath. Error: $_ "
        }
    }

    return $Report
}

<#
.SYNOPSIS
    Generates a security scorecard for the tenant.
.DESCRIPTION
    This function checks for a variety of security best practices and provides a report with a summary of the findings.
.PARAMETER StaleAdminDays
    The number of days to check for stale admin sign-ins.
.PARAMETER InactiveGuestDays
    The number of days to check for inactive guest users.
.EXAMPLE
    Get-O365SecurityScorecard
.NOTES
    You must be connected to the Microsoft Graph and Exchange Online with the appropriate scopes before running this function.
#>
  
function Get-O365SecurityScorecard {
    [CmdletBinding()]
    param(
        [int]$StaleAdminDays = 30,
        [int]$InactiveGuestDays = 30
    )

    Write-Verbose "Generating security scorecard..."

    $StaleAdmins = Get-O365StaleAdminSignIns -Days $StaleAdminDays
    $InactiveGuests = Get-O365InactiveGuestUsers -Days $InactiveGuestDays
    $UsersWithoutMFA = Get-O365UsersWithoutMFA
    $MailboxForwardingRules = Get-O365MailboxForwardingRules
    $UnsignedInServicePrincipals = Get-O365UnsignedInServicePrincipals

    $Scorecard = [PSCustomObject]@{ 
        StaleAdmins = $StaleAdmins.Count
        InactiveGuests = $InactiveGuests.Count
        UsersWithoutMFA = $UsersWithoutMFA.Count
        MailboxForwardingRules = $MailboxForwardingRules.Count
        UnsignedInServicePrincipals = $UnsignedInServicePrincipals.Count
    }

    return $Scorecard
}

<#
.SYNOPSIS
    Gets Conditional Access policies that are in "report-only" mode.
.DESCRIPTION
    This function gets all Conditional Access policies in the tenant and filters for those that are in "report-only" mode.
.EXAMPLE
    Get-O365ReportOnlyConditionalAccessPolicy
.NOTES
    You must be connected to the Microsoft Graph with the 'Policy.Read.All' scope before running this function.
#>
  
function Get-O365ReportOnlyConditionalAccessPolicy {
    [CmdletBinding()]
    param()

    Write-Verbose "Finding report-only Conditional Access policies..."

    $Policies = Get-MgIdentityConditionalAccessPolicy
    $ReportOnlyPolicies = $Policies | Where-Object { $_.State -eq 'enabledForReportingButNotEnforced' }

    Write-Verbose "Found $($ReportOnlyPolicies.Count) report-only Conditional Access policies."
    return $ReportOnlyPolicies
}