NTS.Tools.MSExchange.psm1

function Get-ExchangeCU {
    <#
        .Description
        downloads exchange cu from microsoft
 
        .Parameter Version
        version of the cu
 
        .Parameter Outpath
        path where the cu is stored
 
        .Example
        # stores the cu to $Outpath
        Get-ExchangeCU -Version 2019_CU12 -Outpath $Outpath
 
        .NOTES
        https://learn.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates?view=exchserver-2019
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("2019_CU13", "2019_CU12", "2016_CU23")]
        [string]
        $Version,

        [Parameter(Mandatory = $true)]
        [string]
        $Outpath
    )

    $ErrorActionPreference = 'Stop'
    if ($Outpath[-1] -eq "\") {
        $Outpath = $Outpath.Substring(0, $Outpath.Length - 1)
    }

    New-ItemIfNotExists -Path $Outpath -ItemType Directory
    $ExchangeCUFilePath = "$($Outpath)\Exchange-$($Version).iso"

    switch ($Version) {
        "2019_CU13" { $DownloadURL = "https://download.microsoft.com/download/7/5/f/75f4d77e-002c-419c-a03a-948e8eb019f2/ExchangeServer2019-x64-CU13.ISO" }
        "2019_CU12" { $DownloadURL = "https://download.microsoft.com/download/b/c/7/bc766694-8398-4258-8e1e-ce4ddb9b3f7d/ExchangeServer2019-x64-CU12.ISO" }
        "2016_CU23" { $DownloadURL = "https://download.microsoft.com/download/8/d/2/8d2d01b4-5bbb-4726-87da-0e331bc2b76f/ExchangeServer2016-x64-CU23.ISO" }
        Default { throw "no version was selected or not supported" }
    }

    try {
        Start-FileDownload -DownloadURL $DownloadURL -FileOutPath $ExchangeCUFilePath
    }
    catch {
        throw "$($env:COMPUTERNAME): error getting exchange cu files - $($PSItem.Exception.Message)"
    }

    Write-Output "$($env:COMPUTERNAME): finished download - check folder $($Outpath)"
}

function Install-ExchangePrerequisites {
    <#
        .Description
        use this function to install exchange windows feature prerequesits
 
        .Example
        # install exchange windows feature prerequesits
        Install-ExchangePrerequisites
 
        .NOTES
        https://learn.microsoft.com/en-us/exchange/plan-and-deploy/prerequisites?view=exchserver-2019
    #>


    $Features = @(
        "Server-Media-Foundation"
        "NET-Framework-45-Features"
        "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-Lgcy-Mgmt-Console"
        "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"
    )
    
    try {
        Write-Output "$($env:COMPUTERNAME): installing required features for exchange"
        Install-WindowsFeature -Name $Features | Out-Null
    }
    catch {
        throw $PSItem.Exception.Message
    }
}

function Initialize-ADForExchange {
    <#
        .Description
        used the exchange setup.exe to prepare ad for exchange installation or upgrade
 
        .Parameter SetupPath
        path to the exchange setup.exe
 
        .Parameter OrganizationName
        name of the exchange organization
 
        .Example
        # prepares active directory for the organization mw corp org
        Initialize-ADForExchange -SetupPath "$($Volume.DriveLetter):\Setup.exe" -OrganizationName "mw corp org"
 
        .NOTES
         
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $SetupPath,

        [Parameter(Mandatory = $true)]
        [string]
        $OrganizationName
    )
    
    if (Test-Path -Path $SetupPath) {
        try {
            Write-Output "$($env:COMPUTERNAME): preparing ad - check logs at $($env:SystemDrive)\ExchangeSetupLogs for more details"
            if ($OrganizationName -eq "") {
                $Process = Start-Process -FilePath $SetupPath -ArgumentList "/PrepareAD /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF" -Wait -NoNewWindow -PassThru
            }
            else {
                $Process = Start-Process -FilePath $SetupPath -ArgumentList "/PrepareAD /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /OrganizationName:`"$($OrganizationName)`"" -Wait -NoNewWindow -PassThru
            }
            if ($Process.ExitCode -ne 0) {
                $MessageCount = 5
                $Content = Get-Content -Path "$($env:SystemDrive)\ExchangeSetupLogs\ExchangeSetup.log"
                $Message = ($Content | Select-String -Pattern "FAILED") | Select-Object -Last $MessageCount | ForEach-Object { $PSItem.ToString() }
                Write-Output "---"
                Write-Output "error preparing ad - last $($MessageCount) messages with 'failed':"
                Write-Output $Message
                throw "check logs at $($env:SystemDrive)\ExchangeSetupLogs for more details"
            }
            Write-Output "$($env:COMPUTERNAME): finished preparing ad"
        }
        catch {
            throw $PSItem.Exception.Message
        }
    }
    else {
        throw "cannot find $($SetupPath)"
    }
}

function Initialize-SchemaForExchange {
    <#
        .Description
        used the exchange setup.exe to prepare schema of ad for exchange installation or upgrade
 
        .Parameter SetupPath
        path to the exchange setup.exe
 
        .Example
        # prepares active directory schema for exchange install
        Initialize-SchemaForExchange -SetupPath "$($Volume.DriveLetter):\Setup.exe"
 
        .NOTES
         
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $SetupPath
    )
    
    if (Test-Path -Path $SetupPath) {
        try {
            Write-Output "$($env:COMPUTERNAME): preparing schema - check logs at $($env:SystemDrive)\ExchangeSetupLogs for more details"
            $Process = Start-Process -FilePath $SetupPath -ArgumentList "/PrepareSchema /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF" -Wait -NoNewWindow -PassThru
            if ($Process.ExitCode -ne 0) {
                $MessageCount = 5
                $Content = Get-Content -Path "$($env:SystemDrive)\ExchangeSetupLogs\ExchangeSetup.log"
                $Message = ($Content | Select-String -Pattern "FAILED") | Select-Object -Last $MessageCount | ForEach-Object { $PSItem.ToString() }
                Write-Output "---"
                Write-Output "error schema - last $($MessageCount) messages with 'failed':"
                Write-Output $Message
                throw "check logs at $($env:SystemDrive)\ExchangeSetupLogs for more details"
            }
            Write-Output "$($env:COMPUTERNAME): finished preparing schema"
        }
        catch {
            throw $PSItem.Exception.Message
        }
    }
    else {
        throw "cannot find $($SetupPath)"
    }
}

function Install-Exchange {
    <#
        .Description
        used the exchange setup.exe install exchange on the local server
 
        .Parameter SetupPath
        path to the exchange setup.exe
 
        .Example
        # installs exchange on the local server
        Install-Exchange -SetupPath "$($Volume.DriveLetter):\Setup.exe"
 
        .NOTES
         
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $SetupPath
    )
    
    if (Test-Path -Path $SetupPath) {
        Write-Output "$($env:COMPUTERNAME): installing exchange - check logs at $($env:SystemDrive)\ExchangeSetupLogs for more details"
        Write-Output "$($env:COMPUTERNAME): this can take a while"
        $Process = Start-Process -FilePath $SetupPath -ArgumentList "/m:install /roles:mb /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF /InstallWindowsComponents" -Wait -NoNewWindow -PassThru
        if ($Process.ExitCode -ne 0) {
            $Content = Get-Content -Path "$($env:SystemDrive)\ExchangeSetupLogs\ExchangeSetup.log"
            $Message = ($Content | Select-String -Pattern "ERROR")[-1].ToString()
            throw "error installing exchange - $($Message)"
        }
        Write-Output "$($env:COMPUTERNAME): finished installing exchange"
    }
    else {
        throw "cannot find $($SetupPath)"
    }
}