DSCResources/MSFT_ADServicePrincipalName/MSFT_ADServicePrincipalName.psm1

$resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
$modulesFolderPath = Join-Path -Path $resourceModulePath -ChildPath 'Modules'

$aDCommonModulePath = Join-Path -Path $modulesFolderPath -ChildPath 'ActiveDirectoryDsc.Common'
Import-Module -Name $aDCommonModulePath

$dscResourceCommonModulePath = Join-Path -Path $modulesFolderPath -ChildPath 'DscResource.Common'
Import-Module -Name $dscResourceCommonModulePath

$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'

<#
    .SYNOPSIS
        Returns the current state of the specified service principal name.
 
    .PARAMETER ServicePrincipalName
        The full SPN to add or remove, e.g. HOST/LON-DC1.
#>

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

    Write-Verbose -Message ($script:localizedData.GetServicePrincipalName -f $ServicePrincipalName)

    $spnAccounts = Get-ADObject -Filter { ServicePrincipalName -eq $ServicePrincipalName } -Properties 'SamAccountName' |
        Select-Object -ExpandProperty 'SamAccountName'

    if ($spnAccounts.Count -eq 0)
    {
        # No SPN found
        Write-Verbose -Message ($script:localizedData.ServicePrincipalNameAbsent -f $ServicePrincipalName)

        $returnValue = @{
            Ensure               = 'Absent'
            ServicePrincipalName = $ServicePrincipalName
            Account              = ''
        }
    }
    else
    {
        # One or more SPN(s) found, return the account name(s)
        Write-Verbose -Message ($script:localizedData.ServicePrincipalNamePresent -f $ServicePrincipalName, ($spnAccounts -join ';'))

        $returnValue = @{
            Ensure               = 'Present'
            ServicePrincipalName = $ServicePrincipalName
            Account              = $spnAccounts -join ';'
        }
    }

    return $returnValue
}

<#
    .SYNOPSIS
        Add or remove the service principal name.
 
    .PARAMETER Ensure
        Specifies if the service principal name should be added or removed.
 
    .PARAMETER ServicePrincipalName
        The full SPN to add or remove, e.g. HOST/LON-DC1.
 
    .PARAMETER Account
        The user or computer account to add or remove the SPN to , e.g. User1 or
        LON-DC1$.
#>

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present',

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

        [Parameter()]
        [AllowEmptyString()]
        [System.String]
        $Account
    )

    # Test if the resource needs changing if called with Invoke-DscResource -Method Set
    $inDesiredConfiguration = Test-TargetResource @PSBoundParameters
    if ($inDesiredConfiguration)
    {
        return
    }

    # Get all Active Directory object having the target SPN configured.
    $spnAccounts = Get-ADObject -Filter { ServicePrincipalName -eq $ServicePrincipalName } -Properties 'SamAccountName', 'DistinguishedName'

    if ($Ensure -eq 'Present')
    {
        <#
            Throw an exception, if no account was specified or the account does
            not exist.
        #>

        if ([System.String]::IsNullOrEmpty($Account) -or ($null -eq (Get-ADObject -Filter { SamAccountName -eq $Account })))
        {
            throw ($script:localizedData.AccountNotFound -f $Account)
        }

        # Remove the SPN(s) from any extra account.
        foreach ($spnAccount in $spnAccounts)
        {
            if ($spnAccount.SamAccountName -ne $Account)
            {
                Write-Verbose -Message ($script:localizedData.RemoveServicePrincipalName -f $ServicePrincipalName, $spnAccount.SamAccountName)

                Set-ADObject -Identity $spnAccount.DistinguishedName -Remove @{
                    ServicePrincipalName = $ServicePrincipalName
                }
            }
        }

        <#
            Add the SPN to the target account. Use Get-ADObject to get the target
            object filtered by SamAccountName. Set-ADObject does not support the
            field SamAccountName as Identifier.
        #>

        if ($spnAccounts.SamAccountName -notcontains $Account)
        {
            Write-Verbose -Message ($script:localizedData.AddServicePrincipalName -f $ServicePrincipalName, $Account)

            Get-ADObject -Filter { SamAccountName -eq $Account } |
                Set-ADObject -Add @{
                    ServicePrincipalName = $ServicePrincipalName
                }
        }
    }

    # Remove the SPN from any account
    if ($Ensure -eq 'Absent')
    {
        foreach ($spnAccount in $spnAccounts)
        {
            Write-Verbose -Message ($script:localizedData.RemoveServicePrincipalName -f $ServicePrincipalName, $spnAccount.SamAccountName)

            Set-ADObject -Identity $spnAccount.DistinguishedName -Remove @{
                ServicePrincipalName = $ServicePrincipalName
            }
        }
    }
}

<#
    .SYNOPSIS
        Tests the service principal name.
 
    .PARAMETER Ensure
        Specifies if the service principal name should be added or removed.
 
    .PARAMETER ServicePrincipalName
        The full SPN to add or remove, e.g. HOST/LON-DC1.
 
    .PARAMETER Account
        The user or computer account to add or remove the SPN to, e.g. User1 or
        LON-DC1$.
#>

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present',

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

        [Parameter()]
        [AllowEmptyString()]
        [System.String]
        $Account
    )

    $currentConfiguration = Get-TargetResource -ServicePrincipalName $ServicePrincipalName

    $desiredConfigurationMatch = $currentConfiguration.Ensure -eq $Ensure

    if ($Ensure -eq 'Present')
    {
        $desiredConfigurationMatch = $desiredConfigurationMatch -and
        $currentConfiguration.Account -eq $Account
    }

    if ($desiredConfigurationMatch)
    {
        Write-Verbose -Message ($script:localizedData.ServicePrincipalNameInDesiredState -f $ServicePrincipalName)
    }
    else
    {
        Write-Verbose -Message ($script:localizedData.ServicePrincipalNameNotInDesiredState -f $ServicePrincipalName)
    }

    return $desiredConfigurationMatch
}