Global Service Monitor (GSM) is a cloud service that provides a simplified way to monitor the availability of web-based applications from multiple locations around the world. GSM tests can be performed from 16 different locations around the world.
GSM supports the following two types of monitoring:
Important: GSM is retiring in favor of Application Insights in November 2018.
Application Insights offer two types of tests, very similar to GSM:
Besides these, there are many other additional features which are offered by Application Insights that can help to diagnose application performance issues in depth https://docs.microsoft.com/en-us/azure/application-insights/app-insights-monitor-web-app-availability
The System Center team has developed a Migration tool which will migrate all your Ping Tests and Web Tests from GSM to Azure Application Insights. This tool will also create corresponding alerts in Application Insights, like what you had configured in GSM for your tests.
Important: The following sections provide information on how to migrate the GSM tests to Application Insights, also few notes and points to consider while migrating.
Understand the following details/do the actions applicable in case you are attempting to migrate the tests under specific scenarios as mentioned below:
| Scenario | Notes |
| You are trying to migrate more than 800 tests | AI limits these. Learn more about the limits. The resource group can hold only 800 web tests and a component can only hold 100 tests. The migration tool creates a new resource group and migrates the remaining tests to this new resource group. This new resource group will be named as ""<RESOURCE_GROUP_NAME1>" and so on. |
| You do not wish to run the script from the SCOM server | Install SCOM Powershell on the computer, from where you want to run the script, and provide the SCOM server name in the PowerShell. |
| You wish to do a logical grouping of tests | The customer can run the tool and move everything on https://resources.azure.com . There is an option to moveResources under -resourceGroups à DTSLogCleaner. |
Important: To avoid duplicate tests in Application Insights, ensure you run the script only once.
Logging from the tool:
Important: During migration if there is any issue with Application Insights and all the tests are not yet migrated then please follow these steps:
Once you have successfully migrated all your GSM tests to Application Insights, verify these tests in the Azure Application Insights.

Note: Once all the tests are migrated to Application Insights, the test summary displays the test results in some time.
Click the green/red dot to find the result summary,
In Application insights, URL ping tests are configured to monitor HTTP response. Http Status code 200 indicates success that a normal web page has been returned. All the GSM availability tests report success/failure based on this criterion after they are migrated to Application Insights. The failures can be sent through an email or setup a webhook which would be called when an alert is raised.
Note: The migration tool with create Alerts for your GSM tests in Application Insights. See highlighted is enabled.

Application insights provides the description and status of alerts under
Home-> Resource groups -> <ResourceGroupName>- Activity Log in Azure Portal.

There are few functionalities available in GSM but not available in Application Insights.
You cannot monitor the following scenarios in AI:

<#
.SYNOPSIS
.
.DESCRIPTION
The script will migrate all the GSM tests to Application Insights.
.PARAMETER Path
The path to the .
.PARAMETER LiteralPath
Specifies a path to one or more locations. Unlike Path, the value of
LiteralPath is used exactly as it is typed. No characters are interpreted
as wildcards. If the path includes escape characters, enclose it in single
quotation marks. Single quotation marks tell Windows PowerShell not to
interpret any characters as escape sequences.
.EXAMPLE
C:\PS> .\MigrateGSMToAI.ps1 -SubscriptionName MyAzureSubscription -AzureResourceGroupName MyResourceGroupName -ResourceLocation "East US" -SCOMMSComputerName "MySCOMComputer"
#>
Param(
############## The Azure subscription Name.
[Parameter(Mandatory=$true)]
[string]$SubscriptionName,
############## The name of the resource group in azure portal, if you do not have the resource group created in azure, the script will create it.
[Parameter(Mandatory=$true)]
[string]$AzureResourceGroupName,
############## The name of the Applications Insight's component, you can provide the parameter, by default it will be take SCOM's Management Group name and all tests will be created under this component.
[Parameter(Mandatory=$false)]
[string]$AIcomponentName,
############## Resource location for the Azure Resource group.
[Parameter(Mandatory=$true)]
[string]$ResourceLocation,
############## Computer Name for SCOM Management Server machine. Provide the parameter if you are using a remote machine to connect to the Management Server or leave blank.
[Parameter(Mandatory=$false)]
[string]$SCOMMSComputerName,
############## Provide the Instance IDs of Ping tests we failed to migrate.
[Parameter(Mandatory=$false)]
[String[]]$FailedPingTestsInstanceIds,
############## Provide the Instance IDs of MultiStep tests we failed to migrate.
[Parameter(Mandatory=$false)]
[String[]]$FailedMultiStepTestsInstanceIds
) #end param
############## Log the progress of Migration.
function LogMessage([string]$message, [string]$logFileName, [int]$logLevel, [string]$testNameForLog)
{
$levelText = ""
If ( $logLevel -eq $ERROR )
{
$levelText = "[ERROR]"
$newLogFileName = $logFileName.Replace($testNameForLog, ($testNameForLog + "-Failed"))
Move-Item -Path $logFileName -Destination $newLogFileName -Force
$logFileName = $newLogFileName
}
ElseIf ( $logLevel -eq $FATALERROR )
{
$levelText = "[FATALERROR]"
Write-Host "The script has ended abruptly, Please see the logs @ :- $migrationLogFileName" -ForegroundColor Red
}
ElseIf ( $logLevel -eq $DUPERROR )
{
$levelText = "[DUPLICATE TEST]"
}
Else
{
$levelText = "[INFO]"
}
$messageToBelogged = $($levelText + " : " + $message)
$messageToBelogged | Out-File -Append -filepath $logFileName -Force
}
############## Check prerequisites
function Check-Prerequisites
{
If ( -not (Get-Module OperationsManager ) )
{
Import-Module OperationsManager
}
If ( -not (Get-Module OperationsManager ) )
{
LogMessage "OperationsManager Powershell module not found." $migrationLogFileName $FATALERROR
exit
}
If ( -not ( Get-Module AzureRm.Profile ) )
{
Import-Module AzureRm.Profile
}
If ( -not (Get-Module AzureRm.Profile ) )
{
LogMessage "AzureRm.Profile Powershell module not found." $migrationLogFileName $FATALERROR
exit
}
}
############## Interactive login to connect to the Azure account.
function Login-ToAzure
{
$azureConnection = $null
Try
{
$azureConnection = Connect-AzureRmAccount
}
Catch
{
LogMessage "Please verify the login credentials. Unable to login to the azure portal." $migrationLogFileName $FATALERROR
exit
}
If ($azureConnection -eq $null)
{
LogMessage "Please verify the login credentials. Unable to login to the azure portal." $migrationLogFileName $FATALERROR
exit
}
}
############## Function creates the resource group if already not present on azure.
function Create-ResourceGroup()
{
$resourceGroupFromAzure = $null
$IsResourceGroupFoundToBeUsed = $false
$IsResourceGroupToBeCreated = $false
While ( -Not $IsResourceGroupFoundToBeUsed )
{
Try
{
LogMessage "Verifying if the resource group with Name:- $script:resourceGroupName and location:- $ResourceLocation already exists." $migrationLogFileName $INFORMATION
$resourceGroupFromAzure = Get-AzureRmResource -ResourceGroupName $script:resourceGroupName -ResourceType "microsoft.insights/components" -ApiVersion 2014-04-01
If ( $resourceGroupFromAzure -ne $null )
{
If ( $( $resourceGroupFromAzure | Group-Object ResourceType).Count -ge 8 )
{
$resourceGroupSuffix ++
$script:resourceGroupName = $( $AzureResourceGroupName + $resourceGroupSuffix)
continue
}
}
Else
{
$resourceGroupFromAzure = Get-AzureRmResource -ResourceId /subscriptions/$subscriptionID/resourceGroups/$script:resourceGroupName -ApiVersion 2014-04-01
If ( $resourceGroupFromAzure -ne $null )
{
LogMessage "Resource group with Name:- $script:resourceGroupName and location:- $resourceLocation already exists." $migrationLogFileName $INFORMATION
$IsResourceGroupFoundToBeUsed = $true
}
Else
{
LogMessage "The azure resource group:- $script:resourceGroupName does not exist. Will try to create the resource group." $migrationLogFileName $INFORMATION
$IsResourceGroupToBeCreated = $true
}
}
}
Catch
{
LogMessage "The azure resource group:- $script:resourceGroupName does not exist. Will try to create the resource group." $migrationLogFileName $INFORMATION
$IsResourceGroupToBeCreated = $true
}
$IsResourceGroupFoundToBeUsed = $true
}
If ( $IsResourceGroupToBeCreated )
{
LogMessage "Creating resource group with Name:- $script:resourceGroupName and location:- $ResourceLocation." $migrationLogFileName $INFORMATION
Try
{
New-AzureRmResourceGroup -Location $ResourceLocation -Name $script:resourceGroupName
}
Catch
{
LogMessage "Unable to create the resource group with Name:- $script:resourceGroupName and location:- $ResourceLocation. Please verify if you have the correct privileges to create the resource group." $migrationLogFileName $FATALERROR
exit
}
}
Else
{
LogMessage "Resource group with Name:- $script:resourceGroupName and location:- $ResourceLocation already exists." $migrationLogFileName $INFORMATION
return
}
LogMessage "Created Resource group with Name:- $script:resourceGroupName and location:- $ResourceLocation." $migrationLogFileName $INFORMATION
}
############## Function for creating component in Application Insights.
function Create-Component( $componentName )
{
$kindOfComponent = "web"
$componentJSON = ""
$componentJSONObject = New-Object -TypeName PSObject
Add-Member -InputObject $componentJSONObject -MemberType NoteProperty -Name name -Value $componentName
Add-Member -InputObject $componentJSONObject -MemberType NoteProperty -Name location -Value $ResourceLocation
Add-Member -InputObject $componentJSONObject -MemberType NoteProperty -Name kind -Value $kindOfComponent
$componentPropertiesObject = New-Object -TypeName PSObject
Add-Member -InputObject $componentPropertiesObject -MemberType NoteProperty -Name Application_Type -Value "web"
Add-Member -InputObject $componentPropertiesObject -MemberType NoteProperty -Name ApplicationId -Value $componentName
Add-Member -InputObject $componentPropertiesObject -MemberType NoteProperty -Name Flow_Type -Value "Bluefield"
Add-Member -InputObject $componentPropertiesObject -MemberType NoteProperty -Name Request_Source -Value "rest"
Add-Member -InputObject $componentJSONObject -MemberType NoteProperty -Name properties -Value $componentPropertiesObject
$componentJSON = $componentJSONObject | ConvertTo-Json
LogMessage $( "Component JSON:- " + $componentJSON ) $migrationLogFileName $INFORMATION
LogMessage "Creating Component with Name:- $componentName." $migrationLogFileName $INFORMATION
Try
{
New-AzureRmResource -ResourceName $componentName -Location $ResourceLocation -PropertyObject $componentJSON -ResourceGroupName $script:resourceGroupName -ResourceType microsoft.insights/components -ApiVersion 2015-05-01 -Force
}
Catch
{
LogMessage "Unable to create Component with Name:- $componentName." $migrationLogFileName $FATALERROR
exit
}
LogMessage $( "Created Component Name:- " + $componentName + " in Resource group :- " + $script:resourceGroupName ) $migrationLogFileName $INFORMATION
}
############## Function to validate the resource location provided by the user as the parameter ResourceLocation
function ValidateResourceLocation()
{
$azureLocations = $(Get-AzureRmLocation).DisplayName
If ( -Not $azureLocations.Contains($ResourceLocation) )
{
LogMessage $("Please provide a valid resource location. Here are the valid locations "+$azureLocations) $migrationLogFileName $FATALERROR
exit
}
}
############## Function for formating GeoLocations to REST API consumable format.
function Format-GeoLocations ( $testLocations )
{
$webTestGeoLocations = @{}, @{}
Foreach ( $location in $testLocations )
{
Switch ( $location )
{
"emea-nl-ams-edge" { $location = "emea-nl-ams-azr" }
"us-ca-lax-edge" { $location = "us-ca-sjc-azr" }
"apac-sg-sin-edge" { $location = "apac-sg-sin-azr" }
"us-tx-sn1-edge" { $location = "us-tx-sn1-azr" }
"us-il-ch1-edge" { $location = "us-il-ch1-azr" }
"emea-gb-lts-edge" { $location = "emea-gb-db3-azr" }
"emea-gb-lts-edge" { $location = "emea-gb-db3-azr" }
"us-nj-ewr-edge" { $location = "us-va-ash-azr" }
"apac-tw-tpa-edge" { $location = "apac-hk-hkn-azr" }
}
$webTestGeoLocations += @{Id = $location}
}
return $webTestGeoLocations | Select-Object -Skip 2
}
############## Common Function for Ping and Multistep test to create JSON.
function Build-TestJSON ( $scomTest, $completeWebTestName, $componentName, $webTestGeoLocations, $webTestXML, $webTestKind, $frequency, $timeout, $testInstanceID)
{
$testID = $completeWebTestName + "-" + $componentName
$testLogFileName = $( $testLogFilePath + $completeWebTestName + ".txt" )
"" | Out-File -filepath $testLogFileName -Force
LogMessage $( "Building JSON for Test NAME:- " + $completeWebTestName ) $testLogFileName $INFORMATION
LogMessage $( "SCOM Instance ID :- " + $testInstanceID ) $testLogFileName $INFORMATION
Write-Host "Building JSON for Test NAME:- " $completeWebTestName -ForegroundColor Green
############# Convert SCOM GSM test frequency into Applications Insights UI supported values (5, 10, 15) minutes
$newFrequency = [int][Math]::Ceiling($frequency/60) *60
If ( $newFrequency -gt 900 )
{
$newFrequency = 900
}
If ( $frequency -ne $newFrequency )
{
Write-Host "The Test frequency is changed from :-"$frequency "to :-"$newFrequency -ForegroundColor Yellow
LogMessage $( "The Test frequency is changed from :-" + $frequency + " to :- "+ $newFrequency) $testLogFileName $INFORMATION
}
############# Convert SCOM GSM test timeout into Applications Insights UI supported values (30, 60, 90, 120) seconds
If ( $webTestKind -eq "ping" )
{
$newTimeout = [int][Math]::Ceiling($timeout/30) *30
If ( $newTimeout -gt 120 )
{
$newTimeout = 120
}
If ( $timeout -ne $newTimeout )
{
Write-Host "The Test Time out is changed from :-"$timeout "to :-"$newTimeout -ForegroundColor Yellow
LogMessage $( "The Test Time out is changed from :-" + $timeout + " to :- "+ $newTimeout) $testLogFileName $INFORMATION
}
}
$enabled = $true
$retryEnabled = $true
$propertiesObject = New-Object -TypeName PSObject
$configurationObject = New-Object -TypeName PSObject
Add-Member -InputObject $configurationObject -MemberType NoteProperty -Name WebTest -Value $webTestXML
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name SyntheticMonitorId -Value $testID
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Name -Value $completeWebTestName
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Description -Value ("$webTestKind web test for " + $completeWebTestName)
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Enabled -Value $enabled
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Frequency -Value $newFrequency
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Timeout -Value $newTimeout
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Kind -Value $webTestKind
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name RetryEnabled -Value $retryEnabled
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Locations -Value @( $webTestGeoLocations )
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Configuration -Value $configurationObject
return $propertiesObject
}
############## Function for Building MultiStepTest JSON.
function Build-MultiStepTest( $FailedMultiStepTestsInstanceIds )
{
$multiTestJSON = ""
$multiStepTestsForRequest = @()
$numberOfTestsInComponent = 0
$componentSuffix = 1
$allGeoLocations = Get-SCOMClass -Name "Microsoft.SystemCenter.Omonline.OutsideIn.OutsideInPop" | Get-SCOMClassInstance
If ( $FailedMultiStepTestsInstanceIds.count -eq 0)
{
$multiStepTests = Get-SCOMClass -DisplayName "Visual Studio Web Test Container" | Get-SCOMClassInstance
}
Else
{
$multiStepTests = Get-SCOMClassInstance -Id $FailedMultiStepTestsInstanceIds
$componentSuffix = $( $( Get-AzureRmResource -ResourceGroupName $script:resourceGroupName -ResourceType "microsoft.insights/components" -ApiVersion 2014-04-01 ) | Group-Object ResourceType).Count
$componentSuffix ++
}
Foreach ( $multiStepTest in $multiStepTests )
{
$multiStepTestID = $multiStepTest.Id
If ( $numberOfTestsInComponent -eq 0 )
{
############## Invoke Create Component using New-AzureRmResource. More info -- https://docs.microsoft.com/en-us/powershell/module/azurerm.resources/new-azurermresource?view=azurermps-6.5.0
$componentName = $("MultiStep-" + $AIcomponentName + $componentSuffix)
$componentSuffix++
}
[xml]$testLocationsXML = $multiStepTest.'[Microsoft.SystemCenter.Omonline.OutsideIn.VSWebTest.VSWebTestContainer].Locations'.Value
$testLocationsIDs = $testLocationsXML.Locations.Location.managementActionPointId
$testLocations = $allGeoLocations | Select-Object -Property Id, Name | Where-Object {$_.Id -in $testLocationsIDs}
$webTestGeoLocations = Format-GeoLocations $testLocations.Name
## Case where test is configured to run on internal agent.
If ( $webTestGeoLocations -eq $null )
{
continue;
}
$NumOfLocMultiTest = $webTestGeoLocations.Count
If ( $NumOfLocMultiTest -ge 3 )
{
$NumOfLocMultiTest = 3;
}
[xml]$testConfigXML = $multiStepTest.'[Microsoft.SystemCenter.Omonline.OutsideIn.VSWebTest.VSWebTestContainer].VSDeclarativeTestConfigs'.Value
$webTestXML = $testConfigXML.VSWebTestConfigs.VSWebTestElement.content
$multiTestFrequency = $multiStepTest.'[Microsoft.SystemCenter.Omonline.OutsideIn.VSWebTest.VSWebTestContainer].IntervalInSeconds'.Value
## Case where test name is a duplicate
If ( $allTests.Contains($multiStepTest.DisplayName) )
{
$testLogFileName = $( $testLogFilePath + "MultiStep-DuplicateTest.txt" )
LogMessage $( "Cannot migrate test, as there is already a multistep test with name :- " + $multiStepTest.DisplayName + ". Please rename the test to migrate." ) $testLogFileName $DUPERROR
LogMessage $( "Information to find the SCOM Instance ID :- " + $multiStepTestID + " with name :- " + $multiStepTest.DisplayName ) $testLogFileName $DUPERROR
Write-Host "Cannot migrate test, as there is already a multistep test with name :- " $multiStepTest.DisplayName ". Please rename the test to migrate." -ForegroundColor Red
continue
}
Else
{
$allTests.Add($multiStepTest.DisplayName)
$numberOfTestsInComponent++
If ( $numberOfTestsInComponent -ge $componentLimit )
{
$numberOfTestsInComponent = 0
}
}
$multiStepTestProperty = Build-TestJSON $multiStepTest $multiStepTest.DisplayName $componentName $webTestGeoLocations $webTestXML "multistep" $multiTestFrequency 120 $multiStepTestID
$multiStepTestForRequest = New-Object -TypeName PSObject
Add-Member -InputObject $multiStepTestForRequest -MemberType NoteProperty -Name NAME -Value $multiStepTest.DisplayName
Add-Member -InputObject $multiStepTestForRequest -MemberType NoteProperty -Name ID -Value $multiStepTestID
Add-Member -InputObject $multiStepTestForRequest -MemberType NoteProperty -Name PROPERTY -Value $multiStepTestProperty
Add-Member -InputObject $multiStepTestForRequest -MemberType NoteProperty -Name COMPONENT -Value $componentName
Add-Member -InputObject $multiStepTestForRequest -MemberType NoteProperty -Name NUMOFLOC -Value $NumOfLocMultiTest
$multiStepTestsForRequest += $multiStepTestForRequest
}
return $multiStepTestsForRequest
}
############## Function for Http Headers for PingTest.
function Build-HTTPHeaders( $httpHeaders )
{
[xml]$pingHeaders = ' '
$headersFlag = $true
Foreach ( $httpHeader in $httpHeaders.HttpHeader )
{
If ( $headersFlag )
{
$pingHeaders.Headers.Header.Name = $httpHeader.Name
$pingHeaders.Headers.Header.Value = $httpHeader.Value
$headersFlag = $false
}
Else
{
$pingHeader = $pingHeaders.CreateElement("Header")
$pingHeader.SetAttribute("Name", $httpHeader.Name)
$pingHeader.SetAttribute("Value", $httpHeader.Value)
[void]$pingHeaders.Headers.AppendChild($pingHeader)
}
}
return $pingHeaders.OuterXml
}
############## Function to create StringHttpBody for Ping Test, We are assuming that the customer has provided the body in JSON format
function Build-StringHttpBody( $pingTestBody, $contentType )
{
return ''+$([Convert]::ToBase64String($([System.Text.Encoding]::Unicode.GetBytes($pingTestBody))))+' '
}
############## Function for Building PingTest JSON.
function Build-PingTest( $FailedPingTestsInstanceIds )
{
$pingTestsForRequest = @()
$numberOfTestsInComponent = 0
$componentSuffix = 1
$allGeoLocations = Get-SCOMClass -Name "Microsoft.SystemCenter.Omonline.OutsideIn.OutsideInPop" | Get-SCOMClassInstance
If ( $FailedPingTestsInstanceIds.count -eq 0 )
{
$pingTests = Get-SCOMClass -Name "Microsoft.SystemCenter.WebApplicationSolutions.SingleUrlTest" | Get-SCOMClassInstance
}
Else
{
$pingTests = Get-SCOMClassInstance -Id $FailedPingTestsInstanceIds
$componentSuffix = $( $( Get-AzureRmResource -ResourceGroupName $script:resourceGroupName -ResourceType "microsoft.insights/components" -ApiVersion 2014-04-01 ) | Group-Object ResourceType).Count
$componentSuffix ++
}
Foreach ( $pingTest in $pingTests )
{
If ( $numberOfTestsInComponent -eq 0 )
{
############## Invoke Create Component using New-AzureRmResource. More info -- https://docs.microsoft.com/en-us/powershell/module/azurerm.resources/new-azurermresource?view=azurermps-6.5.0
$componentName = $("Ping-" + $AIcomponentName + $componentSuffix)
$componentSuffix++
}
$pingJSON = ""
$parametersValues = ""
$pingTestFrequency = 0
$pingTestConfig = $null
$pingTestRequest = $null
$pingTestTimeOut = 0
$followRedirects = ""
$httpVersion = ""
$httpMethod = ""
$httpHeaders = ""
$pingTestHttpHeaders = ""
$pingTestStringHttpBody= ""
$pingTestBody = ""
$pingTestForRequest = $null
$pingTestId = $pingTest.Id
[xml]$testLocationsXML = $pingTest.'[Microsoft.SystemCenter.WebApplicationSolutions.SingleUrlTest].Locations'.Value
$testLocationsIDs = $testLocationsXML.Locations.Location.managementActionPointId
$testLocations = $allGeoLocations | Select-Object -Property Id, Name | Where-Object {$_.Id -in $testLocationsIDs}
$webTestGeoLocations = Format-GeoLocations $testLocations.Name
## Case where test is configured to run on internal agent.
If ( $webTestGeoLocations -eq $null )
{
continue;
}
$NumOfLocPingTest = $webTestGeoLocations.Count
If ( $NumOfLocPingTest -ge 3 )
{
$NumOfLocPingTest = 3;
}
$parametersValues = ([xml]($pingTest.'[Microsoft.SystemCenter.WebApplicationSolutions.SingleUrlTest].Parameters'.Value)).Parameters.Parameter.Value
$pingTestFrequency = $pingTest.'[Microsoft.SystemCenter.WebApplicationSolutions.SingleUrlTest].IntervalInSeconds'.Value
$pingTestConfig = ([xml]($pingTest.'[Microsoft.SystemCenter.WebApplicationSolutions.SingleUrlTest].TestConfig'.Value)).TestConfig
$pingTestRequest = $pingTestConfig.Requests.Request
$pingTestTimeOut = $pingTestConfig.TestTimeout
If ( $pingTestTimeOut -eq $null )
{
$pingTestTimeOut = 30
}
$followRedirects = $pingTestConfig.FollowRedirects
If ( $followRedirects -eq $null )
{
$followRedirects = "True"
}
$httpVersion = $pingTestRequest.Version
If ( $httpVersion -eq $null )
{
$httpVersion = "1.1"
}
$httpMethod = $pingTestRequest.Verb
If ( $httpMethod -eq $null )
{
$httpMethod = "GET"
}
$httpHeaders = $pingTestRequest.HttpHeaders
If ( $httpHeaders -ne $null )
{
$pingTestHttpHeaders = Build-HTTPHeaders $httpHeaders
}
$pingTestBody = $pingTestRequest.Body
If ( $pingTestBody -ne $null )
{
[xml]$pingTestHttpHeadersXML = $pingTestHttpHeaders
$contentTypeForTest = $($pingTestHttpHeadersXML.Headers.Header | Where-Object {$_.Name -eq "Content-Type"}).Value
$pingTestStringHttpBody = Build-StringHttpBody $pingTestBody $contentTypeForTest
}
Foreach ( $parametersValue in $parametersValues )
{
$pingTestURL = $parametersValue.Innertext
$webTestXML = '' + $pingTestHttpHeaders + $pingTestStringHttpBody + ' '
$pingtestName = $( $pingTest.DisplayName + "-" + $parametersValue.displayName )
## Case where test name is a duplicate
If ( $allTests.Contains( $($pingtestName + "-" + $componentName) ) )
{
$testLogFileName = $( $testLogFilePath + "Ping-DuplicateTest.txt" )
LogMessage $( "Cannot migrate Ping test, as there is already a Ping test with name :- " + $pingtestName + " in component :- " + $componentName + ". Please rename the test to migrate." ) $testLogFileName $DUPERROR
LogMessage $( "Information to find the SCOM Instance ID :- " + $pingTestId + " with name :- " + $pingtestName ) $testLogFileName $DUPERROR
Write-Host "Cannot migrate Ping test, as there is already a Ping test with name :- " $pingtestName " with URL :- " $pingTestURL " in component :- " $componentName " . Please rename the test to migrate" -ForegroundColor Red
continue
}
Else
{
$allTests.Add( $($pingtestName + "-" + $componentName) )
$numberOfTestsInComponent++
If ( $numberOfTestsInComponent -ge $componentLimit )
{
$numberOfTestsInComponent = 0
}
}
$pingTestProperty = Build-TestJSON $pingTest $pingtestName $componentName $webTestGeoLocations $webTestXML "ping" $pingTestFrequency $pingTestTimeOut $pingTestId
$pingTestForRequest = New-Object -TypeName PSObject
Add-Member -InputObject $pingTestForRequest -MemberType NoteProperty -Name NAME -Value $pingtestName
Add-Member -InputObject $pingTestForRequest -MemberType NoteProperty -Name ID -Value $pingTestId
Add-Member -InputObject $pingTestForRequest -MemberType NoteProperty -Name PROPERTY -Value $pingTestProperty
Add-Member -InputObject $pingTestForRequest -MemberType NoteProperty -Name COMPONENT -Value $componentName
Add-Member -InputObject $pingTestForRequest -MemberType NoteProperty -Name NUMOFLOC -Value $NumOfLocPingTest
$pingTestsForRequest += $pingTestForRequest
}
}
return $pingTestsForRequest
}
############## Function for creating a web test in Application Insights
function Create-WebTest( $webTests )
{
$previousComponentName = ""
Foreach ( $webTest in $webTests )
{
$componentName = $webTest.COMPONENT
$propertiesObject = $webtest.PROPERTY
If ( $componentName -ne $previousComponentName)
{
$previousComponentName = $componentName
Create-Component $componentName
}
$testLogFileName = $( $testLogFilePath + $webtest.NAME + ".txt" )
############## REST Call to create the Web Test
$webTestCompleteName = $( $webtest.NAME + "-" + $componentName)
$webTestMigrated = $null
$webTestAlertCreated = $null
LogMessage $( "Migrating Test for NAME:- " + $webtest.NAME ) $testLogFileName $INFORMATION
LogMessage $( "Component NAME:- " + $componentName ) $testLogFileName $INFORMATION
LogMessage $( "Resource Group NAME:- " + $script:resourceGroupName ) $testLogFileName $INFORMATION
LogMessage $( "JSON for Test :- " + $( $propertiesObject | ConvertTo-Json -Depth 5 ) ) $testLogFileName $INFORMATION
Write-Host "Migrating Test for NAME:- " $webtest.NAME -ForegroundColor Green
Try
{
$tagsObject = @{}
$tagsObject.Add("hidden-link:/subscriptions/$subscriptionID/resourceGroups/$script:resourceGroupName/providers/microsoft.insights/components/$componentName", "Resource")
$tagsObject.Add("hidden-link:Microsoft.Internal.IsScomWebTest", "true")
New-AzureRmResource -ResourceName $webTestCompleteName -Location $ResourceLocation -PropertyObject $propertiesObject -ResourceGroupName $script:resourceGroupName -ResourceType microsoft.insights/webtests -Tag $tagsObject -ApiVersion 2015-05-01 -Force
}
Catch
{
$exceptionString = $_.Exception.Message
If ( $exceptionString.contains("ResourceQuotaExceeded") )
{
LogMessage $( "Creating a new Resource Group as Resource Group :- " + $script:resourceGroupName + " has exceed the quota of '800' resources." ) $migrationLogFileName $INFORMATION
$resourceGroupSuffix ++
$script:resourceGroupName = $( $AzureResourceGroupName + $resourceGroupSuffix)
########### If the quota of 800 resource exceeds, we will create a new Resource group and a new Component in that resource group and re-try the test creation in new Resource group.
Create-ResourceGroup
Create-Component $componentName
Try
{
$tagsObject = @{}
$tagsObject.Add("hidden-link:/subscriptions/$subscriptionID/resourceGroups/$script:resourceGroupName/providers/microsoft.insights/components/$componentName", "Resource")
$tagsObject.Add("hidden-link:Microsoft.Internal.IsScomWebTest", "true")
New-AzureRmResource -ResourceName $webTestCompleteName -Location $ResourceLocation -PropertyObject $propertiesObject -ResourceGroupName $script:resourceGroupName -ResourceType microsoft.insights/webtests -Tag $tagsObject -ApiVersion 2015-05-01 -Force
}
Catch
{
LogMessage $( "Exception:- " + $_.Exception.Message ) $testLogFileName $INFORMATION $webtest.NAME
LogMessage $( "Unable to migrate the Test with Name:- " + $webtest.NAME + " SCOM Instance ID :- " + $webtest.ID ) $testLogFileName $ERROR $webtest.NAME
Write-Host "Unable to migrate the Test with Name:- " $webtest.NAME ". Please see the logs @ :- $testLogFileName" -ForegroundColor Red
continue
}
}
Else
{
LogMessage $( "Exception:- " + $_.Exception.Message ) $testLogFileName $INFORMATION $webtest.NAME
LogMessage $( "Unable to migrate the Test with Name:- " + $webtest.NAME + " SCOM Instance ID :- " + $webtest.ID ) $testLogFileName $ERROR $webtest.NAME
Write-Host "Unable to migrate the Test with Name:- " $webtest.NAME ". Please see the logs @ :- $testLogFileName" -ForegroundColor Red
continue
}
}
LogMessage $( "Verifying if the test:- " + $webtest.NAME + " got created in Application Insights." ) $testLogFileName $INFORMATION
$webTestMigrated = Get-AzureRmResource -ResourceGroupName $script:resourceGroupName -ResourceType microsoft.insights/webtests -ResourceName $webTestCompleteName
If ( $webTestMigrated -eq $null )
{
LogMessage $( "Exception:- " + $_.Exception.Message ) $testLogFileName $INFORMATION $webtest.NAME
LogMessage $( "Migration Not Successful for the Test with Name:- " + $webtest.NAME + " SCOM Instance ID :- " + $webtest.ID ) $testLogFileName $ERROR $webtest.NAME
Write-Host "Migration Not Successful for the Test with Name:- " $webtest.NAME ". Please see the logs @ :- $testLogFileName" -ForegroundColor Red
continue
}
Else
{
LogMessage $( "Migration Successful for the Test with Name:- " + $webtest.NAME ) $testLogFileName $INFORMATION
}
Try
{
LogMessage $( "Creating alert for test :- " + $webtest.NAME ) $migrationLogFileName $INFORMATION
Create-WebTestAlert $webTestCompleteName $webTest.COMPONENT $webTest.NUMOFLOC
}
Catch
{
LogMessage $( "Exception:- " + $_.Exception.Message ) $testLogFileName $INFORMATION $webtest.NAME
LogMessage $( "Unable to create alert for Name:- " + $webtest.NAME ) $testLogFileName $ERROR $webtest.NAME
Write-Host "Unable to create alert for Name:- " $webtest.NAME ". Please see the logs @ :- $testLogFileName" -ForegroundColor Red
continue
}
LogMessage $( "Verifying if the alert for test:- " + $webtest.NAME + " got created in Application Insights." ) $testLogFileName $INFORMATION
$webTestAlertCreated = Get-AzureRmResource -ResourceGroupName $script:resourceGroupName -ResourceType microsoft.insights/alertrules -ResourceName $webTestCompleteName
If ( $webTestAlertCreated -eq $null )
{
LogMessage $( "Exception:- " + $_.Exception.Message ) $testLogFileName $INFORMATION $webtest.NAME
LogMessage $( "Creation of alert Not Successful for the Test with Name:- " + $webtest.NAME ) $testLogFileName $ERROR $webtest.NAME
Write-Host "Creation of alert Not Successful for the Test with Name:- " $webtest.NAME ". Please see the logs @ :- $testLogFileName" -ForegroundColor Red
continue
}
Else
{
LogMessage $( "Creation of alert Successful for the Test with Name:- " + $webtest.NAME ) $testLogFileName $INFORMATION
}
Write-Host "Migration Successful for the Test with Name:- " $webtest.NAME -ForegroundColor Green
$newTestLogFileName = $testLogFileName.Replace($webtest.NAME, ($webtest.NAME + "-Migrated"))
Move-Item -Path $testLogFileName -Destination $newTestLogFileName -Force
$testLogFileName = $newTestLogFileName
}
}
############## Function for creating Alert for a web test.
function Create-WebTestAlert( $completeWebTestName, $componentName, $numberOfAlertLocations)
{
$tagsObject = @{}
$propertiesObject = New-Object -TypeName PSObject
$conditionObject = New-Object -TypeName PSObject
$dataSourceObject = New-Object -TypeName PSObject
$actionObject = New-Object -TypeName PSObject
$tagsObject.Add("hidden-link:/subscriptions/$subscriptionID/resourceGroups/$script:resourceGroupName/providers/microsoft.insights/components/$componentName", "Resource")
$tagsObject.Add("hidden-link:/subscriptions/$subscriptionID/resourceGroups/$script:resourceGroupName/providers/microsoft.insights/webtests/$completeWebTestName", "Resource")
Add-Member -InputObject $dataSourceObject -MemberType NoteProperty -Name odata.type -Value "Microsoft.Azure.Management.Insights.Models.RuleMetricDataSource"
Add-Member -InputObject $dataSourceObject -MemberType NoteProperty -Name MetricName -Value "GSMT_AvRaW"
Add-Member -InputObject $dataSourceObject -MemberType NoteProperty -Name ResourceUri -Value "/subscriptions/$subscriptionID/resourceGroups/$script:resourceGroupName/providers/microsoft.insights/webtests/$completeWebTestName"
Add-Member -InputObject $conditionObject -MemberType NoteProperty -Name odata.type -Value "Microsoft.Azure.Management.Insights.Models.LocationThresholdRuleCondition"
Add-Member -InputObject $conditionObject -MemberType NoteProperty -Name DataSource -Value $dataSourceObject
Add-Member -InputObject $conditionObject -MemberType NoteProperty -Name WindowSize -Value "PT5M"
Add-Member -InputObject $conditionObject -MemberType NoteProperty -Name FailedLocationCount -Value $numberOfAlertLocations
Add-Member -InputObject $actionObject -MemberType NoteProperty -Name odata.type -Value "Microsoft.Azure.Management.Insights.Models.RuleEmailAction"
Add-Member -InputObject $actionObject -MemberType NoteProperty -Name SendToServiceOwners -Value $true
Add-Member -InputObject $actionObject -MemberType NoteProperty -Name CustomEmails -Value @()
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Name -Value $completeWebTestName
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Description -Value ("Alert rule for web test " + $completeWebTestName)
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name IsEnabled -Value $true
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Condition -Value $conditionObject
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name Action -Value $actionObject
Add-Member -InputObject $propertiesObject -MemberType NoteProperty -Name actions -Value @()
LogMessage $( "JSON Data for the alert :- " + $( $propertiesObject | ConvertTo-Json -Depth 5 ) ) $testLogFileName $INFORMATION
New-AzureRmResource -ResourceName $completeWebTestName -Location $ResourceLocation -PropertyObject $propertiesObject -ResourceGroupName $script:resourceGroupName -ResourceType microsoft.insights/alertrules -Tag $tagsObject -ApiVersion 2016-03-01 -Force
}
############## START OF SCRIPT ##############
clear
############## Logging variables
$ERROR = 1
$FATALERROR = 2
$DUPERROR = 3
$INFORMATION = 4
$testLogFilePath = $(Get-Location).Path + "\Logs\"
$migrationLogFileName = $testLogFilePath + "MigrationLog.txt"
$testLogFileName = ""
$allTests = New-Object 'System.Collections.Generic.List[string]'
############## Variables used in the script.
$subscriptionID = ""
$pingWebTests = $null
$multiStepWebTests = $null
$subscription = $null
$componentLimit = 100
$resourceGroupSuffix = 0
$resourceGroupName = $AzureResourceGroupName
$allWebTestsToMigrate = @()
############## Creating Log folder if does not exist, and delete all log files if already exists.
If ( -Not (Test-Path -Path $testLogFilePath) )
{
New-Item -ItemType directory -Path $testLogFilePath -ErrorAction Stop
}
Else
{
Remove-Item $testLogFilePath* -Include *.txt
}
"[INFO] START OF SCRIPT" | Out-File -filepath $migrationLogFileName -Force
############## Check Prerequisites.
Check-Prerequisites
############## Creates a persistent connection to a management group.
Try
{
IF ( [string]::IsNullOrEmpty($SCOMMSComputerName) )
{
New-SCOMManagementGroupConnection
}
Else
{
$SCOMCredentials = Get-Credential -Message "Enter SCOM Management Server Credentials."
New-SCOMManagementGroupConnection -ComputerName $SCOMMSComputerName -Credential $SCOMCredentials
}
}
Catch
{
LogMessage $( "Exception:- " + $_.Exception.Message ) $migrationLogFileName $FATALERROR
LogMessage "Unable to connect to SCOM. Please verify your credentials" $migrationLogFileName $FATALERROR
exit
}
############## If the Component Name is not provided in the script parameters, defaulting it to SCOM Management Group.
If ( [string]::IsNullOrEmpty($AIcomponentName) )
{
$AIcomponentName = $(Get-SCOMManagementGroup).Name
}
############## Interactive Login to connect to the Azure account.
Login-ToAzure
############## Interactive Login to connect to the Azure account.
ValidateResourceLocation
############## Getting The Azure subscription ID from the subcription name.
Try
{
Select-AzureRmSubscription -SubscriptionName $SubscriptionName
$subscription = (Get-AzureRmContext).Subscription
$subscriptionID = $subscription.Id
}
Catch
{
LogMessage $( "Exception:- " + $_.Exception.Message ) $migrationLogFileName $FATALERROR
LogMessage "Unable to get the Subscription:- $SubscriptionName from the azure account. Please verify the name of the subscription." $migrationLogFileName $FATALERROR
exit
}
############## Creates Resource group if not present.
Create-ResourceGroup
############## Get the data of the ping test from operations manager and then create the test in Application Insights
If ( (( $FailedPingTestsInstanceIds.count -eq 0 ) -and ( $FailedMultiStepTestsInstanceIds.count -eq 0 )) -or ($FailedPingTestsInstanceIds.count -ne 0) )
{
$pingWebTests = Build-PingTest $FailedPingTestsInstanceIds
$allWebTestsToMigrate += $pingWebTests
}
############## Get the data of the multi step test from operations manager and then create the test in Application Insights
If ( (( $FailedPingTestsInstanceIds.count -eq 0 ) -and ( $FailedMultiStepTestsInstanceIds.count -eq 0 )) -or ($FailedMultiStepTestsInstanceIds.count -ne 0) )
{
$multiStepWebTests = Build-MultiStepTest $FailedMultiStepTestsInstanceIds
$allWebTestsToMigrate += $multiStepWebTests
}
############## Create Ping and MultiStep Tests in Azure Application Insights.
Create-WebTest $allWebTestsToMigrate
############## Logout the session.
Disconnect-AzureRmAccount
############## Deletes persistent connections to management groups.
Get-SCOMManagementGroupConnection |?{$_.IsActive } | Remove-SCOMManagementGroupConnection
############## All the Log files of the migrated tests.
$migratedTestsLogFiles = get-childitem -Path $testLogFilePath | where-object {$_.Name -like "*-Migrated*"}
If ( $migratedTestsLogFiles )
{
LogMessage $( "Logs for all the tests we successfully migrated to Azure App Insights :-" + $migratedTestsLogFiles ) $migrationLogFileName $INFORMATION
Write-Host "Logs for all the tests we successfully migrated to Azure App Insights :-" $migratedTestsLogFiles -ForegroundColor Green
}
############## All the Log files of the failed tests.
$failedTestsLogFiles = get-childitem -Path $testLogFilePath | where-object {$_.Name -like "*-Failed*"}
If ( $failedTestsLogFiles )
{
LogMessage $( "Logs of all the tests which are failed to migrate to Azure App Insights :-" + $failedTestsLogFiles ) $migrationLogFileName $INFORMATION
Write-Host "Logs of all the tests which are failed to migrate to Azure App Insights :-" $failedTestsLogFiles -ForegroundColor Red
}
############## All the Log files of the duplicate tests.
$duplicateTestsLogFiles = get-childitem -Path $testLogFilePath | where-object {$_.Name -like "*-DuplicateTest*"}
If ( $duplicateTestsLogFiles )
{
LogMessage $( "Please refer the logs :- " + $duplicateTestsLogFiles + " to get all the tests which are duplicate, please rename them and re-migrate." ) $migrationLogFileName $INFORMATION
Write-Host "Please refer the logs :- " $duplicateTestsLogFiles " to get all the tests which are duplicate, please rename them and re-migrate." -ForegroundColor Red
}
LogMessage "[INFO] END OF SCRIPT" $migrationLogFileName $INFORMATION
############## Please verify the web test should get created on the Application Insights.
############## END OF SCRIPT ##############
# SIG # Begin signature block
# MIIkmgYJKoZIhvcNAQcCoIIkizCCJIcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD+JCbmHCRozGmo
# bLxC0111LvTnQmJ9bybdN3/mwuPfOqCCDYEwggX/MIID56ADAgECAhMzAAABA14l
# HJkfox64AAAAAAEDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTgwNzEyMjAwODQ4WhcNMTkwNzI2MjAwODQ4WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDRlHY25oarNv5p+UZ8i4hQy5Bwf7BVqSQdfjnnBZ8PrHuXss5zCvvUmyRcFrU5
# 3Rt+M2wR/Dsm85iqXVNrqsPsE7jS789Xf8xly69NLjKxVitONAeJ/mkhvT5E+94S
# nYW/fHaGfXKxdpth5opkTEbOttU6jHeTd2chnLZaBl5HhvU80QnKDT3NsumhUHjR
# hIjiATwi/K+WCMxdmcDt66VamJL1yEBOanOv3uN0etNfRpe84mcod5mswQ4xFo8A
# DwH+S15UD8rEZT8K46NG2/YsAzoZvmgFFpzmfzS/p4eNZTkmyWPU78XdvSX+/Sj0
# NIZ5rCrVXzCRO+QUauuxygQjAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUR77Ay+GmP/1l1jjyA123r3f3QP8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDM3OTY1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn/XJ
# Uw0/DSbsokTYDdGfY5YGSz8eXMUzo6TDbK8fwAG662XsnjMQD6esW9S9kGEX5zHn
# wya0rPUn00iThoj+EjWRZCLRay07qCwVlCnSN5bmNf8MzsgGFhaeJLHiOfluDnjY
# DBu2KWAndjQkm925l3XLATutghIWIoCJFYS7mFAgsBcmhkmvzn1FFUM0ls+BXBgs
# 1JPyZ6vic8g9o838Mh5gHOmwGzD7LLsHLpaEk0UoVFzNlv2g24HYtjDKQ7HzSMCy
# RhxdXnYqWJ/U7vL0+khMtWGLsIxB6aq4nZD0/2pCD7k+6Q7slPyNgLt44yOneFuy
# bR/5WcF9ttE5yXnggxxgCto9sNHtNr9FB+kbNm7lPTsFA6fUpyUSj+Z2oxOzRVpD
# MYLa2ISuubAfdfX2HX1RETcn6LU1hHH3V6qu+olxyZjSnlpkdr6Mw30VapHxFPTy
# 2TUxuNty+rR1yIibar+YRcdmstf/zpKQdeTr5obSyBvbJ8BblW9Jb1hdaSreU0v4
# 6Mp79mwV+QMZDxGFqk+av6pX3WDG9XEg9FGomsrp0es0Rz11+iLsVT9qGTlrEOla
# P470I3gwsvKmOMs1jaqYWSRAuDpnpAdfoP7YO0kT+wzh7Qttg1DO8H8+4NkI6Iwh
# SkHC3uuOW+4Dwx1ubuZUNWZncnwa6lL2IsRyP64wggd6MIIFYqADAgECAgphDpDS
# 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/BvW1taslScxMNelDNMYIWbzCCFmsCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAQNeJRyZH6MeuAAAAAABAzAN
# BglghkgBZQMEAgEFAKCB2DAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgcFbsP8Rq
# TSsOVfu+s8r3OJ8ZVdTwo/Cd7Hg54yWTjugwbAYKKwYBBAGCNwIBDDFeMFygMIAu
# AE0AaQBjAHIAbwBzAG8AZgB0ACAAUwB5AHMAdABlAG0AIABDAGUAbgB0AGUAcqEo
# gCZodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vU3lzdGVtQ2VudGVyIDANBgkqhkiG
# 9w0BAQEFAASCAQADnpFjiTloqk5V9kfRV+UsXvR7AW/NuPscz0F+o3bye8v3K7Sh
# 0LH+fUE/TWL7IoP62ebbebp0lPCISiauOmkrKUM7CLNsnGE0W1d+yu4Dez467lMh
# Y4xD5HiwPJxbhU5RYpoTvvqBtFIxRZQfQJVK8hTehtO9lYqTHZN+HAbxjPs3FwF5
# gLW7a0IohuP9ebPDgZV/kYzDWHptjI8iIGN1xfZK5vaKXM5CG/QHpKtNXHyjaFRi
# 0xdtmtdIEPZDWpgdfF9w2lEorM2lkSSjxL8ABUsj5rR7FGF9qu4MHDqU3UNuMFHs
# MRj4B1g+CpGVqHp+YVb8j/Tl2GSZvwqL/iZtoYITzzCCE8sGCisGAQQBgjcDAwEx
# ghO7MIITtwYJKoZIhvcNAQcCoIITqDCCE6QCAQMxDzANBglghkgBZQMEAgEFADCC
# AVgGCyqGSIb3DQEJEAEEoIIBRwSCAUMwggE/AgEBBgorBgEEAYRZCgMBMDEwDQYJ
# YIZIAWUDBAIBBQAEIOp0tHy2xDS2PWFabVsBgwP6Xi0db7rmpAprihmc079ZAgZb
# iBHO5a8YEzIwMTgwOTEwMTAwNDIwLjc3MVowBwIBAYACAfSggdSkgdEwgc4xCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jv
# c29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNT
# IEVTTjo5OEZELUM2MUUtRTY0MTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgU2VydmljZaCCDx8wggT1MIID3aADAgECAhMzAAAAy194yyMOlJfDAAAAAADL
# MA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4X
# DTE4MDgyMzIwMjYyNFoXDTE5MTEyMzIwMjYyNFowgc4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRp
# b25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo5OEZELUM2
# MUUtRTY0MTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMV4yB3v8B1BcBxtNEo/VALK
# GnizA1WCEIU22DCpyy838O0VlW7D3KUomZPIU3nsx3MxaQXpai0OiVs+DPuHqdoK
# tsuYCaMxeDHhodCgPWdPT9NN0hngnC07R2nDB2NhvtRBpr4V36791Pqi3CssKDdL
# jBrOQUhqEn8S0VP5xldDQPfMIpqRFQdP6Ut4dvaI/Mva5e86HbawJxdGKrTdHp7L
# Oae3YHX25khbhuNatqp3dDu3Do6xDE1BIa2GuUGZa4oHVNwWIWk3SZ4xZlarT3eA
# i712yWyeTrjGv56Ryje8yDiBtd+1UCn67t0TwQpTa+a2ZPP2v8HyQxQegc+9ThUC
# AwEAAaOCARswggEXMB0GA1UdDgQWBBQo5PLm9snRTa5uyNsqlr8xw/vZdjAfBgNV
# HSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBHhkVo
# dHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUaW1T
# dGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAC
# hj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0YVBD
# QV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUF
# BwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCL9GGFgwVibMsUlJfD6SUDbHKxL9pN6ZYM
# g+aOTE8AyCh9oD6HcuinUjkj6afQU63TvgVRWExYJLzrQBysAh2GgbGkKIPtdV6y
# QQMlJxclXpR48t1jS1VvBX0KksR5Bq/4/0e58+jXvUaU2JcUQVw3lHn9I/YtQJeu
# AvnNfLENxJKE3A7FOjOAw+fEH49OGK1IBR9yhXS+r6HslFuFLfjK7DU89+Cu1zAg
# 9JTCCrqlWSydWApAYh/ACInONLHHp9OZdilC42zGjB8Ro/07YqMAjPhK7Ze12lWT
# hiZIFqc5fZTxCi3L2T8pQI91/Nxu4CnpIzLXUwSXUxkIpfSNsK7OMIIGcTCCBFmg
# AwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3Qg
# Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcNMjUw
# NzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIwDQYJ
# KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0VBDV
# pQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEwRA/x
# YIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQedGFn
# kV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKxXf13
# Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4GkbaI
# CDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEAAaOC
# AeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7fEYb
# xTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYw
# DwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoY
# xDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp
# L2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYB
# BQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
# cGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0gAQH/
# BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYBBQUH
# AgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBl
# AG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOhIW+z
# 66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS+7lT
# jMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlKkVIA
# rzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon/VWv
# L/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOiPPp/
# fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/fmNZ
# JQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCIIYdqw
# UB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0cs0d
# 9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7aKLix
# qduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQcdeh
# 0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+NR4I
# uto229Nfj950iEkSoYIDrTCCApUCAQEwgf6hgdSkgdEwgc4xCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo5OEZE
# LUM2MUUtRTY0MTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZaIlCgEBMAkGBSsOAwIaBQADFQC5o5PSQHbRtx8VowRRl644K9uaIaCB3jCB26SB
# 2DCB1TELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UE
# CxMgTWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJzAlBgNVBAsTHm5D
# aXBoZXIgTlRTIEVTTjo1N0Y2LUMxRTAtNTU0QzErMCkGA1UEAxMiTWljcm9zb2Z0
# IFRpbWUgU291cmNlIE1hc3RlciBDbG9jazANBgkqhkiG9w0BAQUFAAIFAN9Ad/Aw
# IhgPMjAxODA5MTAxMjU2MTZaGA8yMDE4MDkxMTEyNTYxNlowdDA6BgorBgEEAYRZ
# CgQBMSwwKjAKAgUA30B38AIBADAHAgEAAgIU6zAHAgEAAgIWpDAKAgUA30HJcAIB
# ADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMBoAowCAIBAAIDFuNgoQow
# CAIBAAIDB6EgMA0GCSqGSIb3DQEBBQUAA4IBAQCQNbEJlYUpSLy39rCmIhHOxn6L
# 7dxJ+UtC0IokKI4JYm7wgV9d8tBe0iGbA1IZdR1lfnt7iPGXQDwyFvc7MA2Ndu/V
# DrruUiZtLbQ87vAaxwCQ/RdJHkIoJjjOIDv9UKwkyH1MOtuuf49VX+ZYvF7tVz3S
# HChsPffGG3WpN/3lL/PyGdeaCWvc4qzDTfc8PnXXHSpI8fc56h32HfsUEzHXuidT
# d6iakoZkkfmqVsx9VrIDvKiPSiigU90w0W1GUheY/9VKOrrsH7Sg9JbR1bpcwABq
# Y140G1cULPhK7SGF6KE333Jm5Wixzu2ePTDTfORyM3Fij/3OIV8ZPXHKtdACMYID
# DTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAADL
# X3jLIw6Ul8MAAAAAAMswDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzEN
# BgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgmJ7xUUlWd+g3nNKvMH81Vw+I
# l6RYuBy6iZYk6Ivvo3IwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCA2JyGq
# qWCnXutz0KS9S3wuF/afS9Mu7hRHXqpg3cEdZDCBmDCBgKR+MHwxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAAy194yyMOlJfDAAAAAADLMCIEIF94UIgv
# XMNDmgF6TuOsuWjS0VkxLAmBopLCk2o1lLkYMA0GCSqGSIb3DQEBCwUABIIBAI47
# W5ne9e0dEip8s/wpGkUfLQ8LaSOCQkyHkySnrLrUFoJnBjUQKkV1CrgDHi/1F4HT
# ylZsyj51qNXEaPLr/CofgP4SAMqcS7N4JirL3F6PRuCkKafOk0phbvd/LiBjF67V
# 4wzKm9T19Y4X2q31MXiXBWnrNK1r1UeLpfHL+s4maMc85emWOzXJhXlcxQrtcrsI
# 5Gnsuxyg2IswI+s2X2a9h3gbz2VrsRhMABc8AY9pEDqOtuz5woUAXKO8RDEqT0C6
# tY78isSqyXYLZVZ25rydOugCCbkbDN02RpEnzQ1gnPN9LPSIXhE7kK9tMCZedByr
# rpxuUdgaKIm63sBo56U=
# SIG # End signature block