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
#############################