DSCResources/MSFT_xRDSessionCollectionConfiguration/MSFT_xRDSessionCollectionConfiguration.psm1

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

$rdCommonModulePath = Join-Path -Path $modulesFolderPath -ChildPath 'xRemoteDesktopSessionHostCommon.psm1'
Import-Module -Name $rdCommonModulePath

$dscResourceCommonModulePath = Join-Path -Path $modulesFolderPath -ChildPath 'DscResource.Common'
Import-Module -Name $dscResourceCommonModulePath

if (-not (Test-xRemoteDesktopSessionHostOsRequirement))
{
    throw 'The minimum OS requirement was not met.'
}
Import-Module RemoteDesktop

#######################################################################
# The Get-TargetResource cmdlet.
#######################################################################
function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateLength(1, 256)]
        [string] $CollectionName,
        [Parameter()]
        [uint32] $ActiveSessionLimitMin,
        [Parameter()]
        [boolean] $AuthenticateUsingNLA,
        [Parameter()]
        [boolean] $AutomaticReconnectionEnabled,
        [Parameter()]
        [string] $BrokenConnectionAction,
        [Parameter()]
        [string] $ClientDeviceRedirectionOptions,
        [Parameter()]
        [boolean] $ClientPrinterAsDefault,
        [Parameter()]
        [boolean] $ClientPrinterRedirected,
        [Parameter()]
        [string] $CollectionDescription,
        [Parameter()]
        [string] $ConnectionBroker,
        [Parameter()]
        [string] $CustomRdpProperty,
        [Parameter()]
        [uint32] $DisconnectedSessionLimitMin,
        [Parameter()]
        [string] $EncryptionLevel,
        [Parameter()]
        [uint32] $IdleSessionLimitMin,
        [Parameter()]
        [uint32] $MaxRedirectedMonitors,
        [Parameter()]
        [boolean] $RDEasyPrintDriverEnabled,
        [Parameter()]
        [string] $SecurityLayer,
        [Parameter()]
        [boolean] $TemporaryFoldersDeletedOnExit,
        [Parameter()]
        [string[]] $UserGroup,
        [Parameter()]
        [string] $DiskPath,
        [Parameter()]
        [bool] $EnableUserProfileDisk,
        [Parameter()]
        [uint32] $MaxUserProfileDiskSizeGB,
        [Parameter()]
        [string[]] $IncludeFolderPath,
        [Parameter()]
        [string[]] $ExcludeFolderPath,
        [Parameter()]
        [string[]] $IncludeFilePath,
        [Parameter()]
        [string[]] $ExcludeFilePath
    )
    Write-Verbose "Getting currently configured RDSH Collection properties for collection $CollectionName"

    $collectionGeneral = Get-RDSessionCollectionConfiguration -CollectionName $CollectionName
    $collectionClient = Get-RDSessionCollectionConfiguration -CollectionName $CollectionName -Client
    $collectionConnection = Get-RDSessionCollectionConfiguration -CollectionName $CollectionName -Connection
    $collectionSecurity = Get-RDSessionCollectionConfiguration -CollectionName $CollectionName -Security
    $collectionUserGroup = Get-RDSessionCollectionConfiguration -CollectionName $CollectionName -UserGroup

    $result = @{
        CollectionName                 = $collectionGeneral.CollectionName
        ActiveSessionLimitMin          = $collectionConnection.ActiveSessionLimitMin
        AuthenticateUsingNLA           = $collectionSecurity.AuthenticateUsingNLA
        AutomaticReconnectionEnabled   = $collectionConnection.AutomaticReconnectionEnabled
        BrokenConnectionAction         = $collectionConnection.BrokenConnectionAction
        ClientDeviceRedirectionOptions = $collectionClient.ClientDeviceRedirectionOptions
        ClientPrinterAsDefault         = $collectionClient.ClientPrinterAsDefault
        ClientPrinterRedirected        = $collectionClient.ClientPrinterRedirected
        CollectionDescription          = $collectionGeneral.CollectionDescription
        # For whatever reason this value gets returned with a trailing carriage return
        CustomRdpProperty              = ([string]$collectionGeneral.CustomRdpProperty).Trim()
        DisconnectedSessionLimitMin    = $collectionConnection.DisconnectedSessionLimitMin
        EncryptionLevel                = $collectionSecurity.EncryptionLevel
        IdleSessionLimitMin            = $collectionConnection.IdleSessionLimitMin
        MaxRedirectedMonitors          = $collectionClient.MaxRedirectedMonitors
        RDEasyPrintDriverEnabled       = $collectionClient.RDEasyPrintDriverEnabled
        SecurityLayer                  = $collectionSecurity.SecurityLayer
        TemporaryFoldersDeletedOnExit  = $collectionConnection.TemporaryFoldersDeletedOnExit
        UserGroup                      = $collectionUserGroup.UserGroup
    }

    # This part of the configuration only applies to Win 2016+
    if ((Get-xRemoteDesktopSessionHostOsVersion).Major -ge 10)
    {
        Write-Verbose 'Running on W2016+, get UserProfileDisk configuration'
        $collectionUserProfileDisk = Get-RDSessionCollectionConfiguration -CollectionName $CollectionName -UserProfileDisk

        $null = $result.Add('DiskPath', $collectionUserProfileDisk.DiskPath)
        $null = $result.Add('EnableUserProfileDisk', $collectionUserProfileDisk.EnableUserProfileDisk)
        $null = $result.Add('MaxUserProfileDiskSizeGB', $collectionUserProfileDisk.MaxUserProfileDiskSizeGB)
        $null = $result.Add('IncludeFolderPath', $collectionUserProfileDisk.IncludeFolderPath)
        $null = $result.Add('ExcludeFolderPath', $collectionUserProfileDisk.ExcludeFolderPath)
        $null = $result.Add('IncludeFilePath', $collectionUserProfileDisk.IncludeFilePath)
        $null = $result.Add('ExcludeFilePath', $collectionUserProfileDisk.ExcludeFilePath)
    }

    $result
}

########################################################################
# The Set-TargetResource cmdlet.
########################################################################
function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateLength(1, 256)]
        [string] $CollectionName,
        [Parameter()]
        [uint32] $ActiveSessionLimitMin,
        [Parameter()]
        [boolean] $AuthenticateUsingNLA,
        [Parameter()]
        [boolean] $AutomaticReconnectionEnabled,
        [Parameter()]
        [string] $BrokenConnectionAction,
        [Parameter()]
        [string] $ClientDeviceRedirectionOptions,
        [Parameter()]
        [boolean] $ClientPrinterAsDefault,
        [Parameter()]
        [boolean] $ClientPrinterRedirected,
        [Parameter()]
        [string] $CollectionDescription,
        [Parameter()]
        [string] $ConnectionBroker,
        [Parameter()]
        [string] $CustomRdpProperty,
        [Parameter()]
        [uint32] $DisconnectedSessionLimitMin,
        [Parameter()]
        [string] $EncryptionLevel,
        [Parameter()]
        [uint32] $IdleSessionLimitMin,
        [Parameter()]
        [uint32] $MaxRedirectedMonitors,
        [Parameter()]
        [boolean] $RDEasyPrintDriverEnabled,
        [Parameter()]
        [string] $SecurityLayer,
        [Parameter()]
        [boolean] $TemporaryFoldersDeletedOnExit,
        [Parameter()]
        [string[]] $UserGroup,
        [Parameter()]
        [string] $DiskPath,
        [Parameter()]
        [bool] $EnableUserProfileDisk,
        [Parameter()]
        [uint32] $MaxUserProfileDiskSizeGB,
        [Parameter()]
        [string[]] $IncludeFolderPath,
        [Parameter()]
        [string[]] $ExcludeFolderPath,
        [Parameter()]
        [string[]] $IncludeFilePath,
        [Parameter()]
        [string[]] $ExcludeFilePath
    )
    Write-Verbose 'Setting DSC collection properties'

    try
    {
        $null = Get-RDSessionCollection -CollectionName $CollectionName -ErrorAction Stop
    }
    catch
    {
        throw "Failed to lookup RD Session Collection $CollectionName. Error: $_"
    }

    # By default we do not configure the UserProfileDisk (this is in a different parameter set and we could be running on W2012 R2)
    $null = $PSBoundParameters.Remove('DiskPath')
    $null = $PSBoundParameters.Remove('EnableUserProfileDisk')
    $null = $PSBoundParameters.Remove('ExcludeFilePath')
    $null = $PSBoundParameters.Remove('ExcludeFolderPath')
    $null = $PSBoundParameters.Remove('IncludeFilePath')
    $null = $PSBoundParameters.Remove('IncludeFolderPath')
    $null = $PSBoundParameters.Remove('MaxUserProfileDiskSizeGB')

    if ((Get-xRemoteDesktopSessionHostOsVersion).Major -ge 10)
    {
        Write-Verbose 'Running on W2016 or higher, prepare to set UserProfileDisk configuration'

        # First set the initial configuration before trying to modify the UserProfileDisk Configuration
        Set-RDSessionCollectionConfiguration @PSBoundParameters

        if ($EnableUserProfileDisk)
        {
            Write-Verbose 'EnableUserProfileDisk is True - a DiskPath and MaxUserProfileDiskSizeGB are now mandatory'

            if ($DiskPath)
            {
                $validateDiskPath = Test-Path -Path $DiskPath -ErrorAction SilentlyContinue
                if (-not($validateDiskPath))
                {
                    throw "To enable UserProfileDisk we need a valid DiskPath. Path $DiskPath not found"
                }
                else
                {
                    Write-Verbose "EnableUserProfileDisk: Validated diskPath: $DiskPath"
                }
            }
            else
            {
                throw 'No value found for parameter DiskPath. This is a mandatory parameter if EnableUserProfileDisk is set to True'
            }

            if ($MaxUserProfileDiskSizeGB -gt 0)
            {
                Write-Verbose "EnableUserProfileDisk: Validated MaxUserProfileDiskSizeGB size: $MaxUserProfileDiskSizeGB"
            }
            else
            {
                throw "To enable UserProfileDisk we need a setting for MaxUserProfileDiskSizeGB that is greater than 0. Current value $MaxUserProfileDiskSizeGB is not valid"
            }

            $enableUserProfileDiskSplat = @{
                CollectionName           = $CollectionName
                DiskPath                 = $DiskPath
                EnableUserProfileDisk    = $EnableUserProfileDisk
                ExcludeFilePath          = $ExcludeFilePath
                ExcludeFolderPath        = $ExcludeFolderPath
                IncludeFilePath          = $IncludeFilePath
                IncludeFolderPath        = $IncludeFolderPath
                MaxUserProfileDiskSizeGB = $MaxUserProfileDiskSizeGB
            }

            # 2>&1 redirects the error stream to output stream. This for us to be able to ignore certain errors that popup in Set-RDSessionCollectionConfiguration.
            $null = Set-RDSessionCollectionConfiguration @enableUserProfileDiskSplat -ErrorAction SilentlyContinue -ErrorVariable setRDSessionCollectionErrors 2>&1

            # This is a workaround for the buggy Set-RDSessionCollectionConfiguration. This command starts the functions in the Microsoft.windows.servermanagerworkflows configuration.
            # In this configuration, the C:\Windows\system32\WindowsPowerShell\v1.0\Modules\RemoteDesktop\Utility.psm1 module cannot call the RemoteDesktop module functions as they seem to load without the -RD prefix.
            # Here, we work around the errors thrown by Test-UserVhdPathInUse (the function in the Utility.psm1 module which calls the RemoteDesktop module functions)

            foreach ($setRDSessionCollectionError in $setRDSessionCollectionErrors)
            {
                if ($SetRDSessionCollectionError.FullyQualifiedErrorId -eq 'CommandNotFoundException')
                {
                    Write-Verbose "Set-RDSessionCollectionConfiguration: trapped erroneous CommandNotFoundException errors, that's ok, continuing..."
                    # ignore & continue
                }
                else
                {
                    Write-Error "Set-RDSessionCollectionConfiguration error: $setRDSessionCollectionError"
                }
            }
        }
        else
        {
            Set-RDSessionCollectionConfiguration -CollectionName $CollectionName -DisableUserProfileDisk
        }
    }
    else
    {
        Set-RDSessionCollectionConfiguration @PSBoundParameters
    }
}


#######################################################################
# The Test-TargetResource cmdlet.
#######################################################################
function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateLength(1, 256)]
        [string] $CollectionName,
        [Parameter()]
        [uint32] $ActiveSessionLimitMin,
        [Parameter()]
        [boolean] $AuthenticateUsingNLA,
        [Parameter()]
        [boolean] $AutomaticReconnectionEnabled,
        [Parameter()]
        [string] $BrokenConnectionAction,
        [Parameter()]
        [string] $ClientDeviceRedirectionOptions,
        [Parameter()]
        [boolean] $ClientPrinterAsDefault,
        [Parameter()]
        [boolean] $ClientPrinterRedirected,
        [Parameter()]
        [string] $CollectionDescription,
        [Parameter()]
        [string] $ConnectionBroker,
        [Parameter()]
        [string] $CustomRdpProperty,
        [Parameter()]
        [uint32] $DisconnectedSessionLimitMin,
        [Parameter()]
        [string] $EncryptionLevel,
        [Parameter()]
        [uint32] $IdleSessionLimitMin,
        [Parameter()]
        [uint32] $MaxRedirectedMonitors,
        [Parameter()]
        [boolean] $RDEasyPrintDriverEnabled,
        [Parameter()]
        [string] $SecurityLayer,
        [Parameter()]
        [boolean] $TemporaryFoldersDeletedOnExit,
        [Parameter()]
        [string[]] $UserGroup,
        [Parameter()]
        [string] $DiskPath,
        [Parameter()]
        [bool] $EnableUserProfileDisk,
        [Parameter()]
        [uint32] $MaxUserProfileDiskSizeGB,
        [Parameter()]
        [string[]] $IncludeFolderPath,
        [Parameter()]
        [string[]] $ExcludeFolderPath,
        [Parameter()]
        [string[]] $IncludeFilePath,
        [Parameter()]
        [string[]] $ExcludeFilePath
    )

    $verbose = $PSBoundParameters.Verbose -eq $true

    Write-Verbose 'Testing DSC collection properties'

    $null = $PSBoundParameters.Remove('Verbose')
    $null = $PSBoundParameters.Remove('Debug')
    $null = $PSBoundParameters.Remove('ConnectionBroker')

    if ((Get-xRemoteDesktopSessionHostOsVersion).Major -lt 10)
    {
        Write-Verbose 'Running on W2012R2 or lower, removing properties that are not compatible'

        $null = $PSBoundParameters.Remove('CollectionName')
        $null = $PSBoundParameters.Remove('DiskPath')
        $null = $PSBoundParameters.Remove('EnableUserProfileDisk')
        $null = $PSBoundParameters.Remove('ExcludeFilePath')
        $null = $PSBoundParameters.Remove('ExcludeFolderPath')
        $null = $PSBoundParameters.Remove('IncludeFilePath')
        $null = $PSBoundParameters.Remove('IncludeFolderPath')
        $null = $PSBoundParameters.Remove('MaxUserProfileDiskSizeGB')
    }

    if (-not($EnableUserProfileDisk))
    {
        Write-Verbose 'Running on W2016+ and UserProfileDisk is disabled. Removing properties from compare'

        $null = $PSBoundParameters.Remove('DiskPath')
        $null = $PSBoundParameters.Remove('ExcludeFilePath')
        $null = $PSBoundParameters.Remove('ExcludeFolderPath')
        $null = $PSBoundParameters.Remove('IncludeFilePath')
        $null = $PSBoundParameters.Remove('IncludeFolderPath')
        $null = $PSBoundParameters.Remove('MaxUserProfileDiskSizeGB')
    }

    $testDscParameterStateSplat = @{
        CurrentValues       = Get-TargetResource -CollectionName $CollectionName
        DesiredValues       = $PSBoundParameters
        TurnOffTypeChecking = $true
        SortArrayValues     = $true
        Verbose             = $verbose
    }

    Test-DscParameterState @testDscParameterStateSplat
}

Export-ModuleMember -Function *-TargetResource