DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "")] # To be removed when username/password changed to a credential
param ()

# A global variable that contains localized messages.
data LocalizedData
{
# culture="en-US"
ConvertFrom-StringData @'
UserWithName=User: {0}
RemoveOperation=Remove
AddOperation=Add
SetOperation=Set
ConfigurationStarted=Configuration of user {0} started.
ConfigurationCompleted=Configuration of user {0} completed successfully.
UserCreated=User {0} created successfully.
UserUpdated=User {0} properties updated successfully.
UserRemoved=User {0} removed successfully.
NoConfigurationRequired=User {0} exists on this node with the desired properties. No action required.
NoConfigurationRequiredUserDoesNotExist=User {0} does not exist on this node. No action required.
InvalidUserName=The name {0} cannot be used. Names may not consist entirely of periods and/or spaces, or contain these characters: {1}
UserExists=A user with the name {0} exists.
UserDoesNotExist=A user with the name {0} does not exist.
PropertyMismatch=The value of the {0} property is expected to be {1} but it is {2}.
PasswordPropertyMismatch=The value of the {0} property does not match.
AllUserPropertisMatch=All {0} {1} properties match.
ConnectionError = There could be a possible connection error while trying to use the System.DirectoryServices API's.
MultipleMatches = There could be a possible multiple matches exception while trying to use the System.DirectoryServices API's.
'@

}

# Commented-out until more languages are supported
# Import-LocalizedData LocalizedData -FileName MSFT_xUserResource.strings.psd1

Import-Module "$PSScriptRoot\..\CommonResourceHelper.psm1"

if (-not (Test-IsNanoServer))
{
    Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement'
}

<#
    .SYNOPSIS
    The Get-TargetResource cmdlet.
#>

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

    if (Test-IsNanoServer)
    {
        Get-TargetResourceOnNanoServer @PSBoundParameters
    }
    else
    {
        Get-TargetResourceOnFullSKU @PSBoundParameters
    }
}

<#
    .SYNOPSIS
    The Set-TargetResource cmdlet.
#>

function Set-TargetResource
{
    [CmdletBInding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName,

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

        [System.String]
        $FullName,

        [System.String]
        $Description,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Password,

        [System.Boolean]
        $Disabled,

        [System.Boolean]
        $PasswordNeverExpires,

        [System.Boolean]
        $PasswordChangeRequired,

        [System.Boolean]
        $PasswordChangeNotAllowed
    )

    if (Test-IsNanoServer)
    {
        Set-TargetResourceOnNanoServer @PSBoundParameters
    }
    else
    {
        Set-TargetResourceOnFullSKU @PSBoundParameters
    }
}

<#
    .SYNOPSIS
    The Test-TargetResource cmdlet is used to validate if the resource is in a state as expected in the instance document.
#>

function Test-TargetResource
{
    [OutputType([Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName,

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

        [System.String]
        $FullName,

        [System.String]
        $Description,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Password,

        [System.Boolean]
        $Disabled,

        [System.Boolean]
        $PasswordNeverExpires,

        [System.Boolean]
        $PasswordChangeRequired,

        [System.Boolean]
        $PasswordChangeNotAllowed
    )

    if (Test-IsNanoServer)
    {
        Test-TargetResourceOnNanoServer @PSBoundParameters
    }
    else
    {
        Test-TargetResourceOnFullSKU @PSBoundParameters
    }
}


<#
    .SYNOPSIS
    The Get-TargetResource cmdlet on a full server.
#>

function Get-TargetResourceOnFullSKU
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName
    )

    Set-StrictMode -Version Latest

    ValidateUserName -UserName $UserName

    # Try to find a user by a name.
    $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine)

    try
    {
        $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName);
        if($user -ne $null)
        {
            # The user is found. Return all user properties and Ensure="Present".
            $returnValue = @{
                                UserName = $user.Name;
                                Ensure = "Present";
                                FullName = $user.DisplayName;
                                Description = $user.Description;
                                Disabled = -not $user.Enabled;
                                PasswordNeverExpires = $user.PasswordNeverExpires;
                                PasswordChangeRequired = $null;
                                PasswordChangeNotAllowed = $user.UserCannotChangePassword;
                            }

            return $returnValue;
        }

        # The user is not found. Return Ensure=Absent.
        return @{
                    UserName = $UserName;
                    Ensure = "Absent";
                }
    }
    catch
    {
         ThrowExceptionDueToDirectoryServicesError -ErrorId "MultipleMatches" -ErrorMessage ($LocalizedData.MultipleMatches + $_)
    }
    finally
    {
        if($user -ne $null)
        {
            $user.Dispose();
        }

        $principalContext.Dispose();
    }
}

<#
    .SYNOPSIS
    The Set-TargetResource cmdlet on a full server.
#>

function Set-TargetResourceOnFullSKU
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName,

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

        [System.String]
        $FullName,

        [System.String]
        $Description,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Password,

        [System.Boolean]
        $Disabled,

        [System.Boolean]
        $PasswordNeverExpires,

        [System.Boolean]
        $PasswordChangeRequired,

        [System.Boolean]
        $PasswordChangeNotAllowed
    )

    Set-StrictMode -Version Latest

    Write-Verbose -Message ($LocalizedData.ConfigurationStarted -f $UserName)

    ValidateUserName -UserName $UserName


    # Try to find a user by a name.
    $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine)

    try
    {
        $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName);
        if($Ensure -eq "Present")
        {
            # Ensure is set to "Present".

            $whatIfShouldProcess = $true;
            $userExists = $false;
            $saveChanges = $false;

            if($user -eq $null)
            {
                # A user does not exist. Check WhatIf for adding a user.
                $whatIfShouldProcess = $pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.AddOperation);
            }
            else
            {
                # A user exists.
                $userExists = $true;

                # Check WhatIf for setting a user.
                $whatIfShouldProcess = $pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.SetOperation);
            }

            if($whatIfShouldProcess)
            {
                if(-not $userExists)
                {
                    # The user with the provided name does not exist. Add a new user.
                    $user = New-Object System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $principalContext
                    $user.Name = $UserName;
                    $saveChanges = $true;
                }

                # Set user properties.
                if($PSBoundParameters.ContainsKey('FullName') -and (-not $userExists -or $FullName -ne $user.DisplayName))
                {
                    $user.DisplayName = $FullName;
                    $saveChanges = $true;
                }
                else
                {
                    if(-not $userExists)
                    {
                        # For a newly created user, set the DisplayName property to an empty string. By default DisplayName is set to user's name.
                        $user.DisplayName = [String]::Empty;
                    }
                }

                if($PSBoundParameters.ContainsKey('Description') -and (-not $userExists -or $Description -ne $user.Description))
                {
                    $user.Description = $Description;
                    $saveChanges = $true;
                }

                # Password. Set the password regardless of the state of the user.
                if($PSBoundParameters.ContainsKey('Password'))
                {
                    $user.SetPassword($Password.GetNetworkCredential().Password);
                    $saveChanges = $true;
                }

                if($PSBoundParameters.ContainsKey('Disabled') -and (-not $userExists -or $Disabled -eq $user.Enabled))
                {
                    $user.Enabled = -not $Disabled;
                    $saveChanges = $true;
                }

                if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and (-not $userExists -or $PasswordNeverExpires -ne $user.PasswordNeverExpires))
                {
                    $user.PasswordNeverExpires = $PasswordNeverExpires;
                    $saveChanges = $true;
                }

                if($PSBoundParameters.ContainsKey('PasswordChangeRequired'))
                {
                    if($PasswordChangeRequired)
                    {
                        # Expire the password. This will force the user to change the password at the next logon.
                        $user.ExpirePasswordNow();
                        $saveChanges = $true;
                    }
                }

                if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and (-not $userExists -or $PasswordChangeNotAllowed -ne $user.UserCannotChangePassword))
                {
                    $user.UserCannotChangePassword = $PasswordChangeNotAllowed;
                    $saveChanges = $true;

                }

                if($saveChanges)
                {
                    $user.Save();

                    # Send an operation success verbose message.
                    if($userExists)
                    {
                        Write-Verbose -Message ($LocalizedData.UserUpdated -f $UserName)
                    }
                    else
                    {
                        Write-Verbose -Message ($LocalizedData.UserCreated -f $UserName)
                    }
                }
                else
                {
                    Write-Verbose -Message ($LocalizedData.NoConfigurationRequired -f $UserName)
                }
            }
        }
        else
        {
            # Ensure is set to "Absent".
            if($user -ne $null)
            {
                # The user exists.
                if($pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.RemoveOperation))
                {
                    # Remove the user by the provided name.
                    $user.Delete();
                }

                Write-Verbose -Message ($LocalizedData.UserRemoved -f $UserName)
            }
            else
            {
                Write-Verbose -Message ($LocalizedData.NoConfigurationRequiredUserDoesNotExist -f $UserName)
            }
        }
    }
    catch
    {
         ThrowExceptionDueToDirectoryServicesError -ErrorId "MultipleMatches" -ErrorMessage ($LocalizedData.MultipleMatches + $_)
    }
    finally
    {
        if($user -ne $null)
        {
            $user.Dispose();
        }

        $principalContext.Dispose();
    }

    Write-Verbose -Message ($LocalizedData.ConfigurationCompleted -f $UserName)
}

<#
    .SYNOPSIS
    The Test-TargetResource cmdlet on a full server.
#>

function Test-TargetResourceOnFullSKU
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName,

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

        [System.String]
        $FullName,

        [System.String]
        $Description,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Password,

        [System.Boolean]
        $Disabled,

        [System.Boolean]
        $PasswordNeverExpires,

        [System.Boolean]
        $PasswordChangeRequired,

        [System.Boolean]
        $PasswordChangeNotAllowed
    )

    Set-StrictMode -Version Latest

    ValidateUserName -UserName $UserName

    # Try to find a user by a name.
    $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine)

    try
    {
        $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName);
        if($user -eq $null)
        {
            # A user with the provided name does not exist.
            Write-Log -Message ($LocalizedData.UserDoesNotExist -f $UserName)

            if($Ensure -eq "Absent")
            {
                return $true;
            }
            else
            {
                return $false;
            }
        }

        # A user with the provided name exists.
        Write-Log -Message ($LocalizedData.UserExists -f $UserName)

        # Validate separate properties.
        if($Ensure -eq "Absent")
        {
            Write-Log -Message ($LocalizedData.PropertyMismatch -f "Ensure", "Absent", "Present")
            return $false; # The Ensure property does not match. Return $false;
        }

        if($PSBoundParameters.ContainsKey('FullName') -and $FullName -ne $user.DisplayName)
        {
            Write-Log -Message ($LocalizedData.PropertyMismatch -f "FullName", $FullName, $user.DisplayName)
            return $false; # The FullName property does not match. Return $false;
        }

        if($PSBoundParameters.ContainsKey('Description') -and $Description -ne $user.Description)
        {
            Write-Log -Message ($LocalizedData.PropertyMismatch -f "Description", $Description, $user.Description)
            return $false; # The Description property does not match. Return $false;
        }

        # Password
        if($PSBoundParameters.ContainsKey('Password'))
        {
            if(-not $principalContext.ValidateCredentials($UserName, $Password.GetNetworkCredential().Password))
            {
                Write-Log -Message ($LocalizedData.PasswordPropertyMismatch -f "Password")
                return $false; # The Password property does not match. Return $false;
            }
        }

        if($PSBoundParameters.ContainsKey('Disabled') -and $Disabled -eq $user.Enabled)
        {
            Write-Log -Message ($LocalizedData.PropertyMismatch -f "Disabled", $Disabled, $user.Enabled)
            return $false; # The Disabled property does not match. Return $false;
        }

        if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PasswordNeverExpires -ne $user.PasswordNeverExpires)
        {
            Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordNeverExpires", $PasswordNeverExpires, $user.PasswordNeverExpires)
            return $false; # The PasswordNeverExpires property does not match. Return $false;
        }

        if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and $PasswordChangeNotAllowed -ne $user.UserCannotChangePassword)
        {
            Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordChangeNotAllowed", $PasswordChangeNotAllowed, $user.UserCannotChangePassword)
            return $false; # The PasswordChangeNotAllowed property does not match. Return $false;
        }
    }
    catch
    {
         ThrowExceptionDueToDirectoryServicesError -ErrorId "ConnectionError" -ErrorMessage ($LocalizedData.ConnectionError + $_)
    }

    finally
    {
        if($user -ne $null)
        {
            $user.Dispose();
        }

        $principalContext.Dispose();

    }

    # All properties match. Return $true.
    Write-Log -Message ($LocalizedData.AllUserPropertisMatch -f "User", $UserName)
    return $true;
}


<#
.Synopsys
The Get-TargetResource cmdlet.
#>

function Get-TargetResourceOnNanoServer
{
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName
    )

    Set-StrictMode -Version Latest

    ValidateUserName -UserName $UserName

    # Try to find a user by a name.
    try
    {
        [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop
    }
    catch [System.Exception]
    {
        if ($_.CategoryInfo.ToString().Contains('UserNotFoundException'))
        {
            # The user is not found. Return Ensure=Absent.
            return @{
                        UserName = $UserName;
                        Ensure = "Absent";
                    }
        }
        Throw-TerminatingError -ErrorRecord $_
    }

    # The user is found. Return all user properties and Ensure="Present".
    $returnValue = @{
                        UserName = $user.Name;
                        Ensure = "Present";
                        FullName = $user.FullName;
                        Description = $user.Description;
                        Disabled = -not $user.Enabled;
                        PasswordChangeRequired = $null;
                        PasswordChangeNotAllowed = -not $user.UserMayChangePassword;
                    }

    if ($user.PasswordExpires)
    {
        $returnValue.Add('PasswordNeverExpires', $false)
    }
    else
    {
        $returnValue.Add('PasswordNeverExpires', $true)
    }

    return $returnValue;
}

<#
    .SYNOPSIS
    The Set-TargetResource cmdlet on a Nano server.
#>

function Set-TargetResourceOnNanoServer
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName,

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

        [System.String]
        $FullName,

        [System.String]
        $Description,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Password,

        [System.Boolean]
        $Disabled,

        [System.Boolean]
        $PasswordNeverExpires,

        [System.Boolean]
        $PasswordChangeRequired,

        [System.Boolean]
        $PasswordChangeNotAllowed
    )

    Set-StrictMode -Version Latest

    Write-Verbose -Message ($LocalizedData.ConfigurationStarted -f $UserName)

    ValidateUserName -UserName $UserName

    ## Try to find a user by a name.
    [bool] $userExists = $false
    try
    {
        [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop
        $userExists = $true;
    }
    catch [System.Exception]
    {
        if ($_.CategoryInfo.ToString().Contains('UserNotFoundException'))
        {
            # The user is not found.
            Write-Log -Message ($LocalizedData.UserDoesNotExist -f $UserName)
        }
        else
        {
            Throw-TerminatingError -ErrorRecord $_
        }
    }

    if($Ensure -eq "Present")
    {
        # Ensure is set to "Present".

        if(-not $userExists)
        {
            # The user with the provided name does not exist. Add a new user.
            New-LocalUser -Name $UserName -NoPassword
            Write-Verbose -Message ($LocalizedData.UserCreated -f $UserName)
        }

        # Set user properties.
        if($PSBoundParameters.ContainsKey('FullName'))
        {
            if (-not $userExists -or $FullName -ne $user.FullName)
            {
                if ($FullName -eq $null)
                {
                    Set-LocalUser -Name $UserName -FullName ([String]::Empty)
                }
                else
                {
                    Set-LocalUser -Name $UserName -FullName $FullName
                }
            }
        }
        else
        {
            if (-not $userExists)
            {
                # For a newly created user, set the DisplayName property to an empty string. By default DisplayName is set to user's name.
                Set-LocalUser -Name $UserName -FullName ([String]::Empty)
            }
        }

        if($PSBoundParameters.ContainsKey('Description') -and (-not $userExists -or $Description -ne $user.Description))
        {
            if ($Description -eq $null)
            {
                Set-LocalUser -Name $UserName -Description ([String]::Empty)
            }
            else
            {
                Set-LocalUser -Name $UserName -Description $Description
            }
        }

        # Password. Set the password regardless of the state of the user.
        if($PSBoundParameters.ContainsKey('Password'))
        {
            Set-LocalUser -Name $UserName -Password $Password.Password
        }

        if($PSBoundParameters.ContainsKey('Disabled') -and (-not $userExists -or $Disabled -eq $user.Enabled))
        {
            if ($Disabled)
            {
                Disable-LocalUser -Name $UserName
            }
            else
            {
                Enable-LocalUser -Name $UserName
            }
        }

        $existingUserPasswordNeverExpires = (($userExists) -and ($user.PasswordExpires -eq $null))
        if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and (-not $userExists -or ($PasswordNeverExpires -ne $existingUserPasswordNeverExpires)))
        {
            Set-LocalUser -Name $UserName -PasswordNeverExpires:$passwordNeverExpires
        }

        if($PSBoundParameters.ContainsKey('PasswordChangeRequired') -and ($PasswordChangeRequired))
        {
            Set-LocalUser -Name $UserName -PasswordChangeableDate ([datetime]::Now)
        }

        # NOTE: The parameter name and the property name have opposite meaning.
        [bool] $expected = -not $PasswordChangeNotAllowed
        [bool] $actual = $expected
        if($userExists) {
            $actual = $user.UserMayChangePassword
        }
        if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and (-not $userExists -or $expected -ne $actual))
        {
            Set-LocalUser -Name $UserName -UserMayChangePassword $expected
        }
    }
    else
    {
        # Ensure is set to "Absent".
        if($userExists)
        {
            # The user exists.
            Remove-LocalUser -Name $UserName

            Write-Verbose -Message ($LocalizedData.UserRemoved -f $UserName)
        }
        else
        {
            Write-Verbose -Message ($LocalizedData.NoConfigurationRequiredUserDoesNotExist -f $UserName)
        }
    }


    Write-Verbose -Message ($LocalizedData.ConfigurationCompleted -f $UserName)
}

<#
    .SYNOPSIS
    The Test-TargetResource cmdlet on a Nano server.
#>

function Test-TargetResourceOnNanoServer
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName,

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

        [System.String]
        $FullName,

        [System.String]
        $Description,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Password,

        [System.Boolean]
        $Disabled,

        [System.Boolean]
        $PasswordNeverExpires,

        [System.Boolean]
        $PasswordChangeRequired,

        [System.Boolean]
        $PasswordChangeNotAllowed
    )

    Set-StrictMode -Version Latest

    ValidateUserName -UserName $UserName

    # Try to find a user by a name.
    try
    {
        [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop
    }
    catch [System.Exception]
    {
        if ($_.CategoryInfo.ToString().Contains('UserNotFoundException'))
        {
            # The user is not found. Return Ensure=Absent.
            if($Ensure -eq "Absent")
            {
                return $true
            }
            else
            {
                return $false
            }
        }
        Throw-TerminatingError -ErrorRecord $_
    }

    # A user with the provided name exists.
    Write-Log -Message ($LocalizedData.UserExists -f $UserName)

    # Validate separate properties.
    if($Ensure -eq "Absent")
    {
        Write-Log -Message ($LocalizedData.PropertyMismatch -f "Ensure", "Absent", "Present")
        return $false; # The Ensure property does not match. Return $false;
    }

    if($PSBoundParameters.ContainsKey('FullName') -and $FullName -ne $user.FullName)
    {
        Write-Log -Message ($LocalizedData.PropertyMismatch -f "FullName", $FullName, $user.FullName)
        return $false; # The FullName property does not match. Return $false;
    }

    if($PSBoundParameters.ContainsKey('Description') -and $Description -ne $user.Description)
    {
        Write-Log -Message ($LocalizedData.PropertyMismatch -f "Description", $Description, $user.Description)
        return $false; # The Description property does not match. Return $false;
    }

    if($PSBoundParameters.ContainsKey('Password'))
    {
        if(-not (ValidateCredentialsOnNanoServer -UserName $UserName -Password $Password.Password))
        {
            Write-Log -Message ($LocalizedData.PasswordPropertyMismatch -f "Password")
            return $false; # The Password property does not match. Return $false;
        }
    }

    if($PSBoundParameters.ContainsKey('Disabled') -and $Disabled -eq $user.Enabled)
    {
        Write-Log -Message ($LocalizedData.PropertyMismatch -f "Disabled", $Disabled, $user.Enabled)
        return $false; # The Disabled property does not match. Return $false;
    }

    $existingUserPasswordNeverExpires = ($user.PasswordExpires -eq $null)
    if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PasswordNeverExpires -ne $existingUserPasswordNeverExpires)
    {
        Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordNeverExpires", $PasswordNeverExpires, $existingUserPasswordNeverExpires)
        return $false; # The PasswordNeverExpires property does not match. Return $false;
    }

    if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and $PasswordChangeNotAllowed -ne (-not $user.UserMayChangePassword))
    {
        Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordChangeNotAllowed", $PasswordChangeNotAllowed, (-not $user.UserMayChangePassword))
        return $false; # The PasswordChangeNotAllowed property does not match. Return $false;
    }

    # All properties match. Return $true.
    Write-Log -Message ($LocalizedData.AllUserPropertisMatch -f "User", $UserName)
    return $true;
}

<#
    .SYNOPSIS
    Validates the User name for invalid charecters.
#>

function ValidateUserName
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName
    )

    # Check if the name consists of only periods and/or white spaces.
    $wrongName = $true;
    for($i = 0; $i -lt $UserName.Length; $i++)
    {
        if(-not [Char]::IsWhiteSpace($UserName, $i) -and $UserName[$i] -ne '.')
        {
            $wrongName = $false;
            break;
        }
    }

    $invalidChars = @('\','/','"','[',']',':','|','<','>','+','=',';',',','?','*','@')

    if($wrongName)
    {
        ThrowInvalidArgumentError -ErrorId "UserNameHasOnlyWhiteSpacesAndDots" -ErrorMessage ($LocalizedData.InvalidUserName -f $UserName, [string]::Join(" ", $invalidChars))
    }

    if($UserName.IndexOfAny($invalidChars) -ne -1)
    {
        ThrowInvalidArgumentError -ErrorId "UserNameHasInvalidCharachter" -ErrorMessage ($LocalizedData.InvalidUserName -f $UserName, [string]::Join(" ", $invalidChars))
    }
}

<#
    .SYNOPSIS
    Throws an argument error.
#>

function ThrowInvalidArgumentError
{
    [CmdletBinding()]
    param
    (

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

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ErrorMessage
    )

    $errorCategory=[System.Management.Automation.ErrorCategory]::InvalidArgument
    $exception = New-Object System.ArgumentException $ErrorMessage;
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $errorCategory, $null
    throw $errorRecord
}

function ThrowExceptionDueToDirectoryServicesError
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ErrorId,

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

    $errorCategory = [System.Management.Automation.ErrorCategory]::ConnectionError
    $exception = New-Object System.ArgumentException $ErrorMessage
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $errorCategory, $null
    throw $errorRecord
}

function Throw-TerminatingError
{
    param(
        [string] $Message,
        [System.Management.Automation.ErrorRecord] $ErrorRecord
    )


    if ($ErrorRecord -ne $null)
    {
        $exception = new-object "System.InvalidOperationException" $Message,$ErrorRecord.Exception
    }
    else
    {
        $exception = new-object "System.InvalidOperationException" $Message
    }
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,"MachineStateIncorrect","InvalidOperation",$null
    throw $errorRecord
}

<#
    .SYNOPSIS
    Writes either to Verbose or ShouldProcess channel.
#>

function Write-Log
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Message
    )

    if ($PSCmdlet.ShouldProcess($Message, $null, $null))
    {
        Write-Verbose $Message
    }
}

<#
    .SYNOPSIS
    Validates the local user's credentials on the local machine.
#>

function ValidateCredentialsOnNanoServer
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $UserName,

        [ValidateNotNullOrEmpty()]
        [securestring]
        $Password
    )

    $source = @'
        [Flags]
        private enum LogonType
        {
            Logon32LogonInteractive = 2,
            Logon32LogonNetwork,
            Logon32LogonBatch,
            Logon32LogonService,
            Logon32LogonUnlock,
            Logon32LogonNetworkCleartext,
            Logon32LogonNewCredentials
        }
 
        [Flags]
        private enum LogonProvider
        {
            Logon32ProviderDefault = 0,
            Logon32ProviderWinnt35,
            Logon32ProviderWinnt40,
            Logon32ProviderWinnt50
        }
 
        [DllImport("api-ms-win-security-logon-l1-1-1.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern Boolean LogonUser(
            String lpszUserName,
            String lpszDomain,
            IntPtr lpszPassword,
            LogonType dwLogonType,
            LogonProvider dwLogonProvider,
            out IntPtr phToken
            );
 
 
        [DllImport("api-ms-win-core-handle-l1-1-0.dll",
            EntryPoint = "CloseHandle", SetLastError = true,
            CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
        internal static extern bool CloseHandle(IntPtr handle);
 
        public static bool ValidateCredentials(string username, SecureString password)
        {
            IntPtr tokenHandle = IntPtr.Zero;
            IntPtr unmanagedPassword = IntPtr.Zero;
 
            unmanagedPassword = SecureStringMarshal.SecureStringToCoTaskMemUnicode(password);
 
            try
            {
                return LogonUser(
                    username,
                    null,
                    unmanagedPassword,
                    LogonType.Logon32LogonInteractive,
                    LogonProvider.Logon32ProviderDefault,
                    out tokenHandle);
            }
            catch
            {
                return false;
            }
            finally
            {
                if (tokenHandle != IntPtr.Zero)
                {
                    CloseHandle(tokenHandle);
                }
                if (unmanagedPassword != IntPtr.Zero) {
                    Marshal.ZeroFreeCoTaskMemUnicode(unmanagedPassword);
                }
                unmanagedPassword = IntPtr.Zero;
            }
        }
'@


    Add-Type -PassThru -Namespace Microsoft.Windows.DesiredStateConfiguration.NanoServer.UserResource `
        -Name CredentialsValidationTool -MemberDefinition $source -Using System.Security -ReferencedAssemblies System.Security.SecureString.dll | Out-Null
    return [Microsoft.Windows.DesiredStateConfiguration.NanoServer.UserResource.CredentialsValidationTool]::ValidateCredentials($UserName, $Password)
}

Export-ModuleMember -Function *-TargetResource