DscResources/AccessControlResourceHelper/AccessControlResourceHelper.psm1

try
{
    $importLocalizedDataParams = @{
        BaseDirectory = $PSScriptRoot
        UICulture     = $PSUICulture
        FileName      = 'AccessControlResourceHelper.strings.psd1'
        ErrorAction   = 'Stop'
    }
    $script:localizedData = Import-LocalizedData @importLocalizedDataParams
}
catch
{
    $importLocalizedDataParams.UICulture = 'en-US'
    try
    {
        $script:localizedData = Import-LocalizedData @importLocalizedDataParams
    }
    catch
    {
        throw 'Unable to load localized data'
    }
}

<#
    .SYNOPSIS
        Resolves the principal name SID
 
    .PARAMETER Identity
        Specifies the identity of the principal.
 
    .EXAMPLE
    Resolve-Identity -Identity "everyone"
#>

function Resolve-Identity
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Identity
    )
    process
    {
        Write-Verbose -Message "Resolving identity for '$Identity'."

        $tryNTService = $false

        try
        {
            if ($Identity -match '^S-\d-(\d+-){1,14}\d+$')
            {
                [System.Security.Principal.SecurityIdentifier]$Identity = $Identity

                # Support for capability sids
                if ($Identity.Value.StartsWith('S-1-15-3-'))
                {
                    return [PSCustomObject]@{
                        Name = $Identity.Value
                        SID = $Identity.Value
                    }
                }
            }
            else
            {
                 [System.Security.Principal.NTAccount]$Identity = $Identity
            }

            $SID = $Identity.Translate([System.Security.Principal.SecurityIdentifier])
            $NTAccount = $SID.Translate([System.Security.Principal.NTAccount])

            $Principal = [PSCustomObject]@{
                Name = $NTAccount.Value
                SID = $SID.Value
            }

            return $Principal
        }
        catch
        {
            # Try to resolve identity to NT Service
            $tryNTService = $true
        }

        if ($tryNTService)
        {
            try
            {
                [System.Security.Principal.NTAccount]$Id = "NT Service\" + $Identity
                $SID = $Id.Translate([System.Security.Principal.SecurityIdentifier])
                $NTAccount = $SID.Translate([System.Security.Principal.NTAccount])

                $Principal = [PSCustomObject]@{
                    Name = $NTAccount.Value
                    SID = $SID.Value
                }

                return $Principal
            }
            catch
            {
                $ErrorMessage = "Could not resolve identity '{0}': '{1}'." -f $Identity, $_.Exception.Message
                Write-Error -Exception $_.Exception -Message $ErrorMessage
            }
        }
    }
}

<#
    .SYNOPSIS
    Takes identity name and translates to SID
 
    .PARAMETER IdentityReference
    System.Security.Principal.NTAccount object
 
    .EXAMPLE
    $IdentityReference = (Get-Acl -Path C:\temp).access[0].IdentityReference
    ConvertTo-SID -IdentityReference $IdentityReference
#>


function ConvertTo-SID
{
    Param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $IdentityReference
    )

    try
    {
        If($IdentityReference.Contains("\"))
        {
            $IdentityReference = $IdentityReference.split('\')[1]
        }

        [System.Security.Principal.NTAccount]$PrinicipalName = $IdentityReference
        $SID = $PrinicipalName.Translate([System.Security.Principal.SecurityIdentifier])

        Return $SID
    }
    catch
    {
        # Probably NT Service which needs domain portion to translate without error
        [System.Security.Principal.NTAccount]$Id = "NT Service\" + $IdentityReference
        $SID = $Id.Translate([System.Security.Principal.SecurityIdentifier])

        return $SID
    }

}

<#
    .SYNOPSIS
        Confirms a required module exists.
#>

function Assert-Module
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ModuleName
    )

    if (-not (Get-Module -Name $ModuleName -ListAvailable))
    {
        $errorId = '{0}_ModuleNotFound' -f $ModuleName;
        $errorMessage = $localizedString.RoleNotFoundError -f $ModuleName;
        ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage;
    }
}

<#
    .SYNOPSIS
        Retrieves teh guid of the delegation right
#>

function Get-DelegationRightsGuid
{
    Param
    (
        [Parameter()]
        [string]
        $ObjectName
    )

    if ($ObjectName)
    {
        # Create a hashtable to store the GUID value of each schemaGuids and rightsGuids
        $guidmap = @{}
        $rootdse = Get-ADRootDSE
        Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter "(schemaidguid=*)" -Properties Name,schemaIDGUID |
            Foreach-Object -Process {$guidmap[$_.Name] = [System.GUID]$_.schemaIDGUID}

        Get-ADObject -SearchBase ($rootdse.ConfigurationNamingContext) -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties Name,rightsGuid |
            Foreach-Object -Process {$guidmap[$_.Name] = [System.GUID]$_.rightsGuid}

        return [system.guid]$guidmap[$ObjectName]
    }
    else
    {
        return [system.guid]'00000000-0000-0000-0000-000000000000'
    }
}

<#
    .SYNOPSIS
        Retrieves the name of the AD schema object.
#>

function Get-SchemaObjectName
{
    [CmdletBinding()]
    [OutputType([System.String])]
    Param
    (
        [Parameter()]
        [guid]
        $SchemaIdGuid
    )

    if ($SchemaIdGuid -and ($SchemaIdGuid.Guid -ne '00000000-0000-0000-0000-000000000000'))
    {
        $guidmap = @{}
        $rootdse = Get-ADRootDSE
        Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter "(schemaidguid=*)" -Properties Name,schemaIDGUID |
            Foreach-Object -Process {$guidmap[$_.Name] = [System.GUID]$_.schemaIDGUID}

        Get-ADObject -SearchBase ($rootdse.ConfigurationNamingContext) -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties Name,rightsGuid |
            Foreach-Object -Process {$guidmap[$_.Name] = [System.GUID]$_.rightsGuid}

        # This is to address the edge case where one guid resolves to multiple names ex. f3a64788-5306-11d1-a9c5-0000f80367c1 resolves to Service-Principal-Name,Validated-SPN
        $names = ($guidmap.GetEnumerator() | Where-Object -FilterScript {$_.Value -eq $SchemaIdGuid}).Name
        return $names -join ','
    }
    else
    {
        return 'None'
    }
}

<#
    .SYNOPSIS
        Produces a custom verbose message displaying details of every property touched by the resource.
#>

function Write-CustomVerboseMessage
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Action,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [Parameter(Mandatory = $true)]
        [ValidateScript({
            $_ -is [System.DirectoryServices.ActiveDirectoryAccessRule] -or
            $_ -is [System.DirectoryServices.ActiveDirectoryAuditRule] -or
            $_ -is [System.Security.AccessControl.FileSystemAccessRule] -or
            $_ -is [System.Security.AccessControl.AuditRule]
        })]
        $Rule
    )

    $properties = [ordered]@{
        IdentityReference = $Rule.IdentityReference
    }

    switch ($Rule.GetType().Name)
    {
        'ActiveDirectoryAccessRule'
        {
            # future expansion
            break
        }

        'ActiveDirectoryAuditRule'
        {
            $properties.Add('ActiveDirectoryRights', $Rule.ActiveDirectoryRights)
            $properties.Add('AuditFlags', $Rule.AuditFlags)
            $properties.Add('ObjectType', $(Get-SchemaObjectName -SchemaIdGuid $Rule.ObjectType))
            $properties.Add('InheritanceType', $Rule.InheritanceType)
            $properties.Add('InheritedObjectType', $(Get-SchemaObjectName -SchemaIdGuid $Rule.InheritedObjectType))
            break
        }

        'FileSystemAccessRule'
        {
            $properties.Add('AccessControlType', $Rule.AccessControlType)
            $properties.Add('FileSystemRights', $Rule.FileSystemRights)
            $properties.Add('InheritanceFlags', $Rule.InheritanceFlags)
            $properties.Add('PropagationFlags', $Rule.PropagationFlags)
            break
        }
        'FileSystemAuditRule'
        {
            $properties.Add('FileSystemRights', $Rule.FileSystemRights)
            $properties.Add('AuditFlags', $Rule.AuditFlags)
            $properties.Add('InheritanceFlags', $Rule.InheritanceFlags)
            $properties.Add('PropagationFlags', $Rule.PropagationFlags)
            break
        }
    }

    Write-Verbose -Message $localizedData[$Action] -Verbose
    Write-Verbose -Message ($localizedData.Path -f $Path) -Verbose

    foreach ($property in $properties.Keys -as [array])
    {
        Write-Verbose -Message ($localizedData[$property] -f $properties[$property]) -Verbose
    }
}

<#
    .SYNOPSIS
        Resolves inheritance to inheritanceFlag and propagationFlag
#>

function Get-NtfsInheritenceFlag
{
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Inheritance
    )

    switch($Inheritance)
    {
        "This folder only"{
            $InheritanceFlag = "0"
            $PropagationFlag = "0"
            break
        }
        "This folder subfolders and files"{
            $InheritanceFlag = "3"
            $PropagationFlag = "0"
            break

        }
        "This folder and subfolders"{
            $InheritanceFlag = "1"
            $PropagationFlag = "0"
            break
        }
        "This folder and files"{
            $InheritanceFlag = "2"
            $PropagationFlag = "0"
            break

        }
        "Subfolders and files only"{
            $InheritanceFlag = "3"
            $PropagationFlag = "2"
            break

        }
        "Subfolders only"{
            $InheritanceFlag = "1"
            $PropagationFlag = "2"
            break
        }
        "Files only"{
            $InheritanceFlag = "2"
            $PropagationFlag = "2"
            break
        }
    }

    return [PSCustomObject]@{
        InheritanceFlag = $InheritanceFlag
        PropagationFlag = $PropagationFlag
    }
}

<#
    .SYNOPSIS
        Returns Inheritance name from inheritanceFlag and propagationFlag
#>

function Get-NtfsInheritenceName
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $InheritanceFlag,

        [Parameter(Mandatory = $true)]
        [System.String]
        $PropagationFlag
    )

    switch("$InheritanceFlag-$PropagationFlag")
    {
        "0-0"{
            return "This folder only"
        }
        "3-0"{
            return "This folder subfolders and files"
        }
        "1-0"{
            return "This folder and subfolders"
        }
        "2-0"{
            return "This folder and files"
        }
        "3-2"{
            return "Subfolders and files only"
        }
        "1-2"{
            return "Subfolders Only"
        }
        "2-2"{
            return "Files Only"
        }
    }

    return "none"
}

<#
    .SYNOPSIS
        Resolves environment variable that are included in a folder/file path.
#>

function Get-InputPath
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Path
    )

    # If Path has a environment variable, convert it to a locally usable path
    $returnPath = [System.Environment]::ExpandEnvironmentVariables($Path)

    return $returnPath
}

<#
    .SYNOPSIS
        Removes the domain from an NT Principal
    .DESCRIPTION
        Removes the domain from an NT Principal
    .PARAMETER Identity
        Specifies the identity of the principal.
#>

function Remove-NtPrincipalDomain
{
    [CmdletBinding()]
    [OutputType([System.Security.Principal.NTAccount])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Security.Principal.NTAccount]
        $Identity
    )

    $modifiedIdentity = $Identity.ToString() -replace '.*\\'
    $returnIdentity = New-Object -Type System.Security.Principal.NTAccount -ArgumentList $modifiedIdentity
    return $returnIdentity
}

function Assert-Module
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ModuleName
    )

    if (-not (Get-Module -Name $ModuleName -ListAvailable))
    {
        $errorId = '{0}_ModuleNotFound' -f $ModuleName;
        $errorMessage = $localizedString.RoleNotFoundError -f $ModuleName;
        ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage;
    }
} 

function Get-DelegationRightsGuid
{
    Param 
    (
        [Parameter()]
        [string]
        $ObjectName
    )

    if($ObjectName)
    {
        # Create a hashtable to store the GUID value of each schemaGuids and rightsGuids
        $guidmap = @{}
        $rootdse = Get-ADRootDSE
        Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter "(schemaidguid=*)" -Properties Name,schemaIDGUID | 
            Foreach-Object -Process { $guidmap[$_.Name] = [System.GUID]$_.schemaIDGUID }

        Get-ADObject -SearchBase ($rootdse.ConfigurationNamingContext) -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties Name,rightsGuid | 
            Foreach-Object -Process { $guidmap[$_.Name] = [System.GUID]$_.rightsGuid }

        return [system.guid]$guidmap[$ObjectName]
    }
    else
    {
        return [system.guid]"00000000-0000-0000-0000-000000000000"
    }
}

function Get-SchemaObjectName
{
    Param
    (
        [Parameter()]
        [guid]
        $SchemaIdGuid
    )

    if($SchemaIdGuid)
    {
        $guidmap = @{}
        $rootdse = Get-ADRootDSE
        Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter "(schemaidguid=*)" -Properties Name,schemaIDGUID | 
            Foreach-Object -Process { $guidmap[$_.Name] = [System.GUID]$_.schemaIDGUID }

        Get-ADObject -SearchBase ($rootdse.ConfigurationNamingContext) -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties Name,rightsGuid | 
            Foreach-Object -Process { $guidmap[$_.Name] = [System.GUID]$_.rightsGuid }

        # This is to address the edge case where one guid resolves to multiple names ex. f3a64788-5306-11d1-a9c5-0000f80367c1 resolves to Service-Principal-Name,Validated-SPN
        $names = ( $guidmap.GetEnumerator() | Where-Object -FilterScript { $_.Value -eq $SchemaIdGuid } ).Name
        return $names -join ','
    }
    else
    {
        return "none"
    }
}