AzureKQLPowerShellExtractor.psm1


function Get-AzureKQLPowerShellExtract {
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 0)]
        [string]$kqlQueryPath = "empty",
        [switch]$inExcel = $false,
        [switch]$inJSON = $false,
        [switch]$h = $false,
        [switch]$inCSV = -not ($inJSON -and $inExcel)
    )


    if ($h) {
        help
        return
    }

    if ($kqlQueryPath -eq "empty") {
        Write-Error "Path to the KQL Query is missing. Please provide the path to the KQL Query and pass it to the -kqlQueryPath Argument"  -Category InvalidArgument
        return
    }

    Write-Host "Export format selected - Excel: $inExcel, CSV: $inCSV, JSON: $inJSON" -ForegroundColor Yellow
    Login
    Write-host "Querying for data" -ForegroundColor Yellow
    $query = Get-Content $kqlQueryPath -Raw
    $queryRows = $query + " | summarize count() "
    $RowsResult = Search-AzGraph -Query $queryRows 
    $ResultRows = $RowsResult.count_
    Write-Host Total $ResultRows rows to be fetched  -ForegroundColor Blue
    $batchSize = 1000
    $totalRows = 0
    #$skipToken = $null
    $JSONdata = $null


    Exists -Excel inExcel -CSV inCSV -JSON inJSON



    # Set initial values
  
    $throttleLimit = 15
    $throttleWindow = 5
    $remainingQuota = $throttleLimit
    $lastRequestTime = Get-Date

    $subscriptions = Get-AzSubscription
    $subscriptionIds = $subscriptions.Id

    # Grouping queries by subscription


    foreach ($ID in $subscriptionIds) {
        $skipToken = $null
        $retryCount = 0
        do {
            # Staggering queries
            if ($remainingQuota -le 0) {
                $timeSinceLastRequest = (Get-Date) - $lastRequestTime
                if ($timeSinceLastRequest.TotalSeconds -lt $throttleWindow) {
                    Start-Sleep -Seconds ($throttleWindow - $timeSinceLastRequest.TotalSeconds)
                }
                $remainingQuota = $throttleLimit
            }


            try {
                $results = Search-AzGraph -Query $query -First $batchSize -SkipToken $skipToken -Subscription $ID
    
                # Output data
                if ($inJSON -eq $true) {
                    $JSONdata += $results
                }
                if ($inExcel -eq $true) {
                    $P = "result.xlsx"
                    $results | Export-Excel -Path $P -Append
                }
                if ($inCSV -eq $true) {
                    $P = "result.csv"
                    $results | Export-Csv -Path $P -Append -NoTypeInformation
                }
        
                # Update skip token and progress
                $skipToken = $results.SkipToken
                Write-Progress -Activity "Fetching data" -Status "Fetched $totalRows rows so far" -PercentComplete (($totalRows / $ResultRows) * 100)
                $totalRows += $results.Count
        
                # Update remaining quota and last request time
                $remainingQuota--
                $lastRequestTime = Get-Date
                $retryCount = 0
            }
            catch {
                <#Do this if a terminating exception happens#>
                Write-Error $_.Exception.Message

                # Handle error and retry or skip batch if necessary
                $retryCount++
                if ($retryCount -gt 3) {
                    Write-Warning "Failed to query data after $retryCount attempts. Skipping batch."
                    break
                }
                else {
                    Write-Warning "Error encountered during query. Retrying in 20 seconds..."
                    Start-Sleep -Seconds 20
                }


            }
            # Query data
           
        } while ($null -ne $skipToken)
    
    }

    if ($inJSON -eq $true) {
        $json = $JSONdata | ConvertTo-Json 
        $json | Out-File "result.json"
    }
    Write-Host "Fetched a total of $totalRows rows available at $PWD "-ForegroundColor Green
}












    
# do {
# $results = Search-AzGraph -Query $query -First $batchSize -SkipToken $skipToken
# # Output -data $results -Excel $inExcel -CSV $inCSV

# if ($inJSON -eq $true) {
# $JSONdata += $results

# }
# if ($inExcel -eq $true) {
            
# $P = "result.xlsx"
# $results | Export-Excel -Path $P -Append
# }
# if ($inCSV -eq $true) {
# $P = "result.csv"
# $results | Export-Csv -Path $P -Append -NoTypeInformation
# }

# $skipToken = $results.SkipToken
# $currentBatch++
# Write-Progress -Activity "Fetching data" -Status "Fetched $totalRows rows so far" -PercentComplete (($totalRows / $ResultRows) * 100)
# $totalRows += $results.Count
# } while ($null -ne $skipToken)




function Login () {
    $context = Get-AzContext
    if (!$context) {
        Connect-AzAccount
        if ($? -eq "False") {
            Write-Error "Error: Connect to Azure Account to proceed" -RecommendedAction "Disconnect using Disconnect-AzAccount and reconnect using Connect-AzAccount" 
        }
    }
    else {
        Write-Host "Connected to Azure" -ForegroundColor Green
        Write-Host "Connected Account" $(Get-AzContext).Account "to" $(Get-AzContext).Tenant.Id
    }
}


function help () {
    
    Write-Host "Description" -ForegroundColor Green
    Write-Host "
    With this script you will be able to generate the results of Azure Resource Graph (ARG) queries locally in csv, excel or json format.
    This script also helps to mitigate the limitation of running ARG queries on Azure Resource Graph Explorer on Azure Portal and the maximum rows that can be downloaded at a time per query. Project and documentation is available at - https://github.com/G-Lucifer/AzureKQLPowerShellExtractor .
    The new results file will generated from where the Get-AzureKQLPowerShellExtract is invoked."
 
    Write-Host "Examples" -ForegroundColor Green
    Write-Host "To generate a CSV file " 
    Write-Host "Get-AzureKQLPowerShellExtract .\sample.kql -inCSV" -ForegroundColor Yellow
    Write-Host "To generate a JSON file " 
    Write-Host "Get-AzureKQLPowerShellExtract -kqlQueryPath .\sample.kql -inJSON" -ForegroundColor Yellow
    Write-Host "To generate an Excel file " 
    Write-Host "Get-AzureKQLPowerShellExtract -kqlQueryPath .\sample.kql -inExcel" -ForegroundColor Yellow
    Write-Host "To generate all three types of file " 
    Write-Host "Get-AzureKQLPowerShellExtract -kqlQueryPath .\sample.kql -inCSV -inExcel -inJSON" -ForegroundColor Yellow
    Write-Host "Arguments" -ForegroundColor Green
    Write-Host "-kqlQueryPath {Mandatory Parameter, Postitional Argument, Requires Path to the KQL file}" 
    Write-Host "-inExcel {Optional Flag, Mention this to generate result.xlsx file}" 
    Write-Host "-inJSON {Optional Flag, Mention this to generate result.json file}" 
    Write-Host "-inCSV {Optional Flag, Mention this to generate result.csv file}" 
    Write-Host "-h {Optional Flag, Mention this for help}" 

       





        
}


function Exists () {
    param(
                
        [switch]$Excel = $false,
        [switch]$CSV = $false,
        [switch]$JSON = $false
            
    )

    
    if (($inJSON -eq $true) -and (Test-Path -Path "result.json") ) {
        Write-Information "Removing the existing result.json file" 
        Remove-Item "result.json"
        
    }
    if (($inExcel -eq $true) -and (Test-Path -Path "result.xlsx") ) {
        Write-Information "Removing the existing result.xlsx file" 

        Remove-Item "result.xlsx"
      
    }
    if (($inCSV -eq $true) -and (Test-Path -Path "result.csv") ) {
        Write-Information "Removing the existing result.csv file" 

        Remove-Item "result.csv"
        
    }


}
# function Output () {
# param(
# [Parameter(Mandatory = $true)]
# $data,
# [switch]$Excel = $false,
# [switch]$CSV = $false
    
# )
# Write-Host "Excel: $Excel, CSV: $CSV"

# if ($Excel) {
# $P = "result.xlsx"
# $data | Export-Excel -Path $P -Append
# Write-Host "p is $P"
# }
# if ($CSV) {
# $P ="result.csv"
# $data | Export-Csv -Path $P -Append -NoTypeInformation
# Write-Host "p is $P "
# }
# }

Export-ModuleMember -Function Get-AzureKQLPowerShellExtract