DSCResources/DSC_ExchClientAccessServer/DSC_ExchClientAccessServer.psm1

<#
    .SYNOPSIS
        Retrieves the current DSC configuration for this resource.
 
    .PARAMETER Identity
        The hostname of the Client Access Server.
 
    .PARAMETER Credential
        The Credentials to use when creating a remote PowerShell session to
        Exchange.
 
    .PARAMETER AlternateServiceAccountCredential
        The AlternateServiceAccountCredential parameter specifies an
        alternative service account that'stypically used for Kerberos
        authentication.
 
    .PARAMETER AutoDiscoverServiceInternalUri
        The AutoDiscoverServiceInternalUri parameter specifies the internal URL
        of the Autodiscover service.
 
    .PARAMETER AutoDiscoverSiteScope
        The AutoDiscoverSiteScope parameter specifies the Active Directory site
        that the Autodiscover service is authoritative for. Clients that
        connect to the Autodiscover service by using the internal URL need to
        exist in the specified site.
 
    .PARAMETER CleanUpInvalidAlternateServiceAccountCredentials
        The CleanUpInvalidAlternateServiceAccountCredentialsswitch specifies
        whether to remove a previously configured alternate service account
        that's no longer valid. You don't need to specify a value with this
        switch.
 
    .PARAMETER DomainController
        The DomainController parameter specifies the domain controller that's
        used by this cmdlet to read data from or write data to Active
        Directory. You identify the domain controller by its fully qualified
        domain name (FQDN). For example, dc01.contoso.com.
 
    .PARAMETER RemoveAlternateServiceAccountCredentials
        The RemoveAlternateServiceAccountCredentialsswitch specifies whether to
        remove a previously distributed alternate service account. You don't
        need to specify a value with this switch.
#>

function Get-TargetResource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Identity,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        [ValidateNotNullOrEmpty()]
        $AlternateServiceAccountCredential,

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

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

        [Parameter()]
        [System.Boolean]
        $CleanUpInvalidAlternateServiceAccountCredentials,

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

        [Parameter()]
        [System.Boolean]
        $RemoveAlternateServiceAccountCredentials
    )

    Write-FunctionEntry -Parameters @{
        'Identity' = $Identity
    } -Verbose:$VerbosePreference

    # Establish remote PowerShell session
    Get-RemoteExchangeSession -Credential $Credential -CommandsToLoad 'Get-ClientAccessServ*' -Verbose:$VerbosePreference

    $cas = Get-ClientAccessServerInternal @PSBoundParameters

    if ($null -ne $cas)
    {
        if ($null -ne $cas.AutoDiscoverSiteScope)
        {
            $sites = $cas.AutoDiscoverSiteScope.ToArray()
        }
        else
        {
            $sites = @()
        }

        $returnValue = @{
            Identity                                         = [System.String] $Identity
            AutoDiscoverServiceInternalUri                   = [System.String] $cas.AutoDiscoverServiceInternalUri
            AutoDiscoverSiteScope                            = [System.String[]] $sites
            CleanUpInvalidAlternateServiceAccountCredentials = [System.Boolean] $CleanUpInvalidAlternateServiceAccountCredentials
            DomainController                                 = [System.String] $DomainController
            RemoveAlternateServiceAccountCredentials         = [System.Boolean] $RemoveAlternateServiceAccountCredentials
        }

        if ($cas.AlternateServiceAccountConfiguration.EffectiveCredentials.Count -gt 0)
        {
            $returnValue.Add("AlternateServiceAccountCredential", [System.Management.Automation.PSCredential] $cas.AlternateServiceAccountConfiguration.EffectiveCredentials.Credential)
        }
    }

    $returnValue
}

<#
    .SYNOPSIS
        Sets the DSC configuration for this resource.
 
    .PARAMETER Identity
        The hostname of the Client Access Server.
 
    .PARAMETER Credential
        The Credentials to use when creating a remote PowerShell session to
        Exchange.
 
    .PARAMETER AlternateServiceAccountCredential
        The AlternateServiceAccountCredential parameter specifies an
        alternative service account that'stypically used for Kerberos
        authentication.
 
    .PARAMETER AutoDiscoverServiceInternalUri
        The AutoDiscoverServiceInternalUri parameter specifies the internal URL
        of the Autodiscover service.
 
    .PARAMETER AutoDiscoverSiteScope
        The AutoDiscoverSiteScope parameter specifies the Active Directory site
        that the Autodiscover service is authoritative for. Clients that
        connect to the Autodiscover service by using the internal URL need to
        exist in the specified site.
 
    .PARAMETER CleanUpInvalidAlternateServiceAccountCredentials
        The CleanUpInvalidAlternateServiceAccountCredentialsswitch specifies
        whether to remove a previously configured alternate service account
        that's no longer valid. You don't need to specify a value with this
        switch.
 
    .PARAMETER DomainController
        The DomainController parameter specifies the domain controller that's
        used by this cmdlet to read data from or write data to Active
        Directory. You identify the domain controller by its fully qualified
        domain name (FQDN). For example, dc01.contoso.com.
 
    .PARAMETER RemoveAlternateServiceAccountCredentials
        The RemoveAlternateServiceAccountCredentialsswitch specifies whether to
        remove a previously distributed alternate service account. You don't
        need to specify a value with this switch.
#>

function Set-TargetResource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Identity,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        [ValidateNotNullOrEmpty()]
        $AlternateServiceAccountCredential,

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

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

        [Parameter()]
        [System.Boolean]
        $CleanUpInvalidAlternateServiceAccountCredentials,

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

        [Parameter()]
        [System.Boolean]
        $RemoveAlternateServiceAccountCredentials
    )

    # check for ambiguous parameter
    if (($AlternateServiceAccountCredential -and $RemoveAlternateServiceAccountCredentials) -or ($CleanUpInvalidAlternateServiceAccountCredentials -and $RemoveAlternateServiceAccountCredentials))
    {
        throw "Ambiguous parameter detected! Don't combine AlternateServiceAccountCredential with RemoveAlternateServiceAccountCredentials or CleanUpInvalidAlternateServiceAccountCredentials with RemoveAlternateServiceAccountCredentials!"
    }
    if ($AlternateServiceAccountCredential)
    {
        # check if credentials are in correct format DOMAIN\USERNAME
        $parts = @($AlternateServiceAccountCredential.Username.Split('\'))
        if ($parts.Count -ne 2 -or $parts[0] -eq '')
        {
            throw "The username must be fully qualified!"
        }
    }

    Write-FunctionEntry -Parameters @{
        'Identity' = $Identity
    } -Verbose:$VerbosePreference

    # Establish remote PowerShell session
    Get-RemoteExchangeSession -Credential $Credential -CommandsToLoad 'Set-ClientAccessServ*' -Verbose:$VerbosePreference

    Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $PSBoundParameters -ParamsToRemove "Credential"

    Set-EmptyStringParamsToNull -PSBoundParametersIn $PSBoundParameters

    $serverVersion = Get-ExchangeVersionYear -ThrowIfUnknownVersion $true

    if ($serverVersion -in '2016', '2019')
    {
        $setCasCmd = 'Set-ClientAccessService'
    }
    elseif ($serverVersion -eq '2013')
    {
        $setCasCmd = 'Set-ClientAccessServer'
    }

    # The AlternateServiceAccount can't be set with parameters other than Identity and DomainController, so execute as one off
    if ($null -ne $AlternateServiceAccountCredential)
    {
        $asaParams = @{
            Identity                          = $Identity
            AlternateServiceAccountCredential = $AlternateServiceAccountCredential
        }

        if (![String]::IsNullOrEmpty($DomainController))
        {
            $asaParams.Add('DomainController', $DomainController)
        }

        & $setCasCmd @asaParams

        $PSBoundParameters.Remove('AlternateServiceAccountCredential')
    }

    # Remove AlternateServiceAccount can't be performed with parameters other than Identity and DomainController, so execute as one off
    if ($RemoveAlternateServiceAccountCredentials)
    {
        $asaParams = @{
            Identity                                 = $Identity
            RemoveAlternateServiceAccountCredentials = $true
        }

        if (![String]::IsNullOrEmpty($DomainController))
        {
            $asaParams.Add('DomainController', $DomainController)
        }

        & $setCasCmd @asaParams

        $PSBoundParameters.Remove('RemoveAlternateServiceAccountCredentials')
    }

    & $setCasCmd @PSBoundParameters
}

<#
    .SYNOPSIS
        Tests whether the desired configuration for this resource has been
        applied.
 
    .PARAMETER Identity
        The hostname of the Client Access Server.
 
    .PARAMETER Credential
        The Credentials to use when creating a remote PowerShell session to
        Exchange.
 
    .PARAMETER AlternateServiceAccountCredential
        The AlternateServiceAccountCredential parameter specifies an
        alternative service account that'stypically used for Kerberos
        authentication.
 
    .PARAMETER AutoDiscoverServiceInternalUri
        The AutoDiscoverServiceInternalUri parameter specifies the internal URL
        of the Autodiscover service.
 
    .PARAMETER AutoDiscoverSiteScope
        The AutoDiscoverSiteScope parameter specifies the Active Directory site
        that the Autodiscover service is authoritative for. Clients that
        connect to the Autodiscover service by using the internal URL need to
        exist in the specified site.
 
    .PARAMETER CleanUpInvalidAlternateServiceAccountCredentials
        The CleanUpInvalidAlternateServiceAccountCredentialsswitch specifies
        whether to remove a previously configured alternate service account
        that's no longer valid. You don't need to specify a value with this
        switch.
 
    .PARAMETER DomainController
        The DomainController parameter specifies the domain controller that's
        used by this cmdlet to read data from or write data to Active
        Directory. You identify the domain controller by its fully qualified
        domain name (FQDN). For example, dc01.contoso.com.
 
    .PARAMETER RemoveAlternateServiceAccountCredentials
        The RemoveAlternateServiceAccountCredentialsswitch specifies whether to
        remove a previously distributed alternate service account. You don't
        need to specify a value with this switch.
#>

function Test-TargetResource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Identity,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        [ValidateNotNullOrEmpty()]
        $AlternateServiceAccountCredential,

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

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

        [Parameter()]
        [System.Boolean]
        $CleanUpInvalidAlternateServiceAccountCredentials,

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

        [Parameter()]
        [System.Boolean]
        $RemoveAlternateServiceAccountCredentials
    )

    Write-FunctionEntry -Parameters @{
        'Identity' = $Identity
    } -Verbose:$VerbosePreference

    # Establish remote PowerShell session
    Get-RemoteExchangeSession -Credential $Credential -CommandsToLoad 'Get-ClientAccessServ*' -Verbose:$VerbosePreference

    $cas = Get-ClientAccessServerInternal @PSBoundParameters

    $testResults = $true

    if ($null -eq $cas)
    {
        Write-Error -Message 'Unable to retrieve Client Access Server settings for server'

        $testResults = $false
    }
    else
    {
        if (!(Test-ExchangeSetting -Name 'AutoDiscoverServiceInternalUri' -Type 'String' -ExpectedValue $AutoDiscoverServiceInternalUri -ActualValue $cas.AutoDiscoverServiceInternalUri.AbsoluteUri -PSBoundParametersIn $PSBoundParameters -Verbose:$VerbosePreference))
        {
            $testResults = $false
        }

        if (!(Test-ExchangeSetting -Name 'AutoDiscoverSiteScope' -Type 'Array' -ExpectedValue $AutoDiscoverSiteScope -ActualValue $cas.AutoDiscoverSiteScope -PSBoundParametersIn $PSBoundParameters -Verbose:$VerbosePreference))
        {
            $testResults = $false
        }

        if (!(Test-ExchangeSetting -Name 'AlternateServiceAccountCredential' -Type 'PSCredential' -ExpectedValue $AlternateServiceAccountCredential -ActualValue ($cas.AlternateServiceAccountConfiguration.EffectiveCredentials | Sort-Object WhenAddedUTC | Select-Object -Last 1).Credential $PSBoundParameters -Verbose:$VerbosePreference))
        {
            $testResults = $false
        }

        if ($CleanUpInvalidAlternateServiceAccountCredentials)
        {
            Write-Verbose -Message 'CleanUpInvalidAlternateServiceAccountCredentials is set to $true. Forcing Test-TargetResource to return $false.'
            $testResults = $false
        }

        if ($RemoveAlternateServiceAccountCredentials -and ($cas.AlternateServiceAccountConfiguration.EffectiveCredentials.Count -gt 0))
        {
            Write-Verbose -Message 'RemoveAlternateServiceAccountCredentials is set to $true, and AlternateServiceAccountConfiguration currently has credentials configured. Returning $false.'
            $testResults = $false
        }
    }

    return $testResults
}

<#
    .SYNOPSIS
        Used as a wrapper for Get-ClientAccessServer. Runs
        Get-ClientAcccessServer, only specifying Identity, and optionally
        DomainController, and returns the results.
 
    .PARAMETER Identity
        The hostname of the Client Access Server.
 
    .PARAMETER Credential
        The Credentials to use when creating a remote PowerShell session to
        Exchange.
 
    .PARAMETER AlternateServiceAccountCredential
        The AlternateServiceAccountCredential parameter specifies an
        alternative service account that'stypically used for Kerberos
        authentication.
 
    .PARAMETER AutoDiscoverServiceInternalUri
        The AutoDiscoverServiceInternalUri parameter specifies the internal URL
        of the Autodiscover service.
 
    .PARAMETER AutoDiscoverSiteScope
        The AutoDiscoverSiteScope parameter specifies the Active Directory site
        that the Autodiscover service is authoritative for. Clients that
        connect to the Autodiscover service by using the internal URL need to
        exist in the specified site.
 
    .PARAMETER CleanUpInvalidAlternateServiceAccountCredentials
        The CleanUpInvalidAlternateServiceAccountCredentialsswitch specifies
        whether to remove a previously configured alternate service account
        that's no longer valid. You don't need to specify a value with this
        switch.
 
    .PARAMETER DomainController
        The DomainController parameter specifies the domain controller that's
        used by this cmdlet to read data from or write data to Active
        Directory. You identify the domain controller by its fully qualified
        domain name (FQDN). For example, dc01.contoso.com.
 
    .PARAMETER RemoveAlternateServiceAccountCredentials
        The RemoveAlternateServiceAccountCredentialsswitch specifies whether to
        remove a previously distributed alternate service account. You don't
        need to specify a value with this switch.
#>

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

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        [ValidateNotNullOrEmpty()]
        $AlternateServiceAccountCredential,

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

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

        [Parameter()]
        [System.Boolean]
        $CleanUpInvalidAlternateServiceAccountCredentials,

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

        [Parameter()]
        [System.Boolean]
        $RemoveAlternateServiceAccountCredentials
    )

    # Remove params we don't want to pass into the next command
    Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $PSBoundParameters -ParamsToKeep 'Identity', 'DomainController'

    $serverVersion = Get-ExchangeVersionYear -ThrowIfUnknownVersion $true
    if (($null -ne $AlternateServiceAccountCredential) -or ($RemoveAlternateServiceAccountCredentials))
    {
        $PSBoundParameters.Add('IncludeAlternateServiceAccountCredentialPassword', $true)
    }

    if ($serverVersion -in '2016', '2019')
    {
        return (Get-ClientAccessService @PSBoundParameters)
    }
    elseif ($serverVersion -eq '2013')
    {
        return (Get-ClientAccessServer @PSBoundParameters)
    }
}

Export-ModuleMember -Function *-TargetResource