Public/Reconnaissance/Invoke-AzBatch.ps1

function Invoke-AzBatch {
    [cmdletbinding()]
    param (
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^Microsoft\.[A-Za-z]+(/[A-Za-z]+)+$|^$')]
        [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceTypeCompleterAttribute()]
        [Alias('resource-type')]
        [string]$ResourceType,

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

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [switch]$Silent,

        [Parameter(Mandatory = $false)]
        [string]$filter,

        [Parameter(Mandatory = $false)]
        [switch]$SkipCache,

        [Parameter(Mandatory = $false)]
        [int]$CacheExpirationMinutes = 30,

        [Parameter(Mandatory = $false)]
        [int]$MaxCacheSize = 100,

        [Parameter(Mandatory = $false)]
        [switch]$CompressCache,

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

    begin {

        Write-Verbose "Starting function $($MyInvocation.MyCommand.Name)"
        $MyInvocation.MyCommand.Name | Invoke-BlackCat
    }

    process {
        $cacheParams = @{
            ResourceType = $ResourceType
            Name = $Name
            Filter = $filter
            Silent = $Silent.IsPresent
        }
        $baseIdentifier = "azbatch"
        $cacheKey = ConvertTo-CacheKey -BaseIdentifier $baseIdentifier -Parameters $cacheParams

        if (-not $SkipCache) {
            try {
                $cachedResult = Get-BlackCatCache -Key $cacheKey -CacheType 'AzBatch'
                if ($null -ne $cachedResult) {
                    Write-Verbose "Retrieved result from cache for Azure Batch query"
                    return $cachedResult
                }
            }
            catch {
                Write-Verbose "Error retrieving from cache: $($_.Exception.Message). Proceeding with fresh API call."
            }
        }

        try {
            $allResources = @()
            $skipToken = $null
            $pageCount = 0

            do {
                $pageCount++
                Write-Verbose "Retrieving page $pageCount of resources"

                $payload = @{
                    requests = @(
                        @{
                            httpMethod = 'POST'
                            url        = $($sessionVariables.resourceGraphUri)
                            content    = @{
                                query = "resources"
                            }
                        }
                    )
                }

                if (![string]::IsNullOrEmpty($ResourceType)) {
                    $payload.requests[0].content.query = "resources | where type == '$($ResourceType.ToLower())'"
                }

                if (![string]::IsNullOrEmpty($Name)) {
                    $payload.requests[0].content.query += " | where name == '$($Name)'"
                    Write-Output "Filtering resources by name: $Name"
                }

                if (![string]::IsNullOrEmpty($filter)) {
                    $payload.requests[0].content.query += "$filter"
                    Write-Output "Filtering resources with: $($payload.requests[0].content.query)"
                }

                # Add skipToken to the request if available
                if ($skipToken) {
                    if (!$payload.requests[0].content.options) {
                        $payload.requests[0].content.options = @{}
                    }
                    $payload.requests[0].content.options.'$skipToken' = $skipToken
                    Write-Verbose "Using skipToken for pagination: $skipToken"
                }

                $requestParam = @{
                    Headers     = $script:authHeader
                    Uri         = $sessionVariables.batchUri
                    Method      = 'POST'
                    ContentType = 'application/json'
                    Body        = $payload | ConvertTo-Json -Depth 10
                    UserAgent   = $($sessionVariables.userAgent)
                }

                Write-Verbose "Making API request using User-Agent: $($sessionVariables.userAgent)"
                $response = Invoke-RestMethod @requestParam
                $pageData = $response.responses.content.data

                if ($pageData) {
                    $allResources += $pageData
                    Write-Verbose "Retrieved $($pageData.Count) resources on page $pageCount. Total count: $($allResources.Count)"
                }

                # Get skipToken for next page if it exists
                $skipToken = $response.responses.content.'$skipToken'

            } while ($skipToken)

            if ($allResources.Count -eq 0) {
                if (-not $Silent) {
                    Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "No resources found" -Severity 'Information'
                }
                return $null
            }

            if (-not $SkipCache -and $null -ne $allResources) {
                try {
                    Set-BlackCatCache -Key $cacheKey -Data $allResources -ExpirationMinutes $CacheExpirationMinutes -CacheType 'AzBatch' -MaxCacheSize $MaxCacheSize -CompressData:$CompressCache
                    Write-Verbose "Cached Azure Batch result (expires in $CacheExpirationMinutes minutes)"
                }
                catch {
                    Write-Verbose "Failed to cache Azure Batch result - $($_.Exception.Message)"
                }
            }

            $formatParam = @{
                Data          = $allResources
                OutputFormat  = $OutputFormat
                FunctionName  = $MyInvocation.MyCommand.Name
                FilePrefix    = 'AzBatch'
                Silent        = $Silent.IsPresent
            }
            return Format-BlackCatOutput @formatParam
        }
        catch {
            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message $($_.Exception.Message) -Severity 'Error'
        }
    }
    <#
    .SYNOPSIS
        Invokes Azure Resource Graph queries using batch requests with caching support.
 
    .DESCRIPTION
        This function sends batch requests to Azure Resource Graph API to query Azure resources.
        It supports filtering by resource type, name, and custom filters. The function includes
        automatic pagination, caching for improved performance, and comprehensive error handling.
 
    .PARAMETER ResourceType
        The Azure resource type to filter by (e.g., 'Microsoft.Storage/storageAccounts').
        Must follow the format 'Microsoft.Provider/resourceType'.
 
    .PARAMETER Name
        The specific resource name to filter by. When specified, only resources with this exact name will be returned.
 
    .PARAMETER Silent
        When specified, suppresses informational messages about no resources found.
 
    .PARAMETER filter
        Additional KQL (Kusto Query Language) filter to apply to the resource query.
        This is appended to the base query for advanced filtering.
 
    .PARAMETER SkipCache
        When specified, bypasses the cache and forces a fresh API call.
 
    .PARAMETER CacheExpirationMinutes
        Sets the cache expiration time in minutes. Default is 30 minutes.
        This parameter controls how long the cached results remain valid.
 
    .PARAMETER MaxCacheSize
        Maximum number of entries to store in the cache. Default is 100.
        When this limit is reached, least recently used entries are removed.
 
    .PARAMETER CompressCache
        When specified, compresses cache data to reduce memory usage.
        Recommended for large environments with many resources.
 
    .PARAMETER OutputFormat
        Specifies the output format for the results. Valid values are:
        - Object: Returns PowerShell objects (default)
        - JSON: Exports results to a timestamped JSON file and displays file path
        - CSV: Exports results to a timestamped CSV file and displays file path
        - Table: Displays results in a formatted table and returns objects
        Default is 'Object'. Supports aliases 'output' and 'o'.
 
    .EXAMPLE
        Invoke-AzBatch -ResourceType "Microsoft.Storage/storageAccounts"
 
        This example retrieves all storage accounts in the current subscription context.
 
    .EXAMPLE
        Invoke-AzBatch -ResourceType "Microsoft.Compute/virtualMachines" -Name "myVM"
 
        This example retrieves a specific virtual machine named "myVM".
 
    .EXAMPLE
        Invoke-AzBatch -ResourceType "Microsoft.KeyVault/vaults" -SkipCache
 
        This example forces a fresh API call to retrieve key vaults, bypassing any cached results.
 
    .EXAMPLE
        Invoke-AzBatch -ResourceType "Microsoft.Storage/storageAccounts" -filter "| where location == 'eastus'"
 
        This example retrieves storage accounts in the East US region using a custom filter.
 
    .EXAMPLE
        Invoke-AzBatch -ResourceType "Microsoft.Compute/virtualMachines" -CacheExpirationMinutes 60
 
        This example retrieves virtual machines and caches the results for 60 minutes instead of the default 30 minutes.
 
    .EXAMPLE
        Invoke-AzBatch -ResourceType "Microsoft.Storage/storageAccounts" -MaxCacheSize 50 -CompressCache
 
        This example retrieves storage accounts with a smaller cache size and enables compression for large environments.
 
    .EXAMPLE
        Invoke-AzBatch -ResourceType "Microsoft.Network/virtualNetworks" -CompressCache
 
        This example retrieves virtual networks and compresses the cached data to reduce memory usage.
 
    .NOTES
        - This function requires appropriate Azure permissions to query resources
        - Results are automatically cached to improve performance for repeated queries
        - Use Get-BlackCatCacheStats to monitor cache usage
        - The function handles pagination automatically for large result sets
    #>

}