Skip to content

Commit df89f90

Browse files
Overhaul member binding - 0.2.0 Release (#3)
* Overhaul member binding With this change comes the ability to access non-public members without having to output them first. This allows you to use any non-public member like they've always been public as long as ImpliedReflection is enabled. This is done by replacing the delegate used by the engine to retrieve CLR members with a custom delegate that includes non-public members. For non-public static members, it's still required to output either an instance of the type or the System.Type object that represents it. Some additional changes: - Non-public constructors can now be accessed via an additional static method named "ctor" - Significantly increased performance - Now uses dynamically compiled delegates for all reflection calls - Module is now a compiled binary module - Caches retrieved member data * Various fixes - Fix member binding of non-public types in PowerShell Core - Fix binding of non-public constructors for non-public types - Fix binding of non-public constructors when the type already has a method named "ctor" - Fix binding of non-public constructors when parameters contain a type that cannot be used for a generic type instantiation * Fix exception when member is write only * Update README
1 parent 051c336 commit df89f90

41 files changed

Lines changed: 3042 additions & 474 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
## Ignore Visual Studio temporary files, build results, and
22
## files generated by popular Visual Studio add-ons.
33

4+
tools/ResGen
5+
src/ImpliedReflection/gen
6+
47
# User-specific files
58
*.suo
69
*.user

.vscode/extensions.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// See http://go.microsoft.com/fwlink/?LinkId=827846
33
// for the documentation about the extensions.json format
44
"recommendations": [
5-
"ms-vscode.PowerShell"
6-
]
5+
"ms-vscode.csharp",
6+
"ms-vscode.powershell",
7+
"DavidAnson.vscode-markdownlint",
8+
],
79
}

.vscode/launch.json

Lines changed: 11 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,20 @@
11
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
25
"version": "0.2.0",
36
"configurations": [
47
{
5-
"type": "PowerShell",
6-
"request": "launch",
7-
"name": "PowerShell debugHarness (Temporary Console)",
8-
"script": "${workspaceRoot}/debugHarness.ps1",
9-
"args": [],
10-
"cwd":"${workspaceRoot}",
11-
"createTemporaryIntegratedConsole": true
12-
},
13-
{
14-
"type": "PowerShell",
15-
"request": "launch",
16-
"name": "PowerShell debugHarness",
17-
"script": "${workspaceRoot}/debugHarness.ps1",
18-
"args": [],
19-
"cwd":"${workspaceRoot}"
20-
},
21-
{
22-
"type": "PowerShell",
23-
"request": "launch",
24-
"name": "PowerShell Launch Current File",
25-
"script": "${file}",
26-
"args": [],
27-
"cwd": "${file}"
28-
},
29-
{
30-
"type": "PowerShell",
31-
"request": "launch",
32-
"name": "PowerShell Launch Current File in Temporary Console",
33-
"script": "${file}",
34-
"args": [],
35-
"cwd": "${file}",
36-
"createTemporaryIntegratedConsole": true
37-
},
38-
{
39-
"type": "PowerShell",
40-
"request": "launch",
41-
"name": "PowerShell Launch Current File w/Args Prompt",
42-
"script": "${file}",
43-
"args": [
44-
"${command:SpecifyScriptArgs}"
45-
],
46-
"cwd": "${file}"
8+
"name": ".NET FullCLR Attach",
9+
"type": "clr",
10+
"request": "attach",
11+
"processId": "${command:pickProcess}",
4712
},
4813
{
49-
"type": "PowerShell",
14+
"name": ".NET CoreCLR Attach",
15+
"type": "coreclr",
5016
"request": "attach",
51-
"name": "PowerShell Attach to Host Process",
52-
"processId": "${command:PickPSHostProcess}",
53-
"runspaceId": 1
17+
"processId": "${command:pickProcess}",
5418
},
55-
{
56-
"type": "PowerShell",
57-
"request": "launch",
58-
"name": "PowerShell Interactive Session",
59-
"cwd": "${workspaceRoot}"
60-
}
61-
]
19+
],
6220
}
63-

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"files.insertFinalNewline": true,
99

1010
"search.exclude": {
11-
"Release": true
11+
"Release": true,
12+
"tools/ResGen": true,
13+
"tools/dotnet": true,
1214
},
1315

1416
//-------- PowerShell Configuration --------

ImpliedReflection.build.ps1

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
#requires -Module @{ModuleName = 'Pester'; RequiredVersion = '3.4.3'}, InvokeBuild, PSScriptAnalyzer, PlatyPS -Version 5.1
1+
#requires -Module InvokeBuild -Version 5.1
22
[CmdletBinding()]
3-
param()
3+
param(
4+
[ValidateSet('Debug', 'Release')]
5+
[string] $Configuration = 'Debug'
6+
)
47

58
$moduleName = 'ImpliedReflection'
6-
$manifest = Test-ModuleManifest -Path $PSScriptRoot\module\$moduleName.psd1 `
7-
-ErrorAction Ignore `
8-
-WarningAction Ignore
9+
$manifest = Test-ModuleManifest -Path $PSScriptRoot\module\$moduleName.psd1 -ErrorAction Ignore -WarningAction Ignore
910

1011
$script:Settings = @{
1112
Name = $moduleName
@@ -25,57 +26,79 @@ $script:Folders = @{
2526

2627
$script:Discovery = @{
2728
HasDocs = Test-Path ('{0}\{1}\*.md' -f $Folders.Docs, $PSCulture)
28-
HasTests = Test-Path ('{0}\*.Tests.ps1' -f $Folders.Test)
29+
HasTests = Test-Path ('{0}\*.Test.ps1' -f $Folders.Test)
30+
IsUnix = $PSEdition -eq 'Core' -and -not $IsWindows
2931
}
3032

3133
task Clean {
32-
if (Test-Path $script:Folders.Release) {
33-
Remove-Item $script:Folders.Release -Recurse
34+
$releaseFolder = $Folders.Release
35+
if (Test-Path $releaseFolder) {
36+
Remove-Item $releaseFolder -Recurse
3437
}
35-
$null = New-Item $script:Folders.Release -ItemType Directory
38+
39+
New-Item -ItemType Directory $releaseFolder | Out-Null
3640
}
3741

38-
task BuildDocs -If { $script:Discovery.HasDocs } {
39-
$null = New-ExternalHelp -Path $PSScriptRoot\docs\$PSCulture `
40-
-OutputPath ('{0}\{1}' -f $script:Folders.Release, $PSCulture)
42+
task BuildDocs -If { $Discovery.HasDocs } {
43+
$output = '{0}\{1}' -f $Folders.Release, $PSCulture
44+
$null = New-ExternalHelp -Path $PSScriptRoot\docs\$PSCulture -OutputPath $output
4145
}
4246

43-
task CopyToRelease {
44-
Copy-Item -Path ('{0}\*' -f $script:Folders.PowerShell) `
45-
-Destination $script:Folders.Release `
46-
-Recurse `
47-
-Force
47+
task AssertPSResGen {
48+
# Download the ResGen tool used by PowerShell core internally. This will need to be replaced
49+
# when the dotnet cli gains support for it.
50+
# The SHA in the uri's are for the 6.0.2 release commit.
51+
if (-not (Test-Path $PSScriptRoot/tools/ResGen)) {
52+
New-Item -ItemType Directory $PSScriptRoot/tools/ResGen | Out-Null
53+
}
54+
55+
if (-not (Test-Path $PSScriptRoot/tools/ResGen/Program.cs)) {
56+
$programUri = 'https://raw.githubusercontent.com/PowerShell/PowerShell/36b71ba39e36be3b86854b3551ef9f8e2a1de5cc/src/ResGen/Program.cs'
57+
Invoke-WebRequest $programUri -OutFile $PSScriptRoot/tools/ResGen/Program.cs -ErrorAction Stop
58+
}
59+
60+
if (-not (Test-Path $PSScriptRoot/tools/ResGen/ResGen.csproj)) {
61+
$projUri = 'https://raw.githubusercontent.com/PowerShell/PowerShell/36b71ba39e36be3b86854b3551ef9f8e2a1de5cc/src/ResGen/ResGen.csproj'
62+
Invoke-WebRequest $projUri -OutFile $PSScriptRoot/tools/ResGen/ResGen.csproj -ErrorAction Stop
63+
}
4864
}
4965

50-
task Analyze -If { $script:Settings.ShouldAnalyze } {
51-
Invoke-ScriptAnalyzer -Path $script:Folders.Release `
52-
-Settings $PSScriptRoot\ScriptAnalyzerSettings.psd1 `
53-
-Recurse
66+
task ResGenImpl {
67+
Push-Location $PSScriptRoot/src/ImpliedReflection
68+
try {
69+
dotnet run --project $PSScriptRoot/tools/ResGen/ResGen.csproj
70+
} finally {
71+
Pop-Location
72+
}
5473
}
5574

56-
task Test -If { $script:Discovery.HasTests -and $script:Settings.ShouldTest } {
57-
$projectRoot = $PSScriptRoot
58-
$pesterCC = "$PSScriptRoot\module\*\*.ps1", "$PSScriptRoot\module\*.psm1"
59-
Start-Job {
60-
Set-Location $using:projectRoot
75+
task BuildManaged {
76+
$script:dotnet = $dotnet = & $PSScriptRoot\tools\GetDotNet.ps1 -Unix:$Discovery.IsUnix
77+
78+
& $dotnet publish --framework netstandard2.0 --configuration $Configuration --verbosity q -nologo
79+
}
80+
81+
task CopyToRelease {
82+
$releaseFolder = $Folders.Release
83+
Copy-Item $PSScriptRoot/module/ImpliedReflection.psd1 -Destination $releaseFolder -Recurse
84+
Copy-Item $PSScriptRoot/src/ImpliedReflection/bin/$Configuration/netstandard2.0/publish/ImpliedReflection.* -Destination $releaseFolder
85+
Copy-Item $PSScriptRoot/src/ImpliedReflection/bin/$Configuration/netstandard2.0/publish/System.Buffers.dll -Destination $releaseFolder
86+
}
6187

62-
Invoke-Pester -CodeCoverage $using:pesterCC -PesterOption @{ IncludeVSCodeMarker = $true }
63-
} | Receive-Job -Wait -AutoRemoveJob
88+
task Analyze -If { $Settings.ShouldAnalyze } {
89+
Invoke-ScriptAnalyzer -Path $Folders.Release -Settings $PSScriptRoot\ScriptAnalyzerSettings.psd1 -Recurse
6490
}
6591

6692
task DoInstall {
6793
$installBase = $Home
6894
if ($profile) { $installBase = $profile | Split-Path }
69-
$installPath = '{0}\Modules\{1}\{2}' -f $installBase, $script:Settings.Name, $script:Settings.Version
95+
$installPath = '{0}\Modules\{1}\{2}' -f $installBase, $Settings.Name, $Settings.Version
7096

7197
if (-not (Test-Path $installPath)) {
7298
$null = New-Item $installPath -ItemType Directory
7399
}
74100

75-
Copy-Item -Path ('{0}\*' -f $script:Folders.Release) `
76-
-Destination $installPath `
77-
-Force `
78-
-Recurse
101+
Copy-Item -Path ('{0}\*' -f $Folders.Release) -Destination $installPath -Force -Recurse
79102
}
80103

81104
task DoPublish {
@@ -84,16 +107,16 @@ task DoPublish {
84107
}
85108

86109
$apiKey = (Import-Clixml $env:USERPROFILE\.PSGallery\apikey.xml).GetNetworkCredential().Password
87-
Publish-Module -Name $script:Folders.Release -NuGetApiKey $apiKey -Confirm
110+
Publish-Module -Name $Folders.Release -NuGetApiKey $apiKey -Confirm
88111
}
89112

90-
task Build -Jobs Clean, CopyToRelease, BuildDocs
113+
task ResGen -Jobs AssertPSResGen, ResGenImpl
91114

92-
task PreRelease -Jobs Build, Analyze, Test
115+
task Build -Jobs Clean, ResGen, BuildManaged, CopyToRelease, BuildDocs
93116

94-
task Install -Jobs PreRelease, DoInstall
117+
task Install -Jobs Build, DoInstall
95118

96-
task Publish -Jobs PreRelease, DoPublish
119+
task Publish -Jobs Build, DoPublish
97120

98121
task . Build
99122

ImpliedReflection.sln

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.26124.0
5+
MinimumVisualStudioVersion = 15.0.26124.0
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B7E6340F-D948-4C97-B5CA-E60F6D352491}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImpliedReflection", "src\ImpliedReflection\ImpliedReflection.csproj", "{0C10B1B6-777E-436E-ABAE-F0DE33630B50}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Debug|x64 = Debug|x64
14+
Debug|x86 = Debug|x86
15+
Release|Any CPU = Release|Any CPU
16+
Release|x64 = Release|x64
17+
Release|x86 = Release|x86
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
23+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Debug|Any CPU.Build.0 = Debug|Any CPU
25+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Debug|x64.ActiveCfg = Debug|Any CPU
26+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Debug|x64.Build.0 = Debug|Any CPU
27+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Debug|x86.ActiveCfg = Debug|Any CPU
28+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Debug|x86.Build.0 = Debug|Any CPU
29+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Release|Any CPU.ActiveCfg = Release|Any CPU
30+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Release|Any CPU.Build.0 = Release|Any CPU
31+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Release|x64.ActiveCfg = Release|Any CPU
32+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Release|x64.Build.0 = Release|Any CPU
33+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Release|x86.ActiveCfg = Release|Any CPU
34+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50}.Release|x86.Build.0 = Release|Any CPU
35+
EndGlobalSection
36+
GlobalSection(NestedProjects) = preSolution
37+
{0C10B1B6-777E-436E-ABAE-F0DE33630B50} = {B7E6340F-D948-4C97-B5CA-E60F6D352491}
38+
EndGlobalSection
39+
EndGlobal

README.md

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,32 @@ By participating, you are expected to uphold this code. Please report unacceptab
99

1010
- All members are bound to the object the **same way public members are** by the PowerShell engine.
1111
- Supports any parameter types (including ByRef, Pointer, etc) that the PowerShell engine can handle.
12-
- Members are **bound automatically** when output to the console.
13-
- Supports non-public static members with `[type]::Member` syntax.
12+
- Member binding is done automatically by hooking into the PowerShell engine's binder.
13+
14+
## Known Issues
15+
16+
- Non-public static members will only be available once either:
17+
1. A `System.Type` object representing the type has been outputted
18+
1. An instance of the type has been outputted
19+
There's only an instance delegate in the engine, so static still depends on `Out-Default`
20+
21+
- Non-public constructors are available via an added `_ctor` static method
22+
23+
- Family (aka `protected`) properties will throw an exception stating that the property is
24+
write only. This is due to explicit checks for family properties in the PowerShell's binder. You can
25+
get around it by calling the getter method instead (e.g. instead of `$Host.Runspace.RunningPipelines` try `$Host.Runpsace.get_RunningPipelines()`)
1426

1527
## Documentation
1628

1729
Check out our **[documentation](https://github.com/SeeminglyScience/ImpliedReflection/tree/master/docs/en-US/ImpliedReflection.md)** for information about how to use this project.
1830

19-
## Demo
31+
## Demos
32+
33+
![new-member-binding](https://user-images.githubusercontent.com/24977523/45323875-79ada380-b51a-11e8-95d0-15e605b5eb4a.gif)
2034

2135
![implied-reflection-demo](https://user-images.githubusercontent.com/24977523/28750154-28fda216-74af-11e7-8629-8ada279e860e.gif)
2236

37+
2338
## Installation
2439

2540
### Gallery
@@ -42,10 +57,6 @@ Invoke-Build -Task Install
4257

4358
```powershell
4459
Enable-ImpliedReflection -Force
45-
$ExecutionContext
46-
<# Output omitted #>
47-
$ExecutionContext._context
48-
<# Output omitted #>
4960
$ExecutionContext._context.HelpSystem
5061
<#
5162
ExecutionContext : System.Management.Automation.ExecutionContext
@@ -77,10 +88,6 @@ _culture :
7788

7889
```powershell
7990
$scriptblock = { 'Test ScriptBlock' }
80-
$scriptblock
81-
<# (Formatting still applies)
82-
'Test ScriptBlock'
83-
#>
8491
$scriptblock.InvokeUsingCmdlet
8592
<#
8693
OverloadDefinitions
@@ -96,6 +103,8 @@ $scriptblock.InvokeUsingCmdlet($PSCmdlet, $true, 'SwallowErrors', $_, $input, $t
96103
### Explore static members
97104

98105
```powershell
106+
# For static binding either the type (like below) or an instance of the type needs to hit
107+
# Out-Default first.
99108
[scriptblock]
100109
[scriptblock]::delegateTable
101110
<#

build.ps1

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[CmdletBinding()]
2+
param(
3+
[switch] $Force
4+
)
5+
end {
6+
& "$PSScriptRoot\tools\AssertRequiredModule.ps1" InvokeBuild 5.4.1 -Force:$Force.IsPresent
7+
$invokeBuildSplat = @{
8+
Task = 'PreRelease'
9+
File = "$PSScriptRoot/ImpliedReflection.build.ps1"
10+
GenerateCodeCoverage = $true
11+
Force = $Force.IsPresent
12+
Configuration = 'Release'
13+
}
14+
15+
Invoke-Build @invokeBuildSplat
16+
}

debugHarness.ps1

Lines changed: 0 additions & 2 deletions
This file was deleted.

docs/en-US/Add-PrivateMember.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ If either the ReturnPropertyName parameter or the PassThru switch parameter is s
9696
## NOTES
9797
9898
- If the InputObject is a type info object (System.Type) then static members of the value will be added instead.
99-
- Currently this does not work with Constructors
99+
- Non-public constructors will be added as a static method named "ctor".
100100
- Properties or fields with the same name of an existing property will not be added.
101101
- Non-public method overloads of a public method will not be loaded.
102102
- Overloads of a method that is not already present will be grouped into a single PSMethod object,

0 commit comments

Comments
 (0)