Postfach Statistiken bei Migrationen

Ich migriere immer noch Postfächer zwischen Servern und nutze dazu natürlich Get-Mailbox und Get-Mailboxstatistics, um die Migration zu planen und den Umzug zu kontrollieren. Dabei sollten sie aber ein paar Fallstricke kennen, denn nicht immer sind die Werte wie erwartet. Wenn sie Postfächer von einem Server zum anderen Server oder von einer Datenbank in eine andere Datenbank verlagern, dann könnten Sie ja einfach einen Befehl auslösen wie:

Get-Mailbox -database <quelldb> | new-moverequest -targetdatabase <tagetdb>

Damit sollten sie relativ schnell alle Postfächer von einer Datenbank in eine andere Datenbank verlagern. Schon bei dem einfachen Befehl sind zwei Dinge zu beachten:

  • Nur Postfächer, aber es gibt noch mehr
    Spätestens, wenn Sie die anscheinend leere Datenbank löschen wollen, wird Exchange ihnen sagen, dass dies nicht geht. Es gibt noch Datenbanken für Monitoring, Arbitration, PublicFolder, Auditlog, die nicht angezeigt und damit nicht verschoben wurden.  Zum Glück können Sie die Monitoring-Postfächer einfach mit einem "Get-Mailbox -Database <dbname> -monitoring | Disable-Mailbox" entfernen und die anderen können aufgrund der geringen Datenmenge schnell nachmigriert werden.
  • Vorsicht Archive
    Der Befehl "New-MoveRequest" kennt die Parameter "-PrimaryOnly" und "-ArchiveOnly". Wenn Sie diese nicht nutzen, dann werden beide Postfächer in die gleiche Zieldatenbank verlagert. In der Regel ist dies aber nicht erwünscht, denn die Archive landen meist auf langsameren aber größeren Disks mit angepassten Backupplänen.

Get-Mailbox

Bei einer Migration möchte ich natürlich wissen, wie viele Postfächer noch auf einer Datenbank sind. Auch bei einer alternativen Auswahl der Postfächer können Sie verschiedene Befehle verwenden. Sie könnten nun annehmen, dass alle drei Befehle die gleiche Anzahl an Postfächern der Datenbank anzeigen und sich nur in den unterschiedlichen Properties unterscheiden.

#Der folgende Befehl liefert keine Mailbox, obwohl es keinen Fehler gibt.
# Das liegt aber daran dass als Wert nicht der kurze Name sondern der Distinguishedname gefordert wird.
# Siehe auch https://learn.microsoft.com/en-us/powershell/exchange/filter-properties?view=exchange-ps#database 
(Get-Mailbox -Filter {Database -eq "DB01"}).count
0

# Dieser Abruf liefert die primären Postfächer in der Datenbank, aber ist natürlich langsam
(Get-Mailbox | where {$_.database -eq DB01}).count
3

# Besser ist der Aufruf mit der Datenbank als Parameter
(Get-Mailbox -database DB01).count
3

Sie könnten nun annehmen, dass in der Datenbank noch drei Postfächer drin sind, die sie mal schnell migrieren können. Dazu frage ich zur Sicherheit noch einmal die Größe der Postfächer ab.

(Get-Mailboxstatistics -database <dbname>).count
251

Das ist schon einmal eine große Differenz, die ich mir erst erklären musste. Auf der Suche nach den Differenzen habe ich mich daran erinnert, dass es noch jede Menge Spezial-Postfächer gibt, die man bei Get-Mailbox extra über einen Parameter extra angeben muss. Leider gibt es kein "-all"-Parameter.

# So erhalte ich die Anzahl der Archivpostfächer in der Datenbank
(Get-Mailbox -database DB01 -archive).count
2

# So erhalte ich die Anzahl der Archivpostfächer in der Datenbank
(Get-Mailbox -database DB01 -Monitoring).count
7

# Weitere Spezialpostfaecher
(Get-Mailbox -database DB01 -Arbitration).count
0

# Weitere Spezialpostfaecher
(Get-Mailbox -database DB01 -Auditlog).count
0

# Weitere Spezialpostfaecher
(Get-Mailbox -database DB01 -PublicFolder).count
0

Wenn ich die einzelnen Werte addiere, dann komme ich aber auf 3+2+7 = 12 Postfächer. Das sind mehr, aber nimmer noch nicht genug. 

Get-MailboxStatistics

Also musste ich mir die 251 Einträge von "Get-MailboxStatistics" mal genauer anschauen, was dies für Postfächer sein sollen, indem ich die zurückgegebenen Werte verglichen habe. Sehr schnell habe ich einige Objekte gefunden, die ganz sicher schon auf andere Datenbanken verschoben wurden. Aber in der Ausgabe von "Get-MailboxStatistics" waren sie noch drin. Allerdings haben Sie sich von den regulären Einträgen durch eine "DisconectReason" unterschieden.

# Anzeige der Mailboxen nach ihrem Status in der Datenbank
Get-MailboxStatistics -database DB01 | Group-Object DisconnectReason -NoElement

Count    Name
-----    ---------
 22
226      Softdeleted
  3      Disabled

Ich habe hier auch wieder die Gesamtzahl von 251 Einträgen und drei Postfächer wurden wohl durch ein "Disable-Mailbox" gelöscht und die anderen 226 Postfächer wurden schon mit einem MoveRequest verschoben. Exchange behält auch verschobene und gelöschte Postfächer für normalerweise 30 Tage in der Datenbank und diese Einträge finden wir hier.

Achtung: Sie sehen hier aber auch nur Postfacheinträge, die angelegt wurden, d.h. der Anwender muss sich einmal angemeldet haben. Dies ist keine zuverlässige Aussage um Datenbanken zu löschen, nur weil es keine Einträge mit "DisconnectReason -eq $null) gibt.

Es bleiben aber immer noch 22 Postfächer übrig, was deutlich mehr als 3 ist. Leider kann ich nicht nach "DisconnectReason" über das "-Filter"-Property filtern aber nach "DisconnectDate".

# Nur verbundene Postfächer zählen
(Get-MailboxStatistics -database DB01 -Filter {DisconnectDate -eq $null}).count
22

Das gleiche Ergebnis erhalte ich auch mit Where was aber länger dauert

(Get-MailboxStatistics -database DB01 | Where {$_.DisconnectDate -eq $null}).count
22

(Get-MailboxStatistics -database DB01 | Where {$_.DisconnectReason -eq $null}).count
22

Die daraus resultierenden 22 Postfächer habe ich mit noch einmal anders angeschaut:

Get-MailboxStatistics -database DB01 | Where {$_.DisconnectDate -eq $null} | group MailboxTypeDetail

Count Name
----- -----
   16 UserMailbox
    6 SharedMailbox

Ein "Get-Mailbox -Shared" gibt es aber nicht. Ich habe mir dann die Liste gefiltert und gegen prüfen lassen.

Get-MailboxStatistics -database DB01 `
   | Where {$_.DisconnectDate -eq $null -and $_.MailboxTypeDetail.value -eq "SharedMailbox"} `
   | foreach {Get-Mailbox $_} `
   | format-table Displayname,Database

Achtung: Auch hier muss ich beim "Where" auf den "Value" von "MailboxTypeDetail" gehen. Die Ausgabe auf dem Bildschirm ist ein lesbarer Sting aber beim Vergleichen passt es sonst nicht. Alternativ könnten Sie ein ".tostring()" nutzen.

Von den 6 Shared Mailboxen waren 5 auch wirklich auf der DB01 und eine konnte die PowerShell nicht finden. Ich habe dann die Gegenprobe mit den anderen 16 "Usermailboxen" gemacht.

Get-MailboxStatistics -database DB01 `
   | Where {$_.DisconnectDate -eq $null -and $_.MailboxTypeDetail.value -eq "UserMailbox"} `
   | foreach {Get-Mailbox $_} `
   | format-table Displayname,Database

Von den 16 Postfächern der Datenbank, die kein Disconnectdate haben, konnte nur 5 real gefunden werden, die auch alle auf der richtigen Datenbank DB01 lagen.

Ein Vergleich der Displaynamen von "GetMailboxstatistics" und Get-Mailbox lieferte die zusätzlichen Postfächer in der Datenbank. Die Displaynamen führen dann zur Lösung weiterer Unstimmigkeiten:

In-Place Archive -Healthmailbox-EX2016-003
In-Place Archive -Healthmailbox-EX2016-004
In-Place Archive -Healthmailbox-EX2016-DB01
In-Place Archive -Healthmailbox-EX2016-DB02
Healthmailbox-EX2016-DB02

Auch Health-Mailboxen können ein Archiv haben bzw. vermutlich legt Exchange dies absichtlich so an, damit der Healthcheck auch die Archivfunktion überprüfen kann.

Größen ermitteln

Nun wissen wir, wie wir an die Anzahl der Postfächer in einer Datenbank kommen. Aber damit wissen wir noch nicht, wie große die Datenbanken sind. Damit aber im Ziel die Festplatten nicht überlaufen, z.B. weil das Backup die Transaktionsprotokolle nicht abschneiden kann, sollten wir auch hier eine Abschätzung vornehmen. Folgende Zeilen helfen mir dabei pro Datenbank.

Get-Mailboxdatabase DB01 `
| Foreach {Get-Mailbox -Database $_} `
| Get-MailboxStatistics `
| Foreach {$_.totalitemsize.value.tomb()} `
| Measure-Object -sum

Damit bekomme ich natürlich nur die primären Postfächer der Datenbanken. Für das Archiv muss ich das noch mal gesondert machen.

Get-Mailboxdatabase DB01 `
| Foreach {Get-Mailbox -Database $_ -Archive} `
| Get-MailboxStatistics -Archive `
| Foreach {$_.totalitemsize.value.tomb()} `
| Measure-Object -sum

Achten Sie darauf, dass Sie beide Male den Parameter "-Archive" anfügen, um nicht nur die Archivpostfächer in der Datenbank sondern auch die Größe der Archivpostfächer in der Datenbank erhalten.

Auch hier der Hinweis: Ich sehe nur Postfächer, die sich schon einmal angemeldet haben. Wer aber sich noch nie angemeldet hat, belegt ja auch kein Platz und muss auch nicht aufwändig migriert werden. Wer letztlich in der Datenbank aktiv ist, bekommen Sie m

Get-Mailboxdatabase -Status

Auch die Abfrage der Postfachdatenbank selbst liefert einige Informationen, z.B. die Pfade, die Quotas und mit dem Parameter "-Status" auch die Datenbankgröße anzeigen lassen.

Get-Mailboxdatabase -Status | format-table name,DatabaseSize
Name  DatabaseSize
----  ----------------
DB01  263.4 GB (282,796,752,896 bytes)
DB02  385 GB (413,390,602,240 bytes)
DB03  1.039 TB (1,142,595,518,464 bytes)

Leider war bei mir das Property "DatabaseSize" ein String in diesem unglücklichen Format, welche sich nicht mal eben mit ".toGB()" umrechnen lässt.

Wir finden über diesen Weg aber keine Information über die Anzahl der Postfächer und auch die Größenabgaben sind mit Vorsicht zu genießen, denn wenn Sie z.B. ein Postfach löschen oder verschieben, dann bleibt es per Default noch 30 Tage in der Datenbank und auch danach wird der Platz zwar freigegeben, aber die EDB-Datei wird natürlich nicht kleiner.

Insofern ist diese Auswertung für eine Migration nur hinsichtlich der Zieldatenbanken nützlich, um zu sehen, wo Postfächer hin verschoben werden können um eine "balanciert" Datenbank zu erreichen.

Get-Recipient

Auch der Versuch auf "Ger-Recipient" auszuweichen, hat nicht wirklich geholfen. Mit einem "-Verbose" sehen wir zwar den umfangreichen Filter, aber mehr auch nicht

Get-Recipient -Database DB01 -Verbose

VERBOSE: [16:09:34.328 GMT] Get-Recipient : Searching objects of type "ReducedRecipient" with filter "
(&((|((RecipientTypeDetails Equal RoomMailbox)
(RecipientTypeDetails Equal LinkedRoomMailbox)
(RecipientTypeDetails Equal EquipmentMailbox)
(RecipientTypeDetails Equal SchedulingMailbox)
(RecipientTypeDetails Equal LegacyMailbox)
(RecipientTypeDetails Equal LinkedMailbox)
(RecipientTypeDetails Equal UserMailbox)
(RecipientTypeDetails Equal MailContact)
(RecipientTypeDetails Equal DynamicDistributionGroup)
(RecipientTypeDetails Equal MailForestContact)
(RecipientTypeDetails Equal MailNonUniversalGroup)
(RecipientTypeDetails Equal MailUniversalDistributionGroup)
(RecipientTypeDetails Equal MailUniversalSecurityGroup)
(RecipientTypeDetails Equal RoomList)
(RecipientTypeDetails Equal MailUser)
(RecipientTypeDetails Equal GuestMailUser)
(RecipientTypeDetails Equal DiscoveryMailbox)
(RecipientTypeDetails Equal PublicFolder)
(RecipientTypeDetails Equal TeamMailbox)
(RecipientTypeDetails Equal SharedMailbox)
(RecipientTypeDetails Equal RemoteUserMailbox)
(RecipientTypeDetails Equal RemoteRoomMailbox)
(RecipientTypeDetails Equal RemoteEquipmentMailbox)
(RecipientTypeDetails Equal RemoteTeamMailbox)
(RecipientTypeDetails Equal RemoteSharedMailbox)
)
)
(Database Equal DBX011)
)
)", scope "SubTree" under the root "$null".

Der ebenfalls ausgegebene LDAP-Filter zeigt was alles ausgeschlossen wird:

VERBOSE: [16:09:34.328 GMT] Get-Recipient : Request filter in Get Task:
(&(mailNickname=*)
  (|(objectCategory=person)
    (objectCategory=msExchDynamicDistributionList)
    (objectCategory=group)
    (objectCategory=publicFolder)
  )
  (!(msExchRecipientTypeDetails=8388608))
  (!(msExchRecipientTypeDetails=16777216))
  (!(msExchRecipientTypeDetails=4398046511104))
  (!(msExchRecipientTypeDetails=70368744177664))
  (!(msExchRecipientTypeDetails=140737488355328))
  (homeMDB=<GUID=f6c45511-4d340-4112-affe-ae4ff704ad23>)
  (!(msExchCU=*))
  (|(&(msExchVersion<=2251799813685248)
      (!(msExchVersion=2251799813685248))
    )
    (!(msExchVersion=*))
  )
).

Hier sehen Sie auch, dass die Datenbank in die GUID statt dem DN umgesetzt wird

Größe richtig auswerten

Ich habe daraus wieder gelernt, dass Sie ihre Ergebnisse besser genau prüfen und nicht einfach mal ein paar Befehle aneinanderketten. "Get-Mailboxstatistics" liefert zu einer Datenbank nicht nur die regulären Postfächer, die für eine Migration relevant sind, sondern auch früher vorhandene und schon verschobene oder gelöschte Postfächer als auch Systempostfächer. Wenn ich einen echten Einblick in die realen Benutzer einer Datenbank haben will, muss ich mit Get-Mailbox starten aber dies einmal für die primären Postfächer und einmal für die Archivpostfächer machen. Wenn ich dann noch die Größe ermitteln möchte, dann muss ich pro Postfach und pro Archivpostfach mit "Get-MailboxStatistics" arbeiten.

Ich kann natürlich auch einen schnellen Abruf aller Statistiken einer Datenbank machen, der dann aber viele Einträge und sowohl versteckte Systempostfächer als auch Archive liefert, z.B. mit:

Get-MailboxStatistics `
   -Database DB01 `
   -Filter {DisconnectDate -eq $null} `
| format-table Displayname, TotalItemSize, IsArchiveMailbox

Leider gibt es in der Liste aber kein Property, welches einen Rückschluss auf den genauen Postfachtyp erlaubt. Über den Displaynamen einer Archivmailbox kann ich nur unsicher auf das Postfach schließe, da das Prefix manchmal ein "In-Place Archive -" ist aber ich auch schon ein "In-Situ-Archiv -" gesehen habe. Also nutzen wir bessern den sicheren und eindeutigen Weg über das Postfach und erstellen uns eine komplette Liste mit folgenden Code:

Set-ADServerSettings -ViewEntireForest $true
Foreach ($mailbox in (Get-Mailbox -resultsize unlimited)) {
   $result = [pscustomobject][ordered]@{
                Displayname    = $mailbox.displayname
                MailboxDB      = $mailbox.database
                MailboxSizeMB  = -1
                ArchiveDB      = $mailbox.ArchiveDatabase
                ArchiveSizeMB  = -1
                ArchiveState   = $mailbox.ArchiveState
              }
   $result.MailboxSizeMB = (Mailboxstatistics -Identity $mailbox.identity).TotalItemSize.value.toMB()
   if ($mailbox.archivestate -eq "local") {
      $result.ArchiveSizeMB  = (Mailboxstatistics -Identity $mailbox.identity -archive).TotalItemSize.value.toMB()
   }
   $result
}

Das Ergebnis ist eine Tabelle, die Sie mit Export-CSV o.ä. als Datei speichern oder direkt weiter verarbeiten können. Auch eine direkte Ansicht mit "Out-GridView" ist möglich. Aber selbst dieses Skript liefert manchmal noch Fehlermeldungen, z.B. wenn Benutzer noch nie angemeldet waren und daher "Get-MailboxStatistics" nichts liefern kann.

Datenbankuser auflisten

Wenn ich hingegen alle AD-Objekte suchen möchte, die in einer bestimmten Datenbank sind, dann bleibt nur nur die LDAP-Suche nach dem "HomeMDB" und "msExchArchiveDatabaseLink"

[CMDLetbinding()]

param (
   $database = "*"
)

Write-Verbose "Get-DatabaseUsers: Start"
Write-Verbose " Loading nearest GC"
gc=((Get-ADDomainController -Discover -NextClosestSite -Service "GlobalCatalog").hostname)[0]
Write-Verbose " Using GC $($gc)"
 
Write-Verbose " Searching primary mailboxes"
Get-MailboxDatabase $database `
| %{`
   get-adobject `
      -SearchBase "" `
      -LDAPFilter "(HomeMDB=$($_.distinguishedname))"  `
      -Server "$($gc):3268" `
   }

Write-Verbose " Searching archiv mailboxes"
Get-MailboxDatabase $database  `
| %{get-adobject  `
      -SearchBase ""  `
      -LDAPFilter "(msExchArchiveDatabaseLink=$($_.distinguishedname))" `
      -Server "$($gc):3268" `
   }

Write-Verbose "Get-DatabaseUsers: End"

Wenn ein Benutzer natürlich ein primäres Postfach und das Archivpostfach hat, dann erscheint er doppelt.

Mit hilft das Skript aber schon schnell einen Überblick zu bekommen

Weitere Links