ASQ.psm1

function Create-SetupFile {

md $env:TEMP\ASQ -Force -erroraction silentlycontinue

@"
 
{
"tenantIDtmp" : "",
"subscriptionIDtmp" : "",
"openAIServiceNametmp" : "",
"UAMIname" : ""
}
 
"@
 | out-file $env:TEMP\ASQ\setupVars.json -force -erroraction silentlycontinue

}



function Validate-ASQSetup{

param(
$tenantID,
$subscriptionID,
$openAIServiceName,
$resourceGroupName,
$location,
$functionAppName,
$storageAccountName,
$UAMIname,
$UAMIrg
)


# 1. Validate Az Module



    if (-not(get-module Az -ListAvailable)) {
        throw "Az module not found. Please install it first to proceed"
    }

    # 2. Validate Tenant

    if ($tenantID) {

        if (-not(Get-AzTenant -tenantID $tenantID -ErrorAction SilentlyContinue)) {
            throw "Tenant not found or incorrect"
        }
        else { write-host "Tenant validated and set succesfully" -foregroundcolor green }


    }


    # 3. Validate Subscription

    if ($subscriptionID) {

        if (-not(Get-AzSubscription -SubscriptionId $subscriptionID -ErrorAction SilentlyContinue)) {
            throw "Subscription not found or incorrect"
        }
        else { write-host "Subscription validated and set succesfully" -foregroundcolor green }

    }


    # 4. Validate OpenAI Service

    if ($openAIServiceName) {

        if (-not(GET-AZRESOURCE -Name $openAIServiceName -ResourceType "Microsoft.CognitiveServices/accounts" -ErrorAction SilentlyContinue)) {
            throw "OpenAI not found or incorrect"
        }
        else { write-host "OpenAI Service validated and set succesfully" -foregroundcolor green }

    }

    # 5. Validate Resource Group

    if ($resourceGroupName) {

        if (-not(Get-AzResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue)) {
            throw "Resource Group not found or incorrect"
        }
        else { write-host "Resource Group validated and set succesfully" -foregroundcolor green }

    }

    # 6. Validate Location

    if ($location) {

        if (-not(Get-AzLocation | Where-Object{$_.location -eq "$location"} -ErrorAction SilentlyContinue)) {
            throw "Invalid Location"
        }
        else { write-host "Location validated and set succesfully" -foregroundcolor green }

    } 

    # 7. Validate Storage Account Name

    if ($storageAccountName) {
        $strTmp = Get-AzStorageAccountNameAvailability -Name $storageAccountName -ErrorAction SilentlyContinue

        if ($strTmp.NameAvailable -eq $false) {
            throw "$($strTmp.Message)"
        }
        else { write-host "Storage Account Name validated and set succesfully" -foregroundcolor green }

    } 

    # 8. Validate Function App Name

    if ($functionAppName) {

        if ((Test-NetConnection "$functionAppName.azurewebsites.net").pingsucceeded) {
            throw "Function App Name not available"
        }
        else { write-host "Function App Name validated and set succesfully" -foregroundcolor green }

    } 

    # 9. Validate user assigned managed identity

    if ($UAMIname) {

        if (-not(get-AZRESOURCE -Name $UAMIname -ResourceType "Microsoft.ManagedIdentity/userAssignedIdentities" -ErrorAction SilentlyContinue)) {
            throw "User Assigned Managed Identity NOT found or incorrect"
        }
        else { write-host "User Assigned Managed Identity validated and set succesfully" -foregroundcolor green }

    } 
}


function Invoke-ByeBye{

$funcN = $global:functionAppName
$funcK = $global:functionKey

$funcuri = "https://$($funcN).azurewebsites.net/api/MyHttpTriggerFunction?code=$funcK"
$funcuri | out-file $env:TEMP\ASQ\functionUri.txt -force
"irm '$funcuri' -body '{`"Query`":`"Give me a list of virtual networks across the tenant`"}'" | add-content  $env:TEMP\ASQ\functionUri.txt -force



Write-Host "Installation completed succesfully" -foregroundcolor blue
Write-Host "----------------------------------" -foregroundcolor green
Write-Host ""
Write-Host "You can now start sending queries like the following example"
Write-Host ""
Write-Host "ASQ -query 'List all virtual networks across the tenant with their names and locations'" -foregroundcolor green
Write-Host ""
Write-Host "Your function is also ready to be used independently. Use the following uri to call your function from anywhere you want. See the below example" -foregroundcolor green
Write-Host ""
Write-Host "irm 'URI' -body '{`"query`":`"Give me a list of virtual networks across the tenant`"}'" -foregroundcolor green
Write-Host ""
Write-Host "Your function URI is: $funcuri" -foregroundcolor green



}


function Get-ASQURI {

if(test-path "$env:TEMP\ASQ\functionUri.txt"){

gc "$env:TEMP\ASQ\functionUri.txt"
}else{

throw "Azure Function setup is not in place. Run Invoke Invoke-ASQSetup first to deploy the environment. Once the environment is ready, then you can use the Get-ASQURI command to obtain the function URI"
}

}


function Invoke-ASQSetup{
    $WarningPreference = "SilentlyContinue"

    if(!(test-path "$env:TEMP\ASQ\setupVars.json")){

    Create-SetupFile

    }

if(Test-Path "$env:TEMP\ASQ\setupVars.json"){

cls
$reDeploy = read-host "do you want to re-define the context (Tenant, Subscription, Azure OpenAI Service or User Assigned Managed Identity) Y/N?"

    if($redeploy -eq "y"){

       Create-SetupFile 

    }

$setupVarData = (gc "$env:TEMP\ASQ\setupVars.json") | convertfrom-json

if(($setupVarData.subscriptionIDtmp -eq "") -OR ($setupVarData.tenantIDtmp -eq "") -OR ($setupVarData.openAIServiceNametmp -eq "") -OR ($setupVarData.UAMIname -eq "")){

    write-host "please connect to an Azure Tenant and select a Subsciption to proceed" -foregroundcolor yellow
    $context = connect-azaccount

    if($context){
    cls
    $openAIServiceNamePrompt = read-host "please type the name of Azure OpenAI service that you want to get connected"
    $UAMInamePrompt = read-host "Please type the name of User Assigned Managed Identity listed above that has access to the selected Azure OpenAI Service"

    [pscustomobject]$setupVarData = @{
    tenantIDtmp = "$($context.context.tenant.id)"
    subscriptionIDtmp = "$($context.context.subscription.id)"    
    openAIServiceNametmp = "$($openAIServiceNamePrompt)"
    UAMIname = "$($UAMInamePrompt)"
    }

    $setupVarData | convertto-json | out-file "$env:TEMP\ASQ\setupVars.json" -force -erroraction silentlycontinue

    }

}

write-host "All required variables are already set. Proceeding with the validation" -foregroundcolor green


}




$rnd = get-random

$global:resourceGroupName = "AzureSmartQueries$($rnd)"
$global:location = "EastUS"
$global:functionAppName = "AzureSmartQueries$($rnd)"
$global:planName = "AzureSmartQueries$($rnd)"
$global:storageAccountName = "asqstorage$($rnd)"



$global:tenantIDtmp = $setupVarData.tenantIDtmp
$global:subscriptionIDtmp = $setupVarData.subscriptionIDtmp
$global:openAIServiceNametmp = $setupVarData.openAIServiceNametmp
$global:UAMIname = $setupVarData.UAMIname

Validate-ASQSetup -tenantID $($global:tenantIDtmp) -subscriptionID $($global:subscriptionIDtmp) -openAIServiceName $($global:openAIServiceNametmp) -location $($global:location) -storageAccountName $($global:storageAccountName) -UAMIname $($global:UAMIname)

$global:UAMI = Get-AzUserAssignedIdentity -Name "$global:UAMIname" -ResourceGroupName (get-AZRESOURCE -Name "$global:UAMIname" -ResourceType "Microsoft.ManagedIdentity/userAssignedIdentities").ResourceGroupName
$global:accountIDtmp = ($global:uami).ClientId


Write-Host ""
Write-Host ""
Write-Host "Validation for all the inputs are done. Now proceeding with the setup" -foregroundcolor green
Write-Host "---------------------------------------------------------------------" -foregroundcolor green
Write-Host ""
Write-Host ""

New-AzResourceGroup -Name $global:resourceGroupName -Location $global:location -Force -erroraction SilentlyContinue
New-AzStorageAccount -ResourceGroupName $global:resourceGroupName -Name $global:storageAccountName -SkuName Standard_LRS -Location $global:location -Kind StorageV2
#New-AzAppServicePlan -ResourceGroupName $global:resourceGroupName -Name $global:planName -Location $global:location -WorkerSize Small -Tier "Dynamic"
New-AzFunctionApp -ResourceGroupName $global:resourceGroupName -Name $global:functionAppName -StorageAccountName $global:storageAccountName -Runtime PowerShell -Location $global:location

Update-AzFunctionApp -ResourceGroupName $global:resourceGroupName -Name $global:functionAppName -IdentityType UserAssigned -IdentityID "$(($global:uami).ID)" -Force

$resourceId = (Get-AzResource -ResourceGroupName $global:resourceGroupName -Name $global:functionAppName -ResourceType "Microsoft.Web/sites").ResourceId
$path = "$resourceId/host/default/listkeys?api-version=2021-02-01"
$result = Invoke-AzRestMethod -path $Path -Method POST
if($result -and $result.StatusCode -eq 200)
{

   $contentBody = $result.Content | ConvertFrom-Json
   $global:functionKey = $contentBody.masterKey
}


###


$functionName = "MyHttpTriggerFunction"

# Create the function directory
$functionAppPath = "$env:TEMP\$functionName\"
$functionPath = "$env:TEMP\$functionName\$functionName"
New-Item -ItemType Directory -Path $functionAppPath -Force
New-Item -ItemType Directory -Path $functionPath -Force

# Create run.ps1
@"
 
using namespace System.Net
 
# Input bindings are passed in via param block.
param(`$Request, `$TriggerMetadata)
 
`$tenantID = '$global:tenantIDtmp'
`$subscriptionID = '$global:subscriptionIDtmp'
`$openAIServiceName = '$global:openAIServiceNametmp'
`$accountID = '$global:accountIDtmp'
 
 
`$inputQuery = `$Request.Body.query
if (-not `$inputQuery) {
    return Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = 400
        Body = "Query parameter is required."
    })
}
 
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
 
Connect-AzAccount -tenant "`$tenantID" -Identity -AccountId "`$AccountID" -ErrorAction SilentlyContinue
select-azsubscription "`$subscriptionID"
 
 
 
 
function Invoke-GraphQuery {
param(
    `$query
)
 
# Install-module 'Az.ResourceGraph' -force
# Import-module 'Az.ResourceGraph'
`$queryResult = Search-AzGraph -UseTenantScope -Query `$query
return `$queryResult
 
 
}
 
 
 
function Invoke-OpenAIChatGPT4o{
 
param(
 
`$question,
`$name
 
)
 
`$AZURE_OPENAI_API_KEY = (Get-AzCognitiveServicesAccountKey ``
 -Name `$name -ResourceGroupName openAI | Select-Object -Property Key1).Key1
 
 
`$AZURE_OPENAI_ENDPOINT = (Get-AzCognitiveServicesAccount ``
 -ResourceGroupName openAI -Name `$name | Select-Object -Property endpoint).endpoint
 
 
 
`$headers = @{
"api-key" = "`$AZURE_OPENAI_API_KEY"
"Content-Type" = "application/json"
}
 
 
`$messages = @()
 
`$messages += @{
  role = 'user'
  content = "`$question"
}
 
 
 
`$body = [ordered]@{
   messages = `$messages
} | ConvertTo-Json
 
`$response = invoke-webrequest -method POST ``
    -uri "`$AZURE_OPENAI_ENDPOINT/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01" ``
    -header `$headers ``
    -body `$body ``
     | convertfrom-json ``
     | select -ExpandProperty choices ``
     | select -ExpandProperty message ``
     | select content
 
`$response.content
 
 
}
 
 
function Get-Azure {
param(
 
`$query
)
 
`$RESP = Invoke-OpenAIChatGPT4o -name "`$(`$openAIServiceName)" -question "`$(`$query). Can You please give me the azure resource graph query based on this request. Please make sure the response only contains the query itself and nothing else so that I can directly copy and paste"
`$resp = `$resp.replace('```','').replace('kusto','').replace('kql','')
 
Invoke-GraphQuery -query "`$(`$resp)"
 
 
}
 
 
 
 
`$executionResult = Get-Azure -query "`$(`$inputQuery)"
 
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = 200
    Body = `$executionResult
})
 
 
"@
 | Out-File -FilePath "$functionPath\run.ps1"

# Upload the function files
#Compress-Archive -Path "$functionPath\*" -DestinationPath "$functionPath\$functionName.zip" -Force


@"
{
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "Request",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    }
  ]
}
"@
 | Out-File -FilePath "$functionPath\function.json"

# Upload the function files
#Compress-Archive -Path "$functionPath\*" -DestinationPath "$functionPath\$functionName.zip" -Force





@"
{
  "version": "2.0",
  "isDefaultHostConfig": true,
  "managedDependency": {
    "Enabled": true
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}
"@
 | Out-File -FilePath "$functionAppPath\host.json"

# Upload the function files
#Compress-Archive -Path "$functionAppPath\*" -DestinationPath "$functionAppPath\$functionName.zip" -Force


############


@"
@{
    # For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'.
    # To use the Az module in your function app, please uncomment the line below.
    'Az' = '11.*'
    'Az.ResourceGraph' = '1.1.0'
}
"@
 | Out-File -FilePath "$functionAppPath\requirements.psd1"



###





@"
if (`$env:MSI_SECRET) {
    Disable-AzContextAutosave -Scope Process | Out-Null
    Connect-AzAccount -Identity
}
"@
 | Out-File -FilePath "$functionAppPath\profile.ps1"

# Upload the function files
Compress-Archive -Path "$functionAppPath\*" -DestinationPath "$functionAppPath\$functionName.zip" -Force





# Uploading the function into the function App
$app = Get-AzWebApp -ResourceGroupName $global:resourceGroupName -Name $global:functionAppName
Publish-AzWebapp -webapp $app -ArchivePath "$functionAppPath\$functionName.zip" -Force

if($?){Invoke-ByeBye}

else {Write-Host "Something went wrong. Please try again" -foregroundcolor red}

$WarningPreference = "Continue"
}


function ASQ{
param(
$query
)

irm $((Get-ASQURI)[0]) -body (@{"query"="$query"} | convertto-json)


}



Export-ModuleMember -Function * -Alias *