Migrazione EXO senza Remote Move
Come eseguire il provisioning controllato di mailbox Exchange locali come nuove mailbox Exchange Online vuote: backup PST, approvazione CSV, RemoteMailbox, sincronizzazione, convalida e rollback.
Un Hybrid Remote Move è il metodo normale per spostare una mailbox Exchange locale, inclusi i contenuti, in Exchange Online. Non tutte le organizzazioni consentono questo percorso di migrazione. Se una policy di sicurezza esclude i Remote Move, può essere giustificato un approccio deliberatamente diverso: la mailbox locale viene sottoposta a backup come PST, separata dall’utente AD sincronizzato e viene eseguito il provisioning di una nuova mailbox vuota in Exchange Online per lo stesso utente.
Questa procedura non è una migrazione di mailbox. Non trasferisce messaggi, calendari, regole o autorizzazioni nel cloud. Il PST funge esclusivamente da backup e in questo scenario non viene importato. Il processo è pertanto adatto solo se una mailbox di destinazione vuota è accettabile dal punto di vista aziendale e la perdita della configurazione attiva della mailbox è esplicitamente approvata.
Stato di destinazione e requisiti imprescindibili
Dopo il cutover, lo stesso utente AD continua a esistere. Tuttavia, localmente non viene più gestito come UserMailbox, SharedMailbox, RoomMailbox o EquipmentMailbox, bensì come RemoteMailbox. Dopo la sincronizzazione, questo oggetto rappresenta la nuova mailbox in Exchange Online.
Lo stato desiderato è il seguente:
- È stato eseguito un backup completo della mailbox locale in formato PST.
- La mailbox locale è disconnessa, ma non ancora eliminata definitivamente entro il periodo di conservazione configurato.
- L’utente AD esistente è abilitato come RemoteMailbox.
- L’indirizzo primario, gli alias e il vecchio
LegacyExchangeDNvengono mantenuti. - Entra Connect ha sincronizzato le modifiche.
- Per le mailbox utente è assegnato un piano di servizio Exchange Online.
- Exchange Online mostra una vera mailbox cloud e il flusso di posta termina lì.
Prima dell’avvio devono inoltre essere chiariti i seguenti aspetti:
- La condivisione PST è raggiungibile tramite UNC. Il gruppo
Exchange Trusted Subsystemdispone di autorizzazioni di lettura e scrittura. - L’account esecutore dispone del ruolo di gestione
Mailbox Import Export. - Il PST è esclusivamente il backup concordato; non è previsto alcun import successivo.
- Conservazione, Litigation Hold, eDiscovery e requisiti normativi sono verificati separatamente.
- Deleghe, Send-As, Send-on-Behalf, inoltri, regole della posta in arrivo, dispositivi mobili e accessi applicativi sono inventariati.
- Durante l’esportazione e la commutazione, i messaggi in entrata vengono trattenuti in modo controllato al gateway a monte. Utenti e applicazioni non devono più scrivere nella mailbox di origine.
- La conservazione del database delle mailbox locali copre la finestra di rollback.
Perché un elenco di approvazione CSV è indispensabile
Una pipeline diretta come Get-Mailbox | Disable-Mailbox è troppo rischiosa per questa procedura. Potrebbe includere anche mailbox di sistema, Discovery o mailbox non altrimenti approvate. Il processo seguente utilizza pertanto due approvazioni esplicite:
Action=CUTOVERdetermina quale riga può effettivamente essere convertita.PstVerified=YESconferma che il file di esportazione è stato verificato dal punto di vista tecnico e organizzativo.
Inizialmente viene creato solo l’inventario:
$CsvPath = "C:\Migration\mailboxes.csv"
$RemoteRoutingDomain = "contoso.mail.onmicrosoft.com"
Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails `
UserMailbox,SharedMailbox,RoomMailbox,EquipmentMailbox |
Sort-Object PrimarySmtpAddress |
ForEach-Object {
[pscustomobject]@{
Identity = $_.Identity
DisplayName = $_.DisplayName
PrimarySmtpAddress = $_.PrimarySmtpAddress.ToString()
Alias = $_.Alias
SourceType = $_.RecipientTypeDetails.ToString()
ArchiveStatus = $_.ArchiveStatus.ToString()
ServerName = $_.ServerName
Database = $_.Database.ToString()
RemoteRoutingAddress = "$($_.Alias)@$RemoteRoutingDomain"
Action = "REVIEW"
PstVerified = "NO"
}
} |
Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8
Il file viene quindi ripulito dal punto di vista aziendale. Solo alle mailbox effettivamente approvate viene assegnato Action=CUTOVER. Le mailbox di sistema e gli oggetti speciali non devono comparire in questo elenco.
Fase 1: eseguire il backup della mailbox primaria e dell’archivio come PST
New-MailboxExportRequest scrive solo su un percorso UNC. Per ogni mailbox viene generato un nome file univoco. Un archivio online attivo dell’Exchange locale viene esportato separatamente:
$CsvPath = "C:\Migration\mailboxes.csv"
$PstShare = "\\fileserver\exchange-pst$"
$BatchName = "EXO-NewMailbox-20260807"
$targets = Import-Csv $CsvPath | Where-Object Action -eq "CUTOVER"
foreach ($row in $targets) {
$safeName = ($row.PrimarySmtpAddress -replace '[^a-zA-Z0-9@._-]', '_')
$primaryPath = "$PstShare\$safeName.pst"
New-MailboxExportRequest `
-Mailbox $row.Identity `
-FilePath $primaryPath `
-Name "Primary-$safeName" `
-BatchName $BatchName
if ($row.ArchiveStatus -eq "Active") {
New-MailboxExportRequest `
-Mailbox $row.Identity `
-IsArchive `
-FilePath "$PstShare\$safeName-archive.pst" `
-Name "Archive-$safeName" `
-BatchName $BatchName
}
}
L’esportazione viene approvata solo quando ogni richiesta ha lo stato Completed:
Get-MailboxExportRequest -BatchName $BatchName |
Get-MailboxExportRequestStatistics -IncludeReport |
Format-Table DisplayName,Status,PercentComplete,FailureCode,Message -AutoSize
Get-MailboxExportRequest -BatchName $BatchName |
Where-Object Status -ne "Completed"
Devono inoltre essere verificati esistenza, dimensione, leggibilità, presa in carico dal backup e protezione degli accessi ai file. Solo dopo viene impostato PstVerified=YES per la relativa riga CSV.
Fase 2: salvare i dati della mailbox e gli attributi Exchange
Prima della prima modifica viene creato uno snapshot leggibile da una macchina per ogni mailbox. È più importante di uno screenshot, poiché alias, GUID e il LegacyExchangeDN possono essere ricostruiti esattamente in seguito:
$SnapshotPath = "C:\Migration\Snapshots"
New-Item -ItemType Directory -Path $SnapshotPath -Force | Out-Null
Import-Csv "C:\Migration\mailboxes.csv" |
Where-Object { $_.Action -eq "CUTOVER" -and $_.PstVerified -eq "YES" } |
ForEach-Object {
$mailbox = Get-Mailbox -Identity $_.Identity
$safeName = ($_.PrimarySmtpAddress -replace '[^a-zA-Z0-9@._-]', '_')
$mailbox |
Select-Object Identity,DistinguishedName,ExchangeGuid,ArchiveGuid,
RecipientTypeDetails,PrimarySmtpAddress,EmailAddresses,
LegacyExchangeDN,Alias,Database,ServerName |
Export-Clixml "$SnapshotPath\$safeName.xml"
}
Deleghe e inoltri richiedono esportazioni separate. Almeno le seguenti informazioni dovrebbero essere salvate separatamente:
$mailbox = Get-Mailbox -Identity user01@contoso.com
$mailbox | Format-List ForwardingAddress,ForwardingSmtpAddress,DeliverToMailboxAndForward
Get-MailboxPermission -Identity $mailbox.Identity
Get-ADPermission -Identity $mailbox.DistinguishedName
Get-InboxRule -Mailbox $mailbox.Identity
Get-CalendarProcessing -Identity $mailbox.Identity -ErrorAction SilentlyContinue
Queste configurazioni non vengono trasferite automaticamente alla nuova mailbox cloud.
Fase 3: disconnettere la mailbox locale e abilitare RemoteMailbox
Il cutover effettivo è breve, ma ha conseguenze rilevanti. Disable-Mailbox rimuove gli attributi Exchange dall’utente AD e disconnette la mailbox locale. I dati della mailbox restano disponibili come disconnected mailbox fino alla scadenza della conservazione del database. Subito dopo, Enable-RemoteMailbox abilita lo stesso utente AD per Exchange Online.
Lo script seguente elabora esclusivamente righe approvate due volte. Mantiene l’indirizzo SMTP primario, tutti gli indirizzi proxy esistenti e il vecchio LegacyExchangeDN come indirizzo X500. La voce X500 evita NDR nelle risposte a messaggi meno recenti o con voci di completamento automatico di Outlook obsolete.
$CsvPath = "C:\Migration\mailboxes.csv"
$PstShare = "\\fileserver\exchange-pst$"
$targets = Import-Csv $CsvPath |
Where-Object { $_.Action -eq "CUTOVER" -and $_.PstVerified -eq "YES" }
foreach ($row in $targets) {
$safeName = ($row.PrimarySmtpAddress -replace '[^a-zA-Z0-9@._-]', '_')
$pstPath = "$PstShare\$safeName.pst"
if (-not (Test-Path $pstPath)) {
throw "PST fehlt: $pstPath"
}
if ((Get-Item $pstPath).Length -eq 0) {
throw "PST ist leer: $pstPath"
}
$mailbox = Get-Mailbox -Identity $row.Identity -ErrorAction Stop
$primary = $mailbox.PrimarySmtpAddress.ToString()
$legacyDn = $mailbox.LegacyExchangeDN
if ($primary -ine $row.PrimarySmtpAddress) {
throw "Primaere SMTP-Adresse stimmt nicht mit der Freigabeliste ueberein: $($row.Identity)"
}
$preservedAddresses = @($mailbox.EmailAddresses | ForEach-Object ToString)
Disable-Mailbox -Identity $row.Identity -Confirm:$false
$enableParams = @{
Identity = $row.Identity
Alias = $row.Alias
PrimarySmtpAddress = $primary
RemoteRoutingAddress = $row.RemoteRoutingAddress
ACLableSyncedObjectEnabled = $true
}
switch ($row.SourceType) {
"SharedMailbox" { $enableParams.Shared = $true }
"RoomMailbox" { $enableParams.Room = $true }
"EquipmentMailbox" { $enableParams.Equipment = $true }
"UserMailbox" { }
default { throw "Nicht unterstuetzter Mailbox-Typ: $($row.SourceType)" }
}
Enable-RemoteMailbox @enableParams
$orderedAddresses = @(
"SMTP:$primary"
$preservedAddresses |
Where-Object { $_ -notmatch '^SMTP:' -or $_.Substring(5) -ine $primary }
"smtp:$($row.RemoteRoutingAddress)"
"X500:$legacyDn"
)
$seen = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
$finalAddresses = foreach ($address in $orderedAddresses) {
if ($seen.Add($address)) { $address }
}
Set-RemoteMailbox -Identity $row.Identity -EmailAddressPolicyEnabled $false
Set-RemoteMailbox -Identity $row.Identity -EmailAddresses $finalAddresses
Set-RemoteMailbox -Identity $row.Identity -PrimarySmtpAddress $primary
Get-RemoteMailbox -Identity $row.Identity |
Format-List DisplayName,RecipientTypeDetails,PrimarySmtpAddress,
RemoteRoutingAddress,EmailAddresses
}
Lo script non è intenzionalmente uno strumento di migrazione completamente automatizzato. Interrompe l’esecuzione alla prima incongruenza, affinché un amministratore possa valutare causa e stato. Prima di un batch di produzione, il codice dovrebbe essere convalidato con poche mailbox di test e con le versioni Exchange in uso.
Fase 4: sincronizzare, assegnare licenze e verificare
Dopo la modifica locale viene avviato un ciclo delta sul server Entra Connect:
Start-ADSyncSyncCycle -PolicyType Delta
Per le mailbox utente deve quindi essere assegnato un piano di servizio Exchange Online valido, ad esempio tramite l’assegnazione di licenze basata su gruppi. Le mailbox condivise, di sala e di dispositivo devono essere valutate secondo le attuali condizioni di licenza Microsoft e le funzionalità necessarie.
Il provisioning è asincrono. Microsoft indica per le modifiche normali di solito meno di 30 minuti, ma in singoli casi fino a 24 ore. Durante questo periodo, il flusso di posta a monte dovrebbe trattenere i messaggi in modo controllato invece di recapitarli a una destinazione non ancora pronta.
Il controllo locale deve ora mostrare una RemoteMailbox:
Get-RemoteMailbox -Identity user01@contoso.com |
Format-List RecipientTypeDetails,PrimarySmtpAddress,RemoteRoutingAddress,EmailAddresses
Get-Mailbox -Identity user01@contoso.com -ErrorAction SilentlyContinue
In Exchange Online viene verificato se il precedente MailUser è diventato una vera mailbox:
Get-EXORecipient -Identity user01@contoso.com |
Format-List RecipientTypeDetails,PrimarySmtpAddress,EmailAddresses
Get-EXOMailbox -Identity user01@contoso.com |
Format-List RecipientTypeDetails,PrimarySmtpAddress,ExchangeGuid
Il batch è considerato completato solo se hanno esito positivo anche i seguenti test:
- Recapito dall’esterno e dall’interno
- Invio verso l’esterno e l’interno
- Risposta a un vecchio messaggio, per verificare l’indirizzo X500
- Accesso con Outlook e Outlook sul Web
- Deleghe e Send-As
- Inoltri e regole di trasporto
- Prenotazioni di sale e dispositivi
- Applicazioni, scanner e relay SMTP
- Message Trace con recapito alla nuova mailbox cloud
Rollback e pulizia
La mailbox di origine locale non deve essere eliminata con Remove-StoreMailbox durante la fase di convalida. Finché è presente entro il periodo di conservazione della mailbox come disconnected mailbox, esiste ancora una possibilità tecnica di ripristino. Tuttavia, un rollback richiede l’inversione controllata degli attributi RemoteMailbox e la riconnessione della mailbox locale; allo stesso tempo occorre impedire la presenza di due destinazioni di recapito attive.
Prima di un rollback è pertanto necessario salvaguardare il flusso di posta, lo stato della sincronizzazione e i messaggi già ricevuti nel cloud. Il ritorno non è un semplice comando su una riga e deve far parte del change come runbook testato.
Dopo l’accettazione positiva, le richieste di esportazione vengono rimosse, i file PST archiviati secondo il concetto di protezione e conservazione e le autorizzazioni temporanee sulla condivisione di esportazione eliminate:
Get-MailboxExportRequest -BatchName $BatchName |
Remove-MailboxExportRequest -Confirm:$false
Le disconnected mailboxes dovrebbero essere eliminate definitivamente solo dopo la scadenza della finestra di rollback concordata e secondo il concetto di conservazione.
Conclusione
Se gli Hybrid Remote Move non sono consentiti e non è necessario trasferire dati delle mailbox in Exchange Online, un utente AD sincronizzato esistente può essere convertito in modo controllato da una mailbox locale a una nuova mailbox cloud. La parte critica non è Enable-RemoteMailbox, ma il controllo del processo che lo circonda: inventario completo, backup PST verificato, approvazioni esplicite, mantenimento degli indirizzi proxy e X500, flusso di posta controllato e una reale finestra di rollback.

Commenti
I commenti vengono caricati da GitHub / Giscus.