Add Exchange/Exchange-Inventory.ps1

This commit is contained in:
2025-09-18 11:17:04 +02:00
parent 3bdece27ea
commit 79057c6661

View File

@ -0,0 +1,593 @@
<#
.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)