Poke.psm1

###########################################
#
# POKE Toolkit 1.1
# By Oisin Grehan (MVP)
#
#
# 1.0 initial release
# 1.1 add support for out/ref parameters
#
###########################################

Set-StrictMode -Version Latest

# libraries to include
. (join-path $PSScriptRoot delegate.ps1)

# global cache for -ascustomobject instance/type proxies
# this is needed for the format helper functions as metadata
# and hints about proxied objects are hidden in ETS member
# attributes
$SCRIPT:proxyTable = @{}

############################################
#
# PS1XML Format function helper definitions
#
############################################

# used in this module (poke) for property/field filter
filter Limit-SpecialMember {
    if (-not ($_.isspecialname -or $_.GetCustomAttributes([System.Runtime.CompilerServices.CompilerGeneratedAttribute], $false).count)) {
        $_
    } else {
        if ($_.isspecialname) {
            Write-Verbose "skipping special member $_"
        } else {
            Write-Verbose "skipping compiler generated $_"
        }
    }
}

function Get-Modifier {
    param(
        [parameter(mandatory=$true)]
        [validatenotnull()]
        [System.Reflection.MemberInfo]$Member
    )

    $modifiers = ""

    try {
        if ($Member.ispublic) {
            $modifiers = "public"
        } elseif ($Member.isFamily) {
            $modifiers = "protected"
        } elseif ($Member.isFamilyOrAssembly) {
            $modifiers = "protected internal"
        } elseif ($Member.isAssembly) {
            $modifiers = "internal"
        } elseif ($Member.isPrivate) {
            $modifiers = "private"
        }
    } catch {
        $modifiers = "ERROR"
    }
    
    # declared readonly?
    if ($member.MemberType -eq "Field") {
      $fieldInfo = [system.reflection.fieldinfo]$member
      if ($fieldInfo.IsInitOnly) {
        $modifiers += " readonly"
      }
    }    

    $modifiers
}

$SCRIPT:formatHelperFunctions = {
    # These functions are defined within module scope for a single instance or type proxy.

    $SCRIPT:adapterType = [psobject].assembly.gettype("System.Management.Automation.DotNetAdapter")
    $SCRIPT:getMethodDefinition = $adapterType.getmethod("GetMethodInfoOverloadDefinition", [reflection.bindingflags]"static,nonpublic")
    
    # cache some often used members for performance reasons
    $SCRIPT:miType = [psobject].assembly.gettype("System.Management.Automation.MethodInformation")
    
    $SCRIPT:miCtor = $mitype.GetConstructor("nonpublic,instance", $null, [type[]]@([reflection.methodinfo], [int]), $null)    
    $SCRIPT:miDefinition = $mitype.GetProperty("methodDefinition", [reflection.bindingflags]"instance,nonpublic")

    $SCRIPT:fieldCall = $false

    # workaround
    if (-not $miDefinition) {
        $SCRIPT:miCtor = $mitype.GetConstructor("nonpublic,instance", $null, [type[]]@([string],[reflection.methodinfo], [int]), $null)
        $SCRIPT:miDefinition = $mitype.GetField("methodDefinition", [reflection.bindingflags]"instance,nonpublic")    
        $SCRIPT:fieldCall = $true
    }

    # used in dynamic modules (proxies) for method filter
    filter Limit-SpecialMember {
        if (-not ($_.isspecialname -or $_.GetCustomAttributes([System.Runtime.CompilerServices.CompilerGeneratedAttribute], $false).count)) {
            $_
        } else {
            if ($_.isspecialname) {
                Write-Verbose "skipping special member $_"
            } else {
                Write-Verbose "skipping compiler generated $_"
            }
        }
    }

    # enhanced .ctor definition with parameter names
    function Get-ConstructorDefinition {
        param(
            [parameter(mandatory=$true)]
            [type]$Type
        )
        $type.GetConstructors("public,nonpublic,instance") | % {
            ".ctor ({0})" -f (($_.getparameters() | % {
                "{0} {1}" -f [microsoft.powershell.tostringcodemethods]::type($_.parametertype), $_.name
            }) -join ", ")
        }
    }

    function Get-MethodDefinition {
        param(
            [parameter(mandatory=$true)]
            [validatenotnull()]
            [reflection.methodinfo]$MethodInfo
        )
        # let powershell do the work
        if ($SCRIPT:fieldCall) {
            $mi = $miCtor.Invoke(@($methodinfo.name, $MethodInfo, 0))
            $miDefinition.getvalue($mi) # field call
        } else {
            $mi = $miCtor.Invoke(@($MethodInfo, 0))
            $miDefinition.getvalue($mi, @()) # prop call
        }
    }

    function Get-MemberDefinition {
        param(
            [parameter(mandatory=$true)]
            [Microsoft.PowerShell.Commands.MemberDefinition]$Member,

            [parameter()]
            [pstypename("Pokeable.Object")]
            [psobject]$Proxy
        )

        # NOTE: we don't want to recursively trigger ETS so use psbase
        $memberType = $member.psbase.MemberType

        switch ($memberType) {
            
            ScriptProperty {
                # Property* or Field*
                $baseMemberType = $member.MemberType.split(":")[0] # grab _our_ ETS value (not using psbase!)

                # TODO: show {get;} or {get;set;} depending on readonly or not
                # TODO: show modifiers for get/set if different; e.g. {get;private set;}
                $getset = $(if ($baseMemberType -eq "Property*") { " { get; set; }" })

                "{0}{1} {2}{3}" -f "", $proxy.psobject.Members[$member.Name].TypeNameOfValue, $Member.Name, $getset
            }

            ScriptMethod {
                switch ($member.Name) {
                    __CreateInstance {
                        $baseObject = $Proxy.__GetBaseObject()
                        if ($baseObject -is [type]) {
                            (Get-ConstructorDefinition -Type $baseObject) -join ", "
                        } else {
                            (Get-ConstructorDefinition -Type $baseObject.gettype()) -join ", "
                        }
                    }
                
                    __GetBaseObject {
                        $baseObject = $Proxy.__GetBaseObject()
                        if ($baseObject -is [type]) {
                            "type __GetBaseObject()"
                        } else {
                            "{0} __GetBaseObject()" -f [Microsoft.PowerShell.ToStringCodeMethods]::Type($baseObject.gettype())
                        }
                    }
                
                    __GetModuleInfo {
                        "psmoduleinfo __GetModuleInfo()"
                    }
                    
                    ToString {
                        "string ToString()"
                    }

                    default {
                        # retrieve from scriptmethod scriptblock attributes
                        $body = $proxy.psobject.members[$member.Name].script
                        $description = $body.Attributes.Find( { $args[0] -is [System.ComponentModel.DescriptionAttribute] })
                        if ($description) {
                            # overloads cached in description attribute above param block
                            $description.description
                        } else {
                            "..."
                        }
                    }
                }
            }
            
            Method {
                # pass through
                $Member.psbase.Definition
            }
            
            Property {
                # pass through
                $Member.psbase.definition
            }
        }
    }

    # computes modifiers for a memberdefinition instance (public, private, internal, static etc)
    function Get-MemberModifier {
        param(
            [parameter(mandatory=$true)]
            [Microsoft.PowerShell.Commands.MemberDefinition]$Member,

            [parameter()]
            [pstypename("Pokeable.Object")]
            [psobject]$Proxy
        )
        # modifiers are cached in exported function description
        Write-Verbose "getting function description for $($Member.psbase.name)"
        
        switch ($member.psbase.MemberType) {
            ScriptProperty {
                $getter = $proxy.psobject.members[$member.Name].getterscript
                $description = $getter.Attributes.Find( { $args[0] -is [System.ComponentModel.DescriptionAttribute] })
                $description.description.split(":")[1]
            }

            ScriptMethod {
                try {
                    $description = (get-item function:"$($Member.psbase.name)").Description
                    if ($description) {
                        $description.split(":")[0]
                    } else {
                        # special cases
                        if ($member.psbase.name -eq "ToString") {
                            "public"
                        } else {
                            # special case proxy helpers, like __CreateInstance, __GetModuleInfo etc
                            "-"
                        }
                    }
                } catch { "-" } # no description property on function
            }
            default { "public" }
        }
    }

    # computes member type for a memberdefinition (e.g. replaces ScriptProperty with Field or Property
    # and ScriptMethod with Method)
    function Get-MemberType {
        param(
            [parameter(mandatory=$true)]
            [Microsoft.PowerShell.Commands.MemberDefinition]$Member,
            
            [parameter()]
            [pstypename("Pokeable.Object")]
            [psobject]$Proxy
        )

        # don't want to recursive trigger ETS so use psbase
        $memberType = $member.psbase.MemberType

        switch ($memberType) {
            ScriptProperty {
                # the proxied member type is cached in a description attribute on the scriptproperty's getterscript (e.g. field/property)
                $getter = $proxy.psobject.members[$member.Name].getterscript
                $description = $getter.Attributes.Find( { $args[0] -is [System.ComponentModel.DescriptionAttribute] })
                $description.description.split(":")[0]
            }
            ScriptMethod {
                # add asterisk to differentiate between methods on the psobject (gettype etc) and proxied members
                "Method*"
            }
            default {
                # catch all
                $memberType
            }
        }
    }

    ###
    #
    # ByRef helper
    #
    ###

    filter ConvertFrom-PSReference {
        if ([ref].IsAssignableFrom($_)) {        
            $_.generictypearguments[0].makebyreftype()
        } else {
            $_
        }
    }
}

############################################
#
# Proxy Method generator definition (lambda)
#
############################################

$SCRIPT:initializer = {
    param(
        [Parameter(mandatory=$true, position=0)]
        [validatenotnull()]
        $baseObject,

        [Parameter(mandatory=$true, position=1)]
        [reflection.bindingflags]$flags,

        [switch]$IncludeCompilerGenerated,

        [switch]$IncludeSpecialName
    )

    Write-Progress -Id 1 -Activity "Peek" -Status "Initializing methods..."

    write-verbose "Method initializer."

    if ($baseObject.gettype().Name -eq "RuntimeType") {
        # type
        Write-Verbose "`$baseObject is a Type"
        $methodInfos = $baseobject.getmethods($flags)| Limit-SpecialMember
        $baseType = $baseObject
    } else {
        # instance
        Write-Verbose "`$baseObject is an instance"
        $methodInfos = $baseObject.GetType().getmethods($flags) | Limit-SpecialMember
        $baseType = $baseObject.GetType()
    }

    foreach ($method in @($methodInfos|sort name -unique)) {

        $methodName = $method.name        

        write-verbose "Creating method $methodname`(...`); building method definitions..."
        $overloads = ($methodInfos|? name -eq $methodName|% { Get-MethodDefinition $_ }) -join ", "

        # psscriptmethod ignores outputtype - maybe this will get fixed in later releases of ps?
        # ultimately it's of dubious use for methods as overloads may differ in return type.
        # of course, they must have differing parameters too as a method cannot differ _only_ by return type.
        $definition = new-item function:script:$methodName -value ([scriptblock]::create("
            # cache overloads in description attribute which is easily retrieved from this
            # scriptblock's attributes property when emitting memberdefinition definition
            [componentmodel.description('$overloads')]
            param();
 
            write-verbose 'called $methodName'
            [reflection.bindingflags]`$binding = '$flags'
 
            try {
                if ((`$overloads = @(`$baseType.getmethods(`$binding)|? name -eq '$methodname')).count -gt 1) {
                    write-verbose 'self $self ; flags: $flags ; finding best fit overload'
                    `$types = [type]::gettypearray(`$args).foreach({
                        if (`$_.basetype -eq [ref]) {
                            `$_.generictypearguments[0].makebyreftype() # fix up [ref][int] to [int&]
                        } else { `$_ }
                    })
                    `$method = `$baseType.getmethod('$methodname', `$binding, `$null, `$types, `$null)
                    if (-not `$method) {
                        write-warning ""Could not find best fit overload for `$(`$types -join ',').""
                        throw
                    }
                } else {
                    write-verbose 'single method; no overloads'
                }
 
                `$inargs = new-object 'object[]' `$args.length
                [array]::copy(`$args, `$inargs, `$args.length)
 
                # deref [ref] to value
                for (`$i = 0; `$i -lt `$inargs.length; `$i++) {
                    `$elem = `$inargs[`$i]
                    if (`$elem -is [ref]) {
                        `$inargs[`$i] = `$elem.value
                    }
                }
 
                # invoke
                write-verbose ""invoking '`$method' with: `$(`$inargs -join ',')""
                `$method.invoke(`$self, `$binding, `$null, `$inargs, `$null)
 
                # update any [ref] values in original `$args
                for (`$i = 0; `$i -lt `$args.length; `$i++) {
                    `$elem = `$args[`$i]
                    if (`$elem -is [ref]) {
                        `$elem.value = `$inargs[`$i]
                    }
                }
 
            } catch {
 
                # TODO: remove this redundant check
 
                if (`$_.exception.innerexception -is [Reflection.TargetParameterCountException]) {
                         
                    write-warning ""Could not find matching overload with `$(`$args.count) parameter(s).""
                    # dump overloads (public methods only?)
                    #`$self.'$methodname'
 
                } else {
                    # error is from invocation target, rethrow
                    write-verbose 'rethrow on invoke'
                    throw
                }
            }"
)).GetNewClosure()
            
        # isfamily: protected
        # isfamilyORassembly: protected internal
        # isassembly: internal
        # isprivate: private

        $modifiers = ""
        if ($method.ispublic) {
            $modifiers = "public"
        } elseif ($method.isFamily) {
            $modifiers = "protected"
        } elseif ($method.isFamilyOrAssembly) {
            $modifiers = "protected internal"
        } elseif ($method.isAssembly) {
            $modifiers = "internal"
        } elseif ($method.isPrivate) {
            $modifiers = "private"
        }
        
        $definition.description = $modifiers + ":" + $(if ($method.isstatic) { "static" } else { "" })
        
        export-modulemember $methodname
    } # /foreach method
}

############################################
#
# Type Proxy
#
############################################

function New-TypeProxy {
<#
 
#>

    [cmdletbinding()]
    param(        
        [parameter()]
        [validatenotnullorempty()]
        [string]$TypeName,
        [switch]$CaseSensitive
    )

    $type = Find-Type $typeName `
        -CaseSensitive:$CaseSensitive                

    if (-not $type) {
        write-warning "Could not find ${typeName}. Are you sure the containing assembly has been loaded?"
        return
    }
        
    # Create TypeProxy
    $proxy = new-module -ascustomobject -name "Pokeable.System.RuntimeType#$($type.fullname)" {
        param(
            [type]$type,
            [scriptblock]$initializer,
            [scriptblock]$formatHelperFunctions
        )
         
        Set-StrictMode -Version latest

        function apply {
            param([scriptblock]$block)
            . $ExecutionContext.SessionState.Module.NewBoundScriptBlock($block) @args
        }
        
        function __CreateInstance {
            write-verbose "Type is $type ; `$args count is $($args.count)"

            if ($type.IsAbstract) {
                write-warning "Type is abstract."
                return
            }
            
            $types = @()
            $args|%{if($_ -eq $null){$types+=$null}else{$types+=$_.gettype()}}
            write-verbose ".ctor args: length $($types.length)"

            $ctor = $type.GetConstructor("Public,NonPublic,Instance", $null, $types, $null)

            if (-not $ctor) {
                write-warning "No matching constructor found. Available constructors:"
                Get-ConstructorDefinition $type | % { write-host -ForegroundColor green " $_" }
                return 
            }

            # return wrapped object
            try {
                New-InstanceProxy $ctor.invoke($args) -verbose
            } catch {
                write-warning "Could not create instance: $_"
            }
        }
        
        $self = $type

        # bind format helper functions to this module's scope
        . apply $formatHelperFunctions        

        # define methods
        . apply $initializer $type "Public,NonPublic,Static,DeclaredOnly"
         
        function __GetBaseObject {
            $type
        }
        
        function __GetModuleInfo {
            $ExecutionContext.SessionState.Module
        }
        
        function ToString {
            "Pokeable.System.RuntimeType#$($type.fullname)"
        }        

        export-modulemember __CreateInstance, __GetBaseObject, __GetModuleInfo, ToString
        
    } -args $type, $initializer, $formatHelperFunctions
    
    if ($proxy) {        
    
        $proxy.psobject.typenames.insert(0, "Pokeable.Object")
        $proxy.psobject.typenames.insert(0, "Pokeable.System.RuntimeType#$($type.fullname)")

        Add-fields $proxy "Public,NonPublic,DeclaredOnly,Static" > $null
        Add-properties $proxy "Public,NonPublic,DeclaredOnly,Static" > $null

        write-verbose "Registering in proxyTable"
        $proxyTable[$proxy.tostring()] = $proxy

        $proxy
    }
}    

############################################
#
# Instance Proxy
#
############################################

function New-InstanceProxy {
<#
 
#>

    [cmdletbinding()]
    param(
        [parameter(mandatory=$true)]
        [validatenotnull()]
        [object]$Instance,
        [switch]$IncludeInheritedMembers, # not implemented
        [switch]$ExcludePublic, # not implemented
        [switch]$ExcludeFields # not implemented
    )
    
    $instanceId = [guid]::NewGuid()
        
    $type = $instance.GetType()
        
    $proxy = new-module -ascustomobject -name "Pokeable.$($type.fullname)#$instanceId" -verbose {
        param(
            $self,
            $instanceId,
            [scriptblock]$initializer,
            [scriptblock]$formatHelperFunctions
        )
        
        Set-StrictMode -Version latest
    
        function apply {
            param([scriptblock]$block)
            . $ExecutionContext.SessionState.Module.NewBoundScriptBlock($block) @args
        }

        $type = $self.gettype()
        write-verbose "Created an instance of $type"

        # bind format helper functions to this module's scope
        . apply $formatHelperFunctions
        
        # define methods
        . apply $initializer $self "Public,NonPublic,DeclaredOnly,Instance"

        function __GetModuleInfo {
            $ExecutionContext.SessionState.Module
        }

        function __GetBaseObject {
            $self
        }
        
        function ToString() {
            "Pokeable.$($type.fullname)#$instanceId"
        }

        export-modulemember __GetBaseObject, __GetModuleInfo, ToString

        # register dispose handler on module remove
        $ExecutionContext.SessionState.Module.OnRemove = {

            # TODO: handle Close
            if ($self.Dispose) { # -as IDisposable?

                # will fail on explicit idisposable.dispose
                $self.Dispose()
            }
        }
    } -args $instance, $instanceId, $initializer, $formatHelperFunctions
    
    if ($proxy) {
    
        $psobject = $proxy.psobject        
        
        $psobject.typenames.insert(0, "Pokeable.Object")
        $psobject.typenames.insert(0, "Pokeable.$($type.fullname)#$instanceId")

        Add-fields $proxy "Public,NonPublic,DeclaredOnly,Instance" > $null        
        Add-properties $proxy "Public,NonPublic,DeclaredOnly,Instance" > $null

        write-verbose "Registering in proxyTable"
        $proxyTable[$proxy.tostring()] = $proxy
                
        $proxy
    }
}

############################################
#
# Add Fields
#
############################################

function Add-Fields {
    param(
        [Parameter(mandatory=$true, position=0)]
        [validatenotnull()]
        [pstypename("Pokeable.Object")]
        $baseObject,

        [Parameter(mandatory=$true, position=1)]
        [reflection.bindingflags]$flags
    )

    Write-Progress -Id 1 -Activity "Peek" -Status "Initializing fields..."

    if ($baseObject.__GetBaseObject() -is [type]) {
        $type = $baseObject.__GetBaseObject()
        $self = $type
    } else {
        $type = $baseObject.__GetBaseObject().gettype()
        $self = $baseObject.__GetBaseObject()
    }

    $fields = $type.getfields($flags)
    $psobject = $baseObject.psobject

    # add fields
    foreach ($field in ($fields|limit-specialmember|sort name)) {
        
        # clean up type string for generics and accelerated types
        $outputType = [Microsoft.PowerShell.ToStringCodeMethods]::type($field.FieldType)

        $modifiers = Get-Modifier $field

        # close over field and instance vars but insert literal for fieldtype
        $getter = [scriptblock]::create(
            "[componentmodel.description('Field*:$modifiers')][outputtype('$outputtype')]param(); `$field.GetValue(`$self)").GetNewClosure()

      
        # if readonly then IsInitOnly would be flagged, but reflection can still invoke the setter so we ignore
        # TODO: strongly type $value parameter in setter
        $setter = { param($value); $field.SetValue($self, $value) }.GetNewClosure()            
        $fieldDef = New-Object management.automation.psscriptproperty $field.Name, $getter, $setter

        write-verbose "Adding $flags field $($field.name) InitOnly: $($field.isinitonly)"
        
        $psobject.properties.add($fieldDef)
    }
}

############################################
#
# Add Properties
#
############################################

function Add-Properties {
    param(
        [Parameter(mandatory=$true, position=0)]
        [validatenotnull()]
        [pstypename("Pokeable.Object")]
        $baseObject,

        [Parameter(mandatory=$true, position=1)]
        [reflection.bindingflags]$flags
    )

    Write-Progress -Id 1 -Activity "Peek" -Status "Initializing properties..."

    if ($baseObject.__GetBaseObject() -is [type]) {
        $type = $baseObject.__GetBaseObject()
        $self = $type
    } else {
        $type = $baseObject.__GetBaseObject().gettype()
        $self = $baseObject.__GetBaseObject()
    }
    $properties = $type.getproperties($flags)
    $psobject = $baseObject.psobject

    # add properties
    foreach ($property in ($properties|limit-specialmember|sort name)) {
       
        # clean up type string for generics and accelerated types
        $outputType = [Microsoft.PowerShell.ToStringCodeMethods]::type($property.PropertyType)

        write-verbose ("property: {0} {1} {{ ... }}" -f $outputtype, $property.name)

        $getmethod = $property.GetGetMethod(<# nonpublic: #>$true)  # 4.0 -- 4.5 can use GetMethod property
        $setmethod = $(if ($property.CanWrite) { $property.GetSetMethod(<# nonpublic: #>$true) } else { $getmethod }) # 4.0 -- 4.5 can use SetMethod property

        if ((Get-Modifier $getmethod) -eq (Get-Modifier $setmethod)) {
            # readonly prop, or getter/setter have same visibility
            $modifiers = Get-Modifier $getmethod
        } else {
            # getter/setter have different visibility
            # TODO: highlight this in definition with { private get; internal set; }
            $modifiers = "-"
        }

        # property getter
        $getter = [scriptblock]::create(
            "[componentmodel.description('Property*:$modifiers')][outputtype('$outputType')]param(); `$property.GetValue(`$self, @())").GetNewClosure()
            #"[componentmodel.description('Property*:$modifiers')][outputtype('$outputType')]param(); if (`$value = `$property.GetValue(`$self, @())) { peek `$value }").GetNewClosure()

        # I don't account for setter-only properties
        if (-not $property.CanWrite) {
            $propertyDef = New-Object management.automation.psscriptproperty $property.Name, $getter
        } else {
            # TODO: strongly type $value parameter in setter
            # property setter
            $setter = { param($value); $property.SetValue($self, $value, @()) }.GetNewClosure()
            $propertyDef = New-Object management.automation.psscriptproperty $property.Name, $getter, $setter
        }
        write-verbose "Adding $flags property $($property.name)"
        $psObject.properties.add($propertyDef)
    }
}

############################################
#
# Find Type
#
############################################

function Find-Type {
    param(
        [string]$TypeName,
        [reflection.bindingflags]$BindingFlags = "Public,NonPublic",
        [switch]$CaseSensitive
    )
    
    write-verbose "Searching for $typeName"

    $assemblies = [appdomain]::CurrentDomain.GetAssemblies()
        
    $matches = @()
    
    $assemblies | % {
        #write-verbose "Searching $($_.getname().name)..."
        
        $match = $_.gettype($typename, $false, !$CaseSensitive)
        if ($match) {
            $matches += $match
        }        
    }
    
    write-verbose "Found $($matches.length) match(es)."
        
    $matches
}


############################################
#
# New Object Proxy (peek)
#
############################################

function New-ObjectProxy {
<#
.SYNOPSIS
Return a type or instance proxy of a managed type
 
.DESCRIPTION
Return a type or instance proxy of a managed type, exposing all non-public fields, properties and methods.
Methods can be invoked, fields written to and private properties set. To see modifiers and method definitions
use the standard command of Get-Member. Use the following meta-methods to work with proxies:
 
* __CreateInstance() Create an instance of a proxied System.Type.
 
* __GetBaseObject() Get the proxied System.Type or instance.
 
.PARAMETER InputObject
Accepts a Type or instance from the pipeline. Can also accept input as the first positional parameter.
 
.PARAMETER Name
Accepts the name of a Type to proxy, e.g. system.text.stringbuilder.
 
.PARAMETER CaseSensitive
Used in conjunction with -Name to specific a case-sensitive type name.
 
.EXAMPLE
$ise = peek $psise
$ise | get-member
 
Get a live instance of the ISE's $psise global, and examine and manipulate its internal structures.
 
.EXAMPLE
$job = start-job { 42 } | peek
$job | gm
 
Get an instance of psremotingjob and view all private, internal, protected and public members.
 
.EXAMPLE
$throttlemanager = peek (start-job { 42 } | peek).throttlemanager
$throttlemanager.ThrottleLimit = 64
 
Get an instance of a job's internal throttle manager and increse the throttle limit from 32 to 64.
#>

    [cmdletbinding(defaultparametersetname="inputobject")]
    param(
        [parameter(position=0, mandatory=$true, parametersetname="typeName")]
        [validatenotnullorempty()]
        [string]$Name,
        
        [parameter(parametersetname="typeName")]
        [switch]$CaseSensitive,

        [parameter(valuefrompipeline=$true, parametersetname="inputobject", position="0")]
        [validatenotnull()]
        $InputObject
    )

    if ($PSCmdlet.ParameterSetName -eq "inputobject") {
    
        if ($InputObject -is [type]) {
            New-TypeProxy -TypeName $InputObject.fullname -CaseSensitive
        } else {
            New-InstanceProxy -Instance $InputObject
        }
            
    } else {        
        New-TypeProxy -TypeName $Name -CaseSensitive:$CaseSensitive
    }

    Write-Progress -id 1 -Activity "Poke" -Completed
}

<#
Update-TypeData -Force -TypeName System.Management.Automation.PSMethod -MemberType ScriptMethod -MemberName CreateDelegate -Value {
    param(
        [parameter(position=0, mandatory=$true)]
        [validatenotnull()]
        [validatescript({ ([delegate].isassignablefrom($_)) })]
        [type]$DelegateType
    )
 
    $this | Get-Delegate -Delegate $DelegateType
}
#>


function Invoke-FormatHelper {
    param(
        [parameter()]
        [Microsoft.PowerShell.Commands.MemberDefinition]$Member,
        [parameter()]
        [string]$CommandName,
        [parameter()]
        [string]$DefaultValue
    )
    $proxy = $proxyTable[$Member.TypeName]
    if ($proxy) {
        try {
            # invoke the command in the scope of the module that proxies this type or instance
            & $proxyTable[$Member.TypeName].__GetModuleInfo() $CommandName $Member $proxy @args
        } catch {
            write-warning $_
            $DefaultValue
        }
    } else {
        # not a proxied type or instance
        $DefaultValue
    }
}

#update-formatdata -PrependPath (join-path $ExecutionContext.SessionState.Module.ModuleBase 'Poke.Format.ps1xml')

# scriptblock is not bound to this module's scope? weird bug? we have to use invoke-format helper to lookup module in a shared global
Update-TypeData -typename Microsoft.PowerShell.Commands.MemberDefinition -MemberType ScriptProperty -MemberName MemberType -Value {    
    try { invoke-formathelper $this get-membertype -default $this.psbase.membertype } catch { write-warning "get-membertype: $_" }
} -Force

Update-TypeData -typename Microsoft.PowerShell.Commands.MemberDefinition -MemberType ScriptProperty -MemberName Modifier -Value {    
    try { invoke-formathelper $this get-membermodifier -default "public" } catch { write-warning "get-membermodifier: $_" }
} -Force

# overloads
Update-TypeData -typename Microsoft.PowerShell.Commands.MemberDefinition -MemberType ScriptProperty -MemberName Definition -Value {    
    try { invoke-formathelper $this get-memberdefinition -default $this.psbase.definition } catch { write-warning "get-memberdefinition: $_" }
} -Force

# shortcut for $o | peek | gm
function Get-PokeMember { $args | peek | Get-Member }

$ExecutionContext.SessionState.Module.OnRemove = {
    remove-module poke_init -ErrorAction SilentlyContinue
}

#
# Exports
#

new-alias -Name peek -Value New-ObjectProxy -Force
Export-ModuleMember -Alias peek -Function New-ObjectProxy, New-TypeProxy, New-InstanceProxy, Get-Delegate, Invoke-FormatHelper, Get-PokeMember