DSCResources/MSFT_SPUserProfileProperty/MSFT_SPUserProfileProperty.psm1

$script:SPDscUtilModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SharePointDsc.Util'
Import-Module -Name $script:SPDscUtilModulePath

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

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

        [Parameter(Mandatory = $true)]
        [System.string]
        $UserProfileService,

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

        [Parameter()]
        [ValidateSet("Big Integer",
            "Binary",
            "Boolean",
            "Date",
            "DateNoYear",
            "Date Time",
            "Email",
            "Float",
            "HTML",
            "Integer",
            "Person",
            "String (Single Value)",
            "String (Multi Value)",
            "TimeZone",
            "Unique Identifier",
            "URL")]
        [System.string]
        $Type,

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

        [Parameter()]
        [ValidateSet("Mandatory", "Optin", "Optout", "Disabled")]
        [System.string]
        $PolicySetting,

        [Parameter()]
        [ValidateSet("Public", "Contacts", "Organization", "Manager", "Private")]
        [System.string]
        $PrivacySetting,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $PropertyMappings,

        [Parameter()]
        [System.uint32]
        $Length,

        [Parameter()]
        [System.uint32]
        $DisplayOrder,

        [Parameter()]
        [System.Boolean]
        $IsEventLog,

        [Parameter()]
        [System.Boolean]
        $IsVisibleOnEditor,

        [Parameter()]
        [System.Boolean]
        $IsVisibleOnViewer,

        [Parameter()]
        [System.Boolean]
        $IsUserEditable,

        [Parameter()]
        [System.Boolean]
        $IsAlias,

        [Parameter()]
        [System.Boolean]
        $IsSearchable,

        [Parameter()]
        [System.Boolean]
        $IsReplicable,

        [Parameter()]
        [System.Boolean]
        $UserOverridePrivacy,

        [Parameter()]
        [System.string]
        $TermStore,

        [Parameter()]
        [System.string]
        $TermGroup,

        [Parameter()]
        [System.string]
        $TermSet,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $InstallAccount
    )

    Write-Verbose -Message "Getting user profile property $Name"

    if ($PSBoundParameters.ContainsKey("PropertyMappings") -eq $true)
    {
        $connections = $PropertyMappings.ConnectionName

        $connectionsCounts = @{}
        $duplicates = ""
        $connections | ForEach-Object -Process { $connectionsCounts["$_"] += 1 }
        $connectionsCounts.Keys | Where-Object -FilterScript { $connectionsCounts["$_"] -gt 1 } | ForEach-Object -Process { $duplicates += "$_," }
        $duplicates = $duplicates.TrimEnd(",")

        if ([System.String]::IsNullOrEmpty($duplicates) -eq $false)
        {
            $message = ("You have specified two PropertyMappings with the same ConnectionName. " + `
                    "Make sure each PropertyMapping is using a unique ConnectionName: $duplicate")
            Write-Verbose -Message $message

            return @{
                Name               = $Name
                UserProfileService = $UserProfileService
                Ensure             = "Absent"
            }
        }
    }

    $result = Invoke-SPDscCommand -Credential $InstallAccount `
        -Arguments $PSBoundParameters `
        -ScriptBlock {
        $params = $args[0]

        $upsa = Get-SPServiceApplication | Where-Object -FilterScript {
            $_.Name -eq $params.UserProfileService
        }

        $nullReturn = @{
            Name               = $params.Name
            UserProfileService = $params.UserProfileService
            Ensure             = "Absent"
        }
        if ($null -eq $upsa)
        {
            return $nullReturn
        }

        $caURL = (Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript {
                $_.IsAdministrationWebApplication -eq $true
            }).Url

        $context = Get-SPServiceContext -Site $caURL

        $userProfileSubTypeManager = Get-SPDscUserProfileSubTypeManager -Context $context
        $userProfileSubType = $userProfileSubTypeManager.GetProfileSubtype("UserProfile")

        $userProfileProperty = $userProfileSubType.Properties.GetPropertyByName($params.Name)
        if ($null -eq $userProfileProperty)
        {
            return $nullReturn
        }

        $termSet = @{
            TermSet   = ""
            TermGroup = ""
            TermStore = ""
        }

        if ($null -ne $userProfileProperty.CoreProperty.TermSet)
        {
            $termSet.TermSet = $userProfileProperty.CoreProperty.TermSet.Name
            $termSet.TermGroup = $userProfileProperty.CoreProperty.TermSet.Group.Name
            $termSet.TermStore = $userProfileProperty.CoreProperty.TermSet.TermStore.Name
        }

        $userProfilePropertyMappings = @()

        $userProfileConfigManager = New-Object -TypeName "Microsoft.Office.Server.UserProfiles.UserProfileConfigManager" `
            -ArgumentList $context

        if ($null -eq $userProfileConfigManager.ConnectionManager)
        {
            return $nullReturn
        }

        foreach ($propertyMapping in $params.PropertyMappings)
        {
            try
            {
                $connection = $userProfileConfigManager.ConnectionManager[$propertyMapping.ConnectionName]

                # This only works with SharePoint 2013 and AD Sync Connections.
                $syncConnection = $connection | Where-Object -FilterScript {
                    $null -ne $_.PropertyMapping -and $null -ne $_.PropertyMapping.Item($params.Name)
                }

                if ($null -ne $syncConnection)
                {
                    # This code will only be reached with SP 2013 and AD Sync Connections.
                    $currentMapping = $syncConnection.PropertyMapping.Item($params.Name)
                    if ($null -ne $currentMapping)
                    {
                        $mapping = @{ }
                        $mapping.Direction = "Import"
                        $mapping.ConnectionName = $params.MappingConnectionName
                        if ($currentMapping.IsExport)
                        {
                            $mapping.Direction = "Export"
                        }
                        $mapping.PropertyName = $currentMapping.DataSourcePropertyName

                        $property = @{
                            ConnectionName = $propertyMapping.ConnectionName
                            PropertyName   = $mapping.ConnectionName
                            Direction      = $mapping.Direction
                        }
                        $userProfilePropertyMappings += (New-CimInstance -ClassName MSFT_SPUserProfilePropertyMapping -ClientOnly -Property $property)
                    }
                }
                else
                {
                    # This code is for SP 2013, 2016 and 2019 with AD Import Connections.
                    if ($connection.Type -eq "ActiveDirectoryImport")
                    {
                        try
                        {
                            $adImportConnection = [Microsoft.Office.Server.UserProfiles.ActiveDirectoryImportConnection]$connection
                        }
                        catch [Exception]
                        {
                            $adImportConnection = $connection
                        }

                        $propertyFlags = [System.Reflection.BindingFlags]::Instance -bor `
                            [System.Reflection.BindingFlags]::NonPublic

                        $propMembers = $adImportConnection.GetType().GetMethods($propertyFlags)

                        $adImportPropertyMappingsMethod = $propMembers | Where-Object -FilterScript {
                            $_.Name -eq "ADImportPropertyMappings"
                        }
                        $propertyMappings = $adImportPropertyMappingsMethod.Invoke($adImportConnection, $null)

                        $propertyMappings | ForEach-Object -Process {
                            $currentMappingMembers = $_.GetType().GetMembers($propertyFlags)
                            $profileProperty = $currentMappingMembers | Where-Object -FilterScript {
                                $_.Name -eq "ProfileProperty"
                            }
                            if ($null -ne $profileProperty)
                            {
                                $profilePropertyValue = $profileProperty.GetValue($_)
                                if ($profilePropertyValue -eq $params.Name)
                                {
                                    $adAttributeProperty = $currentMappingMembers | Where-Object -FilterScript {
                                        $_.Name -eq "ADAttribute"
                                    }
                                    if ($null -ne $adAttributeProperty)
                                    {
                                        $property = @{
                                            ConnectionName = $propertyMapping.ConnectionName
                                            PropertyName   = $adAttributeProperty.GetValue($_)
                                            Direction      = "Import"
                                        }
                                        $userProfilePropertyMappings += (New-CimInstance -ClassName MSFT_SPUserProfilePropertyMapping -ClientOnly -Property $property)
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch [Exception]
            {
                Write-Verbose "An unecpexted error occured. Please report an issue! $_"
            }
        }

        return @{
            Name                = $userProfileProperty.Name
            UserProfileService  = $params.UserProfileService
            DisplayName         = $userProfileProperty.DisplayName
            Type                = $userProfileProperty.CoreProperty.Type
            Description         = $userProfileProperty.Description
            PolicySetting       = $userProfileProperty.PrivacyPolicy
            PrivacySetting      = $userProfileProperty.DefaultPrivacy
            PropertyMappings    = $userProfilePropertyMappings
            Length              = $userProfileProperty.CoreProperty.Length
            DisplayOrder        = $userProfileProperty.DisplayOrder
            IsEventLog          = $userProfileProperty.TypeProperty.IsEventLog
            IsVisibleOnEditor   = $userProfileProperty.TypeProperty.IsVisibleOnEditor
            IsVisibleOnViewer   = $userProfileProperty.TypeProperty.IsVisibleOnViewer
            IsUserEditable      = $userProfileProperty.IsUserEditable
            IsAlias             = $userProfileProperty.IsAlias
            IsSearchable        = $userProfileProperty.CoreProperty.IsSearchable
            IsReplicable        = $userProfileProperty.TypeProperty.IsReplicable
            TermStore           = $termSet.TermStore
            TermGroup           = $termSet.TermGroup
            TermSet             = $termSet.TermSet
            UserOverridePrivacy = $userProfileProperty.UserOverridePrivacy
            Ensure              = "Present"
        }

    }
    return $result
}

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

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

        [Parameter(Mandatory = $true)]
        [System.string]
        $UserProfileService,

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

        [Parameter()]
        [ValidateSet("Big Integer",
            "Binary",
            "Boolean",
            "Date",
            "DateNoYear",
            "Date Time",
            "Email",
            "Float",
            "HTML",
            "Integer",
            "Person",
            "String (Single Value)",
            "String (Multi Value)",
            "TimeZone",
            "Unique Identifier",
            "URL")]
        [System.string]
        $Type,

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

        [Parameter()]
        [ValidateSet("Mandatory", "Optin", "Optout", "Disabled")]
        [System.string]
        $PolicySetting,

        [Parameter()]
        [ValidateSet("Public", "Contacts", "Organization", "Manager", "Private")]
        [System.string]
        $PrivacySetting,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $PropertyMappings,

        [Parameter()]
        [System.uint32]
        $Length,

        [Parameter()]
        [System.uint32]
        $DisplayOrder,

        [Parameter()]
        [System.Boolean]
        $IsEventLog,

        [Parameter()]
        [System.Boolean]
        $IsVisibleOnEditor,

        [Parameter()]
        [System.Boolean]
        $IsVisibleOnViewer,

        [Parameter()]
        [System.Boolean]
        $IsUserEditable,

        [Parameter()]
        [System.Boolean]
        $IsAlias,

        [Parameter()]
        [System.Boolean]
        $IsSearchable,

        [Parameter()]
        [System.Boolean]
        $IsReplicable,

        [Parameter()]
        [System.Boolean]
        $UserOverridePrivacy,

        [Parameter()]
        [System.string]
        $TermStore,

        [Parameter()]
        [System.string]
        $TermGroup,

        [Parameter()]
        [System.string]
        $TermSet,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $InstallAccount
    )

    # Note for integration test: CA can take a couple of minutes to notice the change. don't try
    # refreshing properties page. Go through from a fresh "flow" from Service apps page

    Write-Verbose -Message "Setting user profile property $Name"

    if ($PSBoundParameters.ContainsKey("PropertyMappings") -eq $true)
    {
        $connections = $PropertyMappings.ConnectionName

        $connectionsCounts = @{}
        $duplicates = ""
        $connections | ForEach-Object -Process { $connectionsCounts["$_"] += 1 }
        $connectionsCounts.Keys | Where-Object -FilterScript { $connectionsCounts["$_"] -gt 1 } | ForEach-Object -Process { $duplicates += "$_," }
        $duplicates = $duplicates.TrimEnd(",")

        if ([System.String]::IsNullOrEmpty($duplicates) -eq $false)
        {
            $message = ("You have specified two PropertyMappings with the same ConnectionName. " + `
                    "Make sure each PropertyMapping is using a unique ConnectionName: $duplicate")
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $MyInvocation.MyCommand.Source
            throw $message
        }
    }

    $PSBoundParameters.Ensure = $Ensure

    Invoke-SPDscCommand -Credential $InstallAccount `
        -Arguments @($PSBoundParameters, $MyInvocation.MyCommand.Source) `
        -ScriptBlock {
        $params = $args[0]
        $eventSource = $args[1]

        if ( ($params.ContainsKey("TermSet")  `
                    -or $params.ContainsKey("TermGroup") `
                    -or $params.ContainsKey("TermSet") ) `
                -and ($params.ContainsKey("TermSet") `
                    -and $params.ContainsKey("TermGroup") `
                    -and $params.ContainsKey("TermSet") -eq $false)
        )
        {
            $message = ("You have to provide all 3 parameters Termset, TermGroup and TermStore " + `
                    "when providing any of the 3.")
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $eventSource
            throw $message
        }

        if ($params.ContainsKey("TermSet") `
                -and (@("string (single value)", "string (multi value)").Contains($params.Type.ToLower()) -eq $false))
        {
            $message = "Only String and String Multivalue can use Termsets"
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $eventSource
            throw $message
        }

        $ups = Get-SPServiceApplication | Where-Object -FilterScript {
            $_.Name -eq $params.UserProfileService
        }

        if ($null -eq $ups)
        {
            return $null
        }

        $caURL = (Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript {
                $_.IsAdministrationWebApplication -eq $true
            }).Url
        $context = Get-SPServiceContext $caURL

        $userProfileConfigManager = New-Object -TypeName "Microsoft.Office.Server.UserProfiles.UserProfileConfigManager" `
            -ArgumentList $context

        if ($null -eq $userProfileConfigManager)
        {
            #if config manager returns when ups is available then isuee is permissions
            $message = ("Account running process needs admin permissions on the user profile " + `
                    "service application")
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $eventSource
            throw $message
        }
        $coreProperties = $userProfileConfigManager.ProfilePropertyManager.GetCoreProperties()

        $userProfileSubTypeManager = Get-SPDscUserProfileSubTypeManager $context
        $userProfileSubType = $userProfileSubTypeManager.GetProfileSubtype("UserProfile")

        $userProfileProperty = $userProfileSubType.Properties.GetPropertyByName($params.Name)

        if ($null -ne $userProfileProperty -and $params.ContainsKey("Type") `
                -and $userProfileProperty.CoreProperty.Type -ne $params.Type)
        {
            $message = ("Can't change property type. Current Type is " + `
                    "$($userProfileProperty.CoreProperty.Type)")
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $eventSource
            throw $message
        }

        $termSet = $null

        if ($params.ContainsKey("TermSet"))
        {
            $currentTermSet = $userProfileProperty.CoreProperty.TermSet;
            if ($currentTermSet.Name -ne $params.TermSet -or
                $currentTermSet.Group.Name -ne $params.TermGroup -or
                $currentTermSet.TermStore.Name -ne $params.TermStore)
            {
                $session = New-Object -TypeName Microsoft.SharePoint.Taxonomy.TaxonomySession `
                    -ArgumentList $caURL

                $termStore = $session.TermStores[$params.TermStore]

                if ($null -eq $termStore)
                {
                    $message = "Term Store $($params.termStore) not found"
                    Add-SPDscEvent -Message $message `
                        -EntryType 'Error' `
                        -EventID 100 `
                        -Source $eventSource
                    throw $message
                }

                $group = $termStore.Groups[$params.TermGroup]

                if ($null -eq $group)
                {
                    $message = "Term Group $($params.termGroup) not found"
                    Add-SPDscEvent -Message $message `
                        -EntryType 'Error' `
                        -EventID 100 `
                        -Source $eventSource
                    throw $message
                }

                $termSet = $group.TermSets[$params.TermSet]
                if ($null -eq $termSet)
                {
                    $message = "Term Set $($params.termSet) not found"
                    Add-SPDscEvent -Message $message `
                        -EntryType 'Error' `
                        -EventID 100 `
                        -Source $eventSource
                    throw $message
                }
            }
        }

        if ($params.ContainsKey("Ensure") -and $params.Ensure -eq "Absent")
        {
            if ($null -ne $userProfileProperty)
            {
                $coreProperties.RemovePropertyByName($params.Name)
                return
            }
        }
        elseif ($null -eq $userProfileProperty)
        {
            $coreProperty = $coreProperties.Create($false)
            $coreProperty.Name = $params.Name
            $coreProperty.DisplayName = $params.DisplayName

            Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $coreProperty `
                -PropertyToSet "Length" `
                -ParamsValue $params `
                -ParamKey "Length"

            if ($params.Type -eq "String (Multi Value)")
            {
                $coreProperty.IsMultivalued = $true
            }

            $coreProperty.Type = $params.Type
            if ($null -ne $termSet)
            {
                $coreProperty.TermSet = $termSet
            }

            $userProfilePropertyManager = $userProfileConfigManager.ProfilePropertyManager
            $userProfileTypeProperties = $userProfilePropertyManager.GetProfileTypeProperties([Microsoft.Office.Server.UserProfiles.ProfileType]::User)
            $userProfileSubTypeProperties = $userProfileSubType.Properties

            $CoreProperties.Add($coreProperty)
            $upTypeProperty = $userProfileTypeProperties.Create($coreProperty)
            $userProfileTypeProperties.Add($upTypeProperty)
            $upSubProperty = $userProfileSubTypeProperties.Create($UPTypeProperty)
            $userProfileSubTypeProperties.Add($upSubProperty)
            Start-Sleep -Milliseconds 100
            $userProfileProperty = $userProfileSubType.Properties.GetPropertyByName($params.Name)

        }

        $coreProperty = $userProfileProperty.CoreProperty
        $userProfileTypeProperty = $userProfileProperty.TypeProperty
        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $coreProperty `
            -PropertyToSet "DisplayName" `
            -ParamsValue $params `
            -ParamKey "DisplayName"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $coreProperty `
            -PropertyToSet "Description" `
            -ParamsValue $params `
            -ParamKey "Description"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $userProfileTypeProperty `
            -PropertyToSet "IsVisibleOnViewer" `
            -ParamsValue $params `
            -ParamKey "IsVisibleOnViewer"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $userProfileTypeProperty `
            -PropertyToSet "IsVisibleOnEditor" `
            -ParamsValue $params `
            -ParamKey "IsVisibleOnEditor"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $userProfileTypeProperty `
            -PropertyToSet "IsEventLog" `
            -ParamsValue $params `
            -ParamKey "IsEventLog"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $coreProperty `
            -PropertyToSet "IsSearchable" `
            -ParamsValue $params `
            -ParamKey "IsSearchable"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $userProfileTypeProperty `
            -PropertyToSet "IsReplicable" `
            -ParamsValue $params `
            -ParamKey "IsReplicable"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $userProfileProperty `
            -PropertyToSet "DefaultPrivacy" `
            -ParamsValue $params `
            -ParamKey "PrivacySetting"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $userProfileProperty `
            -PropertyToSet "PrivacyPolicy" `
            -ParamsValue $params `
            -ParamKey "PolicySetting"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $userProfileProperty `
            -PropertyToSet "IsUserEditable" `
            -ParamsValue $params `
            -ParamKey "IsUserEditable"

        Set-SPDscObjectPropertyIfValuePresent -ObjectToSet $userProfileProperty `
            -PropertyToSet "UserOverridePrivacy" `
            -ParamsValue $params `
            -ParamKey "UserOverridePrivacy"
        if ($termSet)
        {
            $coreProperty.TermSet = $termSet
        }

        $userProfileProperty.CoreProperty.Commit()
        $userProfileTypeProperty.Commit()
        $userProfileProperty.Commit()

        if ($params.ContainsKey("DisplayOrder"))
        {
            $profileManager = New-Object -TypeName "Microsoft.Office.Server.UserProfiles.UserProfileManager" `
                -ArgumentList $context
            $profileManager.Properties.SetDisplayOrderByPropertyName($params.Name, $params.DisplayOrder)
            $profileManager.Properties.CommitDisplayOrder()
        }

        if ($params.ContainsKey("PropertyMappings"))
        {
            foreach ($propertyMapping in $params.PropertyMappings)
            {
                $syncConnection = $userProfileConfigManager.ConnectionManager[$propertyMapping.ConnectionName]

                if ($null -eq $syncConnection)
                {
                    $message = "connection not found"
                    Add-SPDscEvent -Message $message `
                        -EntryType 'Error' `
                        -EventID 100 `
                        -Source $eventSource
                    throw $message
                }

                if ($null -ne $syncConnection.PropertyMapping)
                {
                    $currentMapping = $syncConnection.PropertyMapping.Item($params.Name)
                }

                if ($null -eq $currentMapping `
                        -or ($currentMapping.DataSourcePropertyName -ne $propertyMapping.PropertyName) `
                        -or ($currentMapping.IsImport `
                            -and $propertyMapping.Direction -eq "Export")
                )
                {
                    if ($null -ne $currentMapping)
                    {
                        $currentMapping.Delete() #API allows updating, but UI doesn't do that.
                    }

                    $export = $propertyMapping.Direction -eq "Export"
                    if ($syncConnection.Type -eq "ActiveDirectoryImport")
                    {
                        if ($export)
                        {
                            $message = "not implemented"
                            Add-SPDscEvent -Message $message `
                                -EntryType 'Error' `
                                -EventID 100 `
                                -Source $eventSource
                            throw $message
                        }
                        else
                        {
                            $syncConnection.AddPropertyMapping($propertyMapping.PropertyName, $params.Name)
                            $syncConnection.Update()
                        }
                    }
                    else
                    {
                        if ($export)
                        {
                            $syncConnection.PropertyMapping.AddNewExportMapping([Microsoft.Office.Server.UserProfiles.ProfileType]::User,
                                $params.Name,
                                $propertyMapping.PropertyName)
                        }
                        else
                        {
                            $syncConnection.PropertyMapping.AddNewMapping([Microsoft.Office.Server.UserProfiles.ProfileType]::User,
                                $params.Name,
                                $propertyMapping.PropertyName)
                        }
                    }
                }
            }
        }
    }
}

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

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

        [Parameter(Mandatory = $true)]
        [System.string]
        $UserProfileService,

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

        [Parameter()]
        [ValidateSet("Big Integer",
            "Binary",
            "Boolean",
            "Date",
            "DateNoYear",
            "Date Time",
            "Email",
            "Float",
            "HTML",
            "Integer",
            "Person",
            "String (Single Value)",
            "String (Multi Value)",
            "TimeZone",
            "Unique Identifier",
            "URL")]
        [System.string]
        $Type,

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

        [Parameter()]
        [ValidateSet("Mandatory", "Optin", "Optout", "Disabled")]
        [System.string]
        $PolicySetting,

        [Parameter()]
        [ValidateSet("Public", "Contacts", "Organization", "Manager", "Private")]
        [System.string]
        $PrivacySetting,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $PropertyMappings,

        [Parameter()]
        [System.uint32]
        $Length,

        [Parameter()]
        [System.uint32]
        $DisplayOrder,

        [Parameter()]
        [System.Boolean]
        $IsEventLog,

        [Parameter()]
        [System.Boolean]
        $IsVisibleOnEditor,

        [Parameter()]
        [System.Boolean]
        $IsVisibleOnViewer,

        [Parameter()]
        [System.Boolean]
        $IsUserEditable,

        [Parameter()]
        [System.Boolean]
        $IsAlias,

        [Parameter()]
        [System.Boolean]
        $IsSearchable,

        [Parameter()]
        [System.Boolean]
        $IsReplicable,

        [Parameter()]
        [System.Boolean]
        $UserOverridePrivacy,

        [Parameter()]
        [System.string]
        $TermStore,

        [Parameter()]
        [System.string]
        $TermGroup,

        [Parameter()]
        [System.string]
        $TermSet,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $InstallAccount

    )

    Write-Verbose -Message "Testing for user profile property $Name"

    $PSBoundParameters.Ensure = $Ensure

    $CurrentValues = Get-TargetResource @PSBoundParameters

    Write-Verbose -Message "Current Values: $(Convert-SPDscHashtableToString -Hashtable $CurrentValues)"
    Write-Verbose -Message "Target Values: $(Convert-SPDscHashtableToString -Hashtable $PSBoundParameters)"

    if ($Ensure -eq "Present")
    {
        $result = Test-SPDscParameterState -CurrentValues $CurrentValues `
            -Source $($MyInvocation.MyCommand.Source) `
            -DesiredValues $PSBoundParameters `
            -ValuesToCheck @("Name",
            "DisplayName",
            "Type",
            "Description",
            "PolicySetting",
            "PrivacySetting",
            "PropertyMappings",
            "Length",
            "DisplayOrder",
            "IsEventLog",
            "IsVisibleOnEditor",
            "IsVisibleOnViewer",
            "IsUserEditable",
            "IsAlias",
            "IsSearchable",
            "IsReplicable",
            "UserOverridePrivacy",
            "TermGroup",
            "TermStore",
            "TermSet",
            "Ensure")
    }
    else
    {
        $result = Test-SPDscParameterState -CurrentValues $CurrentValues `
            -Source $($MyInvocation.MyCommand.Source) `
            -DesiredValues $PSBoundParameters `
            -ValuesToCheck @("Ensure")
    }

    Write-Verbose -Message "Test-TargetResource returned $result"

    return $result
}

function Export-TargetResource
{
    $VerbosePreference = "SilentlyContinue"
    $ParentModuleBase = Get-Module "SharePointDsc" -ListAvailable | Select-Object -ExpandProperty Modulebase
    $module = Join-Path -Path $ParentModuleBase -ChildPath  "\DSCResources\MSFT_SPUserProfileProperty\MSFT_SPUserProfileProperty.psm1" -Resolve
    $Content = ''
    $params = Get-DSCFakeParameters -ModulePath $module
    $caURL = (Get-SpWebApplication -IncludeCentralAdministration | Where-Object -FilterScript { $_.IsAdministrationWebApplication -eq $true }).Url
    $context = Get-SPServiceContext -Site $caURL
    try
    {
        $userProfileConfigManager = New-Object -TypeName "Microsoft.Office.Server.UserProfiles.UserProfileConfigManager" `
            -ArgumentList $context
        $properties = $userProfileConfigManager.GetPropertiesWithSection()
        $properties = $properties | Where-Object { $_.IsSection -eq $false }

        $userProfileServiceApp = Get-SPServiceApplication | Where-Object { $_.GetType().Name -eq "UserProfileApplication" }

        <# WA - Bug in SPDSC 1.7.0.0 if there is a sync connection, then we need to skip the properties. #>
        if ($null -ne $userProfileConfigManager.ConnectionManager.PropertyMapping)
        {
            $i = 1;
            $total = $properties.Length;
            foreach ($property in $properties)
            {
                try
                {
                    $params.Name = $property.Name
                    Write-Host " -> Scanning User Profile Property [$i/$total] {$($property.Name)}"
                    $params.UserProfileService = $userProfileServiceApp[0].DisplayName
                    $PartialContent = " SPUserProfileProperty " + [System.Guid]::NewGuid().ToString() + "`r`n"
                    $PartialContent += " {`r`n"

                    <# Cleanup empty properties #>
                    try
                    {
                        foreach ($param in $params)
                        {
                            if ($param -eq "")
                            {
                                $params.Remove($param)
                            }
                        }
                    }
                    catch
                    {
                    }

                    if ($params.MappingConnectionName -eq "*")
                    {
                        $params.Remove("MappingConnectionName")
                    }
                    $results = Get-TargetResource @params

                    <# WA - Bug in SPDSC 1.7.0.0 where param returned is named UserProfileServiceAppName instead of
                            just UserProfileService. #>

                    if ($null -ne $results.Get_Item("UserProfileServiceAppName"))
                    {
                        $results.Add("UserProfileService", $results.UserProfileServiceAppName)
                        $results.Remove("UserProfileServiceAppName")
                    }

                    if ($results.TermGroup -eq "" -or $results.TermSet -eq "" -or $results.TermStore -eq "")
                    {
                        $results.Remove("TermGroup")
                        $results.Remove("TermStore")
                        $results.Remove("TermSet")
                    }

                    $results = Repair-Credentials -results $results
                    $currentBlock = Get-DSCBlock -Params $results -ModulePath $module
                    $currentBlock = Convert-DSCStringParamToVariable -DSCBlock $currentBlock -ParameterName "PsDscRunAsCredential"
                    $PartialContent += $currentBlock
                    $PartialContent += " }`r`n"
                    $Content += $PartialContent
                }
                catch
                {
                    $_
                    $Global:ErrorLog += "[User Profile Property]" + $property.Name + "`r`n"
                    $Global:ErrorLog += "$_`r`n`r`n"
                }
                $i++
            }
        }
    }
    catch
    {
        $_
    }
    return $Content
}

Export-ModuleMember -Function *-TargetResource