DSCClassResources/JeaRoleCapabilities/JeaRoleCapabilities.psm1

using namespace System.Management.Automation.Language
enum Ensure {
    Present
    Absent
}

[DscResource()]
class JeaRoleCapabilities {

    [DscProperty()]
    [Ensure]$Ensure = [Ensure]::Present

    # Where to store the file.
    [DscProperty(Key)]
    [String]$Path

    # 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.
    [DscProperty()]
    [string[]]$ModulesToImport

    # 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.
    [DscProperty()]
    [String[]]$VisibleAliases

    # Limits the cmdlets in the session to those specified in the value of this parameter.
    # Wildcard characters and Module Qualified Names are supported.
    [DscProperty()]
    [String[]]$VisibleCmdlets

    # 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.
    [DscProperty()]
    [String[]]$VisibleFunctions

    # 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.
    [DscProperty()]
    [String[]]$VisibleExternalCommands

    # Limits the Windows PowerShell providers in the session to those specified in the value of this parameter.
    # Wildcard characters are supported.
    [DscProperty()]
    [String[]]$VisibleProviders

    # Specifies scripts to add to sessions that use the role capability file.
    [DscProperty()]
    [string[]]$ScriptsToProcess

    # Adds the specified aliases to sessions that use the role capability file.
    # Hashtable with keys Name, Value, Description and Options.
    [DscProperty()]
    [string[]]$AliasDefinitions

    # Adds the specified functions to sessions that expose the role capability.
    # Hashtable with keys Name, Scriptblock and Options.
    [DscProperty()]
    [string[]]$FunctionDefinitions

    # Specifies variables to add to sessions that use the role capability file.
    # Hashtable with keys Name, Value, Options.
    [DscProperty()]
    [string[]]$VariableDefinitions

    # Specifies the environment variables for sessions that expose this role capability file.
    # Hashtable of environment variables.
    [DscProperty()]
    [string[]]$EnvironmentVariables

    # 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.
    [DscProperty()]
    [string[]]$TypesToProcess

    # 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.
    [DscProperty()]
    [String[]]$FormatsToProcess

    # Specifies the assemblies to load into the sessions that use the role capability file.
    [DscProperty()]
    [String[]]$AssembliesToLoad

    Hidden [Boolean] ValidatePath() {
        $FileObject = [System.IO.FileInfo]::new($this.Path)
        Write-Verbose -Message "Validating Path: $($FileObject.Fullname)"
        Write-Verbose -Message "Checking file extension is psrc for: $($FileObject.Fullname)"
        if ($FileObject.Extension -ne '.psrc') {
            Write-Verbose -Message "Doesn't have psrc extension for: $($FileObject.Fullname)"
            return $false
        }

        Write-Verbose -Message "Checking parent forlder is RoleCapabilities for: $($FileObject.Fullname)"
        if ($FileObject.Directory.Name -ne 'RoleCapabilities') {
            Write-Verbose -Message "Parent folder isn't RoleCapabilities for: $($FileObject.Fullname)"
            return $false
        }

        Write-Verbose -Message "Checking Folder is in PSModulePath is psrc for: $($FileObject.Fullname)"
        $PSModulePathRegexPattern = (([Regex]::Escape($env:PSModulePath)).TrimStart(';').TrimEnd(';') -replace ';', '|')
        if ($FileObject.FullName -notmatch $PSModulePathRegexPattern) {
            Write-Verbose -Message "Path isn't part of PSModulePath, valid values are:"
            foreach ($path in $env:PSModulePath -split ';') {
                Write-Verbose -Message "$Path"
            }
            return $false
        }

        Write-Verbose -Message "Path is a valid psrc path. Returning true."
        return $true
    }

    [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) {
                $CurrentState.$Property = $CurrentStateFile[$Property]
            }
            $CurrentState.Ensure = [Ensure]::Present
        }
        else {
            $CurrentState.Ensure = [Ensure]::Absent
        }
        return $CurrentState
    }

    [void] Set() {
        if ($this.Ensure -eq [Ensure]::Present) {

            $Parameters = Convert-ObjectToHashtable($this)
            $Parameters.Remove('Ensure')

            Foreach ($Parameter in $Parameters.Keys.Where( {$Parameters[$_] -match '@{'})) {
                $Parameters[$Parameter] = Convert-StringToObject -InputString $Parameters[$Parameter]
            }

            $InvalidConfiguration = $false

            if ($Parameters.ContainsKey('FunctionDefinitions')) {
                foreach ($FunctionDefName in $Parameters['FunctionDefinitions'].Name) {
                    if ($FunctionDefName -notin $Parameters['VisibleFunctions']) {
                        Write-Error -Message "Function defined but not visible to Role Configuration: $FunctionDefName"
                        $InvalidConfiguration = $true
                    }
                }
            }
            if (-not $InvalidConfiguration) {
                $null = New-Item -Path $this.Path -ItemType File -Force

                New-PSRoleCapabilityFile @Parameters
            }
        }
        elseif ($this.Ensure -eq [Ensure]::Absent -and (Test-Path -Path $this.Path)) {
            Remove-Item -Path $this.Path -Confirm:$False
        }

    }

    [bool] Test() {
        if (-not ($this.ValidatePath())) {
            Write-Error -Message "Invalid path specified. It must point to a Module folder, be a psrc file and the parent folder must be called RoleCapabilities"
            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()

            $Parameters = Convert-ObjectToHashtable -Object $this
            $Compare = Compare-JeaConfiguration -ReferenceObject $CurrentState -DifferenceObject $Parameters

            if ($null -eq $Compare) {
                return $true
            }
            else {
                return $false
            }
        }
        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
    }
}

function Convert-StringToObject {
    [cmdletbinding()]
    param (
        [string[]]$InputString
    )

    $ParseErrors = @()
    $FakeCommand = "Totally-NotACmdlet -FakeParameter $InputString"
    $AST = [Parser]::ParseInput($FakeCommand, [ref]$null, [ref]$ParseErrors)
    if (-not $ParseErrors) {
        # Use Ast.Find() to locate the CommandAst parsed from our fake command
        $CmdAst = $AST.Find( {param($ChildAst) $ChildAst -is [CommandAst]}, $false)
        # Grab the user-supplied arguments (index 0 is the command name, 1 is our fake parameter)
        $AllArgumentAst = $CmdAst.CommandElements.Where( {$_ -isnot [CommandParameterAst] -and $_.Value -ne 'Totally-NotACmdlet'})
        foreach ($ArgumentAst in $AllArgumentAst) {
            if ($ArgumentAst -is [ArrayLiteralAst]) {
                # Argument was a list
                foreach ($Element in $ArgumentAst.Elements) {
                    if ($Element.StaticType.Name -eq 'String') {
                        $Element.value
                    }
                    if ($Element.StaticType.Name -eq 'Hashtable') {
                        [Hashtable]$Element.SafeGetValue()
                    }
                }
            }
            else {
                if ($ArgumentAst -is [HashtableAst]) {
                    $ht = [Hashtable]$ArgumentAst.SafeGetValue()
                    for ($i = 1; $i -lt $ht.Keys.Count; $i++) {
                        $value = $ht[([array]$ht.Keys)[$i]]
                        if ($value -is [scriptblock]) {

                            $scriptBlockText = $value.Ast.Extent.Text

                            if ($scriptBlockText[$value.Ast.Extent.StartOffset] -eq '{' -and $scriptBlockText[$endOffset - 1] -eq '}') {

                                $scriptBlockText = $scriptBlockText.Substring(0, $scriptBlockText.Length - 1)
                                $scriptBlockText = $scriptBlockText.Substring(1, $scriptBlockText.Length - 1)
                            }

                            $ht[([array]$ht.Keys)[$i]] = [scriptblock]::Create($scriptBlockText)
                        }
                    }
                    $ht
                }
                elseif ($ArgumentAst -is [StringConstantExpressionAst]) {
                    $ArgumentAst.Value
                }
                else {
                    Write-Error -Message "Input was not a valid hashtable, string or collection of both. Please check the contents and try again."
                }
            }
        }
    }
}