Soluções Tecnológicas para Empresas – Revenda Oficial
Whatsapp: (11) 4270-0585
Telefone: (11) 4270-0585
Por 01IT em 15/08/2025
PowerShell: Relatório de mailboxes (licenciadas, compartilhadas e arquivo morto) no Exchange Online

Precisa mapear rapidamente quem tem caixa licenciada, quais são as caixas compartilhadas e o consumo de e-mail (principal e arquivo morto)?

Estes scripts #Versão 1 e #Versão 2 em PowerShell poderão te ajudar.

#############################

#Versão 1

# Carrega o módulo Exchange Online e conecta-se (necessário no Exchange Online)
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -ShowBanner:$false

# 1. Obter caixas de usuário licenciadas (UserMailbox com SKUAssigned = True)
$licensedUserMailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox |
    Where-Object { $_.SkuAssigned -eq $true }

# 2. Obter caixas compartilhadas (SharedMailbox)
$sharedMailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails SharedMailbox

# 3. Função para obter o tamanho do arquivo morto (In-Place Archive), caso exista
function Get-ArchiveSize {
    param([string]$Identity)
    $stats = Get-MailboxStatistics -Identity $Identity -Archive -ErrorAction SilentlyContinue
    return $stats?.TotalItemSize
}

# 4. Criar relatório combinando caixas licenciadas, compartilhadas e status do arquivo morto
$relatorio = @()

foreach ($mbx in $licensedUserMailboxes) {
    # Usar o UserPrincipalName, e caso esteja em branco, usar o PrimarySmtpAddress [oai_citation:0‡m365scripts.com](https://m365scripts.com/exchange-online/get-mailbox-details-in-microsoft-365-using-powershell/#:~:text=Get,select%20Displayname%2CTotalItemSize)
    $identity = if (![string]::IsNullOrEmpty($mbx.UserPrincipalName)) { $mbx.UserPrincipalName } else { $mbx.PrimarySmtpAddress }

    # Estatísticas do mailbox principal
    $primaryStats = Get-MailboxStatistics -Identity $identity

    # Determinar se o arquivo morto está ativo [oai_citation:1‡morgantechspace.com](https://morgantechspace.com/2021/01/check-size-and-status-of-archive-mailbox-powershell.html#:~:text=Run%20the%20following%20command%20to,has%20an%20active%20archive%20mailbox)
    $archiveActive = ($mbx.ArchiveStatus -eq "Active" -or $mbx.ArchiveDatabase)

    # Obter o tamanho do arquivo morto (se ativo)
    $archiveSize = $null
    if ($archiveActive) {
        $archiveSize = Get-ArchiveSize -Identity $identity
    }

    # Adicionar registro ao relatório
    $relatorio += [PSCustomObject]@{
        DisplayName         = $mbx.DisplayName
        PrimarySMTP         = $mbx.PrimarySmtpAddress
        Tipo                = "Usuário (licenciada)"
        Licenciada          = $mbx.SkuAssigned
        ConsumoMailbox      = $primaryStats.TotalItemSize
        ArquivoMortoAtivo   = $archiveActive
        ConsumoArquivoMorto = $archiveSize
    }
}

foreach ($mbx in $sharedMailboxes) {
    # Para caixas compartilhadas, também tentar usar o UserPrincipalName; se não existir, use o Primary SMTP
    $identity = if (![string]::IsNullOrEmpty($mbx.UserPrincipalName)) { $mbx.UserPrincipalName } else { $mbx.PrimarySmtpAddress }

    $primaryStats = Get-MailboxStatistics -Identity $identity
    $archiveActive = ($mbx.ArchiveStatus -eq "Active" -or $mbx.ArchiveDatabase)
    $archiveSize = $null
    if ($archiveActive) {
        $archiveSize = Get-ArchiveSize -Identity $identity
    }

    $relatorio += [PSCustomObject]@{
        DisplayName         = $mbx.DisplayName
        PrimarySMTP         = $mbx.PrimarySmtpAddress
        Tipo                = "Compartilhada"
        Licenciada          = $mbx.SkuAssigned     # normalmente False, exceto se a caixa compartilhada estiver licenciada
        ConsumoMailbox      = $primaryStats.TotalItemSize
        ArquivoMortoAtivo   = $archiveActive
        ConsumoArquivoMorto = $archiveSize
    }
}

# 5. Exibir o relatório formatado
$relatorio | Format-Table -AutoSize

# 6. (Opcional) Exportar o relatório para CSV
$relatorio | Export-Csv -Path "C:\RelatorioCaixas_ComArquivoMorto.csv" -NoTypeInformation -Encoding UTF8

# Desconectar do Exchange Online (opcional)
Disconnect-ExchangeOnline -Confirm:$false

#############################

#############################

#Versão 2

<# ================================

 Relatório Exchange Online + Licenças (Graph) – versão completa e robusta

 - Consumo do mailbox (texto e numérico)

 - Consumo do arquivo morto (texto e numérico)

 - Quotas (primária e arquivo morto) em bytes

 - ItemCount e LastLogonTime

 - Licenças por usuário (SkuPartNumber e nomes amigáveis)

 - Progresso e resiliência

================================ #>

 

param(

    [string]$CsvPath = "C:\Relatorio-Mailboxes-Licencas.csv",

    # Opcional: exportar também em XLSX via Excel COM (Windows + Excel instalados)

    [string]$XlsxPath = "",

    # Opcional: incluir TODAS as UserMailbox (mesmo sem licença). Padrão: apenas licenciadas.

    [switch]$IncludeAllUserMailboxes

)

 

$ErrorActionPreference = 'Stop'

 

Import-Module ExchangeOnlineManagement

Import-Module Microsoft.Graph.Authentication

Import-Module Microsoft.Graph.Users

 

Write-Host "Conectando ao Exchange Online..." -ForegroundColor Cyan

Connect-ExchangeOnline -ShowBanner:$false

 

Write-Host "Conectando ao Microsoft Graph..." -ForegroundColor Cyan

try {

    Connect-MgGraph -Scopes "User.Read.All" | Out-Null

} catch {

    Write-Warning "Falha com User.Read.All. Tentando Directory.Read.All..."

    Connect-MgGraph -Scopes "Directory.Read.All" | Out-Null

}

 

# -------------------- Utilitários: tamanhos e quotas --------------------

function Get-BytesFromSize {

    <#

      Converte o TotalItemSize/Quota (que pode ser ByteQuantifiedSize/Unlimited/string) para bytes (Int64).

      Tenta:

      - .Value.ToBytes() (Unlimited<ByteQuantifiedSize>)

      - .ToBytes() direto

      - parse do texto entre parênteses "(XXXXXXXX bytes)"

    #>

    param($SizeObj)

    if ($null -eq $SizeObj) { return $null }

    try {

        if ($SizeObj.PSObject.Properties['Value']) {

            $v = $SizeObj.Value

            if ($v -and ($v.PSObject.Methods.Name -contains 'ToBytes')) {

                return [int64]$v.ToBytes()

            }

        }

        if ($SizeObj.PSObject.Methods.Name -contains 'ToBytes') {

            return [int64]$SizeObj.ToBytes()

        }

        $text = $SizeObj.ToString()

        $m = [regex]::Match($text, '\((?<bytes>[\d\.,\s]+)\s*bytes\)', 'IgnoreCase')

        if ($m.Success) {

            $num = ($m.Groups['bytes'].Value -replace '[^\d]', '')

            return [int64]$num

        }

    } catch { }

    return $null

}

 

function To-GB {

    param([Nullable[Int64]]$Bytes)

    if ($Bytes -ne $null) { return [math]::Round($Bytes / 1GB, 2) } else { return $null }

}

 

# -------------------- Ícones de SKU -> nomes amigáveis --------------------

$SkuPretty = @{

  'M365_BUSINESS_PREMIUM'            = 'Microsoft 365 Business Premium'

  'O365_BUSINESS_ESSENTIALS'         = 'Microsoft 365 Business Basic'

  'STANDARDPACK'                     = 'Office 365 E1'

  'ENTERPRISEPACK'                   = 'Office 365 E3'

  'ENTERPRISEPREMIUM'                = 'Office 365 E5'

  'SPE_E3'                           = 'Microsoft 365 E3'

  'SPE_E5'                           = 'Microsoft 365 E5'

  'EXCHANGESTANDARD'                 = 'Exchange Online (Plan 1)'

  'EXCHANGEENTERPRISE'               = 'Exchange Online (Plan 2)'

  'FLOW_FREE'                        = 'Power Automate Free'

  'MICROSOFT_TEAMS_EXPLORATORY'      = 'Microsoft Teams Exploratory'

  'MICROSOFT_TEAMS_EXPLORATORY_DEPT' = 'Teams Exploratory (Dept)'

}

 

function Resolve-LicenseNames {

    param([string[]]$SkuParts)

    if (-not $SkuParts) { return $null }

    return ($SkuParts | ForEach-Object { if ($SkuPretty.ContainsKey($_)) { $SkuPretty[$_] } else { $_ } })

}

 

# -------------------- Licenças (Graph) --------------------

Write-Host "Carregando licenças por usuário (Graph)..." -ForegroundColor Cyan

$LicenseMap = @{}   # AAD ObjectId -> string[] SkuPartNumber

 

$allUsers = Get-MgUser -All -Property "Id,UserPrincipalName"

$i = 0; $t = $allUsers.Count

foreach ($u in $allUsers) {

    $i++

    Write-Progress -Activity "Consultando licenças" -Status $u.UserPrincipalName -PercentComplete (($i/$t)*100)

    try {

        $details = Get-MgUserLicenseDetail -UserId $u.Id -All -ErrorAction Stop

        $parts = @()

        if ($details) { $parts = $details | Select-Object -ExpandProperty SkuPartNumber | Sort-Object -Unique }

        $LicenseMap[$u.Id] = $parts

    } catch {

        $LicenseMap[$u.Id] = @()

    }

}

Write-Progress -Activity "Consultando licenças" -Completed

 

# -------------------- Mailboxes (EXO) --------------------

Write-Host "Coletando mailboxes..." -ForegroundColor Cyan

if ($IncludeAllUserMailboxes) {

    $userMailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox

} else {

    $userMailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox |

                     Where-Object { $_.SkuAssigned -eq $true }

}

$sharedMailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails SharedMailbox

 

function Get-ArchiveStatsRobusto {

    param($mbx)

    $candidatos = @(

        $mbx.UserPrincipalName,

        $mbx.PrimarySmtpAddress,

        $mbx.ExchangeGuid,

        $mbx.ArchiveGuid

    ) | Where-Object { $_ }

 

    foreach ($id in $candidatos) {

        $s = Get-MailboxStatistics -Identity $id -Archive -ErrorAction SilentlyContinue

        if ($s) { return $s }

    }

    return $null

}

 

# -------------------- Monta o relatório --------------------

$relatorio = @()

$allMbx = @($userMailboxes + $sharedMailboxes)

 

$k = 0; $n = $allMbx.Count

foreach ($mbx in $allMbx) {

    $k++

    Write-Progress -Activity "Montando relatório" -Status $mbx.PrimarySmtpAddress -PercentComplete (($k/$n)*100)

 

    # Identity único para estatísticas

    $identity = if ($mbx.UserPrincipalName) { $mbx.UserPrincipalName } else { $mbx.PrimarySmtpAddress }

 

    # Estatísticas do mailbox principal

    $stats = Get-MailboxStatistics -Identity $identity

 

    $primaryBytes  = Get-BytesFromSize $stats.TotalItemSize

    $primaryGB     = To-GB   $primaryBytes

    $deletedBytes  = Get-BytesFromSize $stats.TotalDeletedItemSize

    $deletedGB     = To-GB   $deletedBytes

 

    # Arquivo morto

    $archiveActive = ($mbx.ArchiveStatus -eq "Active" -or $mbx.ArchiveDatabase)

    $archStats     = if ($archiveActive) { Get-ArchiveStatsRobusto -mbx $mbx } else { $null }

    $archBytes     = if ($archStats) { Get-BytesFromSize $archStats.TotalItemSize } else { $null }

    $archGB        = To-GB $archBytes

 

    # Quotas (podem ser Unlimited)

    $mbxQuotaWarnBytes       = Get-BytesFromSize $mbx.IssueWarningQuota

    $mbxQuotaSendBytes       = Get-BytesFromSize $mbx.ProhibitSendQuota

    $mbxQuotaSendRecvBytes   = Get-BytesFromSize $mbx.ProhibitSendReceiveQuota

    $archQuotaBytes          = Get-BytesFromSize $mbx.ArchiveQuota

    $archWarnQuotaBytes      = Get-BytesFromSize $mbx.ArchiveWarningQuota

 

    # Licenças via Graph

    $skuParts = @()

    if ($mbx.ExternalDirectoryObjectId -and $LicenseMap.ContainsKey($mbx.ExternalDirectoryObjectId)) {

        $skuParts = $LicenseMap[$mbx.ExternalDirectoryObjectId]

    }

    $friendly = Resolve-LicenseNames -SkuParts $skuParts

 

    $relatorio += [pscustomobject]@{

        # Identidade

        DisplayName                 = $mbx.DisplayName

        PrimarySMTP                 = $mbx.PrimarySmtpAddress

        Tipo                        = $mbx.RecipientTypeDetails                 # UserMailbox / SharedMailbox

 

        # Licenças

        Licencas                    = ($skuParts -join '; ')                    # SkuPartNumber

        LicencasAmigaveis           = ($friendly -join '; ')                    # Nomes amigáveis

        Licenciada                  = ($mbx.SkuAssigned -or ($skuParts.Count -gt 0))

 

        # Consumo (texto legível)

        ConsumoMailbox              = $stats.TotalItemSize

        ConsumoArquivoMorto         = if ($archStats) { $archStats.TotalItemSize } else { $null }

        DeletedItemSize             = $stats.TotalDeletedItemSize

 

        # Consumo (numérico para Excel)

        MailboxBytes                = $primaryBytes

        MailboxGB                   = $primaryGB

        DeletedBytes                = $deletedBytes

        DeletedGB                   = $deletedGB

        ArchiveBytes                = $archBytes

        ArchiveGB                   = $archGB

 

        # Métricas

        Itens                       = [int64]$stats.ItemCount

        UltimoLogon                 = $stats.LastLogonTime

        ArquivoMortoAtivo           = [bool]$archiveActive

 

        # Quotas (bytes)

        MailboxWarnQuotaBytes       = $mbxQuotaWarnBytes

        MailboxSendQuotaBytes       = $mbxQuotaSendBytes

        MailboxSendReceiveQuotaBytes= $mbxQuotaSendRecvBytes

        ArchiveQuotaBytes           = $archQuotaBytes

        ArchiveWarnQuotaBytes       = $archWarnQuotaBytes

    }

}

Write-Progress -Activity "Montando relatório" -Completed

 

# -------------------- Exportações --------------------

Write-Host "Exportando CSV: $CsvPath" -ForegroundColor Cyan

$relatorio | Sort-Object Tipo, DisplayName | Export-Csv $CsvPath -NoTypeInformation -Encoding UTF8

Write-Host "OK" -ForegroundColor Green

 

if ($XlsxPath -and $IsWindows) {

    try {

        Write-Host "Gerando XLSX (requer Excel instalado): $XlsxPath" -ForegroundColor Cyan

        $xl = New-Object -ComObject Excel.Application

        $xl.Visible = $false

        $wb = $xl.Workbooks.Open($CsvPath)

        $xl.DisplayAlerts = $false

        $wb.SaveAs($XlsxPath, 51)  # 51 = xlOpenXMLWorkbook (.xlsx)

        $wb.Close()

        $xl.Quit()

        Write-Host "XLSX gerado com sucesso." -ForegroundColor Green

    } catch {

        Write-Warning "Falha ao gerar XLSX via COM. Verifique se o Excel está instalado e se o PowerShell está em modo 64-bit."

    }

}

Write-Host "Concluído." -ForegroundColor Green

# -------------------- Desconexões (opcionais) --------------------

Disconnect-ExchangeOnline -Confirm:$false

Disconnect-MgGraph | Out-Null

#############################

Aguarde..