DSCResources/MSFT_xJeaToolkit/MSFT_xJeaToolkit.psm1

#requires -version 5
#region HelperFunctions
# Internal function to throw terminating error with specified errroCategory, errorId and errorMessage
function New-TerminatingError
{
    param
    (
        [Parameter(Mandatory)]
        [String]$errorId,
        
        [Parameter(Mandatory)]
        [String]$ErrorMessage,

        [Parameter(Mandatory)]
        [System.Management.Automation.ErrorCategory]$errorCategory
    )
    
    $exception   = New-Object System.InvalidOperationException $errorMessage 
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null
    throw $errorRecord
}


Add-Type @'
    namespace Jea
    {
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
    public class Parameter
    {
        public string ValidatePattern;
        public string ValidateSet;
        public string ParameterType;
        public string Mandatory;
    }
    public class Proxy
    {
        public string Module;
        public string Name;
        public Hashtable Parameter;
        public Proxy()
        {
            Parameter = new Hashtable(System.StringComparer.InvariantCultureIgnoreCase);
        }
    }
    }
'@


#region Jea*Dir Utilities
function Get-JeaDir
{
        Join-Path $env:ProgramFiles 'Jea' 
}
function Get-JeaToolKitDir
{
      Join-Path (Get-JeaDir) 'Toolkit'
}
function Get-JeaUtilDir
{
      Join-Path (Get-JeaDir) 'Util'
}
function Get-JeaStartupScriptDir
{
      Join-Path (Get-JeaDir) 'StartupScript'
}
function Get-JeaActivityDir
{
      Join-Path (Get-JeaDir) 'Activity'
}
function Get-JeaMotdDir
{
      Join-Path (Get-JeaDir) 'Motd'
}
function Assert-JeaDirectory
{
    $ToolkitDir       = Get-JeaToolKitDir
    $UtilDir          = Get-JeaUtilDir
    $StartupScriptDir = Get-JeaStartupScriptDir
    $ActivityDir      = Get-JeaActivityDir
    $MotdDir          = Get-JeaMotdDir
    foreach ($dir in $ToolKitDir, $UtilDir, $StartupScriptDir, $ActivityDir, $MotdDir)
    {
        if (!(Test-Path $Dir))
        {
            Write-Verbose -Message "New [JeaDirectory]$Dir"
            mkdir $Dir -Force
        }
    }
    $initfile = Join-path $UtilDir 'Initialize-Toolkit.ps1'
    if (!(test-path $initfile))
    {
        $sourcePS1 = Resolve-Path (Join-Path $PSScriptRoot '..\..\Util\Initialize-ToolKit.ps1')
        Copy-Item $sourcePS1 -Destination $initfile -Verbose
    }
}

#endregion

#JeaToolkitHelper.ps1

function ConvertTo-CSpec
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0)]
        $In
    )

    Begin
    {
    }
    Process
    {
        new-object psobject -Property @{
            Module          = $In.module
            Name            = $In.Name
            Parameter       = $In.Parameter
            ValidateSet     = $In.ValidateSet
            ValidatePattern = $In.ValidatePattern
            ParameterType   = $In.ParameterType
            Mandatory       = $In.Mandatory
        }
    }
    End
    {
    }
}
function ConvertTo-CommandsToGenerate
{
param(
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   Position=0)]
        [ValidateNotNull()]
        $CSpec
)
    Begin
    {
        $CommandsToGenerate = @{}
    }
    Process
    {
        if (!$CSpec.Name)
        {
            $CSpec.name = '*'
        }
        foreach ($CmdInfo in Get-Command -Module $CSpec.Module -Name $CSpec.Name -CommandType Function,Cmdlet)
        {
            if (!$CSpec.Parameter)
            {
                $CSpec.Parameter = '*'
            }
            #Names may specify specific commands or have wildcards to specify sets of commands
            if (!$CommandsToGenerate.$($CmdInfo.Name) -and $CSpec.Parameter)
            {
                $CommandsToGenerate.$($CmdInfo.Name) = New-Object Jea.Proxy            
            }
            $proxy = $CommandsToGenerate.$($CmdInfo.Name)

            if ($CSpec.Parameter -eq '*')
            {
                foreach ($ParameterName in $CmdInfo.Parameters.Keys)
                {
                    $p = $proxy.parameter.($ParameterName)
                    if (!$p)
                    {
                        $p = new-object Jea.Parameter
                        $proxy.parameter.Add($ParameterName, $p)
                    }              
                }  
            }
            else
            {
                $p = $proxy.parameter.$($CSpec.Parameter)
                if (!$p)
                {
                    $p = new-object Jea.Parameter
                    $proxy.parameter.Add($CSpec.Parameter.ToLower(), $p)
                }
                if ($CSpec.ValidateSet)
                {
                    $p.ValidateSet =$CSpec.ValidateSet.Tolower()
                }            
                if ($CSpec.ValidatePattern)
                {
                    $p.ValidatePattern = $CSpec.ValidatePattern
                }            
                if ($CSpec.ParameterType)
                {
                    $p.ParameterType = $CSpec.ParameterType
                }            
                if ($CSpec.Mandatory)
                {
                    $p.Mandatory = $CSpec.Mandatory
                }
            }
        }#foreach
    }
    End
    {
        return $CommandsToGenerate 
    }
}
function New-ToolKitPremable
{
    param
    (
        [Parameter(Mandatory)]
        [String]$Name,

        [String]
        $CommandSpecs,

        [System.String[]]
        $Applications
    )
        # Now we generate the File
@"
<#
This is a auto-generated module containing proxy cmdlets.
Generated At: $(Get-date)
Generated On: $(hostname)
Generated By: $($env:UserDomain + '\' + $env:UserName)
 
#region OrginalCSVFile
******************** START Original Source file ***********************
$CommandSpecs
******************** END Original Source file ***********************
#endRegion
 
#>
 
$(
    $list = @()
    foreach ($a in $Applications)
    {
        $list += """$a"""
    }
    if ($list.count)
    {
'$ExportedApplications = ' + ($list -join ',')
    }
    )
"@

}
function ConvertTo-ProxyFunctions
{
    [CmdletBinding()]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0)]
        $CmdName

    )

    Begin
    {
        # Proxy Modules are typically going to be used in constrained runspaces where best
        # practice will be to turn of ModuleAutoloading so the proxy needs to load whatever
        # modules it will proxy
        $modulesToImport = @{'Microsoft.PowerShell.Core'=1 }
        $exportCmdlet = @()
    }
    Process
    {
        $Cmd = Get-Command -Name $CmdName -CommandType Cmdlet,Function -ErrorAction Stop
        if (!$cmd)
        {
            Throw "No such Object [$CmdName :$CommandType]"
        }
        <#
        Need to do some flavor of analsys of MANDATORY PARAMETERS in SETs
        #>

        foreach ($c in $cmd)
        {
            if ($c.Module) {import-module -Name $c.module -ErrorAction Ignore -Verbose:0}
            if ($c.CommandType -eq 'function')
            {
                rename-item function:$($c.Name) $($c.Name + '-Original') 
                $c = Get-command -name ($cmdName + '-Original') -CommandType Function -ErrorAction Stop
            }
            $Parameter = $CommandsToGenerate.$CmdName.Parameter.Keys
            $MetaData = New-Object System.Management.Automation.CommandMetaData $c
            $metaData.Name = $CmdName

            foreach ($p in @($MetaData.Parameters.Keys))
            {
                $p = $p.Tolower()
                if ($p -notin $Parameter)
                {
                    $null = $MetaData.Parameters.Remove($p)
                }
                else
                {
                    $v = $CommandsToGenerate.$CmdName.Parameter.$p.ValidateSet
                    if ($v)
                    {
                        $MetaData.Parameters.$p.attributes.Add( $(New-Object System.Management.Automation.ValidateSetAttribute $($v -split ';')))                        
                    }
                    $v = $CommandsToGenerate.$CmdName.Parameter.$p.ValidatePattern
                    if ($v)
                    {
                        $MetaData.Parameters.$p.attributes.Add( $(New-Object System.Management.Automation.ValidatePatternAttribute $v))                        
                    }
                    $v = $CommandsToGenerate.$CmdName.Parameter.$p.ParameterType
                    if ($v)
                    {
                        $type = [System.AppDomain]::CurrentDomain.GetAssemblies().GetTypes() | where {$_.fullname -match $ParameterType}
                        if ($type)
                        {
                            $MetaData.Parameters.$p.ParameterType = $type[0].FullName
                        }
                    }
                    $v = $CommandsToGenerate.$CmdName.Parameter.$p.Mandatory
                    if ($v)
                    {
                        foreach($ps in $MetaData.Parameters.$p.Parametersets.Keys)
                        {
                            $MetaData.Parameters.$p.Parametersets.$PS.IsMandatory=$true
                        }
                    }
                }#end
            }#foreach

            if ($c.Module)
            {
                $RealModule = $c.module
                if (!$modulesToImport.$RealModule)
                {
                    $modulesToImport.$RealModule = 'Already imported'
@"
Import-Module $($RealModule)
"@

                }

            }
@"
 
#region $cmdname
$(
if ($c.CommandType -eq 'function')
{
"rename-item function:$cmdName $($cmdName+ '-Original')"
}
)
function $cmdName
{
"@

        [System.Management.Automation.ProxyCommand]::create($MetaData) 

@"
} # $cmdName
#endregion
 
 
"@

            $exportCmdlet += $CmdName
            
        } #foreach $cmd
        
    }
    End
    {
@"
Export-ModuleMember -Function $(($exportCmdlet | sort -Unique) -join ',')
#EOF
"@


    }
}
<#
.Synopsis
   Use a CSV-formated string to drive creation of a JeaProxy module
.DESCRIPTION
   JeaProxy modules provide fine grain control over what a user can invoke.
   It accomplishes this by manipulating the command parsing information and
   generating a proxy function. This process is driven off a CommandSpecs which
   is a CSV formated string using the schema:
    Module,Name,Parameter,ValidateSet,ValidatePattern,ParameterType
     
    If only a name is specified, the cmdlet is surfaced in whole
    If a Name and a parameter are specified, then only those parameters will be
        surfaced for that cmdlet. Since it is a CSV format, only one parameter
        can be specified on a line so we need to process all the lines and
        consolidate the information before we create the proxies.
    If a Name, a parameter and a Validate is specified, we add a VALIDATESET
        attribute with the values of the Validate field.
        The values need to be seperated with a ';'.
 
    Applications can also be specified. Applications are non-PowerShell
    native executables (e.g. Ping.exe or IPconfig.exe)
.EXAMPLE
    Export-JeaProxy -Name GeneralAdmin -Applications "ping.exe","ipconfig.exe" -CommandSpecs @`
Module,Name,Parameter,ValidateSet,ValidatePattern,ParameterType
,Get-Process
,Stop-Process,Name,calc;notepad
,get-service
,Stop-Service,Name,,^SQL
`@
.OUTPUTS
    Two files are created in the ($env:ProgramFiles)\Jea\Toolkit directory
    1) $Name-Toolkit.psm1 # The proxy module
    2) $Name-CommandSpecs.csv # For diagnostics
.NOTES
   General notes
#>

function Export-JeaProxy
{
    param
    (
        [Parameter(Mandatory)]
        [String]$Name,

        [String]
        $CommandSpecs,

        [System.String[]]
        $Applications
    )

    $CommandSpecs >  (Join-Path (Get-JeaToolKitDir) "$($Name)-CommandSpecs.csv")
    Write-Verbose "New [JeaDirectory.CSV]$($Name)-CommandSpecs.csv"

    $CommandsToGenerate = $CommandSpecs.ToLower() | ConvertFrom-Csv | ConvertTo-CSPec | ConvertTo-CommandsToGenerate
    $toolkit =  (Join-Path (Get-JeaToolKitDir) "$($Name)-ToolKit.psm1")
    New-ToolKitPremable @PSBoundParameters > $toolkit
    $CommandsToGenerate.Keys |Sort {($_ -split '-')[1]},{($_ -split '-')[0]} | ConvertTo-ProxyFunctions >> $toolkit
    Write-Verbose "New [JeaDirectory.Module]$toolkit"
    
} #Export-JeaProxy

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

    try
    {

        $modulePath = (Join-Path (Join-Path (Get-JeaDir) 'Toolkit') "$($name)-ToolKit.psm1")
        Write-Verbose "Importing [JeaToolKit]$modulePath"
        Import-Module $modulePath  -Force  -DisableNameChecking -Verbose:0

        $module = Microsoft.PowerShell.Core\Get-Module -Name "$name-Toolkit"
        Write-Verbose "Module= $module "
        $returnValue = @{
            Name = [System.String]"$name-Toolkit"
            CommandSpecs = [String]$(
                $csvPath = (Join-Path (Join-Path (Get-JeaDir) 'Toolkit') "$($name)-CommandSpecs.csv")
                if (test-path $csvPath)
                {
                    Microsoft.PowerShell.Management\get-content -Path $csvPath -Raw
                }
                )
            Applications = [System.String[]] $(
                   &$Module{$ExportedApplications}
                )
            Ensure = [System.String]'Present'

        }
        Microsoft.PowerShell.Core\remove-module "$Name-Toolkit" -Verbose:0
        $returnValue
    }catch
    {
        write-verbose "ERROR: $($_|fl * -force|out-string)"
        throw
    }
}


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

        [String]
        $CommandSpecs,

        [System.String[]]
        $Applications,

        [System.String[]]
        $ScriptDirectory,

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

    try
    {       
        write-Verbose "$((get-date).GetDateTimeFormats()[112]) Start Setting [Toolkit]$Name" 
        Assert-JeaDirectory
        Export-JEAProxy -Name $name -CommandSpecs $CommandSpecs -Applications $Applications
    }
    catch 
    {
        Write-Verbose $($_ |fl * -force |Out-String)
        $msg = "$($_.exception) `nTarget: $($_.TargetObject)`n$($_.ScriptStacktrace)"
        New-TerminatingError -errorId $($_.FullqualifiedId + 'JeaToolKitSet') -ErrorMessage $msg -errorCategory OperationStopped
    }finally
    {
        write-Verbose "$((get-date).GetDateTimeFormats()[112]) Done Setting [Toolkit]$Name" 
    }
    return $true
}


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

        [String]
        $CommandSpecs,

        [System.String[]]
        $Applications,

        [System.String[]]
        $ScriptDirectory,

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

    Write-Verbose "Test [JeaToolkit]$name"

    $toolkit     = Join-Path (Get-JeaToolKitDir) "$($Name)-ToolKit.psm1"
    $CommandSpec = Join-Path (Get-JeaToolKitDir) "$($Name)-CommandSpecs.csv"
    $Exists = ((Test-Path $Toolkit) -AND (Test-Path $CommandSpec))
    if ($Exists) { Write-Verbose " [JeaToolkit]$name Present"}
    else         { Write-Verbose " [JeaToolkit]$name Absent"}


    if (($Ensure -eq 'Present' -and !$exists) -or
        ($Ensure -eq 'Absent'  -And  $exists))
    {
       return $false
    }
    if ($Ensure -eq 'Present' -and ($CommandSpecs.Trim() -ne (Get-Content $CommandSpec -Raw).Trim()))
    {
        return $false
    }
    return $true
} #Test-TargetResource


Export-ModuleMember -Function *-TargetResource