Files
PowerShell-scripts/Exchange/Exchange-Inventory.ps1

593 lines
27 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<#
.SYNOPSIS
Exchange on-prem inventarisatie t.b.v. migratie rapport in Word (fallback HTML) + CSV's
Inclusief: werkelijke benodigde mailboxruimte (som TotalItemSize van on-prem mailboxen, excl. whitespace).
.PARAMETER OutputFolder
Doelmap voor outputbestanden. Default: .\Exchange-Inventory-<datum>
.PARAMETER TopMailboxCount
Aantal grootste mailboxen in rapport (default 30)
.PARAMETER IncludeCSVs
Exporteer detail-CSV's (default $true)
.PARAMETER ProgressThrottleMs
Minimale interval (ms) tussen progress updates in intensieve loops (default 75)
.NOTES
- Uitvoeren in Exchange Management Shell met passende RBAC.
- Getest op Exchange 2013/2016/2019. Secties zonder cmdlets worden overgeslagen.
- Gebruik:
# Exchange Management Shell
.\Exchange-Inventory.ps1 -OutputFolder "D:\Reports\<naam>" -TopMailboxCount 50 -IncludeCSVs $true
# Optioneel trager/sneller progress-throttle:
.\Exchange-Inventory.ps1 -ProgressThrottleMs 50
#>
[CmdletBinding()]
param(
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("Exchange-Inventory-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
[int]$TopMailboxCount = 30,
[bool]$IncludeCSVs = $true,
[int]$ProgressThrottleMs = 75
)
# ------------------------- Helpers: Progress / Output / Word/HTML -------------------------
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
$sections = @(
"Voorbereiding","Organisatie & Servers","Client Access & VDirs","POP/IMAP",
"Certificaten","Domeinen & Policies","Transport & Mailflow","Databases & DAG's",
"Database Copies","Mailboxen (on-prem)","Mailbox Stats","Mailbox Join",
"Benodigde mailboxruimte","Top Mailboxen","Retention & Compliance","Mobile Device Policies",
"Addressing (AL/GAL/OAB)","Public Folders","Throttling Policies",
"Hybrid/Federation/OAuth","Mail Queues","Export CSV","Afronden"
)
[int]$sectionIndex = 0
[int]$totalSections = $sections.Count
function Show-Progress([string]$status, [int]$percent = -1) {
$activity = "Exchange Inventory $($sections[$sectionIndex])"
if($percent -lt 0){
Write-Progress -Activity $activity -Status $status
} else {
Write-Progress -Activity $activity -Status $status -PercentComplete ([Math]::Min(100,[Math]::Max(0,$percent)))
}
}
function Next-Section {
if($sectionIndex -lt $totalSections-1){ $sectionIndex++ }
$msg = "[{0}] Start sectie: {1} ({2}/{3})" -f (NowTag), $sections[$sectionIndex], ($sectionIndex+1), $totalSections
Write-Host $msg
Write-Progress -Activity ("Exchange Inventory {0}" -f $sections[$sectionIndex]) -Status "Bezig..." -PercentComplete ([int](($sectionIndex+1)/$totalSections*100))
}
# Outputmap
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
$ReportDocx = Join-Path $OutputFolder "Exchange-Inventory.docx"
$ReportHtml = Join-Path $OutputFolder "Exchange-Inventory.html"
# Word COM of HTML fallback
$script:Word = $null
$script:Doc = $null
$script:UseHtml = $false
$script:HtmlSb = New-Object System.Text.StringBuilder
function Start-Report {
try {
$script:Word = New-Object -ComObject Word.Application
$script:Word.Visible = $false
$script:Doc = $script:Word.Documents.Add()
Add-Heading "Exchange Inventarisatie" 1
Add-Paragraph ("Gegenereerd: " + (Get-Date).ToString("yyyy-MM-dd HH:mm"))
} catch {
$script:UseHtml = $true
[void]$script:HtmlSb.AppendLine("<html><head><meta charset='utf-8'><title>Exchange Inventarisatie</title>")
[void]$script:HtmlSb.AppendLine("<style>body{font-family:Segoe UI,Arial,sans-serif} h1{font-size:22px} h2{font-size:18px} table{border-collapse:collapse;width:100%} th,td{border:1px solid #ccc;padding:6px;text-align:left} th{background:#f3f3f3}</style></head><body>")
[void]$script:HtmlSb.AppendLine("<h1>Exchange Inventarisatie</h1>")
[void]$script:HtmlSb.AppendLine("<p>Gegenereerd: " + (Get-Date).ToString("yyyy-MM-dd HH:mm") + "</p>")
}
}
function Add-Heading([string]$Text, [int]$Level=2) {
if($script:UseHtml){
[void]$script:HtmlSb.AppendLine("<h$Level>$Text</h$Level>")
} else {
$range = $script:Doc.Range()
$range.Collapse(0) | Out-Null
$range.Text = "$Text`r"
$style = switch($Level){ 1 {'Heading 1'} 2 {'Heading 2'} 3 {'Heading 3'} default {'Heading 2'}}
$range.set_Style($style)
$range.InsertParagraphAfter() | Out-Null
}
}
function Add-Paragraph([string]$Text){
if($script:UseHtml){
$enc = [System.Web.HttpUtility]::HtmlEncode($Text)
[void]$script:HtmlSb.AppendLine("<p>$enc</p>")
} else {
$range = $script:Doc.Range()
$range.Collapse(0) | Out-Null
$range.Text = $Text
$range.InsertParagraphAfter() | Out-Null
}
}
function Convert-ToCellText([object]$v){
if($null -eq $v){ return "" }
if($v -is [DateTime]){ return $v.ToString("yyyy-MM-dd HH:mm") }
if($v -is [string]){ return $v }
if($v -is [System.Collections.IEnumerable] -and -not ($v -is [string])){
$items = @()
foreach($i in $v){ $items += [string]$i }
return ($items -join ", ")
}
return [string]$v
}
function Add-Table([object]$Objects, [string[]]$PropOrder, [string]$Title){
if($null -eq $Objects){
Add-Paragraph ("{0}: geen data." -f $Title)
return
}
$arr = @()
if($Objects -is [System.Collections.IEnumerable] -and -not ($Objects -is [string])){
foreach($o in $Objects){ $arr += $o }
} else { $arr = @($Objects) }
if($arr.Count -eq 0){
Add-Paragraph ("{0}: geen data." -f $Title)
return
}
Add-Heading $Title 3
if($script:UseHtml){
[void]$script:HtmlSb.AppendLine("<table><thead><tr>")
foreach($p in $PropOrder){ [void]$script:HtmlSb.AppendLine("<th>$([System.Web.HttpUtility]::HtmlEncode($p))</th>") }
[void]$script:HtmlSb.AppendLine("</tr></thead><tbody>")
foreach($o in $arr){
[void]$script:HtmlSb.AppendLine("<tr>")
foreach($p in $PropOrder){
$cell = Convert-ToCellText ($o.$p)
[void]$script:HtmlSb.AppendLine("<td>$([System.Web.HttpUtility]::HtmlEncode($cell))</td>")
}
[void]$script:HtmlSb.AppendLine("</tr>")
}
[void]$script:HtmlSb.AppendLine("</tbody></table>")
} else {
$rows = $arr.Count
$cols = $PropOrder.Count
$range = $script:Doc.Range()
$table = $script:Doc.Tables.Add($range, [Math]::Max(1,$rows)+1, $cols)
for($c=1; $c -le $cols; $c++){ $table.Cell(1,$c).Range.Text = $PropOrder[$c-1] }
$table.Rows.Item(1).Range.Bold = $true
for($r=0; $r -lt $rows; $r++){
for($c=0; $c -lt $cols; $c++){
$prop = $PropOrder[$c]
$cell = Convert-ToCellText ($arr[$r].$prop)
$table.Cell($r+2, $c+1).Range.Text = $cell
}
}
$table.AutoFitBehavior(2) | Out-Null
$range.InsertParagraphAfter() | Out-Null
}
}
function End-Report {
if($script:UseHtml){
[void]$script:HtmlSb.AppendLine("</body></html>")
$script:HtmlSb.ToString() | Out-File -LiteralPath $ReportHtml -Encoding UTF8
Write-Host ("[{0}] HTML-rapport: {1}" -f (NowTag), $ReportHtml)
} else {
$wdFormatXMLDocument = 12
$script:Doc.SaveAs([ref]$ReportDocx, [ref]$wdFormatXMLDocument)
$script:Doc.Close(); $script:Word.Quit()
Write-Host ("[{0}] Word-rapport: {1}" -f (NowTag), $ReportDocx)
}
Write-Progress -Activity "Exchange Inventory Gereed" -Completed
}
function Export-IfEnabled($data, $name){
if($IncludeCSVs -and $data){
$file = Join-Path $OutputFolder ($name + ".csv")
$data | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $file
Write-Host ("[{0}] CSV geexporteerd: {1}" -f (NowTag), $file)
}
}
# ------------------------- Start -------------------------
Write-Host ("[{0}] Inventarisatie gestart..." -f (NowTag))
Show-Progress "Initialisatie..." 1
Start-Report
# 1) Organisatie & Servers
Next-Section
Show-Progress "Get-OrganizationConfig / Get-ExchangeServer..."
try { $org = Get-OrganizationConfig -ErrorAction SilentlyContinue } catch { $org = $null }
$servers = Get-ExchangeServer -ErrorAction SilentlyContinue | Sort-Object Name
$serversOut = $servers | Select-Object Name, Edition, AdminDisplayVersion, IsClientAccessServer,
IsMailboxServer, IsHubTransportServer, ServerRole, Site, Fqdn, *Build*
Add-Heading "Organisatie & Servers" 2
if($org){
$orgRow = [pscustomobject]@{
Name = $org.Name
DomainName = $org.OriginatingServer
AddressBookPolicyRouting = $org.AddressBookPolicyRoutingEnabled
OAuth2ClientProfile = $org.OAuth2ClientProfileEnabled
PublicFoldersEnabled = $org.PublicFoldersEnabled
IsDehydrated = $org.IsDehydrated
}
Add-Table $orgRow @("Name","DomainName","AddressBookPolicyRouting","OAuth2ClientProfile","PublicFoldersEnabled","IsDehydrated") "Organisatieconfig"
}
Add-Table $serversOut @("Name","Edition","AdminDisplayVersion","ServerRole","Site","Fqdn") "Exchange Servers"
# 2) Client Access & Virtual Directories
Next-Section
Show-Progress "Virtual directories ophalen..."
try {
$owa = Get-OWAVirtualDirectory -ErrorAction Stop | Select-Object Server,Name,InternalUrl,ExternalUrl,BasicAuthentication
} catch {
$owa = Get-OWAVirtualDirectory -ErrorAction SilentlyContinue | Select-Object Server,Name,InternalUrl,ExternalUrl
}
$ecp = Get-EcpVirtualDirectory -ErrorAction SilentlyContinue | Select-Object Server,Name,InternalUrl,ExternalUrl
$ews = Get-WebServicesVirtualDirectory -ErrorAction SilentlyContinue | Select-Object Server,Name,InternalUrl,ExternalUrl
$oabvd= Get-OabVirtualDirectory -ErrorAction SilentlyContinue | Select-Object Server,Name,InternalUrl,ExternalUrl
$autod= Get-AutodiscoverVirtualDirectory -ErrorAction SilentlyContinue | Select-Object Server,Name,InternalUrl,ExternalUrl
$as = Get-ActiveSyncVirtualDirectory -ErrorAction SilentlyContinue | Select-Object Server,Name,InternalUrl,ExternalUrl
$mapi= Get-MapiVirtualDirectory -ErrorAction SilentlyContinue | Select-Object Server,Name,InternalUrl,ExternalUrl, IISAuthenticationMethods
$psvd= Get-PowerShellVirtualDirectory -ErrorAction SilentlyContinue | Select-Object Server,Name,InternalUrl,ExternalUrl
Add-Table $owa @("Server","Name","InternalUrl","ExternalUrl") "OWA"
Add-Table $ecp @("Server","Name","InternalUrl","ExternalUrl") "ECP"
Add-Table $ews @("Server","Name","InternalUrl","ExternalUrl") "EWS"
Add-Table $oabvd@("Server","Name","InternalUrl","ExternalUrl") "OAB"
Add-Table $autod@("Server","Name","InternalUrl","ExternalUrl") "Autodiscover"
Add-Table $as @("Server","Name","InternalUrl","ExternalUrl") "ActiveSync"
Add-Table $mapi @("Server","Name","InternalUrl","ExternalUrl","IISAuthenticationMethods") "MAPI/HTTP"
Add-Table $psvd @("Server","Name","InternalUrl","ExternalUrl") "Remote PowerShell"
# 3) POP/IMAP
Next-Section
Show-Progress "POP/IMAP settings..."
$pop = Get-PopSettings -ErrorAction SilentlyContinue | Select-Object Server, LoginType, X509CertificateName, SSLBindings, UnencryptedOrTLSBindings, ProtocolLogEnabled
$imap= Get-ImapSettings -ErrorAction SilentlyContinue | Select-Object Server, LoginType, X509CertificateName, SSLBindings, UnencryptedOrTLSBindings, ProtocolLogEnabled
Add-Table $pop @("Server","LoginType","X509CertificateName","SSLBindings","UnencryptedOrTLSBindings","ProtocolLogEnabled") "POP"
Add-Table $imap @("Server","LoginType","X509CertificateName","SSLBindings","UnencryptedOrTLSBindings","ProtocolLogEnabled") "IMAP"
# 4) Certificaten
Next-Section
Show-Progress "Exchange certificaten..."
$certs = @()
Get-ExchangeCertificate -ErrorAction SilentlyContinue | ForEach-Object {
$days = ($_.NotAfter - (Get-Date)).TotalDays
$status = if($days -lt 0){"Verlopen"} elseif($days -lt 90){"Verloopt <90d"} else {"OK"}
$certs += [pscustomobject]@{
Thumbprint = $_.Thumbprint
Services = ($_.Services -join ",")
Subject = $_.Subject
FriendlyName = $_.FriendlyName
NotBefore = $_.NotBefore
NotAfter = $_.NotAfter
Status = $status
}
}
Add-Table $certs @("Thumbprint","Services","Subject","FriendlyName","NotBefore","NotAfter","Status") "Exchange Certificates"
# 5) Domeinen & Policies
Next-Section
Show-Progress "Accepted/Remote domains, EAP..."
$accDom = Get-AcceptedDomain -ErrorAction SilentlyContinue | Select-Object Name,DomainName,DomainType,Default
$remDom = Get-RemoteDomain -ErrorAction SilentlyContinue | Select-Object Name,DomainName,AllowedOOFType,AutoReplyEnabled,AutoForwardEnabled
$emailp = Get-EmailAddressPolicy -ErrorAction SilentlyContinue | Select-Object Name,Priority,EnabledPrimaryAddressTemplate,RecipientFilter
Add-Table $accDom @("Name","DomainName","DomainType","Default") "Accepted Domains"
Add-Table $remDom @("Name","DomainName","AllowedOOFType","AutoReplyEnabled","AutoForwardEnabled") "Remote Domains"
Add-Table $emailp @("Name","Priority","EnabledPrimaryAddressTemplate","RecipientFilter") "Email Address Policies"
# 6) Transport & Mailflow
Next-Section
Show-Progress "Transportconfig, rules, connectors..."
try { $tconf = Get-TransportConfig -ErrorAction Stop } catch { $tconf = $null }
if($tconf){
$trow = [pscustomobject]@{
TLSReceiveDomainSecureList = ($tconf.TLSReceiveDomainSecureList -join ",")
TlsSendDomainSecureList = ($tconf.TLSSendDomainSecureList -join ",")
ExternalDNSAdapterEnabled = $tconf.ExternalDNSAdapterEnabled
InternalSMTPServers = ($tconf.InternalSMTPServers -join ",")
MaxSendSize = $tconf.MaxSendSize
MaxReceiveSize = $tconf.MaxReceiveSize
}
Add-Table $trow @("TLSReceiveDomainSecureList","TlsSendDomainSecureList","ExternalDNSAdapterEnabled","InternalSMTPServers","MaxSendSize","MaxReceiveSize") "Transport Config"
}
$trules = Get-TransportRule -ErrorAction SilentlyContinue | Select-Object Name,Priority,State,Mode
Add-Table $trules @("Name","Priority","State","Mode") "Transport Rules"
$send = Get-SendConnector -ErrorAction SilentlyContinue | Select-Object Name,AddressSpaces,Enabled,SmartHosts,UseExternalDNSServersEnabled,SourceTransportServers
$recv = Get-ReceiveConnector -ErrorAction SilentlyContinue | Select-Object Server,Name,TransportRole,Bindings,RemoteIPRanges,AuthMechanism,TlsDomainCapabilities,PermissionGroups
Add-Table $send @("Name","AddressSpaces","Enabled","SmartHosts","UseExternalDNSServersEnabled","SourceTransportServers") "Send Connectors"
Add-Table $recv @("Server","Name","TransportRole","Bindings","RemoteIPRanges","AuthMechanism","TlsDomainCapabilities","PermissionGroups") "Receive Connectors"
Export-IfEnabled $send "SendConnectors"
Export-IfEnabled $recv "ReceiveConnectors"
# 7) Databases & DAG's
Next-Section
Show-Progress "DAG's, DAG-netwerken, databases..."
$dag = Get-DatabaseAvailabilityGroup -ErrorAction SilentlyContinue | Select-Object Name,WitnessServer,WitnessDirectory,OperationalServers,Servers,AutoDagTotalNumberOfServers
Add-Table $dag @("Name","WitnessServer","WitnessDirectory","OperationalServers","Servers","AutoDagTotalNumberOfServers") "Database Availability Groups"
$dagNet = Get-DatabaseAvailabilityGroupNetwork -ErrorAction SilentlyContinue | Select-Object Name,Identity,Description,Subnets,ReplicationEnabled
Add-Table $dagNet @("Name","Identity","Description","Subnets","ReplicationEnabled") "DAG Networks"
$dbs = Get-MailboxDatabase -Status -ErrorAction SilentlyContinue | Sort-Object Name | Select-Object Name,Server,EDBFilePath,LogFolderPath,Mounted,CircularLoggingEnabled,DatabaseSize,AvailableNewMailboxSpace,Recovery
Add-Table $dbs @("Name","Server","EDBFilePath","LogFolderPath","Mounted","CircularLoggingEnabled","DatabaseSize","AvailableNewMailboxSpace","Recovery") "Mailbox Databases"
# 8) Database Copies
Next-Section
Show-Progress "Database copies ophalen... 0%"
$dbCopies = @()
if($dbs){
$dbCount = $dbs.Count
$lastTick = Get-Date
for($i=0; $i -lt $dbCount; $i++){
$db = $dbs[$i]
$copies = Get-MailboxDatabaseCopyStatus $db.Name -ErrorAction SilentlyContinue
$m = Get-MailboxDatabase $db.Name -ErrorAction SilentlyContinue
foreach($c in $copies){
$mbxSrv = $c.Name.Split("\")[-1]
$dbCopies += [pscustomobject]@{
Database = $db.Name
MailboxServer = $mbxSrv
Status = $c.Status
ContentIndexState = $c.ContentIndexState
CopyQueueLength = $c.CopyQueueLength
ReplayQueueLength = $c.ReplayQueueLength
LastInspectedLogTime = $c.LastInspectedLogTime
ActivationPreference = ($m.ActivationPreference | Where-Object {$_.Key -eq $mbxSrv}).Value
}
}
if(((Get-Date) - $lastTick).TotalMilliseconds -ge $ProgressThrottleMs){
$pct = [int](($i+1)/$dbCount*100)
Show-Progress ("Database copies... {0}/{1}" -f ($i+1), $dbCount) $pct
$lastTick = Get-Date
}
}
}
Add-Table $dbCopies @("Database","MailboxServer","Status","ContentIndexState","CopyQueueLength","ReplayQueueLength","LastInspectedLogTime","ActivationPreference") "Database Copies"
# 9) Mailboxen (on-prem) filter Remote* en vereis Database
Next-Section
Show-Progress "Mailboxen (on-prem) ophalen..."
$mailboxes = Get-Mailbox -ResultSize Unlimited -ErrorAction SilentlyContinue |
Where-Object { $_.RecipientTypeDetails -notmatch "^Remote" -and $_.Database } |
Select-Object DisplayName,PrimarySmtpAddress,RecipientTypeDetails,Database,ArchiveStatus,RetentionPolicy, LitigationHoldEnabled,WhenCreated
Add-Heading "Mailboxen (on-premises)" 2
# 10) Mailbox Stats (per database, voorkomt prompt om Identity)
Next-Section
Show-Progress "Mailbox statistics per database..."
$mbStats = @()
$statsDbs = ($mailboxes.Database | Sort-Object -Unique)
$sdCount = ($statsDbs | Measure-Object).Count
if($sdCount -gt 0){
$idx=0; $lastTick = Get-Date
foreach($dbName in $statsDbs){
try{
Get-MailboxStatistics -Database $dbName -ErrorAction SilentlyContinue |
Select-Object DisplayName,Database,TotalItemSize,ItemCount,LastLogonTime,LastLoggedOnUserAccount |
ForEach-Object { $mbStats += $_ }
} catch {}
$idx++
if(((Get-Date) - $lastTick).TotalMilliseconds -ge $ProgressThrottleMs){
Show-Progress ("Mailbox statistics... {0}/{1} databases" -f $idx,$sdCount) ([int]($idx/$sdCount*100))
$lastTick = Get-Date
}
}
}
# 11) Mailbox Join
Next-Section
Show-Progress "Mailbox eigenschappen koppelen..."
$sizeLookup = @{}
foreach($s in $mbStats){ $sizeLookup[$s.DisplayName] = $s }
$mbForTop = @()
if($mailboxes){
$mbCount = $mailboxes.Count
$k=0; $lastTick = Get-Date
foreach($m in $mailboxes){
$s = $sizeLookup[$m.DisplayName]
$mbForTop += [pscustomobject]@{
DisplayName = $m.DisplayName
PrimarySmtpAddress = $m.PrimarySmtpAddress
RecipientTypeDetails = $m.RecipientTypeDetails
Database = $m.Database
ArchiveStatus = $m.ArchiveStatus
RetentionPolicy = $m.RetentionPolicy
LitigationHoldEnabled= $m.LitigationHoldEnabled
TotalItemSize = if($s){ $s.TotalItemSize } else { $null }
ItemCount = if($s){ $s.ItemCount } else { $null }
LastLogonTime = if($s){ $s.LastLogonTime } else { $null }
}
$k++
if($mbCount -gt 0 -and ((Get-Date) - $lastTick).TotalMilliseconds -ge $ProgressThrottleMs){
Show-Progress ("Join mailboxen... {0}/{1}" -f $k,$mbCount) ([int]($k/$mbCount*100))
$lastTick = Get-Date
}
}
}
# 12) Benodigde mailboxruimte (excl. whitespace) totaal en per database
Next-Section
Show-Progress "Benodigde mailboxruimte berekenen..."
# Helper: haal bytes op uit TotalItemSize veilig
function Get-BytesFromSize($tis){
try {
if($null -eq $tis){ return 0 }
if($tis.Value -and ($tis.Value.PSObject.Properties.Name -contains "ToBytes")){
return [int64]$tis.Value.ToBytes()
}
# fallback op string parse (alleen indien nodig)
$s = [string]$tis
if($s -match "([\d\.,]+)\s*(KB|MB|GB|TB)"){
$num = ($matches[1] -replace '\.','.' -replace ',','.')
switch($matches[2].ToUpper()){
"KB" { return [double]$num * 1KB }
"MB" { return [double]$num * 1MB }
"GB" { return [double]$num * 1GB }
"TB" { return [double]$num * 1TB }
}
}
} catch { }
return 0
}
# Totaal bytes
$totalBytes = 0
foreach($row in $mbForTop){
$totalBytes += [int64](Get-BytesFromSize $row.TotalItemSize)
}
$totalGB = [math]::Round($totalBytes / 1GB, 2)
# Per database aggregatie
$byDb = @()
$groups = $mbForTop | Group-Object -Property Database
foreach($g in $groups){
$b = 0
foreach($m in $g.Group){ $b += [int64](Get-BytesFromSize $m.TotalItemSize) }
$byDb += [pscustomobject]@{
Database = $g.Name
MailboxCount = $g.Count
TotalGB = [math]::Round($b / 1GB, 2)
}
}
$totalsRow = [pscustomobject]@{
TotaalMailboxen = ($mbForTop | Measure-Object).Count
TotaleInhoudGB = $totalGB
}
Add-Table $totalsRow @("TotaalMailboxen","TotaleInhoudGB") "Werkelijke benodigde mailboxruimte (excl. whitespace)"
Add-Table ($byDb | Sort-Object -Property Database) @("Database","MailboxCount","TotalGB") "Benodigde mailboxruimte per database"
Export-IfEnabled ($mbForTop | Select-Object DisplayName,PrimarySmtpAddress,Database,TotalItemSize,ItemCount) "MailboxContentSizes"
Export-IfEnabled ($byDb | Sort-Object Database) "MailboxContentPerDatabase"
# 13) Samenvatting + Top Mailboxen
Next-Section
Show-Progress "Samenvatting mailboxen..."
$sum = [pscustomobject]@{
TotaalMailboxen = ($mailboxes | Measure-Object).Count
MetArchive = ($mailboxes | Where-Object {$_.ArchiveStatus -ne "None"} | Measure-Object).Count
LitigationHold = ($mailboxes | Where-Object {$_.LitigationHoldEnabled} | Measure-Object).Count
UniekeDatabases = (($mailboxes.Database | Sort-Object -Unique) -join ", ")
}
Add-Table $sum @("TotaalMailboxen","MetArchive","LitigationHold","UniekeDatabases") "Samenvatting (on-prem)"
function Parse-Size([string]$val){
if(-not $val){ return 0 }
if($val -match "([\d\.,]+)\s*(KB|MB|GB|TB)"){
$num = ($matches[1] -replace '\.','.' -replace ',','.')
switch($matches[2].ToUpper()){
"KB" { return [double]$num * 1KB }
"MB" { return [double]$num * 1MB }
"GB" { return [double]$num * 1GB }
"TB" { return [double]$num * 1TB }
}
}
return 0
}
Next-Section
Show-Progress "Bepalen top mailboxen..."
$topSrc = $mbForTop | Where-Object { $_.TotalItemSize }
$topCount = ($topSrc | Measure-Object).Count
$mbTop = @()
if($topCount -gt 0){
$sorted = $topSrc | Sort-Object @{Expression={ Parse-Size ([string]$_.TotalItemSize) };Descending=$true}
$chunks = 10
for($c=1;$c -le $chunks;$c++){
Start-Sleep -Milliseconds ([Math]::Min($ProgressThrottleMs,150))
Show-Progress ("Sorteren... stap {0}/{1}" -f $c,$chunks) ([int]($c/$chunks*100))
}
$mbTop = $sorted | Select-Object -First $TopMailboxCount
}
Add-Table $mbTop @("DisplayName","PrimarySmtpAddress","Database","TotalItemSize","ItemCount","LastLogonTime","ArchiveStatus","RetentionPolicy","LitigationHoldEnabled") ("Top " + $TopMailboxCount + " grootste mailboxen (on-prem)")
Export-IfEnabled $mbForTop "AllMailboxes" # on-prem only
Export-IfEnabled $mbTop "TopMailboxes" # on-prem only
# 14) Retention & Compliance
Next-Section
Show-Progress "Retention policies & tags..."
$rets = Get-RetentionPolicy -ErrorAction SilentlyContinue | Select-Object Name,RetentionPolicyTagLinks,IsDefault
$tags = Get-RetentionPolicyTag -ErrorAction SilentlyContinue | Select-Object Name,Type,RetentionAction,AgeLimitForRetention,RetentionEnabled
$holds= Get-Mailbox -ResultSize Unlimited -Filter "LitigationHoldEnabled -eq 'True'" -ErrorAction SilentlyContinue | Select-Object DisplayName,PrimarySmtpAddress,WhenCreated
Add-Table $rets @("Name","RetentionPolicyTagLinks","IsDefault") "Retention Policies"
Add-Table $tags @("Name","Type","RetentionAction","AgeLimitForRetention","RetentionEnabled") "Retention Tags"
Add-Table $holds @("DisplayName","PrimarySmtpAddress","WhenCreated") "Mailboxen met Litigation Hold"
# 15) Mobile Device Policies
Next-Section
Show-Progress "Mobile Device policies..."
$mdpol = Get-MobileDeviceMailboxPolicy -ErrorAction SilentlyContinue | Select-Object Name,AllowNonProvisionableDevices,AlphanumericDevicePasswordRequired,MaxInactivityTimeDeviceLock,MinDevicePasswordLength,PasswordRecoveryEnabled
Add-Table $mdpol @("Name","AllowNonProvisionableDevices","AlphanumericDevicePasswordRequired","MaxInactivityTimeDeviceLock","MinDevicePasswordLength","PasswordRecoveryEnabled") "Mobile Device Policies"
# 16) Addressing (AL/GAL/OAB)
Next-Section
Show-Progress "Address lists, GAL, OAB..."
$als = Get-AddressList -ErrorAction SilentlyContinue | Select-Object Name,RecipientFilter
$gals= Get-GlobalAddressList -ErrorAction SilentlyContinue | Select-Object Name,RecipientFilter
$oab = Get-OfflineAddressBook -ErrorAction SilentlyContinue | Select-Object Name,AddressLists,IsDefault
Add-Table $als @("Name","RecipientFilter") "Address Lists"
Add-Table $gals @("Name","RecipientFilter") "Global Address Lists"
Add-Table $oab @("Name","AddressLists","IsDefault") "Offline Address Books"
# 17) Public Folders
Next-Section
Show-Progress "Public Folders..."
try {
$pfmailboxes = Get-Mailbox -PublicFolder -ErrorAction SilentlyContinue | Select-Object Name,Database,IsRootPublicFolderMailbox
if($pfmailboxes){ Add-Table $pfmailboxes @("Name","Database","IsRootPublicFolderMailbox") "Public Folder Mailboxes (Modern PFs)" }
} catch { }
try {
$pfdb = Get-PublicFolderDatabase -ErrorAction SilentlyContinue | Select-Object Name,Server,DeletedItemRetention,DistinguishedName
if($pfdb){ Add-Table $pfdb @("Name","Server","DeletedItemRetention","DistinguishedName") "Public Folder Database (Legacy)" }
} catch { }
# 18) Throttling Policies
Next-Section
Show-Progress "Throttling policies..."
$throt = Get-ThrottlingPolicy -ErrorAction SilentlyContinue | Select-Object Name,IsDefault,GlobalThrottlingPolicy
Add-Table $throt @("Name","IsDefault","GlobalThrottlingPolicy") "Throttling Policies"
# 19) Hybrid / Federation / OAuth
Next-Section
Show-Progress "Hybrid/Federation/OAuth..."
try { $fed = Get-FederationTrust -ErrorAction SilentlyContinue | Select-Object Name,ApplicationUri,TokenIssuerUri,OrgContact } catch { $fed = $null }
try { $auth= Get-AuthConfig -ErrorAction SilentlyContinue | Select-Object ServiceName,Issuer,EvoStsMetadataUrl,CurrentCertificateThumbprint,PreviousCertificateThumbprint } catch { $auth = $null }
try { $hyb = Get-HybridConfiguration -ErrorAction SilentlyContinue | Select-Object Features,Domains,ClientAccessServers,ReceivingTransportServers,SendingTransportServers } catch { $hyb = $null }
if($fed){ Add-Table $fed @("Name","ApplicationUri","TokenIssuerUri","OrgContact") "Federation Trust" }
if($auth){ Add-Table $auth @("ServiceName","Issuer","EvoStsMetadataUrl","CurrentCertificateThumbprint","PreviousCertificateThumbprint") "OAuth Config" }
if($hyb){ Add-Table $hyb @("Features","Domains","ClientAccessServers","ReceivingTransportServers","SendingTransportServers") "Hybrid Configuration" }
# 20) Mail Queues
Next-Section
Show-Progress "Mail queues per server..."
$qdata = foreach($sv in $servers){
try{
$qs = Get-Queue -Server $sv.Name -ErrorAction Stop | Measure-Object -Property MessageCount -Sum
[pscustomobject]@{ Server=$sv.Name; TotalQueued=$qs.Sum }
} catch {
[pscustomobject]@{ Server=$sv.Name; TotalQueued="N/A" }
}
}
Add-Table $qdata @("Server","TotalQueued") "Queue per server"
# 21) CSV Export
Next-Section
Show-Progress "CSV export..."
Export-IfEnabled $dbs "MailboxDatabases"
Export-IfEnabled $serversOut "Servers"
Export-IfEnabled $dbCopies "DatabaseCopies"
# 22) Afronden
Next-Section
Show-Progress "Rapport opslaan..."
End-Report
Write-Host ("[{0}] Inventarisatie gereed. Map: {1}" -f (NowTag), $OutputFolder)