Migration von Exchange 2016 zu Exchange SE (Subscription Edition)

Exchange SE - Migration von Exchange 2016
Exchange SE – Migration von Exchange 2016

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
# 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
}

Die gesuchte Lösung noch nicht gefunden oder benötigen Sie Hilfe bei anderen Themen aus meinem Blog? Nehmen Sie gerne Kontakt mit mir bzw. meinem Unternehmen Jan Mischo IT auf. Ich freue mich auf Ihre Anfrage: https://janmischo.it/kontakt/


+49 2801 7004300

info@janmischo.it


Beitrag veröffentlicht

in

, , ,

von

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Seite verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden..