Shared/ADDirectoryEntry.psm1

Set-StrictMode -Version Latest
$ErrorActionPreference = [Management.Automation.ActionPreference]::Stop


function Update-DirectoryEntryFlag {
    <#
    .SYNOPSIS
        Add a boolean note property to the entry based on a bitfield value.
    .DESCRIPTION
        Editing the direct members of the entries tends to just cause
        errors, so instead we can set Note properties to expose useful
        boolean flags that are expressed internally to the properties as bit
        fields. These Note properties will shadow the built-in properties
        that cannot be meaningfully updated and don't necessarily match
        their underlying value.
    #>

    [CmdletBinding()]
    param (
        # The entry to set a note flag upon.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [DirectoryServices.DirectoryEntry] $Entry,

        # The property name of the entry to read the note flag from.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string] $BitFieldProperty,

        # The value to bit-test against the entry property.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [int] $BitMask,

        # The name to set the resulting Boolean note onto the entry.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string] $NotePropertyName,

        # What value to set on the object it the bit value is true.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [object] $TrueValue,

        # What value to set on the object it the bit value is false. If the bit
        # value is found, nothing will be set on the object, which allows
        # multiple bit-values to contribute to a single note property.
        [Parameter(ValueFromPipelineByPropertyName)]
        [object] $FalseValue = $null
    )
    begin {
        [bool] $isFlagTrue = Get-DirectoryEntryFlag $Entry $BitFieldProperty $BitMask
        
        $noteValue = if ($isFlagTrue) {
            $TrueValue
        } else {
            $FalseValue
        }
        # $noteValue will only be $null if the bitmask returned false *and*
        # there was no FalseValue provided.
        if ($null -ne $noteValue) {
            $entry | Add-Member -NotePropertyName $NotePropertyName -NotePropertyValue $noteValue -Force
        }
    }
}


function Get-DirectoryEntryFlag {
    <#
    .SYNOPSIS
        Gets a bitfield flag within the properties of a given DirectoryEntry
    #>

    [OutputType([bool])]
    [CmdletBinding()]
    param (
        # The entry to set a note flag upon.
        [Parameter(Mandatory, ValueFromPipeline)]
        [DirectoryServices.DirectoryEntry] $Entry,

        # The property name of the entry to read the note flag from.
        [Parameter(Mandatory)]
        [string] $BitFieldProperty,

        # The value to bit-test against the entry property.
        [Parameter(Mandatory)]
        [int] $BitMask
    )
    process {
        # output
        [bool] ($Entry.Properties[$BitFieldProperty].Value -band $BitMask)
    }
}


function Set-DirectoryEntryFlag {
    <#
    .SYNOPSIS
        Set or clears bitfield flag within the properties of a given DirectoryEntry
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        # The entry to set a note flag upon.
        [Parameter(Mandatory, ValueFromPipeline)]
        [DirectoryServices.DirectoryEntry] $Entry,

        # The property name of the entry to read the note flag from.
        [Parameter(Mandatory)]
        [string] $BitFieldProperty,

        # The value to bit-test against the entry property.
        [Parameter(Mandatory)]
        [int] $BitMask,

        # Whether to set or clear the bitmask from the bitfield.
        [bool] $Value
    )
    process {
        $targetSummary = "'$($entry.DistinguishedName)' property '$BitFieldProperty' flag '$("0x" + $BitMask.ToString('X'))' to '$value'"
        Write-Verbose "$($MyInvocation.MyCommand): $targetSummary..."
        if ($PSCmdlet.ShouldProcess($targetSummary)) {
            $entry.Properties[$BitFieldProperty].Value = if ($Value) {
                # true
                $entry.Properties[$BitFieldProperty].Value -bor $BitMask
            } else {
                # false
                $entry.Properties[$BitFieldProperty].Value -band (-bnot $BitMask)
            }
        }
    }
}


function Set-DirectoryEntryPropertyTable {
    <#
    .SYNOPSIS
        Set properties of a given DirectoryEntry from the given Properties hashtable
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        # The entry to set a note flag upon.
        [Parameter(Mandatory, ValueFromPipeline)]
        [DirectoryServices.DirectoryEntry] $Entry,

        [Parameter(Mandatory)]
        [Hashtable] $OtherAttributes
    )
    begin {
        if ($PSCmdlet.ShouldProcess($Entry.distinguishedName)) {
            Write-Verbose "Setting properties of $Type '$($Entry.distinguishedName)"
            foreach ($key in $OtherAttributes.Keys) {
                $entry.Properties[$key].Value = $OtherAttributes[$key]
            }
        }
    }
}


Export-ModuleMember -Function *-DirectoryEntryFlag, *-DirectoryEntryPropertyTable