DSCResources/Library/JeaProxy.psm1
Import-Module $PSScriptRoot\..\Library\Helper.psm1 Import-Module $PSScriptRoot\..\Library\JeaDir.psm1 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); } } } '@ 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 |