Skip to content

Instantly share code, notes, and snippets.

@SeeminglyScience
Created April 17, 2017 02:07
Show Gist options
  • Save SeeminglyScience/3372a8ef1ac8324f12bb95f14d151580 to your computer and use it in GitHub Desktop.
Save SeeminglyScience/3372a8ef1ac8324f12bb95f14d151580 to your computer and use it in GitHub Desktop.
Proof of concept for forcing external type definitions to export in a module.
using namespace System.Management.Automation.Language
using namespace System.Collections.Generic
using namespace System.Reflection
# The current contents of the psm1 file would go here, including dot sourcing the class definition
# files normally and exporting module members.
# The rest can most likely be loaded into a function but I haven't tested it yet. It could also
# use some cleaning up.
$usingStatements = [List[UsingStatementAst]]::new()
$text = Get-ChildItem $PSScriptRoot\Classes\*.ps1 | ForEach-Object {
# Pull out using statements and type definitions
$ast = [Parser]::ParseInput((Get-Content $PSItem.FullName -Raw), [ref]$null, [ref]$null)
$ast.UsingStatements.ForEach{ $usingStatements.Add($PSItem) }
$ast.FindAll({ $args[0] -is [TypeDefinitionAst] }, $true).Extent.Text
}
# Join each file along with unique using statements and create a dynamic module.
$text = (@($usingStatements.Extent.Text | Sort-Object -Unique) + $text) -join [Environment]::NewLine
$module = New-Module -ScriptBlock ([scriptblock]::Create($text))
# This might not be neccessary, it seems the session state is mainly determined by the keeper.
$module.SessionState = $ExecutionContext.SessionState
# Grab the current SessionStateInternal.
$internal = $ExecutionContext.SessionState.GetType().
GetProperty('Internal', [BindingFlags]'Instance, NonPublic').
GetValue($ExecutionContext.SessionState)
# Each dynamic type has a non public type called ClassName_<staticHelper>. This contains
# wrappers for static methods and a SessionStateKeeper.
$module.ImplementingAssembly.DefinedTypes |
Where-Object IsPublic -eq $false |
ForEach-Object {
# Get the session state mapping from the keeper.
$keeper = $PSItem.
GetField('__sessionStateKeeper', [BindingFlags]'Static, NonPublic').
GetValue($PSItem)
$map = $keeper.GetType().
GetField('_stateMap', [BindingFlags]'Instance, NonPublic').
GetValue($keeper)
# Remove the current mapping for this runspace and add one with the correct session.
$map.Remove($host.Runspace)
$map.Add($host.Runspace, $internal)
}
# Import the dynamic module so it's loaded as a nested module. Exported types from nested modules
# aren't cached, so when the using module statement is parsed it will directly load the types from
# our nested module.
Import-Module $module
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment