Public/Discovery/Get-ResourcePermission.ps1

function Get-ResourcePermission {
    [cmdletbinding()]
    [OutputType([System.Collections.Concurrent.ConcurrentBag[PSCustomObject]])]
    param (
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^[0-9a-fA-F-]{36}$', ErrorMessage = "It does not match expected pattern '{1}'")]
        [Alias('subscription-id')]
        [string]$SubscriptionId,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceGroupCompleterAttribute()]
        [Alias('resource-group')]
        [string]$ResourceGroupName,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceTypeCompleterAttribute()]
        [Alias('resource-type')]
        [string]$ResourceType,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Alias('resource-name')]
        [string]$ResourceName,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Alias('permission-type')]
        [ValidateSet('Write', 'Action', 'All')]
        [string]$PermissionType = 'All',

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false)]
        [Alias('throttle-limit')]
        [ValidateRange(1, 1000)]
        [int]$ThrottleLimit = 100,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Object", "JSON", "CSV", "Table")]
        [Alias("output", "o")]
        [string]$OutputFormat = "Table"
    )

    begin {
        Write-Verbose "Starting function $($MyInvocation.MyCommand.Name)"
        $MyInvocation.MyCommand.Name | Invoke-BlackCat
        
        Write-Host "🎯 Starting Azure Resource Permission Analysis..." -ForegroundColor Green
        
        if ($SubscriptionId) {
            Write-Host " 🔒 Scope: Specific Subscription = $SubscriptionId" -ForegroundColor Cyan
        }
        if ($ResourceGroupName) {
            Write-Host " 📁 Filter: Resource Group = $ResourceGroupName" -ForegroundColor Cyan
        }
        if ($ResourceType) {
            Write-Host " 🏗️ Filter: Resource Type = $ResourceType" -ForegroundColor Cyan
        }
        if ($ResourceName) {
            Write-Host " 📝 Filter: Resource Name = $ResourceName" -ForegroundColor Cyan
        }
        Write-Host " 🔐 Permission Type: $PermissionType" -ForegroundColor Cyan
        
        $resourcePermissions = [System.Collections.Concurrent.ConcurrentBag[PSCustomObject]]::new()
        $baseUri = 'https://management.azure.com'
    }

    process {
        try {
            Write-Verbose "Retrieving all resources for the current user context"

            # Build a dynamic filter string based on provided parameters
            $filterParts = @()

            if ($SubscriptionId) {
                $filterParts += "| where subscriptionId == '$SubscriptionId'"
            }
            if ($ResourceGroupName) {
                $filterParts += "| where resourceGroup == '$ResourceGroupName'"
            }
            if ($ResourceType) {
                $filterParts += "| where type == '$($ResourceType.ToLower())'"
            }
            if ($ResourceName) {
                $filterParts += "| where name == '$ResourceName'"
            }

            if ($filterParts.Count -gt 0) {
                $filterString = $filterParts -join ' '
                $resources = Invoke-AzBatch -filter $filterString
                Write-Verbose "Filter string: $filterString"
            } else {
                $resources = Invoke-AzBatch
            }

            if (-not $resources -or $resources.Count -eq 0) {
                Write-Host "⚠️ No resources found matching the specified criteria" -ForegroundColor Yellow
                Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "No resources found matching the specified criteria" -Severity 'Information'
                return $resourcePermissions
            }

            Write-Host " 📊 Found $($resources.Count) resources to analyze" -ForegroundColor Cyan
            Write-Verbose "Processing $($resources.Count) resources"
            if ($resources.Count -gt 20) {
                Write-Host " ⏳ Processing $($resources.Count) resources, this may take a while..." -ForegroundColor Yellow
                Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Processing $($resources.Count) resources, this may take a while" -Severity 'Information'
            }

            Write-Host " 🔍 Analyzing resource permissions across $($resources.Count) resources with $ThrottleLimit concurrent threads..." -ForegroundColor Cyan
            $resources | ForEach-Object -Parallel {
                $resourceId          = $_.id
                $permissionType      = $using:PermissionType
                $resourcePermissions = $using:resourcePermissions
                $baseUri             = $using:baseUri

                Write-Verbose "Check permissions for each resource: $($_.name)"

                # Check permissions for each resource
                $permissionsUri = "$baseUri$resourceId/providers/Microsoft.Authorization/permissions?api-version=2018-07-01"

                $permRequestParam = @{
                    Headers = $using:script:authHeader
                    Uri     = $permissionsUri
                    Method  = 'GET'
                    ErrorAction = 'SilentlyContinue'
                }

                try {
                    $permissions = (Invoke-RestMethod @permRequestParam).value

                    if (-not $permissions -or $permissions.Count -eq 0) {
                        Write-Verbose "No permissions found for resource: $resourceId"
                        continue
                    }

                    # Filter permissions based on the requested type
                    $filteredPermissions = $permissions
                    if ($permissionType -eq 'Write') {
                        $filteredPermissions = $permissions | Where-Object { 
                            ($_.actions -contains '*') -or 
                            ($_.actions | Where-Object { $_ -match '/write$' }) 
                        }
                    }
                    elseif ($permissionType -eq 'Action') {
                        $filteredPermissions = $permissions | Where-Object { 
                            ($_.actions -contains '*') -or 
                            ($_.actions | Where-Object { $_ -match '/action$' }) 
                        }
                    }

                    if ($filteredPermissions.Count -gt 0) {
                        $resourceObject = [PSCustomObject]@{
                            ResourceId   = $resourceId
                            ResourceName = $_.name
                            ResourceType = $_.type
                            Subscription = $_.subscriptionId
                            ResourceGroup = $_.resourceGroup
                            Permissions  = $filteredPermissions
                            HasWritePermission  = ($permissions | Where-Object { 
                                ($_.actions -contains '*') -or 
                                ($_.actions | Where-Object { $_ -match '/write$' }) 
                            }).Count -gt 0
                            HasActionPermission = ($permissions | Where-Object { 
                                ($_.actions -contains '*') -or 
                                ($_.actions | Where-Object { $_ -match '/action$' }) 
                            }).Count -gt 0
                        }
                        $resourcePermissions.Add($resourceObject)
                    }
                }
                catch {
                    Write-Verbose "Unable to fetch permissions for resource: $resourceId"
                }
            } -ThrottleLimit $ThrottleLimit

            Write-Verbose "Completed retrieving resource permissions"
            Write-Verbose "Found $($resourcePermissions.Count) resources with matching permissions"
        }
        catch {
            Write-Host "❌ Error while retrieving resource permissions: $($_.Exception.Message)" -ForegroundColor Red
            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message $($_.Exception.Message) -Severity 'Error'
        }

        if ($resourcePermissions.Count -eq 0) {
            Write-Host "⚠️ No resources found with the specified permission type" -ForegroundColor Yellow
        } else {
            Write-Host "`n📊 Resource Permission Discovery Summary:" -ForegroundColor Magenta
            Write-Host " Total Resources with Permissions: $($resourcePermissions.Count)" -ForegroundColor Green
            
            # Group by resource type for summary
            $resourceTypeSummary = $resourcePermissions | Group-Object ResourceType
            foreach ($group in $resourceTypeSummary) {
                Write-Host " $($group.Name): $($group.Count)" -ForegroundColor Cyan
            }
            
            # Show permission type summary
            $writePermissions = $resourcePermissions | Where-Object { $_.HasWritePermission -eq $true }
            $actionPermissions = $resourcePermissions | Where-Object { $_.HasActionPermission -eq $true }
            if ($writePermissions.Count -gt 0) {
                Write-Host " Write Permissions: $($writePermissions.Count)" -ForegroundColor Green
            }
            if ($actionPermissions.Count -gt 0) {
                Write-Host " Action Permissions: $($actionPermissions.Count)" -ForegroundColor Yellow
            }
        }

        Write-Host "✅ Resource permission analysis completed successfully!" -ForegroundColor Green
        
        # Convert ConcurrentBag to array for output formatting
        $result = @($resourcePermissions)
        
        # Return results in requested format
        switch ($OutputFormat) {
            "JSON" {
                $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
                $jsonOutput = $result | ConvertTo-Json -Depth 3
                $jsonFilePath = "ResourcePermissions_$timestamp.json"
                $jsonOutput | Out-File -FilePath $jsonFilePath -Encoding UTF8
                Write-Host "💾 JSON output saved to: $jsonFilePath" -ForegroundColor Green
                # File created, no console output needed
                return
            }
            "CSV" {
                $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
                $csvOutput = $result | ConvertTo-Csv -NoTypeInformation
                $csvFilePath = "ResourcePermissions_$timestamp.csv"
                $csvOutput | Out-File -FilePath $csvFilePath -Encoding UTF8
                Write-Host "💾 CSV output saved to: $csvFilePath" -ForegroundColor Green
                # File created, no console output needed
                return
            }
            "Object" { return $result }
            "Table" { return $result | Format-Table -AutoSize }
        }
    }

    end {
        Write-Verbose "Function $($MyInvocation.MyCommand.Name) completed"
    }
    <#
    .SYNOPSIS
        Validates which Azure resources the authenticated user has write or action permissions on.
 
    .DESCRIPTION
        The Get-ResourcePermission function identifies Azure resources where the authenticated user
        has specified permissions (write, action, or both). It supports filtering by subscription, resource group,
        and resource type, and processes subscriptions in parallel for efficiency.
 
    .PARAMETER SubscriptionId
        Optional. Specifies a specific subscription ID to check. If omitted, all accessible subscriptions are checked.
 
    .PARAMETER ResourceGroupName
        Optional. Filters resources to a specific resource group.
 
    .PARAMETER ResourceType
        Optional. Filters resources by their type (e.g., 'Microsoft.Compute/virtualMachines' or just 'virtualMachines').
 
    .PARAMETER ResourceName
        Optional. Filters resources by their name.
 
    .PARAMETER PermissionType
        Optional. Specifies what kind of permissions to check for. Valid options:
        - Write: Checks for write permissions
        - Action: Checks for action permissions
        - All: Checks for both write and action permissions (default)
 
    .PARAMETER ThrottleLimit
        Optional. Specifies the maximum number of concurrent operations. Default is 100.
 
    .PARAMETER OutputFormat
        Optional. Specifies the output format for results. Valid options:
        - Object: Returns PowerShell objects (default when piping)
        - JSON: Returns results in JSON format and saves to file
        - CSV: Returns results in CSV format and saves to file
        - Table: Returns results in formatted table (default)
        Aliases: output, o
 
    .OUTPUTS
        Returns a collection of resources with their permission details, including:
        - ResourceId: The full resource ID
        - ResourceName: The name of the resource
        - ResourceType: The type of the resource
        - Subscription: The subscription ID
        - ResourceGroup: The resource group name
        - Permissions: Detailed permission objects
        - HasWritePermission: Boolean indicating if the user has write permissions
        - HasActionPermission: Boolean indicating if the user has action permissions
 
    .EXAMPLE
        Get-ResourcePermission -PermissionType Write
        Returns all resources across all subscriptions where the user has write permissions in table format.
 
    .EXAMPLE
        Get-ResourcePermission -SubscriptionId "00000000-0000-0000-0000-000000000000" -ResourceGroupName "MyResourceGroup"
        Returns resources in the specified subscription and resource group where the user has any permissions.
 
    .EXAMPLE
        Get-ResourcePermission -ResourceType "virtualMachines" -PermissionType Action
        Returns all virtual machine resources where the user has action permissions.
 
    .EXAMPLE
        Get-ResourcePermission -PermissionType Write -OutputFormat JSON
        Returns all resources with write permissions and saves results to a timestamped JSON file.
 
    .EXAMPLE
        Get-ResourcePermission -OutputFormat CSV
        Returns all resources with permissions and saves results to a timestamped CSV file.
 
    .NOTES
        - Requires authentication to Azure with appropriate permissions
        - Performance depends on the number of resources and subscriptions
    #>

}