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 |