Framework/Core/AzureMonitoring/OMSMonitoring.ps1

Set-StrictMode -Version Latest 

class OMSMonitoring: CommandBase
{
    [string] $OMSSampleViewTemplateFilepath;
    [string] $OMSSearchesTemplateFilepath;
    [string] $OMSAlertsTemplateFilepath;
    [string] $OMSGenericTemplateFilepath;
    
    [string] $OMSLocation;
    [string] $OMSResourceGroup;
    [string] $OMSWorkspaceName;
    [string] $OMSWorkspaceId;
    [string] $ApplicationSubscriptionName

    OMSMonitoring([string] $_omsSubscriptionId,[string] $_omsResourceGroup,[string] $_omsWorkspaceId, [InvocationInfo] $invocationContext): 
        Base([string] $_omsSubscriptionId, $invocationContext) 
    {                     
        $this.OMSResourceGroup = $_omsResourceGroup
        $this.OMSWorkspaceId = $_omsWorkspaceId
        $omsWorkSpaceInstance = Get-AzureRmOperationalInsightsWorkspace | Where-Object {$_.CustomerId -eq "$_omsWorkspaceId" -and $_.ResourceGroupName -eq  "$($this.OMSResourceGroup)"}
        if($null -eq $omsWorkSpaceInstance)
        {
            throw [SuppressedException] "Invalid OMS Workspace."
        }
        $this.OMSWorkspaceName = $omsWorkSpaceInstance.Name;
        $locationInstance = Get-AzureRmLocation | Where-Object { $_.DisplayName -eq $omsWorkSpaceInstance.Location -or  $_.Location -eq $omsWorkSpaceInstance.Location } 
        $this.OMSLocation = $locationInstance.Location                
    }

    [void] ConfigureOMS([string] $_applicationSubscriptionId,[string] $_applicationResourceGroups,[string] $_viewName,[string] $_securityContactEmails,[OMSInstallationOption] $_omsInstallationOption, [bool] $_validateOnly, [bool] $_useOMSModel)    
    {        
        
        $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nStarted setting up AzSDK OMS solution pack`r`n"+[Constants]::DoubleDashLine);
        
        $OptionalParameters = New-Object -TypeName Hashtable

        #the default installation option is All
        if($null -eq $_omsInstallationOption)
        {
            $OMSInstallationOption = [OMSInstallationOption]::All
        }

        $OMSLogPath = [Constants]::AzSdkTempFolderPath + "\OMS";
        if(-not (Test-Path -Path $OMSLogPath))
        {
            mkdir -Path $OMSLogPath -Force | Out-Null
        }
        
        if($_useOMSModel)
        {
            [MessageData] $data = [MessageData]@{                            
                            Message = "`nSetting up OMS view using old query language.";
                            MessageType = [MessageType]::Warning;
                    };                    
            $this.PublishCustomMessage($data)
            $appSub = Get-AzureRmSubscription -SubscriptionId $_applicationSubscriptionId
            $this.ApplicationSubscriptionName = $appSub.Name
            if([OMSInstallationOption]::All -eq $_omsInstallationOption -or [OMSInstallationOption]::Queries -eq $_omsInstallationOption)
            {
                $searchesTemplateFilepath = [ConfigurationManager]::LoadServerConfigFile("AzSDK.AM.OMS.Searches.omsview");
                $this.OMSSearchesTemplateFilepath = $OMSLogPath+"\AzSDK.AM.OMS.Searches.omsview";
                $searchesTemplateFilepath | ConvertTo-Json -Depth 100 | Out-File $this.OMSSearchesTemplateFilepath
                
                $this.PublishCustomMessage("`r`nSetting up OMS search queries.");
                $this.ConfigureSearches($_validateOnly);
            }
            if([OMSInstallationOption]::All -eq $_omsInstallationOption -or [OMSInstallationOption]::Alerts -eq $_omsInstallationOption)
            {
                $alertsTemplateFilepath = [ConfigurationManager]::LoadServerConfigFile("AzSDK.AM.OMS.Alerts.omsview"); 
                $this.OMSAlertsTemplateFilepath = $OMSLogPath+"\AzSDK.AM.OMS.Alerts.omsview";
                $alertsTemplateFilepath | ConvertTo-Json -Depth 100 | Out-File $this.OMSAlertsTemplateFilepath
                $this.PublishCustomMessage("`r`nSetting up OMS alerts.");
                $this.ConfigureAlerts($_viewName, $_securityContactEmails, $_validateOnly);
            }
            if([OMSInstallationOption]::All -eq $_omsInstallationOption -or [OMSInstallationOption]::SampleView -eq $_omsInstallationOption)
            {
                $sampleViewTemplateFilepath = [ConfigurationManager]::LoadServerConfigFile("AzSDK.AM.OMS.SampleView.omsview"); 
                $this.OMSSampleViewTemplateFilepath = $OMSLogPath+"\AzSDK.AM.OMS.SampleView.omsview";    
                $sampleViewTemplateFilepath | ConvertTo-Json -Depth 100 | Out-File $this.OMSSampleViewTemplateFilepath

                $this.PublishCustomMessage("`r`nSetting up OMS sample view.");
                $this.ConfigureSampleView($_viewName, $_applicationResourceGroups, $_validateOnly);
            }
            if([OMSInstallationOption]::All -eq $_omsInstallationOption -or [OMSInstallationOption]::GenericView -eq $_omsInstallationOption)
            {
                $sampleViewTemplateFilepath = [ConfigurationManager]::LoadServerConfigFile("AZSDK.AM.OMS.GenericView.V1.omsview"); 
                $this.OMSGenericTemplateFilepath = $OMSLogPath+"\AZSDK.AM.OMS.GenericView.V1.omsview";    
                $sampleViewTemplateFilepath | ConvertTo-Json -Depth 100 | Out-File $this.OMSGenericTemplateFilepath

                $this.PublishCustomMessage("`r`nSetting up OMS AzSDK Generic view.");
                $this.ConfigureGenericView($_viewName, $_validateOnly);
            }
        }
        else
        {
            $genericViewTemplateFilepath = [ConfigurationManager]::LoadServerConfigFile("AZSDK.AM.OMS.GenericView.V2.omsview");                 
            $this.OMSGenericTemplateFilepath = $OMSLogPath+"\AZSDK.AM.OMS.GenericView.V2.omsview";
            $genericViewTemplateFilepath | ConvertTo-Json -Depth 100 | Out-File $this.OMSGenericTemplateFilepath
            $this.PublishCustomMessage("`r`nSetting up OMS AzSDK generic view.");
            $this.ConfigureGenericView($_viewName, $_validateOnly);            
        }
        $this.PublishCustomMessage([Constants]::SingleDashLine + "`r`nWe have added new queries for the OMS solution. These will help reflect the aggregate control pass/fail status more accurately. Please go here to get them: https://aka.ms/azsdk/omsqueries `r`n",[MessageType]::Warning);
        $this.PublishCustomMessage([Constants]::SingleDashLine + "`r`nCompleted setting up AzSDK OMS solution pack.`r`n"+[Constants]::DoubleDashLine);
        
    }

    [void] ConfigureGenericView([string] $_viewName, [bool] $_validateOnly)
    {
        $OptionalParameters = New-Object -TypeName Hashtable

        $OptionalParameters = $this.GetOMSGenericViewParameters($_viewName);
        $this.PublishCustomMessage([MessageData]::new("Starting template deployment for OMS generic view. Detailed logs are shown below."));
        $ErrorMessages = @()
        if ($_validateOnly) {
            $ErrorMessages =@()
                Test-AzureRmResourceGroupDeployment -ResourceGroupName $this.OMSResourceGroup `
                                                    -TemplateFile $this.OMSGenericTemplateFilepath `
                                                    -TemplateParameterObject $OptionalParameters -Verbose
        }
        else {

            $ErrorMessages =@()
            $SubErrorMessages = @()
            New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $this.OMSGenericTemplateFilepath).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
                                        -ResourceGroupName $this.OMSResourceGroup `
                                        -TemplateFile $this.OMSGenericTemplateFilepath  `
                                        -TemplateParameterObject $OptionalParameters `
                                        -Verbose -Force -ErrorVariable SubErrorMessages
            $SubErrorMessages = $SubErrorMessages | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }
            $ErrorMessages += $SubErrorMessages
           
        }
        if ($ErrorMessages)
        {
            "", ("{0} returned the following errors:" -f ("Template deployment", "Validation")[[bool]$_validateOnly]), @($ErrorMessages) | ForEach-Object { $this.PublishCustomMessage([MessageData]::new($_));}
        }
        else
        {
            $this.PublishCustomMessage([MessageData]::new("Completed template deployment for OMS generic view."));            
        }
    }

    [void] ConfigureSampleView([string] $_applicationName, [string] $_applicationResourceGroups, [bool] $_validateOnly)
    {
        $OptionalParameters = New-Object -TypeName Hashtable
        $_query = $this.GetOMSAppQuery($_applicationResourceGroups);

        $OptionalParameters = $this.GetOMSSampleViewParameters($_applicationName, $_query);

        $this.PublishCustomMessage([MessageData]::new("Starting template deployment for OMS sample view. Detailed logs are shown below."));
        $ErrorMessages = @()
        if ($_validateOnly) {
            $ErrorMessages =@()
                Test-AzureRmResourceGroupDeployment -ResourceGroupName $this.OMSResourceGroup `
                                                    -TemplateFile $this.OMSSampleViewTemplateFilepath `
                                                    -TemplateParameterObject $OptionalParameters -Verbose
        }
        else {

            $ErrorMessages =@()
            $SubErrorMessages = @()
            New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $this.OMSSampleViewTemplateFilepath).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
                                        -ResourceGroupName $this.OMSResourceGroup `
                                        -TemplateFile $this.OMSSampleViewTemplateFilepath  `
                                        -TemplateParameterObject $OptionalParameters `
                                        -Verbose -Force -ErrorVariable SubErrorMessages
            $SubErrorMessages = $SubErrorMessages | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }
            $ErrorMessages += $SubErrorMessages
           
        }
        if ($ErrorMessages)
        {
            "", ("{0} returned the following errors:" -f ("Template deployment", "Validation")[[bool]$_validateOnly]), @($ErrorMessages) | ForEach-Object { $this.PublishCustomMessage([MessageData]::new($_));}
        }
        else
        {
            $this.PublishCustomMessage([MessageData]::new("Completed template deployment for OMS sample view."));            
        }
    }

    [void] ConfigureSearches([bool] $_validateOnly)
    {

        $OptionalParameters = New-Object -TypeName Hashtable

        $savedSearches = Get-AzureRmOperationalInsightsSavedSearch -ResourceGroupName $this.OMSResourceGroup -WorkspaceName $this.OMSWorkspaceName
        $azSdkSearches = @()
        if($savedSearches -ne $null -and $savedSearches.Value -ne $null)
        {
            $savedSearches.Value | %{
                Set-Variable -Name savedSearch -Value $_
                if($savedSearch.Properties -ne $null -and $savedSearch.Properties.Category -like "AzSDK_Searches*")
                {
                    $azSdkSearches += $savedSearch
                }
            }
        }        
        if($azSdkSearches.Length -gt 0)
        {
            return;
        }
        $OptionalParameters = $this.GetOMSBaseParameters();

        $this.PublishCustomMessage([MessageData]::new("Starting template deployment for OMS sample search queries. Detailed logs are shown below."));
        $ErrorMessages = @()
        if ($_validateOnly) {
            $ErrorMessages =@()
                Test-AzureRmResourceGroupDeployment -ResourceGroupName $this.OMSResourceGroup `
                                                    -TemplateFile $this.OMSSearchesTemplateFilepath `
                                                    -TemplateParameterObject $OptionalParameters -Verbose
        }
        else {

            $ErrorMessages =@()
            $SubErrorMessages = @()
            New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $this.OMSSearchesTemplateFilepath).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
                                        -ResourceGroupName $this.OMSResourceGroup `
                                        -TemplateFile $this.OMSSearchesTemplateFilepath  `
                                        -TemplateParameterObject $OptionalParameters `
                                        -Verbose -Force -ErrorVariable SubErrorMessages
            $SubErrorMessages = $SubErrorMessages | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }
            $ErrorMessages += $SubErrorMessages
           
        }
        if ($ErrorMessages)
        {
            "", ("{0} returned the following errors:" -f ("Template deployment", "Validation")[[bool] $_validateOnly]), @($ErrorMessages) | ForEach-Object { $this.PublishCustomMessage([MessageData]::new($_));}
        }
        else
        {
            $this.PublishCustomMessage([MessageData]::new("Completed template deployment for OMS sample search queries."));            
        }
    }

    [void] ConfigureAlerts([string] $_applicationName, [string] $_alertemails, [bool] $_validateOnly)
    {

        $OptionalParameters = New-Object -TypeName Hashtable
        $OptionalParameters = $this.GetOMSAlertParameters($_applicationName, $_alertemails);

        $this.PublishCustomMessage([MessageData]::new("Starting template deployment for OMS sample alerts. Detailed logs are shown below."));
        $ErrorMessages = @()
        if ($_validateOnly) {
            $ErrorMessages =@()
                Test-AzureRmResourceGroupDeployment -ResourceGroupName $this.OMSResourceGroup `
                                                    -TemplateFile $this.OMSAlertsTemplateFilepath `
                                                    -TemplateParameterObject $OptionalParameters -Verbose
        }
        else {

            $ErrorMessages =@()
            $SubErrorMessages = @()
            New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $this.OMSAlertsTemplateFilepath).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
                                        -ResourceGroupName $this.OMSResourceGroup `
                                        -TemplateFile $this.OMSAlertsTemplateFilepath  `
                                        -TemplateParameterObject $OptionalParameters `
                                        -Verbose -Force -ErrorVariable SubErrorMessages
            $SubErrorMessages = $SubErrorMessages | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }
            $ErrorMessages += $SubErrorMessages
           
        }
        if ($ErrorMessages)
        {
            "", ("{0} returned the following errors:" -f ("Template deployment", "Validation")[[bool]$_validateOnly]), @($ErrorMessages) | ForEach-Object { $this.PublishCustomMessage([MessageData]::new($_));}
        }
        else
        {
            $this.PublishCustomMessage([MessageData]::new("Completed template deployment for OMS sample alerts."));            
        }
    }

    [void] UninstallOMSPack()
    {
        $this.PublishCustomMessage("Started Clean up of OMS Security Solution pack");
    
        #Get all the searches with the name of AzSDK
        $savedSearches = Get-AzureRmOperationalInsightsSavedSearch -ResourceGroupName $this.OMSResourceGroup -WorkspaceName $this.OMSWorkspaceName
        $azSdkSearches = @()
        if($savedSearches -ne $null -and $savedSearches.Value -ne $null)
        {
            $savedSearches.Value | %{
                Set-Variable -Name savedSearch -Value $_
                if($savedSearch.Properties -ne $null -and $savedSearch.Properties.Category -like "*AzSDK*")
                {
                    $azSdkSearches += $savedSearch
                }
            }
        }        
        $RequestURI= [Constants]::OMSRequestURI
        $accessToken =  [Helpers]::GetAccessToken([Constants]::ARMManagementUri);
        $header = "Bearer " + $accessToken
        $headers = @{"Authorization"=$header;"Content-Type"="application/json"}
        $schedules = $null
        $azSdkSearches | %{
            Set-Variable -Name azSdkSearch -Value $_
            $azSdkSearchId = $azSdkSearch.Id.Replace($azSdkSearch.Properties.DisplayName.ToLower(),$azSdkSearch.Properties.DisplayName)
            Set-Variable -Name scheduleURI 
            $scheduleURI = ($RequestURI -f ($azSdkSearchId +"/schedules"))
            #Get all the Schedules configured using this search
            $result = ""
            $err = $null;
            try { 
                $result = Invoke-WebRequest -Method GET -Uri $scheduleURI -Headers $headers 
            } 
            catch{ 
                $err = $_ 
            }
            if($result.StatusCode -ge 200 -and $result.StatusCode -le 399){
                if($result.Content -ne $null){
                    $json = (ConvertFrom-Json $result.Content)
                    if($json -ne $null){
                        $schedules = $json
                        if($json.value -ne $null)
                        {
                            $schedules = $json.value
                        }        
                    }
                }
            }
            if($err -ne $null)
            {
                if($err.ErrorDetails.Message -ne $null){
                    $json = (ConvertFrom-Json $err.ErrorDetails.Message)
                    if($json -ne $null){
                        $return = $json
                        if($json.'odata.error'.code -eq "Request_ResourceNotFound")
                        {
                            $return = $json.'odata.error'.message
                        }
                    }
                }
                return $null           
            }
            $actions = $null;
            #Get AlertActions
            if($schedules -ne $null)
            {
                $schedules | %{
                    Set-Variable -Name schedule -Value $_
                    Set-Variable -Name actionsURI 
                    if(Get-Member -InputObject $schedule -Name id)
                    {
                        $actionsURI = ($RequestURI -f ($schedule.Id +"/Actions"))
                        #Get all the actions configured using this search
                        $result = ""
                        $err = $null;
                        try { 
                            $result = Invoke-WebRequest -Method GET -Uri $actionsURI -Headers $headers 
                        } 
                        catch{ 
                            $err = $_ 
                        }
                        if($result.StatusCode -ge 200 -and $result.StatusCode -le 399){
                            if($result.Content -ne $null){
                                $json = (ConvertFrom-Json $result.Content)
                                if($json -ne $null){
                                    $actions = $json
                                    if($json.value -ne $null)
                                    {
                                        $actions = $json.value
                                    }        
                                }
                            }
                        }
                        if($err -ne $null)
                        {
                            if($err.ErrorDetails.Message -ne $null){
                                $json = (ConvertFrom-Json $err.ErrorDetails.Message)
                                if($json -ne $null){
                                    $return = $json
                                    if($json.'odata.error'.code -eq "Request_ResourceNotFound")
                                    {
                                        $return = $json.'odata.error'.message
                                    }
                                }
                            }
                            return $null           
                        }

                        if($actions -ne $null)
                        {
                            $actions | %{
                                Set-Variable -Name action -Value $_
                                if(Get-Member -InputObject $actions -Name name)
                                {
                                    $subValues = $action.name.Split("|",[StringSplitOptions]::RemoveEmptyEntries)
                                    if($subValues.Length -gt 0)
                                    {
                                        $actionid = $subValues[$subValues.Length-1]
                                        Set-Variable -Name actionURI 
                                        $actionURI = ($RequestURI -f ($schedule.Id+"/Actions/"+$actionid))
                                        #Get all the actions configured using this search
                                        $result = ""
                                        $err = $null;
                                        try { 
                                            $result = Invoke-WebRequest -Method DELETE -Uri $actionURI -Headers $headers 
                                            $this.PublishCustomMessage($action.Id + " : Deleted") 
                                        } 
                                        catch{ 
                                            $err = $_ 
                                        }
                                        if($result.StatusCode -ge 200 -and $result.StatusCode -le 399){
                                            if($result.Content -ne $null){
                                                $json = (ConvertFrom-Json $result.Content)
                                                if($json -ne $null){
                                                    $actions = $json
                                                    if($json.value -ne $null)
                                                    {
                                                        $actions = $json.value
                                                    }        
                                                }
                                            }
                                        }
                                        if($err -ne $null)
                                        {
                                            if($err.ErrorDetails.Message -ne $null){
                                                $json = (ConvertFrom-Json $err.ErrorDetails.Message)
                                                if($json -ne $null){
                                                    $return = $json
                                                    if($json.'odata.error'.code -eq "Request_ResourceNotFound")
                                                    {
                                                        $return = $json.'odata.error'.message
                                                    }
                                                }
                                            }
                                            return $null           
                                        }
                                    }
                                }
                                #TODO Message
                            }
                        }

                        #clean up schedule
                        Set-Variable -Name scheduleURI 
                        $scheduleURI = ($RequestURI -f $schedule.Id)
                        #Get all the actions configured using this search
                        $result = ""
                        try { 
                            $result = Invoke-WebRequest -Method DELETE -Uri $scheduleURI -Headers $headers 
                                $this.PublishCustomMessage($schedule.Id + " : Deleted") 
                        } 
                        catch{ 
                            $err = $_ 
                        }
                        if($result.StatusCode -ge 200 -and $result.StatusCode -le 399){
                            if($result.Content -ne $null){
                                $json = (ConvertFrom-Json $result.Content)
                                if($json -ne $null){
                                    $actions = $json
                                    if($json.value -ne $null)
                                    {
                                        $actions = $json.value
                                    }        
                                }
                            }
                        }
                        if($err -ne $null)
                        {
                            if($err.ErrorDetails.Message -ne $null){
                                $json = (ConvertFrom-Json $err.ErrorDetails.Message)
                                if($json -ne $null){
                                    $return = $json
                                    if($json.'odata.error'.code -eq "Request_ResourceNotFound")
                                    {
                                        $return = $json.'odata.error'.message
                                    }
                                }
                            }
                            return $null           
                        }
                    }
                }
            }
            #Done cleaning up all the schedules and actions
             
            }
            #Finally remove the saved search
            $azSdkSearches | ForEach-Object {
                $tempAzSDKSearch = $_;
                Remove-AzureRmOperationalInsightsSavedSearch -ResourceGroupName $this.OMSResourceGroup -WorkspaceName $this.OMSWorkspaceName -SavedSearchId $tempAzSDKSearch.Properties.DisplayName
                $this.PublishCustomMessage($tempAzSDKSearch.Id + " : Deleted");     
        }
        $this.PublishCustomMessage("Completed clean up of OMS Security Solution pack.");
    }

    [Hashtable] GetOMSBaseParameters()
    {
        [Hashtable] $omsParams = @{};
        $omsParams.Add("omsWorkspaceLocation",$this.OMSLocation);
        $omsParams.Add("omsResourcegroup",$this.OMSResourceGroup);
        $omsParams.Add("omsSubscriptionId",$this.SubscriptionContext.SubscriptionId);
        $omsParams.Add("omsWorkspaceName",$this.OMSWorkspaceName);
        return $omsParams;
    }

    [Hashtable] GetOMSAlertParameters([string] $_applicationName, [string] $_alertemails)
    {
        $uniqueID =  (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss");
        $emailList = $_alertemails.Split(",",[StringSplitOptions]::RemoveEmptyEntries)
        [Hashtable] $omsParams = $this.GetOMSBaseParameters();
        $omsParams.Add("alertEmailsPointOfContact",$emailList);
        $omsParams.Add("appName",$_applicationName);
        $omsParams.Add("uniqueId",$uniqueID);
        $omsParams.Add("applicationSubscriptionName",$this.ApplicationSubscriptionName);
        return $omsParams;
    }

    [Hashtable] GetOMSSampleViewParameters([string] $_applicationName,[string] $_query)
    {
        [Hashtable] $omsParams = $this.GetOMSBaseParameters();
        $omsParams.Add("appName",$_applicationName);
        $omsParams.Add("query",$_query);
        $omsParams.Add("applicationSubscriptionName",$this.ApplicationSubscriptionName);
        return $omsParams;
    }

    [Hashtable] GetOMSGenericViewParameters([string] $_applicationName)
    {
        [Hashtable] $omsParams = $this.GetOMSBaseParameters();
        $omsParams.Add("viewName",$_applicationName);
        return $omsParams;
    }

    [string] GetOMSAppQuery([string] $_resourceGroups)
    {
        $rgQueryFormat = "ResourceGroup=""{0 }"""

        $queryOutput = ""
        $resourceGroupList = $_resourceGroups.Split(',',[StringSplitOptions]::RemoveEmptyEntries);
        if($resourceGroupList.Length -gt 0)
        {
            $resourceGroupList | ForEach-Object {
                if([string]::IsNullOrWhiteSpace($queryOutput))
                {
                    $queryOutput = $rgQueryFormat -f $_
                }
                else
                {
                    $queryOutput += " OR " + [string]::Format($rgQueryFormat, $_);
                }
            }
        }

        return $queryOutput;
    }
    
}