
Heute ein Beitrag zur (letzten?) „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.
Die Ausgangssituation:
- Exchange Server 2016 CU23 mit derzeit aktuellstem Mai 25 Hotfix Update
- Kein „Best Practices“ bzgl. Split DNS
- Kein vertrauenswürdiges Zertifikat / Let’s Encrypt oder eigene PKI
- Selbstsigniertes Zertifikat mit <Hostname> als Subject und <FQDN> als SAN
- Windows Server 2016 aktuellster Patch Stand
- IIS Crypto „Best Practices“ Einstellungen
Das Ziel:
- Exchange SE RTM
- Split-DNS (mit vorerst selbst signiertem Zertifikat)
- 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 bereitet diesen vor.
Installation der pre Requirements von Exchange SE sowie Vorbereitung Exchange Server 2016
- Windows Server Rollen (Exchange Server prerequisites, Exchange 2019 system requirements, Exchange SE system requirements, Exchange 2019 requirements, Exchange SE requirements | Microsoft Learn)
- Visual C++ Redistributable 2012
- Visual C++ Redistributable 2013
- Unified Communications Managed API 4.0 Runtime
- IIS URL Rewrite Modul
- (IIS Crypto)
# Install-ExchangeSEPreRequirements.ps1
#
#
# IIS Crypto:
# https://www.nartac.com/Downloads/IISCrypto/IISCryptoCli.exe
# Visual C++ Redistributable 2012 Update 4:
# https://download.microsoft.com/download/1/6/b/16b06f60-3b20-4ff2-b699-5e9b7962f9ae/VSU_4/vcredist_x64.exe
# Visual C++ Redistributable 2013:
# https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe
# Unified Communications Managed API 4.0 Runtime:
# https://download.microsoft.com/download/2/c/4/2c47a5c1-a1f3-4843-b9fe-84c0032c61ec/UcmaRuntimeSetup.exe
# IIS URL Rewrite:
# https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi
# Windows Server roles:
# 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
$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
}
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"
Start-Process -FilePath "C:\_install\IISCryptoCli.exe" `
-ArgumentList "/template best"`
-NoNewWindow `
-Wait
Write-Host "Installing Visual C++ Redistributable 2012"
Start-Process -FilePath "C:\_install\VC2012x64.exe" `
-ArgumentList "/install /quiet /norestart" `
-NoNewWindow `
-Wait
Write-Host "Installing Visual C++ Redistributable 2013:"
Start-Process -FilePath "C:\_install\VC2013x64.exe" `
-ArgumentList "/install /quiet /norestart" `
-NoNewWindow `
-Wait
Write-Host "Installing Unified Communications Managed API 4.0 Runtime"
Start-Process -FilePath "C:\_install\UcmaRuntimeSetup.exe" `
-ArgumentList "/quiet /norestart" `
-NoNewWindow `
-Wait
Write-Host "Installing IIS URL Rewrite module"
Start-Process -FilePath "C:\Windows\System32\msiexec.exe" `
-ArgumentList "/i `"C:\_install\rewrite_amd64_en-US.msi`"/quiet /norestart" `
-NoNewWindow `
-Wait
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
$ExchSRV2016 = Get-ADGroupMember -Identity "Exchange Servers" |
Where-Object { $_.objectClass -eq "computer" -and $_.Name -ne $env:COMPUTERNAME }
$ExchSRV2016 = Get-ADComputer -Identity $ExchSRV2016.distinguishedName `
-Properties DNSHostName
$ExchSRV2016IP = Resolve-DnsName -Name $ExchSRV2016.DNSHostName `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
$Exch2016ConUri = -join("http://", `
$ExchSRV2016.DNSHostName , `
"/PowerShell" `
)
Write-Host "Connecting to Exchange PowerShell Session `"$Exch2016ConUri`""
$ExchSession = New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri $Exch2016ConUri
Import-PSSession -Session $ExchSession -DisableNameChecking `
-AllowClobber |
Out-Null
Write-Host "Getting accepted domains"
$SMTPDoms = Get-AcceptedDomain |
Where-Object { $_.DomainName -ne $DNSDomain }
$defaultSMTPDom = Get-AcceptedDomain |
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"
$ExchDNS = -join(
"outlook.",
$defaultSMTPDom.DomainName
)
$cusExchDNS = Read-Host -Prompt "Client access name (<ENTER> to use `"$ExchDNS`")"
if(-not [string]::IsNullOrEmpty($cusExchDNS)){
$ExchDNS = $cusExchDNS
}
$newSANCert += $ExchDNS
Write-Host "Creating DNS zone `"$ExchDNS`" on server `"$DNSServer`""
Add-DnsServerPrimaryZone -ComputerName $DNSServer `
-Name $ExchDNS `
-DynamicUpdate None `
-ReplicationScope Forest
Write-Host "Creating A-Record `"$ExchSRV2016IP`" in zone `"$ExchDNS`" on server `"$DNSServer`""
Add-DnsServerResourceRecordA -Name "." `
-IPv4Address $ExchSRV2016IP `
-ZoneName $ExchDNS `
-ComputerName $DNSServer
foreach($d in $SMTPDoms){
$tmpAutoD = -join(
"autodiscover.",
$d.DomainName
)
Write-Host "Creating DNS zone `"$tmpAutoD`" on server `"$DNSServer`""
Add-DnsServerPrimaryZone -ComputerName $DNSServer `
-Name $tmpAutoD `
-DynamicUpdate None `
-ReplicationScope Forest
Write-Host "Creating A-Record `"$ExchSRV2016IP`" in zone `"$tmpAutoD`" on server `"$DNSServer`""
Add-DnsServerResourceRecordA -Name "." `
-IPv4Address $ExchSRV2016IP `
-ZoneName $tmpAutoD `
-ComputerName $DNSServer
$newSANCert += $tmpAutoD
}
Write-Host "Create new Exchange certificate with subject `"$ExchDNS`""
$newSANCert += $ExchSRVSE.DNSHostName, $ExchSRV2016.DNSHostName, $ExchSRVSE.Name, $ExchSRV2016.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`""
if(-not(Test-Path -Path "C:\_install" -PathType Container)){
New-Item -Path "C:\" `
-Name "_install" `
-ItemType Directory |
Out-Null
}
Export-Certificate -Cert $newExchCert `
-Type CERT `
-FilePath "C:\_install\ExchCert_$ExchDNS.cer" `
-Confirm:$false `
-Force
Copy-Item -Path "C:\_install\ExchCert_$ExchDNS.cer" `
-Destination "\\$($ExchSRV2016.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 $ExchSRV2016.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 $($ExchSRV2016.Name) trusted roots"
Invoke-Command -ComputerName $ExchSRV2016.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 $ExchSRV2016.DNSHostName
Write-Host "`nCopy & paste thumbprint of current client access certificate"
$curCert = Read-Host -Prompt "Thumbprint"
$curCert = Get-ExchangeCertificate -Thumbprint $curCert
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 $ExchSRV2016.Name
[System.IO.File]::WriteAllBytes("C:\_install\ExchCert_$ExchDNS.pfx", $PFXExchCert.FileData)
Write-Host "Exporting Urls of virtual directories to `"C:\_install\vDirs.csv`""
$OWAUrl = Get-OwaVirtualDirectory -Server $ExchSRV2016.DNSHostName |
Select-Object InternalUrl -ExpandProperty InternalUrl
$ECPUrl = Get-EcpVirtualDirectory -Server $ExchSRV2016.DNSHostName |
Select-Object InternalUrl -ExpandProperty InternalUrl
$OABUrl = Get-OabVirtualDirectory -Server $ExchSRV2016.DNSHostName |
Select-Object InternalUrl -ExpandProperty InternalUrl
$EASUrl = Get-ActiveSyncVirtualDirectory -Server $ExchSRV2016.DNSHostName |
Select-Object InternalUrl -ExpandProperty InternalUrl
$EWSUrl = Get-WebServicesVirtualDirectory -Server $ExchSRV2016.DNSHostName |
Select-Object InternalUrl -ExpandProperty InternalUrl
$MapiUrl = Get-MapiVirtualDirectory -Server $ExchSRV2016.DNSHostName |
Select-Object InternalUrl -ExpandProperty InternalUrl
$CASUri = Get-ClientAccessService -Identity $ExchSRV2016.DNSHostName |
Select-Object AutodiscoverServiceInternalUri -ExpandProperty AutodiscoverServiceInternalUri
$OAUrl = Get-OutlookAnywhere -Server $ExchSRV2016.DNSHostName |
Select-Object InternalHostName -ExpandProperty InternalHostName
Set-Content -Path "C:\_install\vDirs.csv" `
-Value "vDir;Url"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "OWA;$($OWAUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "ECP;$($ECPUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "OAB;$($OABUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "EAS;$($EASUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "EWS;$($EWSUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "MAPI;$($MapiUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "CAS;$($CASUri.AutoDiscoverServiceInternalUri)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "OA;$($OAUrl)"
}
Write-Host "Closing session to `"$Exch2016ConUri`""
Remove-PSSession -Session $ExchSession
Write-Host "Server will reboot after pressing <Enter>"
pause
Restart-Computer
Jetzt ist es Zeit für einen Break. ☕ Idealerweise warten wir bis zum nächsten Tag, damit alle Clients dem neu erstellten und selbst signierten Zertifikat für den Exchange Server 2016 sowie den neuen Exchange Subscription Edition vertrauen.
Einrichtung von Split DNS auf dem alten Exchange Server
Das nächste Script „Configure-SplitDNS.ps1“ wird natürlich nur benötigt, sofern Split DNS bislang nicht im Einsatz war.
# Configure-SplitDNS.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
$ExchSRV2016 = Get-ADGroupMember -Identity "Exchange Servers" |
Where-Object { $_.objectClass -eq "computer" -and $_.Name -ne $env:COMPUTERNAME }
$ExchSRV2016 = Get-ADComputer -Identity $ExchSRV2016.distinguishedName `
-Properties DNSHostName
$ExchSRV2016IP = Resolve-DnsName -Name $ExchSRV2016.DNSHostName `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
$Exch2016ConUri = -join("http://", `
$ExchSRV2016.DNSHostName , `
"/PowerShell" `
)
Write-Host "Connecting to Exchange PowerShell Session `"$Exch2016ConUri`""
$ExchSession = New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri $Exch2016ConUri
Import-PSSession -Session $ExchSession -DisableNameChecking `
-AllowClobber |
Out-Null
$newExchCert = Get-ExchangeCertificate -Server $ExchSRV2016.DNSHostName |
Where-Object { $_.DnsNameList.Unicode -contains $ExchSRV2016.DNSHostName -and $_.DnsNameList.Unicode -contains $ExchSRVSE.DNSHostName} |
Sort-Object NotBefore -Descending |
Select-Object -First 1
Write-Host "Enabling new Exchange certificate for all Services (IIS, IMAP, POP, SMTP)"
Enable-ExchangeCertificate -Thumbprint $newExchCert.Thumbprint `
-Services IIS, IMAP, POP, SMTP `
-Confirm:$false `
-Force
$ExchDNS = $newExchCert.DnsNameList |
Select-Object Unicode -ExpandProperty Unicode `
-First 1
$OWAUrl = -join ("https://", `
$ExchDNS, `
"/owa" `
)
Write-Host "Setting OWA internal / external Url to `"$OWAUrl`""
$EcpUrl = -join ("https://", `
$ExchDNS, `
"/ecp" `
)
Write-Host "Setting ECP internal / external Url to `"$EcpUrl`""
$OABUrl = -join ("https://", `
$ExchDNS, `
"/OAB" `
)
Write-Host "Setting OAB internal / external Url to `"$OABUrl`""
$EASUrl = -join ("https://", `
$ExchDNS, `
"/Microsoft-Server-ActiveSync" `
)
Write-Host "Setting EAS internal / external Url to `"$EASUrl`""
$EWSUrl = -join ("https://", `
$ExchDNS, `
"/EWS/Exchange.asmx" `
)
Write-Host "Setting EWS internal / external Url to `"$EWSUrl`""
$MapiUrl = -join ("https://", `
$ExchDNS, `
"/mapi" `
)
Write-Host "Setting MAPI internal / external Url to `"$MapiUrl`""
$CASUri = -join ("https://", `
$ExchDNS, `
"/autodiscover/autodiscover.xml" `
)
Write-Host "Setting CAS service to `"$CASUri`""
$OAUrl = $ExchDNS
Write-Host "Setting Outlook Anywhere host to `"$OAUrl`""
Get-OwaVirtualDirectory -Server $ExchSRV2016.DnsHostName |
Set-OwaVirtualDirectory -InternalUrl $OWAUrl `
-ExternalUrl $OWAUrl
Get-EcpVirtualDirectory -Server $ExchSRV2016.DnsHostName |
Set-EcpVirtualDirectory -InternalUrl $EcpUrl `
-ExternalUrl $EcpUrl
Get-OABVirtualDirectory -Server $ExchSRV2016.DnsHostName |
Set-OABVirtualDirectory -InternalURL $OABUrl `
-ExternalUrl $OABUrl
Get-ActiveSyncVirtualDirectory -Server $ExchSRV2016.DnsHostName |
Set-ActiveSyncVirtualDirectory -InternalURL $EASUrl `
-ExternalUrl $EASUrl
Get-WEbServicesVirtualDirectory -Server $ExchSRV2016.DnsHostName |
Set-WEbServicesVirtualDirectory -InternalURL $EWSUrl `
-ExternalUrl $EWSUrl
Get-MapiVirtualDirectory -Server $ExchSRV2016.DnsHostName |
Set-MapiVirtualDirectory -InternalURL $MapiUrl `
-ExternalUrl $MapiUrl
Get-ClientAccessService -Identity $ExchSRV2016.DnsHostName |
Set-ClientAccessService -AutodiscoverServiceInternalUri $CASUri
Get-OutlookAnywhere -Server $ExchSRV2016.DnsHostName |
Set-OutlookAnywhere -InternalHostname $OAUrl `
-InternalClientsRequireSsl:$true `
-ExternalHostname $OAUrl `
-ExternalClientsRequireSsl:$true `
-ExternalClientAuthenticationMethod "Negotiate"
Write-Host "Exporting Urls of virtual directories to `"C:\_install\vDirs.csv`""
Set-Content -Path "C:\_install\vDirs.csv" `
-Value "vDir;Url"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "OWA;$($OWAUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "ECP;$($ECPUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "OAB;$($OABUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "EAS;$($EASUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "EWS;$($EWSUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "MAPI;$($MapiUrl.InternalUrl)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "CAS;$($CASUri.AutoDiscoverServiceInternalUri)"
Add-Content -Path "C:\_install\vDirs.csv" `
-Value "OA;$($OAUrl)"
$curOrgConfig = Get-OrganizationConfig
if(-not $curOrgConfig.MapiHttpEnabled){
Write-Host "Enabling Mapi over Http"
Set-OrganizationConfig -MapiHttpEnabled:$true `
-Confirm:$false
}
Write-Host "Closing session to `"$Exch2016ConUri`""
Remove-PSSession -Session $ExchSession
Damit wäre der alte Exchange Server 2016 auch schon zur Migration auf Exchange SE vorbereitet. 🙂 Im nächsten Schritt installieren wir den Exchange Server SE per „Install-ExchangeSE.ps1“ auf einen frisch installierten und gepatchten Windows Server 2025 und nehmen die Grundkonfiguration vor.
Installation und Grundkonfiguration Exchange SE
# Install-ExchangeSE.ps1
#
#
# Exchange Server Subscription Edition RTM
# https://download.microsoft.com/download/ad9b1e53-f28e-4a82-987d-6c88803795b2/ExchangeServerSE-x64.iso
$ExchSEUri = "https://download.microsoft.com/download/ad9b1e53-f28e-4a82-987d-6c88803795b2/ExchangeServerSE-x64.iso"
if(-not(Test-Path -Path "C:\_install\ExchSERTM" -PathType Container)){
New-Item -Path "C:\_install" `
-Name "ExchSERTM" `
-ItemType Directory `
-Confirm:$false `
-Force |
Out-Null
}
Start-BitsTransfer -Source $ExchSEUri `
-Destination "C:\_install\ExchSERTM\ExchangeServerSE-x64.iso"
Unblock-File -Path "C:\_install\ExchSERTM\ExchangeServerSE-x64.iso"
Write-Host "Mounting DVD Image `"C:\_install\ExchSERTM\ExchangeServerSE-x64.iso`""
$mdi = Mount-DiskImage -ImagePath "C:\_install\ExchSERTM\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-Output "Check setup logs in `"C:\ExchangeSetupLogs`" before continuing by pressing <Enter>"
pause
}
Rename-Item -Path "C:\ExchangeSetupLogs" `
-NewName "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-Output "Check setup logs in `"C:\ExchangeSetupLogs`" before continuing by pressing <Enter>"
pause
}
Rename-Item -Path "C:\ExchangeSetupLogs" `
-NewName "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
$ExchSRV2016 = Get-ADGroupMember -Identity "Exchange Servers" |
Where-Object { $_.objectClass -eq "computer" -and $_.Name -ne $env:COMPUTERNAME }
$ExchSRV2016 = Get-ADComputer -Identity $ExchSRV2016.distinguishedName `
-Properties DNSHostName
$ExchSRV2016IP = Resolve-DnsName -Name $ExchSRV2016.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
Write-Host "Enabling Exchange certificate on $($ExchSRVSE.Name)"
Enable-ExchangeCertificate -Server $ExchSRVSE.DNSHostName `
-Thumbprint $ExchCert.Thumbprint `
-Services IIS, IMAP, POP, SMTP `
-Confirm:$false `
-Force
$vDirCsv = Import-Csv -Path "C:\_install\vDirs.csv" `
-Delimiter ";"
ForEach($vd in $vDirCsv){
switch ($vd.vDir){
"OWA" {
Write-Host "Configuring Outlook Web App to `"$($vd.Url)`""
Get-OwaVirtualDirectory -Server $ExchSRVSE.DNSHostName |
Set-OwaVirtualDirectory -InternalUrl $vd.Url `
-ExternalUrl $vd.Url
}
"ECP" {
Write-Host "Configuring Exchange Control Panel to `"$($vd.Url)`""
Get-EcpVirtualDirectory -Server $ExchSRVSE.DnsHostName |
Set-EcpVirtualDirectory -InternalUrl $vd.Url `
-ExternalUrl $vd.Url
}
"OAB" {
Write-Host "Configuring Offline Address Book to `"$($vd.Url)`""
Get-OABVirtualDirectory -Server $ExchSRVSE.DnsHostName |
Set-OABVirtualDirectory -InternalURL $vd.Url `
-ExternalUrl $vd.Url
}
"EAS" {
Write-Host "Configuring Exchange Active-Sync to `"$($vd.Url)`""
Get-ActiveSyncVirtualDirectory -Server $ExchSRVSE.DnsHostName |
Set-ActiveSyncVirtualDirectory -InternalURL $vd.Url `
-ExternalUrl $vd.Url
}
"EWS" {
Write-Host "Configuring Echange WebServices to `"$($vd.Url)`""
Get-WEbServicesVirtualDirectory -Server $ExchSRVSE.DnsHostName |
Set-WEbServicesVirtualDirectory -InternalURL $vd.Url `
-ExternalUrl $vd.Url
}
"MAPI" {
Write-Host "Configuring MAPI/HTTP to `"$($vd.Url)`""
Get-MapiVirtualDirectory -Server $ExchSRVSE.DnsHostName |
Set-MapiVirtualDirectory -InternalURL $vd.Url `
-ExternalUrl $vd.Url
}
"CAS" {
Write-Host "Configuring Client Access Service to `"$($vd.Url)`""
Get-ClientAccessService -Identity $ExchSRVSE.DnsHostName |
Set-ClientAccessService -AutodiscoverServiceInternalUri $vd.Url
}
"OA" {
Write-Host "Configuring Outlook Anywhere to `"$($vd.Url)`""
Get-OutlookAnywhere -Server $ExchSRVSE.DnsHostName |
Set-OutlookAnywhere -InternalHostname $vd.Url `
-InternalClientsRequireSsl:$true `
-ExternalHostname $OAUrl `
-ExternalClientsRequireSsl:$true `
-ExternalClientAuthenticationMethod "Negotiate"
}
}
}
$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
Remove-PSSession -Session $ExchSession
Write-Host "Server will reboot after pressing <Enter>"
pause
Restart-Computer
Kommen wir zum letzten Schritt mit dem Script „Move-MailboxesToExchangeSE.ps1“ und verschieben die Postfächer vom Exchange Server 2016 auf den Exchange Server SE.
Verschieben der Postfächer
# 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
$ExchSRV2016 = Get-ADGroupMember -Identity "Exchange Servers" |
Where-Object { $_.objectClass -eq "computer" -and $_.Name -ne $env:COMPUTERNAME }
$ExchSRV2016 = Get-ADComputer -Identity $ExchSRV2016.distinguishedName `
-Properties DNSHostName
$ExchSRV2016IP = Resolve-DnsName -Name $ExchSRV2016.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
$ExchSEDB = Get-MailboxDatabase -Server $ExchSRVSE.Name
Write-Host "Enabling circular logging for mailbox moves"
Set-MailboxDatabase $ExchSEDB.Name `
-CircularLoggingEnabled:$true
Restart-Service MSExchangeIS
Write-Host "Moving Arbitration, Audit log and Discoverysearch Mailbox to `"$($ExchSRVSE.Name)`""
Get-Mailbox -Arbitration `
-Server $ExchSRV2016.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name
Get-Mailbox -AuditLog `
-Server $ExchSRV2016.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name
Get-Mailbox -RecipientTypeDetails "DiscoveryMailbox" `
-Server $ExchSRV2016.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name
Write-Host "Moving Admin Mailbox"
Get-Mailbox -Identity $env:USERNAME |
Where-Object { $_.ServerName -eq $ExchSRV2016.Name } |
New-MoveRequest -TargetDatabase $ExchSEDB.Name
do{
$curMoveReqs = Get-MoveRequest |
Where-Object { $_.Status -ne "Completed" }
Get-MoveRequest -MoveStatus "InProgress" |
Get-MoveRequestStatistics |
ft DisplayName, PercentComplete
Write-Host "`nWaiting to complete running move request...`n"
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 $ExchSRV2016.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name `
-CompleteAfter $CompletionDate
Get-Mailbox -Server $ExchSRV2016.Name |
New-MoveRequest -TargetDatabase $ExchSEDB.Name.Name `
-CompleteAfter $CompletionDate
do{
$curMoveReqs = Get-MoveRequest |
Where-Object { $_.Status -ne "Completed" }
Write-Host "Waiting for completion $CompletionDate (UTC)`n"
Start-Sleep -Seconds 60
} until($null -eq $curMoveReqs)
Write-Host "Disabling circular logging for mailbox moves"
Set-MailboxDatabase $ExchSEDB.Name `
-CircularLoggingEnabled:$false
Restart-Service MSExchangeIS
Write-Host "Getting accepted domains"
$SMTPDoms = Get-AcceptedDomain |
Where-Object { $_.DomainName -ne $DNSDomain }
$vDirCsv = Import-Csv -Path "C:\_install\vDirs.csv" `
-Delimiter ";"
$DNSZones = @()
ForEach($u in $vDirCsv.Url){
$DNSZones += ([uri]$u).Host
}
foreach($Zone in $DNSZones | Select-Object -Unique){
Write-Host "Setting A-Record `"$ExchSRVSEIP`" in zone `"$Zone`" on server `"$DNSServer`""
Add-DnsServerResourceRecordA -Name "." `
-IPv4Address $ExchSRVSEIP `
-ZoneName $Zone `
-ComputerName $DNSServer
Remove-DnsServerResourceRecord -ZoneName $Zone `
-RRType A `
-Name "." `
-RecordData $ExchSRV2016IP `
-ComputerName $DNSServer `
-Confirm:$false `
-Force
}
foreach($d in $SMTPDoms){
$tmpAutoD = -join(
"autodiscover.",
$d.DomainName
)
Write-Host "Setting A-Record `"$ExchSRVSEIP`" in zone `"$tmpAutoD`" on server `"$DNSServer`""
Add-DnsServerResourceRecordA -Name "." `
-IPv4Address $ExchSRVSEIP `
-ZoneName $tmpAutoD `
-ComputerName $DNSServer
Remove-DnsServerResourceRecord -ZoneName $tmpAutoD `
-RRType A `
-Name "." `
-RecordData $ExchSRV2016IP `
-ComputerName $DNSServer `
-Confirm:$false `
-Force
}
Schreibe einen Kommentar