DSCResources/DSC_ExchExchangeCertificate/DSC_ExchExchangeCertificate.psm1

<#
    .SYNOPSIS
        Retrieves the current DSC configuration for this resource.
 
    .PARAMETER Thumbprint
        Thumbprint of the certificate to work on.
 
    .PARAMETER Credential
        The Credentials to use when creating a remote PowerShell session to
        Exchange.
 
    .PARAMETER Ensure
        Whether the certificate should be present or not.
 
    .PARAMETER AllowExtraServices
        Get-ExchangeCertificate sometimes displays more services than are
        actually enabled. Setting this to true allows tests to pass in that
        situation as long as the requested services are present.
 
    .PARAMETER CertCreds
        Credentials containing the password to the .pfx file in CertFilePath.
 
    .PARAMETER CertFilePath
        The file path to the certificate .pfx file that should be imported.
 
    .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 DoNotRequireSsl
        Setting DoNotRequireSsl to True prevents DSC from enabling the Require
        SSL setting on the Default Web Site when you enable the certificate for
        IIS. Defaults to False.
 
    .PARAMETER NetworkServiceAllowed
        Setting NetworkServiceAllowed to True gives the built-in Network
        Service account permission to read the certificate's private key
        without enabling the certificate for SMTP. Defaults to False.
 
    .PARAMETER Services
        Services to enable on the certificate.
#>

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

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

        [Parameter(Mandatory = $true)]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure,

        [Parameter()]
        [System.Boolean]
        $AllowExtraServices = $false,

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

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

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

        [Parameter()]
        [System.Boolean]
        $DoNotRequireSsl = $false,

        [Parameter()]
        [System.Boolean]
        $NetworkServiceAllowed = $false,

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

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

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

    $cert = Get-ExchangeCertificateInternal @PSBoundParameters

    if ($null -ne $cert)
    {
        $returnValue = @{
            Thumbprint = [System.String] $Thumbprint
            Services   = [System.String[]] $cert.Services.ToString().Split(',').Trim()
        }
    }

    $returnValue
}

<#
    .SYNOPSIS
        Sets the DSC configuration for this resource.
 
    .PARAMETER Thumbprint
        Thumbprint of the certificate to work on.
 
    .PARAMETER Credential
        The Credentials to use when creating a remote PowerShell session to
        Exchange.
 
    .PARAMETER Ensure
        Whether the certificate should be present or not.
 
    .PARAMETER AllowExtraServices
        Get-ExchangeCertificate sometimes displays more services than are
        actually enabled. Setting this to true allows tests to pass in that
        situation as long as the requested services are present.
 
    .PARAMETER CertCreds
        Credentials containing the password to the .pfx file in CertFilePath.
 
    .PARAMETER CertFilePath
        The file path to the certificate .pfx file that should be imported.
 
    .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 DoNotRequireSsl
        Setting DoNotRequireSsl to True prevents DSC from enabling the Require
        SSL setting on the Default Web Site when you enable the certificate for
        IIS. Defaults to False.
 
    .PARAMETER NetworkServiceAllowed
        Setting NetworkServiceAllowed to True gives the built-in Network
        Service account permission to read the certificate's private key
        without enabling the certificate for SMTP. Defaults to False.
 
    .PARAMETER Services
        Services to enable on the certificate.
#>

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Thumbprint,

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

        [Parameter(Mandatory = $true)]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure,

        [Parameter()]
        [System.Boolean]
        $AllowExtraServices = $false,

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

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

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

        [Parameter()]
        [System.Boolean]
        $DoNotRequireSsl = $false,

        [Parameter()]
        [System.Boolean]
        $NetworkServiceAllowed = $false,

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

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

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

    $cert = Get-ExchangeCertificateInternal @PSBoundParameters

    # Check whether any UM services are being enabled, and if they weren't enable before. If so, we should stop those services, enable the cert, then start them up
    $needUMServiceReset = $false
    $needUMCallRouterServiceReset = $false

    if ($null -ne $cert)
    {
        $currentServices = Convert-StringToArray -StringIn $cert.Services -Separator ','
    }

    if ((Test-ArrayElementsInSecondArray -Array2 $Services -Array1 'UM' -IgnoreCase) -eq $true)
    {
        if ($null -eq $cert -or (Test-ArrayElementsInSecondArray -Array2 $currentServices -Array1 'UM' -IgnoreCase) -eq $false)
        {
            $needUMServiceReset = $true
        }
    }

    if ((Test-ArrayElementsInSecondArray -Array2 $Services -Array1 'UMCallRouter' -IgnoreCase) -eq $true)
    {
        if ($null -eq $cert -or (Test-ArrayElementsInSecondArray -Array2 $currentServices -Array1 'UMCallRouter' -IgnoreCase) -eq $false)
        {
            $needUMCallRouterServiceReset = $true
        }
    }

    # Stop required services before working with the cert
    if ($needUMServiceReset -eq $true)
    {
        Write-Verbose -Message 'Stopping service MSExchangeUM before enabling the UM service on the certificate'
        Stop-Service -Name MSExchangeUM -Confirm:$false
    }

    if ($needUMCallRouterServiceReset -eq $true)
    {
        Write-Verbose -Message 'Stopping service MSExchangeUMCR before enabling the UMCallRouter service on the certificate'
        Stop-Service -Name MSExchangeUMCR -Confirm:$false
    }

    # The desired cert is not present. Deal with that scenario.
    if ($null -eq $cert)
    {
        # If the cert is null and it's supposed to be present, then we need to import one
        if ($Ensure -eq 'Present')
        {
            $cert = Import-ExchangeCertificate -FileData ([Byte[]] $(Get-Content -Path "$($CertFilePath)" -Encoding Byte -ReadCount 0)) -Password:$CertCreds.Password -Server $env:COMPUTERNAME
        }
    }
    else
    {
        # cert is present and it shouldn't be. Remove it
        if ($Ensure -eq 'Absent')
        {
            Remove-ExchangeCertificate -Thumbprint $Thumbprint -Confirm:$false -Server $env:COMPUTERNAME
        }
    }

    # Cert is present. Set props on it
    if ($Ensure -eq 'Present')
    {
        if ($null -ne $cert)
        {
            $previousError = Get-PreviousError

            $enableParams = @{
                Server     = $env:COMPUTERNAME
                Services   = $Services
                Thumbprint = $Thumbprint
                Force      = $true
            }

            if ($DoNotRequireSsl)
            {
                $enableParams.Add('DoNotRequireSsl', $true)
            }

            if ($NetworkServiceAllowed)
            {
                $enableParams.Add('NetworkServiceAllowed', $true)
            }

            Enable-ExchangeCertificate @enableParams

            Assert-NoNewError -CmdletBeingRun 'Enable-ExchangeCertificate' -PreviousError $previousError -Verbose:$VerbosePreference
        }
        else
        {
            Write-Error 'Failed to install certificate'
        }
    }

    # Start UM services that were stopped earlier
    if ($needUMServiceReset -eq $true)
    {
        Write-Verbose -Message 'Starting service MSExchangeUM'
        Start-Service -Name MSExchangeUM -Confirm:$false
    }

    if ($needUMCallRouterServiceReset -eq $true)
    {
        Write-Verbose -Message 'Starting service MSExchangeUMCR'
        Start-Service -Name MSExchangeUMCR -Confirm:$false
    }
}

<#
    .SYNOPSIS
        Tests whether the desired configuration for this resource has been
        applied.
 
    .PARAMETER Thumbprint
        Thumbprint of the certificate to work on.
 
    .PARAMETER Credential
        The Credentials to use when creating a remote PowerShell session to
        Exchange.
 
    .PARAMETER Ensure
        Whether the certificate should be present or not.
 
    .PARAMETER AllowExtraServices
        Get-ExchangeCertificate sometimes displays more services than are
        actually enabled. Setting this to true allows tests to pass in that
        situation as long as the requested services are present.
 
    .PARAMETER CertCreds
        Credentials containing the password to the .pfx file in CertFilePath.
 
    .PARAMETER CertFilePath
        The file path to the certificate .pfx file that should be imported.
 
    .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 DoNotRequireSsl
        Setting DoNotRequireSsl to True prevents DSC from enabling the Require
        SSL setting on the Default Web Site when you enable the certificate for
        IIS. Defaults to False.
 
    .PARAMETER NetworkServiceAllowed
        Setting NetworkServiceAllowed to True gives the built-in Network
        Service account permission to read the certificate's private key
        without enabling the certificate for SMTP. Defaults to False.
 
    .PARAMETER Services
        Services to enable on the certificate.
#>

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

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

        [Parameter(Mandatory = $true)]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure,

        [Parameter()]
        [System.Boolean]
        $AllowExtraServices = $false,

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

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

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

        [Parameter()]
        [System.Boolean]
        $DoNotRequireSsl = $false,

        [Parameter()]
        [System.Boolean]
        $NetworkServiceAllowed = $false,

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

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

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

    $cert = Get-ExchangeCertificateInternal @PSBoundParameters

    $testResults = $true

    if ($null -ne $cert)
    {
        if ($Ensure -eq 'Present')
        {
            if (!(Test-CertServicesInRequiredState -ServicesActual $cert.Services -ServicesDesired $Services -AllowExtraServices $AllowExtraServices))
            {
                Write-InvalidSettingVerbose -SettingName 'Services' -ExpectedValue $Services -ActualValue $cert.Services -Verbose:$VerbosePreference
                $testResults = $false
            }
        }
        else
        {
            Write-Verbose -Message "Certificate with thumbprint $Thumbprint still exists"
            $testResults = $false
        }
    }
    else
    {
        if ($Ensure -ne 'Absent')
        {
            Write-Verbose -Message "Certificate with thumbprint $Thumbprint does not exist"
            $testResults = $false
        }
    }

    return $testResults
}

<#
    .SYNOPSIS
        Used as a wrapper for Get-ExchangeCertificate. Runs
        Get-ExchangeCertificate, only specifying Thumbprint, ErrorAction, and
        optionally DomainController, and returns the results.
 
    .PARAMETER Thumbprint
        Thumbprint of the certificate to work on.
 
    .PARAMETER Credential
        The Credentials to use when creating a remote PowerShell session to
        Exchange.
 
    .PARAMETER Ensure
        Whether the certificate should be present or not.
 
    .PARAMETER AllowExtraServices
        Get-ExchangeCertificate sometimes displays more services than are
        actually enabled. Setting this to true allows tests to pass in that
        situation as long as the requested services are present.
 
    .PARAMETER CertCreds
        Credentials containing the password to the .pfx file in CertFilePath.
 
    .PARAMETER CertFilePath
        The file path to the certificate .pfx file that should be imported.
 
    .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 DoNotRequireSsl
        Setting DoNotRequireSsl to True prevents DSC from enabling the Require
        SSL setting on the Default Web Site when you enable the certificate for
        IIS. Defaults to False.
 
    .PARAMETER NetworkServiceAllowed
        Setting NetworkServiceAllowed to True gives the built-in Network
        Service account permission to read the certificate's private key
        without enabling the certificate for SMTP. Defaults to False.
 
    .PARAMETER Services
        Services to enable on the certificate.
#>

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

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

        [Parameter(Mandatory = $true)]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure,

        [Parameter()]
        [System.Boolean]
        $AllowExtraServices = $false,

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

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

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

        [Parameter()]
        [System.Boolean]
        $DoNotRequireSsl = $false,

        [Parameter()]
        [System.Boolean]
        $NetworkServiceAllowed = $false,

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

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

    return (Get-ExchangeCertificate @PSBoundParameters -ErrorAction SilentlyContinue -Server $env:COMPUTERNAME)
}

<#
    .SYNOPSIS
        Compares whether services from a certificate object match the services
        that were requested. If AllowsExtraServices is true, it is OK for more
        services to be on the cert than were requested, as long as the
        requested services are present.
 
    .PARAMETER ServicesActual
        The actual Services discovered by Get-ExchangeCertificate.
 
    .PARAMETER ServicesDesired
        The desired Services to enable on the certificate.
 
    .PARAMETER AllowExtraServices
        Get-ExchangeCertificate sometimes displays more services than are
        actually enabled. Setting this to true allows tests to pass in that
        situation as long as the requested services are present.
#>

function Test-CertServicesInRequiredState
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter()]
        [System.String]
        $ServicesActual,

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

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

    $actual = Convert-StringToArray -StringIn $ServicesActual -Separator ','

    if ($AllowExtraServices -eq $true)
    {
        if (!([System.String]::IsNullOrEmpty($ServicesDesired)) -and $ServicesDesired.Contains('NONE'))
        {
            $result = $true
        }
        else
        {
            $result = Test-ArrayElementsInSecondArray -Array1 $ServicesDesired -Array2 $actual -IgnoreCase
        }
    }
    else
    {
        $result = Compare-ArrayContent -Array1 $actual -Array2 $ServicesDesired -IgnoreCase
    }

    return $result
}

Export-ModuleMember -Function *-TargetResource