diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48a07ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..614efd8 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "ms-vscode.PowerShell" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..60f1f97 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,63 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell debugHarness (Temporary Console)", + "script": "${workspaceRoot}/debugHarness.ps1", + "args": [], + "cwd":"${workspaceRoot}", + "createTemporaryIntegratedConsole": true + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell debugHarness", + "script": "${workspaceRoot}/debugHarness.ps1", + "args": [], + "cwd":"${workspaceRoot}" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File", + "script": "${file}", + "args": [], + "cwd": "${file}" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File in Temporary Console", + "script": "${file}", + "args": [], + "cwd": "${file}", + "createTemporaryIntegratedConsole": true + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File w/Args Prompt", + "script": "${file}", + "args": [ + "${command:SpecifyScriptArgs}" + ], + "cwd": "${file}" + }, + { + "type": "PowerShell", + "request": "attach", + "name": "PowerShell Attach to Host Process", + "processId": "${command:PickPSHostProcess}", + "runspaceId": 1 + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Interactive Session", + "cwd": "${workspaceRoot}" + } + ] +} + diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cf27e07 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + //-------- Files configuration -------- + + // When enabled, will trim trailing whitespace when you save a file. + "files.trimTrailingWhitespace": true, + + // When enabled, insert a final new line at the end of the file when saving it. + "files.insertFinalNewline": true, + + "search.exclude": { + "Release": true + }, + + //-------- PowerShell Configuration -------- + + // Use a custom PowerShell Script Analyzer settings file for this workspace. + // Relative paths for this setting are always relative to the workspace root dir. + "powershell.scriptAnalysis.settingsPath": "ScriptAnalyzerSettings.psd1" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..e4b36f8 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,108 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${relativeFile}: the current opened file relative to workspaceRoot +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "runner": "terminal", + + // Start PowerShell + "windows": { + "command": "PowerShell.exe" + }, + "linux": { + "command": "/usr/bin/powershell" + }, + "osx": { + "command": "/usr/local/bin/powershell" + }, + + // The command is a shell script + "isShellCommand": true, + + // Show the output window always + "showOutput": "always", + + "args": [ + "-NoProfile", "-ExecutionPolicy", "Bypass" + ], + + // Associate with test task runner + "tasks": [ + { + "taskName": "Clean", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Invoke-Build -Task Clean" + ] + }, + { + "taskName": "Build", + "suppressTaskName": true, + "isBuildCommand": true, + "showOutput": "always", + "args": [ + "Invoke-Build" + ] + }, + { + "taskName": "BuildHelp", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Invoke-Build -Task BuildDocs" + ] + }, + { + "taskName": "Analyze", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Invoke-Build -Task Analyze" + ] + }, + { + "taskName": "Install", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Invoke-Build -Task Install" + ] + }, + { + "taskName": "Test", + "suppressTaskName": true, + "isTestCommand": true, + "showOutput": "always", + "args": [ + "Invoke-Build -Task Test" + ], + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": ["absolute"], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "^\\s+at\\s+[^,]+,\\s*(.*?):\\s+line\\s+(\\d+)$", + "file": 1, + "line": 2 + } + ] + } + ] + } + ] +} + diff --git a/ImpliedReflection.build.ps1 b/ImpliedReflection.build.ps1 new file mode 100644 index 0000000..5d0ebb9 --- /dev/null +++ b/ImpliedReflection.build.ps1 @@ -0,0 +1,98 @@ +#requires -Module InvokeBuild, PSScriptAnalyzer, Pester, PlatyPS -Version 5.1 +[CmdletBinding()] +param() + +$moduleName = 'ImpliedReflection' +$manifest = Test-ModuleManifest -Path $PSScriptRoot\module\$moduleName.psd1 ` + -ErrorAction Ignore ` + -WarningAction Ignore + +$script:Settings = @{ + Name = $moduleName + Manifest = $manifest + Version = $manifest.Version + ShouldAnalyze = $true + ShouldTest = $true +} + +$script:Folders = @{ + PowerShell = "$PSScriptRoot\module" + Release = '{0}\Release\{1}\{2}' -f $PSScriptRoot, $moduleName, $manifest.Version + Docs = "$PSScriptRoot\docs" + Test = "$PSScriptRoot\test" + PesterCC = "$PSScriptRoot\*.psm1", "$PSScriptRoot\Public\*.ps1", "$PSScriptRoot\Private\*.ps1" +} + +$script:Discovery = @{ + HasDocs = Test-Path ('{0}\{1}\*.md' -f $Folders.Docs, $PSCulture) + HasTests = Test-Path ('{0}\*.Tests.ps1' -f $Folders.Test) +} + +task Clean { + if (Test-Path $script:Folders.Release) { + Remove-Item $script:Folders.Release -Recurse + } + $null = New-Item $script:Folders.Release -ItemType Directory +} + +task BuildDocs -If { $script:Discovery.HasDocs } { + $null = New-ExternalHelp -Path $PSScriptRoot\docs\$PSCulture ` + -OutputPath ('{0}\{1}' -f $script:Folders.Release, $PSCulture) +} + +task CopyToRelease { + Copy-Item -Path ('{0}\*' -f $script:Folders.PowerShell) ` + -Destination $script:Folders.Release ` + -Recurse ` + -Force +} + +task Analyze -If { $script:Settings.ShouldAnalyze } { + Invoke-ScriptAnalyzer -Path $script:Folders.Release ` + -Settings $PSScriptRoot\ScriptAnalyzerSettings.psd1 ` + -Recurse +} + +task Test -If { $script:Discovery.HasTests -and $script:Settings.ShouldTest } { + $projectRoot = $PSScriptRoot + $pesterCC = "$PSScriptRoot\module\*\*.ps1", "$PSScriptRoot\module\*.psm1" + Start-Job { + Set-Location $using:projectRoot + Invoke-Pester -PesterOption @{ IncludeVSCodeMarker = $true } -CodeCoverage $using:pesterCC + } | Receive-Job -Wait -AutoRemoveJob +} + +task DoInstall { + $installBase = $Home + if ($profile) { $installBase = $profile | Split-Path } + $installPath = '{0}\Modules\{1}\{2}' -f $installBase, $script:Settings.Name, $script:Settings.Version + + if (-not (Test-Path $installPath)) { + $null = New-Item $installPath -ItemType Directory + } + + Copy-Item -Path ('{0}\*' -f $script:Folders.Release) ` + -Destination $installPath ` + -Force ` + -Recurse +} + +task DoPublish { + if (-not (Test-Path $env:USERPROFILE\.PSGallery\apikey.xml)) { + throw 'Could not find PSGallery API key!' + } + + $apiKey = (Import-Clixml $env:USERPROFILE\.PSGallery\apikey.xml).GetNetworkCredential().Password + Publish-Module -Name $script:Folders.Release -NuGetApiKey $apiKey -Confirm +} + +task Build -Jobs Clean, CopyToRelease, BuildDocs + +task PreRelease -Jobs Build, Analyze, Test + +task Install -Jobs PreRelease, DoInstall + +task Publish -Jobs PreRelease, DoPublish + +task . Build + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..44dd8f6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017 Patrick Meinecke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f41c10a --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# ImpliedReflection + +ImpliedReflection is a PowerShell module for exploring non-public properties and methods of objects as if they were public. + +This project adheres to the Contributor Covenant [code of conduct](https://github.com/SeeminglyScience/ImpliedReflection/tree/master/docs/CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable behavior to seeminglyscience@gmail.com. + +## Features + +- All members are bound to the object the **same way public members are** by the PowerShell engine. +- Supports any parameter types (including ByRef, Pointer, etc) that the PowerShell engine can handle. +- Members are **bound automatically** when output to the console. +- Supports non-public static members with `[type]::Member` syntax. + +## Documentation + +Check out our **[documentation](https://github.com/SeeminglyScience/ImpliedReflection/tree/master/docs/en-US/ImpliedReflection.md)** for information about how to use this project. + +## Demo + +![implied-reflection-demo](https://user-images.githubusercontent.com/24977523/28750154-28fda216-74af-11e7-8629-8ada279e860e.gif) + +## Installation + +### Gallery + +```powershell +Install-Module ImpliedReflection -Scope CurrentUser +``` + +### Source + +```powershell +git clone 'https://github.com/SeeminglyScience/ImpliedReflection.git' +Set-Location .\ImpliedReflection +Invoke-Build -Task Install +``` + +## Usage + +### Explore properties and fields + +```powershell +Enable-ImpliedReflection -Force +$ExecutionContext +<# Output omitted #> +$ExecutionContext._context +<# Output omitted #> +$ExecutionContext._context.HelpSystem +<# +ExecutionContext : System.Management.Automation.ExecutionContext +LastErrors : {} +LastHelpCategory : None +VerboseHelpErrors : False +HelpProviders : {System.Management.Automation.AliasHelpProvider, + System.Management.Automation.ScriptCommandHelpProvider, + System.Management.Automation.CommandHelpProvider, + System.Management.Automation.ProviderHelpProvider...} +HelpErrorTracer : System.Management.Automation.HelpErrorTracer +ScriptBlockTokenCache : {} +_executionContext : System.Management.Automation.ExecutionContext +OnProgress : +_lastErrors : {} +_lastHelpCategory : None +_verboseHelpErrors : False +_searchPaths : +_helpProviders : {System.Management.Automation.AliasHelpProvider, + System.Management.Automation.ScriptCommandHelpProvider, + System.Management.Automation.CommandHelpProvider, + System.Management.Automation.ProviderHelpProvider...} +_helpErrorTracer : System.Management.Automation.HelpErrorTracer +_culture : +#> +``` + +### Invoke private methods + +```powershell +$scriptblock = { 'Test ScriptBlock' } +$scriptblock +<# (Formatting still applies) + 'Test ScriptBlock' +#> +$scriptblock.InvokeUsingCmdlet +<# +OverloadDefinitions +------------------- +void InvokeUsingCmdlet(System.Management.Automation.Cmdlet contextCmdlet, bool useLocalScope, +System.Management.Automation.ScriptBlock+ErrorHandlingBehavior, System.Management.Automation, Version=3.0.0.0, +Culture=neutral, PublicKeyToken=31bf3856ad364e35 errorHandlingBehavior, System.Object dollarUnder, System.Object +input, System.Object scriptThis, System.Object[] args) +#> +$scriptblock.InvokeUsingCmdlet($PSCmdlet, $true, 'SwallowErrors', $_, $input, $this, $args) +``` + +### Explore static members + +```powershell +[scriptblock] +[scriptblock]::delegateTable +<# +Keys : { $args[0].Name } +Values : {System.Collections.Concurrent.ConcurrentDictionary`2[System.Type,System.Delegate]} +_buckets : {-1, -1, 11, 7...} +_entries : {System.Runtime.CompilerServices.ConditionalWeakTable`2+Entry[System.Management.Automation.ScriptBlock,Syst + em.Collections.Concurrent.ConcurrentDictionary`2[System.Type,System.Delegate]], System.Runtime.CompilerServ + ices.ConditionalWeakTable`2+Entry[System.Management.Automation.ScriptBlock,System.Collections.Concurrent.Co + ncurrentDictionary`2[System.Type,System.Delegate]], System.Runtime.CompilerServices.ConditionalWeakTable`2+ + Entry[System.Management.Automation.ScriptBlock,System.Collections.Concurrent.ConcurrentDictionary`2[System. + Type,System.Delegate]], System.Runtime.CompilerServices.ConditionalWeakTable`2+Entry[System.Management.Auto + mation.ScriptBlock,System.Collections.Concurrent.ConcurrentDictionary`2[System.Type,System.Delegate]]...} +_freeList : 3 +_lock : System.Object +_invalid : False +#> +[ref].Assembly.GetType('System.Management.Automation.Utils') +[ref].Assembly.GetType('System.Management.Automation.Utils')::IsAdministrator() +<# +False +#> +[psmoduleinfo] +[psmoduleinfo] | Get-Member -Static +<# + TypeName: System.Management.Automation.PSModuleInfo + +Name MemberType Definition +---- ---------- ---------- +AddModuleToList Method static void AddModuleToList(psmoduleinfo module, System.Col... +AddToAppDomainLevelModuleCache Method static void AddToAppDomainLevelModuleCache(string moduleNam... +ClearAppDomainLevelModulePathCache Method static void ClearAppDomainLevelModulePathCache() +Equals Method static bool Equals(System.Object objA, System.Object objB) +GetUriFromString Method static uri GetUriFromString(string uriString) +new Method psmoduleinfo new(bool linkToGlobal), psmoduleinfo new(scrip... +ReferenceEquals Method static bool ReferenceEquals(System.Object objA, System.Obje... +RemoveFromAppDomainLevelCache Method static bool RemoveFromAppDomainLevelCache(string moduleName) +ResolveUsingAppDomainLevelModuleCache Method static string ResolveUsingAppDomainLevelModuleCache(string ... +SetDefaultDynamicNameAndPath Method static void SetDefaultDynamicNameAndPath(psmoduleinfo module) +k__BackingField Property static bool k__BackingField {... +DynamicModulePrefixString Property static string DynamicModulePrefixString {get;set;} +EmptyTypeDefinitionDictionary Property static System.Collections.ObjectModel.ReadOnlyDictionary[st... +ScriptModuleExtensions Property static System.Collections.Generic.HashSet[string] ScriptMod... +UseAppDomainLevelModuleCache Property static bool UseAppDomainLevelModuleCache {get;set;} +_appdomainModulePathCache Property static System.Collections.Concurrent.ConcurrentDictionary[s... +_builtinVariables Property static string[] _builtinVariables {get;set;} +#> +``` + +## Contributions Welcome! + +We would love to incorporate community contributions into this project. If you would like to +contribute code, documentation, tests, or bug reports, please read our [Contribution Guide](https://github.com/SeeminglyScience/ImpliedReflection/tree/master/docs/CONTRIBUTING.md) to learn more. diff --git a/ScriptAnalyzerSettings.psd1 b/ScriptAnalyzerSettings.psd1 new file mode 100644 index 0000000..729db50 --- /dev/null +++ b/ScriptAnalyzerSettings.psd1 @@ -0,0 +1,30 @@ +# The PowerShell Script Analyzer will generate a warning +# diagnostic record for this file due to a bug - +# https://github.com/PowerShell/PSScriptAnalyzer/issues/472 +@{ + # Only diagnostic records of the specified severity will be generated. + # Uncomment the following line if you only want Errors and Warnings but + # not Information diagnostic records. + + # Severity = @('Error','Warning') + + # Analyze **only** the following rules. Use IncludeRules when you want + # to invoke only a small subset of the defualt rules. + + # IncludeRules = @('PSAvoidDefaultValueSwitchParameter', + # 'PSMissingModuleManifestField', + # 'PSReservedCmdletChar', + # 'PSReservedParams', + # 'PSShouldProcess', + # 'PSUseApprovedVerbs', + # 'PSUseDeclaredVarsMoreThanAssigments') + + # Do not analyze the following rules. Use ExcludeRules when you have + # commented out the IncludeRules settings above and want to include all + # the default rules except for those you exclude below. + # Note: if a rule is in both IncludeRules and ExcludeRules, the rule + # will be excluded. + + # ExcludeRules = @('PSAvoidUsingWriteHost') + ExcludeRules = 'PSUseShouldProcessForStateChangingFunctions' +} diff --git a/debugHarness.ps1 b/debugHarness.ps1 new file mode 100644 index 0000000..4b3101c --- /dev/null +++ b/debugHarness.ps1 @@ -0,0 +1,2 @@ +# Use this file to debug the module. +Import-Module -Name $PSScriptRoot\module\ImpliedReflection.psd1 diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2d98888 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at seeminglyscience@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ + diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..76ae568 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# Contribution Guidelines + +Bug fixes are very welcome as contributions + +We welcome many kinds of community contributions to this project! Whether it's a bug fix, or a good idea, please create an issue so that we can discuss it. It is not necessary to create an + issue before sending a pull request but it may speed up the process if we can discuss your idea before + you start implementing it. + +Because this project exposes a couple different public APIs, we must be very mindful of any potential breaking + changes. Some contributions may not be accepted if they risk introducing breaking changes or if they + don't match the goals of the project. The core maintainer team has the right of final approval over +any contribution to this project. However, we are very happy to hear community feedback on any decision + so that we can ensure we are solving the right problems in the right way. + +## Ways to Contribute + +- File a bug or feature request as an [issue](https://github.com/SeeminglyScience/ImpliedReflection/issues) +- Comment on existing issues to give your feedback on how they should be fixed/implemented +- Contribute a bug fix by submitting a pull request +- Contribute more unit tests for feature areas that lack good coverage +- Review the pull requests that others submit to ensure they follow [established guidelines](#pull-request-guidelines) + +## Code Contribution Guidelines + +Here's a high level list of guidelines to follow to ensure your code contribution is accepted: + +- Follow established guidelines for coding style and design +- Follow established guidelines for commit hygiene +- Write unit tests to validate new features and bug fixes +- Ensure that the 'Release' build and unit tests pass locally +- Respond to all review feedback and final commit cleanup + +### Practice Good Commit Hygiene + +First of all, make sure you are practicing [good commit hygiene](http://blog.ericbmerritt.com/2011/09/21/commit-hygiene-and-git.html) +so that your commits provide a good history of the changes you are making. To be more specific: + +- **Write good commit messages** + + Commit messages should be clearly written so that a person can look at the commit log and understand + how and why a given change was made. Here is a great model commit message taken from a [blog post + by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html): + + Capitalized, short (50 chars or less) summary + + More detailed explanatory text, if necessary. Wrap it to about 72 + characters or so. In some contexts, the first line is treated as the + subject of an email and the rest of the text as the body. The blank + line separating the summary from the body is critical (unless you omit + the body entirely); tools like rebase can get confused if you run the + two together. + + Write your commit message in the imperative: "Fix bug" and not "Fixed bug" + or "Fixes bug." This convention matches up with commit messages generated + by commands like git merge and git revert. + + Further paragraphs come after blank lines. + + - Bullet points are okay, too + + - Typically a hyphen or asterisk is used for the bullet, followed by a + single space, with blank lines in between, but conventions vary here + + - Use a hanging indent + + A change that fixes a known bug with an issue filed should use the proper syntax so that the [issue + is automatically closed](https://help.github.com/articles/closing-issues-via-commit-messages/) once + your change is merged. Here's an example of what such a commit message should look like: + + Fix #3: Catch NullReferenceException from DoThing + + This change adds a try/catch block to catch a NullReferenceException that + gets thrown by DoThing [...] + +- **Squash your commits** + + If you are introducing a new feature but have implemented it over multiple commits, + please [squash those commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) + into a single commit that contains all the changes in one place. This especially applies to any "oops" + commits where a file is forgotten or a typo is being fixed. Following this approach makes it a lot easier + to pull those changes to other branches or roll back the change if necessary. + +- **Keep individual commits for larger changes** + + You can certainly maintain individual commits for different phases of a big change. For example, if + you want to reorganize some files before adding new functionality, have your first commit contain all + of the file move changes and then the following commit can have all of the feature additions. We + highly recommend this approach so that larger commits don't turn into a jumbled mess. + +### Follow the Pull Request Process + +- **Create your pull request** + + Use the [typical process](https://help.github.com/articles/using-pull-requests/) to send a pull request + from your fork of the project. In your pull request message, please give a high-level summary of the + changes that you have made so that reviewers understand the intent of the changes. You should receive + initial comments within a day or two, but please feel free to ping if things are taking longer than + expected. + +- **Respond to code review feedback** + + If the reviewers ask you to make changes, make them as a new commit to your branch and push them so + that they are made available for a final review pass. Do not rebase the fixes just yet so that the + commit hash changes don't upset GitHub's pull request UI. + +- **If necessary, do a final rebase** + + Once your final changes have been accepted, we may ask you to do a final rebase to have your commits + so that they follow our commit guidelines. If specific guidance is given, please follow it when + rebasing your commits. Once you do your final push, we will merge your changes! diff --git a/docs/en-US/Add-PrivateMember.md b/docs/en-US/Add-PrivateMember.md new file mode 100644 index 0000000..18abfbb --- /dev/null +++ b/docs/en-US/Add-PrivateMember.md @@ -0,0 +1,108 @@ +--- +external help file: ImpliedReflection-help.xml +online version: https://github.com/SeeminglyScience/ImpliedReflection/blob/master/docs/en-US/Add-PrivateMember.md +schema: 2.0.0 +--- + +# Add-PrivateMember + +## SYNOPSIS + +Bind all non-public members to an object. + +## SYNTAX + +```powershell +Add-PrivateMember [[-ReturnPropertyName] ] [-InputObject ] [-PassThru] +``` + +## DESCRIPTION + +The Add-PrivateMember function binds all non-public members to an object in the same way the PowerShell engine binds public members. This allows the members to be viewed and invoked like any other property or method typically bound by PowerShell. Properties will be added as PSProperty objects and Methods will be bound as PSMethod objects. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +$ExecutionContext | Add-PrivateMember +``` + +Binds all private members to the EngineIntrinsics object contained in the global variable +$ExecutionContext. + +## PARAMETERS + +### -ReturnPropertyName + +Specifies the property to return after binding members. This can be used to chain Add-PrivateMember calls to quickly traverse an object without outputting it to the console. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject + +Specifies the object aquire and bind private members for. + +```yaml +Type: PSObject +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -PassThru + +If specified, objects specified in the InputObject parameter will be returned to the pipeline after member binding. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +## INPUTS + +### PSObject + +You can pipe any object to this function. + +## OUTPUTS + +### None, System.Object + +If either the ReturnPropertyName parameter or the PassThru switch parameter is specified, any object can be returned. Otherwise this function does not have output. + +## NOTES + +- If the InputObject is a type info object (System.Type) then static members of the value will be added instead. +- Currently this does not work with Constructors +- Properties or fields with the same name of an existing property will not be added. +- Non-public method overloads of a public method will not be loaded. +- Overloads of a method that is not already present will be grouped into a single PSMethod object, + like you would see in a method bound by the PowerShell engine. + +## RELATED LINKS + +[Enable-ImpliedReflection](Enable-ImpliedReflection.md) +[Disable-ImpliedReflection](Disable-ImpliedReflection.md) diff --git a/docs/en-US/Disable-ImpliedReflection.md b/docs/en-US/Disable-ImpliedReflection.md new file mode 100644 index 0000000..2a62d51 --- /dev/null +++ b/docs/en-US/Disable-ImpliedReflection.md @@ -0,0 +1,57 @@ +--- +external help file: ImpliedReflection-help.xml +online version: https://github.com/SeeminglyScience/ImpliedReflection/blob/master/docs/en-US/Disable-ImpliedReflection.md +schema: 2.0.0 +--- + +# Disable-ImpliedReflection + +## SYNOPSIS + +Disables automatic binding of non-public members. + +## SYNTAX + +```powershell +Disable-ImpliedReflection +``` + +## DESCRIPTION + +The Disable-ImpliedReflection function will disable the binding of non-public members to any object outputted to the console. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +Enable-ImpliedReflection -Force +$ExecutionContext +Disable-ImpliedReflection +$ExecutionContext._context +``` + +Enables implied reflection, uses it to bind private members to $ExecutionContext, disables implied reflection, and returns one of the members bound. + +## PARAMETERS + +## INPUTS + +### None + +This function does not accept input from the pipeline. + +## OUTPUTS + +### None + +This function does not output to the pipeline. + +## NOTES + +- This will not remove members bound by implied reflection. + +## RELATED LINKS + +[Add-PrivateMember](Add-PrivateMember.md) +[Disable-ImpliedReflection](Disable-ImpliedReflection.md) diff --git a/docs/en-US/Enable-ImpliedReflection.md b/docs/en-US/Enable-ImpliedReflection.md new file mode 100644 index 0000000..ee12122 --- /dev/null +++ b/docs/en-US/Enable-ImpliedReflection.md @@ -0,0 +1,225 @@ +--- +external help file: ImpliedReflection-help.xml +online version: https://github.com/SeeminglyScience/ImpliedReflection/blob/master/docs/en-US/Enable-ImpliedReflection.md +schema: 2.0.0 +--- + +# Enable-ImpliedReflection + +## SYNOPSIS + +Enable the binding of non public members to all objects outputted. + +## SYNTAX + +```powershell +Enable-ImpliedReflection [-Force] [-WhatIf] [-Confirm] +``` + +## DESCRIPTION + +The Enable-ImpliedReflection function replaces the Out-Default cmdlet with a proxy function that invokes Add-PrivateMember on every object outputted to the console. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +Enable-ImpliedReflection -Force +$ExecutionContext + + _context : System.Management.Automation.ExecutionContext + _host : System.Management.Automation.Internal.Host.InternalHost + _invokeCommand : System.Management.Automation.CommandInvocationIntrinsics + Host : System.Management.Automation.Internal.Host.InternalHost + Events : System.Management.Automation.PSLocalEventManager + InvokeProvider : System.Management.Automation.ProviderIntrinsics + SessionState : System.Management.Automation.SessionState + InvokeCommand : System.Management.Automation.CommandInvocationIntrinsics + +$ExecutionContext._context + + CurrentExceptionBeingHandled : + PropagateExceptionsToEnclosingStatementBlock : True + QuestionMarkVariableValue : True + LanguageMode : FullLanguage + EngineIntrinsics : System.Management.Automation.EngineIntrinsics + ShellFunctionErrorOutputPipe : System.Management.Automation.Internal.Pipe + TypeTable : System.Management.Automation.Runspaces.TypeTable + ExpressionWarningOutputPipe : + ExpressionVerboseOutputPipe : + ExpressionDebugOutputPipe : + ExpressionInformationOutputPipe : + Events : System.Management.Automation.PSLocalEventManager + Debugger : System.Management.Automation.ScriptDebugger + PSDebugTraceLevel : 0 + PSDebugTraceStep : False + ShouldTraceStatement : False + ScriptCommandProcessorShouldRethrowExit : False + IgnoreScriptDebug : False + Engine : System.Management.Automation.AutomationEngine + RunspaceConfiguration : + InitialSessionState : System.Management.Automation.Runspaces.InitialSessionSt + ate + IsSingleShell : True + PreviousModuleProcessed : + ModuleBeingProcessed : + AppDomainForModuleAnalysis : + AuthorizationManager : Microsoft.PowerShell.PSAuthorizationManager + ProviderNames : System.Management.Automation.SingleShellProviderNames + Modules : System.Management.Automation.ModuleIntrinsics + ShellID : Microsoft.PowerShell + EngineSessionState : System.Management.Automation.SessionStateInternal + TopLevelSessionState : System.Management.Automation.SessionStateInternal + SessionState : System.Management.Automation.SessionState + HasRunspaceEverUsedConstrainedLanguageMode : False + UseFullLanguageModeInDebugger : False + IsModuleWithJobSourceAdapterLoaded : False + LocationGlobber : System.Management.Automation.LocationGlobber + EngineState : Available + HelpSystem : System.Management.Automation.HelpSystem + FormatInfo : + CustomArgumentCompleters : + NativeArgumentCompleters : + CurrentCommandProcessor : format-default + CommandDiscovery : System.Management.Automation.CommandDiscovery + EngineHostInterface : System.Management.Automation.Internal.Host.InternalHost + InternalHost : System.Management.Automation.Internal.Host.InternalHost + LogContextCache : System.Management.Automation.LogContextCache + ExternalSuccessOutput : System.Management.Automation.Internal.ObjectWriter + ExternalErrorOutput : System.Management.Automation.Internal.ObjectWriter + ExternalProgressOutput : + CurrentRunspace : System.Management.Automation.Runspaces.LocalRunspace + CurrentPipelineStopping : False + DebugPreferenceVariable : SilentlyContinue + VerbosePreferenceVariable : SilentlyContinue + ErrorActionPreferenceVariable : Continue + WarningActionPreferenceVariable : Continue + InformationActionPreferenceVariable : SilentlyContinue + WhatIfPreferenceVariable : False + ConfirmPreferenceVariable : High + FormatDBManager : Microsoft.PowerShell.Commands.Internal.Format.TypeInfoD + ataBaseManager + TransactionManager : System.Management.Automation.Internal.PSTransactionMana + ger + _debugger : System.Management.Automation.ScriptDebugger + _debuggingMode : 0 + eventManager : System.Management.Automation.PSLocalEventManager + debugTraceLevel : 0 + debugTraceStep : False + _scriptCommandProcessorShouldRethrowExit : False + _engine : System.Management.Automation.AutomationEngine + _runspaceConfiguration : + _initialSessionState : System.Management.Automation.Runspaces.InitialSessionSt + ate + _previousModuleProcessed : + _moduleBeingProcessed : + _responsibilityForModuleAnalysisAppDomainOwned : False + _authorizationManager : Microsoft.PowerShell.PSAuthorizationManager + _modules : System.Management.Automation.ModuleIntrinsics + _shellId : Microsoft.PowerShell + _languageMode : FullLanguage + k__BackingField : False + k__BackingField : False + _locationGlobber : System.Management.Automation.LocationGlobber + _engineState : Available + _helpSystem : System.Management.Automation.HelpSystem + _formatInfo : + commandFactory : System.Management.Automation.CommandFactory + myHostInterface : System.Management.Automation.Internal.Host.InternalHost + _engineIntrinsics : System.Management.Automation.EngineIntrinsics + _externalErrorOutput : System.Management.Automation.Internal.ObjectWriter + _externalProgressOutput : + k__BackingField : True + k__BackingField : + _questionMarkVariableValue : True + _typeTable : System.Management.Automation.Runspaces.TypeTable + _assemblyCacheInitialized : False + _moduleNestingDepth : 0 + +$moduleOutput = $null +$sessionState = [System.Management.Automation.SessionState]::new() +$ExecutionContext._context.Modules.CreateModule('MyModule', 'FakeModulePath', {'FakeModuleOutput'}, $sessionState, [ref]$moduleOutput, @()) + + ModuleType Version Name ExportedCommands + ---------- ------- ---- ---------------- + Script 0.0 MyModule + +$moduleOutput + + FakeModuleOutput +``` + +Enables implied reflection and explores the current ExecutionContext. Then creates a module using a non-public method that allows for more control. + +## PARAMETERS + +### -Force + +If specified, this function will not prompt for confirmation before enabling implied reflection. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf + +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm + +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## INPUTS + +### None + +This function does not accept input from the pipeline. + +## OUTPUTS + +### None + +This function does not output to the pipeline. + +## NOTES + +This function will not work in scripts as it relys on objects being passed to Out-Default. This is also working as intended, as it's meant to be a tool for exploration and should not be used in scripts. + +## RELATED LINKS + +[Add-PrivateMember](Add-PrivateMember.md) +[Disable-ImpliedReflection](Disable-ImpliedReflection.md) diff --git a/module/ImpliedReflection.psd1 b/module/ImpliedReflection.psd1 new file mode 100644 index 0000000..4a156f7 --- /dev/null +++ b/module/ImpliedReflection.psd1 @@ -0,0 +1,92 @@ +# +# Module manifest for module 'ImpliedReflection' +# +# Generated by: Patrick Meinecke +# +# Generated on: 7/23/2017 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'ImpliedReflection.psm1' + +# Version number of this module. +ModuleVersion = '0.1.0' + +# ID used to uniquely identify this module +GUID = '8834a5bf-9bf2-4f09-8415-3c1e561109f6' + +# Author of this module +Author = 'Patrick Meinecke' + +# Company or vendor of this module +CompanyName = 'Community' + +# Copyright statement for this module +Copyright = '(c) 2017 Patrick Meinecke. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'Explore private properties and methods as if they were public.' + +# Minimum version of the Windows PowerShell engine required by this module +PowerShellVersion = '5.1' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +DotNetFrameworkVersion = '4.0' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +CLRVersion = '4.0' + +# Processor architecture (None, X86, Amd64) required by this module +ProcessorArchitecture = 'None' + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'Add-PrivateMember', + 'Disable-ImpliedReflection', + 'Enable-ImpliedReflection' + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = @() + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +# List of all files packaged with this module +FileList = 'ImpliedReflection.psd1', + 'ImpliedReflection.psm1', + 'Public\Add-PrivateMember.ps1', + 'Public\Disable-ImpliedReflection.ps1', + 'Public\Enable-ImpliedReflection.ps1' + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @() + + # A URL to the license for this module. + LicenseUri = 'https://github.com/SeeminglyScience/ImpliedReflection/blob/master/LICENSE' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/SeeminglyScience/ImpliedReflection' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +} + + + diff --git a/module/ImpliedReflection.psm1 b/module/ImpliedReflection.psm1 new file mode 100644 index 0000000..937bf9e --- /dev/null +++ b/module/ImpliedReflection.psm1 @@ -0,0 +1,12 @@ +Import-LocalizedData -BindingVariable Strings -FileName Strings -ErrorAction Ignore + +# Include all function files. +Get-ChildItem $PSScriptRoot\Public\*.ps1, $PSScriptRoot\Private\*.ps1 | ForEach-Object { + . $PSItem.FullName +} + +# Export only the functions using PowerShell standard verb-noun naming. +# Be sure to list each exported functions in the FunctionsToExport field of the module manifest file. +# This improves performance of command discovery in PowerShell. +Export-ModuleMember -Function *-* + diff --git a/module/Public/Add-PrivateMember.ps1 b/module/Public/Add-PrivateMember.ps1 new file mode 100644 index 0000000..6cd8b8b --- /dev/null +++ b/module/Public/Add-PrivateMember.ps1 @@ -0,0 +1,201 @@ +using namespace System.Reflection +using namespace System.Linq +using namespace System.Management.Automation + +function Add-PrivateMember { + <# + .EXTERNALHELP ImpliedReflection-help.xml + #> + [CmdletBinding(PositionalBinding=$false)] + param( + [Parameter(Position=0)] + [string] + $ReturnPropertyName, + + [Parameter(ValueFromPipeline)] + [psobject] + $InputObject, + + [switch] + $PassThru + ) + begin { + $FLAG_MAP = @{ + StaticAll = [BindingFlags]'Static, NonPublic, Public' + Static = [BindingFlags]'Static, NonPublic' + Instance = [BindingFlags]'Instance, NonPublic' + } + + function GetCacheEntry([MemberInfo[]] $member) { + if ($member[0] -is [MethodBase]) { + return NewMethodCacheEntry $member + } + return NewPropertyCacheEntry $member[0] + } + + function NewPropertyCacheEntry([MemberInfo] $property) { + $cacheEntry = [ref].Assembly. + GetType('System.Management.Automation.DotNetAdapter+PropertyCacheEntry'). + GetConstructor(60, $null, @($property.GetType()), 1). + Invoke($property) + + # The cache entry uses these fields to determine if the properties for the + # Getter/Setter delegates should create the delegate. They are set to false during + # construction if the method isn't public. + $cacheEntry.GetType().GetField('writeOnly', 60).SetValue($cacheEntry, $false) + $isWritable = $property.SetMethod -or + ('Field' -eq $property.MemberType -and + -not $property.IsInitOnly) + + if ($isWritable) { + $cacheEntry.GetType().GetField('readOnly', 60).SetValue($cacheEntry, $false) + } + return $cacheEntry + } + + function NewMethodCacheEntry([MethodBase[]] $methods) { + # PowerShell tries to flatten the arrays without explicitly constructing them like this. + $argumentList = [object[]]::new(1) + $argumentList[0] = $methods + + return [ref]. + Assembly. + GetType('System.Management.Automation.DotNetAdapter+MethodCacheEntry'). + GetConstructor($FLAG_MAP.Instance, $null, @([MethodBase[]]), 1). + Invoke($argumentList) + } + + function NewPSMethod([Lookup[string, MethodInfo]] $methodGroups) { + foreach ($methodGroup in $methodGroups) { + if ($InputObject.psobject.Methods[$methodGroup.Key]) { continue } + $adapterData = NewMethodCacheEntry $methodGroup + + # Mainly cosmetic, just shows or doesn't show in Get-Member if this is set. + $isSpecial = $methodGroup.Key -match '(set|get|add|remove)_\w+$' + + $psMethod = [PSMethod].InvokeMember( + <# name: #> '', + <# invokeAttr: #> [BindingFlags]'CreateInstance, Instance, NonPublic', + <# binder: #> $null, + <# target: #> $null, + <# args: #> @( + <# name: #> $methodGroup.Key, + <# adapter: #> $bindings.Adapter, + <# baseObject: #> $InputObject.psobject.BaseObject, + <# adapterData: #> $adapterData, + <# isSpecial: #> $isSpecial, + <# isHidden: #> $isSpecial)) + + # The true here is "preValidated", it throws an exception about not being able to add + # PSMethods if you don't claim to have validated it. + $psMethod # yield + } + } + + function BindInstanceProperties([MemberInfo[]] $properties) { + foreach ($property in $properties) { + + if ($InputObject.psobject.Properties[$property.Name]) { continue } + + $adapterData = NewPropertyCacheEntry $property + + $psProperty = [PSProperty].InvokeMember( + <# name: #> '', + <# invokeAttr: #> [BindingFlags]'CreateInstance, Instance, NonPublic', + <# binder: #> $null, + <# target: #> $null, + <# args: #> @( + <# name: #> $property.Name, + <# adapter: #> $bindings.Adapter, + <# baseObject: #> $InputObject.psobject.BaseObject, + <# adapterData: #> $adapterData)) + + $InputObject.psobject.Properties.Add($psProperty, $true) + } + } + + function GetCacheTable([string] $memberType) { + return $bindings.Adapter. + GetType(). + GetMethod("GetStatic${memberType}ReflectionTable", $FLAG_MAP.Static). + Invoke($null, @($bindings.Type)) + } + + function GetBindingInfo([psobject] $target) { + if ($target -is [type]) { + return @{ + IsStatic = $true + Type = $target.psobject.BaseObject + Flags = $FLAG_MAP.Static + Adapter = [psobject]. + GetField('dotNetStaticAdapter', $FLAG_MAP.Static). + GetValue($null) + } + } + return @{ + IsStatic = $false + Type = $target.GetType() + Flags = $FLAG_MAP.Instance + Adapter = [PSMethod]. + GetField('adapter', $FLAG_MAP.Instance). + GetValue($target.psobject.Methods.Item('GetType')) + } + } + } + process { + $bindings = GetBindingInfo $InputObject + $members = @{ + Method = $bindings.Type.GetMethods($bindings.Flags) + Property = $bindings.Type.GetProperties($bindings.Flags) + + $bindings.Type.GetFields($bindings.Flags) + } + + # Group methods by name so we can add them to the same cache entry. + if ($members.Method) { + $members.Method = [Enumerable]::ToLookup( + $members.Method, + [Func[MethodInfo, string]]{ $args[0].Name }) + } + + if ($bindings.IsStatic) { + # Cache the add method for the loop. + $add = [ref]. + Assembly. + GetType('System.Management.Automation.CacheTable'). + GetMethod('Add', $FLAG_MAP.Instance) + + foreach ($memberType in 'Method', 'Property') { + $table = GetCacheTable $memberType + foreach ($member in $members.$memberType) { + + $memberName = $member.Key + if (-not $memberName) { $memberName = $member.Name } + + $alreadyExists = $table. + GetType(). + GetField('indexes', $FLAG_MAP.Instance). + GetValue($table). + ContainsKey($memberName) + + if (-not $alreadyExists) { + $cacheEntry = GetCacheEntry $member + + $null = $add.Invoke($table, @($memberName, $cacheEntry)) + + } + } + } + } else { + $psMethods = NewPSMethod $members.Method + foreach ($psMethod in $psMethods) { + $InputObject.psobject.Methods.Add($psMethod, $true) + } + BindInstanceProperties $members.Property + } + if ($ReturnPropertyName) { + $InputObject.$ReturnPropertyName + } elseif ($PassThru.IsPresent) { + $InputObject # yield + } + } +} diff --git a/module/Public/Disable-ImpliedReflection.ps1 b/module/Public/Disable-ImpliedReflection.ps1 new file mode 100644 index 0000000..86852d1 --- /dev/null +++ b/module/Public/Disable-ImpliedReflection.ps1 @@ -0,0 +1,36 @@ +using namespace System.Management.Automation +using namespace System.Reflection + +function Disable-ImpliedReflection { + <# + .EXTERNALHELP ImpliedReflection-help.xml + #> + [CmdletBinding()] + param() + end { + if (-not $script:OriginalOutDefault) { + $PSCmdlet.ThrowTerminatingError( + [ErrorRecord]::new( + [InvalidOperationException]::new('Implied reflection is not enabled.'), + 'ImpliedReflectionDisabled', + [ErrorCategory]::InvalidOperation, + $null)) + } + foreach ($visibilityScope in 'static', 'instance') { + foreach ($memberType in 'Property', 'Method') { + [ref].Assembly. + GetType('System.Management.Automation.DotNetAdapter'). + GetField("${visibilityScope}${memberType}CacheTable", [BindingFlags]'Static, NonPublic'). + GetValue($null). + Clear() + } + } + + if ($script:OriginalOutDefault -is [CmdletInfo]) { + Get-Item function:\Out-Default | Remove-Item + } elseif ($script:OriginalOutDefault -is [FunctionInfo]) { + Set-Content function:\Out-Default -Value $script:OriginalOutDefault.ScriptBlock + } + Remove-Variable -Scope Script -Name OriginalOutDefault + } +} diff --git a/module/Public/Enable-ImpliedReflection.ps1 b/module/Public/Enable-ImpliedReflection.ps1 new file mode 100644 index 0000000..3b65f7c --- /dev/null +++ b/module/Public/Enable-ImpliedReflection.ps1 @@ -0,0 +1,44 @@ +using namespace System.Management.Automation + +function Enable-ImpliedReflection { + <# + .EXTERNALHELP ImpliedReflection-help.xml + #> + [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')] + param( + [switch] + $Force + ) + end { + if ($script:OriginalOutDefault) { + $PSCmdlet.ThrowTerminatingError( + [ErrorRecord]::new( + [InvalidOperationException]::new('Implied reflection is already enabled.'), + 'ImpliedReflectionAlreadyEnabled', + [ErrorCategory]::InvalidOperation, + $null)) + } + $outDefault = $ExecutionContext.InvokeCommand.GetCommand('Out-Default', 'All') + $script:OriginalOutDefault = $outDefault + $proxy = [ProxyCommand]::Create($outDefault) + $injectedProxy = $proxy -replace + '(process(\r?\n){\s+try {\r?\n)', + '$1 if ($null -ne $PSItem) { $null = Add-PrivateMember -InputObject $PSItem$2 }' + + $function = 'function global:Out-Default {', + $injectedProxy, + '}' -join [Environment]::NewLine + + if (-not $Force.IsPresent) { + $shouldProcess = $PSCmdlet.ShouldProcess( + $Strings.EnableIRWhatIf, + $Strings.EnableIRConfirmMessage, + $Strings.EnableIRConfirmTitle) + } + $definer = [scriptblock]::Create($function) + + if ($shouldProcess -or $Force.IsPresent) { + . $definer + } + } +} diff --git a/module/en-US/Strings.psd1 b/module/en-US/Strings.psd1 new file mode 100644 index 0000000..4e098ba --- /dev/null +++ b/module/en-US/Strings.psd1 @@ -0,0 +1,5 @@ +ConvertFrom-StringData -StringData @' +EnableIRWhatIf=Injecting Out-Default with a command to bind all private and internal members for all outputted objects for the rest of the session. +EnableIRConfirmMessage=If you continue, the default output command will be injected with a command that binds all private and internal members to any object outputted for the rest of the session. This can have unintended consequences, are you sure you want to continue? +EnableIRConfirmTitle=Confirm +'@ diff --git a/test/Add-PrivateMember.Tests.ps1 b/test/Add-PrivateMember.Tests.ps1 new file mode 100644 index 0000000..c593261 --- /dev/null +++ b/test/Add-PrivateMember.Tests.ps1 @@ -0,0 +1,70 @@ +$moduleName = 'ImpliedReflection' +$manifestPath = "$PSScriptRoot\..\module\$moduleName.psd1" + +Import-Module $manifestPath +Describe 'Session isn''t dirty ' { + It 'has a normal $ExecutionContext' { + $ExecutionContext.psobject.Properties.Item('_context') | Should BeNullOrEmpty + } +} +Describe 'Add-PrivateMember operation' { + Context 'instance checks' { + It 'can add private instance members' { + Add-PrivateMember -InputObject $ExecutionContext + } + It 'can access fields' { + $ExecutionContext._context | Should Not BeNullOrEmpty + $ExecutionContext._context.GetType().Name | Should Be 'ExecutionContext' + } + It 'can access properties' { + Add-PrivateMember -InputObject $ExecutionContext.SessionState + $ExecutionContext.SessionState.Internal.GetType().Name | Should Be 'SessionStateInternal' + } + It 'can invoke methods' { + Add-PrivateMember -InputObject $ExecutionContext.SessionState.Internal + $ExecutionContext.SessionState.Internal.GetFunction('prompt') | + Should BeOfType 'System.Management.Automation.FunctionInfo' + } + It 'can invoke methods with ByRef parameters' { + $scope = $null + $ExecutionContext.SessionState.Internal.GetVariableItem('ExecutionContext', [ref]$scope) | + Should BeOfType 'psvariable' + + $scope.GetType().Name | Should Be 'SessionStateScope' + + } + It 'does not implicitly add properties without being enabled' { + $ExecutionContext._context._formatDBManager | Should Be $null + } + It 'can chain with return property parameter' { + $ExecutionContext | + Add-PrivateMember _context | + Add-PrivateMember Modules | + Add-PrivateMember _moduleTable | + Should BeOfType 'System.Collections.Generic.Dictionary[string,psmoduleinfo]' + } + It 'returns the object with PassThru' { + $ExecutionContext | + Add-PrivateMember -PassThru | + Should BeOfType 'System.Management.Automation.EngineIntrinsics' + } + } + Context 'static checks' { + It 'can add static members' { + [scriptblock] | Add-PrivateMember + } + It 'can access properties' { + [scriptblock]::EmptyScriptBlock | Should BeOfType scriptblock + } + It 'can access fields' { + [scriptblock]::_cachedScripts.GetType().Name | Should Be 'ConcurrentDictionary`2' + } + It 'can access methods' { + [scriptblock]::TokenizeWordElements('one two three') | Should Be 'one','two','three' + } + It 'added methods are the correct type' { + [scriptblock]::BindArgumentsForScripblockInvoke | + Should BeOfType 'System.Management.Automation.PSMethod' + } + } +} diff --git a/test/Disable-ImpliedReflection.Tests.ps1 b/test/Disable-ImpliedReflection.Tests.ps1 new file mode 100644 index 0000000..8696cfc --- /dev/null +++ b/test/Disable-ImpliedReflection.Tests.ps1 @@ -0,0 +1,42 @@ +$moduleName = 'ImpliedReflection' +$manifestPath = "$PSScriptRoot\..\module\$moduleName.psd1" + +Import-Module $manifestPath + +Describe 'Disable-ImpliedReflection operation' { + BeforeAll { + # Disable before tests if already enabled. + try { Disable-ImpliedReflection -ErrorAction Ignore } catch {} + } + It 'throws if not enabled' { + { Disable-ImpliedReflection } | Should Throw 'is not enabled' + } + It 'runs successfully if was enabled' { + Enable-ImpliedReflection -Force + Disable-ImpliedReflection + } + It 'stops adding members after disable' { + $ps = [powershell]::Create() + try { + $result = $ps.AddScript(' + Import-Module $args[0] + + Enable-ImpliedReflection -Force + + $Host | Out-Default + $Host + + Disable-ImpliedReflection + $ExecutionContext'). + AddArgument($manifestPath). + Invoke() + if ($ps.HadErrors) { + throw $ps.Streams.Error + } + } finally { + if ($ps) { $ps.Dispose() } + } + $result[0].Context.GetType().Name | Should Be 'ExecutionContext' + $result[1]._context | Should Be $null + } +} diff --git a/test/Enable-ImpliedReflection.Tests.ps1 b/test/Enable-ImpliedReflection.Tests.ps1 new file mode 100644 index 0000000..cbf6dee --- /dev/null +++ b/test/Enable-ImpliedReflection.Tests.ps1 @@ -0,0 +1,64 @@ +$moduleName = 'ImpliedReflection' +$manifestPath = "$PSScriptRoot\..\module\$moduleName.psd1" + +Import-Module $manifestPath + +function OutConsoleProxy { + param([Parameter(ValueFromPipeline)]$InputObject) + process { + $ps = [powershell]::Create() + try { + $ps.AddScript(' + param($modulePath, $inputObject) + Import-Module $modulePath + Enable-ImpliedReflection -Force + $inputObject | Out-Default'). + AddArgument($manifestPath). + AddArgument($InputObject). + Invoke() + if ($ps.HadErrors) { + throw $ps.Streams.Error + } + } finally { + if ($ps) { $ps.Dispose() } + } + } +} + +Describe 'Enable-ImpliedReflection operation' { + It 'can be enabled' { + (Get-Command Out-Default).Module.Name | Should Not Be 'ImpliedReflection' + Enable-ImpliedReflection -Force + (Get-Command Out-Default).Module.Name | Should Be 'ImpliedReflection' + } + It 'throws if already enabled' { + { Enable-ImpliedReflection -Force + Enable-ImpliedReflection -Force } | Should Throw 'already enabled' + } + It 'can implicitly add to $ExecutionContext' { + $ExecutionContext | OutConsoleProxy + $ExecutionContext._context.GetType().Name | Should Be 'ExecutionContext' + } + It 'implicitly adds to $Host' { + $ps = [powershell]::Create() + try { + $ps.AddScript(' + Import-Module $args[0] + + Enable-ImpliedReflection -Force + + $Host | Out-Default + $Host'). + AddArgument($manifestPath). + Invoke(). + Context. + GetType(). + Name | Should Be 'ExecutionContext' + if ($ps.HadErrors) { + throw $ps.Streams.Error + } + } finally { + if ($ps) { $ps.Dispose() } + } + } +} diff --git a/test/ImpliedReflection.Tests.ps1 b/test/ImpliedReflection.Tests.ps1 new file mode 100644 index 0000000..0743a66 --- /dev/null +++ b/test/ImpliedReflection.Tests.ps1 @@ -0,0 +1,15 @@ +$moduleName = 'ImpliedReflection' +$manifestPath = "$PSScriptRoot\..\module\$moduleName.psd1" + +Describe 'module manifest values' { + It 'can retrieve manfiest data' { + $script:manifest = Test-ModuleManifest $manifestPath -ErrorAction Ignore + $script:manifest | Should Not BeNullOrEmpty + } + It 'has the correct name' { + $script:manifest.Name | Should Be $moduleName + } + It 'has the correct guid' { + $script:manifest.Guid | Should Be '8834a5bf-9bf2-4f09-8415-3c1e561109f6' + } +}