Modules/AzureDevOpsDsc.Common/Resources/Functions/Public/AzDoProcessPermission/Get-AzDoProcessPermission.ps1

<#
.SYNOPSIS
Retrieves the current state of Azure DevOps Process permissions.
 
.DESCRIPTION
Compares the desired Process namespace ACEs against the live ACL for a process scope. The scope is the
org-wide root ('$PROCESS', via ProcessName 'AllProcesses') which governs who can create/edit/delete and
administer processes — including creating inherited (child) processes — or a specific inherited process
('$PROCESS:{parentProcessTypeId}:{processTypeId}').
 
.PARAMETER ProcessName
The process name, or the sentinel 'AllProcesses' for the org-wide root scope.
 
.PARAMETER Permissions
An array of hashtables describing the desired ACEs (Identity + Permission map, e.g. @{ Create = 'Allow' }).
 
.PARAMETER isInherited
Whether the ACL inherits permissions.
 
.PARAMETER LookupResult
A hashtable to store the lookup result.
 
.PARAMETER Ensure
Specifies the desired state.
 
.PARAMETER Force
Forces the operation.
 
.OUTPUTS
System.Collections.Hashtable
#>

function Get-AzDoProcessPermission
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ProcessName,

        [Parameter()]
        [HashTable[]]$Permissions,

        [Parameter()]
        [bool]$isInherited = $true,

        [Parameter()]
        [HashTable]$LookupResult,

        [Parameter()]
        [Ensure]$Ensure,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]$Force
    )

    Write-Verbose "[Get-AzDoProcessPermission] Started."

    $SecurityNamespace = 'Process'
    $OrganizationName  = (Get-AzDoOrganizationName)

    $getResult = @{
        Ensure            = [Ensure]::Absent
        propertiesChanged = @()
        processName       = $ProcessName
        status            = $null
        reason            = $null
    }

    # Resolve the ACL token for this scope ($PROCESS root, or $PROCESS:{parent}:{id}).
    $processToken = Get-DevOpsProcessAclToken -ProcessName $ProcessName -OrganizationName $OrganizationName
    if (-not $processToken)
    {
        $getResult.status = [DSCGetSummaryState]::Error
        $getResult.reason = "Could not resolve a Process ACL token for '$ProcessName'."
        return $getResult
    }

    $namespace = Get-CacheItem -Key $SecurityNamespace -Type 'SecurityNamespaces'
    if (-not $namespace)
    {
        Write-Error "[Get-AzDoProcessPermission] Security namespace '$SecurityNamespace' not found." -ErrorAction Continue
        $getResult.status = [DSCGetSummaryState]::Error
        $getResult.reason = "Security namespace '$SecurityNamespace' not found."
        return $getResult
    }

    $getResult.namespace = $namespace

    $DevOpsACLs = Get-DevOpsACL -OrganizationName $OrganizationName -SecurityDescriptorId $namespace.namespaceId
    if (-not $DevOpsACLs)
    {
        $getResult.status = [DSCGetSummaryState]::Error
        $getResult.reason = 'No ACLs found.'
        return $getResult
    }

    # Filter the raw ACLs to just this scope's token BEFORE the expensive identity formatting.
    $DevOpsACLs = $DevOpsACLs | Where-Object { $_.token -eq $processToken }

    # Wrap in @() so single results are not unrolled to a bare hashtable (breaks [0] indexing downstream).
    $DifferenceACLs = @($DevOpsACLs | ConvertTo-FormattedACL -SecurityNamespace $SecurityNamespace -OrganizationName $OrganizationName)

    $params = @{
        Permissions       = $Permissions
        SecurityNamespace = $SecurityNamespace
        isInherited       = $isInherited
        OrganizationName  = $OrganizationName
        TokenName         = $processToken
    }

    $ReferenceACLs = @(ConvertTo-ACL @params | Where-Object { $_.token.Type -ne 'ProcessUnknown' })

    # The Process namespace has protected system-group ACEs that cannot be removed. Filter the live ACEs
    # down to just the identities we manage so the comparison only considers the relevant ACE(s).
    if ($ReferenceACLs.Count -gt 0 -and $DifferenceACLs.Count -gt 0)
    {
        $desiredOriginIds = @($ReferenceACLs[0].aces | ForEach-Object { $_.Identity.value.originId } | Where-Object { $_ })
        if ($desiredOriginIds.Count -gt 0)
        {
            $DifferenceACLs[0]['aces'] = @($DifferenceACLs[0].aces | Where-Object { $_.Identity.value.originId -in $desiredOriginIds })
        }
        else
        {
            $DifferenceACLs[0]['aces'] = @()
        }
    }

    $compareResult = Test-ACLListforChanges -ReferenceACLs $ReferenceACLs -DifferenceACLs $DifferenceACLs

    $getResult.propertiesChanged = $compareResult.propertiesChanged
    $getResult.status            = [DSCGetSummaryState]::"$($compareResult.status)"
    $getResult.reason            = $compareResult.reason
    $getResult.ReferenceACLs     = $ReferenceACLs
    $getResult.DifferenceACLs    = $DifferenceACLs

    return $getResult
}