DSCResources/MSFT_SPCertificate/MSFT_SPCertificate.psm1

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $CertificateFilePath,

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

        [Parameter()]
        [ValidateSet('EndEntity', 'Intermediate', 'Pending', 'Root')]
        [System.String]
        $Store = 'EndEntity',

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

        [Parameter()]
        [ValidateSet("Present", "Absent")]
        [System.String]
        $Ensure = "Present"
    )

    Write-Verbose -Message "Getting certificate"

    $PSBoundParameters.Store = $Store

    $installedVersion = Get-SPDscInstalledProductVersion
    if ($installedVersion.FileMajorPart -lt 16 -or `
            $installedVersion.ProductBuildPart -lt 13000)
    {
        $message = ("Certificate Management is not available in SharePoint 2019 or earlier")
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    if ((Test-Path -Path $PSBoundParameters.CertificateFilePath) -eq $false)
    {
        throw "CertificateFilePath '$($PSBoundParameters.CertificateFilePath)' not found"
    }

    # Check for PFX or CER
    $file = Get-ChildItem -Path $PSBoundParameters.CertificateFilePath
    switch ($file.Extension)
    {
        ".cer"
        {
            Write-Verbose "Specified CertificateFilePath is a CER file"
            if ($PSBoundParameters.ContainsKey("CertificatePassword"))
            {
                Write-Verbose "Specifying a CertificatePassword isn't required when CertificateFilePath is a CER file."
            }
        }
        ".pfx"
        {
            Write-Verbose "Specified CertificateFilePath is a PFX file"
            if ($PSBoundParameters.ContainsKey("CertificatePassword") -eq $false)
            {
                Write-Verbose ("CertificatePassword isn't specified, make sure '$PsDscRunAsCredential' " + `
                        "has permissions to import the PFX file.")
            }
        }
        default
        {
            throw "Unsupported file extension. Please specify a PFX or CER file"
        }
    }

    $result = Invoke-SPDscCommand -Arguments $PSBoundParameters `
        -ScriptBlock {
        $params = $args[0]

        try
        {
            $null = Get-SPFarm
        }
        catch
        {
            Write-Verbose -Message ("No local SharePoint farm was detected. Certificate " + `
                    "will not be applied")
            return @{
                CertificateFilePath = $params.CertificateFilePath
                Ensure              = "Absent"
            }
        }

        # Check for PFX or CER
        $file = Get-ChildItem -Path $params.CertificateFilePath
        switch ($file.Extension)
        {
            ".cer"
            {
                $isPFX = $false
                $certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $params.CertificateFilePath
            }
            ".pfx"
            {
                $isPFX = $true
                if ($params.ContainsKey("CertificatePassword"))
                {
                    $certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $params.CertificateFilePath, $params.CertificatePassword.Password
                }
                else
                {
                    $certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $params.CertificateFilePath
                }
            }
        }

        $thumbprint = $certificateObject.Thumbprint
        $spCertificate = Get-SPCertificate -Thumbprint $thumbprint -ErrorAction SilentlyContinue

        $result = @{
            CertificateFilePath = $params.CertificateFilePath
        }

        if ($null -ne $spCertificate)
        {
            if ($spCertificate -isnot [Array])
            {
                Write-Verbose "Certificate with thumbprint $thumbprint found in SharePoint"
                if ($spCertificate.HasPrivateKey -eq $false -and $isPFX -eq $true)
                {
                    Write-Verbose ("Discovered certificate does not have a private key and the " + `
                            "specified CertificateFilePath is a PFX. Returning Absent.")
                    $result.Ensure = "Absent"
                }
                else
                {
                    $result.CertificatePassword = $params.CertificatePassword
                    $result.Store = $spCertificate.StoreType
                    $result.Exportable = $spCertificate.Exportable
                    $result.Ensure = "Present"
                }
            }
            else
            {
                Write-Verbose "Multiple certificates with thumbprint $thumbprint found in SharePoint"
                Write-Verbose "Checking for correct certificate"
                $spCertificate = $spCertificate | Where-Object -FilterScript {
                    $_.StoreType -eq $params.Store
                }

                if ($null -eq $spCertificate)
                {
                    Write-Verbose "Correct certificate not found, returning Absent"
                    $result.Ensure = "Absent"
                }
                else
                {
                    if ($spCertificate.HasPrivateKey -eq $false -and $isPFX -eq $true)
                    {
                        Write-Verbose ("Discovered certificate does not have a private key and the " + `
                                "specified CertificateFilePath is a PFX. Returning Absent.")
                        $result.Ensure = "Absent"
                    }
                    else
                    {
                        Write-Verbose "Correct certificate found, returning Present"
                        $result.Store = $spCertificate.StoreType
                        $result.Exportable = $spCertificate.Exportable
                        $result.Ensure = "Present"
                    }
                }
            }
        }
        else
        {
            Write-Verbose "Certificate with thumbprint $thumbprint not found in SharePoint"
            $result.Ensure = "Absent"
        }

        return $result
    }

    return $result
}

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

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

        [Parameter()]
        [ValidateSet('EndEntity', 'Intermediate', 'Pending', 'Root')]
        [System.String]
        $Store = 'EndEntity',

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

        [Parameter()]
        [ValidateSet("Present", "Absent")]
        [System.String]
        $Ensure = "Present"
    )

    Write-Verbose -Message "Setting certificate"

    $PSBoundParameters.Store = $Store
    $PSBoundParameters.Ensure = $Ensure

    $installedVersion = Get-SPDscInstalledProductVersion
    if ($installedVersion.FileMajorPart -lt 16 -or `
            $installedVersion.ProductBuildPart -lt 13000)
    {
        $message = ("Certificate Management is not available in SharePoint 2019 or earlier")
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    if ((Test-Path -Path $PSBoundParameters.CertificateFilePath) -eq $false)
    {
        $message = ("CertificateFilePath '$($PSBoundParameters.CertificateFilePath)' not found")
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    # Check for PFX or CER
    $file = Get-ChildItem -Path $PSBoundParameters.CertificateFilePath
    switch ($file.Extension)
    {
        ".cer"
        {
            Write-Verbose "Specified CertificateFilePath is a CER file"
            if ($PSBoundParameters.ContainsKey("CertificatePassword"))
            {
                Write-Verbose ("Specifying a CertificatePassword isn't required when " + `
                        "CertificateFilePath is a CER file.")
            }
        }
        ".pfx"
        {
            Write-Verbose "Specified CertificateFilePath is a PFX file"
            if ($PSBoundParameters.ContainsKey("CertificatePassword") -eq $false)
            {
                Write-Verbose ("CertificatePassword isn't specified, make sure '$PsDscRunAsCredential' " + `
                        "has permissions to import the PFX file.")
            }
        }
        default
        {
            $message = "Unsupported file extension. Please specify a PFX or CER file."
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $MyInvocation.MyCommand.Source
            throw $message
        }
    }

    Invoke-SPDscCommand -Arguments @($PSBoundParameters, $MyInvocation.MyCommand.Source) `
        -ScriptBlock {
        $params = $args[0]
        $eventSource = $args[1]

        try
        {
            $null = Get-SPFarm
        }
        catch
        {
            $message = "No local SharePoint farm was detected. Antivirus settings will not be applied"
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $eventSource
            throw $message
        }

        # Check for PFX or CER
        Write-Verbose "Reading certificate thumbprint from CertificateFilePath"
        $file = Get-ChildItem -Path $params.CertificateFilePath
        switch ($file.Extension)
        {
            ".cer"
            {
                $isPFX = $false
                $certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $params.CertificateFilePath
            }
            ".pfx"
            {
                $isPFX = $true
                if ($params.ContainsKey("CertificatePassword"))
                {
                    $certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $params.CertificateFilePath, $params.CertificatePassword.Password
                }
                else
                {
                    $certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $params.CertificateFilePath
                }
            }
        }

        $thumbprint = $certificateObject.Thumbprint
        $spCertificate = Get-SPCertificate -Thumbprint $thumbprint -ErrorAction SilentlyContinue

        if ($params.Ensure -eq 'Present')
        {
            $runImport = $false
            if ($null -ne $spCertificate)
            {
                if ($spCertificate -isnot [Array])
                {
                    Write-Verbose "Certificate with thumbprint $thumbprint found in SharePoint"
                    if ($isPFX)
                    {
                        if ($spCertificate.HasPrivateKey -eq $false)
                        {
                            Write-Verbose -Message ("Discovered certificate does not have a private " + `
                                    "key and the specified CertificateFilePath is a PFX. Importing PFX.")
                            $runImport = $true
                        }
                        elseif ($params.Store -ne $spCertificate.StoreType)
                        {
                            Write-Verbose -Message "Moving certificate to store $($params.Store)"
                            Move-SPCertificate -Identity $spCertificate -NewStore $params.Store
                        }
                    }
                    else
                    {
                        if ($params.Store -ne $spCertificate.StoreType)
                        {
                            if ($spCertificate.HasPrivateKey -eq $true)
                            {
                                Write-Verbose -Message ("Discovered certificate has a private key, the " + `
                                        "specified CertificateFilePath is a CER and specified store " + `
                                        "is different. Importing CER.")
                                $runImport = $true
                            }
                            else
                            {
                                Write-Verbose -Message "Moving certificate to store $($params.Store)"
                                Move-SPCertificate -Identity $spCertificate -NewStore $params.Store
                            }
                        }
                    }
                }
                else
                {
                    Write-Verbose "Multiple certificates with thumbprint $thumbprint found in SharePoint"
                    Write-Verbose "Checking for correct certificate"
                    $spCertificate = $spCertificate | Where-Object -FilterScript {
                        $_.StoreType -eq $params.Store
                    }

                    if ($null -eq $spCertificate)
                    {
                        Write-Verbose "Correct certificate not found, importing certificate"
                        $runImport = $true
                    }
                    else
                    {
                        Write-Verbose "Correct certificate found"
                        if ($spCertificate.HasPrivateKey -eq $false -and $isPFX -eq $true)
                        {
                            Write-Verbose ("Discovered certificate does not have a private key and the " + `
                                    "specified CertificateFilePath is a PFX. Importing PFX.")
                            $runImport = $true
                        }
                    }
                }
            }
            else
            {
                Write-Verbose "Certificate with thumbprint $thumbprint not found in SharePoint. Importing file."
                $runImport = $true
            }

            if ($runImport -eq $true)
            {
                $certParams = @{
                    Path  = $params.CertificateFilePath
                    Store = $params.Store
                }

                if ($params.ContainsKey("CertificatePassword"))
                {
                    $certParams.Password = $params.CertificatePassword.Password
                }

                if ($params.ContainsKey("Exportable"))
                {
                    $certParams.Exportable = $params.Exportable
                }
                Write-Verbose "Running Import-SPCertificate with parameters: $(Convert-SPDscHashtableToString -Hashtable $certParams)"
                Import-SPCertificate @certParams
            }
        }
        else
        {
            if ($null -ne $spCertificate)
            {
                Write-Verbose "Certificate with thumbprint $thumbprint found. Removing certificate"
                Write-Verbose "Checking for correct certificate store"
                $spCertificate = $spCertificate | Where-Object -FilterScript {
                    $_.StoreType -eq $params.Store
                }

                if ($null -ne $spCertificate)
                {
                    Write-Verbose "Correct certificate found, removing certificate"
                    Remove-SPCertificate -Identity $spCertificate -Confirm:$false
                }
                else
                {
                    Write-Verbose "Certificate with thumbprint $thumbprint NOT found."
                }
            }
        }
    }
}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $CertificateFilePath,

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

        [Parameter()]
        [ValidateSet('EndEntity', 'Intermediate', 'Pending', 'Root')]
        [System.String]
        $Store = 'EndEntity',

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

        [Parameter()]
        [ValidateSet("Present", "Absent")]
        [System.String]
        $Ensure = "Present"
    )

    Write-Verbose -Message "Testing certificate"

    $PSBoundParameters.Store = $Store
    $PSBoundParameters.Ensure = $Ensure

    $CurrentValues = Get-TargetResource @PSBoundParameters

    Write-Verbose -Message "Current Values: $(Convert-SPDscHashtableToString -Hashtable $CurrentValues)"
    Write-Verbose -Message "Target Values: $(Convert-SPDscHashtableToString -Hashtable $PSBoundParameters)"

    if ($Ensure -eq 'Present')
    {
        $result = Test-SPDscParameterState -CurrentValues $CurrentValues `
            -Source $($MyInvocation.MyCommand.Source) `
            -DesiredValues $PSBoundParameters `
            -ValuesToCheck (
            "Store",
            "Ensure"
        )
    }
    else
    {
        $result = Test-SPDscParameterState -CurrentValues $CurrentValues `
            -Source $($MyInvocation.MyCommand.Source) `
            -DesiredValues $PSBoundParameters `
            -ValuesToCheck (
            "Ensure"
        )
    }

    Write-Verbose -Message "Test-TargetResource returned $result"

    return $result
}

function Export-TargetResource
{
    $VerbosePreference = "SilentlyContinue"

    $installedVersion = Get-SPDscInstalledProductVersion
    if ($installedVersion.FileMajorPart -eq 16 -and `
            $installedVersion.ProductBuildPart -gt 13000)
    {
        $ParentModuleBase = Get-Module "SharePointDsc" -ListAvailable | Select-Object -ExpandProperty Modulebase
        $module = Join-Path -Path $ParentModuleBase -ChildPath  "\DSCResources\MSFT_SPCertificate\MSFT_SPCertificate.psm1" -Resolve

        $Content = ''
        $params = Get-DSCFakeParameters -ModulePath $module

        $certificates = Get-SPCertificate

        $password = $global:spFarmAccount.Password

        foreach ($certificate in $certificates)
        {
            $exportPath = Join-Path -Path (Get-Location) -ChildPath ($certificate.Subject -replace 'CN=')
            if ($certificate.HasPrivateKey -eq $true -and $certificate.Exportable -eq $true)
            {
                $exportFilePath = "$exportPath.pfx"
                Export-SPCertificate -Identity $certificate -Password $password -Path $exportFilePath -Force
            }
            else
            {
                $exportFilePath = "$exportPath.cer"
                Export-SPCertificate -Identity $certificate -Type 'Cert' -Path $exportFilePath -Force
            }

            $params.CertificateFilePath = $exportFilePath
            $params.Store = $certificate.StoreType

            $PartialContent = " SPCertificate " + [System.Guid]::NewGuid().ToString() + "`r`n"
            $PartialContent += " {`r`n"
            $results = Get-TargetResource @params
            $results = Repair-Credentials -results $results
            $results.CertificatePassword = Resolve-Credentials -UserName $global:spFarmAccount.UserName
            $currentBlock = Get-DSCBlock -Params $results -ModulePath $module
            $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "CertificatePassword"
            $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "PsDscRunAsCredential"
            $PartialContent += $currentBlock
            $PartialContent += " }`r`n"

            $Content += $PartialContent
        }

        return $Content
    }
}

Export-ModuleMember -Function *-TargetResource