4
\$\begingroup\$

By "Pseudo-Enum" classes I mean cases where someone's used a class with public static properties to emulate enum behaviour; e.g. as described here.

Per the example below, this is useful for classes such as [System.Management.Automation.VerbsCommon], where there is a list of values we're interested in, but no "Get All Values of This Type" method.

I wrote this function for the above type, and it works well for that, but maybe I've missed a trick / there are likely other scenarios that this code could cover which I've not thought of yet.

Clear-Host
function Get-EnumValues {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,ValueFromPipeline)]
        [Type]$Type
    )
    process {
        if ($Type) {

            Write-Verbose ("Type: {0}" -f $Type.ToString())
            Write-Verbose ("IsEnum? {0}" -f $Type.IsEnum)

            if ($Type.IsEnum) {
                [System.Enum]::GetNames($Type)
            } else {
                #handle pseudo-enums (i.e. for cases where this trick's been used https://stackoverflow.com/a/15713789/361842)
                $Type.GetFields() | ?{$_.IsStatic -and $_.IsPublic} | select -ExpandProperty Name
            }
        } else {
            Write-Verbose ("TypeName is Null")
            $null
        }
    }
}

$t = [type][System.ConsoleColor]
Get-EnumValues $t -Verbose
$t = [type][System.Management.Automation.VerbsCommon]
Get-EnumValues $t -Verbose

Output:

VERBOSE: Type: System.ConsoleColor
VERBOSE: IsEnum? True
Black
DarkBlue
DarkGreen
DarkCyan
DarkRed
DarkMagenta
DarkYellow
Gray
DarkGray
Blue
Green
Cyan
Red
Magenta
Yellow
White
VERBOSE: Type: System.Management.Automation.VerbsCommon
VERBOSE: IsEnum? False
Add
Clear
Close
Copy
Enter
Exit
Find
Format
Get
Hide
Lock
Move
New
Open
Optimize
Push
Pop
Remove
Rename
Reset
Resize
Set
Search
Show
Skip
Step
Join
Redo
Split
Switch
Select
Undo
Unlock
Watch

I'm aware that the shorthand coding style sometimes frowned upon, such as:

$Type.GetFields() | ?{$_.IsStatic -and $_.IsPublic} | select -ExpandProperty Name

could be written as:

$Type.GetFields() | Where-Object{$_.IsStatic -and $_.IsPublic} | Select-Object -ExpandProperty Name

I've taken the view whereby I allow shorthand for the most common, general-purpose cmdlets (i.e. select for select-object, % for foreach-object, ? for where-object) as those should be familiar to any powershell developer with any experience; whilst for all others I avoid aliases completely. My view is that these common aliases are more easily readable, and are unlikely to cause confusion.

\$\endgroup\$
2
\$\begingroup\$

Let's look for scenarios that this code could cover.

Next 143296.ps1 script, using Get-Type function - Get exported types in the current session

  • Exports all types from all namespaces in the current session to $allTypes, then
  • Selects those with non-empty (pseudo)enumeration to auxiliary Hashtable $ppp and $subTypes.

Script 143296.ps1:

$allTypes = Get-Type -Namespace * -ErrorAction SilentlyContinue
$allTypes.Count
$ppp=@{}              ### auxiliary Hashtable object, could be useful though
$allTypes | 
    ForEach-Object {
        $aux = $_ | Get-EnumValue -AsFlags
        if ( $aux.Count -ne 0 ) { $ppp.add( $_.FullName, $_.BaseType) }
    }
$ppp.Count 
$subTypes = $allTypes | Where-Object { $ppp.ContainsKey($_.FullName)}
$subTypes.Count
$subGrups = $subtypes | Group-Object -Property BaseType
$subGrups.Count
### make public the most frequent items:
$subGrups | Sort-Object -Property Count -Descending | Select-Object -First 15

<# ### another approach to make public results:
$allBases = $ppp.Keys | %{ "{0,15} {1}" -f ($ppp[$_] | Select -ExpandProperty name), $_ } | sort
$allBases.Count
$subBases = $ppp.Keys | %{ ($ppp[$_] | Select -ExpandProperty name)} | sort -Unique
$subBases.Count
#>

Output:

PS D:\PShell> D:\PShell\CR\143296.ps1
12413
2490
2490
182

Count Name                      Group                                                          
----- ----                      -----                                                          
 1710 System.Enum               {System.StringSplitOptions, System.StringComparison, System....
  201 System.Object             {System.String, System.EventArgs, System.BitConverter, Syste...
   58 System.ValueType          {System.DateTime, System.DateTimeOffset, System.Boolean, Sys...
   53 System.Attribute          {System.ComponentModel.BindableAttribute, System.ComponentMo...
   28 System.Windows.Freezable  {System.Windows.Media.Animation.ThicknessKeyFrame, System.Wi...
   24 System.Windows.Framewo... {System.Windows.Shapes.Shape, System.Windows.Controls.Panel,...
   22 System.Windows.Control... {Microsoft.VisualStudio.Text.Editor.OutliningMarginHeaderCon...
   15 System.Windows.Media.A... {System.Windows.TextDecoration, System.Windows.Media.Animati...
   11 System.Windows.Control... {System.Windows.Window, System.Windows.Controls.Primitives.B...
    9 System.Windows.Depende... {System.Windows.Navigation.JournalEntry, System.Windows.Cont...
    8 System.Globalization.C... {System.Globalization.GregorianCalendar, System.Globalizatio...
    8 System.Windows.Control... {System.Windows.Controls.Canvas, System.Windows.Controls.Vir...
    7 System.Windows.Media.P... {System.Windows.Media.ArcSegment, System.Windows.Media.Bezie...
    7 System.Windows.Control... {System.Windows.Controls.Border, System.Windows.Controls.Ink...
    7 System.Windows.Media.G... {System.Windows.Media.CombinedGeometry, System.Windows.Media...

The Get-EnumValue function is explained using Comment-Based Help (CBH):

Set-StrictMode -Version latest
function Get-EnumValue { 
    <#
    .SYNOPSIS
        Return names (and values) for an enumeration or enumeration-like object.

    .DESCRIPTION
        With -AsFlags or no switch: return Ordered Dictionary of names and values 
                                    for an enumeration (or enumeration-like) object.
        With -NoValues switch: return an array of names for such object.

    .PARAMETER Type
        Pass in an actual type, or a string for the type name.

    .PARAMETER NoValues
        If True, then output would be [string[]] array of names;
           otherwise, [OrderedDictionary] type type.
        If True, then -AsFlags parameter is ignored.

    .PARAMETER AsFlags
        If True *and* specified <type> is attributed 'FlagsAttribute' (some [Enum] types)
           then output values would be hexadecimal-like strings (8+ hexadecimal ciphers);
           otherwise, output values would follow <type> type.
        Applies only if -NoValues parameter evaluates to False.

    .INPUTS
        Valid [type], or a string for the type name.

    .OUTPUTS
        Always returns [OrderedDictionary] or [system.array] object (see switches).
        Never outputs Null; returns an empty (.Count -eq 0) object instead.

    .NOTES
        If -NoValues evaluates to True, then -AsFlags is ignored.

    .EXAMPLE
        # Enum, flag enumeration (FlagsAttribute present on object supplied)
        [System.AttributeTargets]|Get-EnumValue -AsFlags -Verbose
    .EXAMPLE
        # Enum, value enumeration (no FlagsAttribute on object supplied)
        [System.Runtime.InteropServices.VarEnum]|Get-EnumValue -AsFlags -Verbose
    .EXAMPLE
        # Struct (not Enum)
        [System.Double]|Get-EnumValue -Verbose
    .EXAMPLE
        # Class (not Enum): useful or at least interesting output
        [System.Security.Claims.ClaimValueTypes]|Get-EnumValue -AsFlags | ft -a -Wrap

    .REMARKS
        Based on 'Get-EnumValues' function and used in answer to:
        http://codereview.stackexchange.com/questions/143296/getting-enum-values-of-pseudo-enum-classes
        Useful related article: 'Get-Type' - Get exported types in the current session
        https://gallery.technet.microsoft.com/scriptcenter/Get-Type-Get-exported-fee19cf7

    .FUNCTIONALITY
        Tested: Windows 8.1/64bit, Powershell 4
    #>
    [CmdletBinding(PositionalBinding=$false)]
    param (
        [Parameter(Mandatory,ValueFromPipeline,Position=0)]$Type,
        [parameter(Mandatory=$false)][Switch][bool]$NoValues,
        [parameter(Mandatory=$false)][Switch][bool]$AsFlags
          )
    begin {

        Function EnumNoEnum ([type]$noEnumClass) {
            $enumFlagsHash = [ordered]@{}
            $noEnumClass.GetFields() | 
                Where-Object {$_.IsStatic -and $_.IsPublic} | 
                    Select-Object -ExpandProperty Name |
                        ForEach-Object {
                            $enumFlagsHash.ADD( $_, $noEnumClass::$_)
                        }
            return $enumFlagsHash
        }

        Function EnumFlags ([type]$enumClass, [bool]$enumFlags) {
            $enumFlagsHash = [ordered]@{}
            [System.Enum]::GetNames($enumClass) | 
                ForEach-Object {
                    if ($enumFlags) {
                        $enumFlagsHash.ADD( $_, "0x{0:x8}" -F $($enumClass::$_.value__))
                    } else {
                        $enumFlagsHash.ADD( $_, $($enumClass::$_.value__))
                    }
                }
            return $enumFlagsHash
        }
    }

    process {
        Try { $Type = [type]$Type} Catch { $Type = $null }
        if ( $Type) {
            Write-Verbose ("Type:   {0}" -f $Type.ToString())
            ### ToDo: find out $enumFlagsAttribute in a more efficient way
            $enumFlagsAttribute = ''
            if ($Type.CustomAttributes | 
                Where-Object {$_.AttributeType.Name -eq 'FlagsAttribute'})
                    { $enumFlagsAttribute = 'FlagsAttribute'}
            Write-Verbose ("IsEnum? {0,-20} {1}" -f 
                $Type.IsEnum.ToString(), $enumFlagsAttribute)

            if ($Type.IsEnum) {
                if (-not $NoValues) {
                    if ( $AsFlags -and $enumFlagsAttribute) {
                        #   flags enumeration
                        $TypeOut = . EnumFlags $Type $AsFlags
                    } else {
                        #   value enumeration
                        $TypeOut = . EnumFlags $Type $False
                    }
                } else {
                    $TypeOut = [System.Enum]::GetNames($Type)
                }
            } else {
                # handle pseudo-enums i.e. for cases where this trick's been used
                #                      http://stackoverflow.com/a/15713789/361842
                if (-not $NoValues) {
                    $TypeOut = . EnumNoEnum $Type
                } else {
                    $TypeOut = $Type.GetFields() | 
                        Where-Object {$_.IsStatic -and $_.IsPublic} | 
                            Select-Object -ExpandProperty Name 
                }
            }
            if ( $TypeOut ) { $TypeOut } else { ,@() }
        } else {
            Write-Verbose ("Null or invalid TypeName")
            if ( $NoValues ) { ,@() } else { [ordered]@{} }
        }
    }
}

I think that information about FlagsAttribute on a particular enum type could matter (added to verbosed output), see FlagsAttribute class.

See also the AsFlags parameter in the above function: it causes that values of the FlagsAttribute enum have a hexadecimal look (fake, however sometimes useful).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.