593 lines
27 KiB
PowerShell
593 lines
27 KiB
PowerShell
<#
|
||
.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) |