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_CU12", "2016_CU23")]
        [string]
        $Version,

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

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

    if ((Test-Path -Path $Outpath) -eq $false) {
        New-Item -Path $Outpath -Force -ItemType Directory | Out-Null
    }

    $ExchangeCUFilePath = "$($Outpath)\Exchange-$($Version).iso"

    switch ($Version) {
        "2019_CU12" { $DownloadURL = "https://www.microsoft.com/en-us/download/confirmation.aspx?id=104131" }
        "2016_CU23" { $DownloadURL = "https://www.microsoft.com/en-us/download/confirmation.aspx?id=104132" }
        Default { throw "no version was selected or not supported" }
    }

    try {
        $Content = Invoke-WebRequest -UseBasicParsing -Uri $DownloadURL
        $UpdateLink = ($Content.Links | Where-Object -FilterScript { $PSItem.href -like "*download.microsoft.com*" -and $PSItem.outerHTML -like "*download manually*" }).href

        Start-FileDownload -DownloadURL $UpdateLink -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)"
    }
}