DSCResources/MSFT_xADUser/MSFT_xADUser.psm1

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

## Import the common AD functions
$adCommonFunctions = Join-Path `
    -Path (Split-Path -Path $PSScriptRoot -Parent) `
    -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1'
Import-Module -Name $adCommonFunctions

# Localized messages
data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData @'
        RoleNotFoundError = Please ensure that the PowerShell module for role '{0}' is installed.
        RetrievingADUserError = Error looking up Active Directory user '{0}' ({0}@{1}).
        PasswordParameterConflictError = Parameter '{0}' cannot be set to '{1}' when the '{2}' parameter is specified.
 
        RetrievingADUser = Retrieving Active Directory user '{0}' ({0}@{1}) ...
        CreatingADDomainConnection = Creating connection to Active Directory domain '{0}' ...
        CheckingADUserPassword = Checking Active Directory user '{0}' password ...
        ADUserIsPresent = Active Directory user '{0}' ({0}@{1}) is present.
        ADUserNotPresent = Active Directory user '{0}' ({0}@{1}) was NOT present.
        ADUserNotDesiredPropertyState = User '{0}' property is NOT in the desired state. Expected '{1}', actual '{2}'.
 
        AddingADUser = Adding Active Directory user '{0}'.
        RemovingADUser = Removing Active Directory user '{0}'.
        UpdatingADUser = Updating Active Directory user '{0}'.
        SettingADUserPassword = Setting Active Directory user password.
        UpdatingADUserProperty = Updating user property '{0}' with/to '{1}'.
        RemovingADUserProperty = Removing user property '{0}' with '{1}'.
        MovingADUser = Moving user from '{0}' to '{1}'.
        RenamingADUser = Renaming user from '{0}' to '{1}'.
'@

}

## 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 = 'PasswordNeverExpires'; UseCmdletParameter = $true; }
    @{ Parameter = 'CannotChangePassword'; UseCmdletParameter = $true; }
)

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        ## Name of the domain where the user account is located (only used if password is managed)
        [Parameter(Mandatory)]
        [System.String] $DomainName,

        # Specifies the Security Account Manager (SAM) account name of the user (ldapDisplayName 'sAMAccountName')
        [Parameter(Mandatory)]
        [System.String] $UserName,

        ## Specifies a new password value for an account
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Password,

        ## Specifies whether the user account is created or deleted
        [ValidateSet('Present', 'Absent')]
        [System.String] $Ensure = 'Present',

        ## Specifies the common nane assigned to the user account (ldapDisplayName 'cn')
        [ValidateNotNull()]
        [System.String] $CommonName = $UserName,

        ## Specifies the UPN assigned to the user account (ldapDisplayName 'userPrincipalName')
        [ValidateNotNull()]
        [System.String] $UserPrincipalName,

        ## Specifies the display name of the object (ldapDisplayName 'displayName')
        [ValidateNotNull()]
        [System.String] $DisplayName,

        ## Specifies the X.500 path of the Organizational Unit (OU) or container where the new object is created
        [ValidateNotNull()]
        [System.String] $Path,

        ## Specifies the user's given name (ldapDisplayName 'givenName')
        [ValidateNotNull()]
        [System.String] $GivenName,

        ## Specifies the initials that represent part of a user's name (ldapDisplayName 'initials')
        [ValidateNotNull()]
        [System.String] $Initials,

        ## Specifies the user's last name or surname (ldapDisplayName 'sn')
        [ValidateNotNull()]
        [System.String] $Surname,

        ## Specifies a description of the object (ldapDisplayName 'description')
        [ValidateNotNull()]
        [System.String] $Description,

        ## Specifies the user's street address (ldapDisplayName 'streetAddress')
        [ValidateNotNull()]
        [System.String] $StreetAddress,

        ## Specifies the user's post office box number (ldapDisplayName 'postOfficeBox')
        [ValidateNotNull()]
        [System.String] $POBox,

        ## Specifies the user's town or city (ldapDisplayName 'l')
        [ValidateNotNull()]
        [System.String] $City,

        ## Specifies the user's or Organizational Unit's state or province (ldapDisplayName 'st')
        [ValidateNotNull()]
        [System.String] $State,

        ## Specifies the user's postal code or zip code (ldapDisplayName 'postalCode')
        [ValidateNotNull()]
        [System.String] $PostalCode,

        ## Specifies the country or region code for the user's language of choice (ldapDisplayName 'c')
        [ValidateNotNull()]
        [System.String] $Country,

        ## Specifies the user's department (ldapDisplayName 'department')
        [ValidateNotNull()]
        [System.String] $Department,

        ## Specifies the user's division (ldapDisplayName 'division')
        [ValidateNotNull()]
        [System.String] $Division,

        ## Specifies the user's company (ldapDisplayName 'company')
        [ValidateNotNull()]
        [System.String] $Company,

        ## Specifies the location of the user's office or place of business (ldapDisplayName 'physicalDeliveryOfficeName')
        [ValidateNotNull()]
        [System.String] $Office,

        ## Specifies the user's title (ldapDisplayName 'title')
        [ValidateNotNull()]
        [System.String] $JobTitle,

        ## Specifies the user's e-mail address (ldapDisplayName 'mail')
        [ValidateNotNull()]
        [System.String] $EmailAddress,

        ## Specifies the user's employee ID (ldapDisplayName 'employeeID')
        [ValidateNotNull()]
        [System.String] $EmployeeID,

        ## Specifies the user's employee number (ldapDisplayName 'employeeNumber')
        [ValidateNotNull()]
        [System.String] $EmployeeNumber,

        ## Specifies a user's home directory path (ldapDisplayName 'homeDirectory')
        [ValidateNotNull()]
        [System.String] $HomeDirectory,

        ## Specifies a drive that is associated with the UNC path defined by the HomeDirectory property (ldapDisplayName 'homeDrive')
        [ValidateNotNull()]
        [System.String] $HomeDrive,

        ## Specifies the URL of the home page of the object (ldapDisplayName 'wWWHomePage')
        [ValidateNotNull()]
        [System.String] $HomePage,

        ## Specifies a path to the user's profile (ldapDisplayName 'profilePath')
        [ValidateNotNull()]
        [System.String] $ProfilePath,

        ## Specifies a path to the user's log on script (ldapDisplayName 'scriptPath')
        [ValidateNotNull()]
        [System.String] $LogonScript,

        ## Specifies the notes attached to the user's accoutn (ldapDisplayName 'info')
        [ValidateNotNull()]
        [System.String] $Notes,

        ## Specifies the user's office telephone number (ldapDisplayName 'telephoneNumber')
        [ValidateNotNull()]
        [System.String] $OfficePhone,

        ## Specifies the user's mobile phone number (ldapDisplayName 'mobile')
        [ValidateNotNull()]
        [System.String] $MobilePhone,

        ## Specifies the user's fax phone number (ldapDisplayName 'facsimileTelephoneNumber')
        [ValidateNotNull()]
        [System.String] $Fax,

        ## Specifies the user's home telephone number (ldapDisplayName 'homePhone')
        [ValidateNotNull()]
        [System.String] $HomePhone,

         ## Specifies the user's pager number (ldapDisplayName 'pager')
        [ValidateNotNull()]
        [System.String] $Pager,

        ## Specifies the user's IP telephony phone number (ldapDisplayName 'ipPhone')
        [ValidateNotNull()]
        [System.String] $IPPhone,

        ## Specifies the user's manager specified as a Distinguished Name (ldapDisplayName 'manager')
        [ValidateNotNull()]
        [System.String] $Manager,

        ## Specifies if the account is enabled (default True)
        [ValidateNotNull()]
        [System.Boolean] $Enabled = $true,

        ## Specifies whether the account password can be changed
        [ValidateNotNull()]
        [System.Boolean] $CannotChangePassword,

        ## Specifies whether the password of an account can expire
        [ValidateNotNull()]
        [System.Boolean] $PasswordNeverExpires,

        ## Specifies the Active Directory Domain Services instance to use to perform the task.
        [ValidateNotNull()]
        [System.String] $DomainController,

        ## Specifies the user account credentials to use to perform this task. Ideally this should just be called 'Credential' but is here for backwards compatibility
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $DomainAdministratorCredential,

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

    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 ($LocalizedData.RetrievingADUser -f $UserName, $DomainName);
        $adUser = Get-ADUser @adCommonParameters -Properties $adProperties;
        Write-Verbose -Message ($LocalizedData.ADUserIsPresent -f $UserName, $DomainName);
        $Ensure = 'Present';
    }
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
    {
        Write-Verbose -Message ($LocalizedData.ADUserNotPresent -f $UserName, $DomainName);
        $Ensure = 'Absent';
    }
    catch
    {
        Write-Error -Message ($LocalizedData.RetrievingADUserError -f $UserName, $DomainName);
        throw $_;
    }

    $targetResource = @{
        DomainName        = $DomainName;
        Password          = $Password;
        UserName          = $UserName;
        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)
    {
        if ($property.Parameter -eq 'Path') {
            ## The path returned is not the parent container
            if (-not [System.String]::IsNullOrEmpty($adUser.DistinguishedName))
            {
                $targetResource['Path'] = Get-ADObjectParentDN -DN $adUser.DistinguishedName;
            }
        }
        elseif ($property.ADProperty)
        {
            ## The AD property name is different to the function parameter to use this
            $targetResource[$property.Parameter] = $adUser.($property.ADProperty);
        }
        else
        {
            ## The AD property name matches the function parameter
            $targetResource[$property.Parameter] = $adUser.($property.Parameter);
        }
    }
    return $targetResource;

} #end function Get-TargetResource

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        ## Name of the domain where the user account is located (only used if password is managed)
        [Parameter(Mandatory)]
        [System.String] $DomainName,

        # Specifies the Security Account Manager (SAM) account name of the user (ldapDisplayName 'sAMAccountName')
        [Parameter(Mandatory)]
        [System.String] $UserName,

        ## Specifies a new password value for an account
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Password,

        ## Specifies whether the user account is created or deleted
        [ValidateSet('Present', 'Absent')]
        [System.String] $Ensure = 'Present',

        ## Specifies the common nane assigned to the user account (ldapDisplayName 'cn')
        [ValidateNotNull()]
        [System.String] $CommonName = $UserName,

        ## Specifies the UPN assigned to the user account (ldapDisplayName 'userPrincipalName')
        [ValidateNotNull()]
        [System.String] $UserPrincipalName,

        ## Specifies the display name of the object (ldapDisplayName 'displayName')
        [ValidateNotNull()]
        [System.String] $DisplayName,

        ## Specifies the X.500 path of the Organizational Unit (OU) or container where the new object is created
        [ValidateNotNull()]
        [System.String] $Path,

        ## Specifies the user's given name (ldapDisplayName 'givenName')
        [ValidateNotNull()]
        [System.String] $GivenName,

        ## Specifies the initials that represent part of a user's name (ldapDisplayName 'initials')
        [ValidateNotNull()]
        [System.String] $Initials,

        ## Specifies the user's last name or surname (ldapDisplayName 'sn')
        [ValidateNotNull()]
        [System.String] $Surname,

        ## Specifies a description of the object (ldapDisplayName 'description')
        [ValidateNotNull()]
        [System.String] $Description,

        ## Specifies the user's street address (ldapDisplayName 'streetAddress')
        [ValidateNotNull()]
        [System.String] $StreetAddress,

        ## Specifies the user's post office box number (ldapDisplayName 'postOfficeBox')
        [ValidateNotNull()]
        [System.String] $POBox,

        ## Specifies the user's town or city (ldapDisplayName 'l')
        [ValidateNotNull()]
        [System.String] $City,

        ## Specifies the user's or Organizational Unit's state or province (ldapDisplayName 'st')
        [ValidateNotNull()]
        [System.String] $State,

        ## Specifies the user's postal code or zip code (ldapDisplayName 'postalCode')
        [ValidateNotNull()]
        [System.String] $PostalCode,

        ## Specifies the country or region code for the user's language of choice (ldapDisplayName 'c')
        [ValidateNotNull()]
        [System.String] $Country,

        ## Specifies the user's department (ldapDisplayName 'department')
        [ValidateNotNull()]
        [System.String] $Department,

        ## Specifies the user's division (ldapDisplayName 'division')
        [ValidateNotNull()]
        [System.String] $Division,

        ## Specifies the user's company (ldapDisplayName 'company')
        [ValidateNotNull()]
        [System.String] $Company,

        ## Specifies the location of the user's office or place of business (ldapDisplayName 'physicalDeliveryOfficeName')
        [ValidateNotNull()]
        [System.String] $Office,

        ## Specifies the user's title (ldapDisplayName 'title')
        [ValidateNotNull()]
        [System.String] $JobTitle,

        ## Specifies the user's e-mail address (ldapDisplayName 'mail')
        [ValidateNotNull()]
        [System.String] $EmailAddress,

        ## Specifies the user's employee ID (ldapDisplayName 'employeeID')
        [ValidateNotNull()]
        [System.String] $EmployeeID,

        ## Specifies the user's employee number (ldapDisplayName 'employeeNumber')
        [ValidateNotNull()]
        [System.String] $EmployeeNumber,

        ## Specifies a user's home directory path (ldapDisplayName 'homeDirectory')
        [ValidateNotNull()]
        [System.String] $HomeDirectory,

        ## Specifies a drive that is associated with the UNC path defined by the HomeDirectory property (ldapDisplayName 'homeDrive')
        [ValidateNotNull()]
        [System.String] $HomeDrive,

        ## Specifies the URL of the home page of the object (ldapDisplayName 'wWWHomePage')
        [ValidateNotNull()]
        [System.String] $HomePage,

        ## Specifies a path to the user's profile (ldapDisplayName 'profilePath')
        [ValidateNotNull()]
        [System.String] $ProfilePath,

        ## Specifies a path to the user's log on script (ldapDisplayName 'scriptPath')
        [ValidateNotNull()]
        [System.String] $LogonScript,

        ## Specifies the notes attached to the user's accoutn (ldapDisplayName 'info')
        [ValidateNotNull()]
        [System.String] $Notes,

        ## Specifies the user's office telephone number (ldapDisplayName 'telephoneNumber')
        [ValidateNotNull()]
        [System.String] $OfficePhone,

        ## Specifies the user's mobile phone number (ldapDisplayName 'mobile')
        [ValidateNotNull()]
        [System.String] $MobilePhone,

        ## Specifies the user's fax phone number (ldapDisplayName 'facsimileTelephoneNumber')
        [ValidateNotNull()]
        [System.String] $Fax,

        ## Specifies the user's home telephone number (ldapDisplayName 'homePhone')
        [ValidateNotNull()]
        [System.String] $HomePhone,

         ## Specifies the user's pager number (ldapDisplayName 'pager')
        [ValidateNotNull()]
        [System.String] $Pager,

        ## Specifies the user's IP telephony phone number (ldapDisplayName 'ipPhone')
        [ValidateNotNull()]
        [System.String] $IPPhone,

        ## Specifies the user's manager specified as a Distinguished Name (ldapDisplayName 'manager')
        [ValidateNotNull()]
        [System.String] $Manager,

        ## Specifies if the account is enabled (default True)
        [ValidateNotNull()]
        [System.Boolean] $Enabled = $true,

        ## Specifies whether the account password can be changed
        [ValidateNotNull()]
        [System.Boolean] $CannotChangePassword,

        ## Specifies whether the password of an account can expire
        [ValidateNotNull()]
        [System.Boolean] $PasswordNeverExpires,

        ## Specifies the Active Directory Domain Services instance to use to perform the task.
        [ValidateNotNull()]
        [System.String] $DomainController,

        ## Specifies the user account credentials to use to perform this task. Ideally this should just be called 'Credential' but is here for backwards compatibility
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $DomainAdministratorCredential,

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

    Assert-Parameters @PSBoundParameters;
    $targetResource = Get-TargetResource @PSBoundParameters;
    $isCompliant = $true;

    if ($Ensure -eq 'Absent')
    {
        if ($targetResource.Ensure -eq 'Present')
        {
            Write-Verbose -Message ($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')
            {
                $testPasswordParams = @{
                    Username = $UserName;
                    Password = $Password;
                    DomainName = $DomainName;
                    PasswordAuthentication = $PasswordAuthentication;
                }
                if ($DomainAdministratorCredential)
                {
                    $testPasswordParams['DomainAdministratorCredential'] = $DomainAdministratorCredential;
                }
                if (-not (Test-Password @testPasswordParams))
                {
                    Write-Verbose -Message ($LocalizedData.ADUserNotDesiredPropertyState -f 'Password', '<Password>', '<Password>');
                    $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
                }
                elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter)
                {
                    Write-Verbose -Message ($LocalizedData.ADUserNotDesiredPropertyState -f $parameter, $PSBoundParameters.$parameter, $targetResource.$parameter);
                    $isCompliant = $false;
                }
            }
        } #end foreach PSBoundParameter
    }

    return $isCompliant;

} #end function Test-TargetResource

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        ## Name of the domain where the user account is located (only used if password is managed)
        [Parameter(Mandatory)]
        [System.String] $DomainName,

        # Specifies the Security Account Manager (SAM) account name of the user (ldapDisplayName 'sAMAccountName')
        [Parameter(Mandatory)]
        [System.String] $UserName,

        ## Specifies a new password value for an account
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Password,

        ## Specifies whether the user account is created or deleted
        [ValidateSet('Present', 'Absent')]
        [System.String] $Ensure = 'Present',

        ## Specifies the common nane assigned to the user account (ldapDisplayName 'cn')
        [ValidateNotNull()]
        [System.String] $CommonName = $UserName,

        ## Specifies the UPN assigned to the user account (ldapDisplayName 'userPrincipalName')
        [ValidateNotNull()]
        [System.String] $UserPrincipalName,

        ## Specifies the display name of the object (ldapDisplayName 'displayName')
        [ValidateNotNull()]
        [System.String] $DisplayName,

        ## Specifies the X.500 path of the Organizational Unit (OU) or container where the new object is created
        [ValidateNotNull()]
        [System.String] $Path,

        ## Specifies the user's given name (ldapDisplayName 'givenName')
        [ValidateNotNull()]
        [System.String] $GivenName,

        ## Specifies the initials that represent part of a user's name (ldapDisplayName 'initials')
        [ValidateNotNull()]
        [System.String] $Initials,

        ## Specifies the user's last name or surname (ldapDisplayName 'sn')
        [ValidateNotNull()]
        [System.String] $Surname,

        ## Specifies a description of the object (ldapDisplayName 'description')
        [ValidateNotNull()]
        [System.String] $Description,

        ## Specifies the user's street address (ldapDisplayName 'streetAddress')
        [ValidateNotNull()]
        [System.String] $StreetAddress,

        ## Specifies the user's post office box number (ldapDisplayName 'postOfficeBox')
        [ValidateNotNull()]
        [System.String] $POBox,

        ## Specifies the user's town or city (ldapDisplayName 'l')
        [ValidateNotNull()]
        [System.String] $City,

        ## Specifies the user's or Organizational Unit's state or province (ldapDisplayName 'st')
        [ValidateNotNull()]
        [System.String] $State,

        ## Specifies the user's postal code or zip code (ldapDisplayName 'postalCode')
        [ValidateNotNull()]
        [System.String] $PostalCode,

        ## Specifies the country or region code for the user's language of choice (ldapDisplayName 'c')
        [ValidateNotNull()]
        [System.String] $Country,

        ## Specifies the user's department (ldapDisplayName 'department')
        [ValidateNotNull()]
        [System.String] $Department,

        ## Specifies the user's division (ldapDisplayName 'division')
        [ValidateNotNull()]
        [System.String] $Division,

        ## Specifies the user's company (ldapDisplayName 'company')
        [ValidateNotNull()]
        [System.String] $Company,

        ## Specifies the location of the user's office or place of business (ldapDisplayName 'physicalDeliveryOfficeName')
        [ValidateNotNull()]
        [System.String] $Office,

        ## Specifies the user's title (ldapDisplayName 'title')
        [ValidateNotNull()]
        [System.String] $JobTitle,

        ## Specifies the user's e-mail address (ldapDisplayName 'mail')
        [ValidateNotNull()]
        [System.String] $EmailAddress,

        ## Specifies the user's employee ID (ldapDisplayName 'employeeID')
        [ValidateNotNull()]
        [System.String] $EmployeeID,

        ## Specifies the user's employee number (ldapDisplayName 'employeeNumber')
        [ValidateNotNull()]
        [System.String] $EmployeeNumber,

        ## Specifies a user's home directory path (ldapDisplayName 'homeDirectory')
        [ValidateNotNull()]
        [System.String] $HomeDirectory,

        ## Specifies a drive that is associated with the UNC path defined by the HomeDirectory property (ldapDisplayName 'homeDrive')
        [ValidateNotNull()]
        [System.String] $HomeDrive,

        ## Specifies the URL of the home page of the object (ldapDisplayName 'wWWHomePage')
        [ValidateNotNull()]
        [System.String] $HomePage,

        ## Specifies a path to the user's profile (ldapDisplayName 'profilePath')
        [ValidateNotNull()]
        [System.String] $ProfilePath,

        ## Specifies a path to the user's log on script (ldapDisplayName 'scriptPath')
        [ValidateNotNull()]
        [System.String] $LogonScript,

        ## Specifies the notes attached to the user's accoutn (ldapDisplayName 'info')
        [ValidateNotNull()]
        [System.String] $Notes,

        ## Specifies the user's office telephone number (ldapDisplayName 'telephoneNumber')
        [ValidateNotNull()]
        [System.String] $OfficePhone,

        ## Specifies the user's mobile phone number (ldapDisplayName 'mobile')
        [ValidateNotNull()]
        [System.String] $MobilePhone,

        ## Specifies the user's fax phone number (ldapDisplayName 'facsimileTelephoneNumber')
        [ValidateNotNull()]
        [System.String] $Fax,

        ## Specifies the user's home telephone number (ldapDisplayName 'homePhone')
        [ValidateNotNull()]
        [System.String] $HomePhone,

         ## Specifies the user's pager number (ldapDisplayName 'pager')
        [ValidateNotNull()]
        [System.String] $Pager,

        ## Specifies the user's IP telephony phone number (ldapDisplayName 'ipPhone')
        [ValidateNotNull()]
        [System.String] $IPPhone,

        ## Specifies the user's manager specified as a Distinguished Name (ldapDisplayName 'manager')
        [ValidateNotNull()]
        [System.String] $Manager,

        ## Specifies if the account is enabled (default True)
        [ValidateNotNull()]
        [System.Boolean] $Enabled = $true,

        ## Specifies whether the account password can be changed
        [ValidateNotNull()]
        [System.Boolean] $CannotChangePassword,

        ## Specifies whether the password of an account can expire
        [ValidateNotNull()]
        [System.Boolean] $PasswordNeverExpires,

        ## Specifies the Active Directory Domain Services instance to use to perform the task.
        [ValidateNotNull()]
        [System.String] $DomainController,

        ## Specifies the user account credentials to use to perform this task. Ideally this should just be called 'Credential' but is here for backwards compatibility
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $DomainAdministratorCredential,

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

    Assert-Parameters @PSBoundParameters;
    $targetResource = Get-TargetResource @PSBoundParameters;

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

    if ($Ensure -eq 'Present')
    {
        if ($targetResource.Ensure -eq 'Absent') {
            ## User does not exist and needs creating
            $newADUserParams = Get-ADCommonParameters @PSBoundParameters -UseNameParameter;
            if ($PSBoundParameters.ContainsKey('Path'))
            {
                $newADUserParams['Path'] = $Path;
            }
            Write-Verbose -Message ($LocalizedData.AddingADUser -f $UserName);
            New-ADUser @newADUserParams -SamAccountName $UserName;
            ## Now retrieve the newly created user
            $targetResource = Get-TargetResource @PSBoundParameters;
        }

        $setADUserParams = Get-ADCommonParameters @PSBoundParameters;
        $replaceUserProperties = @{};
        $removeUserProperties = @{};
        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))
            {
                if ($parameter -eq 'Path' -and ($PSBoundParameters.Path -ne $targetResource.Path))
                {
                    ## Cannot move users by updating the DistinguishedName property
                    $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
                    ## Using the SamAccountName for identity with Move-ADObject does not work, use the DN instead
                    $adCommonParameters['Identity'] = $targetResource.DistinguishedName;
                    Write-Verbose -Message ($LocalizedData.MovingADUser -f $targetResource.Path, $PSBoundParameters.Path);
                    Move-ADObject @adCommonParameters -TargetPath $PSBoundParameters.Path;
                }
                elseif ($parameter -eq 'CommonName' -and ($PSBoundParameters.CommonName -ne $targetResource.CommonName))
                {
                    ## Cannot rename users by updating the CN property directly
                    $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
                    ## Using the SamAccountName for identity with Rename-ADObject does not work, use the DN instead
                    $adCommonParameters['Identity'] = $targetResource.DistinguishedName;
                    Write-Verbose -Message ($LocalizedData.RenamingADUser -f $targetResource.CommonName, $PSBoundParameters.CommonName);
                    Rename-ADObject @adCommonParameters -NewName $PSBoundParameters.CommonName;
                }
                elseif ($parameter -eq 'Password')
                {
                    $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
                    Write-Verbose -Message ($LocalizedData.SettingADUserPassword -f $UserName);
                    Set-ADAccountPassword @adCommonParameters -Reset -NewPassword $Password.Password;
                }
                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 ($LocalizedData.UpdatingADUserProperty -f $parameter, $PSBoundParameters.$parameter);
                }
                elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter)
                {
                    ## Find the associated AD property
                    $adProperty = $adPropertyMap | Where-Object { $_.Parameter -eq $parameter };

                    if ([System.String]::IsNullOrEmpty($adProperty))
                    {
                        ## We can't do anything is an empty AD property!
                    }
                    elseif ([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter))
                    {
                        ## We are removing properties
                        ## Only remove if the existing value in not null or empty
                        if (-not ([System.String]::IsNullOrEmpty($targetResource.$parameter)))
                        {
                            Write-Verbose -Message ($LocalizedData.RemovingADUserProperty -f $parameter, $PSBoundParameters.$parameter);
                            if ($adProperty.UseCmdletParameter -eq $true)
                            {
                                ## We need to pass the parameter explicitly to Set-ADUser, not via -Remove
                                $setADUserParams[$adProperty.Parameter] = $PSBoundParameters.$parameter;
                            }
                            elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty))
                            {
                                $removeUserProperties[$adProperty.Parameter] = $targetResource.$parameter;
                            }
                            else
                            {
                                $removeUserProperties[$adProperty.ADProperty] = $targetResource.$parameter;
                            }
                        }
                    } #end if remove existing value
                    else
                    {
                        ## We are replacing the existing value
                        Write-Verbose -Message ($LocalizedData.UpdatingADUserProperty -f $parameter, $PSBoundParameters.$parameter);
                        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 -Remove and/or -Replace if we have something to set/change
        if ($replaceUserProperties.Count -gt 0)
        {
            $setADUserParams['Replace'] = $replaceUserProperties;
        }
        if ($removeUserProperties.Count -gt 0)
        {
            $setADUserParams['Remove'] = $removeUserProperties;
        }

        Write-Verbose -Message ($LocalizedData.UpdatingADUser -f $UserName);
        [ref] $null = Set-ADUser @setADUserParams -Enabled $Enabled;
    }
    elseif (($Ensure -eq 'Absent') -and ($targetResource.Ensure -eq 'Present'))
    {
        ## User exists and needs removing
        Write-Verbose ($LocalizedData.RemovingADUser -f $UserName);
        $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
        [ref] $null = Remove-ADUser @adCommonParameters -Confirm:$false;
    }

} #end function Set-TargetResource

# Internal function to validate unsupported options/configurations
function Assert-Parameters
{
    [CmdletBinding()]
    param
    (
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential] $Password,

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

        [Parameter(ValueFromRemainingArguments)]
        $IgnoredArguments
    )

    ## We cannot test/set passwords on disabled AD accounts
    if (($PSBoundParameters.ContainsKey('Password')) -and ($Enabled -eq $false))
    {
        $throwInvalidArgumentErrorParams = @{
            ErrorId = 'xADUser_DisabledAccountPasswordConflict';
            ErrorMessage = $LocalizedData.PasswordParameterConflictError -f 'Enabled', $false, 'Password';
        }
        ThrowInvalidArgumentError @throwInvalidArgumentErrorParams;
    }

} #end function Assert-Parameters

# Internal function to test the validity of a user's password.
function Test-Password
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [System.String] $DomainName,

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

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

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

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

    Write-Verbose -Message ($LocalizedData.CreatingADDomainConnection -f $DomainName);
    Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement';

    if ($DomainAdministratorCredential)
    {
        $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext(
                                [System.DirectoryServices.AccountManagement.ContextType]::Domain,
                                $DomainName,
                                $DomainAdministratorCredential.UserName,
                                $DomainAdministratorCredential.GetNetworkCredential().Password
                            );
    }
    else
    {
        $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext(
                                [System.DirectoryServices.AccountManagement.ContextType]::Domain,
                                $DomainName,
                                $null,
                                $null
                            );
    }
    Write-Verbose -Message ($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

Export-ModuleMember -Function *-TargetResource