
Heute ein Beitrag zur „legacy“ Migration: Der Weg zu Exchange SE auf Windows Server 2025 ausgehend von Exchange Server 2016 CU 23 auf Windows Server 2016. Im Fokus steht der möglichst automatisierteste Weg per PowerShell, um die Zeit bis Oktober effektiv zu nutzen.
Im Artikel geht es um recht simple Exchange Server Bereitstellungen, die einen einzelnen Exchange Server 2016 beinhalten sowie zu einem einzelnen Exchange Server SE migriert werden. Hier sind keine DAGs, Load Balancer oder Web Application Firewalls im Spiel. Ein recht typisches Szenario halt, welches mir häufig in „kleineren“ Umgebungen unterkommt.
Der „alte Exchange“ ist läuft bereits mit Exchange Server 2019? Hier entlang zum Exchange 2019 In-Place Upgrade: Upgrade auf Exchange Server SE – Jans Cloud
Die Ausgangssituation:
- Exchange Server 2016 CU23 mit derzeit aktuellstem Mai 25 Hotfix Update
- Windows Server 2016 aktuellster Patch Stand
- IIS Crypto „Best Practices“ Einstellungen
Das Ziel:
- Exchange Server SE RTM
- Split-DNS (mit vorerst selbst signiertem Zertifikat sofern keine Split-DNS Konfiguration vorliegt)
- Windows Server 2025 aktuellster Patch Stand
- „Best Practices“ bzgl. TLS dank IIS Crypto
Der ausführende User ist Mitglied der:
- Domain Admins (Domänen-Administratoren)
- Enterprise Admins (Organisations-Admins)
- Schema Admins (Schema-Admins)
- Organization Management (Organisationsverwaltung)
- Lokale Gruppe „Administrators“ (Administratoren) der beiden Exchange Server
Alle Scripte werden auf dem neuen Windows Server 2025 (vorerst ohne installierten Exchange SE) ausgeführt. Gestartet wird mit dem Script „Install-ExchangeSEPreRequirements.ps1„, welches die pre Requirements herunterläd und installiert. Ebenso verbindet sich das Script mit dem Exchange Server 2016 und trifft – sofern benötigt – Vorbereitungen für Split DNS samt neuem, selbst signierten Zertifikat. Ist Split DNS bereits eingerichtet, wird das derzeit genutzte Zertifikat exportiert. Wird ein neues Zertifikat erstellt, wird ebenfalls ein neues Gruppenrichtlinienobjekt („C_Distribute Root certificates“) zur Zertifikatsverteilung erstellt.
Installation der pre Requirements von Exchange SE sowie Vorbereitung Exchange Server 2016
- Windows Server Rollen
- Visual C++ Redistributable 2012
- Visual C++ Redistributable 2013
- Unified Communications Managed API 4.0 Runtime
- IIS URL Rewrite Modul
- (IIS Crypto)
# Install-ExchangeSEPreRequirements.ps1
#
#
$IISCryptoUri = "https://www.nartac.com/Downloads/IISCrypto/IISCryptoCli.exe"
$VC2012Uri = "https://download.microsoft.com/download/1/6/b/16b06f60-3b20-4ff2-b699-5e9b7962f9ae/VSU_4/vcredist_x64.exe"
$VC2013Uri = "https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe"
$UCMAUri = "https://download.microsoft.com/download/2/c/4/2c47a5c1-a1f3-4843-b9fe-84c0032c61ec/UcmaRuntimeSetup.exe"
$URLRWUri = "https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi"
if(-not(Test-Path -Path "C:\_install" -PathType Container)){
New-Item -Path "C:\" `
-Name "_install" `
-ItemType Directory |
Out-Null
}
if(-not(Test-Path -Path "C:\_install\Logging" -PathType Container)){
New-Item -Path "C:\_install" `
-Name "Logging" `
-ItemType Directory |
Out-Null
}
Write-Host "Downloading IIS Crypto to C:\_install\IISCryptoCli.exe"
Start-BitsTransfer -Source $IISCryptoUri `
-Destination "C:\_install\IISCryptoCli.exe"
Unblock-File -Path "C:\_install\IISCryptoCli.exe"
Write-Host "Downloading VC++ Redistributable 2012 to C:\_install\VC2012x64.exe"
Start-BitsTransfer -Source $VC2012Uri `
-Destination "C:\_install\VC2012x64.exe"
Unblock-File -Path "C:\_install\VC2012x64.exe"
Write-Host "Downloading VC++ Redistributable 2013 to C:\_install\VC2013x64.exe"
Start-BitsTransfer -Source $VC2013Uri `
-Destination "C:\_install\VC2013x64.exe"
Unblock-File -Path "C:\_install\VC2013x64.exe"
Write-Host "Downloading Unified Communications Managed API 4.0 Runtime to C:\_install\UcmaRuntimeSetup.exe"
Start-BitsTransfer -Source $UCMAUri `
-Destination "C:\_install\UcmaRuntimeSetup.exe"
Unblock-File -Path "C:\_install\UcmaRuntimeSetup.exe"
Write-Host "Downloading IIS URL Rewrite to C:\_install\rewrite_amd64_en-US.msi"
Start-BitsTransfer -Source $URLRWUri `
-Destination "C:\_install\rewrite_amd64_en-US.msi"
Unblock-File -Path "C:\_install\rewrite_amd64_en-US.msi"
Write-Host "Installing Windows roles for Exchange Server SE"
Install-WindowsFeature Server-Media-Foundation, NET-Framework-45-Core, NET-Framework-45-ASPNET, NET-WCF-HTTP-Activation45, `
NET-WCF-Pipe-Activation45, NET-WCF-TCP-Activation45, NET-WCF-TCP-PortSharing45, RPC-over-HTTP-proxy, RSAT-Clustering, `
RSAT-Clustering-CmdInterface, RSAT-Clustering-Mgmt, RSAT-Clustering-PowerShell, WAS-Process-Model, Web-Asp-Net45, `
Web-Basic-Auth, Web-Client-Auth, Web-Digest-Auth, Web-Dir-Browsing, Web-Dyn-Compression, Web-Http-Errors, `
Web-Http-Logging, Web-Http-Redirect, Web-Http-Tracing, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Metabase, Web-Mgmt-Console, `
Web-Mgmt-Service, Web-Net-Ext45, Web-Request-Monitor, Web-Server, Web-Stat-Compression, Web-Static-Content, `
Web-Windows-Auth, Web-WMI, Windows-Identity-Foundation, RSAT-ADDS, GPMC, RSAT-DNS-Server |
Out-Null
Write-Host "Running IIS Crypto"
$objProc = Start-Process -FilePath "C:\_install\IISCryptoCli.exe" `
-ArgumentList "/template best /backup C:\_install\IISCryptoBackup.reg"`
-NoNewWindow `
-PassThru `
-Wait
if($objProc.ExitCode -ne 0){
Write-Host "Please check IIS Crypto manualy" `
-ForegroundColor Yellow
pause
}
Write-Host "Installing Visual C++ Redistributable 2012"
$objProc = Start-Process -FilePath "C:\_install\VC2012x64.exe" `
-ArgumentList "/install /quiet /log C:\_install\Logging\vcredist2012x64.txt /norestart" `
-NoNewWindow `
-PassThru `
-Wait
if($objProc.ExitCode -notmatch "^(0|1638|3010)$"){
Write-Host "Please check VC redist logs `"C:\_install\Logging\vcredist2012x64.txt`"" `
-ForegroundColor Red
pause
} else{
Remove-Item -Path "C:\_install\VC2012x64.exe"
}
Write-Host "Installing Visual C++ Redistributable 2013:"
$objProc = Start-Process -FilePath "C:\_install\VC2013x64.exe" `
-ArgumentList "/install /quiet /log C:\_install\Logging\vcredist2013x64.txt /norestart" `
-NoNewWindow `
-PassThru `
-Wait
if($objProc.ExitCode -notmatch "^(0|1638|3010)$"){
Write-Host "Please check VC redist logs `"C:\_install\Logging\vcredist2013x64.txt`"" `
-ForegroundColor Red
pause
} else{
Remove-Item -Path "C:\_install\VC2013x64.exe"
}
Write-Host "Installing Unified Communications Managed API 4.0 Runtime"
$objProc = Start-Process -FilePath "C:\_install\UcmaRuntimeSetup.exe" `
-ArgumentList "/q /log C:\_install\Logging\UcmaRuntime.txt /norestart" `
-NoNewWindow `
-PassThru `
-Wait
if($objProc.ExitCode -notmatch "^0$"){
Write-Host "Please check Ucma runtime logs `"C:\_install\Logging\UcmaRuntime.txt`"" `
-ForegroundColor Red
pause
} else{
Remove-Item -Path "C:\_install\UcmaRuntimeSetup.exe"
}
Write-Host "Installing IIS URL Rewrite module"
$objProc = Start-Process -FilePath "C:\Windows\System32\msiexec.exe" `
-ArgumentList "/i `"C:\_install\rewrite_amd64_en-US.msi`"/quiet /l*v C:\_install\Logging\iisrewrite.txt /norestart" `
-NoNewWindow `
-PassThru `
-Wait
if($objProc.ExitCode -notmatch "^(0|1638|3010)$"){
Write-Host "Please check IIS rewrite logs `"C:\_install\Logging\iisrewrite.txt`"" `
-ForegroundColor Red
pause
} else{
Remove-Item -Path "C:\_install\rewrite_amd64_en-US.msi"
}
Write-Host "Configuring TLS settings .Net v2.0.50727"
New-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v2.0.50727" `
-Name "SchUseStrongCrypto" `
-PropertyType Dword `
-Value 1 |
Out-Null
New-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v2.0.50727" `
-Name "SchUseStrongCrypto" `
-PropertyType Dword `
-Value 1 |
Out-Null
New-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v2.0.50727" `
-Name "SystemDefaultTlsVersions" `
-PropertyType Dword `
-Value 1 |
Out-Null
New-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v2.0.50727" `
-Name "SystemDefaultTlsVersions" `
-PropertyType Dword `
-Value 1 |
Out-Null
Write-Host "Configuring TLS settings .Net v4.0.30319"
New-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" `
-Name "SchUseStrongCrypto" `
-PropertyType Dword `
-Value 1 |
Out-Null
New-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0.30319" `
-Name "SchUseStrongCrypto" `
-PropertyType Dword `
-Value 1 |
Out-Null
New-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" `
-Name "SystemDefaultTlsVersions" `
-PropertyType Dword `
-Value 1 |
Out-Null
New-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0.30319" `
-Name "SystemDefaultTlsVersions" `
-PropertyType Dword `
-Value 1 |
Out-Null
$objADDomain = Get-ADDomain -Current LocalComputer
$NBDomain = $objADDomain.NetBIOSName
$DNSDomain = $objADDomain.DNSRoot
$DNSServer = $objADDomain.PDCEmulator
$CertGPO = "C_Distribute Root certificates"
$newSANCert = @()
$ExchSRVSE = Get-ADComputer -Identity $env:COMPUTERNAME `
-Properties DNSHostName
$ExchSRVSEIP = Resolve-DnsName -Name $ExchSRVSE.DNSHostName `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
$oldExchSRV = Get-ADGroupMember -Identity "Exchange Servers" |
Where-Object { $_.objectClass -eq "computer" -and $_.Name -ne $env:COMPUTERNAME }
$oldExchSRV = Get-ADComputer -Identity $oldExchSRV.distinguishedName `
-Properties DNSHostName
$oldExchSRVIP = Resolve-DnsName -Name $oldExchSRV.DNSHostName `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
$oldExchConUri = -join("http://", `
$oldExchSRV.DNSHostName , `
"/PowerShell" `
)
Write-Host "Connecting to Exchange PowerShell Session `"$oldExchConUri`""
$ExchSession = New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri $oldExchConUri
Import-PSSession -Session $ExchSession -DisableNameChecking `
-AllowClobber |
Out-Null
Write-Host "Getting accepted domains"
$SMTPDoms = Get-AcceptedDomain
$defaultSMTPDom = $SMTPDoms |
Where-Object { $_.Default -eq $true }
do{
$SDconf = Read-Host -Prompt "Is Split DNS already configured? (y/n)"
}until($SDconf -match "^(y|n)$")
if($SDconf -eq "n"){
Write-Host "Starting initial Split DNS configuration"
do{
$SDHost = Read-Host "What host name should be used (e.g. outlook, mail or owa)"
}until(-not [string]::IsNullOrEmpty($SDHost) -and $SDHost -match "^[A-Za-z0-9]{1,15}$")
do{
$i = 0
foreach($s in $SMTPDoms){
Write-Host "[$($i+1)]: $($s.DomainName)"
$i++
}
do{
$j = Read-Host "What domain should be used (1 - $i)"
}until($j -match "^\d{1,2}$")
}until($j -in 1..$i)
$SDDom = $SMTPDoms[$j-1].DomainName
$ExchDNS = -join(
$SDHost,
".",
$SDDom
)
Write-Host "Creating helper file `"C:\_install\ExchDNS.txt`" with content `"$ExchDNS`""
Set-Content -Path "C:\_install\ExchDNS.txt" `
-Value $ExchDNS
$newSANCert += $ExchDNS
foreach($s in $SMTPDoms){
$newSANCert += -join(
"autodiscover.",
$s.DomainName
)
}
Write-Host "Create new Exchange certificate with subject `"$ExchDNS`""
$newSANCert += $ExchSRVSE.DNSHostName, $oldExchSRV.DNSHostName, $ExchSRVSE.Name, $oldExchSRV.Name
$newExchCert = New-ExchangeCertificate -KeySize 2048 `
-PrivateKeyExportable:$true `
-FriendlyName "Exchange ($ExchDNS)" `
-SubjectName "CN=$ExchDNS, O=$DNSDomain, C=DE" `
-DomainName $newSANCert `
-Confirm:$false `
-Force
Write-Host "Exporting new Exchange certificate to `"C:\_install\ExchCert_$ExchDNS.cer`""
Export-Certificate -Cert $newExchCert `
-Type CERT `
-FilePath "C:\_install\ExchCert_$ExchDNS.cer" `
-Confirm:$false `
-Force
if(-not(Test-Path -Path "\\$($oldExchSRV.DNSHostName)\c$\_install" -PathType Container)){
New-Item -Path "\\$($oldExchSRV.DNSHostName)\c$" `
-Name "_install" `
-ItemType Directory |
Out-Null
}
Copy-Item -Path "C:\_install\ExchCert_$ExchDNS.cer" `
-Destination "\\$($oldExchSRV.DNSHostName)\c$\_install\ExchCert.cer" `
-Confirm:$false `
-Force
Write-Host "Exporting new Exchange certificate PFX to `"C:\_install\ExchCert_$ExchDNS.pfx`""
$PFXPass = Read-Host -Prompt "Enter PFX export password" `
-AsSecureString
$PFXExchCert = Export-ExchangeCertificate -Thumbprint $newExchCert.Thumbprint `
-BinaryEncoded `
-Password $PFXPass `
-Server $oldExchSRV.Name
[System.IO.File]::WriteAllBytes("C:\_install\ExchCert_$ExchDNS.pfx", $PFXExchCert.FileData)
Write-Host "Importing certificate in $($ExchSRVSE.Name) trusted roots"
Import-Certificate -FilePath "C:\_install\ExchCert_$ExchDNS.cer" `
-CertStoreLocation "Cert:\LocalMachine\Root" `
-Confirm:$false |
Out-Null
Write-Host "Importing certificate in $($oldExchSRV.Name) trusted roots"
Invoke-Command -ComputerName $oldExchSRV.DNSHostName `
-ScriptBlock {
Import-Certificate -FilePath "C:\_install\ExchCert.cer" `
-CertStoreLocation "Cert:\LocalMachine\Root" `
-Confirm:$false |
Out-Null
Remove-Item -Path "C:\_install\ExchCert.cer" `
-Confirm:$false `
-Force
}
$objCertGPO = Get-GPO -All |
Where-Object { $_.DisplayName -eq $CertGPO }
if($null -eq $objCertGPO){
Write-Host "Creating new GPO `"$CertGPO`" for self signed certificate distribtion"
$objCertGPO = New-GPO -Name $CertGPO
$objCertGPO.GpoStatus = "UserSettingsDisabled"
Write-Host "Linking GPO `"$CertGPO`" to `"$($objADDomain.DistinguishedName)`""
New-GPLink -Guid $objCertGPO.Id `
-Target $objADDomain.DistinguishedName `
-LinkEnabled Yes |
Out-Null
}
$certRegPath = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\{0}" `
-f $newExchCert.Thumbprint
$certBlob = Get-ItemProperty -Path $certRegPath `
-Name "Blob" |
Select-Object Blob `
-Expand Blob
Write-Host "Adding new certificate to GPO `"$CertGPO`""
$certPolRegPath = "HKLM\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates\{0}" `
-f $newExchCert.Thumbprint
Set-GPRegistryValue -Name $CertGPO `
-Key $certPolRegPath `
-ValueName "Blob" `
-Type Binary `
-Value $certBlob |
Out-Null
} else{
Write-Host "Split DNS is present. Export current certificate"
Get-ExchangeCertificate -Server $oldExchSRV.DNSHostName
Write-Host "`nCopy & paste thumbprint of current client access certificate"
$curCert = Read-Host -Prompt "Thumbprint"
$curCert = Get-ExchangeCertificate -Thumbprint $curCert `
-Server $oldExchSRV.Name
Export-Certificate -Cert $curCert `
-Type CERT `
-FilePath "C:\_install\ExchCert_$ExchDNS.cer" `
-Confirm:$false `
-Force
Write-Host "Exporting current Exchange certificate PFX to `"C:\_install\ExchCert_$ExchDNS.pfx`""
$PFXPass = Read-Host -Prompt "Enter PFX export password" `
-AsSecureString
$PFXExchCert = Export-ExchangeCertificate -Thumbprint $curCert.Thumbprint `
-BinaryEncoded `
-Password $PFXPass `
-Server $oldExchSRV.Name
[System.IO.File]::WriteAllBytes("C:\_install\ExchCert_$ExchDNS.pfx", $PFXExchCert.FileData)
Set-Content -Path "C:\_install\SplitDNSCheck.txt" `
-Value $curCert.Thumbprint
}
Write-Host "Closing session to `"$oldExchConUri`""
Remove-PSSession -Session $ExchSession
Write-Host "Server will reboot after pressing <Enter>"
pause
Restart-Computer
Jetzt ist es Zeit für mindestens eine Kaffee ☕ oder Bier 🍻 Pause. Idealerweise warten wir bis zum nächsten Tag, damit alle Clients dem neu erstellten und selbst signierten Zertifikat für den neuen Exchange Server Subscription Edition vertrauen.
Neuinstallation Exchange SE auf dem neuen Windows Server 2025
Im nächsten Schritt installieren wir den Exchange Server SE per „Install-ExchangeSE.ps1“ auf einen frisch installierten sowie durchgepatchten Windows Server 2025 und nehmen die Grundkonfiguration vor. Ebenfalls werden die notwendigen DNS Zonen / Records für Split DNS eingerichtet. Sofern Split DNS bereits konfiguriert ist, werden nur die vorhandenen DNS Records auf den neuen Exchange geschwenkt.
Installation und Grundkonfiguration Exchange SE
# Install-ExchangeSE.ps1
#
#
$ExchSEUri = "https://download.microsoft.com/download/ad9b1e53-f28e-4a82-987d-6c88803795b2/ExchangeServerSE-x64.iso"
if(-not(Test-Path -Path "C:\_install\ExchSEISO" -PathType Container)){
New-Item -Path "C:\_install" `
-Name "ExchSEISO" `
-ItemType Directory `
-Confirm:$false `
-Force |
Out-Null
}
Start-BitsTransfer -Source $ExchSEUri `
-Destination "C:\_install\ExchSEISO\ExchangeServerSE-x64.iso"
Unblock-File -Path "C:\_install\ExchSEISO\ExchangeServerSE-x64.iso"
Write-Host "Mounting DVD Image `"C:\_install\ExchSEISO\ExchangeServerSE-x64.iso`""
$mdi = Mount-DiskImage -ImagePath "C:\_install\ExchSEISO\ExchangeServerSE-x64.iso" `
-StorageType ISO `
-Access ReadOnly `
-PassThru
$DVD = $mdi |
Get-Volume
$ExchSetup = -join(
$DVD.DriveLetter,
":\Setup.exe"
)
$ExchPrepAD = "/IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /PrepareAD"
$ExchInstall = "/IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /Mode:Install /Roles:Mailbox "
$ExchInstall += "/LogFolderPath:`"C:\EX-Logs\EXSE-DB01`" /DbFilePath:`"C:\EX-DBs\EXSE-DB01\EXSE-DB01.edb`" "
$ExchInstall += "/MdbName:`"EXSE-DB01`""
Write-Host "Preparing Active Directory `"$ExchSetup $ExchPrepAD`""
$procPrepAD = Start-Process -FilePath $ExchSetup `
-ArgumentList $ExchPrepAD `
-NoNewWindow `
-PassThru `
-Wait
if($procPrepAD.ExitCode -ne 0){
Write-Host "Check setup logs in `"C:\ExchangeSetupLogs`" before continuing by pressing <Enter>" `
-ForegroundColor Yellow
pause
}
Rename-Item -Path "C:\ExchangeSetupLogs" `
-NewName "ExchangeSetupLogs-PrepareAD"
Move-Item -Path "C:\ExchangeSetupLogs-PrepareAD" `
-Destination "C:\_install\Logging\ExchangeSetupLogs-PrepareAD"
Write-Host "Installaing Exchange Server SE RTM"
$procExchSetup = Start-Process -FilePath $ExchSetup `
-ArgumentList $ExchInstall `
-NoNewWindow `
-PassThru `
-Wait
if($procExchSetup.ExitCode -ne 0){
Write-Host "Check setup logs in `"C:\ExchangeSetupLogs`" before continuing by pressing <Enter>" `
-ForegroundColor Yellow
pause
}
Rename-Item -Path "C:\ExchangeSetupLogs" `
-NewName "ExchangeSetupLogs-Installed"
Move-Item -Path "C:\ExchangeSetupLogs-Installed" `
-Destination "C:\_install\Logging\ExchangeSetupLogs-Installed"
$objADDomain = Get-ADDomain -Current LocalComputer
$NBDomain = $objADDomain.NetBIOSName
$DNSDomain = $objADDomain.DNSRoot
$DNSServer = $objADDomain.PDCEmulator
$ExchSRVSE = Get-ADComputer -Identity $env:COMPUTERNAME `
-Properties DNSHostName
$ExchSRVSEIP = Resolve-DnsName -Name $ExchSRVSE.DNSHostName `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
$oldExchSRV = Get-ADGroupMember -Identity "Exchange Servers" |
Where-Object { $_.objectClass -eq "computer" -and $_.Name -ne $env:COMPUTERNAME }
$oldExchSRV = Get-ADComputer -Identity $oldExchSRV.distinguishedName `
-Properties DNSHostName
$oldExchSRVIP = Resolve-DnsName -Name $oldExchSRV.DNSHostName `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
$ExchSEConUri = -join("http://", `
$ExchSRVSE.DNSHostName , `
"/PowerShell" `
)
Write-Host "Connecting to Exchange PowerShell Session `"$ExchSEConUri`""
$ExchSession = New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri $ExchSEConUri
Import-PSSession -Session $ExchSession -DisableNameChecking `
-AllowClobber |
Out-Null
$PFXFile = Get-ChildItem -Path "C:\_install" `
-Filter *.pfx |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Write-Host "Import Exchange certificate"
$PFXPass = Read-Host -Prompt "Enter PFX import password" `
-AsSecureString
$ExchCert = Import-ExchangeCertificate -Server $ExchSRVSE.DNSHostName `
-FileData ([System.IO.File]::ReadAllBytes("C:\_install\$($PFXFile.Name)")) `
-Password $PFXPass `
-PrivateKeyExportable:$true
if(Test-Path -Path "C:\_install\SplitDNSCheck.txt" -PathType Leaf){
$tmpUrl = Get-OwaVirtualDirectory -Server $oldExchSRV.DNSHostName |
Select-Object InternalUrl
Write-Host "Set Owa vDir Url to `"$($tmpUrl.InternalUrl)`""
Set-OwaVirtualDirectory -Identity "$($ExchSRVSE.Name)\owa (Default Web Site)" `
-InternalUrl $tmpUrl.InternalUrl `
-ExternalUrl $tmpUrl.InternalUrl `
-WarningAction SilentlyContinue
$tmpUrl = Get-EcpVirtualDirectory -Server $oldExchSRV.DNSHostName |
Select-Object InternalUrl
Write-Host "Set Ecp vDir Url to `"$($tmpUrl.InternalUrl)`""
Set-EcpVirtualDirectory -Identity "$($ExchSRVSE.Name)\ecp (Default Web Site)" `
-InternalUrl $tmpUrl.InternalUrl `
-ExternalUrl $tmpUrl.InternalUrl `
-WarningAction SilentlyContinue
$tmpUrl = Get-OabVirtualDirectory -Server $oldExchSRV.DNSHostName |
Select-Object InternalUrl
Write-Host "Set Oab vDir Url to `"$($tmpUrl.InternalUrl)`""
Set-OabVirtualDirectory -Identity "$($ExchSRVSE.Name)\OAB (Default Web Site)" `
-InternalUrl $tmpUrl.InternalUrl `
-ExternalUrl $tmpUrl.InternalUrl
$tmpUrl = Get-ActiveSyncVirtualDirectory -Server $oldExchSRV.DNSHostName |
Select-Object InternalUrl
Write-Host "Set Eas vDir Url to `"$($tmpUrl.InternalUrl)`""
Set-ActiveSyncVirtualDirectory -Identity "$($ExchSRVSE.Name)\Microsoft-Server-ActiveSync (Default Web Site)" `
-InternalUrl $tmpUrl.InternalUrl `
-ExternalUrl $tmpUrl.InternalUrl
$tmpUrl = Get-WebServicesVirtualDirectory -Server $oldExchSRV.DNSHostName |
Select-Object InternalUrl
Write-Host "Set Ews vDir Url to `"$($tmpUrl.InternalUrl)`""
Set-WebServicesVirtualDirectory -Identity "$($ExchSRVSE.Name)\EWS (Default Web Site)" `
-InternalUrl $tmpUrl.InternalUrl `
-ExternalUrl $tmpUrl.InternalUrl
$tmpUrl = Get-MapiVirtualDirectory -Server $oldExchSRV.DNSHostName |
Select-Object InternalUrl
Write-Host "Set Mapi vDir Url to `"$($tmpUrl.InternalUrl)`""
Set-MapiVirtualDirectory -Identity "$($ExchSRVSE.Name)\mapi (Default Web Site)" `
-InternalUrl $tmpUrl.InternalUrl `
-ExternalUrl $tmpUrl.InternalUrl
$tmpUrl = Get-ClientAccessService -Identity $oldExchSRV.DNSHostName |
Select-Object AutodiscoverServiceInternalUri
Write-Host "Set Autodiscover Service Internal Uri to `"$($tmpUrl.AutodiscoverServiceInternalUri)`""
Set-ClientAccessService -Identity $ExchSRVSE.Name `
-AutoDiscoverServiceInternalUri $tmpUrl.AutoDiscoverServiceInternalUri
$tmpUrl = Get-OutlookAnywhere -Server $oldExchSRV.Name |
Select-Object InternalHostname
Write-Host "Set Outlook Anywhere host names to `"$($tmpUrl.InternalHostname)`""
Set-OutlookAnywhere -Identity "$($ExchSRVSE.Name)\Rpc (Default Web Site)" `
-InternalHostname $tmpUrl.InternalHostname `
-InternalClientsRequireSsl:$true `
-ExternalHostname $tmpUrl.InternalHostname `
-ExternalClientsRequireSsl:$true `
-ExternalClientAuthenticationMethod "Negotiate" `
-WarningAction SilentlyContinue
} else{
$ExchDNS = Get-Content -Path "C:\_install\ExchDNS.txt"
Write-Host "Enabling Exchange certificate on $($oldExchSRV.Name)"
Enable-ExchangeCertificate -Server $oldExchSRV.DNSHostName `
-Thumbprint $ExchCert.Thumbprint `
-Services IIS, IMAP, POP, SMTP `
-WarningAction SilentlyContinue `
-Confirm:$false `
-Force
$tmpUrl = "https://{0}/owa" -f $ExchDNS
Write-Host "Set Owa vDir Url to `"$($tmpUrl)`""
Set-OwaVirtualDirectory -Identity "$($ExchSRVSE.Name)\owa (Default Web Site)" `
-InternalUrl $tmpUrl `
-ExternalUrl $tmpUrl `
-WarningAction SilentlyContinue
$tmpUrl = "https://{0}/ecp" -f $ExchDNS
Write-Host "Set Ecp vDir Url to `"$($tmpUrl)`""
Set-EcpVirtualDirectory -Identity "$($ExchSRVSE.Name)\ecp (Default Web Site)" `
-InternalUrl $tmpUrl `
-ExternalUrl $tmpUrl `
-WarningAction SilentlyContinue
$tmpUrl = "https://{0}/OAB" -f $ExchDNS
Write-Host "Set Oab vDir Url to `"$($tmpUrl)`""
Set-OabVirtualDirectory -Identity "$($ExchSRVSE.Name)\OAB (Default Web Site)" `
-InternalUrl $tmpUrl `
-ExternalUrl $tmpUrl
$tmpUrl = "https://{0}/Microsoft-Server-ActiveSync" -f $ExchDNS
Write-Host "Set Eas vDir Url to `"$($tmpUrl)`""
Set-ActiveSyncVirtualDirectory -Identity "$($ExchSRVSE.Name)\Microsoft-Server-ActiveSync (Default Web Site)" `
-InternalUrl $tmpUrl `
-ExternalUrl $tmpUrl
$tmpUrl = "https://{0}/EWS/Exchange.asmx" -f $ExchDNS
Write-Host "Set Ews vDir Url to `"$($tmpUrl)`""
Set-WebServicesVirtualDirectory -Identity "$($ExchSRVSE.Name)\EWS (Default Web Site)" `
-InternalUrl $tmpUrl `
-ExternalUrl $tmpUrl
$tmpUrl = "https://{0}/mapi" -f $ExchDNS
Write-Host "Set Mapi vDir Url to `"$($tmpUrl)`""
Set-MapiVirtualDirectory -Identity "$($ExchSRVSE.Name)\mapi (Default Web Site)" `
-InternalUrl $tmpUrl `
-ExternalUrl $tmpUrl
$tmpUrl = "https://{0}/autodiscover/autodiscover.xml" -f $ExchDNS
Write-Host "Set Autodiscover Service Internal Uri to `"$($tmpUrl)`""
Set-ClientAccessService -Identity $ExchSRVSE.Name `
-AutoDiscoverServiceInternalUri $tmpUrl
$tmpUrl = $ExchDNS
Write-Host "Set Outlook Anywhere host names to `"$($tmpUrl)`""
Set-OutlookAnywhere -Identity "$($ExchSRVSE.Name)\Rpc (Default Web Site)" `
-InternalHostname $tmpUrl `
-InternalClientsRequireSsl:$true `
-ExternalHostname $tmpUrl `
-ExternalClientsRequireSsl:$true `
-ExternalClientAuthenticationMethod "Negotiate" `
-WarningAction SilentlyContinue
}
Write-Host "Enabling Exchange certificate on $($ExchSRVSE.Name)"
Enable-ExchangeCertificate -Server $ExchSRVSE.DNSHostName `
-Thumbprint $ExchCert.Thumbprint `
-Services IIS, IMAP, POP, SMTP `
-WarningAction SilentlyContinue `
-Confirm:$false `
-Force
$certDomains = $ExchCert.DnsNameList.Unicode -match "^.*\..*\..*$"
$certDomains = $certDomains |
ForEach-Object{
$tmpDom = $_ -replace "^$($oldExchSRV.DNSHostName)|$($ExchSRVSE.DNSHostName)$"
if(-not [string]::IsNullOrEmpty($tmpDom)){
$tmpDom
}
}
foreach($d in $certDomains){
$tmpDom = ($d -replace $d.Split(".")[0]).Substring(1)
$tmpDomZone = Get-DnsServerZone -ComputerName $DNSServer |
Where-Object { $_.ZoneName -eq $tmpDom }
if($null -eq $tmpDomZone){
$tmpZone = Get-DnsServerZone -ComputerName $DNSServer |
Where-Object { $_.ZoneName -eq $d }
if($null -eq $tmpZone){
Write-Host "Adding DNS zone `"$d`" on server `"$DNSServer`""
Add-DnsServerPrimaryZone -ComputerName $DNSServer `
-Name $d `
-DynamicUpdate None `
-ReplicationScope Forest
Write-Host "Adding A-Record `".`" with IP `"$ExchSRVSEIP`" in zone `"$d`" on server `"$DNSServer`""
Add-DnsServerResourceRecordA -ComputerName $DNSServer `
-ZoneName $d `
-Name "." `
-IPv4Address $ExchSRVSEIP
} else{
$tmpRecord = Get-DnsServerResourceRecord -ComputerName $DNSServer `
-ZoneName $tmpZone.ZoneName `
-RRType A |
Where-Object { $_.Hostname -eq "@" }
if($null -eq $tmpRecord){
Write-Host "Adding A-Record `".`" with IP `"$ExchSRVSEIP`" in zone `"$d`" on server `"$DNSServer`""
Add-DnsServerResourceRecordA -ComputerName $DNSServer `
-ZoneName $tmpZone.ZoneName `
-Name "." `
-IPv4Address $ExchSRVSEIP
} else{
Write-Host "Changing A-Record `".`" to IP `"$ExchSRVSEIP`" in zone `"$($tmpZone.ZoneName)`" on server `"$DNSServer`""
$newRecord = $tmpRecord.Clone()
$newRecord.RecordData.IPv4Address = $ExchSRVSEIP
Set-DnsServerResourceRecord -ComputerName $DNSServer `
-ZoneName $tmpZone.ZoneName `
-OldInputObject $tmpRecord `
-NewInputObject $newRecord
}
}
} else{
$tmpRecord = Get-DnsServerResourceRecord -ComputerName $DNSServer `
-ZoneName $tmpDomZone.ZoneName `
-RRType A |
Where-Object { $_.Hostname -eq $d.Split(".")[0] }
if($null -eq $tmpRecord){
Write-Host "Adding A-Record `"$($d.Split(".")[0])`" with IP `"$ExchSRVSEIP`" in zone `"$($tmpDomZone.ZoneName)`" on server `"$DNSServer`""
Add-DnsServerResourceRecordA -ComputerName $DNSServer `
-ZoneName $tmpDomZone.ZoneName `
-Name $d.Split(".")[0] `
-IPv4Address $ExchSRVSEIP
} else{
$newRecord = $tmpRecord.Clone()
$newRecord.RecordData.IPv4Address = $ExchSRVSEIP
Write-Host "Changing A-Record `"$($newRecord.HostName)`" to IP `"$ExchSRVSEIP`" in zone `"$($tmpDomZone.ZoneName)`" on server `"$DNSServer`""
Set-DnsServerResourceRecord -ComputerName $DNSServer `
-ZoneName $tmpDomZone.ZoneName `
-OldInputObject $tmpRecord `
-NewInputObject $newRecord
}
}
}
$curOAB = Get-OfflineAddressBook
Write-Host "Setting Offline Address Book `"$($curOAB.Name)`""
Get-MailboxDatabase -Server $ExchSRVSE.DNSHostName |
Set-MailboxDatabase -OfflineAddressBook $curOAB.Name
Write-Host "Adding `"$($ExchSRVSE.Name)`" to Send Connectors"
Get-SendConnector |
Set-SendConnector -SourceTransportServers @{Add="$($ExchSRVSE.Name)"}
Write-Host "Disabling TNEF"
Get-RemoteDomain |
Set-RemoteDomain -TNEFEnabled $False
Get-Service -ComputerName $oldExchSRV.DNSHostName |
Where-Object { $_.Name -match "^(MSExchangeImap4|MSExchangePop3|wsbexchange)" -and $_.StartType -eq "Automatic" } |
ForEach-Object {
Write-Host "Configuring service `"$($_.DisplayName)`" to automatic start"
Set-Service -Name $_.Name `
-StartupType Automatic
}
Remove-PSSession -Session $ExchSession
Write-Host "Server will reboot after pressing <Enter>"
pause
Restart-Computer
Nach dem erfolgten Reboot kann nahtlos mit dem Verschieben der Postfächer vom Exchange Server 2016 auf den Exchange Server Subscription Edition begonnen werden.
Verschieben der Postfächer
Kommen wir mit dem Script „Move-MailboxesToExchangeSE.ps1“ zum letzten Schritt und verschieben die Postfächer vom Exchange Server 2016 auf den Exchange Server SE. Dabei ist zu beachten, dass die Postfächer erst am nächsten Tag um 02:30 „unserer Zeit“ finalisiert werden. Wer das nicht möchte, muss eingreifen. 🙂
# Move-MailboxesToExchangeSE.ps1
#
#
$objADDomain = Get-ADDomain -Current LocalComputer
$NBDomain = $objADDomain.NetBIOSName
$DNSDomain = $objADDomain.DNSRoot
$DNSServer = $objADDomain.PDCEmulator
$ExchSRVSE = Get-ADComputer -Identity $env:COMPUTERNAME `
-Properties DNSHostName
$ExchSRVSEIP = Resolve-DnsName -Name $ExchSRVSE.DNSHostName `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
$oldExchSRV = Get-ADGroupMember -Identity "Exchange Servers" |
Where-Object { $_.objectClass -eq "computer" -and $_.Name -ne $env:COMPUTERNAME }
$oldExchSRV = Get-ADComputer -Identity $oldExchSRV.distinguishedName `
-Properties DNSHostName
$oldExchSRVIP = Resolve-DnsName -Name $oldExchSRV.DNSHostName `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
$ExchSEConUri = -join("http://", `
$ExchSRVSE.DNSHostName , `
"/PowerShell" `
)
do{
Write-Host "Waiting for Exchange services to start"
$ExServices = Get-Service |
Where-Object { $_.DisplayName -match "^Microsoft Exchange" -and $_.StartType -eq "Automatic" -and $_.Status -ne "Running" }
Start-Sleep -Seconds 10
}until($null -eq $ExServices)
Write-Host "Connecting to Exchange PowerShell Session `"$ExchSEConUri`""
$ExchSession = New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri $ExchSEConUri
Import-PSSession -Session $ExchSession -DisableNameChecking `
-AllowClobber |
Out-Null
$ExchSEDB = Get-MailboxDatabase -Server $ExchSRVSE.Name
Write-Host "Enabling circular logging for mailbox moves"
Set-MailboxDatabase $ExchSEDB.Name `
-CircularLoggingEnabled:$true `
-WarningAction SilentlyContinue
Write-Host "Dismounting database `"$($ExchSEDB.Name)`""
Dismount-Database -Identity $ExchSEDB.Identity `
-Confirm:$false
Start-Sleep -Seconds 5
Write-Host "Mounting database `"$($ExchSEDB.Name)`""
Mount-Database -Identity $ExchSEDB.Identity
Start-Sleep -Seconds 25
Write-Host "Moving Arbitration, Audit log and Discoverysearch Mailbox to `"$($ExchSRVSE.Name)`""
Get-Mailbox -Arbitration `
-Server $oldExchSRV.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name |
Out-Null
Get-Mailbox -AuditLog `
-Server $oldExchSRV.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name |
Out-Null
Get-Mailbox -RecipientTypeDetails "DiscoveryMailbox" `
-Server $oldExchSRV.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name |
Out-Null
do{
$curMoveReqs = Get-MoveRequest |
Where-Object { $_.Status -ne "Completed" }
$curMoveStats = $curMoveReqs |
Get-MoveRequestStatistics
Write-Host "Waiting to complete running move requests (Arbitration, Audit log and Discoverysearch)..." `
-ForegroundColor Yellow
foreach($m in $curMoveStats){
Write-Host "$($m.DisplayName) ($($m.TotalMailboxSize)): $($m.PercentComplete)%"
}
if($null -ne $curMoveReqs){
Write-Host ""
}
Start-Sleep -Seconds 60
}until($null -eq $curMoveReqs)
$tmpDate = (Get-Date).AddDays(1)
$tmpDate = -join($tmpDate.Day,
".",
$tmpDate.Month,
".",
$tmpDate.Year,
" 02:30:00"
)
$CompletionDate = (Get-Date $tmpDate).ToUniversalTime()
Write-Host "Moving all remaining mailboxes with completion date $CompletionDate (UTC)"
Get-Mailbox -PublicFolder `
-Server $oldExchSRV.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name `
-CompleteAfter $CompletionDate |
Out-Null
Get-Mailbox -Server $oldExchSRV.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name.Name `
-CompleteAfter $CompletionDate |
Out-Null
do{
$curMoveReqs = Get-MoveRequest |
Where-Object { $_.Status -ne "Completed" }
$curMoveStats = $curMoveReqs |
Get-MoveRequestStatistics
Write-Host "Waiting for completion $CompletionDate (UTC)" `
-ForegroundColor Yellow
foreach($m in $curMoveStats){
Write-Host "$($m.DisplayName) ($($m.TotalMailboxSize)): $($m.PercentComplete)%"
}
if($null -ne $curMoveReqs){
Write-Host ""
}
Start-Sleep -Seconds 60
} until($null -eq $curMoveReqs)
Write-Host "Disabling circular logging for mailbox moves"
Set-MailboxDatabase $ExchSEDB.Name `
-CircularLoggingEnabled:$false `
-WarningAction SilentlyContinue
Write-Host "Dismounting database `"$($ExchSEDB.Name)`""
Dismount-Database -Identity $ExchSEDB.Identity `
-Confirm:$false
Start-Sleep -Seconds 5
Write-Host "Mounting database `"$($ExchSEDB.Name)`""
Mount-Database -Identity $ExchSEDB.Identity
Start-Sleep -Seconds 25
Write-Host "Done :)"
Write-Host "`nPlease check `"customized things`" not (yet) handled by the script (e.g. journaling, ...)!" `
-ForegroundColor Yellow
Deinstallation Exchange Server 2016
Sobald mögliche Nacharbeiten (Mailrouting auf neuen Exchange Server konfigurieren, Empfangskonnektoren, Journaling, Scan2Mail, andere Systeme die über Exchange relayen, …) erledigt sind, kann es nach ein paar Tagen / ein, zwei Wochen mit der Deinstallation des alten Exchange weitergehen. Dazu verbindet man sich am einfachsten per RDP auf den auszumusternden Exchange und führt das Script „Uninstall-Exchange.ps1“ aus.
# Install-ExchangeSEPreRequirements.ps1
#
#
$ExBinPath = Join-Path -Path $env:ExchangeInstallPath `
-ChildPath "bin"
$ExSetup = Get-ChildItem -Path $ExBinPath `
-Filter "setup.exe"
Write-Host "Uninstalling Exchange Server"
$ExUninstProc = Start-Process -FilePath $ExSetup.FullName `
-ArgumentList "/Mode:uninstall" `
-NoNewWindow `
-PassThru `
-Wait
$ExchSE = Get-ADGroupMember -Identity "Exchange Servers" |
Where-Object { $_.objectClass -eq "computer" }
Write-Host "Moving Exchange uninstall logs to `"\\$($ExchSE.Name)\c$\_install\Logging\ExchangeSetupLogs-Uninstall`""
$ExUninstLogs = Get-ChildItem -Path "C:\" `
-Filter "ExchangeSetupLogs" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Rename-Item -Path $ExUninstLogs.FullName `
-NewName "ExchangeSetupLogs-Uninstall"
Move-Item -Path "C:\ExchangeSetupLogs-Uninstall" `
-Destination "\\$($ExchSE.Name)\c$\_install\Logging\ExchangeSetupLogs-Uninstall"
Write-Host "Server will reboot after pressing <Enter>"
pause
Restart-Computer
Im Anschluss sollte der alte Server natürlich noch aus der Domäne entfernt und das Computerkonto gelöscht werden.
Bei Fragen, fragt. 🙂 Theoretisch sollte das Script in vielen Umgebungen recht unfallfrei funktionieren. Wenn es Probleme gibt, „lasst nen Kommi da“. 😉
Schreibe einen Kommentar