Report-MoveRequest

Wenn sie Postfächer mit einem "Remote-MoveRequest" in die Cloud verlagern, dann saugt sich Exchange Online die Daten aus dem lokalen Exchange Server. Dieses Skript erstellt einen Report über diesen Prozess.

Kleinere Firmen mit vielleicht bis zu 1000 Postfächern lassen sich recht problemlos auf einen Schlag in die Cloud umziehen. Das macht den Betrieb einfacher, da es keine "Cross Ort"-Zugriffe für Free/Busy-Zeiten, Stellvertreter oder öffentliche Ordner gibt. Die meisten Outlook Installationen schwenken alleine und nur die Mobilclients schaffen es nur mit etwas Hilfe durch Intune oder eine andere MDD-Lösung. Wenn Sie aber größere Datenmengen migrieren, dann wollen Sie schon sehen, ob die Daten auch "fließen". Dazu gibt es mehrere Wege.

Get-MoveRequest

Sie verbinden sich per PowerShell mit der Exchange Online Instanz und führen einfach Get-MoveRequest aus. Wenn Sie eine Migration per Browser im ECP einrichten, dann erstellen sie einen Migrationsbatch, in den Sie ein oder mehrere Postfächer zu einer Gruppe zusammenfassen. Im Hintergrund ist dennoch jedes Postfach ein eigener Migrationsjobs, der so aufgelistet wird:

PS C:> Get-Moverequest

User1   InProgress           DEUP281DG008-db049
User2   Synced               DEUP281DG060-db161
User3   Completed            DEUP281DG050-db074
User4   WaitJob              DEUP281DG052-db073
User5   WaitingForJobPickup  DEUP281DG008-db029
User6   Queued               DEUP281DG018-db019

Allerdings enthalten diese Ergebnisse keine Werte zur Postfachgröße sondern nur zum Benutzerobjekt und den Link zum Batch.

Get-MoveRequestStatistics

Wenn ich mehr Daten ermitteln will, muss ich ich mit "Get-MoveRequestStatistics" arbeiten.

Allerdings braucht der Befehl eine Identity, so dass die Aufrufe in der Regel mit einer Liste von Benutzern oder direkt dem "Get-MoveRequest" verbunden wird.

PS C:>$movestat = Get-MoveRequest | Get-MoveRequestStatistics
PS C:>$movestat

DisplayName   StatusDetail     TotalMailboxSize                TotalArchiveSize                PercentComplete
-----------   ------------     ----------------                ----------------                ---------------
User1         IncrementalSync  3.732 GB (4,006,675,714 bytes)  2.886 GB (3,098,322,773 bytes)  32
User2         IncrementalSync  18.09 GB (19,423,930,682 bytes) 16.5 GB (17,718,101,744 bytes)  82
User2         RetryBadItems    4.551 GB (4,886,650,597 bytes)  4.416 GB (4,741,831,330 bytes)  95
user3         Completed        20.04 GB (21,521,078,244 bytes) 19.13 GB (20,537,290,604 bytes) 100
User4         InitializingMove 757 MB (793,753,448 bytes)      619.4 MB (649,482,528 bytes)    10
User5         WorkItemPickup   21.45 GB (23,027,441,158 bytes) 17.97 GB (19,298,111,191 bytes) 00
User6         Synced           1.76 GB (1,889,575,334 bytes)   1.543 GB (1,657,314,442 bytes)  00

Die Liste ist nun schon aussagekräftiger und wir sehen hier insbesondere die Menge der zu migrierenden Daten und sogar die Ausgabe, wie viel Prozent schon geschafft sind. Mit diesen Daten kann ich doch schon einiges anfangen, da ich die gesamte Datenmenge aufaddieren und auch die bereits kopierten Datenmengen ermitteln kann. Die Größe des Postfachs kann ich in der Cloud nämlich noch nicht mit "Get-Mailboxstatistics" ermitteln, da es das Postfach ja noch nicht in der Cloud gibt. Ich müsste dann die Menge erst On-Premises einsammeln und matchen

Interessant sind aus "Get-MoveRequestStatistics" folgende Properties:

PS C:>$movestat[0]

QueuedTimestamp                          : 22.02.2022 10:10:25
StartTimestamp                           : 22.02.2022 10:11:09
LastUpdateTimestamp                      : 25.02.2022 21:15:55
LastSuccessfulSyncTimestamp              : 24.02.2022 21:15:38
InitialSeedingCompletedTimestamp         : 23.02.2022 21:13:30
FinalSyncTimestamp                       :
CompletionTimestamp                      :
SuspendedTimestamp                       :
OverallDuration                          : 00:00:40.8007850
TotalSuspendedDuration                   : 00:00:00
TotalFailedDuration                      : 00:00:00
TotalQueuedDuration                      : 00:00:00
TotalInProgressDuration                  : 00:00:40.8007850
TotalStalledDueToContentIndexingDuration : 00:00:00
TotalStalledDueToMdbReplicationDuration  : 00:00:00
TotalStalledDueToMailboxLockedDuration   : 00:00:00
TotalStalledDueToReadThrottle            : 00:00:00
TotalStalledDueToWriteThrottle           : 00:00:00
TotalStalledDueToReadCpu                 : 00:00:00
TotalStalledDueToWriteCpu                : 00:00:00
TotalStalledDueToReadUnknown             : 00:00:00
TotalStalledDueToWriteUnknown            : 00:00:00
TotalTransientFailureDuration            : 00:00:00
MRSServerName                            : FR0P281MB0882.DEUP281.PROD.OUTLOOK.COM
TotalMailboxSize                         : 18.09 GB (19,423,930,682 bytes)
TotalMailboxItemCount                    : 67924
TotalArchiveSize                         : 16.5 GB (17,718,101,744 bytes)
TotalArchiveItemCount                    : 62507
TotalPrimarySize                         : 1.589 GB (1,705,828,938 bytes)
TotalPrimaryItemCount                    : 5417
SyncedItemCount                          : 67853
BytesTransferred                         : 26.41 GB (28,353,304,327 bytes)
BytesTransferredPerMinute                : 10.56 MB (11,068,709 bytes)
ItemsTransferred                         : 70000
ShardBytesTransferred                    : 3.248 MB (3,406,038 bytes)
ShardItemsTransferred                    : 216
ArchiveBytesTransferred                  : 24.58 GB (26,393,320,477 bytes)
ArchiveItemsTransferred                  : 62510
PercentComplete                          : 95

Deserialized.Microsoft.Exchange.Data.ByteQuantifiedSize

Allerdings gibt es da noch kleines Problem der Exchange Remote PowerShell. Die Zahlen sind keine Zahlen, sondern "Strings"

PS C:\> $movestat[1].TotalMailboxSize
18.09 GB (19,423,930,682 bytes)
PS C:\> $movestat[1].TotalMailboxSize | gm


   TypeName: Deserialized.Microsoft.Exchange.Data.ByteQuantifiedSize

Name        MemberType Definition
----        ---------- ----------
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
ToString    Method     string ToString(), string ToString(st

On-Premises ist es völlig normal, dass die Properties vom Typ "Microsoft.Exchange.Data.ByteQuantifiedSize" sind und ich z.B. mit einem "ToGB()" einfach den Wert in Gigabyte konvertieren kann. Auch andre Werte sind möglich. Bei der Remote Powershell ist das aber leider nicht möglich, das die Werte "deserialisiert" werden:

Microsoft "dampft" quasi einige Ergebnisse auf Basistypen ein und nur primitive Typen werden 1:1 übertragen und beim Empfang wieder "hydriert". Das klingt fast wie Milchpulver und Wasser und ist auch etwa so. Wenn ich aber mit den Zahlen rechnen will, muss ich Sie konvertieren. Ich bekomme also einen String in der folgenden Schreibweise.

18.09 GB (19,423,930,682 bytes)

Wenn ich sicher bin, dass ich eine lokale Exchange PowerShell mit den "Microsoft.Exchange.Data"-DLLs habe, dann könnte ich den Werte einfach wieder parsen lassen.

$size = [Microsoft.Exchange.Data.ByteQuantifiedSize]::Parse($movestat[1].TotalMailboxSize)

Darauf kann ich mich aber besser nicht verlassen und nehme den String besser mal auseinander.

#Zuerst einmal bei der ersten Klammer trennen und dann die Einheit aufteilen
"18.09 GB (19,423,930,682 bytes)".split("(")[0].replace(" ","")
18.09GB

Wenn ich den Wert "18.09GB" einfach auf der Console eingebe, dann rechnet PowerShell den direkt in Bytes um

PS C:\> 18.09GB
19423989596,16

Alle Versuche das aber per "ToInt()", [math] o.ä auch mit einem String hinzubekommen, haben aber nicht gefruchtet. Am Ende konnte ich das aber unkonventionell mit einer Addition einer Zahl mit dem String erreichen:

PS C:\> $f="18.09GB"
PS C:\> $f.gettype()

IsPublic IsSerial Name      BaseType
-------- -------- ----      --------
True     True     String    System.Object

PS C:\> $f.toint64()
Für "toint64" und die folgende Argumenteanzahl kann keine Überladung gefunden werden: "0".

PS C:\> 0+$f
19423989596,16

So ganz funktioniert das aber noch nicht, denn "0B" wird so nicht konvertiert. Da hat die PowerShell wohl einen Fall nicht abgedeckt. Damit hatte ich aber eine Basis für weitere Berechnungen.

Status ermitteln

Mit den Vorarbeiten ist es nun natürlich einfach, z.B. die Gesamtmenge der zu migrierenden Postfächer zu ermiteln.

# Gesamtes Migrationsvolumen als Einzeiler
($movestat.TotalMailboxSize | %{0+ ($_).tostring().split("(")[0].replace(" ","")} | Measure-Object -Sum).sum/1GB

# Oder aufgesplittet
($movestat.TotalMailboxSize `
   | %{`
         0+ ($_).tostring().split("(")[0].replace(" ","")} `
   | Measure-Object -Sum`
).sum/1GB

Analog erhalte ich "BytesTransferred"

($movestat.BytesTransferred | %{0+ ($_).tostring().split("(")[0].replace(" ","")} | Measure-Object -Sum).sum/1GB

Wenn sie so eine Stichtagsmigration mit vielen Postfächern einige Tage "stehen" lassen, bis alle Postfächer an die 95% gekommen sind, dann repliziert Exchange ja zwischenzeitlich immer wieder die Änderungen. Die Summen aus "BytesTransferred" steigen dabei immer weiter an und sind damit regelmäßig höher als TotalMailboxSize.

Leider scheitert der Versuch aus dem Wert "BytesTransferredPerMinute" einen Schnitt zu ermitteln. Bei vielen auch abgeschlossenen Aufträgen steht hier ein "0B". Dafür könnte aber die Auswertung der Dauer aus dem Feld "TotalInProgressDuration" genutzt werden. Aber auch hier habe ich viele Jobs gefunden, bei denen dieser Wert auf "0" gesetzt war. Entsprechend muss der Code ein paar Abfragen tätigen, damit fehlende oder inkonsistente Daten keinen negativen Einfluss haben.

Wenn ein Eintrage einen Wert in "BytesTransferredPerMinute" hat, wird er genutzt. Ansonsten schaue ich mir an, ob in TotalInProgressDuration ein Wert ungleich 00:00:00 steht, um dann anhand des Status entweder die Postfachgröße oder doch die "BytesTransferred" zu nutzen.

# Report-MoveRequest
#
# Try to get some thoughput counters for Remote-MoveRequests

[cmdletbinding()]
param()

set-psdebug -strict

Write-Verbose "Collect MoveRequests"
$moverequests = get-moverequest
Write-Verbose "Total Requests found:  $($moverequests.count)"
Write-Verbose "Collect MoveRequests Statistics - May take some time"
$movestat = $moverequests | get-moverequeststatistics

Write-Verbose "Processing statistics"
foreach ($mailbox in $movestat) {
   Write-Verbose "----- Processing $($mailbox.identity)"
   $result = [pscustomobject][ordered]@{
      identity = $mailbox.identity
      TotalMailboxSizeMB = 0
      TotalArchiveSizeMB = 0
      duration = $mailbox.TotalInProgressDuration
      MBperMinute = -1
      PercentComplete = $mailbox.PercentComplete
      TotalMBToMigrate=-1
      TotalMBDone=-1
   }
   Write-verbose "TotalMailboxSize: $($mailbox.TotalMailboxSize.tostring())"
   if ($mailbox.TotalMailboxSize.tostring() -ne "0 B (0 bytes)") {
      $result.TotalMailboxSizeMB = [int]((0 + ($mailbox.TotalMailboxSize.tostring().split("(")[0].replace(" ","")))/1MB)
   }
   Write-verbose "TotalArchiveSize: $($mailbox.TotalArchiveSize.tostring())"
   if ($mailbox.TotalArchiveSize.tostring() -ne "0 B (0 bytes)") {
      $result.TotalArchiveSizeMB = [int]((0 + ($mailbox.TotalArchiveSize.tostring().split("(")[0].replace(" ","")))/1MB)
   }

   if ($mailbox.BytesTransferredPerMinute.tostring() -ne "0 B (0 bytes)") {
      $result.MBperMinute = [int]((0 + ($mailbox.BytesTransferredPerMinute.tostring().split("(")[0].replace(" ","")))/1MB)
      Write-Verbose "Rate from BytesTransferredPerMinute: $($result.BytesperMinute)"
   }
   elseif (($mailbox.TotalInProgressDuration.tostring() -ne "00:00:00") `
   -and ($mailbox."StatusDetail" -ne "Synced")) {
         Write-Verbose "Collect Mailboxsize and duration"
         $result.MBperMinute = ($result.TotalMailboxSizeMB + $result.TotalArchiveSizeMB)/$result.duration.totalminutes
         Write-Verbose "Calculated Rate from TotalInProgressDuration: $($result.MBperMinute)"
   }
   else {
      Write-Warning "Unable to calculate messagerate for Mailbox $($mailbox.identity)"
      Write-Verbose "Statusdetail            : $($mailbox.StatusDetail)"
      Write-Verbose "TotalInProgressDuration : $($mailbox.TotalInProgressDuration.tostring())"
   }
   $result.TotalMBToMigrate = $result.TotalMailboxSizeMB + $result.TotalArchiveSizeMB
   $result.TotalMBDone = [int]($result.TotalMBToMigrate * $result.PercentComplete / 100)
   $result
}

Leider sind die Ausgaben nun gar nicht so nach meiner Vorstellung. So gibt es Einträge, die halbwegs plausible Werte um einige Megabyte/Minute liefern aber auch einige Werte, die auf nicht ganz realistische 2 GB/Minute ansteigen. Wieder andere Werte haben gar keine Duration oder Durchsatz-Zahlen, obwohl alle Migrationsjobs auf 95% standen.

Die von Microsoft hier gelieferte Daten sind also eher ein grobes "Schätzeisen" und weniger eine passable Möglichkeit einer Durchsatzermittlung.

Daher habe ich noch die Spalten "TotalMBToMigrate" addiert, welche sich aus der Größe von "TotalMailboxSizeMB" und "TotalArchivSizeMB" ermittelt. Zudem errechnet sich das Feld "TotalMBDone" aus "TotalMBToMigrate" und dem PercentComplete-Wert. Da die Ausgabe in eine Pipeline geht, kann man so auch schön die Summen bilden.

Weitere Einschränkung

Dieser Code basierend auf "Get-MoveRequest" und "Get-MoveRequestStatistics" funktioniert natürlich nur für die Postfächer, die noch als MoveRequest vorhanden sind. Wenn Sie die Move-Requests schon mit Remove-Moverequest entfernt haben, dann sehen Sie dies hier nicht mehr. Dann allerdings könnten Sie in das Postfach schauen, in dem auch die letzten Move-Reports enthalten sind.

Get-MigrationUserStatistics `
   -Identity <MailboxID>
   -IncludeSkippedItems `
   -IncludeReport `
   -DiagnosticInfo Verbose `
| Export-Clixml C:\temp\AffectedMigUser1Stats.xml

Ergebnis

Meine Hoffnung, über PowerShell etwas genauer auf die Performance eines Remote-MoveRequest in die Cloud schaue zu können, musste ich leider aufgeben. Die Werte sind alle nicht wirklich stimmig und zuverlässig. Das mag auch daran liegen, dass in der Cloud natürlich mehrere MRS-Instanzen parallel die Postfächer migrieren, Inhalte kopieren und die Statusmeldungen eben nicht in Echtzeit aktualisiert werden. Das ist im Prinzip auch gar nicht erforderlich, da eine Migration im Hintergrund abläuft und selten zeitkritisch ist. Ich wollte aber schon abschätzen können, wie lange die Migration braucht und da wäre der Netzwerkdurchsatz natürlich ein guter Indikator.

Das geht aber nicht aus der Cloud, so dass ich auf der lokalen Firewall oder dem lokalen Exchange Server (MRSProxy) eine Auswertung ansetzen müsste. Der MRS selbst liefert ja auch Performance Counter (Siehe MoveRequest und MigrationBatch).

Bis dahin kann ich aus dem Moverequest einfach nur die Gesamtmenge der Daten jedes Postfachs mit der Prozentangabe multiplizieren und damit einen Rückschluss auf die schon migrierte Datenmenge liefern. Zumindest habe ich gelernt, dass die Mailboxgröße als "deserialized-Object" für weitere Berechnungen erst wieder konvertiert werden muss.

Weitere Links