JeaDsc.psm1

#Region './Enum/Ensure.ps1' 0
enum Ensure
{
    Present
    Absent
}
#EndRegion './Enum/Ensure.ps1' 6
#Region './Classes/1.Reason.ps1' 0
class Reason
{
    [DscProperty()]
    [string] $Phrase

    [DscProperty()]
    [string] $Code
}
#EndRegion './Classes/1.Reason.ps1' 9
#Region './Classes/1.RoleCapabilitiesUtility.ps1' 0
<#
    .DESCRIPTION
        Parent class for DSC resource JeaRoleCapabilities.
#>


$modulePath = Join-Path -Path $PSScriptRoot -ChildPath Modules

Import-Module -Name (Join-Path -Path $modulePath -ChildPath DscResource.Common)
Import-Module -Name (Join-Path -Path $modulePath -ChildPath (Join-Path -Path JeaDsc.Common -ChildPath JeaDsc.Common.psm1))

$script:localizedDataRole = Get-LocalizedData -DefaultUICulture en-US -FileName 'JeaRoleCapabilities.strings.psd1'

class RoleCapabilitiesUtility
{
    hidden [boolean] ValidatePath()
    {
        $fileObject = [System.IO.FileInfo]::new($this.Path)
        Write-Verbose -Message ($script:localizedDataRole.ValidatingPath -f $fileObject.Fullname)
        Write-Verbose -Message ($script:localizedDataRole.CheckPsrcExtension -f $fileObject.Fullname)
        if ($fileObject.Extension -ne '.psrc')
        {
            Write-Verbose -Message ($script:localizedDataRole.NotPsrcExtension -f $fileObject.Fullname)
            return $false
        }

        Write-Verbose -Message ($script:localizedDataRole.CheckParentFolder -f $fileObject.Fullname)
        if ($fileObject.Directory.Name -ne 'RoleCapabilities')
        {
            Write-Verbose -Message ($script:localizedDataRole.NotRoleCapabilitiesParent -f $fileObject.Fullname)
            return $false
        }


        Write-Verbose -Message $script:localizedDataRole.ValidePsrcPath
        return $true
    }
}
#EndRegion './Classes/1.RoleCapabilitiesUtility.ps1' 38
#Region './Classes/1.SessionConfigurationUtility.ps1' 0
<#
    .DESCRIPTION
        Parent class for DSC resource JeaSessionConfiguration.
#>


$script:localizedDataSession = Get-LocalizedData -DefaultUICulture en-US -FileName 'JeaSessionConfiguration.strings.psd1'

class SessionConfigurationUtility
{
    hidden [bool] TestParameters()
    {
        if (-not $this.SessionType)
        {
            $this.SessionType = 'RestrictedRemoteServer'
        }

        if ($this.RunAsVirtualAccountGroups -and $this.GroupManagedServiceAccount)
        {
            throw $script:localizedDataSession.ConflictRunAsVirtualAccountGroupsAndGroupManagedServiceAccount
        }

        if ($this.GroupManagedServiceAccount -and $this.RunAsVirtualAccount)
        {
            throw $script:localizedDataSession.ConflictRunAsVirtualAccountAndGroupManagedServiceAccount
        }

        if (-not $this.GroupManagedServiceAccount -and $null -eq $this.RunAsVirtualAccount)
        {
            $this.RunAsVirtualAccount = $true
            Write-Warning -Message $script:localizedDataSession.NotDefinedGMSaAndVirtualAccount
        }

        return $true
    }

    ## Get a PS Session Configuration based on its name
    hidden [object] GetPSSessionConfiguration($Name)
    {
        $winRMService = Get-Service -Name 'WinRM'
        if ($winRMService -and $winRMService.Status -eq 'Running')
        {
            # Temporary disabling Verbose as xxx-PSSessionConfiguration methods verbose messages are useless for DSC debugging
            $verbosePreferenceBackup = $Global:VerbosePreference
            $Global:VerbosePreference = 'SilentlyContinue'
            $psSessionConfiguration = Get-PSSessionConfiguration -Name $Name -ErrorAction SilentlyContinue
            $Global:VerbosePreference = $verbosePreferenceBackup

            if ($psSessionConfiguration)
            {
                return $psSessionConfiguration
            }
            else
            {
                return $null
            }
        }
        else
        {
            Write-Verbose -Message $script:localizedDataSession.WinRMNotRunningGetPsSession
            return $null
        }
    }

    ## Unregister a PS Session Configuration based on its name
    hidden [void] UnregisterPSSessionConfiguration($Name)
    {
        $winRMService = Get-Service -Name 'WinRM'
        if ($winRMService -and $winRMService.Status -eq 'Running')
        {
            # Temporary disabling Verbose as xxx-PSSessionConfiguration methods verbose messages are useless for DSC debugging
            $verbosePreferenceBackup = $Global:VerbosePreference
            $Global:VerbosePreference = 'SilentlyContinue'
            $null = Unregister-PSSessionConfiguration -Name $Name -Force -WarningAction 'SilentlyContinue'
            $Global:VerbosePreference = $verbosePreferenceBackup
        }
        else
        {
            throw ($script:localizedDataSession.WinRMNotRunningUnRegisterPsSession -f $Name)
        }
    }

    ## Register a PS Session Configuration and handle a WinRM hanging situation
    hidden [Void] RegisterPSSessionConfiguration($Name, $Path, $Timeout)
    {
        $winRMService = Get-Service -Name 'WinRM'
        if ($winRMService -and $winRMService.Status -eq 'Running')
        {
            Write-Verbose -Message ($script:localizedDataSession.RegisterPSSessionConfiguration -f $Name,$Path,$Timeout)
            # Register-PSSessionConfiguration has been hanging because the WinRM service is stuck in Stopping state
            # therefore we need to run Register-PSSessionConfiguration within a job to allow us to handle a hanging WinRM service

            # Save the list of services sharing the same process as WinRM in case we have to restart them
            $processId = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name LIKE 'WinRM'" | Select-Object -ExpandProperty ProcessId
            $serviceList = Get-CimInstance -ClassName 'Win32_Service' -Filter "ProcessId=$processId" | Select-Object -ExpandProperty Name
            foreach ($service in $serviceList.clone())
            {
                $dependentServiceList = Get-Service -Name $service | ForEach-Object { $_.DependentServices }
                foreach ($dependentService in $dependentServiceList)
                {
                    if ($dependentService.Status -eq 'Running' -and $serviceList -notcontains $dependentService.Name)
                    {
                        $serviceList += $dependentService.Name
                    }
                }
            }

            if ($Path)
            {
                $registerString = "`$null = Register-PSSessionConfiguration -Name '$Name' -Path '$Path' -NoServiceRestart -Force -ErrorAction 'Stop' -WarningAction 'SilentlyContinue'"
            }
            else
            {
                $registerString = "`$null = Register-PSSessionConfiguration -Name '$Name' -NoServiceRestart -Force -ErrorAction 'Stop' -WarningAction 'SilentlyContinue'"
            }

            $registerScriptBlock = [scriptblock]::Create($registerString)

            if ($Timeout -gt 0)
            {
                $job = Start-Job -ScriptBlock $registerScriptBlock
                Wait-Job -Job $job -Timeout $Timeout
                Receive-Job -Job $job
                Remove-Job -Job $job -Force -ErrorAction 'SilentlyContinue'

                # If WinRM is still Stopping after the job has completed / exceeded $Timeout, force kill the underlying WinRM process
                $winRMService = Get-Service -Name 'WinRM'
                if ($winRMService -and $winRMService.Status -eq 'StopPending')
                {
                    $processId = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name LIKE 'WinRM'" | Select-Object -ExpandProperty ProcessId
                    Write-Verbose -Message ($script:localizedDataSession.ForcingProcessToStop -f $processId)
                    $failureList = @()
                    try
                    {
                        # Kill the process hosting WinRM service
                        Stop-Process -Id $processId -Force
                        Start-Sleep -Seconds 5
                        Write-Verbose -Message ($script:localizedDataSession.RegisterPSSessionConfiguration -f $($serviceList -join ', '))
                        # Then restart all services previously identified
                        foreach ($service in $serviceList)
                        {
                            try
                            {
                                Start-Service -Name $service
                            }
                            catch
                            {
                                $failureList += $script:localizedDataSession.FailureListStartService -f $service
                            }
                        }
                    }
                    catch
                    {
                        $failureList += $script:localizedDataSession.FailureListKillWinRMProcess
                    }

                    if ($failureList)
                    {
                        Write-Verbose -Message ($script:localizedDataSession.FailureListKillWinRMProcess -f $($failureList -join ', '))
                    }
                }
                elseif ($winRMService -and $winRMService.Status -eq 'Stopped')
                {
                    Write-Verbose -Message $script:localizedDataSession.RestartWinRM
                    Start-Service -Name 'WinRM'
                }
            }
            else
            {
                Invoke-Command -ScriptBlock $registerScriptBlock
            }
        }
        else
        {
            throw ($script:localizedDataSession.WinRMNotRunningRegisterPsSession -f $Name)
        }
    }

}
#EndRegion './Classes/1.SessionConfigurationUtility.ps1' 179
#Region './Classes/JeaRoleCapabilities.ps1' 0
<#
    .SYNOPSIS
        The JeaRoleCapabilities DSC resource creates the Role Capabilities file
        in the specified location using the specified settings.

    .DESCRIPTION
        The JeaRoleCapabilities DSC resource creates the Role Capabilities file
        in the specified location using the specified settings.

    .PARAMETER Ensure
        Specifies whether the Role Capabilities file should be created or removed
        (not exist).

    .PARAMETER Path
        Where to store the file.

    .PARAMETER ModulesToImport
        Specifies the modules that are automatically imported into sessions that
        use the role capability file. By default, all of the commands in listed
        modules are visible. When used with VisibleCmdlets or VisibleFunctions,
        the commands visible from the specified modules can be restricted.
        Hashtable with keys ModuleName, ModuleVersion and GUID.

    .PARAMETER VisibleAliases
        Limits the aliases in the session to those aliases specified in the value
        of this parameter, plus any aliases that you define in the AliasDefinition
        parameter. Wildcard characters are supported. By default, all aliases that
        are defined by the Windows PowerShell engine and all aliases that modules
        export are visible in the session.

    .PARAMETER VisibleCmdlets
        Limits the cmdlets in the session to those specified in the value of this
        parameter. Wildcard characters and Module Qualified Names are supported.

    .PARAMETER VisibleFunctions
        Limits the functions in the session to those specified in the value of this
        parameter, plus any functions that you define in the FunctionDefinitions
        parameter. Wildcard characters are supported.

    .PARAMETER VisibleExternalCommands
        Limits the external binaries, scripts and commands that can be executed in
        the session to those specified in the value of this parameter. Wildcard
        characters are supported.

    .PARAMETER VisibleProviders
        Limits the Windows PowerShell providers in the session to those specified
        in the value of this parameter. Wildcard characters are supported.

    .PARAMETER ScriptsToProcess
        Specifies scripts to add to sessions that use the role capability file.

    .PARAMETER AliasDefinitions
        Adds the specified aliases to sessions that use the role capability file.
        Hashtable with keys Name, Value, Description and Options.

    .PARAMETER FunctionDefinitions
        Adds the specified functions to sessions that expose the role capability.
        Hashtable with keys Name, Scriptblock and Options.

    .PARAMETER VariableDefinitions
        Specifies variables to add to sessions that use the role capability file.
        Hashtable with keys Name, Value, Options.

    .PARAMETER EnvironmentVariables
        Specifies the environment variables for sessions that expose this role
        capability file. Hashtable of environment variables.

    .PARAMETER TypesToProcess
        Specifies type files (.ps1xml) to add to sessions that use the role
        capability file. The value of this parameter must be a full or absolute
        path of the type file names.

    .PARAMETER FormatsToProcess
        Specifies the formatting files (.ps1xml) that run in sessions that use the
        role capability file. The value of this parameter must be a full or absolute
        path of the formatting files.

    .PARAMETER Description
        Specifies the assemblies to load into the sessions that use the role
        capability file.

    .PARAMETER AssembliesToLoad
        Description of the role.

    .PARAMETER Reasons
        Reasons of why the resource isn't in desired state.
#>


[DscResource()]
class JeaRoleCapabilities:RoleCapabilitiesUtility
{
    [DscProperty()]
    [Ensure]$Ensure = [Ensure]::Present

    [DscProperty(Key)]
    [string]$Path

    [DscProperty()]
    [string[]]$ModulesToImport

    [DscProperty()]
    [string[]]$VisibleAliases

    [DscProperty()]
    [string[]]$VisibleCmdlets

    [DscProperty()]
    [string[]]$VisibleFunctions

    [DscProperty()]
    [string[]]$VisibleExternalCommands

    [DscProperty()]
    [string[]]$VisibleProviders

    [DscProperty()]
    [string[]]$ScriptsToProcess

    [DscProperty()]
    [string[]]$AliasDefinitions

    [DscProperty()]
    [string[]]$FunctionDefinitions

    [DscProperty()]
    [string[]]$VariableDefinitions

    [DscProperty()]
    [string[]]$EnvironmentVariables

    [DscProperty()]
    [string[]]$TypesToProcess

    [DscProperty()]
    [string[]]$FormatsToProcess

    [DscProperty()]
    [string]$Description

    [DscProperty()]
    [string[]]$AssembliesToLoad

    [DscProperty(NotConfigurable)]
    [Reason[]]$Reasons

    [JeaRoleCapabilities] Get()
    {
        $currentState = [JeaRoleCapabilities]::new()
        $currentState.Path = $this.Path
        if (Test-Path -Path $this.Path)
        {
            $currentStateFile = Import-PowerShellDataFile -Path $this.Path

            'Copyright', 'GUID', 'Author', 'CompanyName' | Foreach-Object {
                $currentStateFile.Remove($_)
            }

            foreach ($property in $currentStateFile.Keys)
            {
                $propertyType = ($this | Get-Member -Name $property -MemberType Property).Definition.Split(' ')[0]
                $currentState.$property = foreach ($propertyValue in $currentStateFile[$property])
                {
                    if ($propertyValue -is [hashtable] -and $propertyType -ne 'hashtable')
                    {
                        if ($propertyValue.ScriptBlock -is [scriptblock])
                        {
                            $code = $propertyValue.ScriptBlock.Ast.Extent.Text
                            $code -match '(?<=\{)(?<Code>((.|\s)*))(?=\})' | Out-Null
                            $propertyValue.ScriptBlock = [scriptblock]::Create($Matches.Code)
                        }

                        ConvertTo-Expression -Object $propertyValue
                    }
                    elseif ($propertyValue -is [hashtable] -and $propertyType -eq 'hashtable')
                    {
                        $propertyValue
                    }
                    else
                    {
                        $propertyValue
                    }
                }
            }
            $currentState.Ensure = [Ensure]::Present

            # Compare current and desired state to add reasons
            $valuesToCheck = $this.psobject.Properties.Name.Where({$_ -notin 'Path','Reasons'})

            $compareState = Compare-DscParameterState `
                -CurrentValues ($currentState | Convert-ObjectToHashtable) `
                -DesiredValues ($this | Convert-ObjectToHashtable) `
                -ValuesToCheck $valuesToCheck | Where-Object {$_.InDesiredState -eq $false }

            $currentState.Reasons = switch ($compareState)
            {
                {$_.Property -eq 'Ensure'}{
                    [Reason]@{
                        Code = '{0}:{0}:{1}' -f $this.GetType(),$_.Property
                        Phrase = $script:localizedDataRole.ReasonEnsure -f $this.Path
                    }
                    continue
                }
                {$_.Property -eq 'Description'}{
                    [Reason]@{
                        Code = '{0}:{0}:{1}' -f $this.GetType(),$_.Property
                        Phrase = $script:localizedDataRole.ReasonDescription -f $this.Description
                    }
                    continue
                }
                default {
                    [Reason]@{
                        Code = '{0}:{0}:{1}' -f $this.GetType(),$_.Property
                        Phrase = $script:localizedDataRole."Reason$($_.Property)"
                    }
                }
            }
        }
        else
        {
            $currentState.Ensure = [Ensure]::Absent
            if ($this.Ensure -eq [Ensure]::Present)
            {
                $currentState.Reasons = [Reason]@{
                    Code = '{0}:{0}:Ensure' -f $this.GetType()
                    Phrase = $script:localizedDataRole.ReasonFileNotFound -f $this.Path
                }
            }
        }

        return $currentState
    }

    [void] Set()
    {
        $invalidConfiguration = $false

        if ($this.Ensure -eq [Ensure]::Present)
        {
            $desiredState = Convert-ObjectToHashtable -Object $this

            foreach ($parameter in $desiredState.Keys.Where( { $desiredState[$_] -match '@{' }))
            {
                $desiredState[$parameter] = Convert-StringToObject -InputString $desiredState[$parameter]
            }

            $desiredState = Sync-Parameter -Command (Get-Command -Name New-PSRoleCapabilityFile) -Parameters $desiredState

            if ($desiredState.ContainsKey('FunctionDefinitions'))
            {
                foreach ($functionDefinitionName in $desiredState['FunctionDefinitions'].Name)
                {
                    if ($functionDefinitionName -notin $desiredState['VisibleFunctions'])
                    {
                        Write-Verbose ($script:localizedDataRole.FunctionDefinedNotVisible -f $functionDefinitionName)
                        Write-Error ($script:localizedDataRole.FunctionDefinedNotVisible -f $functionDefinitionName)
                        $invalidConfiguration = $true
                    }
                }
            }

            if (-not $invalidConfiguration)
            {
                $parentPath = Split-Path -Path $desiredState.Path -Parent
                mkdir -Path $parentPath -Force

                $fPath = $desiredState.Path
                $desiredState.Remove('Path')
                $content = $desiredState | ConvertTo-Expression
                $content | Set-Content -Path $fPath -Force
            }
        }
        elseif ($this.Ensure -eq [Ensure]::Absent -and (Test-Path -Path $this.Path))
        {
            Remove-Item -Path $this.Path -Confirm:$false -Force
        }

    }

    [bool] Test()
    {
        if (-not ($this.ValidatePath()))
        {
            Write-Error -Message $script:localizedDataRole.InvalidPath
            return $false
        }
        if ($this.Ensure -eq [Ensure]::Present -and -not (Test-Path -Path $this.Path))
        {
            return $false
        }
        elseif ($this.Ensure -eq [Ensure]::Present -and (Test-Path -Path $this.Path))
        {

            $currentState = Convert-ObjectToHashtable -Object $this.Get()
            $desiredState = Convert-ObjectToHashtable -Object $this

            $cmdlet = Get-Command -Name New-PSRoleCapabilityFile
            $desiredState = Sync-Parameter -Command $cmdlet -Parameters $desiredState
            $currentState = Sync-Parameter -Command $cmdlet -Parameters $currentState
            $propertiesAsObject = $cmdlet.Parameters.Keys |
                Where-Object { $_ -in $desiredState.Keys } |
                    Where-Object { $cmdlet.Parameters.$_.ParameterType.FullName -in 'System.Collections.IDictionary', 'System.Collections.Hashtable', 'System.Collections.IDictionary[]', 'System.Object[]' }
            foreach ($p in $propertiesAsObject)
            {
                if ($cmdlet.Parameters.$p.ParameterType.FullName -in 'System.Collections.Hashtable', 'System.Collections.IDictionary', 'System.Collections.IDictionary[]', 'System.Object[]')
                {
                    $desiredState."$($p)" = $desiredState."$($p)" | Convert-StringToObject
                    $currentState."$($p)" = $currentState."$($p)" | Convert-StringToObject
                }
            }

            $compare = Test-DscParameterState -CurrentValues $currentState -DesiredValues $desiredState -SortArrayValues -TurnOffTypeChecking -ReverseCheck

            return $compare
        }
        elseif ($this.Ensure -eq [Ensure]::Absent -and (Test-Path -Path $this.Path))
        {
            return $false
        }
        elseif ($this.Ensure -eq [Ensure]::Absent -and -not (Test-Path -Path $this.Path))
        {
            return $true
        }

        return $false
    }
}
#EndRegion './Classes/JeaRoleCapabilities.ps1' 327
#Region './Classes/JeaSessionConfiguration.ps1' 0
<#
    .SYNOPSIS
        The JeaSessionConfiguration DSC resource configures the PowerShell session
        configurations, which define the mapping of users to roles and general
        session security settings.

    .DESCRIPTION
        The JeaSessionConfiguration DSC resource configures the PowerShell session
        configurations, which define the mapping of users to roles and general
        session security settings.

        >**Note:** Scriptblock logging is not enabled by this resource and should
        >be done using the [registry resource](https://docs.microsoft.com/en-us/powershell/dsc/registryresource).


    .PARAMETER Ensure
       The optional state that ensures the endpoint is present or absent. The
       default value is [Ensure]::Present.

    .PARAMETER Name
        The mandatory endpoint name. Uses 'Microsoft.PowerShell' by default.

    .PARAMETER RoleDefinitions
        The role definition map to be used for the endpoint. This should be a string
        that represents the Hashtable used for the RoleDefinitions property in
        `New-PSSessionConfigurationFile`, such as:
        ```
        RoleDefinitions = '@{ Everyone = @{ RoleCapabilities = "BaseJeaCapabilities" } }'
        ```

    .PARAMETER RunAsVirtualAccount
        Run the endpoint under a Virtual Account.

    .PARAMETER RunAsVirtualAccountGroups
        The optional groups to be used when the endpoint is configured to run as a
        Virtual Account

    .PARAMETER GroupManagedServiceAccount
        The optional Group Managed Service Account (GMSA) to use for this endpoint.
        If configured, will disable the default behavior of running as a Virtual
        Account.

    .PARAMETER TranscriptDirectory
        The optional directory for transcripts to be saved to.

    .PARAMETER ScriptsToProcess
        The optional startup script for the endpoint.

    .PARAMETER SessionType
        The optional session type for the endpoint.

    .PARAMETER MountUserDrive
        The optional switch to enable mounting of a restricted user drive.

    .PARAMETER UserDriveMaximumSize
        The optional size of the user drive. The default is 50MB.

    .PARAMETER RequiredGroups
        The optional expression declaring which domain groups (for example,
        two-factor authenticated users) connected users must be members of. This
        should be a string that represents the Hashtable used for the RequiredGroups
        property in `New-PSSessionConfigurationFile`, such as:
        ```
        RequiredGroups = '@{ And = "RequiredGroup1", @{ Or = "OptionalGroup1", "OptionalGroup2" } }'
        ```

    .PARAMETER ModulesToImport
        The optional modules to import when applied to a session. This should be
        a string that represents a string, a Hashtable, or array of strings and/or
        Hashtables.
        ```
        ModulesToImport = "'MyCustomModule', @{ ModuleName = 'MyCustomModule'; ModuleVersion = '1.0.0.0'; GUID = '4d30d5f0-cb16-4898-812d-f20a6c596bdf' }"
        ```

    .PARAMETER VisibleAliases
        The optional aliases to make visible when applied to a session.

    .PARAMETER VisibleCmdlets
        The optional cmdlets to make visible when applied to a session. This should
        be a string that represents a string, a Hashtable, or array of strings and/or
        Hashtables.
        ```
        VisibleCmdlets = "'Invoke-Cmdlet1', @{ Name = 'Invoke-Cmdlet2'; Parameters = @{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, @{ Name = 'Parameter2'; ValidatePattern = 'L*' } }"
        ```

    .PARAMETER VisibleFunctions
        The optional functions to make visible when applied to a session. This should
        be a string that represents a string, a Hashtable, or array of strings and/or
        Hashtables.
        ```
        VisibleFunctions = "'Invoke-Function1', @{ Name = 'Invoke-Function2'; Parameters = @{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, @{ Name = 'Parameter2'; ValidatePattern = 'L*' } }"
        ```

    .PARAMETER VisibleExternalCommands
        The optional external commands (scripts and applications) to make visible when applied to a session.

    .PARAMETER VisibleProviders
        The optional providers to make visible when applied to a session.

    .PARAMETER AliasDefinitions
        The optional aliases to be defined when applied to a session. This should be
        a string that represents a Hashtable or array of Hashtable.
        ```
        AliasDefinitions = "@{ Name = 'Alias1'; Value = 'Invoke-Alias1'}, @{ Name = 'Alias2'; Value = 'Invoke-Alias2'}"
        ```

    .PARAMETER FunctionDefinitions
        The optional functions to define when applied to a session. This should be
        a string that represents a Hashtable or array of Hashtable.
        ```
        FunctionDefinitions = "@{ Name = 'MyFunction'; ScriptBlock = { param($MyInput) $MyInput } }"
        ```

    .PARAMETER VariableDefinitions
        The optional variables to define when applied to a session. This should be
        a string that represents a Hashtable or array of Hashtable.
        ```
        VariableDefinitions = "@{ Name = 'Variable1'; Value = { 'Dynamic' + 'InitialValue' } }, @{ Name = 'Variable2'; Value = 'StaticInitialValue' }"
        ```

    .PARAMETER EnvironmentVariables
        The optional environment variables to define when applied to a session.
        This should be a string that represents a Hashtable.
        ```
        EnvironmentVariables = "@{ Variable1 = 'Value1'; Variable2 = 'Value2' }"
        ```

    .PARAMETER TypesToProcess
        The optional type files (.ps1xml) to load when applied to a session.

    .PARAMETER FormatsToProcess
        The optional format files (.ps1xml) to load when applied to a session.

    .PARAMETER AssembliesToLoad
        The optional assemblies to load when applied to a session.

    .PARAMETER LanguageMode
        The optional language mode to load. Can be `'NoLanguage'` (recommended),
        `'RestrictedLanguage'`, `'ConstrainedLanguage'`, or `'FullLanguage'` (Default).

    .PARAMETER ExecutionPolicy
        The optional ExecutionPolicy. Execution policy to apply when applied to a
        session. `'Unrestricted'`, `'RemoteSigned'`, `'AllSigned'`, `'Restricted'`,
        `'Default'`, `'Bypass'`, `'Undefined'`.

    .PARAMETER HungRegistrationTimeout
        The optional number of seconds to wait for registering the endpoint to complete.
        Use `0` for no timeout. Default value is `10`.

    .PARAMETER Reasons
        Contains the not compliant properties detected in Get() method.
#>


[DscResource()]
class JeaSessionConfiguration:SessionConfigurationUtility
{
    [DscProperty()]
    [Ensure] $Ensure = [Ensure]::Present

    [DscProperty(Key)]
    [string] $Name = 'Microsoft.PowerShell'

    [Dscproperty()]
    [string] $RoleDefinitions

    [DscProperty()]
    [nullable[bool]] $RunAsVirtualAccount

    [DscProperty()]
    [string[]] $RunAsVirtualAccountGroups

    [DscProperty()]
    [string] $GroupManagedServiceAccount

    [DscProperty()]
    [string] $TranscriptDirectory

    [DscProperty()]
    [string[]] $ScriptsToProcess

    [DscProperty()]
    [string] $SessionType

    [Dscproperty()]
    [bool] $MountUserDrive

    [Dscproperty()]
    [long] $UserDriveMaximumSize

    [Dscproperty()]
    [string[]] $RequiredGroups

    [Dscproperty()]
    [string[]] $ModulesToImport

    [Dscproperty()]
    [string[]] $VisibleAliases

    [Dscproperty()]
    [string[]] $VisibleCmdlets

    [Dscproperty()]
    [string[]] $VisibleFunctions

    [Dscproperty()]
    [string[]] $VisibleExternalCommands

    [Dscproperty()]
    [string[]] $VisibleProviders

    [Dscproperty()]
    [string[]] $AliasDefinitions

    [Dscproperty()]
    [string[]] $FunctionDefinitions

    [Dscproperty()]
    [string] $VariableDefinitions

    [Dscproperty()]
    [string] $EnvironmentVariables

    [Dscproperty()]
    [string[]] $TypesToProcess

    [Dscproperty()]
    [string[]] $FormatsToProcess

    [Dscproperty()]
    [string[]] $AssembliesToLoad

    [Dscproperty()]
    [string] $LanguageMode

    [Dscproperty()]
    [string] $ExecutionPolicy

    [Dscproperty()]
    [int] $HungRegistrationTimeout = 10

    [DscProperty(NotConfigurable)]
    [Reason[]]$Reasons

    [void] Set()
    {
        $ErrorActionPreference = 'Stop'

        $this.TestParameters()

        $psscPath = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName() + '.pssc')
        Write-Verbose -Message ($script:localizedDataSession.StoringPSSessionConfigurationFile -f $psscPath)
        $desiredState = Convert-ObjectToHashtable -Object $this
        $desiredState.Add('Path', $psscPath)

        if ($this.Ensure -eq [Ensure]::Present)
        {
            foreach ($parameter in $desiredState.Keys.Where( { $desiredState[$_] -match '@{' }))
            {
                $desiredState[$parameter] = Convert-StringToObject -InputString $desiredState[$parameter]
            }
        }

        ## Register the endpoint
        try
        {
            ## If we are replacing Microsoft.PowerShell, create a 'break the glass' endpoint
            if ($this.Name -eq 'Microsoft.PowerShell')
            {
                $breakTheGlassName = 'Microsoft.PowerShell.Restricted'
                if (-not ($this.GetPSSessionConfiguration($breakTheGlassName)))
                {
                    $this.RegisterPSSessionConfiguration($breakTheGlassName, $null, $this.HungRegistrationTimeout)
                }
            }

            ## Remove the previous one, if any.
            if ($this.GetPSSessionConfiguration($this.Name))
            {
                $this.UnregisterPSSessionConfiguration($this.Name)
            }

            if ($this.Ensure -eq [Ensure]::Present)
            {
                ## Create the configuration file
                #New-PSSessionConfigurationFile @configurationFileArguments
                $desiredState = Sync-Parameter -Command (Get-Command -Name New-PSSessionConfigurationFile) -Parameters $desiredState
                New-PSSessionConfigurationFile @desiredState

                ## Register the configuration file
                $this.RegisterPSSessionConfiguration($this.Name, $psscPath, $this.HungRegistrationTimeout)
            }
        }
        catch
        {
            Write-Error -ErrorRecord $_
        }
        finally
        {
            if (Test-Path $psscPath)
            {
                Remove-Item $psscPath
            }
        }
    }

    # Tests if the resource is in the desired state.
    [bool] Test()
    {
        $this.TestParameters()

        $currentState = Convert-ObjectToHashtable -Object $this.Get()
        $desiredState = Convert-ObjectToHashtable -Object $this

        # short-circuit if the resource is not present and is not supposed to be present
        if ($currentState.Ensure -ne $desiredState.Ensure)
        {
            Write-Verbose -Message ($script:localizedDataSession.FailureListKillWinRMProcess -f $currentState.Name,$desiredState.Ensure,$currentState.Ensure )
            return $false
        }
        if ($this.Ensure -eq [Ensure]::Absent)
        {
            if ($currentState.Ensure -eq [Ensure]::Absent)
            {
                return $true
            }

            Write-Verbose ($script:localizedDataSession.PSSessionConfigurationNamePresent -f $currentState.Name)
            return $false
        }

        $cmdlet = Get-Command -Name New-PSSessionConfigurationFile
        $desiredState = Sync-Parameter -Command $cmdlet -Parameters $desiredState
        $currentState = Sync-Parameter -Command $cmdlet -Parameters $currentState
        $propertiesAsObject = $cmdlet.Parameters.Keys |
        Where-Object { $_ -in $desiredState.Keys } |
        Where-Object { $cmdlet.Parameters.$_.ParameterType.FullName -in 'System.Collections.IDictionary', 'System.Collections.Hashtable', 'System.Collections.IDictionary[]', 'System.Object[]' }
        foreach ($p in $propertiesAsObject)
        {
            if ($cmdlet.Parameters.$p.ParameterType.FullName -in 'System.Collections.Hashtable', 'System.Collections.IDictionary', 'System.Collections.IDictionary[]', 'System.Object[]')
            {
                $desiredState."$($p)" = $desiredState."$($p)" | Convert-StringToObject
                $currentState."$($p)" = $currentState."$($p)" | Convert-StringToObject

            }
        }

        $compare = Test-DscParameterState -CurrentValues $currentState -DesiredValues $desiredState -TurnOffTypeChecking -SortArrayValues -ReverseCheck

        return $compare
    }

    # Gets the resource's current state.
    [JeaSessionConfiguration] Get()
    {
        $currentState = New-Object JeaSessionConfiguration
        $CurrentState.Name = $this.Name
        $CurrentState.Ensure = [Ensure]::Present

        $sessionConfiguration = $this.GetPSSessionConfiguration($this.Name)
        if (-not $sessionConfiguration -or -not $sessionConfiguration.ConfigFilePath)
        {
            $currentState.Ensure = [Ensure]::Absent
            if ($this.Ensure -eq [Ensure]::Present)
            {
                $currentState.Reasons = [Reason]@{
                    Code = '{0}:{0}:Ensure' -f $this.GetType()
                    Phrase = $script:localizedDataSession.ReasonEpSessionNotFound -f $this.Name
                }
            }

            return $currentState
        }

        $configFile = Import-PowerShellDataFile $sessionConfiguration.ConfigFilePath

        'Copyright', 'GUID', 'Author', 'CompanyName', 'SchemaVersion' | Foreach-Object {
            $configFile.Remove($_)
        }

        foreach ($property in $configFile.Keys)
        {
            $propertyType = ($this | Get-Member -Name $property -MemberType Property).Definition.Split(' ')[0]
            $currentState.$property = foreach ($propertyValue in $configFile[$property])
            {
                if ($propertyValue -is [hashtable] -and $propertyType -ne 'hashtable')
                {
                    if ($propertyValue.ScriptBlock -is [scriptblock])
                    {
                        $code = $propertyValue.ScriptBlock.Ast.Extent.Text
                        $code -match '(?<=\{\{)(?<Code>((.|\s)*))(?=\}\})' | Out-Null
                        $propertyValue.ScriptBlock = [scriptblock]::Create($Matches.Code)
                    }

                    ConvertTo-Expression -Object $propertyValue
                }
                elseif ($propertyValue -is [hashtable] -and $propertyType -eq 'hashtable')
                {
                    $propertyValue
                }
                else
                {
                    $propertyValue
                }
            }
        }

        # Compare current and desired state to add reasons
        $valuesToCheck = $this.psobject.Properties.Name.Where({$_ -notin 'Name','Reasons'})

        $compareState = Compare-DscParameterState `
            -CurrentValues ($currentState | Convert-ObjectToHashtable) `
            -DesiredValues ($this | Convert-ObjectToHashtable) `
            -ValuesToCheck $valuesToCheck | Where-Object {$_.InDesiredState -eq $false }

        $currentState.Reasons = switch ($compareState)
        {
            {$_.Property -eq 'Ensure'}{
                [Reason]@{
                    Code = '{0}:{0}:{1}' -f $this.GetType(),$_.Property
                    Phrase = $script:localizedDataSession.ReasonEnsure -f $this.Path
                }
                continue
            }
            {$_.Property -eq 'Description'}{
                [Reason]@{
                    Code = '{0}:{0}:{1}' -f $this.GetType(),$_.Property
                    Phrase = $script:localizedDataSession.ReasonDescription -f $this.Description
                }
                continue
            }
            default {
                [Reason]@{
                    Code = '{0}:{0}:{1}' -f $this.GetType(),$_.Property
                    Phrase = $script:localizedDataSession."Reason$($_.Property)"
                }
            }
        }

        return $currentState
    }
}
#EndRegion './Classes/JeaSessionConfiguration.ps1' 442