CQDPowerShell.psm1

# (c)2018 Microsoft Corporation. All rights reserved. This document is provided "as-is." Information and views expressed in this document,
# including URL and other Internet Web site references, may change without notice. You bear the risk of using it.
# This document does not provide you with any legal rights to any intellectual property in any Microsoft product.
# You may copy and use this document for your internal, reference purposes. You may modify this document for your internal purposes
#
################################################
# CQD POWERSHELL ##############################
# JUSTW/SETHHA #
# QUERY CQD DIRECT FROM POWERSHELL AND EXPORT #
# #
################################################

[CmdletBinding()]
param(
  # Non Dynamic params go here #
)
###############################
# BEGIN JWT DECODE FUNCTIONS #
###############################
function Convert-64String ([string]$datai)
{
  $datai = $datai.Replace('-','+').Replace('_','/')
  switch ($datai.Length % 4)
  {
    0 { break }
    2 { $datai += '==' }
    3 { $datai += '=' }
    default { throw New-Object ArgumentException ('datai') }
  }
  return [System.Convert]::FromBase64String($datai)
}

function Decode-JWT ([string]$rawToken)
{
  $Tokenparts = $rawToken.Split('.');
  $headers = [System.Text.Encoding]::UTF8.GetString((Convert-64String $Tokenparts[0]))
  $claims = [System.Text.Encoding]::UTF8.GetString((Convert-64String $Tokenparts[1]))
  $signature = (Convert-64String $Tokenparts[2])

  $customObject = [pscustomobject]@{
    headers = ($headers | ConvertFrom-Json)
    claims = ($claims | ConvertFrom-Json)
    signature = $signature
  }

  #Write-Verbose -Message ("JWT`r`n.headers: {0}`r`n.claims: {1}`r`n.signature: {2}`r`n" -f $headers,$claims,[System.BitConverter]::ToString($signature))
  return $customObject
}

function Get-JWTData
{
  [CmdletBinding()]
  param
  (
    # Param1 help description
    [Parameter(Mandatory = $true)]
    [string]$Token,
    [switch]$Recurse
  )

  if ($Recurse)
  {
    $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Token))
    $DecodedJwt = Decode-JWT -rawToken $decoded
  }
  else
  {
    $DecodedJwt = Decode-JWT -rawToken $Token
  }
  #Write-Host("Token Values") -ForegroundColor Green
  #Write-Host ($DecodedJwt | Select headers,claims | ConvertTo-Json)
  return $DecodedJwt
}
#############################
# END JWT DECODE FUNCTIONS #
#############################



###############################
# BEGIN AAD AUTH FUNCTIONS #
###############################


####Function to Get the JWT Token VIA OAuth
function Get-CQDToken ([string]$client_id)
{

  if ($CQDVer -eq "V3")
  {
  $CQDUri = "https://cqd.teams.microsoft.com/spd/"
  $V3Token = $true
  }
  else {
    $CQDUri = "https://cqd.lync.com/spd/"
    $V2Token = $true
  }


  
  Add-Type -AssemblyName System.Web
  $resourceUrl = $WebResource
  $redirectUrl = $CQDUri
  $nonce = [guid]::NewGuid().GUID
  $url = "https://login.microsoftonline.com/common/oauth2/authorize?response_type=token&redirect_uri=" +
  [System.Web.HttpUtility]::UrlEncode($redirectUrl) +
  "&client_id=$client_id" +
  "&prompt=login" + "&nonce=$nonce" + "&resource=" + [System.Web.HttpUtility]::UrlEncode($WebResource)


  Add-Type -AssemblyName System.Windows.Forms

  $form = New-Object -TypeName System.Windows.Forms.Form -Property @{ Width = 440; Height = 640 }
  $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{ Width = 420; Height = 600; Url = ($url) }
  $DocComp = {
    $Global:uri = $web.Url.AbsoluteUri
    #Write-Host $Global:uri
    if ($Global:Uri -match "error=[^&]*|access_token=[^&]*") { $form.Close() }
  }

  $web.ScriptErrorsSuppressed = $true
  $web.Add_DocumentCompleted($DocComp)
  $form.Controls.Add($web)
  $form.Add_Shown({ $form.Activate() })
  $form.ShowDialog() | Out-Null



  $Script:TokenLifeTime = [Web.HttpUtility]::ParseQueryString(($web.Url -replace '^.*?(expires_in.+)$','$1'))['expires_in']
  #Write-Host $TokenLifeTime
  $Script:Token = [Web.HttpUtility]::ParseQueryString(($web.Url -replace '^.*?(access_token.+)$','$1'))['access_token']
  # Write-Host $Token
  
  
  return ('Bearer {0}' -f $Script:Token)
 
}

#Function to connect to CQD Via AzureAD
function Connect-CqdOnline {
if ($CQDVer -eq "V3")
{
  $UriVar = "https://cqd.teams.microsoft.com/repository/clientconfiguration"
}
else {
  $UriVar = "https://repository.cqd.lync.com/clientconfiguration"
}

  $ClientConfigUri =   $UriVar
  $Script:Configuration = Invoke-RestMethod -Uri $ClientConfigUri -Method Get -SessionVariable WebSession -UserAgent "CQDPowerShell V2.0"
  $Script:WebResource = $Configuration.AuthLoginResource
  $Script:AADBearerToken = Get-CQDToken $Configuration.AuthWebAppClientId
  $Script:WebSession = $WebSession
  $Script:RepositoryApiBaseUrl = $Script:Configuration.RepositoryApiBaseUrl
  $WebSession.headers.Add('Authorization',$AADBearerToken)
  $ProvisioningStatus = Invoke-RestMethod -Uri ('{0}tenant/provision' -f $RepositoryApiBaseUrl) -Method Get -WebSession $Script:WebSession -UserAgent "CQDPowerShell V2.0"
  if ($ProvisioningStatus.Status -ne 'Provisioned') {
    throw ('CQD Not Provisioned for TenantId {0}. Stopping...' -f $ProvisioningStatus.TenantId)
  }
  $Script:DataServiceBaseUrl = Invoke-RestMethod -Uri ('{0}tenant/dataservice' -f $Script:RepositoryApiBaseUrl) -WebSession $Script:WebSession -Method Get -UserAgent "CQDPowerShell V2.0"
  
}
Export-ModuleMember -Function Connect-CqdOnline

#Function to validate if token exists or is valid and unexpired
function CheckToken

{
  #Check to see if Token already exists
  if ($Script:AADBearerToken -eq $null)
  {
    Connect-CqdOnline
  }
  else

  {
    #Check Token
    $JWTDecoded = Get-JWTData $Script:Token
    $JWTI = $JWTDecoded.claims.iat
    $JWTE = $JWTDecoded.claims.exp
    $NowTimeUTC = Get-Date

    $origin = New-Object -Type DateTime -ArgumentList 1970,1,1,0,0,0,0
    $JWTIssuedTime = $origin.AddSeconds($JWTI)
    $JWTExpireTime = $origin.AddSeconds($JWTE)
    #Write-Host $JWTIssuedTime $JWTExpireTime

    if ($CQDVer -eq "V2")
    {

      if ($JWTDecoded.claims.aud -eq "https://cqd.lync.com")
      {  if ($NowTimeUTC.ToUniversalTime() -gt $JWTExpireTime)
        {
          Write-Host "Token Expired"
          Connect-CqdOnline
        }
      }
      else 
      {
         write-host "Wrong Token, Expecting V2"
         Connect-CqdOnline 
      }
    } elseif ($CQDVer -eq "V3")
    {
      if ($JWTDecoded.claims.aud -eq "https://cqd.teams.microsoft.com")
      {  if ($NowTimeUTC.ToUniversalTime() -gt $JWTExpireTime)
        {
          Write-Host "Token Expired"
          Connect-CqdOnline
        }
      }
      else {
        write-host "Wrong Token, Expecting V3"
        Connect-CqdOnline 
      }

    }

  


  }

}

###############################
# END AAD AUTH FUNCTIONS #
###############################

#Function to get Array list of Dimensions available in CQD Cube
function Get-CQDDimensions
{
  $DimensionsCheck = GetParams "Dimensions"
  return $DimensionsCheck

}
Export-ModuleMember -Function Get-CQDDimensions

function Get-CQDMeasures
{
  $MeasuresCheck = GetParams "Measures"
  return $MeasuresCheck
}
Export-ModuleMember -Function Get-CQDMeasures

##Generate Dynamic List of CQD Measures and Dimensions
function GetParams ([string]$ParamType) {


  CheckToken

  $CubeRequest = Invoke-WebRequest -Uri ('{0}CubeStructure' -f $DataServiceBaseUrl) -WebSession $WebSession -Method Get -UserAgent "CQDPowerShell V2.0"
  $List = ConvertFrom-Json $CubeRequest

  $D = $List.Dimensions
  $Meas = $List.Measurements
  $Dimensions = @()
  $Measures = @()
  foreach ($i in $D)
  {
    $Dimensions += $i.Category
    $Dimensions += $i.Attributes


  }

  foreach ($a in $Meas)
  {
    $Measures += $a.Category
    $Measures += $a.Attributes

  }

  if ($ParamType -eq "Dimensions")
  {
    $DimensionName = $Dimensions.DataModelName

    $Di = @()
    foreach ($O in $DimensionName)
    {

      $O = $O.Replace("[","")
      $O = $O.Replace("]","")
      $Di += $O
    }
    return $Di
  }
  elseif ($ParamType -eq "Measures")
  {
    $MeasureName = $Measures.DataModelName
    $Mi = @()
    foreach ($M in $MeasureName)
    {
      $M = $M.Replace("[","")
      $M = $M.Replace("]","")
      $Mi += $M
    }
    return $Mi
  }


}


#Function for handling start-end date
function ProcessDateRange
{

  $TS = New-TimeSpan $StartDate $EndDate
  if ($TS.Days -lt 0)
  {
    $DateErrorStartEnd = 'Error in Timerange. Confirm StartDate is BEFORE EndDate'
    Write-Host $DateErrorStartEnd
    throw $DateErrorStartEnd
  }
  $i = $TS.Days
if 
($StartDate -eq $EndDate)
{$DateList = @($EndDate)}
else
{    
$DateList = @($EndDate)
    do 
    {

    $DateList += $EndDate.AddDays(-$i)
    $i --

    }

    while ($i -ne 0)
}



    foreach ($D in $DateList)

    {

        $DateF += "[" +  $D.ToString('yyyy-MM-dd')  + "],"

    }

return $DateF
}
#Function to obtain days of week
function GetDaysofWeek
{

  foreach ($DofWT in $Weekdays)
  {

    $DofW += "[" + $DofWT + "],"

  }
  return $DofW
}



function BuildWeekFilter {
  param(
    [Parameter()] [array]$TimeInput
  )

  # Create list containing "start of weeks'" (Sun) for last 6 month
  $endDay = Get-Date
  $startDay = (Get-Date).AddMonths(-6)
  $cqdLastSixMonthWeeks = @()
  for ($i = $startDay; $i -lt $endDay; $i = $i.AddDays(1)) {
    if (($i.DayOfWeek).ToString() -eq "Sunday") {
      $cqdLastSixMonthWeeks += $i.ToString("yyyy-MM-dd")
    }
  }

  # Initialize variable that will keep final list of "start of weeks'"
  $cqdWeeks = @()

  # There are two cases:
  # 1. Month is provided as an input e.g. "09" for Sep. Must be in this format. length of 2
  # 2. Start of week is provided as an input e.g. "2017-09-18". Must be in this format. length of 10
  $TimeInput | ForEach-Object {
    if ($_.Length -eq 2) {
      $tempMonth = $_
      $cqdLastSixMonthWeeks | ForEach-Object {
        if ($_ -like "*-$($tempMonth)-*") { $cqdWeeks += $_ }
      }
    } elseif ($_.Length -eq 10) {
      $cqdWeeks += $_
    }
  }

  # Create hash table representing CQD Week filter
  $cqdFilterWeek = @{}

  $Caption = ""
  $Value = ""
  $cqdWeeks | ForEach-Object { $Caption += "$($_) | " }
  $cqdWeeks | ForEach-Object { $Value += "[$($_)]," }

  $cqdFilterWeek.Add("DataModelName","[AllStreams].[Week]")
  $cqdFilterWeek.Add("Caption",$Caption)
  $cqdFilterWeek.Add("Value",$Value)
  $cqdFilterWeek.Add("Operand",0)
  $cqdFilterWeek.Add("UnionGroup","")

  # Return Json object representing CQD Week filter
  return ($cqdFilterWeek | ConvertTo-Json)

}

#Function for creating UA Category Filter
function BuildUACategoryFilter {
  param(
    [Parameter()] [array]$UACategory
  )

  # Create hash table representing CQD UA Category filter
  $cqdFilterUACategory = @{}

  $Caption = ""
  $Value = ""
  $UACategory | ForEach-Object { $Caption += "$($_) | " }
  $Caption | ForEach-Object {
    $Length = $Caption.Trim().Length
    $Caption = $Caption.Trim().Substring(0,$Length - 1)
    $Caption = $Caption.Trim()
  }

  $UACategory | ForEach-Object { $Value += "[$($_)]," }
  $Value | ForEach-Object {
    $Length = $Value.Trim().Length
    $Value = $Value.Trim().Substring(0,$Length - 1)
    $Value = $Value.Trim()
  }

  $cqdFilterUACategory.Add("DataModelName","[AllStreams].[Second User Agent Category]")
  $cqdFilterUACategory.Add("Caption",$Caption)
  $cqdFilterUACategory.Add("Value",$Value)
  $cqdFilterUACategory.Add("Operand",0)
  $cqdFilterUACategory.Add("UnionGroup","")

  # Return Json object representing CQD UA Category filter
  return ($cqdFilterUACategory | ConvertTo-Json)
}

#Function for creating Media Type Filter
function BuildMediaTypeFilter {
  param(
    [Parameter()] [array]$MediaType
  )

  # Create hash table representing CQD Media Type filter
  $cqdFilterMediaType = @{}

  $Caption = ""
  $Value = ""
  $MediaType | ForEach-Object { $Caption += "$($_) | " }
  $Caption | ForEach-Object {
    $Length = $Caption.Trim().Length
    $Caption = $Caption.Trim().Substring(0,$Length - 1)
    $Caption = $Caption.Trim()
  }

  $MediaType | ForEach-Object { $Value += "[$($_)]," }
  $Value | ForEach-Object {
    $Length = $Value.Trim().Length
    $Value = $Value.Trim().Substring(0,$Length - 1)
    $Value = $Value.Trim()
  }

  $cqdFilterMediaType.Add("DataModelName","[AllStreams].[Media Type]")
  $cqdFilterMediaType.Add("Caption",$Caption)
  $cqdFilterMediaType.Add("Value",$Value)
  $cqdFilterMediaType.Add("Operand",0)
  $cqdFilterMediaType.Add("UnionGroup","")

  # Return Json object representing CQD Media Type filter
  return ($cqdFilterMediaType | ConvertTo-Json)
}

#Function for Server Pair Filter
function BuildIsServerPairFilter {
  param(
    [Parameter()] [array]$IsServerPair
  )

  # Create hash table representing CQD Is Server Pair filter
  $cqdFilterIsServerPair = @{}

  $Caption = ""
  $Value = ""
  $IsServerPair | ForEach-Object { $Caption += "$($_) | " }
  $Caption | ForEach-Object {
    $Length = $Caption.Trim().Length
    $Caption = $Caption.Trim().Substring(0,$Length - 1)
    $Caption = $Caption.Trim()
  }

  $IsServerPair | ForEach-Object { $Value += "[$($_)]," }
  $Value | ForEach-Object {
    $Length = $Value.Trim().Length
    $Value = $Value.Trim().Substring(0,$Length - 1)
    $Value = $Value.Trim()
  }

  $cqdFilterIsServerPair.Add("DataModelName","[AllStreams].[Is Server Pair]")
  $cqdFilterIsServerPair.Add("Caption",$Caption)
  $cqdFilterIsServerPair.Add("Value",$Value)
  $cqdFilterIsServerPair.Add("Operand",0)
  $cqdFilterIsServerPair.Add("UnionGroup","")

  # Return Json object representing CQD Is Server Pair filter
  return ($cqdFilterIsServerPair | ConvertTo-Json)
}

#Function for Transport Filter (TCP,UDP)
function BuildTransportFilter {
  param(
    [Parameter()] [array]$Transport
  )

  # Create hash table representing CQD transport filter
  $cqdFilterTransport = @{}

  $Caption = ""
  $Value = ""
  $Transport | ForEach-Object { $Caption += "$($_) | " }
  $Caption | ForEach-Object {
    $Length = $Caption.Trim().Length
    $Caption = $Caption.Trim().Substring(0,$Length - 1)
    $Caption = $Caption.Trim()
  }

  $Transport | ForEach-Object { $Value += "[$($_)]," }
  $Value | ForEach-Object {
    $Length = $Value.Trim().Length
    $Value = $Value.Trim().Substring(0,$Length - 1)
    $Value = $Value.Trim()
  }

  $cqdFilterTransport.Add("DataModelName","[AllStreams].[Transport]")
  $cqdFilterTransport.Add("Caption",$Caption)
  $cqdFilterTransport.Add("Value",$Value)
  $cqdFilterTransport.Add("Operand",0)
  $cqdFilterTransport.Add("UnionGroup","")

  # Return Json object representing CQD transport filter
  return ($cqdFilterTransport | ConvertTo-Json)
}

#Function for building all Filters
function FilterCreation
{

  #Build MonthYear Array From Input

  foreach ($MonthYearI in $MonthYear)
  {
    $MonthYearParams += "[" + $MonthYearI + "],"

  }

  $Filters = New-Object system.Data.DataTable "Filters"
  $FilterName = New-Object system.Data.DataColumn FilterName,([string])
  $FilterValue = New-Object system.Data.DataColumn FilterValue,([string])
  $FilterCaption = New-Object system.Data.DataColumn FilterCaption,([string])
  $FilterOperand = New-Object system.Data.DataColumn FilterOperand,([string])
  $FilterUnionGroup = New-Object system.Data.DataColumn FilterUnionGroup,([string])

  #Add the Columns
  $Filters.columns.Add($FilterName)
  $Filters.columns.Add($FilterValue)
  $Filters.columns.Add($FilterCaption)
  $Filters.columns.Add($FilterOperand)
  $Filters.columns.Add($FilterUnionGroup)


  if ($Weekdays -ne $null)
  {
  $row = $Filters.NewRow()
  $row.FilterName = "AllStreams.Day Of Week" 
  $row.FilterValue = "$DaysofWeekList" 
  $row.FilterCaption = "NA" 
  $row.FilterOperand = "0" 
  $row.FilterUnionGroup = "" 
  $Filters.Rows.Add($row)
  }


  if ($MonthYear -ne $null)
  {
  #Create a row
  $row = $Filters.NewRow()
  $row.FilterName = "AllStreams.Month Year" 
  $row.FilterValue = "$MonthYearParams" 
  $row.FilterCaption = "NA" 
  $row.FilterOperand = "0" 
  $row.FilterUnionGroup = "" 
  $Filters.Rows.Add($row)
  }



  if ($StartDate -ne $null -and $EndDate -ne $null)

{

$EndDateV = ProcessDateRange
#Create a row
$row = $Filters.NewRow()
$row.FilterName = "AllStreams.Date"
$row.FilterValue = "$EndDateV" 
$row.FilterCaption = "NA" 
$row.FilterOperand = "0" 
$row.FilterUnionGroup = "" 
$Filters.Rows.Add($row)  

}

if ($Week -ne $null) {
    $WeekV = (BuildWeekFilter $Week) | ConvertFrom-Json
    $row = $Filters.NewRow()
    $row.FilterName = "AllStreams.Week"
    $row.FilterValue = $WeekV.Value
    $row.FilterCaption = "NA" 
    $row.FilterOperand = $WeekV.Operand 
    $row.FilterUnionGroup = $WeekV.UnionGroup 
    $Filters.Rows.Add($row)  
}

if ($UACategory -ne $null) {
    $UACategoryV = (BuildUACategoryFilter $UACategory) | ConvertFrom-Json
    $row = $Filters.NewRow()
    $row.FilterName = "AllStreams.Second User Agent Category"
    $row.FilterValue = $UACategoryV.Value
    $row.FilterCaption = "NA" 
    $row.FilterOperand = $UACategoryV.Operand 
    $row.FilterUnionGroup = $UACategoryV.UnionGroup 
    $Filters.Rows.Add($row)  
}

if ($MediaType -ne $null) {
    $MediaTypeV = (BuildMediaTypeFilter $MediaType) | ConvertFrom-Json
    $row = $Filters.NewRow()
    $row.FilterName = "AllStreams.Media Type"
    $row.FilterValue = $MediaTypeV.Value
    $row.FilterCaption = "NA" 
    $row.FilterOperand = $MediaTypeV.Operand 
    $row.FilterUnionGroup = $MediaTypeV.UnionGroup 
    $Filters.Rows.Add($row)  
}

if ($IsServerPair -ne $null) {
    $IsServerPairV = (BuildIsServerPairFilter $IsServerPair) | ConvertFrom-Json
    $row = $Filters.NewRow()
    $row.FilterName = "AllStreams.Is Server Pair"
    $row.FilterValue = $IsServerPairV.Value
    $row.FilterCaption = "NA" 
    $row.FilterOperand = $IsServerPairV.Operand 
    $row.FilterUnionGroup = $IsServerPairV.UnionGroup 
    $Filters.Rows.Add($row)  
}

if ($Transport -ne $null) {
    $TransportV = (BuildTransportFilter $Transport) | ConvertFrom-Json
    $row = $Filters.NewRow()
    $row.FilterName = "AllStreams.Transport"
    $row.FilterValue = $TransportV.Value
    $row.FilterCaption = "NA" 
    $row.FilterOperand = $TransportV.Operand 
    $row.FilterUnionGroup = $TransportV.UnionGroup 
    $Filters.Rows.Add($row)  
}

if ($null -ne $CustomFilter)
{
foreach ($F in $CustomFilter)
    {
        $row = $Filters.NewRow()
        $row.FilterName = $F.FName
        $row.FilterValue = $F.FValue
        $row.FilterCaption = ""
        $row.FilterOperand = $F.Op
        $row.FilterUnionGroup = ""
        $Filters.Rows.Add($row)

    }
}

  return $Filters

}


function Get-CQDData {
  [CmdletBinding()]
  param(




    [Parameter(Mandatory = $false,Position = 3)]
    [array]$Transport = $null,

    [Parameter(Mandatory = $false,Position = 4)]
    [string]$OutPutFilePath = $null,

    [Parameter(Mandatory = $false,Position = 5)]
    [nullable[datetime]]$StartDate = $null,

    [Parameter(Mandatory = $false,Position = 6)]
    [nullable[datetime]]$EndDate = $null,

    [Parameter(Mandatory = $false,Position = 7)]
    [array]$MonthYear = $null,

    [Parameter(Mandatory = $false,Position = 8)]
    [array]$Weekdays = $null,

    [Parameter(Mandatory = $True,Position = 9)]
    [ValidateSet("CSV","DataTable")]
    [string]$OutPutType,

    [Parameter(Mandatory = $false,Position = 10)]
    [array]$Week = $null,

    [Parameter(Mandatory = $false,Position = 11)]
    [array]$UACategory = $null,

    [Parameter(Mandatory = $false,Position = 12)]
    [array]$MediaType = $null,

    [Parameter(Mandatory = $false,Position = 13)]
    [ValidateSet("Client : Client","Client : Server","Server : Server")]
    [array]$IsServerPair = $null,

    [Parameter(Mandatory = $false,Position = 14)]
    [bool]$ShowQuery = $false,
    
    [Parameter(Mandatory=$false,Position=15)]
  [switch] $OverWriteOutput,
  
  [Parameter(Mandatory=$false,Position=16)]
  [pscustomobject] $CustomFilter,
  
  [Parameter(Mandatory=$false,Position=17)]
  [switch] $LargeQuery,

  [Parameter(Mandatory=$false,Position=18)]
  [ValidateSet("V2","V3")]
  [string]$CQDVer = "V2"

  

  )


  dynamicparam {


    # Set the dynamic parameters' name for Dimensions and Measures
    $DimensionParameter = 'Dimensions'
    $MeasuresParameter = 'Measures'


    # Create the dictionary
    $RuntimeParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

    # Create the collection of attributes
    $DimensionAttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
    $AttributeCollectionMeasures = New-Object System.Collections.ObjectModel.Collection[System.Attribute]

    # Create and set the parameters' attributes
    $DimensionParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
    $DimensionParameterAttribute.Mandatory = $true
    $DimensionParameterAttribute.Position = 1

    $MeasuresParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
    $MeasuresParameterAttribute.Mandatory = $true
    $MeasuresParameterAttribute.Position = 2

    # Add the attributes to the attributes collection
    $DimensionAttributeCollection.Add($DimensionParameterAttribute)
    $AttributeCollectionMeasures.Add($MeasuresParameterAttribute)

    # Generate and set the ValidateSet
    $DimensionarrSet = GetParams "Dimensions"
    $MeasuresarrSet = GetParams "Measures"
    $DimensionsValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute ($DimensionarrSet)
    $MeasuresValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute ($MeasuresarrSet)

    # Add the ValidateSet to the attributes collection
    $DimensionAttributeCollection.Add($DimensionsValidateSetAttribute)
    $AttributeCollectionMeasures.Add($MeasuresValidateSetAttribute)

    # Create and return the dynamic parameter
    $DimensionsRuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ($DimensionParameter,[array],$DimensionAttributeCollection)
    $MeasuresRuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ($MeasuresParameter,[array],$AttributeCollectionMeasures)

    $RuntimeParamDictionary.Add($DimensionParameter,$DimensionsRuntimeParameter)
    $RuntimeParamDictionary.Add($MeasuresParameter,$MeasuresRuntimeParameter)
    return $RuntimeParamDictionary

  }

  begin {
    # Bind the parameter to a friendly variable
    $Dims = $PsBoundParameters[$DimensionParameter]
    $Measures = $PsBoundParameters[$MeasuresParameter]



  }

  process {

    try {
      Write-Host "Working...."

      #Check for Null CSV Path if CSV Chosen output
      if ($OutPutType -eq "CSV" -and $OutPutFilePath -eq "")
      {
        while ($OutPutFilePath -eq "" -or $OutPutFilePath -eq $null)
        {
          Write-Host "You must specify an output path when using CSV output type"
          Write-Host "Enter CSV Output Path:" -NoNewline
          $OutputFilePath = Read-Host
        }
        if ($OutputFilePath -eq $null) { Exit-PSSession }
      }

      $DimFixed = @()
      $DimSimple = @()
      $MeasureFixed = @()
      $MeasureSimple = @()
      ###Fix Dimension Brackets
      foreach ($DimShort in $Dims)
      {
        $Dim1 = $DimShort.Split(".")
        $DimSimple += $Dim1[1].ToString()
        $Dim1[0] = "[" + $Dim1[0] + "]"
        $Dim1[1] = "[" + $Dim1[1] + "]"
        $DimFixed += $Dim1[0] + "." + $Dim1[1]
      }

      foreach ($MeasureShort in $Measures)
      {
        $Measure1 = $MeasureShort.Split(".")
        $MeasureSimple += $Measure1[1].ToString()
        $Measure1[0] = "[" + $Measure1[0] + "]"
        $Measure1[1] = "[" + $Measure1[1] + "]"
        $MeasureFixed += $Measure1[0] + "." + $Measure1[1]
      }

      $Dims = $DimFixed
      $Measures = $MeasureFixed




      if ($Weekdays -ne $null)
      {
        $DaysofWeekList = GetDaysofWeek
      }
      else
      {
        $Weekdays = $null
      }







      $Filters = FilterCreation


      ##Dimension and Measure Arrays##
     $DimensionsArray = $Dims 
     $MeasurementsArray = $Measures 


      ##Create Results Table##
      $DataResults = New-Object system.Data.DataTable "DataResults"
      foreach ($DimensionVar in $DimSimple)
      {
        $Column = New-Object system.Data.DataColumn $DimensionVar,([string])
        $DataResults.columns.Add($Column)
      }

      foreach ($MeasureVar in $MeasureSimple)

      {
        $Column = New-Object system.Data.DataColumn $MeasureVar,([string])
        $DataResults.columns.Add($Column)
      }

      $FilterRowArray = @()
      $DimensionRowArray = @()
      $MeasureRowArray = @()
      $AllArray = $DimSimple + $MeasureSimple


      ##Build Query Structure##

      #Start/End Query
      $StartQuery = "{"
      $EndQuery = "}"

      #Filter Sections
$StartFilters = @'
"Filters":[
'@


#Dimension Sections
$StartDimensions = @'
"Dimensions":[
'@


#Measure Sections
$StartMeasures = @'
"Measurements":[
'@



###############Loop Arrays of Each Type

foreach ($Filter in $Filters)
{
$FLN = $Filter.FilterName
$FCN = $Filter.FilterCaption
$FVE = $Filter.FilterValue
$FOP = $Filter.FilterOperand
$UNGP = $Filter.FilterUnionGroup
$FLNSplit = $FLN.Split(".")
$FLNName = "[" + $FLNSplit[0] + "].[" + $FLNSplit[1] + "]" 
$FilterRowArray += @"
{"DataModelName":"$FLNName","Caption":"$FCN","Value":"$FVE","Operand":$FOP,"UnionGroup":"$UNGP"},
"@

}
$DimCount = $DimensionsArray.Count - 1
foreach ($DimensionLoop in $DimensionsArray)
{

$DimensionRowArray += @"
{"DataModelName":"$DimensionLoop"},
"@


}
$MeaCount = $MeasurementsArray.Count - 1
foreach ($MeasureLoop in $MeasurementsArray)
{

$MeasureRowArray +=  @"
{"DataModelName":"$MeasureLoop"},
"@


}
########Build Dynamic Query
if ($LargeQuery) 
{

  $Query = @"
  {
   
  $StartFilters
  $FilterRowArray
   
  ]
  ,
  $StartDimensions
  $DimensionRowArray
  ]
  ,
  $StartMeasures
  $MeasureRowArray
  ]
  ,
  LimitResultRowsCount:200000
  }
"@


}
else {
  
  $Query = @"
  {
   
  $StartFilters
  $FilterRowArray
   
  ]
  ,
  $StartDimensions
  $DimensionRowArray
  ]
  ,
  $StartMeasures
  $MeasureRowArray
  ]
  }
"@


}


      #Check to see if ShowQuery is true. Used to show JSON query for debugging queries.
      if ($ShowQuery -eq $true)
      {
        Write-Host "JSON Query:"
        Write-Host $Query
      }

      CheckToken
      $verb = "POST"
      $content = $Query
      $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
      $IRM = Invoke-RestMethod -Uri ('{0}RunQuery' -f $DataServiceBaseUrl) -WebSession $WebSession -Method Post -Body $Query -ContentType 'application/json' -UserAgent "CQDPowerShell V2.0" -Headers $headers


      $IRM2 = ConvertTo-Json -InputObject $IRM
      $JsonGarb = ConvertFrom-Json -InputObject $IRM2

      $QueryExecutionTime = $JsonGarb.ExecutionTime
      $ErrorType = $JsonGarb.ErrorType
      $ResultList = $JsonGarb.DataResult

      if ($ErrorType -ne 0)
      {
        Write-Host "Error Querying CQD with Error " $ErrorType
        if ($ErrorType -eq 6) { Write-Host "Invalid Query. Check the query and try again." }
        if ($ErrorType -eq 5) { Write-Host "Query scope too large. Reduce Dimensions, Measures and Timespan" }
        if ($ErrorType -eq 3) { Write-Host "Cube currently unavailable. Try again later." }
      }



      foreach ($ResultItem in $ResultList)
      {
        if (!$ResultItem)
        #if(1 -eq 2)
        {
          Write-Host "Warning: Result is Null:" $ResultItem

        }
        else
        {
          $ItemCount = $ResultItem.Count
          $FieldCount = $DimensionsArray.Count + $MeasurementsArray.Count
          #Create a row
          $Resultrow = $DataResults.NewRow()


          $i = 0

          do
          {

            $Resultrow.($AllArray[$i]) = $ResultItem.Item($i)
            $i++
          }
          while ($i -lt $AllArray.Count)

          $DataResults.Rows.Add($Resultrow)

        }

      }

      if ($OutPutType -eq "CSV")
      {
        if ($OverWriteOutput -eq $true)
            {
            $DataResults | Export-Csv -Path $OutPutFilePath -notypeinformation 
            }
            else
            {
            $DataResults | Export-Csv -Path $OutPutFilePath -notypeinformation -Append
            }
      }
      elseif ($OutPutType -eq "DataTable")
      {
        return $DataResults
      }

      Write-Host "Execution Complete. Query took " $QueryExecutionTime
    }
    catch [System.Net.WebException]
    {
      "Unable to complete query. Please reduce query scope or try again later."
    }
    catch [System.Management.Automation.ParameterBindingException]{ "Error Binding" }

    catch { "Error: $Error" }

  }

}
Export-ModuleMember -Function Get-CQDData


# SIG # Begin signature block
# MIIkYgYJKoZIhvcNAQcCoIIkUzCCJE8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCZIimqZHbeoP2v
# bl30It3RiXa/bQTTEGT7dKeqMd6D16CCDYEwggX/MIID56ADAgECAhMzAAABUZ6N
# j0Bxow5BAAAAAAFRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCVWsaGaUcdNB7xVcNmdfZiVBhYFGcn8KMqxgNIvOZWNH9JYQLuhHhmJ5RWISy1
# oey3zTuxqLbkHAdmbeU8NFMo49Pv71MgIS9IG/EtqwOH7upan+lIq6NOcw5fO6Os
# +12R0Q28MzGn+3y7F2mKDnopVu0sEufy453gxz16M8bAw4+QXuv7+fR9WzRJ2CpU
# 62wQKYiFQMfew6Vh5fuPoXloN3k6+Qlz7zgcT4YRmxzx7jMVpP/uvK6sZcBxQ3Wg
# B/WkyXHgxaY19IAzLq2QiPiX2YryiR5EsYBq35BP7U15DlZtpSs2wIYTkkDBxhPJ
# IDJgowZu5GyhHdqrst3OjkSRAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUV4Iarkq57esagu6FUBb270Zijc8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU0MTM1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAWg+A
# rS4Anq7KrogslIQnoMHSXUPr/RqOIhJX+32ObuY3MFvdlRElbSsSJxrRy/OCCZdS
# se+f2AqQ+F/2aYwBDmUQbeMB8n0pYLZnOPifqe78RBH2fVZsvXxyfizbHubWWoUf
# NW/FJlZlLXwJmF3BoL8E2p09K3hagwz/otcKtQ1+Q4+DaOYXWleqJrJUsnHs9UiL
# crVF0leL/Q1V5bshob2OTlZq0qzSdrMDLWdhyrUOxnZ+ojZ7UdTY4VnCuogbZ9Zs
# 9syJbg7ZUS9SVgYkowRsWv5jV4lbqTD+tG4FzhOwcRQwdb6A8zp2Nnd+s7VdCuYF
# sGgI41ucD8oxVfcAMjF9YX5N2s4mltkqnUe3/htVrnxKKDAwSYliaux2L7gKw+bD
# 1kEZ/5ozLRnJ3jjDkomTrPctokY/KaZ1qub0NUnmOKH+3xUK/plWJK8BOQYuU7gK
# YH7Yy9WSKNlP7pKj6i417+3Na/frInjnBkKRCJ/eYTvBH+s5guezpfQWtU4bNo/j
# 8Qw2vpTQ9w7flhH78Rmwd319+YTmhv7TcxDbWlyteaj4RK2wk3pY1oSz2JPE5PNu
# Nmd9Gmf6oePZgy7Ii9JLLq8SnULV7b+IP0UXRY9q+GdRjM2AEX6msZvvPCIoG0aY
# HQu9wZsKEK2jqvWi8/xdeeeSI9FN6K1w4oVQM4Mwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIWNzCCFjMCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAVGejY9AcaMOQQAAAAABUTAN
# BglghkgBZQMEAgEFAKCBuDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgmTWfMIEE
# VtVKTjjsxZwuXM3Q2W3cuMgIwrv3Novbh4QwTAYKKwYBBAGCNwIBDDE+MDygHIAa
# AEMAUQBEAFAAbwB3AGUAcgBTAGgAZQBsAGyhHIAaaHR0cHM6Ly93d3cubWljcm9z
# b2Z0LmNvbSAwDQYJKoZIhvcNAQEBBQAEggEACP6phEAHuaoyD3j2KMRLREc6sztd
# z54RTpTtv8zKvqvfcJKoj5DxTKfBnJCa+W1HY4GPMZQmHE89fQ26+HKm4KnGwlpb
# CoXgk3421Vobw05Ea2/hQuz5vDlv2xQBYpwVUWBwgeCUJ8YTvRk4ALgco+wu0KQH
# W8TnS8OIh8WT5/XnmEEY13nPkXAvik/QODQ99WYg6c17qsoW44L16lFW4M6JZExy
# xbnpzQb9jgZungIDLB1fhBoIZudYYQHEnxbeX0Cb7NY5hfwM5xOsDxak1WeBEJMB
# C4nwaEVnjnMx4/GY4UNumLVcDs0HMdvBhOZ1OZAids1BFnobQo3B/grnh6GCE7cw
# ghOzBgorBgEEAYI3AwMBMYITozCCE58GCSqGSIb3DQEHAqCCE5AwghOMAgEDMQ8w
# DQYJYIZIAWUDBAIBBQAwggFYBgsqhkiG9w0BCRABBKCCAUcEggFDMIIBPwIBAQYK
# KwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCDzvayhmAzRemNjPDPrFLlt8zlw
# smC5Ckt9uNj4rXKOXQIGXiDyYu9DGBMyMDIwMDEyMjIxMDgwNC40OTJaMAcCAQGA
# AgH0oIHUpIHRMIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSkwJwYDVQQLEyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQG
# A1UECxMdVGhhbGVzIFRTUyBFU046QzBGNC0zMDg2LURFRjgxJTAjBgNVBAMTHE1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wggg8fMIIE9TCCA92gAwIBAgITMwAA
# AQGBPiJG/fo44gAAAAABATANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# dGFtcCBQQ0EgMjAxMDAeFw0xOTA5MDYyMDQxMTVaFw0yMDEyMDQyMDQxMTVaMIHO
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBN
# aWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVz
# IFRTUyBFU046QzBGNC0zMDg2LURFRjgxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFNlcnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCy
# UPZYyITkNb2qIq1P3Shde+CJlHddvcixvnh4AZKR1/LCJdaOqWyBtseYQ14S6o91
# +WfXSzmZFy94l67m7IZhULAPAL1+1DsPfDa5vPG2nDUKTMaI4nfl/O5hpO1GK0Jl
# udOPSh+u2lOvd/uY85FrpiYvghmHfDkt7GUC+xE9jDORTl3c+xQL22uUmu01WP5u
# eRsGSexagFwJ8kALSIavN8eSSpdAj2gXY+L1uh3sdBsJ+pf4rqbfM1VuIZO7PcAt
# 0yQgfv+P6MzAyfb8MIC4EIyH/Sntu2JkBswgyTCW+RRoWG3q4zZAges8BU7raHJ1
# mjvmL1xdB/aLeY6VxK9ZAgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQUv76Ymu9N/A6M
# mGCRLYwVJB25pJEwHwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYD
# VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv
# cHJvZHVjdHMvTWljVGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEB
# BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j
# ZXJ0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADAT
# BgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEAHz22Wt6f5Ve2
# WJBP0Vn1quoEoyCe/4xU4MBKQVHn8kxz50BtwRdva6tI6JDqonlKg0YsGF4Olpp5
# scMn5lbEggEXSV32x1gt8DicNB9Cn1CyJqmdA8QWKA4PE1y1VYDf0IJfIKWM59J/
# 9kDb+5xFVl5aSDbnmdKoRn+xobYacuNIffSxQvJpivzvnHig0R2GSGHjTfI2kJJU
# QS1qkS3dppg0r0cXzeFR3BXv04cfNys71r1I2jpN8xn+dDpkKcro1OBCiDstTypi
# GyhqyxL2fxL6cS2wYbB0UuLXAt+z4dt7YeDvsBCv24OeYDR79FPSI8EIy8fM7Luh
# RVJU32t9YDCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAw
# gYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMT
# KU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEw
# MDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGK
# OiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0Eb
# GpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/
# JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0k
# ZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7l
# msqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlE
# XV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4E
# FgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBD
# AEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZW
# y4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5t
# aWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAt
# MDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0y
# My5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUF
# BwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1
# bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5
# AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohR
# DeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l
# 4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/
# f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WW
# j1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI5
# 7BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1
# gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9Dv
# fYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3ep
# gcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMu
# Ein1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmK
# LxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu
# 7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEqGCA60wggKVAgEBMIH+oYHUpIHR
# MIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQL
# EyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhh
# bGVzIFRTUyBFU046QzBGNC0zMDg2LURFRjgxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFNlcnZpY2WiJQoBATAJBgUrDgMCGgUAAxUACCVTQOIggfoN27b+
# VXzd4SffBfGggd4wgdukgdgwgdUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBS
# aWNvMScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NERFOS0wQzVFLTNFMDkxKzAp
# BgNVBAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJKoZI
# hvcNAQEFBQACBQDh0o+VMCIYDzIwMjAwMTIyMTcwODM3WhgPMjAyMDAxMjMxNzA4
# MzdaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIFAOHSj5UCAQAwBwIBAAICEK4wBwIB
# AAICGnswCgIFAOHT4RUCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoD
# AaAKMAgCAQACAxbjYKEKMAgCAQACAwehIDANBgkqhkiG9w0BAQUFAAOCAQEAq1+0
# 5CLRz+ZWgYQQJdnvUXkNb+S3Lhr2O3WLgPax2r1+xHWHX64w+Zg21mELeD574XGP
# fIfEfT0TTw0QppYVRN25MifoYcboDsP7FtNj6CKVzJiuyMNFi/HanDO1R+LJuRCD
# xdFcfkqTOGO/5+t6sEYGnXtrOWwNCtm0jpsgRqnd5/pabIxJBNoKIEWzO3qus3E8
# fYR/AhmoTYsNPs/7VNRymXFbWPKqCIVvoY3lWBWHoqLyRajvImVjlIDhNpJujTg0
# XU7tlVGc2uPvGEAoTNMsuTpazUzjaQ+LF5PZaaeF2rD1Gjp0vJCFpxXbTEsGQdZO
# elyDoA/V+Knp5B3wCjGCAvUwggLxAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwAhMzAAABAYE+Ikb9+jjiAAAAAAEBMA0GCWCGSAFlAwQCAQUAoIIB
# MjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIMp9
# EVrTno+h+sMwgGds0u1gid9qUuxQzwBS2F4KSg7mMIHiBgsqhkiG9w0BCRACDDGB
# 0jCBzzCBzDCBsQQUCCVTQOIggfoN27b+VXzd4SffBfEwgZgwgYCkfjB8MQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAQGBPiJG/fo44gAAAAABATAWBBSF
# 1/jeC7IhkkePW0tQoMuqnT4ecTANBgkqhkiG9w0BAQsFAASCAQAaSgU0mPaWcPTv
# eKSNsnv7QzcwDIrDMtNkEX1dtKLT8YUfsZTA+7iYlPqilNKXzUdLnKvfJ4wAf4mI
# 0yHdzXHr5cEXq8niF5EyCNYX3f4bCb+LjldUDUop6RQLBXl7GvbY9b7YZZhoPI6O
# LcXFQRPjQncggWTuTmUtmAKbM5AX2TKAiwyOkaJ3TbC3FAzIfVYvHUERN/rQepY6
# RLDHF4Ff2I/7k7qBhw1PawGYOAiSQBMGu6V/0/FMrEpqDi8/Uul6BuHmEC8LJUVf
# iy9vEFtBfCyUKU8weCNGyQj0XLPdFjHjgR1R7iZmyGdiGqXA9hxRNJmgv+5fWL6P
# AAoavnCw
# SIG # End signature block