DSCResources/MSFT_ADManagedServiceAccount/MSFT_ADManagedServiceAccount.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'

$script:errorCodeKdsRootKeyNotFound = -2146893811

<#
    .SYNOPSIS
        Returns the current state of an Active Directory managed service account.
 
    .PARAMETER ServiceAccountName
        Specifies the Security Account Manager (SAM) account name of the managed service account (ldapDisplayName
        'sAMAccountName').
 
    .PARAMETER AccountType
        The type of managed service account. Standalone will create a Standalone Managed Service Account (sMSA) and
        Group will create a Group Managed Service Account (gMSA).
 
    .PARAMETER Credential
        Specifies the user account credentials to use to perform this task.
        This is only required if not executing the task on a domain controller or using the DomainController parameter.
 
    .PARAMETER DomainController
        Specifies the Active Directory Domain Controller instance to use to perform the task.
        This is only required if not executing the task on a domain controller.
 
    .PARAMETER MembershipAttribute
        Active Directory attribute used to perform membership operations for Group Managed Service Accounts (gMSAs).
        If not specified, this value defaults to SamAccountName. Only used when 'Group' is selected for 'AccountType'.
 
    .NOTES
        Used Functions:
            Name | Module
            ------------------------------|--------------------------
            Get-ADObject | ActiveDirectory
            Get-ADServiceAccount | ActiveDirectory
            Assert-Module | DscResource.Common
            New-InvalidOperationException | DscResource.Common
            Get-ADCommonParameters | ActiveDirectoryDsc.Common
            Get-ADObjectParentDN | ActiveDirectoryDsc.Common
#>

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

        [Parameter(Mandatory = $true)]
        [ValidateSet('Group', 'Standalone')]
        [System.String]
        $AccountType,

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

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

        [Parameter()]
        [ValidateSet('SamAccountName', 'DistinguishedName', 'ObjectSid', 'ObjectGUID')]
        [System.String]
        $MembershipAttribute = 'SamAccountName'
    )

    Assert-Module -ModuleName 'ActiveDirectory'
    $adServiceAccountParameters = Get-ADCommonParameters @PSBoundParameters

    Write-Verbose -Message ($script:localizedData.RetrievingManagedServiceAccountMessage -f
        $ServiceAccountName)

    try
    {
        $adServiceAccount = Get-ADServiceAccount @adServiceAccountParameters -Properties @(
            'CN'
            'DistinguishedName'
            'Description'
            'DisplayName'
            'ObjectClass'
            'Enabled'
            'PrincipalsAllowedToRetrieveManagedPassword'
            'KerberosEncryptionType'
            'TrustedForDelegation'
        )
    }

    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
    {
        Write-Verbose -Message ($script:localizedData.ManagedServiceAccountNotFoundMessage -f
            $AccountType, $ServiceAccountName)
        $adServiceAccount = $null
    }
    catch
    {
        $errorMessage = $script:localizedData.RetrievingManagedServiceAccountError -f $ServiceAccountName
        New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
    }

    if ($adServiceAccount)
    {
        # Resource exists
        $managedPasswordPrincipals = @()

        if ($adServiceAccount.ObjectClass -eq 'msDS-ManagedServiceAccount')
        {
            $existingAccountType = 'Standalone'
        }
        else
        {
            $existingAccountType = 'Group'

            Write-Verbose -Message ($script:localizedData.RetrievingManagedPasswordPrincipalsMessage -f
                $MembershipAttribute)

            foreach ($identity in $adServiceAccount.PrincipalsAllowedToRetrieveManagedPassword)
            {
                try
                {
                    $principal = (Get-ADObject -Identity $identity -Properties $MembershipAttribute).$MembershipAttribute
                }
                catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
                {
                    # Add unresolved SID as principal if the identity could not be found
                    $principal = $identity
                }
                catch
                {
                    $errorMessage = $script:localizedData.RetrievingManagedPasswordPrincipalsError -f $identity
                    New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
                }

                $managedPasswordPrincipals += $principal
            }
        }

        $targetResource = @{
            ServiceAccountName        = $ServiceAccountName
            AccountType               = $existingAccountType
            Path                      = Get-ADObjectParentDN -DN $adServiceAccount.DistinguishedName
            CommonName                = $adServiceAccount.CN
            Description               = $adServiceAccount.Description
            DisplayName               = $adServiceAccount.DisplayName
            DistinguishedName         = $adServiceAccount.DistinguishedName
            Enabled                   = $adServiceAccount.Enabled
            KerberosEncryptionType    = $adServiceAccount.KerberosEncryptionType -split (', ')
            TrustedForDelegation      = $adServiceAccount.TrustedForDelegation
            ManagedPasswordPrincipals = $managedPasswordPrincipals
            MembershipAttribute       = $MembershipAttribute
            Ensure                    = 'Present'
        }
    }
    else
    {
        # Resource does not exist
        $targetResource = @{
            ServiceAccountName        = $ServiceAccountName
            AccountType               = $AccountType
            Path                      = $null
            CommonName                = $null
            Description               = $null
            DisplayName               = $null
            DistinguishedName         = $null
            Enabled                   = $false
            KerberosEncryptionType    = @()
            TrustedForDelegation      = $null
            ManagedPasswordPrincipals = @()
            MembershipAttribute       = $MembershipAttribute
            Ensure                    = 'Absent'
        }
    }

    return $targetResource
} #end function Get-TargetResource

<#
    .SYNOPSIS
        Tests if an Active Directory managed service account is in the desired state.
 
    .PARAMETER ServiceAccountName
        Specifies the Security Account Manager (SAM) account name of the managed service account (ldapDisplayName
        'sAMAccountName'). To be compatible with older operating systems, create a SAM account name that is 15
        characters or less. Once created, the user's SamAccountName cannot be changed.
 
    .PARAMETER AccountType
        The type of managed service account. Standalone will create a Standalone Managed Service Account (sMSA) and
        Group will create a Group Managed Service Account (gMSA).
 
    .PARAMETER CommonName
        Specifies the common name assigned to the managed service account (ldapDisplayName 'cn'). If not specified the
        default value will be the same value provided in parameter ServiceAccountName.
 
    .PARAMETER Credential
        Specifies the user account credentials to use to perform this task.
        This is only required if not executing the task on a domain controller or using the DomainController parameter.
 
    .PARAMETER Description
        Specifies the description of the account (ldapDisplayName 'description').
 
    .PARAMETER DisplayName
        Specifies the display name of the account (ldapDisplayName 'displayName').
 
    .PARAMETER DomainController
        Specifies the Active Directory Domain Controller instance to use to perform the task.
        This is only required if not executing the task on a domain controller.
 
    .PARAMETER Ensure
        Specifies whether the user account is created or deleted. If not specified, this value defaults to Present.
 
    .PARAMETER KerberosEncryptionType
        Specifies which Kerberos encryption types the account supports when creating service tickets.
        This value sets the encryption types supported flags of the Active Directory msDS-SupportedEncryptionTypes
        attribute.
 
    .PARAMETER TrustedForDelegation
        Specifies whether an account is trusted for Kerberos delegation. Default value is $false.
 
    .PARAMETER ManagedPasswordPrincipals
        Specifies the membership policy for systems which can use a group managed service account. (ldapDisplayName
        'msDS-GroupMSAMembership'). Only used when 'Group' is selected for 'AccountType'.
 
    .PARAMETER MembershipAttribute
        Active Directory attribute used to perform membership operations for Group Managed Service Accounts (gMSAs).
        If not specified, this value defaults to SamAccountName. Only used when 'Group' is selected for 'AccountType'.
 
    .PARAMETER Path
        Specifies the X.500 path of the Organizational Unit (OU) or container where the new account is created.
        Specified as a Distinguished Name (DN).
 
    .NOTES
        Used Functions:
            Name | Module
            ------------------------------|--------------------------
            Compare-ResourcePropertyState | ActiveDirectoryDsc.Common
#>

function Test-TargetResource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', "",
        Justification = 'False positive on ManagedPasswordPrincipals')]
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ServiceAccountName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Group', 'Standalone')]
        [System.String]
        $AccountType,

        [Parameter()]
        [ValidateNotNull()]
        [System.String]
        $CommonName,

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

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

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

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

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

        [Parameter()]
        [ValidateSet('None', 'RC4', 'AES128', 'AES256')]
        [System.String[]]
        $KerberosEncryptionType,

        [Parameter()]
        [ValidateNotNull()]
        [System.Boolean]
        $TrustedForDelegation,

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

        [Parameter()]
        [ValidateSet('SamAccountName', 'DistinguishedName', 'ObjectSid', 'ObjectGUID')]
        [System.String]
        $MembershipAttribute = 'SamAccountName',

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

    # Need to set these parameters to compare if users are using the default parameter values
    [HashTable] $parameters = $PSBoundParameters
    $parameters['MembershipAttribute'] = $MembershipAttribute

    $getTargetResourceParameters = @{
        ServiceAccountName  = $ServiceAccountName
        AccountType         = $AccountType
        DomainController    = $DomainController
        MembershipAttribute = $MembershipAttribute
    }

    @($getTargetResourceParameters.Keys) |
        ForEach-Object {
            if (-not $parameters.ContainsKey($_))
            {
                $getTargetResourceParameters.Remove($_)
            }
        }

    $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters

    if ($getTargetResourceResult.Ensure -eq 'Present')
    {
        # Resource exists
        if ($Ensure -eq 'Present')
        {
            # Resource should exist
            $propertiesNotInDesiredState = (
                Compare-ResourcePropertyState -CurrentValues $getTargetResourceResult -DesiredValues $parameters `
                    -IgnoreProperties 'DomainController', 'Credential' | Where-Object -Property InDesiredState -eq $false)

            if ($propertiesNotInDesiredState)
            {
                $inDesiredState = $false
            }
            else
            {
                # Resource is in desired state
                Write-Verbose -Message ($script:localizedData.ManagedServiceAccountInDesiredStateMessage -f
                    $AccountType, $ServiceAccountName)
                $inDesiredState = $true
            }
        }
        else
        {
            # Resource should not exist
            Write-Verbose -Message ($script:localizedData.ResourceExistsButShouldNotMessage -f
                $AccountType, $ServiceAccountName)
            $inDesiredState = $false
        }
    }
    else
    {
        # Resource does not exist
        if ($Ensure -eq 'Present')
        {
            # Resource should exist
            Write-Verbose -Message ($script:localizedData.ResourceDoesNotExistButShouldMessage -f
                $AccountType, $ServiceAccountName)
            $inDesiredState = $false
        }
        else
        {
            # Resource should not exist
            Write-Verbose -Message ($script:localizedData.ManagedServiceAccountInDesiredStateMessage -f
                $AccountType, $ServiceAccountName)
            $inDesiredState = $true
        }
    }

    $inDesiredState
} #end function Test-TargetResource

<#
    .SYNOPSIS
        Sets the state of an Active Directory managed service account.
 
    .PARAMETER ServiceAccountName
        Specifies the Security Account Manager (SAM) account name of the managed service account (ldapDisplayName
        'sAMAccountName'). To be compatible with older operating systems, create a SAM account name that is 15
        characters or less. Once created, the user's SamAccountName cannot be changed.
 
    .PARAMETER AccountType
        The type of managed service account. Standalone will create a Standalone Managed Service Account (sMSA) and
        Group will create a Group Managed Service Account (gMSA).
 
    .PARAMETER CommonName
        Specifies the common name assigned to the managed service account (ldapDisplayName 'cn'). If not specified the
        default value will be the same value provided in parameter ServiceAccountName.
 
    .PARAMETER Credential
        Specifies the user account credentials to use to perform this task.
        This is only required if not executing the task on a domain controller or using the DomainController parameter.
 
    .PARAMETER Description
        Specifies the description of the account (ldapDisplayName 'description').
 
    .PARAMETER DisplayName
        Specifies the display name of the account (ldapDisplayName 'displayName').
 
    .PARAMETER DomainController
        Specifies the Active Directory Domain Controller instance to use to perform the task.
        This is only required if not executing the task on a domain controller.
 
    .PARAMETER Ensure
        Specifies whether the user account is created or deleted. If not specified, this value defaults to Present.
 
    .PARAMETER KerberosEncryptionType
        Specifies which Kerberos encryption types the account supports when creating service tickets.
        This value sets the encryption types supported flags of the Active Directory msDS-SupportedEncryptionTypes
        attribute.
 
    .PARAMETER TrustedForDelegation
        Specifies whether an account is trusted for Kerberos delegation. Default value is $false.
 
    .PARAMETER ManagedPasswordPrincipals
        Specifies the membership policy for systems which can use a group managed service account. (ldapDisplayName
        'msDS-GroupMSAMembership'). Only used when 'Group' is selected for 'AccountType'.
 
    .PARAMETER MembershipAttribute
        Active Directory attribute used to perform membership operations for Group Managed Service Accounts (gMSAs).
        If not specified, this value defaults to SamAccountName. Only used when 'Group' is selected for 'AccountType'.
 
    .PARAMETER Path
        Specifies the X.500 path of the Organizational Unit (OU) or container where the new account is created.
        Specified as a Distinguished Name (DN).
 
    .NOTES
        Used Functions:
            Name | Module
            ------------------------------|--------------------------
            Get-ADDomain | ActiveDirectory
            Move-ADObject | ActiveDirectory
            New-ADServiceAccount | ActiveDirectory
            Remove-ADServiceAccount | ActiveDirectory
            Set-ADServiceAccount | ActiveDirectory
            Compare-ResourcePropertyState | ActiveDirectoryDsc.Common
            Get-ADCommonParameters | ActiveDirectoryDsc.Common
            Get-DomainName | ActiveDirectoryDsc.Common
            New-InvalidOperationException | DscResource.Common
#>


function Set-TargetResource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', "",
        Justification = 'False positive on ManagedPasswordPrincipals')]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ServiceAccountName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Group', 'Standalone')]
        [System.String]
        $AccountType,

        [Parameter()]
        [ValidateNotNull()]
        [System.String]
        $CommonName,

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

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

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

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

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

        [Parameter()]
        [ValidateSet('None', 'RC4', 'AES128', 'AES256')]
        [System.String[]]
        $KerberosEncryptionType,

        [Parameter()]
        [ValidateNotNull()]
        [System.Boolean]
        $TrustedForDelegation,

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

        [Parameter()]
        [ValidateSet('SamAccountName', 'DistinguishedName', 'ObjectSid', 'ObjectGUID')]
        [System.String]
        $MembershipAttribute = 'SamAccountName',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path
    )

    # Need to set these to compare if not specified since user is using defaults
    [HashTable] $parameters = $PSBoundParameters
    $parameters['MembershipAttribute'] = $MembershipAttribute

    $adServiceAccountParameters = Get-ADCommonParameters @parameters

    $getTargetResourceParameters = @{
        ServiceAccountName  = $ServiceAccountName
        AccountType         = $AccountType
        DomainController    = $DomainController
        MembershipAttribute = $MembershipAttribute
    }

    @($getTargetResourceParameters.Keys) |
        ForEach-Object {
            if (-not $parameters.ContainsKey($_))
            {
                $getTargetResourceParameters.Remove($_)
            }
        }

    $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters

    if ($Ensure -eq 'Present')
    {
        # Resource should be present
        if ($getTargetResourceResult.Ensure -eq 'Present')
        {
            # Resource is present
            $createNewAdServiceAccount = $false
            $propertiesNotInDesiredState = (
                Compare-ResourcePropertyState -CurrentValues $getTargetResourceResult -DesiredValues $parameters `
                    -IgnoreProperties 'DomainController', 'Credential' | Where-Object -Property InDesiredState -eq $false)
            if ($propertiesNotInDesiredState)
            {
                if ($propertiesNotInDesiredState.ParameterName -contains 'AccountType')
                {
                    # AccountType has changed, so the account needs recreating
                    Write-Verbose -Message ($script:localizedData.RecreatingManagedServiceAccountMessage -f
                        $AccountType, $ServiceAccountName)
                    try
                    {
                        Remove-ADServiceAccount @adServiceAccountParameters -Confirm:$false
                    }
                    catch
                    {
                        $errorMessage = ($script:localizedData.RemovingManagedServiceAccountError -f
                            $AccountType, $ServiceAccountName)
                        New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
                    }

                    $createNewAdServiceAccount = $true
                }
                else
                {
                    $setServiceAccountParameters = $adServiceAccountParameters.Clone()
                    $setAdServiceAccountRequired = $false
                    $moveAdServiceAccountRequired = $false
                    $renameAdServiceAccountRequired = $false

                    foreach ($property in $propertiesNotInDesiredState)
                    {
                        if ($property.ParameterName -eq 'Path')
                        {
                            # The path has changed, so the account needs moving, but not until after any other changes
                            $moveAdServiceAccountRequired = $true
                        }
                        elseif ($property.ParameterName -eq 'CommonName')
                        {
                            # Need to set different CN using Rename-ADObject
                            $renameAdServiceAccountRequired = $true
                        }
                        else
                        {
                            $setAdServiceAccountRequired = $true

                            Write-Verbose -Message ($script:localizedData.UpdatingManagedServiceAccountPropertyMessage -f
                                $AccountType, $ServiceAccountName, $property.ParameterName, ($property.Expected -join ', '))

                            if ($property.ParameterName -eq 'ManagedPasswordPrincipals' -and $AccountType -eq 'Group')
                            {
                                $setServiceAccountParameters.Add('PrincipalsAllowedToRetrieveManagedPassword',
                                    $ManagedPasswordPrincipals)
                            }
                            else
                            {
                                $setServiceAccountParameters.Add($property.ParameterName, $property.Expected)
                            }
                        }
                    }

                    if ($setAdServiceAccountRequired)
                    {
                        try
                        {
                            Set-ADServiceAccount @setServiceAccountParameters
                        }
                        catch
                        {
                            $errorMessage = ($script:localizedData.SettingManagedServiceAccountError -f
                                $AccountType, $ServiceAccountName)
                            New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
                        }
                    }

                    if ($moveAdServiceAccountRequired)
                    {
                        Write-Verbose -Message ($script:localizedData.MovingManagedServiceAccountMessage -f
                            $AccountType, $ServiceAccountName, $getTargetResourceResult.Path, $Path)
                        $moveADObjectParameters = $adServiceAccountParameters.Clone()
                        $moveADObjectParameters.Identity = $getTargetResourceResult.DistinguishedName
                        try
                        {
                            Move-ADObject @moveADObjectParameters -TargetPath $Path
                        }
                        catch
                        {
                            $errorMessage = ($script:localizedData.MovingManagedServiceAccountError -f
                                $AccountType, $ServiceAccountName, $getTargetResourceResult.Path, $Path)
                            New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
                        }
                    }

                    if ($renameAdServiceAccountRequired)
                    {
                        # Cannot update the CN property directly. Must use Rename-ADObject
                        $renameAdObjectParameters = Get-ADCommonParameters @PSBoundParameters

                        # Using the SamAccountName for identity with Rename-ADObject does not work, use the DN instead
                        $renameAdObjectParameters['Identity'] = $getTargetResourceResult.DistinguishedName

                        Rename-ADObject @renameAdObjectParameters -NewName $CommonName
                    }
                }
            }
        }
        else
        {
            # Resource is absent
            $createNewAdServiceAccount = $true
        }

        if ($createNewAdServiceAccount)
        {
            if (-not $parameters.ContainsKey('Path'))
            {
                # Get default MSA path as one has not been specified
                try
                {
                    $domainDistinguishedName = (Get-ADDomain).DistinguishedName
                }
                catch
                {
                    $errorMessage = $script:localizedData.GettingADDomainError
                    New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
                }

                $messagePath = "CN=Managed Service Accounts,$domainDistinguishedName"
            }
            else
            {
                $messagePath = $Path
            }

            Write-Verbose -Message ($script:localizedData.AddingManagedServiceAccountMessage -f
                $AccountType, $ServiceAccountName, $messagePath)

            $newAdServiceAccountParameters = Get-ADCommonParameters @parameters -UseNameParameter -PreferCommonName

            if ($parameters.ContainsKey('CommonName'))
            {
                # We have to specify the SamAccountName to prefent errors when the common name is longer than 15 characters
                $newAdServiceAccountParameters.SamAccountName = $ServiceAccountName
            }

            if ($parameters.ContainsKey('Description'))
            {
                $newAdServiceAccountParameters.Description = $Description
            }

            if ($parameters.ContainsKey('DisplayName'))
            {
                $newAdServiceAccountParameters.DisplayName = $DisplayName
            }

            if ($parameters.ContainsKey('Path'))
            {
                $newAdServiceAccountParameters.Path = $Path
            }

            if ( $AccountType -eq 'Standalone' )
            {
                # Create standalone managed service account
                $newAdServiceAccountParameters.RestrictToSingleComputer = $true
            }
            else
            {
                # Create group managed service account
                $newAdServiceAccountParameters.DNSHostName = "$ServiceAccountName.$(Get-DomainName)"

                if ($parameters.ContainsKey('ManagedPasswordPrincipals'))
                {
                    $newAdServiceAccountParameters.PrincipalsAllowedToRetrieveManagedPassword = `
                        $ManagedPasswordPrincipals
                }
            }

            try
            {
                New-ADServiceAccount @newAdServiceAccountParameters
            }
            catch [Microsoft.ActiveDirectory.Management.ADException]
            {
                if ($_.Exception.ErrorCode -eq $script:errorCodeKdsRootKeyNotFound)
                {
                    $errorMessage = ($script:localizedData.KdsRootKeyNotFoundError -f
                        $ServiceAccountName)
                }
                else
                {
                    $errorMessage = ($script:localizedData.AddingManagedServiceAccountError -f
                        $AccountType, $ServiceAccountName, $messagePath)
                }

                New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
            }
            catch
            {
                $errorMessage = ($script:localizedData.AddingManagedServiceAccountError -f
                    $AccountType, $ServiceAccountName, $messagePath)
                New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
            }
        }
    }
    else
    {
        # Resource should be absent
        if ($getTargetResourceResult.Ensure -eq 'Present')
        {
            # Resource is present
            Write-Verbose -Message ($script:localizedData.RemovingManagedServiceAccountMessage -f
                $AccountType, $ServiceAccountName)

            try
            {
                Remove-ADServiceAccount @adServiceAccountParameters -Confirm:$false
            }
            catch
            {
                $errorMessage = ($script:localizedData.RemovingManagedServiceAccountError -f
                    $AccountType, $ServiceAccountName)
                New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
            }
        }
        else
        {
            # Resource is absent
            Write-Verbose -Message ($script:localizedData.ManagedServiceAccountInDesiredStateMessage -f
                $AccountType, $ServiceAccountName)
        }
    }
} #end function Set-TargetResource

Export-ModuleMember -Function *-TargetResource