Delegating Reporting Access for Exchange Online

Earlier this week, I had a request for assistance with delegating reporting features in Exchange Online to non-administrative users.  This is a frequent topic of discussion when it comes to compliance and security officers validating that systems are not being misused by unauthorized persons.

Let's say we want to allow Adele, our compliance officer, the ability to view the mailbox access reports.  Fortunately, this is a pretty easy task that can be accomplished with just a little PowerShell (or a little UI work).  I'll show both ways.

User Interface

  1. Launch a browser and navigate to https://outlook.office365.com/ecp/UsersGroups/AdminRoleGroups.slab. Select Records Management and click the pencil/edit icon.
  2. Under Members, select + ( Add) .
  3. Select the user(s) from the GAL and click Add.  Click OK when finished.

PowerShell

And then, the PowerShell Way.  Copy, save to a .ps1, and run with the appropriate parameters.  This will enable the organization configuration and update all of the mailboxes with the appropriate parameters (settings that aren't available in the UI).

 <#
.SYNOPSIS
Enable audit logging and reporting capability for non-administrative users.

.PARAMETER Credential
Office 365 global admin credential.

.PARAMETER Logfile
Specify log file for operations.

.PARAMETER Members
Users to add to RecordsManagement role.

.PARAMETER UpdateAllMailboxes
Update all mailboxes in tenant with either AuditBypassDisabled $false (if
Set-OrganizationConfig supports AuditDisabled parameter) or AuditEnabled $true
(if AuditDisabled Set-OrganizationConfig parameter is not available).

.PARAMETER UseExistingSession
Use existing Office 365 session instead of connecting to a new session.
#>
param (
    $Credential,
    [switch]$Debug,
 [array]$Members,
    $Logfile = ".\MailboxAuditLogConfiguration.txt",
    [switch]$UpdateAllMailboxes,
    [switch]$UseExistingSession
)

# Need a global admin credential
If (!($UseExistingSession))
{
    If (!($Credential))
 {
       $Credential = Get-Credential
    }
   # Connect to Office 365
 $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential -Authentication Basic -AllowRedirection
  Import-PSSession $Session
   $ComplianceSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid -Credential $Credential -Authentication Basic -AllowRedirection
  Import-PSSession $ComplianceSession -AllowClobber -DisableNameChecking
}

function Write-Log([string[]]$Message, [string]$LogFile = $Script:LogFile, [switch]$ConsoleOutput, [ValidateSet("SUCCESS", "INFO", "WARN", "ERROR", "DEBUG")][string]$LogLevel)
{
 $Message = $Message + $Input
    If (!$LogLevel) { $LogLevel = "INFO" }
  switch ($LogLevel)
  {
       SUCCESS { $Color = "Green" }
        INFO { $Color = "White" }
       WARN { $Color = "Yellow" }
      ERROR { $Color = "Red" }
        DEBUG { $Color = "Gray" }
   }
   if ($Message -ne $null -and $Message.Length -gt 0)
  {
       $TimeStamp = [System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss")
     if ($LogFile -ne $null -and $LogFile -ne [System.String]::Empty)
        {
           Out-File -Append -FilePath $LogFile -InputObject "[$TimeStamp] [$LogLevel] $Message"
        }
       if ($ConsoleOutput -eq $true)
       {
           Write-Host "[$TimeStamp] [$LogLevel] :: $Message" -ForegroundColor $Color
       }
   }
}

$Error.Clear()
# Configure environment for auditing
Try
{
    Enable-OrganizationCustomization -ea Stop | Out-Null
}
Catch
{
 $Response = $Error.Exception
    If ($Response -like "*This operation is not required.*")
    {
       Write-Log -LogFile $Logfile -Message "$($Response)" -LogLevel INFO
  }
   Else
    {
       Write-Log -LogFile $Logfile -Message "$($Response)" -ConsoleOutput -LogLevel ERROR
  }
}

# Enable Audit Logging organization-wide
Try
{
  Write-Log -Message "Enabling Unified Audit Log Ingestion." -LogFile INFO -ConsoleOutput -LogLevel INFO
  Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true
}
Catch
{
   Write-Log -Message "Error encountered attempting to update Unified Audit Log setting." -LogFile $Logfile -LogLevel ERROR -ConsoleOutput
}

# Set-OrganizationConfig -AuditDisabled is a new feature that is rolling out. If the AuditDisabled feature isn't available,
# mailbox auditing must be enabled on every mailbox.
$Error.Clear()

Write-Log -LogFile $Logfile -Message "Gathering list of mailboxes." -LogLevel INFO -ConsoleOutput
If ($UpdateAllMailboxes)
{
 $Mailboxes = Get-Mailbox -ResultSize Unlimited
}

$Error.Clear()
Try
{
   Write-Log -LogFile $Logfile -Message "Checking for Set-OrganizationConfig AuditDisabled parameter." -LogLevel DEBUG
 Get-Command Set-OrganizationConfig -ParameterName AuditDisabled -ea Stop | Out-Null
 If (!($Error))
  {
       Write-Log -LogFile $Logfile -Message "Parameter AuditDisabled available.  Setting AuditDisabled parameter to FALSE." -LogLevel INFO -ConsoleOutput
      Set-OrganizationConfig -AuditDisabled $False
        If ($UpdateAllMailboxes)
        {
           Write-Log -LogFile $Logfile -Message "Resetting any mailbox auditing bypass settings to default." -LogLevel SUCCESS -ConsoleOutput
          $i = 1
          foreach ($Mailbox in $Mailboxes)
            {
               Write-Progress -Activity "Resetting AuditBypassEnabled for mailbox $($Mailbox.DisplayName)" -Status "[$($i)/$($Mailboxes.Count)]" -PercentComplete ($i/$Mailboxes.Count * 100)
              If ($Debug) { Write-Log -LogFile $Logfile -Message "Resetting AuditBypassEnabled for $($Mailbox.DisplayName) - ($($Mailbox.PrimarySmtpAddress))" -LogLevel DEBUG }
              Set-MailboxAuditBypassAssociation -Identity $Mailbox.Identity -AuditBypassEnabled $False -Confirm:$False -wa SilentlyContinue
               $i++
            }
       }
   }
}
Catch
{
    Write-Log -LogFile $Logfile -Message "Parameter AuditBypassEnabled not available."
  If ($UpdateAllMailboxes)
    {
       Write-Log -LogFile $Logfile -Message "UpdateAllMailboxes set. Updating Mailbox Audit parameters for all mailboxes."
     $i = 1
      Foreach ($Mailbox in $Mailboxes)
        {
           Write-Progress -Activity "Enabling Mailbox Audit setting for mailbox $($Mailbox.DisplayName)" -Status "[$($i)/$($Mailboxes.Count)]" -PercentComplete ($i/$Mailboxes.Count * 100)
            If ($Debug) { Write-Log -LogFile $Logfile -Message "Enabling Mailbox Audit setting for $($Mailbox.DisplayName) - ($($Mailbox.PrimarySmtpAddress))" -LogLevel DEBUG }
            Set-Mailbox -Identity $Mailbox.Identity -AuditEnabled $True -wa SilentlyContinue
        }
   }
}

# Update Role Group Memberships
If ($Members)
{
 Write-Log -LogFile $Logfile -LogLevel INFO -Message "Adding members to RecordsManagement role." -ConsoleOutput
  Foreach ($Member in $Members)
   {
       $Error.Clear()
      Try
     {
           Add-RoleGroupMember -Member $Member -Identity RecordsManagement -ea Stop | Out-Null
     }
       Catch
       {
           $Value = $Error.Exception
           If ($Value -match "already a member") { Write-Log -LogFile $Logfile -Message $Value -LogLevel INFO }
            Else { Write-Log -LogFile $Logfile -Message $Value -ConsoleOutput -LogLevel ERROR }
     }
   }
}
Write-Log -Message "Complete." -LogFile $Logfile -ConsoleOutput -LogLevel INFO

Once the role permissions have been assigned, log back in with a newly added Records Management member, and then a user can navigate directly to https://outlook.office365.com/ecp/Reporting/AuditReports.slab.  For more information on viewing the reports, check out /en-us/Exchange/policy-and-compliance/non-owner-mailbox-access-reports?view=exchserver-2019.