Rules/Test-ForOutputType.ps1

function Test-ForOutputType
{
    <#
    .Synopsis
        Tests for the presence of an output type
    .Description
        Tests for the presence of an output type.
        If the output type isn't a declared object, this also checks to see that the output
    .Example
        Get-Command CommandToTest |
            Test-ForOutputType
    .Link
        Test-Command
    #>

    param(
    [Parameter(ParameterSetName='TestCommandInfo',Mandatory=$true,ValueFromPipeline=$true)]
    [Management.Automation.CommandInfo]
    $CommandInfo
    )
        
    
    process {    
        #region Check that commands have an output type
        if (-not $commandInfo.OutputType) {
            Write-Error "$CommandInfo does not have an output type"
            return            
        }
        #endregion Check that commands have an output type
        
        #region Check dynamic property bags
        if ($commandInfo -is [Management.Automation.FunctionInfo]) {
            $help  = Get-Help $commandInfo.Name        
            $notesText = if ($help.alertSet) {
                $help.alertSet.alert | 
                    Select-Object -ExpandProperty Text
            } else { "" }
        }
        
        foreach ($outType in $commandInfo.OutputType) {        
            if ($outType.Name -and -not $outType.Type) 
            {
                # It's an unnanmed type. Make sure that the type is documented somewhere.
                # If it's a function, check for the presence of the name within the notes section of help
                if ($commandInfo -is [Management.Automation.FunctionInfo]) {
                    if ($notesText -and $notesText -notlike "*$($outType.Name)*") {
                        Write-Error "$CommandInfo returns a custom type, but doesn't document it in the function's notes"                        
                    }
                } elseif ($commandInfo -is [Management.Automation.CmdletInfo]) {
                    if ($notesText -and $notesText -notlike "*$($outType.Name)*") {
                        Write-Error "$CommandInfo returns a custom type, and that command has help, but doesn't document it in the commands's notes"                        
                    }
                    # If the type is compiled, look for an internal class in the same namespace as the cmdlet called ".PS${OutputTypeName}"
                    $expectedPsuedoTypeName = if ($outType.Name.Contains(".")) {
                        $outType.Name 
                    } else {
                        $commandInfo.ImplementingType.Namespace + ".PS" + $outType.Name 
                    }
                    
                    if ($CommandInfo.ImplementingType.GetAssembly) {
                        $psuedoType = $commandInfo.ImplementingType.GetAssembly().GetType("$expectedPsuedoTypeName")
                        if (-not $psuedoType) {
                            Write-Error "$commandInfo returns a property bag, but does not have an internal type declaring what properties should exist on that property bag.
Declare a class (preferably an internal class)"

                            continue
                        }
                    
                    
                        $staticProperties = Get-Member -InputObject $psuedoType -MemberType Properties -Static
                        if (-not $staticProperties) {
                            Write-Error "$commandInfo returns a property bag, but does not have an internal type declaring what properties should exist on that property bag"
                            continue
                        }
                    }
                }

            }
        }
        
        #endregion Check dynamic property bags
        
        
        
    }
}