Modules/ExchangeDscCalculatorHelper/ExchangeDscCalculatorHelper.psm1

<#
    .SYNOPSIS
        Reads a Servers.csv file generated by the Exchange Server Role
        Requirements Calculator, and generates a Database Map which can be
        used in the DiskToDBMap parameter of the ExchAutoMountPoint resource.
 
    .PARAMETER ServersCsvPath
        The full path to the Servers.csv file to read.
 
    .PARAMETER ServerNameInCsv
        The server name within the Server.csv file to look up database
        settings for.
 
    .PARAMETER DbNameReplacements
        A Hashtable containing case sensitive replacements to make to the
        databases discoverd for this server. For each Hashtable key/value pair,
        the key is the content to look for in the original string, and the
        value is what that content should be changed to. If multiple changes
        are requested, the order that the replacements are made in is not
        guaranteed.
#>

function Get-DBMapFromServersCsv
{
    [CmdletBinding()]
    [OutputType([System.String[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ServersCsvPath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ServerNameInCsv,

        [Parameter()]
        [System.Collections.Hashtable]
        $DbNameReplacements = @{ }
    )

    if (!(Test-Path -Path $ServersCsvPath))
    {
        throw 'Unable to access file specified in ServersCsvPath'
    }

    [System.Object[]] $serverData = Import-Csv -Path $ServersCsvPath | Where-Object -FilterScript { $_.ServerName -like $ServerNameInCsv }

    if ($serverData.Count -ne 1)
    {
        throw 'Failed to find single entry for server in Servers.Csv file'
    }

    $dbPerVolume = $serverData.DbPerVolume

    if ($null -eq $dbPerVolume -or $dbPerVolume -le 0)
    {
        throw 'DbPerVolume for server is null or less than 0'
    }

    # Turn the DbMap from string into an array
    if ([String]::IsNullOrEmpty($serverData.DbMap))
    {
        throw 'No data specified in DbMap for server'
    }

    [System.String[]] $dbMapIn = $serverData.DbMap.Split(',')

    # Determine the appropriate size for the output array
    $disksRequired = $dbMapIn.Count / $dbPerVolume

    # Create the output variable
    [System.String[]] $dbMapOut = New-Object -TypeName 'System.String[]' -ArgumentList $disksRequired

    # Loop through the DbMap in increments of dbPerVolume and figure out which DB's will go on a single disk
    for ($i = 0; $i -lt $dbMapIn.Count; $i += $dbPerVolume)
    {
        [System.Text.StringBuilder] $diskBuilder = New-Object -TypeName System.Text.StringBuilder

        # Loop through the individual DB's for this disk
        for ($j = $i; $j -lt $i + $dbPerVolume; $j++)
        {
            # This isn't the first DB on the disk so prepend a comma
            if ($j -gt $i)
            {
                $diskBuilder.Append(',') | Out-Null
            }

            # Make any requested replacements in the DB name
            $currentDb = Update-StringContent -StringIn $dbMapIn[$j] -Replacements $DbNameReplacements

            # Add the db to the current disk string
            $diskBuilder.Append($currentDb) | Out-Null
        }

        # Add the finished disk to the output variable
        $dbMapOut[$i / $dbPerVolume] = $diskBuilder.ToString()
    }

    return $dbMapOut
}

<#
    .SYNOPSIS
        Reads a MailboxDatabases.csv file generated by the Exchange Server Role
        Requirements Calculator, finds the databases within the .CSV file
        corresponding to the specified server, and returns an array of objects
        containing these databases and their properties.
 
    .PARAMETER MailboxDatabasesCsvPath
        The full path to the MailboxDatabases.csv file to read.
 
    .PARAMETER ServerNameInCsv
        The server name within the MailboxDatabases.csv file to look up
        database settings for.
 
    .PARAMETER DbNameReplacements
        A Hashtable containing case sensitive replacements to make to the
        databases discoverd for this server. For each Hashtable key/value pair,
        the key is the content to look for in the original string, and the
        value is what that content should be changed to. If multiple changes
        are requested, the order that the replacements are made in is not
        guaranteed.
#>

function Get-DBListFromMailboxDatabasesCsv
{
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSObject[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $MailboxDatabasesCsvPath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ServerNameInCsv,

        [Parameter()]
        [System.Collections.Hashtable]
        $DbNameReplacements = @{ }
    )

    if (!(Test-Path -Path $MailboxDatabasesCsvPath))
    {
        throw 'Unable to access file specified in MailboxDatabasesCsvPath'
    }

    [System.Object[]] $relevantDBs = Import-Csv -Path $MailboxDatabasesCsvPath | Where-Object -FilterScript { $_.Server -like $ServerNameInCsv }

    # Create the output variable
    [System.Management.Automation.PSObject[]] $dbList = New-Object -TypeName 'System.Management.Automation.PSObject[]' -ArgumentList $relevantDBs.Count

    # Loop through each database, and if it belongs to this server, at it to the list
    for ($i = 0; $i -lt $relevantDBs.Count; $i++)
    {
        $dbIn = $relevantDBs[$i]

        # Build a custom object to hold all the DB props
        $currentDBProps = @{
            Name                              = Update-StringContent -StringIn $dbIn.Name -Replacements $DbNameReplacements
            LogFolderPath                     = Update-StringContent -StringIn $dbIn.LogFolderPath -Replacements $DbNameReplacements
            DeletedItemRetention              = $dbIn.DeletedItemRetention
            GC                                = $dbIn.GC
            OAB                               = $dbIn.OAB
            RetainDeletedItemsUntilBackup     = $dbIn.RetainDeletedItemsUntilBackup
            IndexEnabled                      = $dbIn.IndexEnabled
            CircularLoggingEnabled            = $dbIn.CircularLoggingEnabled
            ProhibitSendReceiveQuota          = $dbIn.ProhibitSendReceiveQuota
            ProhibitSendQuota                 = $dbIn.ProhibitSendQuota
            IssueWarningQuota                 = $dbIn.IssueWarningQuota
            AllowFileRestore                  = $dbIn.AllowFileRestore
            BackgroundDatabaseMaintenance     = $dbIn.BackgroundDatabaseMaintenance
            IsExcludedFromProvisioning        = $dbIn.IsExcludedFromProvisioning
            IsSuspendedFromProvisioning       = $dbIn.IsSuspendedFromProvisioning
            MailboxRetention                  = $dbIn.MailboxRetention
            MountAtStartup                    = $dbIn.MountAtStartup
            EventHistoryRetentionPeriod       = $dbIn.EventHistoryRetentionPeriod
            AutoDagExcludeFromMonitoring      = $dbIn.AutoDagExcludeFromMonitoring
            CalendarLoggingQuota              = $dbIn.CalendarLoggingQuota
            IsExcludedFromInitialProvisioning = $dbIn.IsExcludedFromInitialProvisioning
            DataMoveReplicationConstraint     = $dbIn.DataMoveReplicationConstraint
            RecoverableItemsQuota             = $dbIn.RecoverableItemsQuota
            RecoverableItemsWarningQuota      = $dbIn.RecoverableItemsWarningQuota
        }

        if ($null -ne $dbIn.DBFilePath)
        {
            $currentDBProps.Add('DBFilePath', (Update-StringContent -StringIn $dbIn.DBFilePath -Replacements $DbNameReplacements))
        }
        elseif ($null -ne $dbIn.EDBFilePath)
        {
            $currentDBProps.Add('DBFilePath', (Update-StringContent -StringIn $dbIn.EDBFilePath -Replacements $DbNameReplacements))
        }
        else
        {
            throw 'Unable to locate column containing database file path'
        }

        $dbList[$i] = New-Object -TypeName PSObject -Property $currentDBProps
    }

    return $dbList
}

<#
    .SYNOPSIS
        Reads a MailboxDatabasesCopies.csv file generated by the Exchange
        Server Role Requirements Calculator, finds the databases within the
        .CSV file corresponding to the specified server, and returns an array
        of objects containing these databases and their properties.
 
    .PARAMETER MailboxDatabaseCopiesCsvPath
        The full path to the MailboxDatabasesCopies.csv file to read.
 
    .PARAMETER ServerNameInCsv
        The server name within the MailboxDatabasesCopies.csv file to look up
        database settings for.
 
    .PARAMETER DbNameReplacements
        A Hashtable containing case sensitive replacements to make to the
        databases discoverd for this server. For each Hashtable key/value pair,
        the key is the content to look for in the original string, and the
        value is what that content should be changed to. If multiple changes
        are requested, the order that the replacements are made in is not
        guaranteed.
#>

function Get-DBListFromMailboxDatabaseCopiesCsv
{
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSObject[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $MailboxDatabaseCopiesCsvPath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ServerNameInCsv,

        [Parameter()]
        [System.Collections.Hashtable]
        $DbNameReplacements = @{ }
    )

    if (!(Test-Path -Path $MailboxDatabaseCopiesCsvPath))
    {
        throw 'Unable to access file specified in MailboxDatabaseCopiesCsvPath'
    }

    [System.Object[]] $relevantDBs = Import-Csv -Path $MailboxDatabaseCopiesCsvPath | Where-Object -FilterScript { $_.Server -like $ServerNameInCsv }

    # Create the output variable
    [System.Management.Automation.PSObject[]] $dbList = New-Object -TypeName 'System.Management.Automation.PSObject[]' -ArgumentList $relevantDBs.Count

    # Loop through each database, and if it belongs to this server, at it to the list
    for ($i = 0; $i -lt $relevantDBs.Count; $i++)
    {
        $dbIn = $relevantDBs[$i]

        # Build a custom object to hold all the DB props
        $dbList[$i] = New-Object -TypeName PSObject -Property @{
            Name                 = Update-StringContent -StringIn $dbIn.Name -Replacements $DbNameReplacements
            ActivationPreference = $dbIn.ActivationPreference
            ReplayLagTime        = $dbIn.ReplayLagTime
            TruncationLagTime    = $dbIn.TruncationLagTime
        }
    }

    # Sort copies by order of ActivationPreference, so lowest numbered copies get added first.
    if ($dbList.Count -gt 0)
    {
        $dbList = $dbList | Sort-Object -Property ActivationPreference, Name
    }

    return $dbList
}

<#
    .SYNOPSIS
        Takes a given string, and makes replacements to the string content
        based off the key/values pairs defined in the Replacements hashtable.
 
    .PARAMETER StringIn
        The string to update the contents of.
 
    .PARAMETER Replacements
        A Hashtable containing case sensitive replacements to make to the
        given string. For each Hashtable key/value pair, the key is the content
        to look for in the original string, and the value is what that content
        should be changed to. If multiple changes are requested, the order that
        the replacements are made in is not guaranteed.
#>

function Update-StringContent
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter()]
        [System.String]
        $StringIn,

        [Parameter()]
        [System.Collections.Hashtable]
        $Replacements = @{ }
    )

    if ($Replacements.Count -gt 0)
    {
        foreach ($key in $Replacements.Keys)
        {
            $StringIn = $StringIn.Replace($key, $Replacements[$key])
        }
    }

    return $StringIn
}