Write-TypeView.ps1

function Write-TypeView
{
    <#
    .Synopsis
        Writes extended type view information
    .Description
        PowerShell has a robust, extensible types system. With Write-TypeView, you can easily add extended type information to any type.
        This can include:
            The default set of properties to display (-DefaultDisplay)
            Sets of properties to display (-PropertySet)
            Serialization Depth (-SerializationDepth)
            Virtual methods or properties to add onto the type (-ScriptMethod, -ScriptProperty and -NoteProperty)
            Method or property aliasing (-AliasProperty)
    .Link
        Out-TypeView
    .Link
        Add-TypeView
    #>

    [OutputType([string])]
    param(
    # The name of the type
    #|Default MyCustomTypeName
    #|MaxLength 255
    [Parameter(Mandatory=$true,
        ValueFromPipelineByPropertyName=$true,
        Position=0)]
    [String]
    $TypeName,

    # A collection of virtual method names and the script blocks that will be used to run the virtual method.
    [ValidateScript({
        if ($_.Keys | Where-Object {$_-isnot [string]}) {
            throw "Must provide the names of script methods"
        }
        if ($_.Values | Where-Object {$_ -isnot [ScriptBlock]}) {
            throw "Must provide script blocks to handle each method"
        }
        return $true
    })]
    [Hashtable]$ScriptMethod,

    # A Collection of virtual property names and the script blocks that will be used to get the property values.
    [ValidateScript({
        $in = $_
        foreach ($kv in $in.GetEnumerator()) {
            if ($kv.Key -isnot [string]) {
                throw "Must provide the names of script properties"
            }
            if ($kv.Value.Count -gt 2) {
                throw "No more than two scripts can be provided"
            }
            foreach ($_ in $kv.Value) {
                if ($_ -isnot [ScriptBlock]) {
                    throw "Must provide script blocks to handle each property"
                }
            }
        }
        return $true
    })]
    [Hashtable]$ScriptProperty,

    # A collection of fixed property values.
    [ValidateScript({
        if ($_.Keys | Where-Object { $_-isnot [string] } ) {
            throw "Must provide the names of note properties"
        }
        return $true
    })]
    [Hashtable]$NoteProperty,

    # A collection of property aliases
    [ValidateScript({
        foreach ($kv in $_.GetEnumerator()) {
            if ($kv.Key -isnot [string] -or $kv.Value -isnot [string]) {
                throw "All keys and values in the property rename map must be strings"
            }
        }
        return $true
    })]
    [Hashtable]$AliasProperty,

    # The default display. If only one propertry is used,
    # this will set the default display property. If more than one property is used,
    # this will set the default display member set
    [string[]]$DefaultDisplay,

    # The ID property
    [string]$IdProperty,

    # The serialization depth. If the type is deserialized, this is the depth of subpropeties
    # that will be stored. For instance, a serialization depth of 3 would storage an object, it's
    # subproperties, and those objects' subproperties. You can use the serialization depth
    # to minimize the overhead of moving objects back and forth across the remoting boundary,
    # or to ensure that you capture the correct information.
    [int]$SerializationDepth = 2,

    # The reserializer type used for recreating a deserialized type
    [Type]$Reserializer,

    # Property sets define default views for an object. A property set can be used with Select-Object
    # to display just that set of properties.
    [ValidateScript({
        if ($_.Keys | Where-Object {$_ -isnot [string] } ) {
            throw "Must provide the names of property sets"
        }
        if ($_.Values |
            Where-Object {$_ -isnot [string] -and  $_ -isnot [Object[]] -and $_ -isnot [string[]] }){
            throw "Must provide a name or list of names for each property set"
        }
        return $true
    })]
    [Hashtable]$PropertySet,

    # Will hide any properties in the list from a display
    [string[]]$HideProperty
    )


    process {
        $memberSetXml = ""

        #region Construct Member Set
        if ($psBoundParameters.ContainsKey('SerializationDepth') -or
            $psBoundParameters.ContainsKey('IdProperty') -or
            $psBoundParameters.ContainsKey('DefaultDisplay') -or
            $psBoundParameters.ContainsKey('Reserializer')) {
            $defaultDisplayXml = if ($psBoundParameters.ContainsKey('DefaultDisplay')) {
$referencedProperties = "<Name>" + ($defaultDisplay -join "</Name>
                        <Name>"
) + "</Name>"
" <PropertySet>
                    <Name>DefaultDisplayPropertySet</Name>
                    <ReferencedProperties>
                        $referencedProperties
                    </ReferencedProperties>
                </PropertySet>
 
"

            }
            $serializationDepthXml = if ($psBoundParameters.ContainsKey('SerializationDepth')) {
                "
                <NoteProperty>
                    <Name>SerializationDepth</Name>
                    <Value>$SerializationDepth</Value>
                </NoteProperty>"

            } else {$null }

            $ReserializerXml = if ($psBoundParameters.ContainsKey('Reserializer'))  {
"
                <NoteProperty>
                    <Name>TargetTypeForDeserialization</Name>
                    <Value>$Reserializer</Value>
                </NoteProperty>
 
"

            } else { $null }

            $memberSetXml = "
            <MemberSet>
                <Name>PSStandardMembers</Name>
                <Members>
                    $defaultDisplayXml
                    $serializationDepthXml
                    $reserializerXml
                </Members>
            </MemberSet>
            "

        }
        #endregion Construct Member Set

        #region PropertySetXml
        $propertySetXml  = if ($psBoundParameters.PropertySet) {
            foreach ($NameAndValue in $PropertySet.GetEnumerator()) {
                $referencedProperties = "<Name>" + ($NameAndValue.Value -join "</Name>
                    <Name>"
) + "</Name>"
            "<PropertySet>
                <Name>$([Security.SecurityElement]::Escape($NameAndValue.Key))</Name>
                <ReferencedProperties>
                    $referencedProperties
                </ReferencedProperties>
            </PropertySet>"

            }
        } else {
            ""
        }
        #endregion



        #region Aliases
        $aliasPropertyXml = if ($psBoundParameters.AliasProperty) {
            foreach ($NameAndValue in $AliasProperty.GetEnumerator()) {
                $isHiddenChunk = if ($HideProperty -contains $NameAndValue.Key) {
                    'IsHidden="true"'
                }
                "
            <AliasProperty $isHiddenChunk>
                <Name>$([Security.SecurityElement]::Escape($NameAndValue.Key))</Name>
                <ReferencedMemberName>$([Security.SecurityElement]::Escape($NameAndValue.Value))</ReferencedMemberName>
            </AliasProperty>"

            }
        } else {
            ""
        }
        #endregion Aliases
        $NotePropertyXml = if ($psBoundParameters.NoteProperty) {
            foreach ($NameAndValue in $NoteProperty.GetEnumerator()) {
                $isHiddenChunk = if ($HideProperty -contains $NameAndValue.Key) {
                    'IsHidden="true"'
                }
                "
            <NoteProperty $isHiddenChunk>
                <Name>$([Security.SecurityElement]::Escape($NameAndValue.Key))</Name>
                <Value>$([Security.SecurityElement]::Escape($NameAndValue.Value))</Value>
            </NoteProperty>"

            }
        } else {
            ""
        }
        $scriptMethodXml = if ($psBoundParameters.ScriptMethod) {
            foreach ($methodNameAndCode in $ScriptMethod.GetEnumerator()) {                                "
            <ScriptMethod>
                <Name>$($methodNameAndCode.Key)</Name>
                <Script>
                    $([Security.SecurityElement]::Escape($methodNameAndCode.Value))
                </Script>
            </ScriptMethod>"

            }
        } else {
            ""
        }

        #region Script Property
        $scriptPropertyXml = if ($psBoundParameters.ScriptProperty) {
            foreach ($propertyNameAndCode in $ScriptProperty.GetEnumerator()) {
                $isHiddenChunk = if ($HideProperty -contains $propertyNameAndCode.Key) {
                    'IsHidden="true"'
                }
                $getScript, $setScript = $propertyNameAndCode.Value
                if ($getScript -and $setScript) {
                    "
            <ScriptProperty $isHiddenChunk>
                <Name>$($propertyNameAndCode.Key)</Name>
                <GetScriptBlock>
                    $([Security.SecurityElement]::Escape($getScript))
                </GetScriptBlock>
                <SetScriptBlock>
                    $([Security.SecurityElement]::Escape($setScript))
                </SetScriptBlock>
            </ScriptProperty>"

                } else {
                    "
            <ScriptProperty $isHiddenChunk>
                <Name>$($propertyNameAndCode.Key)</Name>
                <GetScriptBlock>
                    $([Security.SecurityElement]::Escape($propertyNameAndCode.Value))
                </GetScriptBlock>
            </ScriptProperty>"

                }

            }
        }

        $innerXml = @($memberSetXml) + $propertySetXml + $aliasPropertyXml + $codePropertyXml + $codeMethodXml + $scriptMethodXml + $scriptPropertyXml + $NotePropertyXml

        $innerXml = ($innerXml  | Where-Object {$_} ) -join ([Environment]::NewLine)
        "
    <Type>
        <Name>$TypeName</Name>
        <Members>
            $innerXml
        </Members>
    </Type>"

    }

}