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 * |