Search code examples
powershelltypesbit-manipulation

How to define a type of a bit-combination?


I am struggling with Powershell to create a custom type that has a bit-combination like file-attributes. The usage of the type should work later like this:

$attr = [myType]::new()
$attr.read = $true
$attr.write = $true
$attr.execute = $true
write-host $attr # or $attr.toString()? -> read,write,execute
write-host $attr.value__ # -> 7 (3 bits set to 1)

My final type will need 6 bits. I want to skip the part with manual bit-operations in my later code to get each bit back. So, I want to have it in the type, if possible. Any ideas how to get this done?


Solution

  • For this you might use the PowerShell Enumerations as flags feature:

    Enumerations as flags

    Enumerations can be defined as a collection of bit flags. Where, at any given point the enumeration represents one or more of those flags turned on.

    For enumerations as flags to work properly, each label should have a power of two value.

    Example specific to your question

    In the following example the Access enumeration is created

    [Flags()] enum Access {
        Execute = 1
        Write = 2
        Read = 4
    }
    

    Taking advantage of the PowerShell implicit type casting where generally the left-hand-side of the operator dictates the type casting of the right-hand-side (respecting the operator precedence) you might use the flag enum as follows:

    Assigning

    To assign bits (flags) to the [Access] type, you might use a simple string array:

    $Access = [Access]'Read', 'Execute'
    

    Or as commented by @mklement0, use a single string:

    $Access = [Access]'Read, Execute'
    

    Reading

    To read (and/or display) the assigned access:

    $Access
    Execute, Read
    
    [int]$Access
    5
    

    Setting

    To set a bit (flag) of the [Access] type, you might use the binary or (-bor) operator:

    $Access -bor 'Write'
    Execute, Write, Read
    

    Resetting

    To reset a bit (flag) of the [Access] type, you might use the binary and (-band) and a binary invertor (-bnot) operator:

    $Access -band -bnot [Access]'Read'
    Execute
    

    (note that you will need to explicitly set the type for RHS of the -not operator)

    Testing

    To test that a specific bit is set, you can use the binary comparison operator -band. In this example, we test for the [Access] attributes in the value of $Access.

    If ($Access -band 'Read') { 'You have read access' }
    'You have read access'
    

    As noted by @mclayton this will not work for multiple flags in once:

    $Access -band ([Access]'read, write')
    Read
    

    Returns Read which implicitly casts to $True, see: Booleans\Converting from collection types. In other words, if you want to make sure that all concerned bits (flags) are set in the $Access Value, you probably want to use the HasFlag method instead:

    $Access.HasFlag([Access]'read, write')
    False
    

    Thank you @mklement0 for the list of notable enum-relevant GitHub issues (as of PowerShell 7.4.0-preview.3):