DSCResources/MSFT_ADUser/MSFT_ADUser.psm1

[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "PasswordAuthentication")]
param()

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

$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'ActiveDirectoryDsc.Common'
Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'ActiveDirectoryDsc.Common.psm1')

$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_ADUser'

# Create a property map that maps the DSC resource parameters to the
# Active Directory user attributes.
$adPropertyMap = @(
    @{
        Parameter  = 'CommonName'
        ADProperty = 'cn'
    }
    @{
        Parameter = 'UserPrincipalName'
    }
    @{
        Parameter = 'DisplayName'
    }
    @{
        Parameter  = 'Path'
        ADProperty = 'distinguishedName'
    }
    @{
        Parameter = 'GivenName'
    }
    @{
        Parameter = 'Initials'
    }
    @{
        Parameter  = 'Surname'
        ADProperty = 'sn'
    }
    @{
        Parameter = 'Description'
    }
    @{
        Parameter = 'StreetAddress'
    }
    @{
        Parameter = 'POBox'
    }
    @{
        Parameter  = 'City'
        ADProperty = 'l'
    }
    @{
        Parameter  = 'State'
        ADProperty = 'st'
    }
    @{
        Parameter = 'PostalCode'
    }
    @{
        Parameter  = 'Country'
        ADProperty = 'c'
    }
    @{
        Parameter = 'Department'
    }
    @{
        Parameter = 'Division'
    }
    @{
        Parameter = 'Company'
    }
    @{
        Parameter  = 'Office'
        ADProperty = 'physicalDeliveryOfficeName'
    }
    @{
        Parameter  = 'JobTitle'
        ADProperty = 'title'
    }
    @{
        Parameter  = 'EmailAddress'
        ADProperty = 'mail'
    }
    @{
        Parameter = 'EmployeeID'
    }
    @{
        Parameter = 'EmployeeNumber'
    }
    @{
        Parameter = 'HomeDirectory'
    }
    @{
        Parameter = 'HomeDrive'
    }
    @{
        Parameter  = 'HomePage'
        ADProperty = 'wWWHomePage'
    }
    @{
        Parameter = 'ProfilePath'
    }
    @{
        Parameter  = 'LogonScript'
        ADProperty = 'scriptPath'
    }
    @{
        Parameter  = 'Notes'
        ADProperty = 'info'
    }
    @{
        Parameter  = 'OfficePhone'
        ADProperty = 'telephoneNumber'
    }
    @{
        Parameter  = 'MobilePhone'
        ADProperty = 'mobile'
    }
    @{
        Parameter  = 'Fax'
        ADProperty = 'facsimileTelephoneNumber'
    }
    @{
        Parameter = 'Pager'
    }
    @{
        Parameter = 'IPPhone'
    }
    @{
        Parameter = 'HomePhone'
    }
    @{
        Parameter = 'Enabled'
    }
    @{
        Parameter = 'Manager'
    }
    @{
        Parameter = 'Organization'
    }
    @{
        Parameter = 'OtherName'
    }
    @{
        Parameter  = 'ThumbnailPhoto'
        ADProperty = 'thumbnailPhoto'
    }
    @{
        Parameter          = 'PasswordNeverExpires'
        UseCmdletParameter = $true
    }
    @{
        Parameter          = 'CannotChangePassword'
        UseCmdletParameter = $true
    }
    @{
        Parameter          = 'ChangePasswordAtLogon'
        UseCmdletParameter = $true
        ADProperty         = 'pwdLastSet'
    }
    @{
        Parameter          = 'TrustedForDelegation'
        UseCmdletParameter = $true
    }
    @{
        Parameter          = 'AccountNotDelegated'
        UseCmdletParameter = $true
    }
    @{
        Parameter          = 'AllowReversiblePasswordEncryption'
        UseCmdletParameter = $true
    }
    @{
        Parameter          = 'CompoundIdentitySupported'
        UseCmdletParameter = $true
    }
    @{
        Parameter          = 'PasswordNotRequired'
        UseCmdletParameter = $true
    }
    @{
        Parameter          = 'SmartcardLogonRequired'
        UseCmdletParameter = $true
    }
    @{
        Parameter  = 'ServicePrincipalNames'
        ADProperty = 'ServicePrincipalName'
        Type       = 'Array'
    }
    @{
        Parameter = 'ProxyAddresses'
        Type      = 'Array'
    }
)

<#
    .SYNOPSIS
        Returns the current state of the Active Directory User
 
    .PARAMETER DomainName
        Name of the domain where the user account is located (only used if
        password is managed).
 
    .PARAMETER UserName
        Specifies the Security Account Manager (SAM) account name of the user
        (ldapDisplayName 'sAMAccountName').
 
    .PARAMETER DomainController
        Specifies the Active Directory Domain Services instance to use to
        perform the task.
 
    .PARAMETER Credential
        Specifies the user account credentials to use to perform this task.
#>

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

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

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

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

    Assert-Module -ModuleName 'ActiveDirectory'

    try
    {
        $adCommonParameters = Get-ADCommonParameters @PSBoundParameters

        $adProperties = @()

        # Create an array of the AD propertie names to retrieve from the property map
        foreach ($property in $adPropertyMap)
        {
            if ($property.ADProperty)
            {
                $adProperties += $property.ADProperty
            }
            else
            {
                $adProperties += $property.Parameter
            }
        }

        Write-Verbose -Message ($script:localizedData.RetrievingADUser -f $UserName, $DomainName)

        $adUser = Get-ADUser @adCommonParameters -Properties $adProperties

        Write-Verbose -Message ($script:localizedData.ADUserIsPresent -f $UserName, $DomainName)

        $ensure = 'Present'
    }
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
    {
        Write-Verbose -Message ($script:localizedData.ADUserNotPresent -f $UserName, $DomainName)

        $ensure = 'Absent'
    }
    catch
    {
        $errorMessage = $script:localizedData.RetrievingADUserError -f $UserName, $DomainName
        New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
    }

    $targetResource = @{
        DomainName        = $DomainName
        UserName          = $UserName
        Password          = $null
        DistinguishedName = $adUser.DistinguishedName; # Read-only property
        Ensure            = $ensure
        DomainController  = $DomainController
    }

    # Retrieve each property from the ADPropertyMap and add to the hashtable
    foreach ($property in $adPropertyMap)
    {
        $parameter = $property.Parameter
        if ($parameter -eq 'Path')
        {
            # The path returned is not the parent container
            if (-not [System.String]::IsNullOrEmpty($adUser.DistinguishedName))
            {
                $targetResource[$parameter] = Get-ADObjectParentDN -DN $adUser.DistinguishedName
            }
        }
        elseif ($parameter -eq 'ChangePasswordAtLogon')
        {
            if ($adUser.pwdlastset -eq 0)
            {
                $targetResource[$parameter] = $true
            }
            else
            {
                $targetResource[$parameter] = $false
            }
        }
        elseif ($parameter -eq 'ThumbnailPhoto')
        {
            if ([System.String]::IsNullOrEmpty($adUser.$parameter))
            {
                $targetResource[$parameter] = $null
                $targetResource['ThumbnailPhotoHash'] = $null
            }
            else
            {
                $targetResource[$parameter] = [System.Convert]::ToBase64String($adUser.$parameter)
                $targetResource['ThumbnailPhotoHash'] = Get-MD5HashString -Bytes $adUser.$parameter
            }
        }
        elseif ($property.ADProperty)
        {
            # The AD property name is different to the function parameter to use this
            $aDProperty = $property.ADProperty
            if ($property.Type -eq 'Array')
            {
                $targetResource[$parameter] = [System.String[]] $adUser.$aDProperty
            }
            else
            {
                $targetResource[$parameter] = $adUser.$aDProperty
            }
        }
        else
        {
            # The AD property name matches the function parameter
            if ($property.Type -eq 'Array')
            {
                $targetResource[$Parameter] = [System.String[]] $adUser.$parameter
            }
            else
            {
                $targetResource[$Parameter] = $adUser.$parameter
            }
        }
    }

    return $targetResource
} # end function Get-TargetResource

<#
    .SYNOPSIS
        Tests the state of the Active Directory user account.
 
    .PARAMETER DomainName
        Name of the domain where the user account is located (only used if
        password is managed).
 
    .PARAMETER UserName
        Specifies the Security Account Manager (SAM) account name of the user
        (ldapDisplayName 'sAMAccountName').
 
    .PARAMETER Password
        Specifies a new password value for the account.
 
    .PARAMETER Ensure
        Specifies whether the user account should be present or absent. Default
        value is 'Present'.
 
    .PARAMETER CommonName
        Specifies the common name assigned to the user account (ldapDisplayName
        'cn'). If not specified the default value will be the same value
        provided in parameter UserName.
 
    .PARAMETER UserPrincipalName
        Specifies the User Principal Name (UPN) assigned to the user account
        (ldapDisplayName 'userPrincipalName').
 
    .PARAMETER DisplayName
        Specifies the display name of the object (ldapDisplayName
        'displayName').
 
    .PARAMETER Path
        Specifies the X.500 path of the Organizational Unit (OU) or container
        where the new object is created.
 
    .PARAMETER GivenName
        Specifies the user's given name (ldapDisplayName 'givenName').
 
    .PARAMETER Initials
        Specifies the initials that represent part of a user's name
        (ldapDisplayName 'initials').
 
    .PARAMETER Surname
        Specifies the user's last name or surname (ldapDisplayName 'sn').
 
    .PARAMETER Description
        Specifies a description of the object (ldapDisplayName 'description').
 
    .PARAMETER StreetAddress
        Specifies the user's street address (ldapDisplayName 'streetAddress').
 
    .PARAMETER POBox
        Specifies the user's post office box number (ldapDisplayName
        'postOfficeBox').
 
    .PARAMETER City
        Specifies the user's town or city (ldapDisplayName 'l').
 
    .PARAMETER State
        Specifies the user's or Organizational Unit's state or province
        (ldapDisplayName 'st').
 
    .PARAMETER PostalCode
        Specifies the user's postal code or zip code (ldapDisplayName
        'postalCode').
 
    .PARAMETER Country
        Specifies the country or region code for the user's language of choice
        (ldapDisplayName 'c').
 
    .PARAMETER Department
        Specifies the user's department (ldapDisplayName 'department').
 
    .PARAMETER Division
        Specifies the user's division (ldapDisplayName 'division').
 
    .PARAMETER Company
        Specifies the user's company (ldapDisplayName 'company').
 
    .PARAMETER Office
        Specifies the location of the user's office or place of business
        (ldapDisplayName 'physicalDeliveryOfficeName').
 
    .PARAMETER JobTitle
        Specifies the user's title (ldapDisplayName 'title').
 
    .PARAMETER EmailAddress
        Specifies the user's e-mail address (ldapDisplayName 'mail').
 
    .PARAMETER EmployeeID
        Specifies the user's employee ID (ldapDisplayName 'employeeID').
 
    .PARAMETER EmployeeNumber
        Specifies the user's employee number (ldapDisplayName 'employeeNumber').
 
    .PARAMETER HomeDirectory
        Specifies a user's home directory path (ldapDisplayName
        'homeDirectory').
 
    .PARAMETER HomeDrive
        Specifies a drive that is associated with the UNC path defined by the
        HomeDirectory property (ldapDisplayName 'homeDrive').
 
    .PARAMETER HomePage
        Specifies the URL of the home page of the object (ldapDisplayName
        'wWWHomePage').
 
    .PARAMETER ProfilePath
        Specifies a path to the user's profile (ldapDisplayName 'profilePath').
 
    .PARAMETER LogonScript
        Specifies a path to the user's log on script (ldapDisplayName
        'scriptPath').
 
    .PARAMETER Notes
        Specifies the notes attached to the user's accoutn (ldapDisplayName
        'info').
 
    .PARAMETER OfficePhone
        Specifies the user's office telephone number (ldapDisplayName
        'telephoneNumber').
 
    .PARAMETER MobilePhone
        Specifies the user's mobile phone number (ldapDisplayName 'mobile').
 
    .PARAMETER Fax
        Specifies the user's fax phone number (ldapDisplayName
        'facsimileTelephoneNumber').
 
    .PARAMETER HomePhone
        Specifies the user's home telephone number (ldapDisplayName
        'homePhone').
 
    .PARAMETER Pager
        Specifies the user's pager number (ldapDisplayName 'pager').
 
    .PARAMETER IPPhone
        Specifies the user's IP telephony phone number (ldapDisplayName
        'ipPhone').
 
    .PARAMETER Manager
        Specifies the user's manager specified as a Distinguished Name
        (ldapDisplayName 'manager').
 
    .PARAMETER LogonWorkstations
        Specifies the computers that the user can access. To specify more than
        one computer, create a single comma-separated list. You can identify a
        computer by using the Security Account Manager (SAM) account name
        (sAMAccountName) or the DNS host name of the computer. The SAM account
        name is the same as the NetBIOS name of the computer. The LDAP display
        name (ldapDisplayName) for this property is userWorkStations.
 
    .PARAMETER Organization
        Specifies the user's organization. This parameter sets the Organization
        property of a user object. The LDAP display name (ldapDisplayName) of
        this property is 'o'.
 
    .PARAMETER OtherName
        Specifies a name in addition to a user's given name and surname, such as
        the user's middle name. This parameter sets the OtherName property of a
        user object. The LDAP display name (ldapDisplayName) of this property is
        'middleName'.
 
    .PARAMETER Enabled
        Specifies if the account is enabled. Default value is $true.
 
    .PARAMETER CannotChangePassword
        Specifies whether the account password can be changed.
 
    .PARAMETER ChangePasswordAtLogon
        Specifies whether the account password must be changed during the next
        logon attempt. This will only be enabled when the user is initially
        created. This parameter cannot be set to $true if the parameter
        PasswordNeverExpires is also set to $true.
 
    .PARAMETER PasswordNeverExpires
        Specifies whether the password of an account can expire.
 
    .PARAMETER TrustedForDelegation
        Specifies whether an account is trusted for Kerberos delegation. Default
        value is $false.
 
    .PARAMETER AccountNotDelegated
        Indicates whether the security context of the user is delegated to a
        service. When this parameter is set to true, the security context of
        the account is not delegated to a service even when the service account
        is set as trusted for Kerberos delegation. This parameter sets the
        AccountNotDelegated property for an Active Directory account. This
        parameter also sets the ADS_UF_NOT_DELEGATED flag of the Active
        Directory User Account Control (UAC) attribute.
 
    .PARAMETER AllowReversiblePasswordEncryption
        Indicates whether reversible password encryption is allowed for the
        account. This parameter sets the AllowReversiblePasswordEncryption
        property of the account. This parameter also sets the
        ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED flag of the Active Directory User
        Account Control (UAC) attribute.
 
    .PARAMETER CompoundIdentitySupported
        Specifies whether an account supports Kerberos service tickets which
        includes the authorization data for the user's device. This value sets
        the compound identity supported flag of the Active Directory
        msDS-SupportedEncryptionTypes attribute.
 
    .PARAMETER PasswordNotRequired
        Specifies whether the account requires a password. A password is not
        required for a new account. This parameter sets the PasswordNotRequired
        property of an account object.
 
    .PARAMETER SmartcardLogonRequired
        Specifies whether a smart card is required to logon. This parameter sets
        the SmartCardLoginRequired property for a user object. This parameter
        also sets the ADS_UF_SMARTCARD_REQUIRED flag of the Active Directory
        User Account Control attribute.
 
    .PARAMETER DomainController
        Specifies the Active Directory Domain Services instance to use to
        perform the task.
 
    .PARAMETER Credential
        Specifies the user account credentials to use to perform this task.
 
    .PARAMETER PasswordAuthentication
        Specifies the authentication context type used when testing passwords.
        Default value is 'Default'.
 
    .PARAMETER PasswordNeverResets
        Specifies whether existing user's password should be reset. Default
        value is $false.
 
    .PARAMETER RestoreFromRecycleBin
        Try to restore the user object from the recycle bin before creating a
        new one.
 
    .PARAMETER ServicePrincipalNames
        Specifies the service principal names for the user account.
 
    .PARAMETER ProxyAddresses
        Specifies the proxy addresses for the user account.
 
    .PARAMETER ThumbnailPhoto
        Specifies the thumbnail photo to be used for the user object. Can be set
        either to a path pointing to a .jpg-file, or to a Base64-encoded jpeg
        image. If set to an empty string ('') the current thumbnail photo will be
        removed. The property ThumbnailPhoto will always return the image as a
        Base64-encoded string even if the configuration specified a file path.
#>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter()]
        [ValidateNotNull()]
        [System.Boolean]
        $Enabled = $true,

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

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

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

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

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

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

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

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

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

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

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

        [Parameter()]
        [ValidateSet('Default', 'Negotiate')]
        [System.String]
        $PasswordAuthentication = 'Default',

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

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

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

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

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

    <#
        This is a workaround to make the resource able to enter debug mode.
        For more information see issue https://github.com/PowerShell/ActiveDirectoryDsc/issues/427.
    #>

    if (-not $PSBoundParameters.ContainsKey('CommonName'))
    {
        $CommonName = $UserName
    }
    Assert-Parameters @PSBoundParameters

    $getParameters = @{
        DomainName = $DomainName
        UserName   = $UserName
    }

    if ($PSBoundParameters.ContainsKey('DomainController'))
    {
        $getParameters['DomainController'] = $DomainController
    }

    if ($PSBoundParameters.ContainsKey('Credential'))
    {
        $getParameters['Credential'] = $Credential
    }

    $targetResource = Get-TargetResource @getParameters

    $isCompliant = $true

    if ($Ensure -eq 'Absent')
    {
        if ($targetResource.Ensure -eq 'Present')
        {
            Write-Verbose -Message ($script:localizedData.ADUserNotDesiredPropertyState -f 'Ensure', $PSBoundParameters.Ensure, $targetResource.Ensure)
            $isCompliant = $false
        }
    }
    else
    {
        # Add common name, Ensure and enabled as they may not be explicitly passed and we want to enumerate them
        $PSBoundParameters['Ensure'] = $Ensure
        $PSBoundParameters['Enabled'] = $Enabled

        foreach ($parameter in $PSBoundParameters.Keys)
        {
            if ($parameter -eq 'Password' -and $PasswordNeverResets -eq $false)
            {
                $testPasswordParams = @{
                    Username               = $UserName
                    Password               = $Password
                    DomainName             = $DomainName
                    PasswordAuthentication = $PasswordAuthentication
                }

                if ($Credential)
                {
                    $testPasswordParams['Credential'] = $Credential
                }

                if (-not (Test-Password @testPasswordParams))
                {
                    Write-Verbose -Message ($script:localizedData.ADUserNotDesiredPropertyState -f 'Password', '<Password>', '<Password>')
                    $isCompliant = $false
                }
            }
            elseif ($parameter -eq 'ChangePasswordAtLogon' -and $PSBoundParameters.$parameter -eq $true -and $targetResource.Ensure -eq 'Present')
            {
                # Only process the ChangePasswordAtLogon = $true parameter during new user creation
                continue
            }
            elseif ($parameter -eq 'ThumbnailPhoto')
            {
                <#
                    Compare thumbnail hash, if they are the same the function
                    Compare-ThumbnailPhoto returns $null if they are the same.
                #>

                $compareThumbnailPhotoResult = Compare-ThumbnailPhoto -DesiredThumbnailPhoto $ThumbnailPhoto -CurrentThumbnailPhotoHash $targetResource.ThumbnailPhotoHash

                if ($compareThumbnailPhotoResult)
                {
                    Write-Verbose -Message (
                        $script:localizedData.ADUserNotDesiredPropertyState `
                            -f $parameter, $compareThumbnailPhotoResult.DesiredThumbnailPhotoHash, $compareThumbnailPhotoResult.CurrentThumbnailPhotoHash
                    )

                    $isCompliant = $false
                }
            }
            # Only check properties that are returned by Get-TargetResource
            elseif ($targetResource.ContainsKey($parameter))
            {
                # This check is required to be able to explicitly remove values with an empty string, if required
                if (([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter)) -and ([System.String]::IsNullOrEmpty($targetResource.$parameter)))
                {
                    <#
                        Both values are null/empty and therefore we are compliant
                        Must catch this scenario separately, as Compare-Object can't compare Null objects
                    #>

                }
                elseif (($null -ne $PSBoundParameters.$parameter -and $null -eq $targetResource.$parameter) -or
                    ($null -eq $PSBoundParameters.$parameter -and $null -ne $targetResource.$parameter) -or
                    (Compare-Object -ReferenceObject $PSBoundParameters.$parameter -DifferenceObject $targetResource.$parameter))
                {
                    Write-Verbose -Message ($script:localizedData.ADUserNotDesiredPropertyState -f $parameter,
                        ($PSBoundParameters.$parameter -join '; '), ($targetResource.$parameter -join '; '))
                    $isCompliant = $false
                }
            }
        } #end foreach PSBoundParameter
    }

    return $isCompliant
} # end function Test-TargetResource

<#
    .SYNOPSIS
        Sets the properties of the Active Directory user account.
 
    .PARAMETER DomainName
        Name of the domain where the user account is located (only used if
        password is managed).
 
    .PARAMETER UserName
        Specifies the Security Account Manager (SAM) account name of the user
        (ldapDisplayName 'sAMAccountName').
 
    .PARAMETER Password
        Specifies a new password value for the account.
 
    .PARAMETER Ensure
        Specifies whether the user account should be present or absent. Default
        value is 'Present'.
 
    .PARAMETER CommonName
        Specifies the common name assigned to the user account (ldapDisplayName
        'cn'). If not specified the default value will be the same value
        provided in parameter UserName.
 
    .PARAMETER UserPrincipalName
        Specifies the User Principal Name (UPN) assigned to the user account
        (ldapDisplayName 'userPrincipalName').
 
    .PARAMETER DisplayName
        Specifies the display name of the object (ldapDisplayName
        'displayName').
 
    .PARAMETER Path
        Specifies the X.500 path of the Organizational Unit (OU) or container
        where the new object is created.
 
    .PARAMETER GivenName
        Specifies the user's given name (ldapDisplayName 'givenName').
 
    .PARAMETER Initials
        Specifies the initials that represent part of a user's name
        (ldapDisplayName 'initials').
 
    .PARAMETER Surname
        Specifies the user's last name or surname (ldapDisplayName 'sn').
 
    .PARAMETER Description
        Specifies a description of the object (ldapDisplayName 'description').
 
    .PARAMETER StreetAddress
        Specifies the user's street address (ldapDisplayName 'streetAddress').
 
    .PARAMETER POBox
        Specifies the user's post office box number (ldapDisplayName
        'postOfficeBox').
 
    .PARAMETER City
        Specifies the user's town or city (ldapDisplayName 'l').
 
    .PARAMETER State
        Specifies the user's or Organizational Unit's state or province
        (ldapDisplayName 'st').
 
    .PARAMETER PostalCode
        Specifies the user's postal code or zip code (ldapDisplayName
        'postalCode').
 
    .PARAMETER Country
        Specifies the country or region code for the user's language of choice
        (ldapDisplayName 'c').
 
    .PARAMETER Department
        Specifies the user's department (ldapDisplayName 'department').
 
    .PARAMETER Division
        Specifies the user's division (ldapDisplayName 'division').
 
    .PARAMETER Company
        Specifies the user's company (ldapDisplayName 'company').
 
    .PARAMETER Office
        Specifies the location of the user's office or place of business
        (ldapDisplayName 'physicalDeliveryOfficeName').
 
    .PARAMETER JobTitle
        Specifies the user's title (ldapDisplayName 'title').
 
    .PARAMETER EmailAddress
        Specifies the user's e-mail address (ldapDisplayName 'mail').
 
    .PARAMETER EmployeeID
        Specifies the user's employee ID (ldapDisplayName 'employeeID').
 
    .PARAMETER EmployeeNumber
        Specifies the user's employee number (ldapDisplayName 'employeeNumber').
 
    .PARAMETER HomeDirectory
        Specifies a user's home directory path (ldapDisplayName
        'homeDirectory').
 
    .PARAMETER HomeDrive
        Specifies a drive that is associated with the UNC path defined by the
        HomeDirectory property (ldapDisplayName 'homeDrive').
 
    .PARAMETER HomePage
        Specifies the URL of the home page of the object (ldapDisplayName
        'wWWHomePage').
 
    .PARAMETER ProfilePath
        Specifies a path to the user's profile (ldapDisplayName 'profilePath').
 
    .PARAMETER LogonScript
        Specifies a path to the user's log on script (ldapDisplayName
        'scriptPath').
 
    .PARAMETER Notes
        Specifies the notes attached to the user's accoutn (ldapDisplayName
        'info').
 
    .PARAMETER OfficePhone
        Specifies the user's office telephone number (ldapDisplayName
        'telephoneNumber').
 
    .PARAMETER MobilePhone
        Specifies the user's mobile phone number (ldapDisplayName 'mobile').
 
    .PARAMETER Fax
        Specifies the user's fax phone number (ldapDisplayName
        'facsimileTelephoneNumber').
 
    .PARAMETER HomePhone
        Specifies the user's home telephone number (ldapDisplayName
        'homePhone').
 
    .PARAMETER Pager
        Specifies the user's pager number (ldapDisplayName 'pager').
 
    .PARAMETER IPPhone
        Specifies the user's IP telephony phone number (ldapDisplayName
        'ipPhone').
 
    .PARAMETER Manager
        Specifies the user's manager specified as a Distinguished Name
        (ldapDisplayName 'manager').
 
    .PARAMETER LogonWorkstations
        Specifies the computers that the user can access. To specify more than
        one computer, create a single comma-separated list. You can identify a
        computer by using the Security Account Manager (SAM) account name
        (sAMAccountName) or the DNS host name of the computer. The SAM account
        name is the same as the NetBIOS name of the computer. The LDAP display
        name (ldapDisplayName) for this property is userWorkStations.
 
    .PARAMETER Organization
        Specifies the user's organization. This parameter sets the Organization
        property of a user object. The LDAP display name (ldapDisplayName) of
        this property is 'o'.
 
    .PARAMETER OtherName
        Specifies a name in addition to a user's given name and surname, such as
        the user's middle name. This parameter sets the OtherName property of a
        user object. The LDAP display name (ldapDisplayName) of this property is
        'middleName'.
 
    .PARAMETER Enabled
        Specifies if the account is enabled. Default value is $true.
 
    .PARAMETER CannotChangePassword
        Specifies whether the account password can be changed.
 
    .PARAMETER ChangePasswordAtLogon
        Specifies whether the account password must be changed during the next
        logon attempt. This will only be enabled when the user is initially
        created. This parameter cannot be set to $true if the parameter
        PasswordNeverExpires is also set to $true.
 
    .PARAMETER PasswordNeverExpires
        Specifies whether the password of an account can expire.
 
    .PARAMETER TrustedForDelegation
        Specifies whether an account is trusted for Kerberos delegation. Default
        value is $false.
 
    .PARAMETER AccountNotDelegated
        Indicates whether the security context of the user is delegated to a
        service. When this parameter is set to true, the security context of
        the account is not delegated to a service even when the service account
        is set as trusted for Kerberos delegation. This parameter sets the
        AccountNotDelegated property for an Active Directory account. This
        parameter also sets the ADS_UF_NOT_DELEGATED flag of the Active
        Directory User Account Control (UAC) attribute.
 
    .PARAMETER AllowReversiblePasswordEncryption
        Indicates whether reversible password encryption is allowed for the
        account. This parameter sets the AllowReversiblePasswordEncryption
        property of the account. This parameter also sets the
        ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED flag of the Active Directory User
        Account Control (UAC) attribute.
 
    .PARAMETER CompoundIdentitySupported
        Specifies whether an account supports Kerberos service tickets which
        includes the authorization data for the user's device. This value sets
        the compound identity supported flag of the Active Directory
        msDS-SupportedEncryptionTypes attribute.
 
    .PARAMETER PasswordNotRequired
        Specifies whether the account requires a password. A password is not
        required for a new account. This parameter sets the PasswordNotRequired
        property of an account object.
 
    .PARAMETER SmartcardLogonRequired
        Specifies whether a smart card is required to logon. This parameter sets
        the SmartCardLoginRequired property for a user object. This parameter
        also sets the ADS_UF_SMARTCARD_REQUIRED flag of the Active Directory
        User Account Control attribute.
 
    .PARAMETER DomainController
        Specifies the Active Directory Domain Services instance to use to
        perform the task.
 
    .PARAMETER Credential
        Specifies the user account credentials to use to perform this task.
 
    .PARAMETER PasswordAuthentication
        Specifies the authentication context type used when testing passwords.
        Default value is 'Default'.
 
    .PARAMETER PasswordNeverResets
        Specifies whether existing user's password should be reset. Default
        value is $false.
 
    .PARAMETER RestoreFromRecycleBin
        Try to restore the user object from the recycle bin before creating a
        new one.
 
    .PARAMETER ServicePrincipalNames
        Specifies the service principal names for the user account.
 
    .PARAMETER ProxyAddresses
        Specifies the proxy addresses for the user account.
 
    .PARAMETER ThumbnailPhoto
        Specifies the thumbnail photo to be used for the user object. Can be set
        either to a path pointing to a .jpg-file, or to a Base64-encoded jpeg
        image. If set to an empty string ('') the current thumbnail photo will be
        removed. The property ThumbnailPhoto will always return the image as a
        Base64-encoded string even if the configuration specified a file path.
#>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter()]
        [ValidateNotNull()]
        [System.Boolean]
        $Enabled = $true,

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

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

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

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

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

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

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

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

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

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

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

        [Parameter()]
        [ValidateSet('Default', 'Negotiate')]
        [System.String]
        $PasswordAuthentication = 'Default',

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

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

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

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

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

    <#
        This is a workaround to make the resource able to enter debug mode.
        For more information see issue https://github.com/PowerShell/ActiveDirectoryDsc/issues/427.
    #>

    if (-not $PSBoundParameters.ContainsKey('CommonName'))
    {
        $CommonName = $UserName
    }

    Assert-Parameters @PSBoundParameters

    $getParameters = @{
        DomainName = $DomainName
        UserName   = $UserName
    }

    if ($PSBoundParameters.ContainsKey('DomainController'))
    {
        $getParameters['DomainController'] = $DomainController
    }

    if ($PSBoundParameters.ContainsKey('Credential'))
    {
        $getParameters['Credential'] = $Credential
    }

    $targetResource = Get-TargetResource @getParameters

    # Add common name, Ensure and enabled as they may not be explicitly passed
    $PSBoundParameters['Ensure'] = $Ensure
    $PSBoundParameters['Enabled'] = $Enabled
    $newADUser = $false

    if ($Ensure -eq 'Present')
    {
        if ($targetResource.Ensure -eq 'Absent')
        {
            # Try to restore account if it exists
            if ($RestoreFromRecycleBin)
            {
                Write-Verbose -Message ($script:localizedData.RestoringUser -f $UserName)
                $restoreParams = Get-ADCommonParameters @PSBoundParameters
                $restorationSuccessful = Restore-ADCommonObject @restoreParams -ObjectClass User -ErrorAction Stop
            }

            if (-not $RestoreFromRecycleBin -or ($RestoreFromRecycleBin -and -not $restorationSuccessful))
            {
                # User does not exist and needs creating
                $newADUserParams = Get-ADCommonParameters @PSBoundParameters -UseNameParameter

                if ($PSBoundParameters.ContainsKey('Path'))
                {
                    $newADUserParams['Path'] = $Path
                }

                # Populate the AccountPassword parameter of New-ADUser if password declared
                if ($PSBoundParameters.ContainsKey('Password'))
                {
                    $newADUserParams['AccountPassword'] = $Password.Password
                }

                Write-Verbose -Message ($script:localizedData.AddingADUser -f $UserName)

                New-ADUser @newADUserParams -SamAccountName $UserName

                # Now retrieve the newly created user
                $targetResource = Get-TargetResource @getParameters

                $newADUser = $true
            }
        }

        $setADUserParams = Get-ADCommonParameters @PSBoundParameters

        $replaceUserProperties = @{ }
        $clearUserProperties = @()
        $moveUserRequired = $false
        $renameUserRequired = $false

        foreach ($parameter in $PSBoundParameters.Keys)
        {
            # Only check/action properties specified/declared parameters that match one of the function's
            # parameters. This will ignore common parameters such as -Verbose etc.
            if ($targetResource.ContainsKey($parameter))
            {
                # Find the associated AD property
                $adProperty = $adPropertyMap |
                Where-Object -FilterScript {
                    $_.Parameter -eq $parameter
                }

                if ($parameter -eq 'Path' -and ($PSBoundParameters.Path -ne $targetResource.Path))
                {
                    # Move user after any property changes
                    $moveUserRequired = $true
                }
                elseif ($parameter -eq 'CommonName' -and ($PSBoundParameters.CommonName -ne $targetResource.CommonName))
                {
                    # Rename user after any property changes
                    $renameUserRequired = $true
                }
                elseif ($parameter -eq 'Password' -and $PasswordNeverResets -eq $false)
                {
                    $adCommonParameters = Get-ADCommonParameters @PSBoundParameters
                    $testPasswordParams = @{
                        Username               = $UserName
                        Password               = $Password
                        DomainName             = $DomainName
                        PasswordAuthentication = $PasswordAuthentication
                    }

                    if ($Credential)
                    {
                        $testPasswordParams['Credential'] = $Credential
                    }

                    if (-not (Test-Password @testPasswordParams))
                    {
                        Write-Verbose -Message ($script:localizedData.SettingADUserPassword -f $UserName)

                        Set-ADAccountPassword @adCommonParameters -Reset -NewPassword $Password.Password
                    }
                }
                elseif ($parameter -eq 'ChangePasswordAtLogon' -and $PSBoundParameters.$parameter -eq $true -and $newADUser -eq $false)
                {
                    # Only process the ChangePasswordAtLogon = $true parameter during new user creation
                    continue
                }
                elseif ($parameter -eq 'ThumbnailPhoto')
                {
                    <#
                        Compare thumbnail hash, if they are the same the function
                        Compare-ThumbnailPhoto returns $null if they are the same.
                    #>

                    if (Compare-ThumbnailPhoto -DesiredThumbnailPhoto $ThumbnailPhoto -CurrentThumbnailPhotoHash $targetResource.ThumbnailPhotoHash)
                    {
                        if ($ThumbnailPhoto -eq [System.String]::Empty)
                        {
                            $clearUserProperties += $adProperty.ADProperty

                            Write-Verbose -Message (
                                $script:localizedData.RemovingThumbnailPhoto -f $adProperty.ADProperty
                            )
                        }
                        else
                        {
                            [System.Byte[]] $thumbnailPhotoBytes = Get-ThumbnailByteArray -ThumbnailPhoto $ThumbnailPhoto

                            $thumbnailPhotoHash = Get-MD5HashString -Bytes $thumbnailPhotoBytes

                            Write-Verbose -Message (
                                $script:localizedData.UpdatingThumbnailPhotoProperty -f $adProperty.ADProperty, $thumbnailPhotoHash
                            )

                            $replaceUserProperties[$adProperty.ADProperty] = $thumbnailPhotoBytes
                        }
                    }
                }
                elseif ($parameter -eq 'Enabled' -and $PSBoundParameters.$parameter -ne $targetResource.$parameter)
                {
                    <#
                        We cannot enable/disable an account with -Add or -Replace parameters, but inform that
                        we will change this as it is out of compliance (it always gets set anyway).
                    #>

                    Write-Verbose -Message ($script:localizedData.UpdatingADUserProperty -f $parameter, $PSBoundParameters.$parameter)
                }
                elseif (([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter)) -and ([System.String]::IsNullOrEmpty($targetResource.$parameter)))
                {
                    <#
                        Both values are null/empty and therefore we are compliant
                        Must catch this scenario separately, as Compare-Object can't compare Null objects
                    #>

                }
                # Use Compare-Object to allow comparison of string and array parameters
                elseif (($null -ne $PSBoundParameters.$parameter -and $null -eq $targetResource.$parameter) -or
                    ($null -eq $PSBoundParameters.$parameter -and $null -ne $targetResource.$parameter) -or
                    (Compare-Object -ReferenceObject $PSBoundParameters.$parameter -DifferenceObject $targetResource.$parameter))
                {
                    if ([System.String]::IsNullOrEmpty($adProperty))
                    {
                        # We can't do anything is an empty AD property!
                    }
                    else
                    {
                        if ([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter) -and (-not ([System.String]::IsNullOrEmpty($targetResource.$parameter))))
                        {
                            # We are clearing the existing value
                            Write-Verbose -Message ($script:localizedData.ClearingADUserProperty -f $parameter)
                            if ($adProperty.UseCmdletParameter -eq $true)
                            {
                                # We need to pass the parameter explicitly to Set-ADUser, not via -Clear
                                $setADUserParams[$adProperty.Parameter] = $PSBoundParameters.$parameter
                            }
                            elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty))
                            {
                                $clearUserProperties += $adProperty.Parameter
                            }
                            else
                            {
                                $clearUserProperties += $adProperty.ADProperty
                            }
                        } #end if clear existing value
                        else
                        {
                            # We are replacing the existing value
                            Write-Verbose -Message ($script:localizedData.UpdatingADUserProperty -f $parameter, ($PSBoundParameters.$parameter -join ','))

                            if ($adProperty.UseCmdletParameter -eq $true)
                            {
                                # We need to pass the parameter explicitly to Set-ADUser, not via -Replace
                                $setADUserParams[$adProperty.Parameter] = $PSBoundParameters.$parameter
                            }
                            elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty))
                            {
                                $replaceUserProperties[$adProperty.Parameter] = $PSBoundParameters.$parameter
                            }
                            else
                            {
                                $replaceUserProperties[$adProperty.ADProperty] = $PSBoundParameters.$parameter
                            }
                        }
                    } #end if replace existing value
                }

            } #end if TargetResource parameter
        } #end foreach PSBoundParameter

        # Only pass -Clear and/or -Replace if we have something to set/change
        if ($replaceUserProperties.Count -gt 0)
        {
            $setADUserParams['Replace'] = $replaceUserProperties
        }

        if ($clearUserProperties.Count -gt 0)
        {
            $setADUserParams['Clear'] = $clearUserProperties;
        }

        Write-Verbose -Message ($script:localizedData.UpdatingADUser -f $UserName)

        [ref] $null = Set-ADUser @setADUserParams -Enabled $Enabled

        if ($moveUserRequired)
        {
            # Cannot move users by updating the DistinguishedName property
            $moveAdObjectParameters = Get-ADCommonParameters @PSBoundParameters

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

            Write-Verbose -Message ($script:localizedData.MovingADUser -f $targetResource.Path, $PSBoundParameters.Path)

            Move-ADObject @moveAdObjectParameters -TargetPath $PSBoundParameters.Path

            # Set new target resource DN in case a rename is also required
            $targetResource.DistinguishedName = "cn=$($targetResource.CommonName),$($PSBoundParameters.Path)"
        }

        if ($renameUserRequired)
        {
            # Cannot rename users by updating the CN property directly
            $renameAdObjectParameters = Get-ADCommonParameters @PSBoundParameters

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

            Write-Verbose -Message ($script:localizedData.RenamingADUser -f $targetResource.CommonName, $PSBoundParameters.CommonName)

            Rename-ADObject @renameAdObjectParameters -NewName $PSBoundParameters.CommonName
        }
    }
    elseif (($Ensure -eq 'Absent') -and ($targetResource.Ensure -eq 'Present'))
    {
        # User exists and needs removing
        Write-Verbose ($script:localizedData.RemovingADUser -f $UserName)

        $adCommonParameters = Get-ADCommonParameters @PSBoundParameters

        [ref] $null = Remove-ADUser @adCommonParameters -Confirm:$false
    }

} # end function Set-TargetResource

<#
    .SYNOPSIS
        Internal function to validate unsupported options/configurations.
 
    .PARAMETER Password
        Specifies a new password value for the account.
 
    .PARAMETER Enabled
        Specifies if the account is enabled. Default value is $true.
 
    .PARAMETER ChangePasswordAtLogon
        Specifies whether the account password must be changed during the next
        logon attempt. This will only be enabled when the user is initially
        created. This parameter cannot be set to $true if the parameter
        PasswordNeverExpires is also set to $true.
 
    .PARAMETER PasswordNeverExpires
        Specifies whether the password of an account can expire.
 
    .PARAMETER IgnoredArguments
        Sets the rest of the arguments that are not passed into the this
        function.
#>

function Assert-Parameters
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        $Password,

        [Parameter()]
        [ValidateNotNull()]
        [System.Boolean]
        $Enabled = $true,

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

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

        [Parameter(ValueFromRemainingArguments)]
        $IgnoredArguments
    )

    # We cannot test/set passwords on disabled AD accounts
    if (($PSBoundParameters.ContainsKey('Password')) -and ($Enabled -eq $false))
    {
        $errorMessage = $script:localizedData.PasswordParameterConflictError -f 'Enabled', $false, 'Password'
        New-InvalidArgumentException -ArgumentName 'Password' -Message $errorMessage
    }

    # ChangePasswordAtLogon cannot be set for an account that also has PasswordNeverExpires set
    if ($PSBoundParameters.ContainsKey('ChangePasswordAtLogon') -and $PSBoundParameters['ChangePasswordAtLogon'] -eq $true -and
        $PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PSBoundParameters['PasswordNeverExpires'] -eq $true)
    {
        $errorMessage = $script:localizedData.ChangePasswordParameterConflictError
        New-InvalidArgumentException -ArgumentName 'ChangePasswordAtLogon, PasswordNeverExpires' -Message $errorMessage
    }

} #end function Assert-Parameters

<#
    .SYNOPSIS
        Internal function to test the validity of a user's password.
 
    .PARAMETER DomainName
        Name of the domain where the user account is located (only used if
        password is managed).
 
    .PARAMETER UserName
        Specifies the Security Account Manager (SAM) account name of the user
        (ldapDisplayName 'sAMAccountName').
 
    .PARAMETER Password
        Specifies a new password value for the account.
 
    .PARAMETER Credential
        Specifies the user account credentials to use to perform this task.
 
    .PARAMETER PasswordAuthentication
        Specifies the authentication context type used when testing passwords.
        Default value is 'Default'.
#>

function Test-Password
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $DomainName,

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

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

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

        # Specifies the authentication context type when testing user passwords #61
        [Parameter(Mandatory = $true)]
        [ValidateSet('Default', 'Negotiate')]
        [System.String]
        $PasswordAuthentication
    )

    Write-Verbose -Message ($script:localizedData.CreatingADDomainConnection -f $DomainName)

    $typeName = 'System.DirectoryServices.AccountManagement.PrincipalContext'

    Add-TypeAssembly -AssemblyName 'System.DirectoryServices.AccountManagement' -TypeName $typeName

    <#
        If the domain name contains a distinguished name, set it to the fully
        qualified domain name (FQDN) instead.
        If the $DomainName does not contain a distinguished name the function
        Get-ADDomainNameFromDistinguishedName returns $null.
    #>

    $fullyQualifiedDomainName = Get-ADDomainNameFromDistinguishedName -DistinguishedName $DomainName
    if ($fullyQualifiedDomainName)
    {
        $DomainName = $fullyQualifiedDomainName
    }

    if ($Credential)
    {
        Write-Verbose -Message (
            $script:localizedData.TestPasswordUsingImpersonation -f $Credential.UserName, $UserName
        )

        $principalContext = New-Object -TypeName $typeName -ArgumentList @(
            [System.DirectoryServices.AccountManagement.ContextType]::Domain,
            $DomainName,
            $Credential.UserName,
            $Credential.GetNetworkCredential().Password
        )
    }
    else
    {
        $principalContext = New-Object -TypeName $typeName -ArgumentList @(
            [System.DirectoryServices.AccountManagement.ContextType]::Domain,
            $DomainName,
            $null,
            $null
        )
    }

    Write-Verbose -Message ($script:localizedData.CheckingADUserPassword -f $UserName)

    if ($PasswordAuthentication -eq 'Negotiate')
    {
        return $principalContext.ValidateCredentials(
            $UserName,
            $Password.GetNetworkCredential().Password,
            [System.DirectoryServices.AccountManagement.ContextOptions]::Negotiate -bor
            [System.DirectoryServices.AccountManagement.ContextOptions]::Signing -bor
            [System.DirectoryServices.AccountManagement.ContextOptions]::Sealing
        )
    }
    else
    {
        # Use default authentication context
        return $principalContext.ValidateCredentials(
            $UserName,
            $Password.GetNetworkCredential().Password
        )
    }
} # end function Test-Password

<#
    .SYNOPSIS
        Internal function to calculate the thumbnailPhoto hash.
 
    .PARAMETER Bytes
        A Byte array that will be hashed.
 
    .OUTPUTS
        Returns the MD5 hash of the bytes past in parameter Bytes, or $null if
        the value of parameter is $null.
#>

function Get-MD5HashString
{
    [CmdletBinding()]
    [OutputType([System.Byte[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [System.Byte[]]
        $Bytes
    )

    $md5ReturnValue = $null

    if ($null -ne $Bytes)
    {
        $md5 = [System.Security.Cryptography.MD5]::Create()
        $hashBytes = $md5.ComputeHash($Bytes)

        $md5ReturnValue = [System.BitConverter]::ToString($hashBytes).Replace('-', '')
    }

    return $md5ReturnValue
} # end function Get-MD5HashString

<#
    .SYNOPSIS
        Internal function to convert either a .jpg-file or a Base64-encoded jpeg
        image to a Byte array.
 
    .PARAMETER ThumbnailPhoto
        A string of either a .jpg-file or the string of a Base64-encoded jpeg image.
 
    .OUTPUTS
        Returns a byte array of the image specified in the parameter ThumbnailPhoto.
#>

function Get-ThumbnailByteArray
{
    [CmdletBinding()]
    [OutputType([System.Byte[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ThumbnailPhoto
    )

    # If $ThumbnailPhoto contains '.' or '\' then we assume that we have a file path
    if ($ThumbnailPhoto -match '\.|\\')
    {
        if (Test-Path -Path $ThumbnailPhoto)
        {
            Write-Verbose -Message ($script:localizedData.LoadingThumbnailFromFile -f $ThumbnailPhoto)
            $thumbnailPhotoAsByteArray = Get-Content -Path $ThumbnailPhoto -Encoding Byte
        }
        else
        {
            $errorMessage = $script:localizedData.ThumbnailPhotoNotAFile
            New-InvalidOperationException -Message $errorMessage
        }
    }
    else
    {
        $thumbnailPhotoAsByteArray = [System.Convert]::FromBase64String($ThumbnailPhoto)
    }

    return $thumbnailPhotoAsByteArray
} # end function Get-ThumbnailByteArray

<#
    .SYNOPSIS
        Internal function to compare two thumbnail photos.
 
    .PARAMETER DesiredThumbnailPhoto
        The desired thumbnail photo. Can be set to either a path to a .jpg-file,
        a Base64-encoded jpeg image, an empty string, or $null.
 
    .PARAMETER CurrentThumbnailPhotoHash
        The current thumbnail photo MD5 hash, or an empty string or $null if there
        are no current thumbnail photo.
 
    .OUTPUTS
        Returns $null if the thumbnail photos are the same, or a hashtable with
        the hashes if the thumbnail photos does not match.
#>

function Compare-ThumbnailPhoto
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [System.String]
        $DesiredThumbnailPhoto,

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [System.String]
        $CurrentThumbnailPhotoHash
    )

    if ([System.String]::IsNullOrEmpty($DesiredThumbnailPhoto))
    {
        $desiredThumbnailPhotoHash = $null
    }
    else
    {
        $desiredThumbnailPhotoHash = Get-MD5HashString -Bytes (Get-ThumbnailByteArray -ThumbnailPhoto $DesiredThumbnailPhoto)
    }

    <#
        Compare thumbnail hashes. Must [System.String]::IsNullOrEmpty() to
        compare empty values correctly.
    #>

    if ($desiredThumbnailPhotoHash -eq $CurrentThumbnailPhotoHash `
            -or (
            [System.String]::IsNullOrEmpty($desiredThumbnailPhotoHash) `
                -and [System.String]::IsNullOrEmpty($CurrentThumbnailPhotoHash)
        )
    )
    {
        $returnValue = $null
    }
    else
    {
        $returnValue = @{
            CurrentThumbnailPhotoHash = $CurrentThumbnailPhotoHash
            DesiredThumbnailPhotoHash = $desiredThumbnailPhotoHash
        }
    }

    return $returnValue
}

Export-ModuleMember -Function *-TargetResource