Tests/TestHelpers/xExchangeTestHelper.psm1

<#
    Function to be used within pester for end to end testing of Get/Set/Test-TargetResource
    Function first calls Set-TargetResource with provided parameters, then runs Get and Test-TargetResource,
    and ensures they match $ExpectedGetResults and $ExpectedTestResult
#>

function Test-TargetResourceFunctionality
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.Collections.Hashtable]
        $Params,

        [Parameter()]
        [System.String]
        $ContextLabel,

        [Parameter()]
        [System.Collections.Hashtable]
        $ExpectedGetResults,

        [Parameter()]
        [System.Boolean]
        $ExpectedTestResult = $true
    )

    Context $ContextLabel {
        [System.Boolean]$testResult = Test-TargetResource @Params -Verbose

        Write-Verbose -Message "Test-TargetResource results before running Set-TargetResource: $testResult"

        Set-TargetResource @Params -Verbose

        [System.Collections.Hashtable]$getResult = Get-TargetResource @Params -Verbose
        [System.Boolean]$testResult = Test-TargetResource @Params -Verbose

        #The ExpectedGetResults are $null, so let's check that what we got back is $null
        if ($null -eq $ExpectedGetResults)
        {
            It 'Get-TargetResource: Should Be Null' {
                $getResult | Should BeNullOrEmpty
            }
        }
        else
        {
            <#
                Check the members of the Get-TargetResource results and make sure the result types
                match those of the function parameters
            #>

            $getTargetResourceCommand = Get-Command Get-TargetResource

            It "Only 1 Get-TargetResource function is loaded" {
                $getTargetResourceCommand.Count -eq 1 | Should Be $true
            }

            if ($getTargetResourceCommand.Count -eq 1)
            {
                foreach ($getTargetResourceParam in $getTargetResourceCommand.Parameters.Keys | Where-Object {$getResult.ContainsKey($_)})
                {
                    $getResultMemberType = '$null'

                    if ($null -ne ($getResult[$getTargetResourceParam]))
                    {
                        $getResultMemberType = $getResult[$getTargetResourceParam].GetType().ToString()
                    }

                    It "Get-TargetResource: Parameter '$getTargetResourceParam' expects return type: '$($getTargetResourceCommand.Parameters[$getTargetResourceParam].ParameterType.ToString())'. Actual return type: '$getResultMemberType'" {
                        ($getTargetResourceCommand.Parameters[$getTargetResourceParam].ParameterType.ToString()) -eq $getResultMemberType | Should Be $true
                    }
                }
            }

            #Test each individual key in $ExpectedGetResult to see if they exist, and if the expected value matches
            foreach ($key in $ExpectedGetResults.Keys)
            {
                $getContainsKey = $getResult.ContainsKey($key)

                It "Get-TargetResource: Contains Key: $($key)" {
                    $getContainsKey | Should Be $true
                }

                if ($getContainsKey)
                {
                    if ($getResult.ContainsKey($key))
                    {
                        switch ((Get-Command Get-TargetResource).Parameters[$key].ParameterType)
                        {
                            ([System.String[]])
                            {
                                $getValueMatchesForKey = Compare-ArrayContent -Array1 $getResult[$key] -Array2 $ExpectedGetResults[$key]
                            }
                            ([System.Management.Automation.PSCredential])
                            {
                                $getValueMatchesForKey = $getResult[$key].UserName -like $ExpectedGetResults[$key].UserName
                            }
                            default
                            {
                                $getValueMatchesForKey = ($getResult[$key] -eq $ExpectedGetResults[$key])
                            }
                        }
                    }
                    else
                    {
                        $getValueMatchesForKey = $false
                    }

                    It "Get-TargetResource: Value Matches for Key: $($key)" {
                        $getValueMatchesForKey | Should Be $true
                    }
                }
            }
        }

        #Test the Test-TargetResource results
        It 'Test-TargetResource' {
            $testResult | Should Be $ExpectedTestResult
        }
    }
}

function Test-ArrayContentsEqual
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.Collections.Hashtable]
        $TestParams,

        [Parameter()]
        [System.String[]]
        $DesiredArrayContents,

        [Parameter()]
        [System.String]
        $GetResultParameterName,

        [Parameter()]
        [System.String]
        $ContextLabel,

        [Parameter()]
        [System.String]
        $ItLabel
    )

    Context $ContextLabel {
        [System.Collections.Hashtable]$getResult = Get-TargetResource @TestParams

        It $ItLabel {
            Compare-ArrayContent -Array1 $DesiredArrayContents -Array2 $getResult."$($GetResultParameterName)" -IgnoreCase | Should Be $true
        }
    }
}

function Test-Array2ContainsArray1
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.Collections.Hashtable]
        $TestParams,

        [Parameter()]
        [System.String[]]
        $DesiredArrayContents,

        [Parameter()]
        [System.String]
        $GetResultParameterName,

        [Parameter()]
        [System.String]
        $ContextLabel,

        [Parameter()]
        [System.String]
        $ItLabel
    )

    Context $ContextLabel {
        [System.Collections.Hashtable]$getResult = Get-TargetResource @TestParams

        It $ItLabel {
            Array2ContainsArray1Contents -Array1 $DesiredArrayContents -Array2 $getResult."$($GetResultParameterName)" -IgnoreCase | Should Be $true
        }
    }
}

#Creates a test OAB for DSC, or sees if it exists. If it is created or exists, return the name of the OAB.
function Get-TestOfflineAddressBook
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $ShellCredentials
    )

    [System.String]$testOabName = 'Offline Address Book (DSC Test)'

    GetRemoteExchangeSession -Credential $ShellCredentials -CommandsToLoad '*-OfflineAddressBook'

    if ($null -eq (Get-OfflineAddressBook -Identity $testOabName -ErrorAction SilentlyContinue))
    {
        Write-Verbose -Message "Test OAB does not exist. Creating OAB with name '$testOabName'."

        $testOab = New-OfflineAddressBook -Name $testOabName -AddressLists '\'

        if ($null -eq $testOab)
        {
            throw 'Failed to create test OAB.'
        }
    }

    return $testOabName
}

#Removes the test DAG if it exists, and any associated databases
function Initialize-TestForDAG
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String[]]
        $ServerName,

        [Parameter()]
        [System.String]
        $DAGName,

        [Parameter()]
        [System.String]
        $DatabaseName,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $ShellCredentials
    )

    Write-Verbose -Message 'Cleaning up test DAG and related resources'

    GetRemoteExchangeSession -Credential $ShellCredentials -CommandsToLoad '*-MailboxDatabase',`
                                                                                  '*-DatabaseAvailabilityGroup',`
                                                                                  'Remove-DatabaseAvailabilityGroupServer',`
                                                                                  'Get-MailboxDatabaseCopyStatus',`
                                                                                  'Remove-MailboxDatabaseCopy'

    $existingDB = Get-MailboxDatabase -Identity "$($DatabaseName)" -Status -ErrorAction SilentlyContinue

    #First remove the test database copies
    if ($null -ne $existingDB)
    {
        Get-MailboxDatabaseCopyStatus -Identity "$($DatabaseName)" | Where-Object -FilterScript {
            $existingDB.MountedOnServer.ToLower().Contains($_.MailboxServer.ToLower()) -eq $false
        } | Remove-MailboxDatabaseCopy -Confirm:$false
    }

    #Now remove the actual DB's
    Get-MailboxDatabase | Where-Object -FilterScript {
        $_.Name -like "$($DatabaseName)"
    } | Remove-MailboxDatabase -Confirm:$false

    #Remove the files
    foreach ($server in $ServerName)
    {
        Get-ChildItem -LiteralPath "\\$($server)\c`$\Program Files\Microsoft\Exchange Server\V15\Mailbox\$($DatabaseName)" `
                      -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue
    }

    #Last remove the test DAG
    $dag = Get-DatabaseAvailabilityGroup -Identity "$($DAGName)" -ErrorAction SilentlyContinue

    if ($null -ne $dag)
    {
        Set-DatabaseAvailabilityGroup -Identity "$($DAGName)" -DatacenterActivationMode Off

        foreach ($server in $dag.Servers)
        {
            Remove-DatabaseAvailabilityGroupServer -MailboxServer "$($server.Name)" -Identity "$($DAGName)" -Confirm:$false
        }

        Remove-DatabaseAvailabilityGroup -Identity "$($DAGName)" -Confirm:$false
    }

    if ($null -ne (Get-DatabaseAvailabilityGroup -Identity "$($DAGName)" -ErrorAction SilentlyContinue))
    {
        throw 'Failed to remove test DAG'
    }

    #Disable the DAG computer account
    $compAccount = Get-ADComputer -Identity $DAGName -ErrorAction SilentlyContinue

    if ($null -ne $compAccount -and $compAccount.Enabled -eq $true)
    {
        $compAccount | Disable-ADAccount
    }

    Write-Verbose -Message 'Finished cleaning up test DAG and related resources'
}

<#
    .SYNOPSIS
        Prompts for credentials to use for Exchange tests and returns the
        credentials as a PSCredential object. Only prompts for credentials
        on the first call to the function.
#>

function Get-TestCredential
{
    # Suppressing this rule so that Exchange credentials can be re-used across multiple test scripts
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')]
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCredential])]
    param()

    if ($null -eq $Global:TestCredential)
    {
        [PSCredential]$Global:TestCredential = Get-Credential -Message 'Enter credentials for connecting a Remote PowerShell session to Exchange'
    }

    return $Global:TestCredential
}

Export-ModuleMember -Function *