mirror of
https://github.com/Microsoft/sql-server-samples.git
synced 2025-12-08 14:58:54 +00:00
Merge pull request #1124 from lorint/fix_bad_json_creation_for_delivery_van_temperatures
Fix bad JSON creation for delivery van temperatures
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
|
||||
|
||||
|
||||
# This script checks if SQL Server is installed on Windows
|
||||
|
||||
[string] $SqlInstalled = ""
|
||||
|
||||
[string] $SqlInstalled = ""
|
||||
$regPath = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server'
|
||||
if (Test-Path $regPath) {
|
||||
$inst = (get-itemproperty $regPath).InstalledInstances
|
||||
@@ -10,7 +10,7 @@
|
||||
foreach ($i in $inst) {
|
||||
# Read registry data
|
||||
#
|
||||
$p = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$i
|
||||
$p = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$i
|
||||
$setupValues = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup")
|
||||
$edition = ($setupValues.Edition -split ' ')[0]
|
||||
$version = ($setupValues.Version)
|
||||
|
||||
@@ -5,15 +5,15 @@ Contains samples for managing Microsoft's SQL databases including SQL Server, Az
|
||||
## Automatically Export your databases with Azure Automation
|
||||
This includes samples for setting up Azure Automation and exporting your databases to azure blob storage.
|
||||
|
||||
## Collect and monitor resource usage data across multiple pools in a subscription
|
||||
This Solution Quick Start provides a solution for collecting and monitoring Azure SQL Database resource usage across multiple pools in a subscription. When you have a large number of databases in a subscription, it is cumbersome to monitor each elastic pool separately. To solve this, you can combine SQL database PowerShell cmdlets and T-SQL queries to collect resource usage data from multiple pools and their databases for monitoring and analysis of resource usage.
|
||||
## Collect and monitor resource usage data across multiple pools in a subscription
|
||||
This Solution Quick Start provides a solution for collecting and monitoring Azure SQL Database resource usage across multiple pools in a subscription. When you have a large number of databases in a subscription, it is cumbersome to monitor each elastic pool separately. To solve this, you can combine SQL database PowerShell cmdlets and T-SQL queries to collect resource usage data from multiple pools and their databases for monitoring and analysis of resource usage.
|
||||
|
||||
[Manage Multiple Elastic Pools in SQL Database Using PowerShell and Power BI](https://github.com/Microsoft/sql-server-samples/tree/master/samples/manage/azure-sql-db-elastic-pools) in the GitHub SQL Server samples repository provides a set of powershell scripts and T-SQL queries along with documentation on what it does and how to use it.
|
||||
[Manage Multiple Elastic Pools in SQL Database Using PowerShell and Power BI](https://github.com/Microsoft/sql-server-samples/tree/master/samples/manage/azure-sql-db-elastic-pools) in the GitHub SQL Server samples repository provides a set of powershell scripts and T-SQL queries along with documentation on what it does and how to use it.
|
||||
|
||||
## Get started using Elastic Pools in a SaaS scenario
|
||||
This Solution Quick Start provides a solution for a Software-as-a-Solution (SaaS) scenario that leverages Elastic Pools to provide a cost-effective, scalable database back-end of a SaaS application. In this solution, you will walk-though the implementation of a web app that lets you visualize the load created on an Elastic Pool by a load generator using a custom dashboard that supplements the Azure Portal.
|
||||
## Get started using Elastic Pools in a SaaS scenario
|
||||
This Solution Quick Start provides a solution for a Software-as-a-Solution (SaaS) scenario that leverages Elastic Pools to provide a cost-effective, scalable database back-end of a SaaS application. In this solution, you will walk-though the implementation of a web app that lets you visualize the load created on an Elastic Pool by a load generator using a custom dashboard that supplements the Azure Portal.
|
||||
|
||||
[saas-scenario-with-elastic-pools](https://github.com/Microsoft/sql-server-samples/tree/master/samples/manage/azure-sql-db-elastic-pools-custom-dashboard) in the GitHub SQL Server samples repository provides a load generator and monitoring web app along with the documentation on what it does and how to use it.
|
||||
[saas-scenario-with-elastic-pools](https://github.com/Microsoft/sql-server-samples/tree/master/samples/manage/azure-sql-db-elastic-pools-custom-dashboard) in the GitHub SQL Server samples repository provides a load generator and monitoring web app along with the documentation on what it does and how to use it.
|
||||
|
||||
## Windows Containers
|
||||
This includes samples for setting up mssql-server in Windows Containers. Currently it only includes a link to the separately maintained [mssql-docker](https://github.com/Microsoft/mssql-docker/blob/master/windows/README.md) instructions.
|
||||
|
||||
@@ -18,7 +18,7 @@ Use the following steps to migrate your existing SQL Server - Azure Arc resource
|
||||
curl https://raw.githubusercontent.com/microsoft/sql-server-samples/master/samples/manage/azure-arc-enabled-sql-server/migrate-to-azure-arc-data.ps1 -o migrate-to-azure-arc-data.ps1
|
||||
```
|
||||
|
||||
3. Run the script.
|
||||
3. Run the script.
|
||||
|
||||
```console
|
||||
./migrate-to-azure-arc-data.ps1
|
||||
|
||||
@@ -21,7 +21,7 @@ $ResourceGroup=read-host -Prompt "Enter Resource Group Name"
|
||||
$SqlArcResources = Get-AzResource -ExpandProperties -ResourceType Microsoft.AzureData/sqlServerInstances -ResourceGroupName $ResourceGroup
|
||||
foreach ($r in $SqlArcResources) {
|
||||
Write-Host ("Migrating resource: {0}" -f $r.Name)
|
||||
|
||||
|
||||
if ( ! ($r.Properties.containerResourceId -match "Microsoft.HybridCompute/machines") ) {
|
||||
$arcResource = Get-AzResource -ResourceType Microsoft.HybridCompute/machines -Name $r.Properties.containerResourceId
|
||||
if($null -eq $arcResource) {
|
||||
|
||||
@@ -10,19 +10,19 @@ ms.date: 2/16/2023
|
||||
# Overview
|
||||
|
||||
This script allows you to to set or change the license type on all Azure-connected SQL Servers
|
||||
on a specific resource, in a single resource group, a specific subscription, a list of subscriptions or all subscriptions to which you have access. By default, it sets the specified license type value on the servers where it is undefined. But you can request to set it on all servers in the selected scope.
|
||||
on a specific resource, in a single resource group, a specific subscription, a list of subscriptions or all subscriptions to which you have access. By default, it sets the specified license type value on the servers where it is undefined. But you can request to set it on all servers in the selected scope.
|
||||
|
||||
You can specify a single subscription to scan, or provide a list of subscriptions as a .CSV file.
|
||||
You can specify a single subscription to scan, or provide a list of subscriptions as a .CSV file.
|
||||
If not specified, all subscriptions your role has access to are scanned.
|
||||
|
||||
If the license type is not specified, the value "Paid" is used.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
- You must have at least a *Contributor* role in each subscription you modify.
|
||||
- You must have at least a *Contributor* role in each subscription you modify.
|
||||
- The Azure extension for SQL Server is updated to version 1.1.2230.58 or newer.
|
||||
|
||||
# Launching the script
|
||||
# Launching the script
|
||||
|
||||
The script accepts the following command line parameters:
|
||||
|
||||
@@ -36,7 +36,7 @@ The script accepts the following command line parameters:
|
||||
|
||||
<sup>1</sup>You can create a .csv file using the following command and then edit to remove the subscriptions you don't want to scan.
|
||||
```PowerShell
|
||||
Get-AzSubscription | Export-Csv .\mysubscriptions.csv -NoTypeInformation
|
||||
Get-AzSubscription | Export-Csv .\mysubscriptions.csv -NoTypeInformation
|
||||
```
|
||||
|
||||
## Example 1
|
||||
@@ -44,7 +44,7 @@ Get-AzSubscription | Export-Csv .\mysubscriptions.csv -NoTypeInformation
|
||||
The following command will scan all the subscriptions to which the user has access to, and set the license type to "Paid" on all servers where license type is undefined.
|
||||
|
||||
```PowerShell
|
||||
.\modify-license-type.ps1 -LicenseType Paid
|
||||
.\modify-license-type.ps1 -LicenseType Paid
|
||||
```
|
||||
|
||||
## Example 2
|
||||
@@ -75,10 +75,10 @@ This option is recommended because Cloud shell has the Azure PowerShell modules
|
||||
curl https://raw.githubusercontent.com/microsoft/sql-server-samples/master/samples/manage/azure-arc-enabled-sql-server/modify-license-type/modify-license-type.ps1 -o modify-license-type.ps1
|
||||
```
|
||||
|
||||
3. Run the script.
|
||||
3. Run the script.
|
||||
|
||||
```console
|
||||
.//modify-license-type.ps1 -LicenseType Paid
|
||||
.//modify-license-type.ps1 -LicenseType Paid
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -96,14 +96,14 @@ Use the following steps to run the script in a PowerShell session on your PC.
|
||||
curl https://raw.githubusercontent.com/microsoft/sql-server-samples/master/samples/manage/azure-arc-enabled-sql-server/modify-license-type/modify-license-type.ps1 -o modify-license-type.ps1
|
||||
```
|
||||
|
||||
1. Make sure the NuGet package provider is installed:
|
||||
1. Make sure the NuGet package provider is installed:
|
||||
|
||||
```console
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
Install-packageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force
|
||||
Install-packageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force
|
||||
```
|
||||
|
||||
1. Make sure the the Az module is installed. For more information, see [Install the Azure Az PowerShell module](https://learn.microsoft.com/powershell/azure/install-az-ps):
|
||||
1. Make sure the the Az module is installed. For more information, see [Install the Azure Az PowerShell module](https://learn.microsoft.com/powershell/azure/install-az-ps):
|
||||
|
||||
```console
|
||||
Install-Module Az -Scope CurrentUser -Repository PSGallery -Force
|
||||
@@ -115,8 +115,8 @@ Use the following steps to run the script in a PowerShell session on your PC.
|
||||
Connect-AzAccount <parameters>
|
||||
```
|
||||
|
||||
1. Run the script using the desired scope.
|
||||
1. Run the script using the desired scope.
|
||||
|
||||
```console
|
||||
.//modify-license-type.ps1 -LicenseType Paid
|
||||
.//modify-license-type.ps1 -LicenseType Paid
|
||||
```
|
||||
|
||||
+33
-33
@@ -1,36 +1,36 @@
|
||||
#
|
||||
# This script provides a scaleable solution to set or change the license type on all Azure-connected SQL Servers
|
||||
# in a specific subscription, a list of subscruiptions or the entire account. By default, it sets the new license
|
||||
# type value only on the servers where it is undefined.
|
||||
# in a specific subscription, a list of subscruiptions or the entire account. By default, it sets the new license
|
||||
# type value only on the servers where it is undefined.
|
||||
#
|
||||
# You can specfy a single subscription to scan, or provide subscriptions as a .CSV file with the list of IDs.
|
||||
# If not specified, all subscriptions your role has access to are scanned.
|
||||
# You can specfy a single subscription to scan, or provide subscriptions as a .CSV file with the list of IDs.
|
||||
# If not specified, all subscriptions your role has access to are scanned.
|
||||
#
|
||||
# The script accepts the following command line parameters:
|
||||
#
|
||||
#
|
||||
# -SubId [subscription_id] | [csv_file_name] (Limit scope to specific subscriptions. Accepts a .csv file with the list of subscriptions.
|
||||
# If not specified all subscriptions will be scanned)
|
||||
# -ResourceGroup [resource_goup] (Limit scope to a specific resoure group)
|
||||
# -MachineName [machine_name] (Limit scope to a specific machine)
|
||||
# -LicenseType [license_type_value] (Specific LT value)
|
||||
# -All (Required. Set the new license type on all installed extensions.
|
||||
# -All (Required. Set the new license type on all installed extensions.
|
||||
# By default the value is set only if license type is undefined undefined)
|
||||
#
|
||||
#
|
||||
# The script uses a function ConvertTo-HashTable that was created by Adam Bertram (@adam-bertram).
|
||||
# The function was originally published on https://4sysops.com/archives/convert-json-to-a-powershell-hash-table/
|
||||
# and is used here with the author's permission.
|
||||
#
|
||||
|
||||
param (
|
||||
[Parameter (Mandatory=$false)]
|
||||
[string] $SubId,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $ResourceGroup,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $MachineName,
|
||||
[Parameter (Mandatory=$false)]
|
||||
[string] $SubId,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $ResourceGroup,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $MachineName,
|
||||
[Parameter (Mandatory= $true)]
|
||||
[ValidateSet("PAYG","Paid","LicenseOnly", IgnoreCase=$false)]
|
||||
[string] $LicenseType,
|
||||
[string] $LicenseType,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[boolean] $All=$false
|
||||
)
|
||||
@@ -43,7 +43,7 @@ function CheckModule ($m) {
|
||||
if (!(Get-Module | Where-Object {$_.Name -eq $m})) {
|
||||
# If module is not imported, but available on disk then import
|
||||
if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) {
|
||||
Import-Module $m
|
||||
Import-Module $m
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -86,7 +86,7 @@ function ConvertTo-Hashtable {
|
||||
)
|
||||
## Return the array but don't enumerate it because the object may be pretty complex
|
||||
Write-Output -NoEnumerate $collection
|
||||
} elseif ($InputObject -is [psobject]) {
|
||||
} elseif ($InputObject -is [psobject]) {
|
||||
## If the object has properties that need enumeration, cxonvert it to its own hash table and return it
|
||||
$hash = @{}
|
||||
foreach ($property in $InputObject.PSObject.Properties) {
|
||||
@@ -119,7 +119,7 @@ $requiredModules | Foreach-Object {CheckModule $_}
|
||||
if ($SubId -like "*.csv") {
|
||||
$subscriptions = Import-Csv $SubId
|
||||
}elseif($SubId -ne $null){
|
||||
$subscriptions = [PSCustomObject]@{SubscriptionId = $SubId} | Get-AzSubscription
|
||||
$subscriptions = [PSCustomObject]@{SubscriptionId = $SubId} | Get-AzSubscription
|
||||
}else{
|
||||
$subscriptions = Get-AzSubscription
|
||||
}
|
||||
@@ -127,19 +127,19 @@ if ($SubId -like "*.csv") {
|
||||
|
||||
Write-Host ([Environment]::NewLine + "-- Scanning subscriptions --")
|
||||
|
||||
# Scan arc-enabled servers in each subscription
|
||||
# Scan arc-enabled servers in each subscription
|
||||
|
||||
foreach ($sub in $subscriptions){
|
||||
|
||||
if ($sub.State -ne "Enabled") {continue}
|
||||
|
||||
try {
|
||||
Set-AzContext -SubscriptionId $sub.Id
|
||||
Set-AzContext -SubscriptionId $sub.Id
|
||||
}catch {
|
||||
write-host "Invalid subscription: $($sub.Id)"
|
||||
{continue}
|
||||
}
|
||||
|
||||
|
||||
$query = "
|
||||
resources
|
||||
| where type =~ 'microsoft.hybridcompute/machines/extensions'
|
||||
@@ -149,40 +149,40 @@ foreach ($sub in $subscriptions){
|
||||
| parse id with * '/providers/Microsoft.HybridCompute/machines/' machineName '/extensions/' *
|
||||
| project machineName, extensionName = name, resourceGroup, location, subscriptionId, extensionPublisher, extensionType, properties
|
||||
"
|
||||
|
||||
if ($MachineName) {$query += "| where machineName =~ '$($MachineName)'"}
|
||||
|
||||
if ($MachineName) {$query += "| where machineName =~ '$($MachineName)'"}
|
||||
if ($ResourceGroup) {$query += "| where resourceGroup =~ '$($ResourceGroup)'"}
|
||||
|
||||
$resources = Search-AzGraph -Query "$($query) | where subscriptionId =~ '$($sub.Id)'"
|
||||
foreach ($r in $resources) {
|
||||
|
||||
$setID = @{
|
||||
MachineName = $r.MachineName
|
||||
Name = $r.extensionName
|
||||
ResourceGroup = $r.resourceGroup
|
||||
Location = $r.location
|
||||
MachineName = $r.MachineName
|
||||
Name = $r.extensionName
|
||||
ResourceGroup = $r.resourceGroup
|
||||
Location = $r.location
|
||||
SubscriptionId = $r.subscriptionId
|
||||
Publisher = $r.extensionPublisher
|
||||
ExtensionType = $r.extensionType
|
||||
ExtensionType = $r.extensionType
|
||||
}
|
||||
|
||||
$settings = @{}
|
||||
$settings = $r.properties.settings | ConvertTo-Json | ConvertFrom-Json | ConvertTo-Hashtable
|
||||
|
||||
|
||||
if ($settings.ContainsKey("LicenseType")) {
|
||||
if ($All) {
|
||||
if ($settings["LicenseType"] -ne $LicenseType ) {
|
||||
$settings["LicenseType"] = $LicenseType
|
||||
$settings["LicenseType"] = $LicenseType
|
||||
Write-Host "Resource group: [$($r.resourceGroup)] Connected machine: [$($r.MachineName)] : License type: [$($settings["LicenseType"])]"
|
||||
Set-AzConnectedMachineExtension @setId -Settings $settings -NoWait | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$settings["LicenseType"] = $LicenseType
|
||||
$settings["LicenseType"] = $LicenseType
|
||||
Write-Host "Resource group: [$($r.resourceGroup)] Connected machine: [$($r.MachineName)] : License type: [$($settings["LicenseType"])]"
|
||||
Set-AzConnectedMachineExtension @setId -Settings $settings -NoWait | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ function CreateDatabaseObject($databaseName, $serverName)
|
||||
$dbObj = New-Object System.Object;
|
||||
# Add the DatabaseName property and set it.
|
||||
$dbObj | Add-Member -type NoteProperty -name DatabaseName -Value $databaseName;
|
||||
# Add a unique time at the end of DatabaseCopyName so that we have a unique database name every time.
|
||||
# Add a unique time at the end of DatabaseCopyName so that we have a unique database name every time.
|
||||
$currentTime = Get-Date -format "_yyyy-MM-dd_HH:mm.ss";
|
||||
$dbCopyName = $databaseName + $currentTime;
|
||||
# Add the DatabaseCopyName property and set it.
|
||||
@@ -297,7 +297,7 @@ for($currentRun = 0; $currentRun -lt ([math]::Ceiling($databaseServerPairs.Lengt
|
||||
for($currentIndex; $currentIndex -lt $global:databaseServerPairs.Length -and $currentIndex -lt ($currentRun*$batchingLimit + $batchingLimit); $currentIndex++)
|
||||
{
|
||||
$global:dbs.Add((CreateDatabaseObject $global:databaseServerPairs[$currentIndex].DatabaseName $global:databaseServerPairs[$currentIndex].ServerName))
|
||||
}
|
||||
}
|
||||
|
||||
# Continually call ExportProcess until all of the database objects have been removed from the array.
|
||||
while($global:dbs.Count -gt 0)
|
||||
|
||||
@@ -48,7 +48,7 @@ Save the AutoExport.ps1 and AutoExportBlobRetention.ps1 files locally to make th
|
||||
- **$waitTimeInMinutes:** This tells the script how long it can wait for an operation to complete before it fails.
|
||||
- **$storageKeyVariableName:** This is the Azure Automation string Variable name you created to store your Storage Key.
|
||||
- **$storageAccountName:** This is the name of the storage account you are exporting to.
|
||||
- **$connectionAssetName:** Connection Asset Name for Authenticating (Keep as AzureClassicRunAsConnection if you created the default RunAs accounts)
|
||||
- **$connectionAssetName:** Connection Asset Name for Authenticating (Keep as AzureClassicRunAsConnection if you created the default RunAs accounts)
|
||||
2. In AutoExportBlobRetention, here are the values that need to be modified:
|
||||
- **$storageKeyVariableName:** This is the Azure Automation string Variable name you created to store your Storage Key.
|
||||
- **$storageAccountName:** This is the name of your Storage Account you exported your bacpacs to.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
|
||||
|
||||
# This script checks if SQL Server is installed on Windows
|
||||
|
||||
[string] $SqlInstalled = ""
|
||||
|
||||
[string] $SqlInstalled = ""
|
||||
$regPath = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server'
|
||||
if (Test-Path $regPath) {
|
||||
$inst = (get-itemproperty $regPath).InstalledInstances
|
||||
@@ -10,7 +10,7 @@
|
||||
foreach ($i in $inst) {
|
||||
# Read registry data
|
||||
#
|
||||
$p = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$i
|
||||
$p = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$i
|
||||
$setupValues = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup")
|
||||
$edition = ($setupValues.Edition -split ' ')[0]
|
||||
$version = ($setupValues.Version)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
if ! systemctl is-active --quiet mssql-server.service; then dir
|
||||
|
||||
echo "False"
|
||||
exit
|
||||
else
|
||||
echo "True"
|
||||
|
||||
echo "False"
|
||||
exit
|
||||
else
|
||||
echo "True"
|
||||
fi
|
||||
@@ -8,7 +8,7 @@ ms.date: 01/07/2023
|
||||
|
||||
# Overview
|
||||
|
||||
This script provides a simple solution to analyze and track the consolidated utilization of SQL Server licenses by all of the SQL resources in a specific subscription or the entire account. By default, the script scans all subscriptions the user account has access to. Alternatively, you can specify a single subscription or a .CSV file with a list of subscriptions.
|
||||
This script provides a simple solution to analyze and track the consolidated utilization of SQL Server licenses by all of the SQL resources in a specific subscription or the entire account. By default, the script scans all subscriptions the user account has access to. Alternatively, you can specify a single subscription or a .CSV file with a list of subscriptions.
|
||||
|
||||
| **Category** | **Description** |
|
||||
|:--|:--|
|
||||
@@ -29,12 +29,12 @@ This script provides a simple solution to analyze and track the consolidated uti
|
||||
|Unknown vCores|Total vCores used by Azure SQL Server resources with an unknown edition or service tier|
|
||||
|
||||
The following resources are in scope for the license utilization analysis:
|
||||
- Azure SQL databases (vCore-based purchasing model only)
|
||||
- Azure SQL databases (vCore-based purchasing model only)
|
||||
- Azure SQL elastic pools (vCore-based purchasing model only)
|
||||
- Azure SQL managed instances
|
||||
- Azure SQL instance pools
|
||||
- Azure Data Factory SSIS integration runtimes
|
||||
- SQL Servers in Azure virtual machines
|
||||
- SQL Servers in Azure virtual machines
|
||||
- SQL Servers in Azure virtual machines hosted in Azure dedicated host
|
||||
|
||||
>[!NOTE]
|
||||
@@ -45,9 +45,9 @@ The following resources are in scope for the license utilization analysis:
|
||||
|
||||
# Required permissions
|
||||
|
||||
You must be at least a *Reader* of each subscription you scan. If you report unregistered vCores, you must have the `Microsoft.Compute/virtualMachines/runCommand/action` permission. The [Virtual Machine Contributor](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#virtual-machine-contributor) role and higher levels have this permission. If you don't have this permission, the tool will shows zero value for unregistered cores.
|
||||
You must be at least a *Reader* of each subscription you scan. If you report unregistered vCores, you must have the `Microsoft.Compute/virtualMachines/runCommand/action` permission. The [Virtual Machine Contributor](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#virtual-machine-contributor) role and higher levels have this permission. If you don't have this permission, the tool will shows zero value for unregistered cores.
|
||||
|
||||
# Launching the script
|
||||
# Launching the script
|
||||
|
||||
The script accepts the following command line parameters:
|
||||
|
||||
@@ -55,7 +55,7 @@ The script accepts the following command line parameters:
|
||||
|:--|:--|:--|
|
||||
|-SubId|subscription_id *or* a file_name|Optional: subscription id or a .csv file with the list of subscriptions<sup>1</sup>|
|
||||
|-UseInRunbook| \$True or \$False (default) |Optional: must be $True when executed as a Runbook|
|
||||
|-Server|[protocol:]server[instance_name][,port]|Optional: SQL Server connection endpoint to save data to the database.<br> Must be accompanied by -Database and -Cred |
|
||||
|-Server|[protocol:]server[instance_name][,port]|Optional: SQL Server connection endpoint to save data to the database.<br> Must be accompanied by -Database and -Cred |
|
||||
|-Database|database_name|Optional: database name where data will be saved.<br> Must be accompanied by -Server and -Cred|
|
||||
|-Cred|credential_object|Optional: value of type PSCredential to securely pass database user and password|
|
||||
|-FilePath|csv_file_name|Optional: filename where the data will be saved in a .csv format. Ignored if database parameters are specified|
|
||||
@@ -63,12 +63,12 @@ The script accepts the following command line parameters:
|
||||
|
||||
<sup>1</sup>You can create a .csv file using the following command and then edit to remove the subscriptions you don't want to scan.
|
||||
```PowerShell
|
||||
Get-AzSubscription | Export-Csv .\mysubscriptions.csv -NoTypeInformation
|
||||
Get-AzSubscription | Export-Csv .\mysubscriptions.csv -NoTypeInformation
|
||||
```
|
||||
If both database parameters and *FilePath* are omitted, the script will write the results to a `.\sql-license-usage.csv` file. The file is created automatically. If the file already exists, the consecutive scans will append the results to it. If the database parameters are specified, the data will be saved in a *Usage-per-subscription* table. If the table doesn't exist, it will be created automatically.
|
||||
|
||||
>[!IMPORTANT]
|
||||
> Selecting `-ShowUnregistered` option will substantially increase the execution time, especially for the subscriptions with large numbers of virtual machines.
|
||||
> Selecting `-ShowUnregistered` option will substantially increase the execution time, especially for the subscriptions with large numbers of virtual machines.
|
||||
|
||||
## Example 1
|
||||
|
||||
@@ -92,7 +92,7 @@ The following command will scan all the subscriptions the user has access to and
|
||||
|
||||
```PowerShell
|
||||
$cred = Get-Credential
|
||||
.\sql-license-usage.ps1 -Server my-westus2-server.database.windows.net -Database sql-license-usage -Cred $cred
|
||||
.\sql-license-usage.ps1 -Server my-westus2-server.database.windows.net -Database sql-license-usage -Cred $cred
|
||||
```
|
||||
|
||||
## Example 4
|
||||
@@ -104,7 +104,7 @@ $params =@{
|
||||
Server="my-westus2-server.database.windows.net";
|
||||
Database="sql-license-usage";
|
||||
Cred=Get-Credential;
|
||||
}
|
||||
}
|
||||
.\sql-license-usage.ps1 @params
|
||||
```
|
||||
|
||||
@@ -132,7 +132,7 @@ To run the script in the Cloud Shell, use the following steps:
|
||||
|
||||
# Running the script as a Azure runbook
|
||||
|
||||
You can track your license utilization over time by running this script on schedule as a runbook. To set it up using Azure Portal, follow these steps.
|
||||
You can track your license utilization over time by running this script on schedule as a runbook. To set it up using Azure Portal, follow these steps.
|
||||
|
||||
1. Open a command shell on your device and run this command. It will copy the script to your local folder.
|
||||
```console
|
||||
@@ -159,7 +159,7 @@ curl https://raw.githubusercontent.com/microsoft/sql-server-samples/master/sampl
|
||||
- USEINRUNBOOKS. Select True to activate the logic that authenticates the runbook using the *Azure Run As Account*.
|
||||
1. Click **OK** to link to the schedule and **OK** again to create the job.
|
||||
|
||||
For more information about the runbooks, see the [Runbook tutorial](https://docs.microsoft.com/en-us/azure/automation/learn/automation-tutorial-runbook-textual-powershell)
|
||||
For more information about the runbooks, see the [Runbook tutorial](https://docs.microsoft.com/en-us/azure/automation/learn/automation-tutorial-runbook-textual-powershell)
|
||||
|
||||
>[!IMPORTANT]
|
||||
> When running the script as a runbook, it is necessary to save the data in a database so that the results could be analyzed outside of the runbook.
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
# IaaS registration status
|
||||
#
|
||||
# The script accepts the following command line parameters:
|
||||
#
|
||||
#
|
||||
# -SubId [subscription_id] (Optional. If not specified all subscription the user has access to Accepts a .csv file with the list of subscriptions)
|
||||
# -FilePath [csv_file_name] (Optional. Sprcifies a .csv file to save the data. if not specified, saves it in sql-vcm-inventory.csv)
|
||||
#
|
||||
#
|
||||
|
||||
param (
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $SubId,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $SubId,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $FilePath
|
||||
|
||||
@@ -37,7 +37,7 @@ function CheckModule ($m) {
|
||||
if (!(Get-Module | Where-Object {$_.Name -eq $m})) {
|
||||
# If module is not imported, but available on disk then import
|
||||
if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) {
|
||||
Import-Module $m
|
||||
Import-Module $m
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -58,7 +58,7 @@ function CheckModule ($m) {
|
||||
|
||||
function GetVCores {
|
||||
# This function translates each VM or Host sku type and name into vCores
|
||||
|
||||
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
@@ -66,32 +66,32 @@ function GetVCores {
|
||||
[Parameter(Mandatory)]
|
||||
[string]$name
|
||||
)
|
||||
|
||||
|
||||
if ($global:VM_SKUs.Count -eq 0){
|
||||
$global:VM_SKUs = Get-AzComputeResourceSku "westus" | where-object {$_.ResourceType -in 'virtualMachines','hostGroups/hosts'}
|
||||
}
|
||||
# Select first size and get the VCPus available
|
||||
$size_info = $global:VM_SKUs | Where-Object {$_.ResourceType.Contains($type) -and ($_.Name -eq $name)} | Select-Object -First 1
|
||||
|
||||
|
||||
# Save the VCPU count
|
||||
switch ($type) {
|
||||
"hosts" {$vcpu = $size_info.Capabilities | Where-Object {$_.name -eq "Cores"} }
|
||||
"virtualMachines" {$vcpu = $size_info.Capabilities | Where-Object {$_.name -eq "vCPUsAvailable"} }
|
||||
}
|
||||
|
||||
|
||||
if ($vcpu){
|
||||
return $vcpu.Value
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function DiscoveryOnWindows {
|
||||
|
||||
|
||||
# This script checks if SQL Server is installed on Windows
|
||||
|
||||
[string] $SqlInstalled = ""
|
||||
|
||||
[string] $SqlInstalled = ""
|
||||
$regPath = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server'
|
||||
if (Test-Path $regPath) {
|
||||
$inst = (get-itemproperty $regPath).InstalledInstances
|
||||
@@ -99,7 +99,7 @@ function DiscoveryOnWindows {
|
||||
foreach ($i in $inst) {
|
||||
# Read registry data
|
||||
#
|
||||
$p = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$i
|
||||
$p = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$i
|
||||
$setupValues = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup")
|
||||
$edition = ($setupValues.Edition -split ' ')[0]
|
||||
$version = ($setupValues.Version)
|
||||
@@ -111,15 +111,15 @@ function DiscoveryOnWindows {
|
||||
|
||||
#
|
||||
# This script checks if SQL Server is installed on Linux
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
$DiscoveryOnLinux =
|
||||
'if ! systemctl is-active --quiet mssql-server.service; then dir
|
||||
|
||||
echo "False"
|
||||
exit
|
||||
else
|
||||
echo "True"
|
||||
|
||||
echo "False"
|
||||
exit
|
||||
else
|
||||
echo "True"
|
||||
fi'
|
||||
|
||||
|
||||
@@ -150,13 +150,13 @@ New-Item -ItemType file -path DiscoverSql.sh -value $DiscoveryOnLinux -Force |
|
||||
if ($SubId -like "*.csv") {
|
||||
$subscriptions = Import-Csv $SubId
|
||||
}elseif($SubId.length -gt 0){
|
||||
$subscriptions = [PSCustomObject]@{SubscriptionId = $SubId} | Get-AzSubscription
|
||||
$subscriptions = [PSCustomObject]@{SubscriptionId = $SubId} | Get-AzSubscription
|
||||
}else{
|
||||
$subscriptions = Get-AzSubscription
|
||||
}
|
||||
|
||||
|
||||
#File setup
|
||||
#File setup
|
||||
if (!$PSBoundParameters.ContainsKey("FilePath")) {
|
||||
$FilePath = '.\sql-vm-inventory.csv'
|
||||
}
|
||||
@@ -168,51 +168,51 @@ $global:VM_SKUs = @{} # To hold the VM SKU table for future use
|
||||
|
||||
Write-Host ([Environment]::NewLine + "-- Scanning subscriptions --")
|
||||
|
||||
# Calculate usage for each subscription
|
||||
# Calculate usage for each subscription
|
||||
|
||||
foreach ($sub in $subscriptions){
|
||||
|
||||
if ($sub.State -ne "Enabled") {continue}
|
||||
|
||||
try {
|
||||
Set-AzContext -SubscriptionId $sub.Id
|
||||
Set-AzContext -SubscriptionId $sub.Id
|
||||
}catch {
|
||||
write-host "Invalid subscription: " $sub.Id
|
||||
{continue}
|
||||
}
|
||||
|
||||
# Reset the subtotals
|
||||
# Reset the subtotals
|
||||
#$subtotal.psobject.properties.name | Foreach-object {$subtotal.$_ = 0}
|
||||
|
||||
|
||||
# Get all resource groups in the subscription
|
||||
#$rgs = Get-AzResourceGroup
|
||||
|
||||
# Scan all VMs with SQL server installed using a parallel loop (up to 10 at a time).
|
||||
|
||||
# Scan all VMs with SQL server installed using a parallel loop (up to 10 at a time).
|
||||
# NOTE: ForEach-Object -Parallel requires PS v7.1 or higher
|
||||
if ($PSVersionTable.PSVersion.Major -ge 7){
|
||||
#Get-AzVM -Status | Where-Object { $_.powerstate -eq 'VM running' } | ForEach-Object -ThrottleLimit 10 -Parallel {
|
||||
Get-AzVM -Status | Where-Object { $_.powerstate -eq 'VM running' } | ForEach-Object {
|
||||
#$function:GetVCores = $using:GetVCoresDef
|
||||
#$function:GetVCores = $using:GetVCoresDef
|
||||
$SqlEdition = ''
|
||||
$SqlVersion = ''
|
||||
$vCores = GetVCores -type 'virtualMachines' -name $_.HardwareProfile.VmSize
|
||||
$sql_vm = Get-AzSqlVm -ResourceGroupName $_.ResourceGroupName -Name $_.Name -ErrorAction Ignore
|
||||
|
||||
|
||||
|
||||
|
||||
if ($sql_vm) {
|
||||
$RegStatus = 'SQL Server registered'
|
||||
$SqlEdition = $Sql_vm.Sku
|
||||
$SqlVersion = $Sql_vm.Offer
|
||||
}
|
||||
else {
|
||||
if ($_.StorageProfile.OSDisk.OSType -eq "Windows"){
|
||||
if ($_.StorageProfile.OSDisk.OSType -eq "Windows"){
|
||||
$params =@{
|
||||
ResourceGroupName = $_.ResourceGroupName
|
||||
Name = $_.Name
|
||||
CommandId = 'RunPowerShellScript'
|
||||
ScriptPath = 'DiscoverSql.ps1'
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$params =@{
|
||||
@@ -221,11 +221,11 @@ foreach ($sub in $subscriptions){
|
||||
CommandId = 'RunShellScript'
|
||||
ScriptPath = 'DiscoverSql.sh'
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
$out = Invoke-AzVMRunCommand @params
|
||||
if (!$out.Value[0].Message){
|
||||
try {
|
||||
$out = Invoke-AzVMRunCommand @params
|
||||
if (!$out.Value[0].Message){
|
||||
$RegStatus = 'SQL Server not installed'
|
||||
}
|
||||
else {
|
||||
@@ -233,21 +233,21 @@ foreach ($sub in $subscriptions){
|
||||
$RegStatus = 'SQL Server not registered'
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$RegStatus = 'No VM access'
|
||||
catch {
|
||||
$RegStatus = 'No VM access'
|
||||
}
|
||||
}
|
||||
}
|
||||
$inventoryTable += ,(@( $sub.Name, $sub.Id, $_.ResourceGroupName, $_.Name, $_.Location, $_.HardwareProfile.VmSize, $vCores, $_.StorageProfile.ImageReference.Offer, $_.StorageProfile.ImageReference.Sku, $SqlVersion, $SqlEdition, $RegStatus))
|
||||
#write-host $_.ResourceGroupName $_.Name $_.Location $_.HardwareProfile.VmSize $vCores $_.StorageProfile.ImageReference.Offer $_.StorageProfile.ImageReference.Sku $SqlVersion $SqlEdition $RegStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[system.gc]::Collect()
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Write usage data to the .csv file
|
||||
if ($FilePath){
|
||||
(ConvertFrom-Csv ($inventoryTable | %{$_ -join ','})) | Export-Csv $FilePath -NoType #-Append
|
||||
(ConvertFrom-Csv ($inventoryTable | %{$_ -join ','})) | Export-Csv $FilePath -NoType #-Append
|
||||
Write-Host ([Environment]::NewLine + "-- Added the usage data to $FilePath --")
|
||||
} else {
|
||||
Write-Host $inventoryTable
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#
|
||||
# This script provides a simple solution to analyze and track the consolidated utilization of SQL Server licenses
|
||||
# by all of the SQL resources in a specific subscription or the entire the account. By default, the script scans
|
||||
# all subscriptions the user account has access. Alternatively, you can specify a single subscription or a .CSV file
|
||||
# This script provides a simple solution to analyze and track the consolidated utilization of SQL Server licenses
|
||||
# by all of the SQL resources in a specific subscription or the entire the account. By default, the script scans
|
||||
# all subscriptions the user account has access. Alternatively, you can specify a single subscription or a .CSV file
|
||||
# with a list of subscription. The usage report includes the following information for each scanned subscription.
|
||||
#
|
||||
# The following resources are in scope for the license utilization analysis:
|
||||
# - Azure SQL databases (vCore-based purchasing model only)
|
||||
# - Azure SQL databases (vCore-based purchasing model only)
|
||||
# - Azure SQL elastic pools (vCore-based purchasing model only)
|
||||
# - Azure SQL managed instances
|
||||
# - Azure SQL instance pools
|
||||
# - Azure Data Factory SSIS integration runtimes
|
||||
# - SQL Servers in Azure virtual machines
|
||||
# - SQL Servers in Azure virtual machines
|
||||
# - SQL Servers in Azure virtual machines hosted in Azure dedicated host
|
||||
#
|
||||
# NOTE: The script does not calculate usage for Azure SQL resources that use the DTU-based purchasing model
|
||||
#
|
||||
# The script accepts the following command line parameters:
|
||||
#
|
||||
#
|
||||
# -SubId [subscription_id] | [csv_file_name] (Accepts a .csv file with the list of subscriptions)
|
||||
# -Server [protocol:]server[instance_name][,port] (Required to save data to the database)
|
||||
# -Database [database_name] (Required to save data to the database)
|
||||
@@ -24,21 +24,21 @@
|
||||
# -FilePath [csv_file_name] (Required to save data in a .csv format. Ignored if database parameters are specified)
|
||||
# -UseInRunbook [True] | [False] (Required when executed as a Runbook)
|
||||
# -ShowUnregistered [True] | [False] (Optional. If specified, checks every VM if SQL server is installed)
|
||||
#
|
||||
#
|
||||
|
||||
param (
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $SubId,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $Server,
|
||||
[string] $SubId,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[PSCredential] $Cred,
|
||||
[string] $Server,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $Database,
|
||||
[PSCredential] $Cred,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[string] $FilePath,
|
||||
[string] $Database,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[bool] $UseInRunbook = $false,
|
||||
[string] $FilePath,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[bool] $UseInRunbook = $false,
|
||||
[Parameter (Mandatory= $false)]
|
||||
[bool] $ShowNC = $false,
|
||||
[Parameter (Mandatory= $false)]
|
||||
@@ -54,7 +54,7 @@ function CheckModule ($m) {
|
||||
if (!(Get-Module | Where-Object {$_.Name -eq $m})) {
|
||||
# If module is not imported, but available on disk then import
|
||||
if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) {
|
||||
Import-Module $m
|
||||
Import-Module $m
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -75,7 +75,7 @@ function CheckModule ($m) {
|
||||
|
||||
function GetVCores {
|
||||
# This function translates each VM or Host sku type and name into vCores
|
||||
|
||||
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
@@ -83,29 +83,29 @@ function GetVCores {
|
||||
[Parameter(Mandatory)]
|
||||
[string]$name
|
||||
)
|
||||
|
||||
|
||||
if ($global:VM_SKUs.Count -eq 0){
|
||||
$global:VM_SKUs = Get-AzComputeResourceSku "westus" | where-object {$_.ResourceType -in 'virtualMachines','hostGroups/hosts'}
|
||||
}
|
||||
# Select first size and get the VCPus available
|
||||
$size_info = $global:VM_SKUs | Where-Object {$_.ResourceType.Contains($type) -and ($_.Name -eq $name)} | Select-Object -First 1
|
||||
|
||||
|
||||
# Save the VCPU count
|
||||
switch ($type) {
|
||||
"hosts" {$vcpu = $size_info.Capabilities | Where-Object {$_.name -eq "Cores"} }
|
||||
"virtualMachines" {$vcpu = $size_info.Capabilities | Where-Object {$_.name -eq "vCPUsAvailable"} }
|
||||
}
|
||||
|
||||
|
||||
if ($vcpu){
|
||||
return $vcpu.Value
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
function AddVCores {
|
||||
# This function breaks down vCores into the $subtotal columns
|
||||
|
||||
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory=$false)]
|
||||
@@ -120,45 +120,45 @@ function AddVCores {
|
||||
"BusinessCritical" {
|
||||
switch ($LicenseType) {
|
||||
"BasePrice" {$script:subtotal.ahb_ent += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_ent += $CoreCount}
|
||||
default {$script:subtotal.payg_ent += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_ent += $CoreCount}
|
||||
default {$script:subtotal.payg_ent += $CoreCount}
|
||||
}
|
||||
}
|
||||
"GeneralPurpose" {
|
||||
switch ($LicenseType) {
|
||||
"BasePrice" {$script:subtotal.ahb_std += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_std += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_std += $CoreCount}
|
||||
default {$script:subtotal.payg_std += $CoreCount}
|
||||
}
|
||||
}
|
||||
"Hyperscale" {
|
||||
switch ($LicenseType) {
|
||||
"BasePrice" {$script:subtotal.ahb_std += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_std += $CoreCount}
|
||||
default {$script:subtotal.payg_std += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_std += $CoreCount}
|
||||
default {$script:subtotal.payg_std += $CoreCount}
|
||||
}
|
||||
}
|
||||
"Enterprise" {
|
||||
switch ($LicenseType) {
|
||||
"BasePrice" {$script:subtotal.ahb_ent += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_ent += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_ent += $CoreCount}
|
||||
"AHUB" {$script:subtotal.ahb_ent += $CoreCount}
|
||||
"DR" {$script:subtotal.hadr_ent += $CoreCount}
|
||||
"PAYG" {$script:subtotal.payg_ent += $CoreCount}
|
||||
default {$script:subtotal.payg_ent += $CoreCount}
|
||||
"PAYG" {$script:subtotal.payg_ent += $CoreCount}
|
||||
default {$script:subtotal.payg_ent += $CoreCount}
|
||||
}
|
||||
}
|
||||
"Standard" {
|
||||
switch ($LicenseType) {
|
||||
"BasePrice" {$script:subtotal.ahb_std += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_std += $CoreCount}
|
||||
"LicenseIncluded" {$script:subtotal.payg_std += $CoreCount}
|
||||
"AHUB" {$script:subtotal.ahb_std += $CoreCount}
|
||||
"DR" {$script:subtotal.hadr_std += $CoreCount}
|
||||
"PAYG" {$script:subtotal.payg_std += $CoreCount}
|
||||
"PAYG" {$script:subtotal.payg_std += $CoreCount}
|
||||
default {$script:subtotal.payg_std += $CoreCount}
|
||||
}
|
||||
}
|
||||
"Developer" {
|
||||
"Developer" {
|
||||
$script:subtotal.developer += $CoreCount
|
||||
}
|
||||
"Express" {
|
||||
@@ -167,14 +167,14 @@ function AddVCores {
|
||||
default {
|
||||
$script:subtotal.unknown_tier += $CoreCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function DiscoveryOnWindows {
|
||||
|
||||
|
||||
# This script checks if SQL Server is installed on Windows
|
||||
|
||||
[bool] $SqlInstalled = $false
|
||||
|
||||
[bool] $SqlInstalled = $false
|
||||
$regPath = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server'
|
||||
if (Test-Path $regPath) {
|
||||
$inst = (get-itemproperty $regPath).InstalledInstances
|
||||
@@ -190,14 +190,14 @@ Update-AzConfig -DisplayBreakingChangeWarning $false
|
||||
|
||||
#
|
||||
# This script checks if SQL Server is installed on Linux
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
$DiscoveryOnLinux =
|
||||
'if ! systemctl is-active --quiet mssql-server.service; then
|
||||
echo "False"
|
||||
exit
|
||||
else
|
||||
echo "True"
|
||||
'if ! systemctl is-active --quiet mssql-server.service; then
|
||||
echo "False"
|
||||
exit
|
||||
else
|
||||
echo "True"
|
||||
fi'
|
||||
|
||||
|
||||
@@ -252,24 +252,24 @@ New-Item -ItemType file -path DiscoverSql.sh -value $DiscoveryOnLinux -Force |
|
||||
if ($SubId -like "*.csv") {
|
||||
$subscriptions = Import-Csv $SubId
|
||||
}elseif($SubId -ne $null){
|
||||
$subscriptions = [PSCustomObject]@{SubscriptionId = $SubId} | Get-AzSubscription
|
||||
$subscriptions = [PSCustomObject]@{SubscriptionId = $SubId} | Get-AzSubscription
|
||||
}else{
|
||||
$subscriptions = Get-AzSubscription
|
||||
}
|
||||
|
||||
[bool] $useDatabase = $PSBoundParameters.ContainsKey("Server") -and $PSBoundParameters.ContainsKey("Cred") -and $PSBoundParameters.ContainsKey("Database")
|
||||
|
||||
# Initialize tables and arrays
|
||||
# Initialize tables and arrays
|
||||
|
||||
if ($useDatabase){
|
||||
|
||||
|
||||
#Database setup
|
||||
|
||||
#$cred = New-Object System.Management.Automation.PSCredential($Username,$Password)
|
||||
|
||||
|
||||
[String] $tableName = "Usage-per-subscription"
|
||||
[String] $testSQL = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = 'dbo'
|
||||
[String] $testSQL = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = 'dbo'
|
||||
AND TABLE_NAME = '$tableName'"
|
||||
[String] $createSQL = "CREATE TABLE [dbo].[$tableName](
|
||||
[Date] [date] NOT NULL,
|
||||
@@ -304,9 +304,9 @@ if ($useDatabase){
|
||||
[Developer_vCores],
|
||||
[Express_vCores],
|
||||
[Unregistered_vCores],
|
||||
[Unknown_vCores])
|
||||
VALUES
|
||||
('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}','{13}','{14}','{15}' )"
|
||||
[Unknown_vCores])
|
||||
VALUES
|
||||
('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}','{13}','{14}','{15}' )"
|
||||
$propertiesToSplat = @{
|
||||
Database = $Database
|
||||
ServerInstance = $Server
|
||||
@@ -314,7 +314,7 @@ if ($useDatabase){
|
||||
Password = $Cred.GetNetworkCredential().Password
|
||||
Query = $testSQL
|
||||
}
|
||||
|
||||
|
||||
# Create table if does not exist
|
||||
if ((Invoke-SQLCmd @propertiesToSplat).Column1 -eq 0) {
|
||||
$propertiesToSplat.Query = $createSQL
|
||||
@@ -323,7 +323,7 @@ if ($useDatabase){
|
||||
|
||||
}else{
|
||||
|
||||
#File setup
|
||||
#File setup
|
||||
if (!$PSBoundParameters.ContainsKey("FilePath")) {
|
||||
$FilePath = '.\sql-license-usage.csv'
|
||||
}
|
||||
@@ -339,27 +339,27 @@ $subtotal = [pscustomobject]@{ahb_std=0; ahb_ent=0; payg_std=0; payg_ent=0; hadr
|
||||
|
||||
Write-Host ([Environment]::NewLine + "-- Scanning subscriptions --")
|
||||
|
||||
# Calculate usage for each subscription
|
||||
# Calculate usage for each subscription
|
||||
|
||||
foreach ($sub in $subscriptions){
|
||||
|
||||
if ($sub.State -ne "Enabled") {continue}
|
||||
|
||||
try {
|
||||
Set-AzContext -SubscriptionId $sub.Id
|
||||
Set-AzContext -SubscriptionId $sub.Id
|
||||
}catch {
|
||||
write-host "Invalid subscription: " $sub.Id
|
||||
{continue}
|
||||
}
|
||||
|
||||
# Reset the subtotals
|
||||
# Reset the subtotals
|
||||
$subtotal.psobject.properties.name | Foreach-object {$subtotal.$_ = 0}
|
||||
|
||||
|
||||
# Get all resource groups in the subscription
|
||||
$rgs = Get-AzResourceGroup
|
||||
|
||||
|
||||
# Get all logical servers
|
||||
$servers = Get-AzSqlServer
|
||||
$servers = Get-AzSqlServer
|
||||
|
||||
# Scan all vCore-based SQL database resources in the subscription
|
||||
$servers | Get-AzSqlDatabase | Where-Object { $_.SkuName -ne "ElasticPool" -and $_.Edition -in "GeneralPurpose", "BusinessCritical", "Hyperscale"} | Foreach-Object {
|
||||
@@ -378,7 +378,7 @@ foreach ($sub in $subscriptions){
|
||||
AddVCores -Tier $_.Sku.Tier -LicenseType $_.LicenseType -CoreCount $_.VCores
|
||||
}
|
||||
[system.gc]::Collect()
|
||||
|
||||
|
||||
# Scan all instance pool resources in the subscription
|
||||
Get-AzSqlInstancePool | Foreach-Object {
|
||||
AddVCores -Tier $_.Edition -LicenseType $_.LicenseType -CoreCount $_.VCores
|
||||
@@ -388,54 +388,54 @@ foreach ($sub in $subscriptions){
|
||||
# Scan all SSIS imtegration runtime resources in the subscription
|
||||
$rgs | Get-AzDataFactoryV2 | Get-AzDataFactoryV2IntegrationRuntime | Where-Object { $_.State -eq "Started" -and $_.Nodesize -ne $null } | Foreach-Object {
|
||||
$vCores = GetVCores -type "virtualMachines" -name $_.NodeSize
|
||||
AddVCores -Tier $_.Edition -LicenseType $_.LicenseType -CoreCount $vCores
|
||||
AddVCores -Tier $_.Edition -LicenseType $_.LicenseType -CoreCount $vCores
|
||||
}
|
||||
[system.gc]::Collect()
|
||||
|
||||
# Scan all VMs with SQL server installed using a parallel loop (up to 10 at a time). For that reason function AddVCores is not used
|
||||
# Scan all VMs with SQL server installed using a parallel loop (up to 10 at a time). For that reason function AddVCores is not used
|
||||
# NOTE: ForEach-Object -Parallel is not supported in Runbooks (requires PS v7.1)
|
||||
if ($PSVersionTable.PSVersion.Major -ge 7){
|
||||
$vms = Get-AzVM -Status | Where-Object { $_.powerstate -eq 'VM running' } | ForEach-Object -ThrottleLimit 10 -Parallel {
|
||||
$function:GetVCores = $using:GetVCoresDef
|
||||
$function:GetVCores = $using:GetVCoresDef
|
||||
$vCores = GetVCores -type 'virtualMachines' -name $_.HardwareProfile.VmSize
|
||||
$sql_vm = Get-AzSqlVm -ResourceGroupName $_.ResourceGroupName -Name $_.Name -ErrorAction Ignore
|
||||
|
||||
|
||||
if ($sql_vm) {
|
||||
switch ($sql_vm.Sku) {
|
||||
"Enterprise" {
|
||||
switch ($sql_vm.LicenseType) {
|
||||
"AHUB" {$($using:subtotal).ahb_ent += $vCores}
|
||||
"DR" {$($using:subtotal).hadr_ent += $vCores}
|
||||
"PAYG" {$($using:subtotal).payg_ent += $vCores}
|
||||
default {$($using:subtotal).payg_ent += $vCores}
|
||||
"PAYG" {$($using:subtotal).payg_ent += $vCores}
|
||||
default {$($using:subtotal).payg_ent += $vCores}
|
||||
}
|
||||
}
|
||||
"Standard" {
|
||||
switch ($sql_vm.LicenseType) {
|
||||
"AHUB" {$($using:subtotal).ahb_std += $vCores}
|
||||
"DR" {$($using:subtotal).hadr_std += $vCores}
|
||||
"PAYG" {$($using:subtotal).payg_std += $vCores}
|
||||
"PAYG" {$($using:subtotal).payg_std += $vCores}
|
||||
default {$($using:subtotal).payg_std += $vCores}
|
||||
}
|
||||
}
|
||||
"Developer" {
|
||||
"Developer" {
|
||||
$($using:subtotal).developer += $vCores
|
||||
}
|
||||
"Express" {
|
||||
$($using:subtotal).express += $vCores
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($($using:ShowUnregistered)){
|
||||
if ($_.StorageProfile.OSDisk.OSType -eq "Windows"){
|
||||
if ($_.StorageProfile.OSDisk.OSType -eq "Windows"){
|
||||
$params =@{
|
||||
ResourceGroupName = $_.ResourceGroupName
|
||||
Name = $_.Name
|
||||
CommandId = 'RunPowerShellScript'
|
||||
ScriptPath = 'DiscoverSql.ps1'
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$params =@{
|
||||
@@ -444,38 +444,38 @@ foreach ($sub in $subscriptions){
|
||||
CommandId = 'RunShellScript'
|
||||
ScriptPath = 'DiscoverSql.sh'
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
$out = Invoke-AzVMRunCommand @params
|
||||
if ($out.Value[0].Message.Contains('True')){
|
||||
$($using:subtotal).unreg_sqlvm += $vCores
|
||||
}
|
||||
try {
|
||||
$out = Invoke-AzVMRunCommand @params
|
||||
if ($out.Value[0].Message.Contains('True')){
|
||||
$($using:subtotal).unreg_sqlvm += $vCores
|
||||
}
|
||||
}
|
||||
catch {
|
||||
catch {
|
||||
write-host $params.Name "No acceaa"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Get-AzVM -Status | Where-Object { $_.powerstate -eq 'VM running' } | ForEach-Object {
|
||||
$vCores = GetVCores -type 'virtualMachines' -name $_.HardwareProfile.VmSize
|
||||
$sql_vm = Get-AzSqlVm -ResourceGroupName $_.ResourceGroupName -Name $_.Name -ErrorAction Ignore
|
||||
if ($sql_vm) {
|
||||
AddVCores -Tier $sql_vm.Sku -LicenseType $sql_vm.LicenseType -CoreCount $vCores
|
||||
AddVCores -Tier $sql_vm.Sku -LicenseType $sql_vm.LicenseType -CoreCount $vCores
|
||||
}
|
||||
else {
|
||||
if ($ShowUnregistered){
|
||||
if ($_.StorageProfile.OSDisk.OSType -eq "Windows"){
|
||||
if ($_.StorageProfile.OSDisk.OSType -eq "Windows"){
|
||||
$params =@{
|
||||
ResourceGroupName = $_.ResourceGroupName
|
||||
Name = $_.Name
|
||||
CommandId = 'RunPowerShellScript'
|
||||
ScriptPath = 'DiscoverSql.ps1'
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$params =@{
|
||||
@@ -484,68 +484,68 @@ foreach ($sub in $subscriptions){
|
||||
CommandId = 'RunShellScript'
|
||||
ScriptPath = 'DiscoverSql.sh'
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
}
|
||||
}try {
|
||||
$out = Invoke-AzVMRunCommand @params
|
||||
if ($out.Value[0].Message.Contains('True')){
|
||||
$subtotal.unreg_sqlvm += $vCores
|
||||
$out = Invoke-AzVMRunCommand @params
|
||||
if ($out.Value[0].Message.Contains('True')){
|
||||
$subtotal.unreg_sqlvm += $vCores
|
||||
}
|
||||
}
|
||||
catch {
|
||||
catch {
|
||||
write-host $params.Name "No acceaa"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[system.gc]::Collect()
|
||||
|
||||
# Scan the VMs hosts in the subscription
|
||||
$host_groups = Get-AzHostGroup
|
||||
$host_groups = Get-AzHostGroup
|
||||
|
||||
# Get the dedicated host size, match it with the corresponding VCPU count and add to VCore count
|
||||
|
||||
|
||||
foreach ($host_group in $host_groups){
|
||||
|
||||
|
||||
$vm_hosts = $host_group | Select-Object -Property @{Name = 'HostGroupName'; Expression = {$_.Name}},@{Name = 'ResourceGroupName'; Expression = {$_.ResourceGroupName}} | Get-AzHost
|
||||
|
||||
|
||||
foreach ($vm_host in $vm_hosts){
|
||||
|
||||
$token = (Get-AzAccessToken).Token
|
||||
$params = @{
|
||||
Uri = "https://management.azure.com/subscriptions/" + $sub.Id +
|
||||
"/resourceGroups/" + $vm_host.ResourceGroupName.ToLower() +
|
||||
"/providers/Microsoft.Compute/hostGroups/" + $host_group.Name +
|
||||
"/hosts/" + $vm_host.Name +
|
||||
Uri = "https://management.azure.com/subscriptions/" + $sub.Id +
|
||||
"/resourceGroups/" + $vm_host.ResourceGroupName.ToLower() +
|
||||
"/providers/Microsoft.Compute/hostGroups/" + $host_group.Name +
|
||||
"/hosts/" + $vm_host.Name +
|
||||
"/providers/Microsoft.SoftwarePlan/hybridUseBenefits/SQL_" + $host_group.Name + "_" + $vm_host.Name + "?api-version=2019-06-01-preview"
|
||||
Headers = @{ 'Authorization' = "Bearer $token" }
|
||||
Method = 'GET'
|
||||
ContentType = 'application/json'
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$softwarePlan = Invoke-RestMethod @params
|
||||
if ($softwarePlan.Sku.Name -like "SQL*"){
|
||||
if ($softwarePlan.Sku.Name -like "SQL*"){
|
||||
$subtotal.ahb_ent += (GetVCores -type 'hosts' -name $vm_host.Sku.Name)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
catch {
|
||||
$sub.Id
|
||||
$vm_host.ResourceGroupName.ToLower()
|
||||
$host_group.Name
|
||||
$vm_host.Name
|
||||
$params
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[system.gc]::Collect()
|
||||
|
||||
# Add subtotals to the usage array
|
||||
|
||||
|
||||
#$subtotal.psobject.properties.name | Foreach-Object {$total.$_ += $subtotal.$_}
|
||||
|
||||
|
||||
$Date = Get-Date -Format "yyy-MM-dd"
|
||||
|
||||
|
||||
$Time = Get-Date -Format "HH:mm:ss"
|
||||
if ($ShowNC){
|
||||
$ahb_nc = ($subtotal.ahb_std + $subtotal.ahb_ent*4)
|
||||
@@ -563,9 +563,9 @@ foreach ($sub in $subscriptions){
|
||||
}
|
||||
|
||||
if ($useDatabase){
|
||||
Write-Host ([Environment]::NewLine + "-- Added the usage data to $tableName table --")
|
||||
Write-Host ([Environment]::NewLine + "-- Added the usage data to $tableName table --")
|
||||
}else{
|
||||
|
||||
|
||||
# Write usage data to the .csv file
|
||||
|
||||
(ConvertFrom-Csv ($usageTable | %{$_ -join ','})) | Export-Csv $FilePath -Append -NoType
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<section name="LoadGeneratorConsole.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<startup>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
<connectionStrings>
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
|
||||
+5
-5
@@ -23,10 +23,10 @@ namespace LoadGeneratorConsole
|
||||
{
|
||||
tasks.AddRange(ScheduleLoadSpike(dbname, _numTaskPerSpike));
|
||||
}
|
||||
|
||||
|
||||
Task.WaitAll( tasks.ToArray() ) ;
|
||||
Console.WriteLine("Tasks completed.");
|
||||
|
||||
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ namespace LoadGeneratorConsole
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
SqlConnection conn = new SqlConnection(connectionString);
|
||||
|
||||
|
||||
string commandText = "INSERT [SalesLT].[SalesOrderHeader] (PurchaseOrderNumber, DueDate, CustomerID, ShipToAddressID, BillToAddressID, ShipMethod, SubTotal) " +
|
||||
"VALUES (@PoNum, @DueDate,@CustomerID, @ShipToAddressID, @BillToAddressID, @ShipMethod, @SubTotal) ";
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace LoadGeneratorConsole
|
||||
conn = new SqlConnection(connectionString);
|
||||
conn.Open();
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<SqlParameter> parameters = new List<SqlParameter>() {
|
||||
new SqlParameter("@PoNum", String.Format("PO{0}{1}", DateTime.UtcNow.ToString("yyyymmddhhmmss"), r.Next(0,256)) ),
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("LoadGeneratorConsole")]
|
||||
@@ -14,8 +14,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
@@ -25,11 +25,11 @@ using System.Runtime.InteropServices;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
|
||||
+7
-7
@@ -9,20 +9,20 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace LoadGeneratorConsole.Properties {
|
||||
|
||||
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute(@"<?xml version=""1.0"" encoding=""utf-16""?>
|
||||
@@ -37,7 +37,7 @@ namespace LoadGeneratorConsole.Properties {
|
||||
return ((global::System.Collections.Specialized.StringCollection)(this["Spike_DatabaseNames"]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("100000")]
|
||||
@@ -46,7 +46,7 @@ namespace LoadGeneratorConsole.Properties {
|
||||
return ((int)(this["Spike_NumRowsToInsert"]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("16")]
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ namespace MonitoringWebApp
|
||||
{
|
||||
}
|
||||
|
||||
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
|
||||
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
|
||||
{
|
||||
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
|
||||
// Configure validation logic for usernames
|
||||
@@ -81,7 +81,7 @@ namespace MonitoringWebApp
|
||||
var dataProtectionProvider = options.DataProtectionProvider;
|
||||
if (dataProtectionProvider != null)
|
||||
{
|
||||
manager.UserTokenProvider =
|
||||
manager.UserTokenProvider =
|
||||
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
|
||||
}
|
||||
return manager;
|
||||
|
||||
+2
-2
@@ -29,12 +29,12 @@ namespace MonitoringWebApp
|
||||
Provider = new CookieAuthenticationProvider
|
||||
{
|
||||
// Enables the application to validate the security stamp when the user logs in.
|
||||
// This is a security feature which is used when you change a password or add an external login to your account.
|
||||
// This is a security feature which is used when you change a password or add an external login to your account.
|
||||
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
|
||||
validateInterval: TimeSpan.FromMinutes(30),
|
||||
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
|
||||
}
|
||||
});
|
||||
});
|
||||
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
|
||||
|
||||
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
|
||||
|
||||
+11
-11
@@ -5,20 +5,20 @@
|
||||
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCollectorModule, Microsoft.AI.PerfCounterCollector">
|
||||
<!--
|
||||
Use the following syntax here to collect additional performance counters:
|
||||
|
||||
|
||||
<Counters>
|
||||
<Add PerformanceCounter="\Process(??APP_WIN32_PROC??)\Handle Count" ReportAs="Process handle count" />
|
||||
...
|
||||
</Counters>
|
||||
|
||||
|
||||
PerformanceCounter must be either \CategoryName(InstanceName)\CounterName or \CategoryName\CounterName
|
||||
|
||||
|
||||
Counter names may only contain letters, round brackets, forward slashes, hyphens, underscores, spaces and dots.
|
||||
You may provide an optional ReportAs attribute which will be used as the metric name when reporting counter data.
|
||||
For the purposes of reporting, metric names will be sanitized by removing all invalid characters from the resulting metric name.
|
||||
|
||||
|
||||
NOTE: performance counters configuration will be lost upon NuGet upgrade.
|
||||
|
||||
|
||||
The following placeholders are supported as InstanceName:
|
||||
??APP_WIN32_PROC?? - instance name of the application process for Win32 counters.
|
||||
??APP_W3SVC_PROC?? - instance name of the application IIS worker process for IIS/ASP.NET counters.
|
||||
@@ -31,10 +31,10 @@
|
||||
<Add Type="Microsoft.ApplicationInsights.Web.ExceptionTrackingTelemetryModule, Microsoft.AI.Web"/>
|
||||
</TelemetryModules>
|
||||
<TelemetryChannel Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel, Microsoft.AI.ServerTelemetryChannel"/>
|
||||
<!--
|
||||
Learn more about Application Insights configuration with ApplicationInsights.config here:
|
||||
<!--
|
||||
Learn more about Application Insights configuration with ApplicationInsights.config here:
|
||||
http://go.microsoft.com/fwlink/?LinkID=513840
|
||||
|
||||
|
||||
Note: If not present, please add <InstrumentationKey>Your Key</InstrumentationKey> to the top of this file.
|
||||
-->
|
||||
<TelemetryInitializers>
|
||||
@@ -50,9 +50,9 @@
|
||||
<Add Type="Microsoft.ApplicationInsights.Web.UserTelemetryInitializer, Microsoft.AI.Web"/>
|
||||
<Add Type="Microsoft.ApplicationInsights.Web.SessionTelemetryInitializer, Microsoft.AI.Web"/>
|
||||
</TelemetryInitializers>
|
||||
<!--
|
||||
Learn more about Application Insights configuration with ApplicationInsights.config here:
|
||||
<!--
|
||||
Learn more about Application Insights configuration with ApplicationInsights.config here:
|
||||
http://go.microsoft.com/fwlink/?LinkID=513840
|
||||
|
||||
|
||||
Note: If not present, please add <InstrumentationKey>Your Key</InstrumentationKey> to the top of this file.
|
||||
--></ApplicationInsights>
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
/* Override the default bootstrap behavior where horizontal description lists
|
||||
will truncate terms that are too long to fit in the left column
|
||||
/* Override the default bootstrap behavior where horizontal description lists
|
||||
will truncate terms that are too long to fit in the left column
|
||||
*/
|
||||
.dl-horizontal dt {
|
||||
white-space: normal;
|
||||
|
||||
+7
-7
@@ -34,9 +34,9 @@ namespace MonitoringWebApp.Controllers
|
||||
{
|
||||
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
|
||||
}
|
||||
private set
|
||||
{
|
||||
_signInManager = value;
|
||||
private set
|
||||
{
|
||||
_signInManager = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,9 +116,9 @@ namespace MonitoringWebApp.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
// The following code protects for brute force attacks against the two factor codes.
|
||||
// If a user enters incorrect codes for a specified amount of time then the user account
|
||||
// will be locked out for a specified amount of time.
|
||||
// The following code protects for brute force attacks against the two factor codes.
|
||||
// If a user enters incorrect codes for a specified amount of time then the user account
|
||||
// will be locked out for a specified amount of time.
|
||||
// You can configure the account lockout settings in IdentityConfig
|
||||
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
|
||||
switch (result)
|
||||
@@ -156,7 +156,7 @@ namespace MonitoringWebApp.Controllers
|
||||
if (result.Succeeded)
|
||||
{
|
||||
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
|
||||
|
||||
|
||||
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
|
||||
// Send an email with this link
|
||||
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
|
||||
|
||||
+3
-3
@@ -32,9 +32,9 @@ namespace MonitoringWebApp.Controllers
|
||||
{
|
||||
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
|
||||
}
|
||||
private set
|
||||
{
|
||||
_signInManager = value;
|
||||
private set
|
||||
{
|
||||
_signInManager = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -107,9 +107,9 @@ namespace MonitoringWebApp
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
string commandText = @"SELECT Top(1)
|
||||
end_time,
|
||||
(SELECT Max(v) FROM(VALUES(avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS value(v)) AS [avg_DTU_percent],
|
||||
string commandText = @"SELECT Top(1)
|
||||
end_time,
|
||||
(SELECT Max(v) FROM(VALUES(avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS value(v)) AS [avg_DTU_percent],
|
||||
dtu_limit
|
||||
FROM sys.dm_db_resource_stats";
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace MonitoringWebApp
|
||||
SqlDataReader reader = cmd.ExecuteReader();
|
||||
if (reader.Read())
|
||||
{
|
||||
|
||||
|
||||
result = new EDtuMetric()
|
||||
{
|
||||
EndTime = reader.GetDateTime(0),
|
||||
@@ -154,18 +154,18 @@ namespace MonitoringWebApp
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
string commandText = @"SELECT r1.database_name, r1.end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
string commandText = @"SELECT r1.database_name, r1.end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent],
|
||||
dtu_limit
|
||||
dtu_limit
|
||||
FROM sys.resource_stats r1
|
||||
JOIN (SELECT max(end_time) end_time, database_name
|
||||
FROM sys.resource_stats
|
||||
WHERE database_name in (
|
||||
SELECT d.name
|
||||
FROM sys.databases d
|
||||
JOIN sys.database_service_objectives slo
|
||||
SELECT d.name
|
||||
FROM sys.databases d
|
||||
JOIN sys.database_service_objectives slo
|
||||
ON d.database_id = slo.database_id
|
||||
WHERE elastic_pool_name = @PoolName
|
||||
)
|
||||
@@ -218,11 +218,11 @@ namespace MonitoringWebApp
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
string commandText = @"SELECT end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent],
|
||||
elastic_pool_dtu_limit
|
||||
string commandText = @"SELECT end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent],
|
||||
elastic_pool_dtu_limit
|
||||
FROM sys.elastic_pool_resource_stats
|
||||
WHERE elastic_pool_name = @PoolName
|
||||
ORDER BY end_time; ";
|
||||
@@ -279,7 +279,7 @@ namespace MonitoringWebApp
|
||||
|
||||
public override void OnClose(WebSocketCloseStatus? closeStatus, string closeStatusDescription)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -9,14 +9,14 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace MonitoringWebApp.Properties {
|
||||
|
||||
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
|
||||
+11
-11
@@ -63,7 +63,7 @@ $.extend($.fn, {
|
||||
}
|
||||
|
||||
validator = new $.validator( options, this[0] );
|
||||
$.data(this[0], 'validator', validator);
|
||||
$.data(this[0], 'validator', validator);
|
||||
|
||||
if ( validator.settings.onsubmit ) {
|
||||
|
||||
@@ -247,7 +247,7 @@ $.validator.format = function(source, params) {
|
||||
/// </param>
|
||||
/// <returns type="String" />
|
||||
|
||||
if ( arguments.length == 1 )
|
||||
if ( arguments.length == 1 )
|
||||
return function() {
|
||||
var args = $.makeArray(arguments);
|
||||
args.unshift(source);
|
||||
@@ -409,7 +409,7 @@ $.extend($.validator, {
|
||||
for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
|
||||
this.check( elements[i] );
|
||||
}
|
||||
return this.valid();
|
||||
return this.valid();
|
||||
},
|
||||
|
||||
// http://docs.jquery.com/Plugins/Validation/Validator/element
|
||||
@@ -1011,7 +1011,7 @@ $.extend($.validator, {
|
||||
// http://docs.jquery.com/Plugins/Validation/Validator/addMethod
|
||||
addMethod: function(name, method, message) {
|
||||
/// <summary>
|
||||
/// Add a custom validation method. It must consist of a name (must be a legal javascript
|
||||
/// Add a custom validation method. It must consist of a name (must be a legal javascript
|
||||
/// identifier), a javascript based function and a default string message.
|
||||
/// </summary>
|
||||
/// <param name="name" type="String">
|
||||
@@ -1022,8 +1022,8 @@ $.extend($.validator, {
|
||||
/// The actual method implementation, returning true if an element is valid
|
||||
/// </param>
|
||||
/// <param name="message" type="String" optional="true">
|
||||
/// (Optional) The default message to display for this method. Can be a function created by
|
||||
/// jQuery.validator.format(value). When undefined, an already existing message is used
|
||||
/// (Optional) The default message to display for this method. Can be a function created by
|
||||
/// jQuery.validator.format(value). When undefined, an already existing message is used
|
||||
/// (handy for localization), otherwise the field-specific messages have to be defined.
|
||||
/// </param>
|
||||
|
||||
@@ -1065,7 +1065,7 @@ $.extend($.validator, {
|
||||
previous.originalMessage = this.settings.messages[element.name].remote;
|
||||
this.settings.messages[element.name].remote = previous.message;
|
||||
|
||||
param = typeof param == "string" && {url:param} || param;
|
||||
param = typeof param == "string" && {url:param} || param;
|
||||
|
||||
if ( this.pending[element.name] ) {
|
||||
return "pending";
|
||||
@@ -1149,7 +1149,7 @@ $.extend($.validator, {
|
||||
// contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
|
||||
return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
|
||||
},
|
||||
|
||||
|
||||
// http://docs.jquery.com/Plugins/Validation/Methods/date
|
||||
date: function(value, element) {
|
||||
return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
|
||||
@@ -1201,7 +1201,7 @@ $.extend($.validator, {
|
||||
// http://docs.jquery.com/Plugins/Validation/Methods/accept
|
||||
accept: function(value, element, param) {
|
||||
param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif";
|
||||
return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i"));
|
||||
return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i"));
|
||||
},
|
||||
|
||||
// http://docs.jquery.com/Plugins/Validation/Methods/equalTo
|
||||
@@ -1225,7 +1225,7 @@ $.format = $.validator.format;
|
||||
|
||||
// ajax mode: abort
|
||||
// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
|
||||
// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
|
||||
// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
|
||||
;(function($) {
|
||||
var pendingRequests = {};
|
||||
// Use a prefilter if available (1.5+)
|
||||
@@ -1260,7 +1260,7 @@ $.format = $.validator.format;
|
||||
// IE has native support, in other browsers, use event caputuring (neither bubbles)
|
||||
|
||||
// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
|
||||
// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
|
||||
// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
|
||||
;(function($) {
|
||||
// only implement if not provided by jQuery core (since 1.4)
|
||||
// TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs
|
||||
|
||||
+15
-15
@@ -15,30 +15,30 @@
|
||||
/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
|
||||
/*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
|
||||
window.matchMedia = window.matchMedia || (function(doc, undefined){
|
||||
|
||||
|
||||
var bool,
|
||||
docElem = doc.documentElement,
|
||||
refNode = docElem.firstElementChild || docElem.firstChild,
|
||||
// fakeBody required for <FF4 when executed in <head>
|
||||
fakeBody = doc.createElement('body'),
|
||||
div = doc.createElement('div');
|
||||
|
||||
|
||||
div.id = 'mq-test-1';
|
||||
div.style.cssText = "position:absolute;top:-100em";
|
||||
fakeBody.style.background = "none";
|
||||
fakeBody.appendChild(div);
|
||||
|
||||
|
||||
return function(q){
|
||||
|
||||
|
||||
div.innerHTML = '­<style media="'+q+'"> #mq-test-1 { width: 42px; }</style>';
|
||||
|
||||
|
||||
docElem.insertBefore(fakeBody, refNode);
|
||||
bool = div.offsetWidth == 42;
|
||||
bool = div.offsetWidth == 42;
|
||||
docElem.removeChild(fakeBody);
|
||||
|
||||
|
||||
return { matches: bool, media: q };
|
||||
};
|
||||
|
||||
|
||||
})(document);
|
||||
|
||||
|
||||
@@ -163,11 +163,11 @@ window.matchMedia = window.matchMedia || (function(doc, undefined){
|
||||
|
||||
for( ; j < eql; j++ ){
|
||||
thisq = eachq[ j ];
|
||||
mediastyles.push( {
|
||||
mediastyles.push( {
|
||||
media : thisq.split( "(" )[ 0 ].match( /(only\s+)?([a-zA-Z]+)\s?/ ) && RegExp.$2 || "all",
|
||||
rules : rules.length - 1,
|
||||
hasquery: thisq.indexOf("(") > -1,
|
||||
minw : thisq.match( /\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ),
|
||||
minw : thisq.match( /\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ),
|
||||
maxw : thisq.match( /\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" )
|
||||
} );
|
||||
}
|
||||
@@ -213,7 +213,7 @@ window.matchMedia = window.matchMedia || (function(doc, undefined){
|
||||
return ret;
|
||||
},
|
||||
|
||||
//cached container for 1em value, populated the first time it's needed
|
||||
//cached container for 1em value, populated the first time it's needed
|
||||
eminpx,
|
||||
|
||||
//enable/disable styles
|
||||
@@ -278,13 +278,13 @@ window.matchMedia = window.matchMedia || (function(doc, undefined){
|
||||
//this caused crashes in IE in a number of circumstances, such as when the HTML element had a bg image set, so appending beforehand seems best. Thanks to @dvelyk for the initial research on this one!
|
||||
head.insertBefore( ss, lastLink.nextSibling );
|
||||
|
||||
if ( ss.styleSheet ){
|
||||
if ( ss.styleSheet ){
|
||||
ss.styleSheet.cssText = css;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ss.appendChild( doc.createTextNode( css ) );
|
||||
}
|
||||
|
||||
|
||||
//push to appendedEls to track for later removal
|
||||
appendedEls.push( ss );
|
||||
}
|
||||
@@ -307,7 +307,7 @@ window.matchMedia = window.matchMedia || (function(doc, undefined){
|
||||
}
|
||||
req.send( null );
|
||||
},
|
||||
//define ajax obj
|
||||
//define ajax obj
|
||||
xmlHttp = (function() {
|
||||
var xmlhttpmethod = false;
|
||||
try {
|
||||
|
||||
+3
-3
@@ -18,12 +18,12 @@
|
||||
<div>Source: sys.elastic_pool_resource_stats</div>
|
||||
<div>Last Update: <span id="pool_time"></span></div>
|
||||
<div id="pool_linechart"></div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Latest Pool Metrics</h2>
|
||||
<div id="latest_pool"></div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<div>Last Update: <span id="all_databases_time"></span></div>
|
||||
<div>Source: sys.resource_stats</div>
|
||||
<div style="height:500px; overflow:auto"><div id="all_databases"></div></div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+3
-3
@@ -30,13 +30,13 @@
|
||||
</dd>
|
||||
@*
|
||||
Phone Numbers can used as a second factor of verification in a two-factor authentication system.
|
||||
|
||||
|
||||
See <a href="http://go.microsoft.com/fwlink/?LinkId=403804">this article</a>
|
||||
for details on setting up this ASP.NET application to support two-factor authentication using SMS.
|
||||
|
||||
|
||||
Uncomment the following block after you have set up two-factor authentication
|
||||
*@
|
||||
@*
|
||||
@*
|
||||
<dt>Phone Number:</dt>
|
||||
<dd>
|
||||
@(Model.PhoneNumber ?? "None") [
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<title>@ViewBag.Title - My ASP.NET Application</title>
|
||||
@Styles.Render("~/Content/css")
|
||||
@Scripts.Render("~/bundles/modernizr")
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<appSettings>
|
||||
<add key="SelectedDatabaseNames" value="adventureworkscycles,adventureworkscycles2,adventureworkscycles3,adventureworkscycles4"/>
|
||||
<add key="PoolName" value=""/>
|
||||
|
||||
|
||||
<add key="webpages:Version" value="3.0.0.0" />
|
||||
<add key="webpages:Enabled" value="false" />
|
||||
<add key="ClientValidationEnabled" value="true" />
|
||||
|
||||
+7
-7
@@ -1,11 +1,11 @@
|
||||
SELECT Top(40) end_time, avg_cpu_percent as [CPU_%], avg_data_io_percent as [IO_%], avg_log_write_percent as [Write_%],
|
||||
avg_memory_usage_percent as [Mem_%], xtp_storage_percent as [Storage_%],
|
||||
max_worker_percent as [Worker_%], max_session_percent as [Session_%], dtu_limit
|
||||
SELECT Top(40) end_time, avg_cpu_percent as [CPU_%], avg_data_io_percent as [IO_%], avg_log_write_percent as [Write_%],
|
||||
avg_memory_usage_percent as [Mem_%], xtp_storage_percent as [Storage_%],
|
||||
max_worker_percent as [Worker_%], max_session_percent as [Session_%], dtu_limit
|
||||
FROM sys.dm_db_resource_stats;
|
||||
|
||||
SELECT Top(40) end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent], dtu_limit
|
||||
SELECT Top(40) end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent], dtu_limit
|
||||
FROM sys.dm_db_resource_stats
|
||||
;
|
||||
+8
-8
@@ -1,16 +1,16 @@
|
||||
SELECT end_time, avg_cpu_percent as [CPU_%], avg_data_io_percent as [IO_%], avg_log_write_percent as [Write_%], avg_storage_percent as [Size_%],
|
||||
max_worker_percent as [Worker_%], max_session_percent as [Session_%], elastic_pool_dtu_limit as [pool_dtu], elastic_pool_storage_limit_mb as [pool_size]
|
||||
FROM sys.elastic_pool_resource_stats
|
||||
max_worker_percent as [Worker_%], max_session_percent as [Session_%], elastic_pool_dtu_limit as [pool_dtu], elastic_pool_storage_limit_mb as [pool_size]
|
||||
FROM sys.elastic_pool_resource_stats
|
||||
WHERE elastic_pool_name = 'sol-demo-sql-pool'
|
||||
ORDER BY end_time DESC;
|
||||
|
||||
SELECT end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent],
|
||||
elastic_pool_dtu_limit
|
||||
SELECT end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent],
|
||||
elastic_pool_dtu_limit
|
||||
FROM sys.elastic_pool_resource_stats
|
||||
WHERE elastic_pool_name = 'sol-demo-sql-pool'
|
||||
ORDER BY end_time DESC;
|
||||
ORDER BY end_time DESC;
|
||||
|
||||
|
||||
|
||||
+21
-21
@@ -1,50 +1,50 @@
|
||||
SELECT Top(12) database_name, end_time, storage_in_megabytes as [Size_MB], avg_cpu_percent as [CPU_%], avg_data_io_percent as [IO_%], avg_log_write_percent as [Write_%],
|
||||
max_worker_percent as [Worker_%], max_session_percent as [Session_%], dtu_limit
|
||||
SELECT Top(12) database_name, end_time, storage_in_megabytes as [Size_MB], avg_cpu_percent as [CPU_%], avg_data_io_percent as [IO_%], avg_log_write_percent as [Write_%],
|
||||
max_worker_percent as [Worker_%], max_session_percent as [Session_%], dtu_limit
|
||||
FROM sys.resource_stats
|
||||
WHERE database_name = 'soladventureworkscycles' or database_name = 'soladventureworkscycles2'
|
||||
WHERE database_name = 'soladventureworkscycles' or database_name = 'soladventureworkscycles2'
|
||||
order by end_time desc;
|
||||
|
||||
SELECT end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent], dtu_limit
|
||||
SELECT end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent], dtu_limit
|
||||
FROM sys.resource_stats
|
||||
WHERE database_name = 'soladventureworkscycles'
|
||||
WHERE database_name = 'soladventureworkscycles'
|
||||
ORDER BY end_time desc
|
||||
;
|
||||
;
|
||||
|
||||
|
||||
SELECT max(end_time) end_time, database_name
|
||||
FROM sys.resource_stats
|
||||
WHERE database_name in (
|
||||
SELECT d.name
|
||||
FROM sys.databases d
|
||||
JOIN sys.database_service_objectives slo
|
||||
SELECT d.name
|
||||
FROM sys.databases d
|
||||
JOIN sys.database_service_objectives slo
|
||||
ON d.database_id = slo.database_id
|
||||
WHERE elastic_pool_name = 'sol-demo-sql-pool'
|
||||
)
|
||||
GROUP BY database_name
|
||||
ORDER BY end_time desc
|
||||
;
|
||||
;
|
||||
|
||||
|
||||
SELECT r1.database_name, r1.end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
SELECT r1.database_name, r1.end_time,
|
||||
(SELECT Max(v)
|
||||
FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS
|
||||
value(v)) AS [avg_DTU_percent],
|
||||
dtu_limit
|
||||
dtu_limit
|
||||
FROM sys.resource_stats r1
|
||||
JOIN (SELECT max(end_time) end_time, database_name
|
||||
FROM sys.resource_stats
|
||||
WHERE database_name in (
|
||||
SELECT d.name
|
||||
FROM sys.databases d
|
||||
JOIN sys.database_service_objectives slo
|
||||
SELECT d.name
|
||||
FROM sys.databases d
|
||||
JOIN sys.database_service_objectives slo
|
||||
ON d.database_id = slo.database_id
|
||||
WHERE elastic_pool_name = 'sol-demo-sql-pool'
|
||||
)
|
||||
GROUP BY database_name) r2
|
||||
ON r1.database_name = r2.database_name AND r1.end_time = r2.end_time
|
||||
ORDER BY end_time desc
|
||||
;
|
||||
;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ This readme applies to:
|
||||
|
||||
> [AZURE.NOTE] The requirements for building the solution are as follows:
|
||||
- Visual Studio 2015 Update 1 or later
|
||||
- Azure Subscription
|
||||
- Azure Subscription
|
||||
|
||||
## About this sample
|
||||
|
||||
@@ -24,7 +24,7 @@ This readme applies to:
|
||||
|
||||
The Solution Quick Start consists of a single Visual Studio 2015 solution with two projects, as follows:
|
||||
|
||||
- LoadGeneratorConsole: A console application that creates a configurable load against a specified set of databases.
|
||||
- LoadGeneratorConsole: A console application that creates a configurable load against a specified set of databases.
|
||||
- MonitoringWeb: A Web App that shows gathering and reporting on telemetry collected from Elastic Pools and Database instances.
|
||||
|
||||
### Contents
|
||||
@@ -46,9 +46,9 @@ The Solution Quick Start consists of a single Visual Studio 2015 solution with t
|
||||
|
||||
## Scenario
|
||||
|
||||
Contoso Shopkeeper provides business small and mid-size an easy to use, cost-effective shopping virtual store front and e-commerce solution that merchants can use to sell their products online. ShopKeeper is a multi-tenant Software-as-a-Service (SaaS) application that is entirely hosted in Azure and managed by Contoso on behalf of their merchant customers.
|
||||
Contoso Shopkeeper provides business small and mid-size an easy to use, cost-effective shopping virtual store front and e-commerce solution that merchants can use to sell their products online. ShopKeeper is a multi-tenant Software-as-a-Service (SaaS) application that is entirely hosted in Azure and managed by Contoso on behalf of their merchant customers.
|
||||
|
||||
The fundamentals behind the architecture of ShopKeeper are resource sharing amongst tenants (which helps keep costs down for both Contoso and its merchant customers), and isolation between tenants (which aims to guarantee that one merchants code or data is never mixed in with another’s). Take the example below, where a customer is using her Web Browser to shop Fabrikam Fabrics. In the process of placing an order she would be interacting with a Web App that only contains Fabrikam Fabric’s code, and the Web App would interact with the database instance that only contains Fabrikam Fabric’s data- this is the isolation aspect. The fact that various Web Apps share the resources from an App Service Plan or that multiple SQL Databases instances share resources from an Elastic Pool demonstrates the resource sharing aspect.
|
||||
The fundamentals behind the architecture of ShopKeeper are resource sharing amongst tenants (which helps keep costs down for both Contoso and its merchant customers), and isolation between tenants (which aims to guarantee that one merchants code or data is never mixed in with another’s). Take the example below, where a customer is using her Web Browser to shop Fabrikam Fabrics. In the process of placing an order she would be interacting with a Web App that only contains Fabrikam Fabric’s code, and the Web App would interact with the database instance that only contains Fabrikam Fabric’s data- this is the isolation aspect. The fact that various Web Apps share the resources from an App Service Plan or that multiple SQL Databases instances share resources from an Elastic Pool demonstrates the resource sharing aspect.
|
||||
|
||||
<
|
||||
|
||||
@@ -56,9 +56,9 @@ The fundamentals behind the architecture of ShopKeeper are resource sharing amon
|
||||
|
||||
## Solution Overview
|
||||
|
||||
The focus of this Solution Quick Start is on leveraging Elastic Pools and understanding how the support the backend for a SaaS application like Contoso ShopKeeper, the design and implementation of the App Services component is considered out of scope.
|
||||
The focus of this Solution Quick Start is on leveraging Elastic Pools and understanding how the support the backend for a SaaS application like Contoso ShopKeeper, the design and implementation of the App Services component is considered out of scope.
|
||||
|
||||
Since the best way to understand the behavior of Elastic Pools is to experience using them under load, we provide a load generator. The load generator is a console application that targets one or more elastic database instances in an Elastic Pool with a specific write load. You can run multiple instances of the load generator with different settings if you want to create a blended load, e.g., a mix of heavy a light load. In addition, you do not need to target all databases in the pool by the load generator, so you can leave databases you choose without any load.
|
||||
Since the best way to understand the behavior of Elastic Pools is to experience using them under load, we provide a load generator. The load generator is a console application that targets one or more elastic database instances in an Elastic Pool with a specific write load. You can run multiple instances of the load generator with different settings if you want to create a blended load, e.g., a mix of heavy a light load. In addition, you do not need to target all databases in the pool by the load generator, so you can leave databases you choose without any load.
|
||||
|
||||
<
|
||||
|
||||
@@ -77,22 +77,22 @@ After that, we will introduce how you would apply a schema change to all the dat
|
||||
|
||||
Before exploring Elastic Pools “hands-on” by running the load generator and Monitoring Web App in the ShopKeeper scenario, you should review the following guidance.
|
||||
|
||||
### Balance costs vs tenant performance
|
||||
### Balance costs vs tenant performance
|
||||
|
||||
In a multi-tenant scenario, appropriately balancing the cost of resources versus tenant performance is critical. An imbalance in one direction means that too much is being spent on resources and costs are high. An imbalance in the other direction means that tenants experience unacceptably slow performance.
|
||||
In a multi-tenant scenario, appropriately balancing the cost of resources versus tenant performance is critical. An imbalance in one direction means that too much is being spent on resources and costs are high. An imbalance in the other direction means that tenants experience unacceptably slow performance.
|
||||
|
||||
### Freemium Models and Elastic Pools
|
||||
|
||||
In most SaaS application scenarios, like ShopKeeper, there is a notion of a freemium subscription model. In this model, the solution as it is sold to end-customers is priced in different tiers, such as free and paid, with the notion that there is a friction free upgrade path from the free subscription (where most customers start out) to the paid subscription.
|
||||
In the ShopKeeper scenario, for example, assume Contoso has a Free Subscription and a Paid Subscription. Contoso’s goal for the Free Subscription is to keep the per tenant costs as low as possible because the tenants are not paying for this consumption directly (it might be paid from revenue generated by paying customers, or by other means such as a transaction fees or advertising). The Paid Subscription should still aim for cost-effectiveness, but because tenants are paying for usage in this Subscription, it is likely the per tenant costs can be higher (and provide improved peak performance or storage capacity).
|
||||
In most SaaS application scenarios, like ShopKeeper, there is a notion of a freemium subscription model. In this model, the solution as it is sold to end-customers is priced in different tiers, such as free and paid, with the notion that there is a friction free upgrade path from the free subscription (where most customers start out) to the paid subscription.
|
||||
In the ShopKeeper scenario, for example, assume Contoso has a Free Subscription and a Paid Subscription. Contoso’s goal for the Free Subscription is to keep the per tenant costs as low as possible because the tenants are not paying for this consumption directly (it might be paid from revenue generated by paying customers, or by other means such as a transaction fees or advertising). The Paid Subscription should still aim for cost-effectiveness, but because tenants are paying for usage in this Subscription, it is likely the per tenant costs can be higher (and provide improved peak performance or storage capacity).
|
||||
|
||||
So how does this map to how Contoso might leverage Elastic Pools? The database cost per tenant is effectively the cost of the pool divided by the number of tenant databases in the pool. The number of databases that can be added to any given pool is limited by the pricing tier of the pool. For example, a Standard 200 pool supports up to 400 databases. Currently this tier is priced at $446 USD per month. If Contoso were to fully utilize the pool by adding 400 databases, the cost per tenant would near $1.12 per tenant per month. Similarly, if they used the Basic 200 tier (which currently is priced at $298 USD per month), then the cost per tenant- month would near $0.75.
|
||||
So how does this map to how Contoso might leverage Elastic Pools? The database cost per tenant is effectively the cost of the pool divided by the number of tenant databases in the pool. The number of databases that can be added to any given pool is limited by the pricing tier of the pool. For example, a Standard 200 pool supports up to 400 databases. Currently this tier is priced at $446 USD per month. If Contoso were to fully utilize the pool by adding 400 databases, the cost per tenant would near $1.12 per tenant per month. Similarly, if they used the Basic 200 tier (which currently is priced at $298 USD per month), then the cost per tenant- month would near $0.75.
|
||||
|
||||
<
|
||||
|
||||
Naturally, they might consider using Pools in the Basic Tier for their Free Tier merchants in order to realize the lowest cost per tenant-month for that set of tenants. They might then consider reserving Pools in the Standard Tier for their Paid Tier merchants. Alternately, they may choose to use standardize on just one Pool service tier for both Free and Paid Subscriptions. The goal of getting the lowest cost per tenant month would remain the same.
|
||||
|
||||
An important consideration here is that once a Pool is created, the service tier selected cannot be changed. To change the service tier of a Pool amounts to creating a new Pool with the desired tier, removing the databases from the existing Pool and the adding them into the new Pool. While this can be done without any down-time with respect to database access, it is not something you would want to perform on a regular basis on account of the process being time consuming.
|
||||
An important consideration here is that once a Pool is created, the service tier selected cannot be changed. To change the service tier of a Pool amounts to creating a new Pool with the desired tier, removing the databases from the existing Pool and the adding them into the new Pool. While this can be done without any down-time with respect to database access, it is not something you would want to perform on a regular basis on account of the process being time consuming.
|
||||
|
||||
### When to Create New Pools
|
||||
|
||||
@@ -102,13 +102,13 @@ Contoso could also consider moving selected databases into a new pool, especiall
|
||||
|
||||
### When to Adjust eDTU’s Allocated to a Pool
|
||||
|
||||
There are many reasons why Contoso might consider adjusting the number of eDTU’s allocated to a Pool. For example, when they first create a Pool to handle the situation when other existing pools are at capacity, they might create the new Pool with the minimum number of eDTU’s to reduce the all-up cost of the Pool and then scale up the number of eDTU’s on the Pool as more databases are added.
|
||||
There are many reasons why Contoso might consider adjusting the number of eDTU’s allocated to a Pool. For example, when they first create a Pool to handle the situation when other existing pools are at capacity, they might create the new Pool with the minimum number of eDTU’s to reduce the all-up cost of the Pool and then scale up the number of eDTU’s on the Pool as more databases are added.
|
||||
|
||||
Another reason Contoso would adjust the number of eDTU’s allocated to a pool are if their merchants are encountering seasonal fluctuations or other such fluctuations. It is important to note, that while this capability is supported by Elastic Pools, it is not one that should be used frequently as the scaling up or down of eDTU’s takes time to complete—on the order of hours.
|
||||
|
||||
### Pool Management Options
|
||||
|
||||
The provisioning of new Pools, adjusting the eDTU’s assigned to a Pool or the migration of databases between Pools can be accomplished manually using the Azure Portal or it can be automated via C# (using the SQL Database Library for .NET) or PowerShell cmdlets (using Azure PowerShell 1.0 or higher).
|
||||
The provisioning of new Pools, adjusting the eDTU’s assigned to a Pool or the migration of databases between Pools can be accomplished manually using the Azure Portal or it can be automated via C# (using the SQL Database Library for .NET) or PowerShell cmdlets (using Azure PowerShell 1.0 or higher).
|
||||
|
||||
> [AZURE.NOTE] Examples of Pool Management: For examples of management with the above options, see https://azure.microsoft.com/en-us/documentation/articles/sql-database-elastic-pool-manage-portal/
|
||||
|
||||
@@ -118,30 +118,30 @@ The provisioning of new Pools, adjusting the eDTU’s assigned to a Pool or the
|
||||
|
||||
The Contoso ShopKeeper solution demonstrates an example of the challenge faces by SaaS solutions with regards to managing schemas. In this Solution Quick Start, it deploys an instance of the AdventureWorks database for each tenant. Therefore, Contoso would have many copies of a database with the same schema, albeit different data. This raises the question, how should Contoso roll out schema updates when required (e.g., because of application updates), without affecting the per tenant data?
|
||||
|
||||
Using the Azure Portal, as illustrated in this Solution Quick Start, Contoso can use Elastic Jobs to coordinate the execution of a T-SQL script against all of the databases in an Elastic Pool. The important consideration when taking this approach is that the T-SQL script must be written so it is idempotent. That is, running the script multiple times against a single database does not corrupt the target database or raise errors. The approach shown in this Solution Quick Start is to perform checks that examine if the script has already been run against the target database, and to gracefully complete (instead of executing any changes) if the script has previously completed.
|
||||
Using the Azure Portal, as illustrated in this Solution Quick Start, Contoso can use Elastic Jobs to coordinate the execution of a T-SQL script against all of the databases in an Elastic Pool. The important consideration when taking this approach is that the T-SQL script must be written so it is idempotent. That is, running the script multiple times against a single database does not corrupt the target database or raise errors. The approach shown in this Solution Quick Start is to perform checks that examine if the script has already been run against the target database, and to gracefully complete (instead of executing any changes) if the script has previously completed.
|
||||
|
||||
Besides using the Portal, Contoso may consider using PowerShell to control the execution of Elastics Database Jobs. In addition to the automation opportunities this allows for the deployment of updates, using PowerShell has one characteristic that is not present in the Portal: custom groups. With custom groups Contoso can target its T-SQL script to execute on a specific set of databases, instead of all the databases within the Pool.
|
||||
NOTE: For examples of using PowerShell to create and manage Elastic Database jobs, see https://azure.microsoft.com/en-us/documentation/articles/sql-database-elastic-jobs-overview/
|
||||
NOTE: For examples of using PowerShell to create and manage Elastic Database jobs, see https://azure.microsoft.com/en-us/documentation/articles/sql-database-elastic-jobs-overview/
|
||||
|
||||
<a name=monitoring-alerting></a>
|
||||
|
||||
## Monitoring & Alerting
|
||||
|
||||
For Contoso, balancing performance versus cost is critical, and for their ShopKeeper application the cost is directly correlated with the number of Elastic Pools they have allocated. Therefore, they want to monitor their Pools closely so they know when to take actions such as creating new Pools or moving databases between Pools.
|
||||
For Contoso, balancing performance versus cost is critical, and for their ShopKeeper application the cost is directly correlated with the number of Elastic Pools they have allocated. Therefore, they want to monitor their Pools closely so they know when to take actions such as creating new Pools or moving databases between Pools.
|
||||
|
||||
This begs the question, how can they setup notifications if their load is overwhelming the pool? In this Solution Quick Start, we demonstrate configuring alerts on the Pool using the Azure Portal. Contoso can configure alerts for metrics such CPU %, eDTU %, sessions %, storage %, workers % which when can be tracked in the portal when they trigger, or they can be used to send an email out to admins.
|
||||
This begs the question, how can they setup notifications if their load is overwhelming the pool? In this Solution Quick Start, we demonstrate configuring alerts on the Pool using the Azure Portal. Contoso can configure alerts for metrics such CPU %, eDTU %, sessions %, storage %, workers % which when can be tracked in the portal when they trigger, or they can be used to send an email out to admins.
|
||||
|
||||
If the capacity concern has more to do with specific tenant database instances, Contoso can also configure alerts on a per database level, in a similar fashion as they do for Pools, by using the Azure Portal.
|
||||
If the capacity concern has more to do with specific tenant database instances, Contoso can also configure alerts on a per database level, in a similar fashion as they do for Pools, by using the Azure Portal.
|
||||
|
||||
For both Pool alerts and per Database alerts, Contoso can configure Web Hooks that will perform an HTTP POST to the endpoint of their choosing when the alert is triggered, in addition to having an email sent out to administrators. The payload of this HTTP POST contains the information about the alert that was configured (e.g., the alert name, description and metric configuration), but also the value that caused the alert to trigger. This enables Contoso to extend their ability to react to alerts by using their own management web app or to send out notifications (e.g. using Azure Notification Services) or SMS text messages (e.g., using Twilio).
|
||||
For both Pool alerts and per Database alerts, Contoso can configure Web Hooks that will perform an HTTP POST to the endpoint of their choosing when the alert is triggered, in addition to having an email sent out to administrators. The payload of this HTTP POST contains the information about the alert that was configured (e.g., the alert name, description and metric configuration), but also the value that caused the alert to trigger. This enables Contoso to extend their ability to react to alerts by using their own management web app or to send out notifications (e.g. using Azure Notification Services) or SMS text messages (e.g., using Twilio).
|
||||
|
||||
When it comes to monitoring the Pool and the databases Contoso can choose to use the Azure Portal as well as T-SQL, as we show in this Solution Quick Start with the Monitoring Web App. They can use the T-SQL options to collect the telemetry from Azure and store it in their own log analytics solution. This would enable them to perform analysis on the telemetry that spans much longer periods of time than that permitted by the retention policy of the data when it is managed by Azure—for example, enabling them to review pool usage over the course of months instead of the 14 days that is maintained by Azure.
|
||||
When it comes to monitoring the Pool and the databases Contoso can choose to use the Azure Portal as well as T-SQL, as we show in this Solution Quick Start with the Monitoring Web App. They can use the T-SQL options to collect the telemetry from Azure and store it in their own log analytics solution. This would enable them to perform analysis on the telemetry that spans much longer periods of time than that permitted by the retention policy of the data when it is managed by Azure—for example, enabling them to review pool usage over the course of months instead of the 14 days that is maintained by Azure.
|
||||
|
||||
<a name=database-recovery></a>
|
||||
|
||||
## Database Recovery
|
||||
## Database Recovery
|
||||
|
||||
By utilizing Elastic Pools, Contoso gets an improved ability to juggle cost versus tenant performance, but does not lose any of the features supporting availability and disaster recovery that are available to SQL Databases outside of a Pool. For example, they can use Point in Time Restore to recover from user error, such as a DBA accidentally dropping the customers table in a tenant’s database. To accomplish this with minimal down-time, they would ensure Pool to which they will restore has capacity for another database instance, rename the original Database to a temporary name and then restore to new database with same name as original. In this fashion, they could restore the database without having to make any application level changes, such as altering connection strings, because the restored database name would be unchanged.
|
||||
By utilizing Elastic Pools, Contoso gets an improved ability to juggle cost versus tenant performance, but does not lose any of the features supporting availability and disaster recovery that are available to SQL Databases outside of a Pool. For example, they can use Point in Time Restore to recover from user error, such as a DBA accidentally dropping the customers table in a tenant’s database. To accomplish this with minimal down-time, they would ensure Pool to which they will restore has capacity for another database instance, rename the original Database to a temporary name and then restore to new database with same name as original. In this fashion, they could restore the database without having to make any application level changes, such as altering connection strings, because the restored database name would be unchanged.
|
||||
|
||||
They also get to benefit from the restore deleted database feature to provide a window of time during which their system could actually delete the database when a merchant cancels, but be able to restore that database should the customer re-join within the retention window. This retention window is controlled by the service tier of the pool: 7 days for Basic, 14 days for Standard and 35 days for Premium.
|
||||
|
||||
@@ -149,12 +149,12 @@ They also get to benefit from the restore deleted database feature to provide a
|
||||
|
||||
## Summary
|
||||
|
||||
This Solution Quick Start provides guidance on how to leverage Elastic Pools to support the backend of a SaaS application. In addition, this Solution Quick Start provides a tool to simulate load on elastic databases so that you can understand how the effects of load on a database affect the Elastic Pool. Finally, this solution Quick Start demonstrates how you can build collect your Elastic Pool and database telemetry programmatically so that you can implement a monitoring solution that suites the needs of your application.
|
||||
This Solution Quick Start provides guidance on how to leverage Elastic Pools to support the backend of a SaaS application. In addition, this Solution Quick Start provides a tool to simulate load on elastic databases so that you can understand how the effects of load on a database affect the Elastic Pool. Finally, this solution Quick Start demonstrates how you can build collect your Elastic Pool and database telemetry programmatically so that you can implement a monitoring solution that suites the needs of your application.
|
||||
|
||||
<a name=learn-more></a>
|
||||
|
||||
## Learn More
|
||||
|
||||
- SQL Database Forum on MSDN: https://social.msdn.microsoft.com/Forums/azure/en-US/home?forum=ssdsgetstarted
|
||||
- Stack Overflow: http://stackoverflow.com/questions/tagged/azure-sql-database
|
||||
- Azure Documentation on Elastic Pools: https://azure.microsoft.com/en-us/documentation/articles/sql-database-elastic-pool/
|
||||
- SQL Database Forum on MSDN: https://social.msdn.microsoft.com/Forums/azure/en-US/home?forum=ssdsgetstarted
|
||||
- Stack Overflow: http://stackoverflow.com/questions/tagged/azure-sql-database
|
||||
- Azure Documentation on Elastic Pools: https://azure.microsoft.com/en-us/documentation/articles/sql-database-elastic-pool/
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
# ----------------------------------------------------------------------------------
|
||||
#
|
||||
# Copyright Microsoft Corporation
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ----------------------------------------------------------------------------------
|
||||
#
|
||||
# Copyright Microsoft Corporation
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ---------------------------------------------------------------------------------
|
||||
#
|
||||
# Sample script for loading SQL Database telemetry for pools and elastic databases
|
||||
# Sample script for loading SQL Database telemetry for pools and elastic databases
|
||||
# on a single server into a user-supplied Azure SQL database. Script will populate
|
||||
# the target schema if not present.
|
||||
#
|
||||
# the target schema if not present.
|
||||
#
|
||||
# See additional comments in PoolTelemetryRunner.ps1, which includes instructions for
|
||||
#
|
||||
# See additional comments in PoolTelemetryRunner.ps1, which includes instructions for
|
||||
# running this script as a PowerShell job, enabling data gathering for large numbers
|
||||
# of servers in the background.
|
||||
#
|
||||
@@ -32,17 +32,17 @@ function Load-PoolTelemetryForServer {
|
||||
[Parameter(Mandatory=$true)][string]$OutputServerName, # server name of telemetry database, like "telemetryserver"
|
||||
[Parameter(Mandatory=$true)][string]$OutputDatabaseName, # telemetry database name, like "telemetrydb"
|
||||
[Parameter(Mandatory=$true)][PSCredential]$OutputServerCred,
|
||||
[Parameter(Mandatory=$true)][int]$IntervalMinutes, # interval for collection of telemetry
|
||||
[Parameter(Mandatory=$true)][int]$IntervalMinutes, # interval for collection of telemetry
|
||||
[Parameter(Mandatory=$true)][int]$DurationMinutes, # total duration for collection of telemetry
|
||||
[Parameter(Mandatory=$true)][bool]$loadAllAvailablePoolTelemetry, # indicates if all available telemetry should be loaded on th first pass
|
||||
[Parameter(Mandatory=$true)][bool]$IncludeDatabases # indicates if telemetry should be gathered for databases as well as pools
|
||||
)
|
||||
)
|
||||
|
||||
# Create output database metrics collection tables if it does not already exist.
|
||||
|
||||
$OutputServerCred.Password.MakeReadOnly()
|
||||
$sqlCred = new-object ("System.Data.SqlClient.SqlCredential") -ArgumentList $OutputServerCred.UserName, $OutputServerCred.Password
|
||||
|
||||
|
||||
$outputConnection = New-Object ("System.Data.SqlClient.SqlConnection") "Data Source=$OutputServerName.database.windows.net;Integrated Security=false;Initial Catalog=$OutputDatabaseName"
|
||||
$outputConnection.Credential = $sqlCred
|
||||
$outputConnection.Open()
|
||||
@@ -50,40 +50,40 @@ function Load-PoolTelemetryForServer {
|
||||
$poolResourceStatsTable = "pool_resource_stats" # <<< update if a different table name is required
|
||||
$dbResourceStatsTable = "db_resource_stats" # <<< update if a different table name is required
|
||||
|
||||
$sql =`
|
||||
$sql =`
|
||||
"-- Create table for holding collected pool resource stats
|
||||
IF NOT EXISTS (SELECT * FROM sys.objects
|
||||
IF NOT EXISTS (SELECT * FROM sys.objects
|
||||
WHERE object_id = OBJECT_ID(N'$($poolResourceStatsTable)') AND type in (N'U'))
|
||||
|
||||
BEGIN
|
||||
Create Table $($poolResourceStatsTable) (subscription_guid uniqueidentifier, resource_group_name varchar(128), server_name varchar(128), location varchar(128), elastic_pool_name varchar(128), end_time datetime,
|
||||
Create Table $($poolResourceStatsTable) (subscription_guid uniqueidentifier, resource_group_name varchar(128), server_name varchar(128), location varchar(128), elastic_pool_name varchar(128), end_time datetime,
|
||||
elastic_pool_DTU_limit int, avg_cpu_percent decimal(5,2), avg_data_io_percent decimal(5,2), avg_log_io_percent decimal(5,2), max_worker_percent decimal(5,2), max_session_percent decimal(5,2)
|
||||
, avg_DTU_percent decimal(5,2), avg_storage_percent decimal(5,2), elastic_pool_storage_limit_mb bigint);
|
||||
Create Clustered Index ci_endtime ON $($poolResourceStatsTable) (end_time);
|
||||
END
|
||||
|
||||
-- Create table for holding collected database resource stats
|
||||
IF NOT EXISTS (SELECT * FROM sys.objects
|
||||
IF NOT EXISTS (SELECT * FROM sys.objects
|
||||
WHERE object_id = OBJECT_ID(N'$($dbResourceStatsTable)') AND type in (N'U'))
|
||||
|
||||
BEGIN
|
||||
Create Table $($dbResourceStatsTable) (subscription_guid uniqueidentifier, resource_group_name varchar(128),server_name varchar(128), location varchar(128), elastic_pool_name varchar(128), database_name varchar(128), end_time datetime,
|
||||
Create Table $($dbResourceStatsTable) (subscription_guid uniqueidentifier, resource_group_name varchar(128),server_name varchar(128), location varchar(128), elastic_pool_name varchar(128), database_name varchar(128), end_time datetime,
|
||||
database_DTU_limit int, avg_cpu_percent decimal(5,2), avg_data_io_percent decimal(5,2), avg_log_io_percent decimal(5,2), max_worker_percent decimal(5,2), max_session_percent decimal(5,2), avg_DTU_percent decimal(5,2), db_size float);
|
||||
Create Clustered Index ci_endtime ON $($dbResourceStatsTable) (end_time);
|
||||
END
|
||||
|
||||
|
||||
-- Create a function to get aggregated metrics for a given time interval
|
||||
IF NOT EXISTS (SELECT * FROM sys.objects
|
||||
IF NOT EXISTS (SELECT * FROM sys.objects
|
||||
WHERE name = N'get_aggregated_pool_metrics' AND type in ('IF'))
|
||||
EXEC sp_executesql @Statement = N'
|
||||
Create function get_aggregated_pool_metrics(
|
||||
@start datetime
|
||||
,@end datetime)
|
||||
RETURNS TABLE
|
||||
RETURNS TABLE
|
||||
AS
|
||||
RETURN
|
||||
(
|
||||
SELECT
|
||||
SELECT
|
||||
location, server_name, elastic_pool_name
|
||||
,avg([avg_dtu_percent]) as avg_eDTU_percent
|
||||
,avg([avg_cpu_percent]) as avg_cpu_percent
|
||||
@@ -102,11 +102,11 @@ function Load-PoolTelemetryForServer {
|
||||
group by location, server_name, elastic_pool_name
|
||||
)'
|
||||
"
|
||||
|
||||
|
||||
|
||||
$outputServerFullname = $OutputServerName + '.database.windows.net' # assumes server is in Azure SQL Database
|
||||
|
||||
Invoke-Sqlcmd -ServerInstance $outputServerFullName -Database $OutputDatabaseName -Username $OutputServerCred.UserName -Password $OutputServerCred.GetNetworkCredential().Password -Query $sql -ConnectionTimeout 120 -QueryTimeout 120
|
||||
Invoke-Sqlcmd -ServerInstance $outputServerFullName -Database $OutputDatabaseName -Username $OutputServerCred.UserName -Password $OutputServerCred.GetNetworkCredential().Password -Query $sql -ConnectionTimeout 120 -QueryTimeout 120
|
||||
|
||||
$sourceServerFullName = $ServerName + '.database.windows.net'
|
||||
|
||||
@@ -114,7 +114,7 @@ function Load-PoolTelemetryForServer {
|
||||
$now = [DateTime]::UtcNow
|
||||
[DateTime]$startTime = $now.AddMinutes(-$interval) # sets the start time for the first telemetry collection
|
||||
[DateTime]$endTime = $now # sets the end time
|
||||
[DateTime]$finishTime = $now.AddMinutes($DurationMinutes) # sets the overall finish time for a telemetry collection session
|
||||
[DateTime]$finishTime = $now.AddMinutes($DurationMinutes) # sets the overall finish time for a telemetry collection session
|
||||
|
||||
Write-Host "Starting to collect telemetry for" $ServerName
|
||||
|
||||
@@ -131,20 +131,20 @@ function Load-PoolTelemetryForServer {
|
||||
{
|
||||
$poolStartTime = $startTime.AddMinutes(-$poolLagMinutes) # sets the normal start of query window
|
||||
}
|
||||
|
||||
|
||||
$poolEndTime = $endTime.AddMinutes(-$poolLagMinutes) # sets end of query window
|
||||
|
||||
Write-Host "Starting to collect elastic pool telemetry for period" $poolStartTime "to" $poolEndTime "(UTC)"
|
||||
|
||||
# Collect metrics for all elastic pools in this server.
|
||||
$sql = `
|
||||
"SELECT subscription_guid = CAST ('$($SubscriptionId)' AS uniqueidentifier), resource_group_name = '$($ResourceGroupName)', server_name = '$($ServerName)', location = '$($Location)', elastic_pool_name
|
||||
"SELECT subscription_guid = CAST ('$($SubscriptionId)' AS uniqueidentifier), resource_group_name = '$($ResourceGroupName)', server_name = '$($ServerName)', location = '$($Location)', elastic_pool_name
|
||||
, end_time, elastic_pool_dtu_limit, avg_cpu_percent, avg_data_io_percent, avg_log_write_percent as avg_log_io_percent, max_worker_percent, max_session_percent
|
||||
,(SELECT Max(v) FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS value(v)) AS avg_DTU_percent , avg_storage_percent, elastic_pool_storage_limit_mb FROM sys.elastic_pool_resource_stats
|
||||
WHERE end_time > '$($poolStartTime)' and end_time <= '$($poolEndTime)';"
|
||||
|
||||
$poolResult = Invoke-Sqlcmd -ServerInstance $sourceServerFullName -Database "master" -Username $ServerCred.UserName -Password $ServerCred.GetNetworkCredential().Password -Query $sql -ConnectionTimeout 120 -QueryTimeout 3600
|
||||
|
||||
WHERE end_time > '$($poolStartTime)' and end_time <= '$($poolEndTime)';"
|
||||
|
||||
$poolResult = Invoke-Sqlcmd -ServerInstance $sourceServerFullName -Database "master" -Username $ServerCred.UserName -Password $ServerCred.GetNetworkCredential().Password -Query $sql -ConnectionTimeout 120 -QueryTimeout 3600
|
||||
|
||||
if ($poolResult -ne $null)
|
||||
{
|
||||
#bulk copy the pool telemetry metrics to output database
|
||||
@@ -154,7 +154,7 @@ function Load-PoolTelemetryForServer {
|
||||
$bulkCopy.WriteToServer($poolResult);
|
||||
|
||||
Write-Host "Elastic pool telemetry loaded for server" $ServerName
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "No elastic pool telemetry found for server" $ServerName "for period" $poolStartTime "to" $poolEndTime "(UTC)"
|
||||
@@ -168,30 +168,30 @@ function Load-PoolTelemetryForServer {
|
||||
"select Name, elastic_pool_name from sys.databases as db
|
||||
inner join sys.database_service_objectives as dbso on db.database_id = dbso.database_id
|
||||
where (dbso.service_objective = 'ElasticPool') and db.Name != 'master'"
|
||||
|
||||
$dbList = Invoke-Sqlcmd -ServerInstance $sourceServerFullName -Database "master" -Username $ServerCred.UserName -Password $ServerCred.GetNetworkCredential().Password -Query $sql -ConnectionTimeout 120 -QueryTimeout 3600
|
||||
|
||||
$dbList = Invoke-Sqlcmd -ServerInstance $sourceServerFullName -Database "master" -Username $ServerCred.UserName -Password $ServerCred.GetNetworkCredential().Password -Query $sql -ConnectionTimeout 120 -QueryTimeout 3600
|
||||
|
||||
if ($dbList -ne $null)
|
||||
{
|
||||
Write-Host $dbList.Count "elastic databases found on server" $ServerName
|
||||
Write-Host $dbList.Count "elastic databases found on server" $ServerName
|
||||
|
||||
# Collect telemetry for each elastic database
|
||||
foreach ($db in $dbList)
|
||||
{
|
||||
{
|
||||
$sql= `
|
||||
"Declare @db_size float;
|
||||
SELECT @db_size = SUM(reserved_page_count) * 8.0/1024/1024 FROM sys.dm_db_partition_stats
|
||||
SELECT subscription_guid = CAST ('$($SubscriptionId)' AS uniqueidentifier), resource_group_name = '$($ResourceGroupName)',server_name = '$($ServerName)', location = '$($Location)', elastic_pool_name = '$($db.elastic_pool_name)'
|
||||
, '$($db.Name)' as database_name, end_time, dtu_limit as database_dtu_limit, avg_cpu_percent, avg_data_io_percent, avg_log_write_percent as avg_log_io_percent, max_worker_percent, max_session_percent
|
||||
,(SELECT Max(v) FROM (VALUES (avg_cpu_percent), (avg_data_io_percent), (avg_log_write_percent)) AS value(v)) AS avg_DTU_percent ,@db_size as db_size FROM sys.dm_db_resource_stats
|
||||
WHERE end_time > '$($startTime)' and end_time <= '$($endTime)';"
|
||||
WHERE end_time > '$($startTime)' and end_time <= '$($endTime)';"
|
||||
|
||||
$dbResult = Invoke-Sqlcmd -ServerInstance $SourceServerFullName -Database $db.Name -Username $ServerCred.UserName -Password $ServerCred.GetNetworkCredential().Password -Query $sql -ConnectionTimeout 120 -QueryTimeout 3600
|
||||
|
||||
|
||||
if ($dbResult -ne $null)
|
||||
{
|
||||
#bulk copy the data to the telemetry database
|
||||
$bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $outputConnection
|
||||
$bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $outputConnection
|
||||
$bulkCopy.BulkCopyTimeout = 600
|
||||
$bulkCopy.DestinationTableName = "$dbResourceStatsTable";
|
||||
$bulkCopy.WriteToServer($dbResult);
|
||||
@@ -208,19 +208,19 @@ function Load-PoolTelemetryForServer {
|
||||
{
|
||||
$now = [DateTime]::UtcNow
|
||||
|
||||
Write-Host "No elastic databases found for" $ServerName "when checking at" $now
|
||||
Write-Host "No elastic databases found for" $ServerName "when checking at" $now
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Finished collection for server" $ServerName "for period" $startTime "to" $endTime
|
||||
|
||||
# set up time period for next collection
|
||||
# set up time period for next collection
|
||||
$startTime = $startTime.AddMinutes($interval)
|
||||
$endTime = $endTime.AddMinutes($interval)
|
||||
|
||||
# end if the next period doesn't start before the finish time
|
||||
If ($startTime -ge $finishTime) {break}
|
||||
|
||||
|
||||
Write-Host "Sleeping until" $endTime "(UTC)"
|
||||
|
||||
do
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
# ----------------------------------------------------------------------------------
|
||||
#
|
||||
# Copyright Microsoft Corporation
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ----------------------------------------------------------------------------------
|
||||
#
|
||||
# Powershell script for loading telemetry data from elastic pools and elastic database
|
||||
# into a telemetry database. To be used in conjunction with PoolTelemetry.ps1, which
|
||||
# Copyright Microsoft Corporation
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ----------------------------------------------------------------------------------
|
||||
#
|
||||
# Powershell script for loading telemetry data from elastic pools and elastic database
|
||||
# into a telemetry database. To be used in conjunction with PoolTelemetry.ps1, which
|
||||
# should be installed in the same directory.
|
||||
#
|
||||
# This script should be customized as required (see <<<) to select one or more source
|
||||
# servers, and will then spawn a telemetry gathering job for each server based on
|
||||
# Load-PoolTelemetryForServer in PoolTelemetry.ps1, which loads telemetry for all
|
||||
# pools and elastic databases on the server. Each spawned job will run for an
|
||||
# extended period in the background, waking up periodically to load more telemetry.
|
||||
# The interval between telemetry gathering and the total duration can be controlled
|
||||
# This script should be customized as required (see <<<) to select one or more source
|
||||
# servers, and will then spawn a telemetry gathering job for each server based on
|
||||
# Load-PoolTelemetryForServer in PoolTelemetry.ps1, which loads telemetry for all
|
||||
# pools and elastic databases on the server. Each spawned job will run for an
|
||||
# extended period in the background, waking up periodically to load more telemetry.
|
||||
# The interval between telemetry gathering and the total duration can be controlled
|
||||
# by parameters set in this script.
|
||||
#
|
||||
# Pool telemetry is loaded from the server master database, database telemetry is
|
||||
# optionally loaded from each elastic database. This script assumes a common
|
||||
#
|
||||
# Pool telemetry is loaded from the server master database, database telemetry is
|
||||
# optionally loaded from each elastic database. This script assumes a common
|
||||
# user name and password is used to connect to all source servers and databases.
|
||||
#
|
||||
#------------------------------------------------------------------------------
|
||||
#
|
||||
## Prompt for Azure login
|
||||
Login-AzureRMAccount
|
||||
|
||||
Login-AzureRMAccount
|
||||
|
||||
# Set the Azure subscription, needed if your Microsoft account is associated with multiple subscriptions <<< ***
|
||||
$AzureSubscriptionName = '<Your Subscription Name>'
|
||||
$Subscription = Get-AzureRmSubscription -SubscriptionName $AzureSubscriptionName | Select-AzureRmSubscription
|
||||
@@ -41,7 +41,7 @@ $SubscriptionId = $Subscription.Subscription.SubscriptionId
|
||||
|
||||
## Get SQL Server credentials. NOTE: same credential assumed for all source servers <<< ***
|
||||
$sourceCred = Get-Credential -Message 'User name and password for source server' -UserName '<source user name>'# add user name here
|
||||
$outputServerCred = Get-Credential -Message 'User name and password for telemetry database' -UserName '<telemetry user name>'# add user name here
|
||||
$outputServerCred = Get-Credential -Message 'User name and password for telemetry database' -UserName '<telemetry user name>'# add user name here
|
||||
|
||||
## Resource group and server used for source server selection <<< ***
|
||||
$resourceGroupName = '<resource group name>' # name of resource group containing the server from which telemetry will be gathered - see https://portal.azure.com
|
||||
@@ -55,18 +55,18 @@ $outputDatabaseName = '<telemetry database name>' # telemetry database name, lik
|
||||
$staticServerList = $false # set to $true if server list will not change during telemetry gathering period
|
||||
|
||||
## Set telemetry collection timing <<< ***
|
||||
$intervalMinutes = 15 # interval between telemetry collections used by spawned server job (15-30 mins suggested)
|
||||
$durationMinutes = 60 # total duration for collection of telemetry and checking servers; 0 for one time execution, or a multiple of the interval.
|
||||
$intervalMinutes = 15 # interval between telemetry collections used by spawned server job (15-30 mins suggested)
|
||||
$durationMinutes = 60 # total duration for collection of telemetry and checking servers; 0 for one time execution, or a multiple of the interval.
|
||||
|
||||
## Set to $true to include all available pool telemetry (up to 14 days). Be careful if rerunning on the same server as this may load duplicate data <<< ***
|
||||
$loadAllAvailablePoolTelemetry = $false
|
||||
$loadAllAvailablePoolTelemetry = $false
|
||||
|
||||
## Set to $true to gather 15 sec telemetry for elastic databases. Caution: large volumes of data may be returned. <<< ***
|
||||
[bool]$includeDatabases = $false # or $False
|
||||
|
||||
$now = [DateTime]::UtcNow
|
||||
[DateTime]$startTime = $now # sets the start time for the first telemetry collection
|
||||
[DateTime]$finishTime = $now.AddMinutes($DurationMinutes) # sets the overall finish time for a telemetry collection session
|
||||
[DateTime]$finishTime = $now.AddMinutes($DurationMinutes) # sets the overall finish time for a telemetry collection session
|
||||
|
||||
Write-Host "Starting telemetry gathering for period" $startTime "to" $finishTime
|
||||
|
||||
@@ -77,13 +77,13 @@ $jobs = @{}
|
||||
# period the server list is re-evaluated periodically
|
||||
|
||||
# The following identifies the servers to be analyzed and starts a job for each server to gather telemetry data
|
||||
# In each iteration the latest server list is compared to the running jobs list and additional jobs started or
|
||||
# In each iteration the latest server list is compared to the running jobs list and additional jobs started or
|
||||
# current jobs stopped as required.
|
||||
|
||||
|
||||
while ($startTime -le $finishTime)
|
||||
{
|
||||
Write-Host "Finding servers as at" $startTime "(UTC)"
|
||||
|
||||
Write-Host "Finding servers as at" $startTime "(UTC)"
|
||||
|
||||
# initialize the servers list for each iteration
|
||||
$servers = [ordered]@{}
|
||||
|
||||
@@ -91,7 +91,7 @@ while ($startTime -le $finishTime)
|
||||
|
||||
## Get a specific server
|
||||
#$server = Get-AzureRmSqlServer -ResourceGroupName $resourceGroupName -ServerName $serverName
|
||||
#$servers.Add($server.ServerName, $server)
|
||||
#$servers.Add($server.ServerName, $server)
|
||||
|
||||
## Get all servers in a specific resource group
|
||||
#$servers = Get-AzureRmSqlServer -ResourceGroupName $resourceGroupName
|
||||
@@ -100,7 +100,7 @@ while ($startTime -le $finishTime)
|
||||
#$resourceList = Find-AzureRmResource -ResourceType microsoft.sql/servers
|
||||
|
||||
## Get all resources of type server in a specific region
|
||||
$resourceList = Find-AzureRmResource -ResourceType microsoft.sql/servers -ODataQuery "(Location eq 'australiasoutheast' or Location eq 'australiaeast')"
|
||||
$resourceList = Find-AzureRmResource -ResourceType microsoft.sql/servers -ODataQuery "(Location eq 'australiasoutheast' or Location eq 'australiaeast')"
|
||||
|
||||
## Get all resources of type server with common name pattern
|
||||
#$resourceList = Find-AzureRmResource -ResourceType Microsoft.Sql/servers -ResourceNameContains '<common text>'
|
||||
@@ -112,36 +112,36 @@ while ($startTime -le $finishTime)
|
||||
$servers.Add($server.ServerName, $server)
|
||||
}
|
||||
|
||||
Write-Host $servers.Count "servers found"
|
||||
Write-Host $servers.Count "servers found"
|
||||
|
||||
$scriptPath = "$PSScriptRoot\PoolTelemetry.ps1"
|
||||
$initScript = (Get-Command $scriptPath).ScriptBlock
|
||||
|
||||
# For each server, start a job to collect telemetry. Set job name to the server name and put the job in $jobs
|
||||
# For each server, start a job to collect telemetry. Set job name to the server name and put the job in $jobs
|
||||
foreach($server in $servers.Values)
|
||||
{
|
||||
if ($jobs.Contains($server.ServerName) -eq $false)
|
||||
{
|
||||
$job = Start-Job -Name $server.ServerName -ScriptBlock {
|
||||
param ($sp, $all, $inc, $sub, $rgn, $sn, $loc, $sc, $osn, $odn, $osc, $im, $dm)
|
||||
param ($sp, $all, $inc, $sub, $rgn, $sn, $loc, $sc, $osn, $odn, $osc, $im, $dm)
|
||||
. $sp
|
||||
Load-PoolTelemetryForServer -loadAllAvailablePoolTelemetry $all -IncludeDatabases $inc `
|
||||
-SubscriptionId $sub -ResourceGroupName $rgn -ServerName $sn -Location $loc -ServerCred $sc -OutputServerName $osn -OutputDatabaseName $odn -OutputServerCred $osc -IntervalMinutes $im -DurationMinutes $dm} `
|
||||
-ArgumentList $scriptPath, $loadAllAvailablePoolTelemetry, $includeDatabases, $SubscriptionId, $server.ResourceGroupName, $server.ServerName, $server.Location, $sourceCred, $outputServerName, $outputDatabaseName, $outputServerCred, $intervalMinutes, $durationMinutes
|
||||
-ArgumentList $scriptPath, $loadAllAvailablePoolTelemetry, $includeDatabases, $SubscriptionId, $server.ResourceGroupName, $server.ServerName, $server.Location, $sourceCred, $outputServerName, $outputDatabaseName, $outputServerCred, $intervalMinutes, $durationMinutes
|
||||
|
||||
$jobs.Add($server.ServerName, $job)
|
||||
|
||||
|
||||
Write-Host "Job started for server" $server.ServerName
|
||||
|
||||
# Following is useful for debugging changes to PoolTelemetry.ps1. Best used with a single server unless you set the duration to 0
|
||||
#. $scriptPath
|
||||
#Load-PoolTelemetryForServer -loadAllAvailablePoolTelemetry $loadAllAvailablePoolTelemetry -IncludeDatabases $IncludeDatabases -SubscriptionId $SubscriptionId -ResourceGroupName $server.ResourceGroupName `
|
||||
# -ServerName $server.ServerName -Location $server.Location -ServerCred $sourceCred -OutputServerName $outputServerName `
|
||||
# -OutputDatabaseName $outputDatabaseName -OutputServerCred $outputServerCred -IntervalMinutes $intervalMinutes -DurationMinutes $durationMinutes
|
||||
# -OutputDatabaseName $outputDatabaseName -OutputServerCred $outputServerCred -IntervalMinutes $intervalMinutes -DurationMinutes $durationMinutes
|
||||
}
|
||||
}
|
||||
|
||||
# if server list doesn't change then no need to re-evaluate server list or manage the jobs. Already spawned jobs will continue to run.
|
||||
# if server list doesn't change then no need to re-evaluate server list or manage the jobs. Already spawned jobs will continue to run.
|
||||
if ($staticServerList) {break}
|
||||
|
||||
# Stop previously started jobs if the server has been deleted
|
||||
@@ -154,15 +154,15 @@ while ($startTime -le $finishTime)
|
||||
|
||||
Stop-Job $job.Name
|
||||
|
||||
# Remove-Job
|
||||
# Remove-Job
|
||||
# leaving above commented-out allows use of Receive-Job to inspect the trace info emitted by the job
|
||||
}
|
||||
}
|
||||
|
||||
# set up start time for next evaluation of the server list
|
||||
|
||||
# set up start time for next evaluation of the server list
|
||||
$startTime = $startTime.AddMinutes($intervalMinutes)
|
||||
|
||||
# sleep until next start time to ensure telemetry continues to be gathered
|
||||
|
||||
# sleep until next start time to ensure telemetry continues to be gathered
|
||||
Write-Host "Sleeping until" $startTime "(UTC)"
|
||||
|
||||
do
|
||||
@@ -170,13 +170,13 @@ while ($startTime -le $finishTime)
|
||||
Start-Sleep 1
|
||||
|
||||
} until (([DateTime]::UtcNow) -ge $startTime)
|
||||
}
|
||||
}
|
||||
|
||||
# For a static server list this runner script terminates after one pass and the jobs are left running
|
||||
# Otherwise the jobs are stopped as they will at this point have concluded telemetry collection.
|
||||
# In both cases Job trace output can be inspected with Receive-Job unless the jobs are removed.
|
||||
# use Get-Job and Receive-Job [n] -Keep where n is the job number to see the output.
|
||||
|
||||
# use Get-Job and Receive-Job [n] -Keep where n is the job number to see the output.
|
||||
|
||||
if (-not $staticServerList)
|
||||
{
|
||||
# gathering period is complete, now stop all the jobs
|
||||
|
||||
@@ -31,21 +31,21 @@ This readme applies to the PowerShell scripts: PoolTelemetryJobRunner.ps1 and Po
|
||||
|
||||
## What do the PowerShell scripts do?
|
||||
|
||||
The scripts are used to extract telemetry data associated with SQL Database elastic database pools and elastic databases and upload it to a separate telemetry database.
|
||||
The scripts are used to extract telemetry data associated with SQL Database elastic database pools and elastic databases and upload it to a separate telemetry database.
|
||||
|
||||
There is a runner script, PoolTelemetryRunner.ps1, which needs to be modified for your environment to identify one or more servers on which elastic pools and databases are hosted and a telemetry database in which telemetry data is to be gathered. The runner script executes a function in the data collection script, PoolTelemetry.ps1 as a PowerShell job for each server.
|
||||
There is a runner script, PoolTelemetryRunner.ps1, which needs to be modified for your environment to identify one or more servers on which elastic pools and databases are hosted and a telemetry database in which telemetry data is to be gathered. The runner script executes a function in the data collection script, PoolTelemetry.ps1 as a PowerShell job for each server.
|
||||
|
||||
Each data collection job executes in the background on a pre-determined schedule and will on first execution create the required schema in the telemetry database. It then connects to the master database on the server and retrieves elastic pool telemetry data and loads that to the telemetry database. It can optionally look back 14 days on first execution to get all available telemetry.
|
||||
Each data collection job executes in the background on a pre-determined schedule and will on first execution create the required schema in the telemetry database. It then connects to the master database on the server and retrieves elastic pool telemetry data and loads that to the telemetry database. It can optionally look back 14 days on first execution to get all available telemetry.
|
||||
|
||||
It then optionally queries the master database to determine the current elastic databases on the server, resident in each of the pools identified in the prior step. It then connects to each database in turn and retrieves and loads telemetry data for that database. It then sleeps for a period before waking up and repeating the data collection cycle.
|
||||
It then optionally queries the master database to determine the current elastic databases on the server, resident in each of the pools identified in the prior step. It then connects to each database in turn and retrieves and loads telemetry data for that database. It then sleeps for a period before waking up and repeating the data collection cycle.
|
||||
|
||||
<a name=installing-the-scripts></a>
|
||||
|
||||
## Installing the scripts
|
||||
|
||||
Both scripts, PoolTelemetry.ps1 and PooltelemetryRunner.ps1, should be copied to the same directory.
|
||||
Both scripts, PoolTelemetry.ps1 and PooltelemetryRunner.ps1, should be copied to the same directory.
|
||||
|
||||
You must have installed and imported the latest (1.x) Azure PowerShell modules and SQL PowerShell (sqlps) modules. The cmdlet Invoke-SQLCmd is used to execute SQL scripts.
|
||||
You must have installed and imported the latest (1.x) Azure PowerShell modules and SQL PowerShell (sqlps) modules. The cmdlet Invoke-SQLCmd is used to execute SQL scripts.
|
||||
|
||||
<a name=customizing-the-pooltelemetryjobrunner-script></a>
|
||||
|
||||
@@ -55,7 +55,7 @@ The runner script should be customized to provide information about the servers
|
||||
|
||||
### Azure log in
|
||||
|
||||
The script requires you to log on to Azure with a Microsoft Id, either a personal Id or a work or school Id. The Id used must have read access to the servers in the subscription under which the pools and databases have been created (the telemetry database can be created under a different subscription).
|
||||
The script requires you to log on to Azure with a Microsoft Id, either a personal Id or a work or school Id. The Id used must have read access to the servers in the subscription under which the pools and databases have been created (the telemetry database can be created under a different subscription).
|
||||
|
||||
Set the SubscriptionName. If the Microsoft Id used to login has access to multiple Azure subscriptions this allows you to select the subscription under which the server(s) to be monitored were created. All servers to be reported on must be created under the same subscription.
|
||||
|
||||
@@ -63,52 +63,52 @@ Set the SubscriptionName. If the Microsoft Id used to login has access to multi
|
||||
|
||||
### SQL user names for source servers and the telemetry server
|
||||
|
||||
While the runner script uses ARM PowerShell cmdlets to gather information about resource groups and servers, the data collection script uses SQL queries to retrieve data. SQL user names and passwords must be provided at script run time and will be passed to each data collection script job to access the source servers and databases using SQL DMVs. The scripts assume the same SQL user name and password are used for all source servers. The telemetry server user name and password are provided separately and can be different.
|
||||
While the runner script uses ARM PowerShell cmdlets to gather information about resource groups and servers, the data collection script uses SQL queries to retrieve data. SQL user names and passwords must be provided at script run time and will be passed to each data collection script job to access the source servers and databases using SQL DMVs. The scripts assume the same SQL user name and password are used for all source servers. The telemetry server user name and password are provided separately and can be different.
|
||||
|
||||
User credentials are gathered via dialog boxes at run time to avoid storing passwords in the script. You can customize the script to add the user names for each dialog so that these do not need to be entered each time the script is run. To do this add a –UserName parameter to each of the two credentials.
|
||||
|
||||
```$sourceCred = Get-Credential -Message 'User name and password for source server' –UserName '<user name>' ```
|
||||
|
||||
```$outputServerCred = Get-Credential -Message 'User name and password for telemetry database’ –UserName '<user name>' ```
|
||||
```$outputServerCred = Get-Credential -Message 'User name and password for telemetry database’ –UserName '<user name>' ```
|
||||
|
||||
### Source resource group and server
|
||||
|
||||
Provide the resource group if data is to be gathered from all servers in a specific resource group or a specific server.
|
||||
|
||||
``` $resourceGroupName = '<resource group name>' ```
|
||||
``` $resourceGroupName = '<resource group name>' ```
|
||||
|
||||
Provide the server name if data is to be gathered from all a specific server.
|
||||
|
||||
``` $serverName = '<server name>' ```
|
||||
``` $serverName = '<server name>' ```
|
||||
|
||||
### Telemetry server and database
|
||||
|
||||
It is assumed that telemetry is to be loaded to an Azure SQL Database.
|
||||
It is assumed that telemetry is to be loaded to an Azure SQL Database.
|
||||
|
||||
Provide the telemetry database server name.
|
||||
Provide the telemetry database server name.
|
||||
|
||||
``` $outputServerName = '<telemetry server name>’ ```
|
||||
``` $outputServerName = '<telemetry server name>’ ```
|
||||
|
||||
Provide the telemetry database name.
|
||||
Provide the telemetry database name.
|
||||
|
||||
``` $outputDatabaseName = '<telemetry database name>' ```
|
||||
``` $outputDatabaseName = '<telemetry database name>' ```
|
||||
|
||||
### Define if the server to be monitored will change during the monitoring period
|
||||
|
||||
If the set of servers being monitored may change during the overall monitoring period then set $staticServerList to $false to cause server evaluation to be repeated periodically. If this is set to false, the runner script will run for the same duration as the job scripts, and will start additional jobs if new servers are added and stop jobs if servers are removed from the query scope. Otherwise if set to $true, the runner script will complete as soon as the jobs have been spawned.
|
||||
|
||||
``` $staticServerList = $true ```
|
||||
``` $staticServerList = $true ```
|
||||
|
||||
### Collection interval, lag-time and job duration
|
||||
|
||||
Provide the interval in minutes. This defines both how far back the data collection will look on each execution and the interval between executions. A value between 15-30 minutes is probably most appropriate. Note that fine-grained database telemetry (15 second averages) is only retained in each database for 60 minutes, beyond that it based on 5 minute averages. Pool telemetry in the master database is always based on 5 minute averages. Pool telemetry is not available immediately. A lag time of 30 minutes is programmed in the collection script. It is not recommended to change this lag setting. The effect of this is that the look-back window for pool data is pushed back, by this lag time so if gathering data for 15 interval the query window is -45 minutes to -30 minutes on each execution. Note that the lag time setting does not affect gathering 15s averaged telemetry from each database, which is available immediately. 5 minute averaged data is retained for 14 days.
|
||||
Provide the interval in minutes. This defines both how far back the data collection will look on each execution and the interval between executions. A value between 15-30 minutes is probably most appropriate. Note that fine-grained database telemetry (15 second averages) is only retained in each database for 60 minutes, beyond that it based on 5 minute averages. Pool telemetry in the master database is always based on 5 minute averages. Pool telemetry is not available immediately. A lag time of 30 minutes is programmed in the collection script. It is not recommended to change this lag setting. The effect of this is that the look-back window for pool data is pushed back, by this lag time so if gathering data for 15 interval the query window is -45 minutes to -30 minutes on each execution. Note that the lag time setting does not affect gathering 15s averaged telemetry from each database, which is available immediately. 5 minute averaged data is retained for 14 days.
|
||||
|
||||
``` $intervalMinutes = 15 ```
|
||||
``` $intervalMinutes = 15 ```
|
||||
|
||||
Provide the job duration in minutes. This defines how long the job will execute for in the background. A value of zero will cause the job to execute once only. The value is best
|
||||
Provide the job duration in minutes. This defines how long the job will execute for in the background. A value of zero will cause the job to execute once only. The value is best
|
||||
|
||||
``` $durationMinutes = 600 ```
|
||||
|
||||
``` $durationMinutes = 600 ```
|
||||
|
||||
### Load all available pool telemetry
|
||||
|
||||
In normal execution the spawned jobs look back 'window' is based on the interval and lag settings. For pool telemetry which is available for 14 days, the data collection script can be configured to look back 15 days on its first execution to ensue it gathers all available telemetry for each pool. Be careful if you stop the runner script and restart it on the same servers within this 15 day period as it may gather and load duplicate data entries. Using this option with many pools may load a large amount of data.
|
||||
@@ -117,7 +117,7 @@ In normal execution the spawned jobs look back 'window' is based on the interval
|
||||
|
||||
### Specify the source server(s) to use
|
||||
|
||||
The script allows either a single server to be specified or multiple. Several sample PowerShell scripted queries are provided but in general only one should be used, the others should be commented out. The script requires that the $servers variable is populated as input to the job execution. Either uncomment and use one of the queries that populates $servers or use one of the queries that populates $resourceList and then uncomment the section in the script that uses the $resourceList to populate $servers. If not using $resourceList leave this translation section commented out.
|
||||
The script allows either a single server to be specified or multiple. Several sample PowerShell scripted queries are provided but in general only one should be used, the others should be commented out. The script requires that the $servers variable is populated as input to the job execution. Either uncomment and use one of the queries that populates $servers or use one of the queries that populates $resourceList and then uncomment the section in the script that uses the $resourceList to populate $servers. If not using $resourceList leave this translation section commented out.
|
||||
|
||||
<a name=executing-the-runner-script></a>
|
||||
|
||||
@@ -125,13 +125,13 @@ The script allows either a single server to be specified or multiple. Several s
|
||||
|
||||
The runner script PoolTelemetryRunner.ps1 should be executed from within an Azure PowerShell context.
|
||||
|
||||
The script will prompt for Azure login and the user name and password for the source servers and the user name and password for the telemetry server. It will then spawn a PowerShell job for each server that has been identified within the script. Each job will run in the background for the time specified in $durationMinutes.
|
||||
The script will prompt for Azure login and the user name and password for the source servers and the user name and password for the telemetry server. It will then spawn a PowerShell job for each server that has been identified within the script. Each job will run in the background for the time specified in $durationMinutes.
|
||||
|
||||
It will gather data for the most recent period defined by the interval value and load this, then sleep until the next data gathering point, wake up, gather and load more data and then sleep again, etc.
|
||||
It will gather data for the most recent period defined by the interval value and load this, then sleep until the next data gathering point, wake up, gather and load more data and then sleep again, etc.
|
||||
|
||||
To see jobs in progress, use:
|
||||
|
||||
``` Get-Job ```
|
||||
``` Get-Job ```
|
||||
|
||||
To see the current console output from a specific job, use:
|
||||
|
||||
@@ -139,15 +139,15 @@ To see the current console output from a specific job, use:
|
||||
|
||||
If you don’t use *** –Keep***, the output is not retained (but doesn’t affect data collection)
|
||||
|
||||
To stop all jobs, use:
|
||||
To stop all jobs, use:
|
||||
|
||||
``` Stop-Job * ```
|
||||
``` Stop-Job * ```
|
||||
|
||||
Provide a job id to stop a specific job.
|
||||
|
||||
To remove all jobs, use:
|
||||
|
||||
``` Remove-Job * ```
|
||||
``` Remove-Job * ```
|
||||
|
||||
Provide a job id to remove a specific job.
|
||||
|
||||
@@ -157,7 +157,7 @@ Provide a job id to remove a specific job.
|
||||
|
||||
Use SSMS or other tools such as PowerBI to inspect and query the telemetry database. Data is gathered in two tables which are based on the equvalent DMVs:
|
||||
|
||||
- ***dbo.pool_resource_stats*** has the resource usage data for all the elastic pools in the specified servers for the specified duration.
|
||||
- ***dbo.pool_resource_stats*** has the resource usage data for all the elastic pools in the specified servers for the specified duration.
|
||||
|
||||
- ***dbo.db_resource_stats*** has the resource usage data for all elastic databases in the elastic pools for the specified duration.
|
||||
|
||||
@@ -169,7 +169,7 @@ For example, once the telemetry is being collected, this TVF can be called with
|
||||
|
||||
``` select top 10 * from [dbo].[get_aggregated_pool_metrics]('04/29/2016 21:00:00', '04/29/2016 23:00:00') order by avg_DTU_percent desc ```
|
||||
|
||||
Data can be queried while data collection is in progress.
|
||||
Data can be queried while data collection is in progress.
|
||||
|
||||
> [AZURE.NOTE] If the scripts are stopped and started again within a short period they may add duplicate rows to the telemetry tables.
|
||||
|
||||
@@ -179,11 +179,11 @@ Data can be queried while data collection is in progress.
|
||||
|
||||
A sample Power BI designer (PBIX) file is also provided in this location (which can be opened using PowerBI desktop tool). It provides a simple dashboard experience over the elastic pool data collected using the scripts described above. To use this PBIX file follow these steps
|
||||
|
||||
- Download the file and open it in [Power BI desktop tool](https://powerbi.microsoft.com/en-us/desktop/).
|
||||
- Download the file and open it in [Power BI desktop tool](https://powerbi.microsoft.com/en-us/desktop/).
|
||||
- Change the queries to point them to your telemetry database servers and database.
|
||||
- Refresh the report to get current data.
|
||||
- The report will show the busiest top 5 elastic pools over the last 6 hours, 24 hours and 7 days.
|
||||
- This report can also be published as a dashboard to your organization’s PowerBI site for use by others in your organization.
|
||||
- This report can also be published as a dashboard to your organization’s PowerBI site for use by others in your organization.
|
||||
|
||||
<a name=disclaimers></a>
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ $parameters = @{
|
||||
administratorLoginPassword = '<password>'
|
||||
}
|
||||
|
||||
Invoke-Command -ScriptBlock ([Scriptblock]::Create((iwr ($scriptUrlBase+'/attachJumpbox.ps1?t='+ [DateTime]::Now.Ticks)).Content)) -ArgumentList $parameters, $scriptUrlBase
|
||||
Invoke-Command -ScriptBlock ([Scriptblock]::Create((iwr ($scriptUrlBase+'/attachJumpbox.ps1?t='+ [DateTime]::Now.Ticks)).Content)) -ArgumentList $parameters, $scriptUrlBase
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ if ($managementSubnetName -eq '' -or ($null -eq $managementSubnetName)) {
|
||||
function VerifyPSVersion {
|
||||
Write-Host "Verifying PowerShell version."
|
||||
if ($PSVersionTable.PSEdition -eq "Desktop") {
|
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -or
|
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -or
|
||||
(($PSVersionTable.PSVersion.Major -eq 5) -and ($PSVersionTable.PSVersion.Minor -ge 1))) {
|
||||
Write-Host "PowerShell version verified." -ForegroundColor Green
|
||||
}
|
||||
@@ -44,7 +44,7 @@ function VerifyPSVersion {
|
||||
else {
|
||||
Write-Host "You need to install PowerShell version 6.0 or heigher." -ForegroundColor Red
|
||||
Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ function EnsureAzModule {
|
||||
Write-Host "Module Az installed." -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "Module Az imported." -ForegroundColor Green
|
||||
Write-Host "Module Az imported." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
@@ -184,6 +184,6 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -33,7 +33,7 @@ To run this sample, you need the following prerequisites.
|
||||
2. Azure PowerShell Az module
|
||||
|
||||
**Linux prerequisites**
|
||||
1. strongSwan
|
||||
1. strongSwan
|
||||
|
||||
**Azure prerequisites:**
|
||||
|
||||
@@ -56,7 +56,7 @@ $parameters = @{
|
||||
certificateNamePrefix = '<certificateNamePrefix>'
|
||||
}
|
||||
|
||||
Invoke-Command -ScriptBlock ([Scriptblock]::Create((iwr ($scriptUrlBase+'/attachVPNGateway.ps1?t='+ [DateTime]::Now.Ticks)).Content)) -ArgumentList $parameters, $scriptUrlBase
|
||||
Invoke-Command -ScriptBlock ([Scriptblock]::Create((iwr ($scriptUrlBase+'/attachVPNGateway.ps1?t='+ [DateTime]::Now.Ticks)).Content)) -ArgumentList $parameters, $scriptUrlBase
|
||||
|
||||
```
|
||||
|
||||
|
||||
+8
-8
@@ -20,7 +20,7 @@ if ($clientCertificatePassword -eq '' -or ($null -eq $clientCertificatePassword)
|
||||
function VerifyPSVersion {
|
||||
Write-Host "Verifying PowerShell version."
|
||||
if ($PSVersionTable.PSEdition -eq "Desktop") {
|
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -or
|
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -or
|
||||
(($PSVersionTable.PSVersion.Major -eq 5) -and ($PSVersionTable.PSVersion.Minor -ge 1))) {
|
||||
Write-Host "PowerShell version verified." -ForegroundColor Green
|
||||
}
|
||||
@@ -36,7 +36,7 @@ function VerifyPSVersion {
|
||||
else {
|
||||
Write-Host "You need to install PowerShell version 6.0 or heigher." -ForegroundColor Red
|
||||
Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ function EnsureAzModule {
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "Module Az imported." -ForegroundColor Green
|
||||
Write-Host "Module Az imported." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,11 +193,11 @@ function CreateCerificateWindows() {
|
||||
-HashAlgorithm sha256 -KeyLength 2048 `
|
||||
-CertStoreLocation "Cert:\CurrentUser\My" `
|
||||
-Signer $certificate -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2") | Out-null
|
||||
|
||||
|
||||
[Convert]::ToBase64String((Get-Item cert:\currentuser\my\$certificateThumbprint).RawData)
|
||||
}
|
||||
|
||||
function CreateCerificateOpenSsl() {
|
||||
function CreateCerificateOpenSsl() {
|
||||
$dn = "CN=$certificateNamePrefix" + "P2SRoot"
|
||||
ipsec pki --gen --outform pem > caKey.pem
|
||||
ipsec pki --self --in caKey.pem --dn $dn --ca --outform pem > caCert.pem
|
||||
@@ -206,12 +206,12 @@ function CreateCerificateOpenSsl() {
|
||||
ipsec pki --gen --outform pem > "$($dn)Key.pem"
|
||||
ipsec pki --pub --in "$($dn)Key.pem" --outform pem > "$($dn)PubKey.pem"
|
||||
ipsec pki --issue --in "$($dn)PubKey.pem" --cacert caCert.pem --cakey caKey.pem --dn "CN=$($dn)" --san $dn --flag clientAuth --outform pem > "$($dn)Cert.pem"
|
||||
|
||||
|
||||
openssl pkcs12 -in "$($dn)Cert.pem" -inkey "$($dn)Key.pem" -certfile caCert.pem -export -out "$($dn).p12" -password "pass:$($clientCertificatePassword)"
|
||||
#openssl pkcs12 -in "$($dn).p12" -password "pass:$($clientCertificatePassword)" -nocerts -out "$($dn)PrivateKey.pem" -nodes
|
||||
#openssl pkcs12 -in "$($dn).p12" -password "pass:$($clientCertificatePassword)" -nokeys -out "$($dn)PublicCert.pem" -nodes
|
||||
|
||||
$publicRootCertData = openssl x509 -in caCert.pem -outform pem
|
||||
$publicRootCertData = openssl x509 -in caCert.pem -outform pem
|
||||
$publicRootCertData = $publicRootCertData -replace "-----BEGIN CERTIFICATE-----", ""
|
||||
$publicRootCertData = $publicRootCertData -replace "-----END CERTIFICATE-----", ""
|
||||
[string]::Join("", $publicRootCertData.Split())
|
||||
@@ -264,7 +264,7 @@ Write-Host "Starting deployment..."
|
||||
Write-Host "Deployment will take about 1h." -ForegroundColor Yellow
|
||||
|
||||
$templateParameters = @{
|
||||
location = $virtualNetwork.Location
|
||||
location = $virtualNetwork.Location
|
||||
virtualNetworkName = $virtualNetworkName
|
||||
gatewaySubnetPrefix = $gatewaySubnetPrefix
|
||||
vpnClientAddressPoolPrefix = $vpnClientAddressPoolPrefix
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"vpnClientProtocols": [
|
||||
"IkeV2",
|
||||
"SSTP"
|
||||
],
|
||||
],
|
||||
"vpnClientRootCertificates": [
|
||||
{
|
||||
"name": "[variables('clientRootCertName')]",
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
This sample shows one approach to Managed Instance management automation using Function App and system-assigned identity.
|
||||
|
||||
With system-assigned identity, Function App could be assigned permissions to invoke proper actions in a safe way.
|
||||
With system-assigned identity, Function App could be assigned permissions to invoke proper actions in a safe way.
|
||||
|
||||
Instead of granting excessive permissions to users, admins could grant required permissions to Function App that exposes very restrained set of functionalities through API. Code running on Function App doesn't have any secrets configured or hardcoded.
|
||||
Instead of granting excessive permissions to users, admins could grant required permissions to Function App that exposes very restrained set of functionalities through API. Code running on Function App doesn't have any secrets configured or hardcoded.
|
||||
|
||||
Currently available functions:
|
||||
- Assign Azure AD Directory Readers permissions to Managed Instance principal
|
||||
@@ -96,7 +96,7 @@ else
|
||||
|
||||
```
|
||||
|
||||
### Note
|
||||
### Note
|
||||
|
||||
In step 3. use `Get publish profile` to get user name and password. If you are using PowerShell to upload package, put user name and password under single quotes as with double quotes character `$` has special meaning.
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ namespace ManagedInstanceAutomation
|
||||
|
||||
if (string.IsNullOrEmpty(tenantId))
|
||||
throw new Exception($"[MSI Not Assigned]: '{managedInstance.Id}'");
|
||||
|
||||
|
||||
var directoryReadersRole = await GetAzureADDirectoryRoleAsync(tenantId, "Directory Readers");
|
||||
|
||||
return (await AddMemberToAzureADRole(tenantId, directoryReadersRole.ObjectId, principalId).ConfigureAwait(false)) ?
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ declare @verbose int = 0; -- change to 1 to get more verbose comparison;
|
||||
declare @source xml = '<place source XML result here>';
|
||||
declare @target xml = '<place target XML result here>';
|
||||
|
||||
with
|
||||
with
|
||||
src as(
|
||||
select property = x.v.value('name[1]', 'nvarchar(300)'),
|
||||
value = x.v.value('value[1]', 'nvarchar(300)')
|
||||
|
||||
+12
-12
@@ -2,35 +2,35 @@ USE <database_name, , > --> put the database name here like WideWorldImporters
|
||||
|
||||
begin
|
||||
declare @result NVARCHAR(MAX);
|
||||
set @result = (select database_name = name, compatibility_level, recovery_model_desc, snapshot_isolation_state_desc, is_read_committed_snapshot_on,
|
||||
set @result = (select database_name = name, compatibility_level, recovery_model_desc, snapshot_isolation_state_desc, is_read_committed_snapshot_on,
|
||||
is_auto_update_stats_on, is_auto_update_stats_async_on, delayed_durability_desc,
|
||||
is_encrypted, is_auto_create_stats_incremental_on, is_arithabort_on, is_ansi_warnings_on, is_parameterization_forced
|
||||
from sys.databases
|
||||
where name = db_name()
|
||||
for xml raw('db'), elements);
|
||||
set @result += (select compatibility_level, snapshot_isolation_state_desc, is_read_committed_snapshot_on,
|
||||
set @result += (select compatibility_level, snapshot_isolation_state_desc, is_read_committed_snapshot_on,
|
||||
is_auto_update_stats_on, is_auto_update_stats_async_on, delayed_durability_desc,
|
||||
is_encrypted, is_auto_create_stats_incremental_on, is_arithabort_on, is_ansi_warnings_on, is_parameterization_forced,
|
||||
number_of_files = (select count(*) from master.sys.master_files where database_id = db_id('tempdb'))
|
||||
from sys.databases
|
||||
where name = 'tempdb'
|
||||
where name = 'tempdb'
|
||||
for xml raw('tempdb'), elements);
|
||||
set @result += ISNULL((
|
||||
select name = CONCAT('DB-CONFIG:',name), value
|
||||
from sys.database_scoped_configurations
|
||||
for xml raw, elements ),'');
|
||||
declare @tf table (TraceFlag smallint, status bit,global bit, session bit)
|
||||
declare @tf table (TraceFlag smallint, status bit,global bit, session bit)
|
||||
insert into @tf execute('DBCC TRACESTATUS(-1)');
|
||||
set @result += ISNULL((
|
||||
select name=CONCAT('TF:',TraceFlag), value=status from @tf
|
||||
where global=1 and session=0
|
||||
and (TraceFlag in (8690 -- https://blogs.msdn.microsoft.com/psssql/2015/12/15/spool-operator-and-trace-flag-8690/
|
||||
, 8744, 9347, 9349, 9471, 9476, 9488 -- Plan affecting TFs include others such as
|
||||
, 9453, 9495 -- Execution related TFs
|
||||
, 9453, 9495 -- Execution related TFs
|
||||
, 4199, 9481 /*force legacy CE*/, 2312 /* force default CE */
|
||||
--https://kohera.be/blog/sql-server/trace-flags-sql-servers-transformer-like-tuning/
|
||||
, 1118, 2371, 610, 1117, 8048, 1236, 8015, 834, 1224, 2335,
|
||||
-- Taking care of Query-Hint-Hell
|
||||
, 1118, 2371, 610, 1117, 8048, 1236, 8015, 834, 1224, 2335,
|
||||
-- Taking care of Query-Hint-Hell
|
||||
4136, 8602, 8722, 8755,
|
||||
-- random trace flags aka.ms/traceflags
|
||||
634, 3459, 3468, 3505, 9495,
|
||||
@@ -61,15 +61,15 @@ set @result += isnull
|
||||
for xml raw('instance'), elements),''
|
||||
);
|
||||
|
||||
set @result +=
|
||||
isnull((SELECT name = REPLACE([type], 'MEMORYCLERK_', 'MEMORY:')
|
||||
set @result +=
|
||||
isnull((SELECT name = REPLACE([type], 'MEMORYCLERK_', 'MEMORY:')
|
||||
, value = CAST(sum(pages_kb)/1024.1/1024 AS NUMERIC(6,1))
|
||||
FROM sys.dm_os_memory_clerks
|
||||
GROUP BY type
|
||||
HAVING sum(pages_kb) /1024. /1024 > 1
|
||||
for xml raw, elements),'');
|
||||
|
||||
set @result +=
|
||||
set @result +=
|
||||
isnull((
|
||||
select name = 'INDEX:'+schema_name(schema_id)+'.'+object_name(t.object_id)+'.'+ix.name,
|
||||
value = concat(ix.type_desc COLLATE SQL_Latin1_General_CP1_CI_AS,
|
||||
@@ -79,9 +79,9 @@ isnull((
|
||||
where ix.type <> 0
|
||||
for xml raw, elements),'');
|
||||
|
||||
set @result +=
|
||||
set @result +=
|
||||
isnull((
|
||||
select name = 'JOB::'+j.name + '/' + s.step_name,
|
||||
select name = 'JOB::'+j.name + '/' + s.step_name,
|
||||
value = subsystem
|
||||
from msdb.dbo.sysjobs j
|
||||
join msdb.dbo.sysjobsteps s on j.job_id = s.job_id
|
||||
|
||||
@@ -12,7 +12,7 @@ $NScollections = "System.Collections.Generic"
|
||||
function VerifyPSVersion {
|
||||
Write-Host "Verifying PowerShell version."
|
||||
if ($PSVersionTable.PSEdition -eq "Desktop") {
|
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -or
|
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -or
|
||||
(($PSVersionTable.PSVersion.Major -eq 5) -and ($PSVersionTable.PSVersion.Minor -ge 1))) {
|
||||
Write-Host "PowerShell version verified." -ForegroundColor Green
|
||||
}
|
||||
@@ -28,7 +28,7 @@ function VerifyPSVersion {
|
||||
else {
|
||||
Write-Host "You need to install PowerShell version 6.0 or heigher." -ForegroundColor Red
|
||||
Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ function EnsureAzModule {
|
||||
Write-Host "Module Az installed." -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "Module Az imported." -ForegroundColor Green
|
||||
Write-Host "Module Az imported." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ function VerifyDelegation {
|
||||
$subnet
|
||||
)
|
||||
|
||||
$result = @{
|
||||
$result = @{
|
||||
isDelegatedToManagedInstance = $false;
|
||||
isDelegated = $false;
|
||||
success = $false;
|
||||
@@ -186,7 +186,7 @@ function LoadNetworkSecurityGroup {
|
||||
$null -ne $subnet.NetworkSecurityGroup
|
||||
)
|
||||
{
|
||||
$nsgSegments = ($subnet.NetworkSecurityGroup.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$nsgSegments = ($subnet.NetworkSecurityGroup.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$nsgName = $nsgSegments[-1].Trim()
|
||||
$nsgResourceGroup = $nsgSegments[3].Trim()
|
||||
$networkSecurityGroup = Get-AzNetworkSecurityGroup -ResourceGroupName $nsgResourceGroup -Name $nsgName
|
||||
@@ -203,7 +203,7 @@ function HasNSG {
|
||||
param (
|
||||
$subnet
|
||||
)
|
||||
|
||||
|
||||
$nsg = LoadNetworkSecurityGroup $subnet
|
||||
return $nsg -ne $null
|
||||
}
|
||||
@@ -217,7 +217,7 @@ function LoadRouteTable {
|
||||
$null -ne $subnet.RouteTable
|
||||
)
|
||||
{
|
||||
$rtSegments = ($subnet.RouteTable.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$rtSegments = ($subnet.RouteTable.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$rtName = $rtSegments[-1].Trim()
|
||||
$rtResourceGroup = $rtSegments[3].Trim()
|
||||
$routeTable = Get-AzRouteTable -ResourceGroupName $rtResourceGroup -Name $rtName
|
||||
@@ -231,7 +231,7 @@ function HasRouteTable {
|
||||
param (
|
||||
$subnet
|
||||
)
|
||||
|
||||
|
||||
$routeTable = LoadRouteTable $subnet
|
||||
return $routeTable -ne $null
|
||||
}
|
||||
@@ -242,7 +242,7 @@ function CreateNSG
|
||||
$virtualNetwork,
|
||||
$subnet
|
||||
)
|
||||
|
||||
|
||||
Write-Host "Creating Network security group."
|
||||
$networkSecurityGroupName = "nsgManagedInstance" + (Get-Random -Maximum 1000)
|
||||
|
||||
@@ -367,11 +367,11 @@ If($delegationVerificationResult['success'])
|
||||
$hasNsg = HasNSG $subnet
|
||||
$hasRouteTable = HasRouteTable $subnet
|
||||
$isValid = $delegationVerificationResult['isDelegatedToManagedInstance'] -and $hasNsg -and $hasRouteTable
|
||||
|
||||
|
||||
If($isValid -ne $true)
|
||||
{
|
||||
Write-Host
|
||||
Write-Host("---------- To delegate the virtual network subnet for Managed Instance this script will: --------------- ") -ForegroundColor Yellow
|
||||
Write-Host("---------- To delegate the virtual network subnet for Managed Instance this script will: --------------- ") -ForegroundColor Yellow
|
||||
Write-Host
|
||||
|
||||
If(-not $hasNsg)
|
||||
@@ -390,55 +390,55 @@ If($delegationVerificationResult['success'])
|
||||
}
|
||||
|
||||
Write-Host
|
||||
Write-Host("-------------------------------------------------------------------------------------------------------- ") -ForegroundColor Yellow
|
||||
Write-Host("-------------------------------------------------------------------------------------------------------- ") -ForegroundColor Yellow
|
||||
Write-Host
|
||||
|
||||
|
||||
|
||||
|
||||
$applyChanges = $force
|
||||
|
||||
|
||||
If($applyChanges -ne $true)
|
||||
{
|
||||
$reply = Read-Host -Prompt "Do you want to make these changes? [y/n]"
|
||||
$applyChanges = $reply -match "[yY]"
|
||||
$applyChanges = $reply -match "[yY]"
|
||||
Write-Host
|
||||
}
|
||||
|
||||
If ($applyChanges)
|
||||
{
|
||||
|
||||
If ($applyChanges)
|
||||
{
|
||||
If(-not $hasNsg)
|
||||
{
|
||||
CreateNSG $virtualNetwork $subnet
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
If(-not $hasRouteTable)
|
||||
{
|
||||
CreateRouteTable $virtualNetwork $subnet
|
||||
}
|
||||
}
|
||||
|
||||
If(-not $delegationVerificationResult['isDelegatedToManagedInstance'])
|
||||
{
|
||||
DelegateSubnet $subnet
|
||||
}
|
||||
|
||||
|
||||
SetVirtualNetwork $virtualNetwork
|
||||
|
||||
|
||||
Write-Host
|
||||
Write-Host "Subnet delegated to the Managed Instance." -ForegroundColor Green
|
||||
Write-Host "Subnet delegated to the Managed Instance." -ForegroundColor Green
|
||||
Write-Host "https://portal.azure.com/#create/Microsoft.SQLManagedInstance"
|
||||
}
|
||||
Else
|
||||
{
|
||||
Write-Host
|
||||
Write-Host "Subnet delegation canceled." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
Else
|
||||
{
|
||||
Write-Host "Subnet is already delegated to the Managed Instance." -ForegroundColor Green
|
||||
Write-Host "Subnet is already delegated to the Managed Instance." -ForegroundColor Green
|
||||
Write-Host "https://portal.azure.com/#create/Microsoft.SQLManagedInstance"
|
||||
}
|
||||
}
|
||||
}
|
||||
Else
|
||||
Else
|
||||
{
|
||||
Write-Host
|
||||
Write-Host "Subnet is already delegated to other service." -ForegroundColor Red
|
||||
|
||||
@@ -12,7 +12,7 @@ $NScollections = "System.Collections.Generic"
|
||||
function VerifyPSVersion {
|
||||
Write-Host "Verifying PowerShell version."
|
||||
if ($PSVersionTable.PSEdition -eq "Desktop") {
|
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -or
|
||||
if (($PSVersionTable.PSVersion.Major -ge 6) -or
|
||||
(($PSVersionTable.PSVersion.Major -eq 5) -and ($PSVersionTable.PSVersion.Minor -ge 1))) {
|
||||
Write-Host "PowerShell version verified." -ForegroundColor Green
|
||||
}
|
||||
@@ -28,7 +28,7 @@ function VerifyPSVersion {
|
||||
else {
|
||||
Write-Host "You need to install PowerShell version 6.0 or heigher." -ForegroundColor Red
|
||||
Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ function EnsureAzModule {
|
||||
Write-Host "Module Az installed." -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "Module Az imported." -ForegroundColor Green
|
||||
Write-Host "Module Az imported." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ function ConvertCidrToUint32Array
|
||||
{
|
||||
return @(0, [System.Int32]::MaxValue)
|
||||
}
|
||||
|
||||
|
||||
$ipnum = ([Convert]::ToUInt32($cidrRangeParts[0]) -shl 24) -bor `
|
||||
([Convert]::ToUInt32($cidrRangeParts[1]) -shl 16) -bor `
|
||||
([Convert]::ToUInt32($cidrRangeParts[2]) -shl 8) -bor `
|
||||
@@ -96,7 +96,7 @@ function ConvertCidrToUint32Array
|
||||
function ContainsCidr
|
||||
{
|
||||
param(
|
||||
$cidrRangeA,
|
||||
$cidrRangeA,
|
||||
$cidrRangeB
|
||||
)
|
||||
$a = ConvertCidrToUint32Array $cidrRangeA
|
||||
@@ -203,8 +203,8 @@ function VerifySubnet {
|
||||
Break
|
||||
}
|
||||
Else {
|
||||
Write-Host "Passed Validation - There are no conflicting resources inside the subnet." -ForegroundColor Green
|
||||
}
|
||||
Write-Host "Passed Validation - There are no conflicting resources inside the subnet." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
Else {
|
||||
Write-Host "Failed Validation - Subnet is already in use." -ForegroundColor Red
|
||||
@@ -258,7 +258,7 @@ function LoadNetworkSecurityGroup {
|
||||
$null -ne $subnet.NetworkSecurityGroup
|
||||
)
|
||||
{
|
||||
$nsgSegments = ($subnet.NetworkSecurityGroup.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$nsgSegments = ($subnet.NetworkSecurityGroup.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$nsgName = $nsgSegments[-1].Trim()
|
||||
$nsgResourceGroup = $nsgSegments[3].Trim()
|
||||
$networkSecurityGroup = Get-AzNetworkSecurityGroup -ResourceGroupName $nsgResourceGroup -Name $nsgName
|
||||
@@ -322,7 +322,7 @@ function VerifyAddressPrefix {
|
||||
$nsgRuleAddressPrefixes,
|
||||
$securityRuleAddressPrefix
|
||||
)
|
||||
|
||||
|
||||
ForEach($nsgRuleAddressPrefix in $nsgRuleAddressPrefixes) {
|
||||
If($nsgRuleAddressPrefix -eq "*"){
|
||||
return $true;
|
||||
@@ -347,7 +347,7 @@ function VerifyDenyRuleAddressPrefix {
|
||||
$nsgRuleAddressPrefixes,
|
||||
$securityRuleAddressPrefix
|
||||
)
|
||||
|
||||
|
||||
ForEach($nsgRuleAddressPrefix in $nsgRuleAddressPrefixes) {
|
||||
If($nsgRuleAddressPrefix -eq "*"){
|
||||
return $true;
|
||||
@@ -368,7 +368,7 @@ function VerifyDenyRuleAddressPrefix {
|
||||
}
|
||||
|
||||
function IsPrivateCidr
|
||||
{
|
||||
{
|
||||
param($cidrRange)
|
||||
return `
|
||||
(ContainsCidr "10.0.0.0/8" $cidrRange) -or `
|
||||
@@ -399,7 +399,7 @@ function VerifyPort {
|
||||
$nsgRulePorts,
|
||||
$securityRulePort
|
||||
)
|
||||
|
||||
|
||||
ForEach($nsgRulePort in $nsgRulePorts) {
|
||||
If($true -eq (ContainsPort -nsgRulePort $nsgRulePort -securityRulePort $securityRulePort)) {
|
||||
return $true
|
||||
@@ -477,7 +477,7 @@ function VerifyNSGRules {
|
||||
ForEach($nsgRule in $nsgRules){
|
||||
If(VerifyNSGRule -securityRule $securityRule -nsgRule $nsgRule){
|
||||
return ($nsgRule.Access -eq "Allow")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
@@ -490,10 +490,10 @@ function VerifyNSG {
|
||||
)
|
||||
$securityRules = DefineSecurityRules
|
||||
|
||||
$result = @{
|
||||
$result = @{
|
||||
nsgSecurityRules = New-Object "$NScollections.List``1[$NSnetworkModels.PSSecurityRule]"
|
||||
failedSecurityRules = New-Object "$NScollections.List``1[$NSnetworkModels.PSSecurityRule]"
|
||||
success = $false
|
||||
success = $false
|
||||
}
|
||||
Write-Host("Verifying Network security group for subnet '{0}'."-f $subnet.Name)
|
||||
$nsg = LoadNetworkSecurityGroup $subnet
|
||||
@@ -530,7 +530,7 @@ function LoadRouteTable {
|
||||
$null -ne $subnet.RouteTable
|
||||
)
|
||||
{
|
||||
$rtSegments = ($subnet.RouteTable.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$rtSegments = ($subnet.RouteTable.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$rtName = $rtSegments[-1].Trim()
|
||||
$rtResourceGroup = $rtSegments[3].Trim()
|
||||
$routeTable = Get-AzRouteTable -ResourceGroupName $rtResourceGroup -Name $rtName
|
||||
@@ -544,7 +544,7 @@ function RequiredRoutes{
|
||||
param (
|
||||
$subnet
|
||||
)
|
||||
|
||||
|
||||
$subnet_to_vnetlocal = New-AzRouteConfig -Name "prepare-subnet-to-vnetlocal" -AddressPrefix $subnet.AddressPrefix[0] -NextHopType VnetLocal
|
||||
$mi_13_64_11_nexthop_internet = New-AzRouteConfig -Name "prepare-mi-13-64-11-nexthop-internet" -AddressPrefix 13.64.0.0/11 -NextHopType Internet
|
||||
$mi_13_96_13_nexthop_internet = New-AzRouteConfig -Name "prepare-mi-13-96-13-nexthop-internet" -AddressPrefix 13.96.0.0/13 -NextHopType Internet
|
||||
@@ -645,7 +645,7 @@ function RequiredRoutes{
|
||||
$mi_213_199_128_18_nexthop_internet = New-AzRouteConfig -Name "prepare-mi-213-199-128-18-nexthop-internet" -AddressPrefix 213.199.128.0/18 -NextHopType Internet
|
||||
$mi_216_32_180_22_nexthop_internet = New-AzRouteConfig -Name "prepare-mi-216-32-180-22-nexthop-internet" -AddressPrefix 216.32.180.0/22 -NextHopType Internet
|
||||
$mi_216_220_208_20_nexthop_internet = New-AzRouteConfig -Name "prepare-mi-216-220-208-20-nexthop-internet" -AddressPrefix 216.220.208.0/20 -NextHopType Internet
|
||||
|
||||
|
||||
$requiredRoutes = New-Object "$NScollections.List``1[$NSnetworkModels.PSRoute]"
|
||||
$requiredRoutes.Add($subnet_to_vnetlocal)
|
||||
$requiredRoutes.Add($mi_13_64_11_nexthop_internet)
|
||||
@@ -755,12 +755,12 @@ function VerifyRouteTable {
|
||||
param (
|
||||
$subnet
|
||||
)
|
||||
$result = @{
|
||||
$result = @{
|
||||
routes = New-Object "$NScollections.List``1[$NSnetworkModels.PSRoute]"
|
||||
hasRouteTable = $false
|
||||
success = $false
|
||||
success = $false
|
||||
}
|
||||
|
||||
|
||||
Write-Host("Verifying Route table for subnet '{0}'."-f $subnet.Name)
|
||||
|
||||
$requiredRoutes = RequiredRoutes $subnet
|
||||
@@ -820,7 +820,7 @@ function VerifyRouteTable {
|
||||
}
|
||||
|
||||
$result['success'] = $result['hasRouteTable'] -and $hasCompatibleRoutes
|
||||
}
|
||||
}
|
||||
Else
|
||||
{
|
||||
$result['success'] = $false
|
||||
@@ -843,7 +843,7 @@ function VerifyRouteTable {
|
||||
Write-Host "Warning - There is no route table on the subnet." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
@@ -863,13 +863,13 @@ function PrepareServiceDelegation
|
||||
{
|
||||
Write-Host "Failed: $_" -ForegroundColor Red
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
function PrepareNSG
|
||||
{
|
||||
param(
|
||||
$nsgVerificationResult,
|
||||
$virtualNetwork,
|
||||
$virtualNetwork,
|
||||
$subnet
|
||||
)
|
||||
Write-Host "Creating Network security group."
|
||||
@@ -920,7 +920,7 @@ function PrepareRouteTable
|
||||
{
|
||||
param(
|
||||
$routeTableVerificationResult,
|
||||
$virtualNetwork,
|
||||
$virtualNetwork,
|
||||
$subnet
|
||||
)
|
||||
Write-Host "Creating Route table."
|
||||
@@ -979,12 +979,12 @@ $isValid = $isOkServiceEndpoints -and $isOkNSG -and $isOkRouteTable -and $isOkSe
|
||||
If($isValid -ne $true)
|
||||
{
|
||||
Write-Host
|
||||
Write-Host("---------- To prepare the virtual network subnet for Managed Instance this script will: --------------- ") -ForegroundColor Yellow
|
||||
Write-Host("---------- To prepare the virtual network subnet for Managed Instance this script will: --------------- ") -ForegroundColor Yellow
|
||||
Write-Host
|
||||
If($isOkServiceEndpoints -ne $true)
|
||||
{
|
||||
Write-Host "[Endpoints] Remove all service endpoints." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
If($isOkNSG -ne $true)
|
||||
{
|
||||
Write-Host "[NSG] Create a copy of assoicated Network security group and add security rules to:" -ForegroundColor Yellow
|
||||
@@ -992,7 +992,7 @@ If($isValid -ne $true)
|
||||
Write-Host ("[NSG] -"+$rule.Description) -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host "[NSG] Associate newly created Network security group to subnet." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
If($isOkRouteTable -ne $true)
|
||||
{
|
||||
If($hasRouteTable -eq $true)
|
||||
@@ -1008,34 +1008,34 @@ If($isValid -ne $true)
|
||||
If($isOkServiceDelegation -ne $true)
|
||||
{
|
||||
Write-Host "[Service Delegation] Add Microsoft.Sql/managedInstances as a service delegation for subnet" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
Write-Host
|
||||
Write-Host("-------------------------------------------------------------------------------------------------------- ") -ForegroundColor Yellow
|
||||
Write-Host("-------------------------------------------------------------------------------------------------------- ") -ForegroundColor Yellow
|
||||
Write-Host
|
||||
|
||||
|
||||
$applyChanges = $force
|
||||
|
||||
|
||||
If($applyChanges -ne $true)
|
||||
{
|
||||
$reply = Read-Host -Prompt "Do you want to make these changes? [y/n]"
|
||||
$applyChanges = $reply -match "[yY]"
|
||||
$applyChanges = $reply -match "[yY]"
|
||||
Write-Host
|
||||
}
|
||||
|
||||
If ($applyChanges)
|
||||
{
|
||||
|
||||
If ($applyChanges)
|
||||
{
|
||||
|
||||
If($isOkNSG -ne $true)
|
||||
{
|
||||
PrepareNSG $nsgVerificationResult $virtualNetwork $subnet
|
||||
}
|
||||
}
|
||||
|
||||
If($isOkRouteTable -ne $true)
|
||||
{
|
||||
PrepareRouteTable $routeTableVerificationResult $virtualNetwork $subnet
|
||||
}
|
||||
|
||||
|
||||
If($isOkNSG -ne $true)
|
||||
{
|
||||
PrepareNSG $nsgVerificationResult $virtualNetwork $subnet
|
||||
@@ -1048,7 +1048,7 @@ If($isValid -ne $true)
|
||||
SetVirtualNetwork $virtualNetwork
|
||||
|
||||
Write-Host
|
||||
Write-Host "Subnet prepared for the Managed Instance." -ForegroundColor Green
|
||||
Write-Host "Subnet prepared for the Managed Instance." -ForegroundColor Green
|
||||
Write-Host "https://portal.azure.com/#create/Microsoft.SQLManagedInstance"
|
||||
}
|
||||
Else
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@ $password = $parameters['password']
|
||||
|
||||
$Assem = @()
|
||||
|
||||
$Source = @"
|
||||
$Source = @"
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -321,9 +321,9 @@ namespace CL
|
||||
|
||||
"@
|
||||
|
||||
Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp -ErrorAction SilentlyContinue
|
||||
Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp -ErrorAction SilentlyContinue
|
||||
|
||||
function EnsureLogin ()
|
||||
function EnsureLogin ()
|
||||
{
|
||||
$context = Get-AzureRmContext
|
||||
If( $null -eq $context.Subscription)
|
||||
@@ -454,7 +454,7 @@ Write-Host "Adding TDE certificate."
|
||||
Try
|
||||
{
|
||||
Add-AzureRmSqlManagedInstanceTransparentDataEncryptionCertificate -ResourceGroupName $resourceGroupName -ManagedInstanceName $managedInstanceName -PrivateBlob $securePrivateBlob -Password $securePassword -ErrorAction Stop | Out-Null
|
||||
Write-Host "TDE certificate added." -ForegroundColor Green
|
||||
Write-Host "TDE certificate added." -ForegroundColor Green
|
||||
}
|
||||
Catch
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ $publicCertificateFile = $parameters['publicCertificateFile']
|
||||
|
||||
$Assem = @()
|
||||
|
||||
$Source = @"
|
||||
$Source = @"
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
@@ -44,7 +44,7 @@ namespace CL
|
||||
/// <summary>
|
||||
/// ResetConnectionSkipTran TDS Message Status
|
||||
/// Reset the connection before processing event but do not modify the transaction
|
||||
/// state (the state will remain the same before and after the reset).
|
||||
/// state (the state will remain the same before and after the reset).
|
||||
/// </summary>
|
||||
ResetConnectionSkipTran = 0x10
|
||||
}
|
||||
@@ -643,7 +643,7 @@ Using-Object($stream = New-Object CL.TDSStream($stream, [TimeSpan]::FromSeconds(
|
||||
$sslStream = New-Object System.Net.Security.SslStream($stream, $true, {$true})
|
||||
$sslStream.AuthenticateAsClient($hostName)
|
||||
$certificate = $sslStream.RemoteCertificate
|
||||
[System.IO.File]::WriteAllBytes($publicCertificateFile,$certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert))
|
||||
[System.IO.File]::WriteAllBytes($publicCertificateFile,$certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Client {
|
||||
com.sun.security.auth.module.Krb5LoginModule required
|
||||
useTicketCache=false;
|
||||
Client {
|
||||
com.sun.security.auth.module.Krb5LoginModule required
|
||||
useTicketCache=false;
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
# HDFS Kerberos Tester
|
||||
Use the HDFS Kerberos tester to troubleshoot HDFS Kerberos connections for PolyBase, when you experience HDFS Kerberos failure while creating an external table in a Kerberos secured HDFS cluster.
|
||||
Use the HDFS Kerberos tester to troubleshoot HDFS Kerberos connections for PolyBase, when you experience HDFS Kerberos failure while creating an external table in a Kerberos secured HDFS cluster.
|
||||
|
||||
This tool will assist in ruling out non-SQL Server issues, to help you concentrate on resolving HDFS Kerberos setup issues, namely identifying the following issues:
|
||||
- Username/password misconfigurations
|
||||
- Cluster Kerberos setup misconfigurations
|
||||
- Cluster Kerberos setup misconfigurations
|
||||
|
||||
## Prerequisites
|
||||
This tool is completely independent from SQL Server. It is available as a Jupyter Notebook, and requires:
|
||||
@@ -18,7 +18,7 @@ This tool is completely independent from SQL Server. It is available as a Jupyte
|
||||
2. Open Azure Data Studio.
|
||||
|
||||
3. In Azure Data studio click the **File** top menu -> **Open File** -> and navigate to the folder where you saved the `hdfs-kerberos-tester.ipynb` file. Choose the `hdfs-kerberos-tester.ipynb` file and click open.
|
||||
|
||||
|
||||
4. After the Notebook has loaded, choose **Python3** as kernel. For more information on using Notebooks with the Python kernel, see [Configure Python for Notebooks](https://docs.microsoft.com/sql/azure-data-studio/sql-notebooks#configure-python-for-notebooks).
|
||||
|
||||
5. Click on all **RunCells** button in the Notebook and follow instruction in the Notebook.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"version": "0.2",
|
||||
"name": "Custom Overrides",
|
||||
"rules":[
|
||||
"name": "Custom Overrides",
|
||||
"rules":[
|
||||
{
|
||||
"id": "LatestCU",
|
||||
"id": "LatestCU",
|
||||
"itemType": "override",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"id": ["TraceFlag"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"id": ["TraceFlag"],
|
||||
"itemType": "override",
|
||||
"enabled": false
|
||||
},
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"id": ["DefaultRuleset"],
|
||||
"itemType": "override",
|
||||
@@ -22,5 +22,5 @@
|
||||
},
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"version": "0.2",
|
||||
"name": "Custom Checks Ruleset",
|
||||
"name": "Custom Checks Ruleset",
|
||||
"rules":[
|
||||
{
|
||||
"target": {
|
||||
|
||||
@@ -40,6 +40,6 @@ Get-SqlDatabase -ServerInstance 'localhost' | Invoke-SqlAssessment
|
||||
|
||||
To learn more about SQL Assessment API such as customizing and extending the ruleset, saving the results in a table, etc., please visit:
|
||||
|
||||
- Docs online page: https://docs.microsoft.com/sql/sql-assessment-api/sql-assessment-api-overview
|
||||
- Docs online page: https://docs.microsoft.com/sql/sql-assessment-api/sql-assessment-api-overview
|
||||
- GitHub repo: http://aka.ms/sql-assessment-api
|
||||
- SQL Assessment API Tutorial notebook: [SQLAssessmentAPITutorialNotebook.ipynb](./notebooks/SQLAssessmentAPITutorialNotebook.ipynb)
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@
|
||||
while (Prompt(out string? line))
|
||||
{
|
||||
// Use GetAssessmentResultsList to run assessment
|
||||
List<IAssessmentResult> assessmentResults = string.IsNullOrWhiteSpace(line)
|
||||
List<IAssessmentResult> assessmentResults = string.IsNullOrWhiteSpace(line)
|
||||
? await target.GetAssessmentResultsList().ConfigureAwait(false) // all checks
|
||||
: await target.GetAssessmentResultsList(line.Split()).ConfigureAwait(false); // selected checks
|
||||
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ The next example extends the previous check with support for clustered servers.
|
||||
],
|
||||
"condition": [
|
||||
{ "not": "@attr::service::SQLAgent" },
|
||||
{
|
||||
{
|
||||
"ine": [
|
||||
"@attr::service::SQLAgent.account",
|
||||
"@localServiceAccount"
|
||||
|
||||
@@ -34,7 +34,7 @@ Take the following example:
|
||||
"message": "Create index on @{Table} with key columns @{KeyCols}@{IncludedCols: and included columns: #}"
|
||||
```
|
||||
|
||||
When `@IncludedCols` is empty the message would be
|
||||
When `@IncludedCols` is empty the message would be
|
||||
|
||||
```plain
|
||||
Create index on MyTable with key columns Id, Name
|
||||
|
||||
@@ -22,7 +22,7 @@ For example, the following rule makes a check with the `MaxMemory` ID appear in
|
||||
{
|
||||
"id": "MaxMemory",
|
||||
"itemType": "definition",
|
||||
"target":
|
||||
"target":
|
||||
{
|
||||
"type": "Server",
|
||||
"version": "[11.0,)"
|
||||
@@ -47,7 +47,7 @@ Note that the rule override can modify checks for selected targets if needed. Th
|
||||
{
|
||||
"id": "MaxMemory",
|
||||
"itemType": "override",
|
||||
"targetFilter":
|
||||
"targetFilter":
|
||||
{
|
||||
"engineEdition": "Standard"
|
||||
},
|
||||
@@ -57,7 +57,7 @@ Note that the rule override can modify checks for selected targets if needed. Th
|
||||
{
|
||||
"id": "MaxMemory",
|
||||
"itemType": "override",
|
||||
"targetFilter":
|
||||
"targetFilter":
|
||||
{
|
||||
"engineEdition": "Express"
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ The `rules` and `probes` properties are optional because rules from one ruleset
|
||||
… implementation 2 …
|
||||
},
|
||||
|
||||
…
|
||||
…
|
||||
],
|
||||
"probe2": [
|
||||
…
|
||||
|
||||
@@ -28,7 +28,7 @@ In the following example __engineEdition__ is any on-premises edition or Azure M
|
||||
|
||||
### name
|
||||
|
||||
A [string pattern](#string-pattern) for the target SQL
|
||||
A [string pattern](#string-pattern) for the target SQL
|
||||
Server object name. It's a database name or an instance name.
|
||||
|
||||
In the following example __name__ matches anything except _master_, _tempdb_, and _model_.
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
+10
-10
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="173px" preserveAspectRatio="none" style="width:273px;height:173px;background:#FFFFFF;" version="1.1" viewBox="0 0 273 173" width="273px" zoomAndPan="magnify"><defs/><g><rect fill="#F1F1F1" height="151.2656" rx="5" ry="5" style="stroke:#F1F1F1;stroke-width:1.5;" width="251" x="10" y="10"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="39" x="15" y="26.5332">type?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="48" x="132" y="26.5332">Server</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="3" x="184" y="26.5332">|</text><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="65" x="191" y="26.5332">Database</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="10" y2="31.6094"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="31.6094" y2="31.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="63" x="15" y="48.1426">version?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="110" x="132" y="48.1426">Version range list</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="31.6094" y2="53.2188"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="53.2188" y2="53.2188"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="107" x="15" y="69.752">engineEdition?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="64" x="132" y="69.752">Edition list</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="53.2188" y2="74.8281"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="74.8281" y2="74.8281"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="67" x="15" y="91.3613">platform?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="86" x="132" y="91.3613">String pattern</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="74.8281" y2="96.4375"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="96.4375" y2="96.4375"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="47" x="15" y="112.9707">name?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="86" x="132" y="112.9707">String pattern</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="96.4375" y2="118.0469"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="118.0469" y2="118.0469"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="95" x="15" y="134.5801">serverName?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="86" x="132" y="134.5801">String pattern</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="118.0469" y2="139.6563"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="139.6563" y2="139.6563"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="101" x="15" y="156.1895">machineType?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="86" x="132" y="156.1895">String pattern</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="139.6563" y2="161.2656"/><rect fill="none" height="151.2656" rx="5" ry="5" style="stroke:#000000;stroke-width:1.5;" width="251" x="10" y="10"/><!--MD5=[b10fa2690d8d7a1881e1120e70bf9546]
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="173px" preserveAspectRatio="none" style="width:273px;height:173px;background:#FFFFFF;" version="1.1" viewBox="0 0 273 173" width="273px" zoomAndPan="magnify"><defs/><g><rect fill="#F1F1F1" height="151.2656" rx="5" ry="5" style="stroke:#F1F1F1;stroke-width:1.5;" width="251" x="10" y="10"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="39" x="15" y="26.5332">type?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="48" x="132" y="26.5332">Server</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="3" x="184" y="26.5332">|</text><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="65" x="191" y="26.5332">Database</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="10" y2="31.6094"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="31.6094" y2="31.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="63" x="15" y="48.1426">version?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="110" x="132" y="48.1426">Version range list</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="31.6094" y2="53.2188"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="53.2188" y2="53.2188"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="107" x="15" y="69.752">engineEdition?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="64" x="132" y="69.752">Edition list</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="53.2188" y2="74.8281"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="74.8281" y2="74.8281"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="67" x="15" y="91.3613">platform?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="86" x="132" y="91.3613">String pattern</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="74.8281" y2="96.4375"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="96.4375" y2="96.4375"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="47" x="15" y="112.9707">name?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="86" x="132" y="112.9707">String pattern</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="96.4375" y2="118.0469"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="118.0469" y2="118.0469"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="95" x="15" y="134.5801">serverName?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="86" x="132" y="134.5801">String pattern</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="118.0469" y2="139.6563"/><line style="stroke:#000000;stroke-width:1.0;" x1="10" x2="261" y1="139.6563" y2="139.6563"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="101" x="15" y="156.1895">machineType?</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="86" x="132" y="156.1895">String pattern</text><line style="stroke:#000000;stroke-width:1.0;" x1="127" x2="127" y1="139.6563" y2="161.2656"/><rect fill="none" height="151.2656" rx="5" ry="5" style="stroke:#000000;stroke-width:1.5;" width="251" x="10" y="10"/><!--MD5=[b10fa2690d8d7a1881e1120e70bf9546]
|
||||
@startjson
|
||||
{
|
||||
"type?": "<b>Server</b> | <b>Database</b>",
|
||||
@@ -7,15 +7,15 @@
|
||||
"platform?": "<i>String pattern</i>",
|
||||
"name?": "<i>String pattern</i>",
|
||||
"serverName?": "<i>String pattern</i>",
|
||||
"machineType?": "<i>String pattern</i>"
|
||||
"machineType?": "<i>String pattern</i>"
|
||||
}
|
||||
@end
|
||||
|
||||
PlantUML version 1.2022.7(Mon Aug 22 20:01:30 TRT 2022)
|
||||
(GPL source distribution)
|
||||
Java Runtime: Java(TM) SE Runtime Environment
|
||||
JVM: Java HotSpot(TM) 64-Bit Server VM
|
||||
Default Encoding: UTF-8
|
||||
Language: en
|
||||
Country: US
|
||||
|
||||
PlantUML version 1.2022.7(Mon Aug 22 20:01:30 TRT 2022)
|
||||
(GPL source distribution)
|
||||
Java Runtime: Java(TM) SE Runtime Environment
|
||||
JVM: Java HotSpot(TM) 64-Bit Server VM
|
||||
Default Encoding: UTF-8
|
||||
Language: en
|
||||
Country: US
|
||||
--></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
+1
-1
@@ -8,7 +8,7 @@
|
||||
"<i>rule B</i>",
|
||||
"<i>rule C</i>",
|
||||
"…"
|
||||
|
||||
|
||||
],
|
||||
"probes?":{
|
||||
"<i>probe 1</i>": [
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@
|
||||
"platform?": "<i>String pattern</i>",
|
||||
"name?": "<i>String pattern</i>",
|
||||
"serverName?": "<i>String pattern</i>",
|
||||
"machineType?": "<i>String pattern</i>"
|
||||
"machineType?": "<i>String pattern</i>"
|
||||
}
|
||||
@end
|
||||
@@ -25,17 +25,17 @@ PS:> Get-SqlInstance -ServerInstance localhost | Invoke-SqlAssessment
|
||||
|
||||
TargetPath: Server[@Name='LOCAL']
|
||||
|
||||
Sev. Message Check ID Origin
|
||||
---- ------- -------- ------
|
||||
Sev. Message Check ID Origin
|
||||
---- ------- -------- ------
|
||||
Info Enable trace flag 834 to use large-page allocations to improve TF834 Microsoft Ruleset 0.1.202
|
||||
analytical and data warehousing workloads.
|
||||
analytical and data warehousing workloads.
|
||||
Low Detected deprecated or discontinued feature uses: String literals DeprecatedFeatures Microsoft Ruleset 0.1.202
|
||||
as column aliases, syscolumns, sysusers, SET FMTONLY ON, XP_API,
|
||||
Table hint without WITH, More than two-part column name. We
|
||||
recommend to replace them with features actual for SQL Server
|
||||
version 14.0.1000.
|
||||
as column aliases, syscolumns, sysusers, SET FMTONLY ON, XP_API,
|
||||
Table hint without WITH, More than two-part column name. We
|
||||
recommend to replace them with features actual for SQL Server
|
||||
version 14.0.1000.
|
||||
Medi Amount of single use plans in cache is high (100%). Consider PlansUseRatio Microsoft Ruleset 0.1.202
|
||||
enabling the Optimize for ad hoc workloads setting on heavy OLTP
|
||||
enabling the Optimize for ad hoc workloads setting on heavy OLTP
|
||||
ad-hoc workloads to conserve resources.
|
||||
...
|
||||
```
|
||||
@@ -58,7 +58,7 @@ Get-SqlDatabase -ServerInstance 'localhost' | Invoke-SqlAssessment
|
||||
|
||||
To learn more about the SQL Assessment API such as customizing and extending rulesets, saving results to a table, etc., visit:
|
||||
|
||||
- Docs online page for SQL Assessment API PowerShell cmdlets: https://docs.microsoft.com/sql/sql-assessment-api/sql-assessment-api-overview
|
||||
- Docs online page for SQL Assessment API PowerShell cmdlets: https://docs.microsoft.com/sql/sql-assessment-api/sql-assessment-api-overview
|
||||
- [SQL Assessment User Guide](UserGuide/README.md)
|
||||
- SQL Assessment API Tutorial notebook: [SQLAssessmentAPITutorialNotebook.ipynb](./notebooks/SQLAssessmentAPITutorialNotebook.ipynb)
|
||||
- Azure Data Studio extension: https://techcommunity.microsoft.com/t5/sql-server/released-sql-server-assessment-extension-for-azure-data-studio/ba-p/1470603
|
||||
|
||||
@@ -5,7 +5,7 @@ With SQL Assessment cmdlets, you can assess an instance of SQL Server on an Azur
|
||||
To use such rules, do the following:
|
||||
|
||||
1. Make sure that both the [Azure PowerShell module](https://aka.ms/AAbdhwk) and the [Az.ResourceGraph module](https://www.powershellgallery.com/packages/Az.ResourceGraph) are installed.
|
||||
|
||||
|
||||
2. [Sign in with Azure PowerShell](https://aka.ms/AAbdogm) before invoking SQL Assessment against SQL Server on an Azure VM.
|
||||
|
||||
**NOTE:** It is possible to use Azure account connection persisted between PowerShell sessions, i.e. invoke **Connect-AzAccount** in one session and omit this command later. However, in such a scenario, SQL Assessment cmdlets need the **Az.ResourceGraph** module to be imported explicitly by running **Import-Module Az.ResourceGraph**.
|
||||
@@ -32,7 +32,7 @@ The following example shows how to invoke assessment for SQL Server on an Azure
|
||||
$cred = Get-Credential
|
||||
```
|
||||
|
||||
4. Select SQL Server objects to assess. For example, the following command gets a SQL Server instance.
|
||||
4. Select SQL Server objects to assess. For example, the following command gets a SQL Server instance.
|
||||
|
||||
```PowerShell
|
||||
$target = Get-SqlInstance -ServerInstance "Computer002\InstanceName" -Credential $cred
|
||||
@@ -51,12 +51,12 @@ As a result, you would get an output similar to the following one.
|
||||
```
|
||||
TargetPath : Server[@Name='ContosoAzureSQL']
|
||||
|
||||
Sev. Message Check ID Origin
|
||||
---- ------- -------- ------
|
||||
Sev. Message Check ID Origin
|
||||
---- ------- -------- ------
|
||||
Medi Amount of single use plans in cache is high (100%). Consider PlansUseRatio Microsoft Ruleset 0.1.202
|
||||
enabling the Optimize for ad hoc workloads setting on heavy OLTP
|
||||
ad-hoc workloads to conserve resources
|
||||
Low Use memory optimized virtual machine sizes for the best AzSqlVmSize Microsoft Ruleset 0.1.202
|
||||
enabling the Optimize for ad hoc workloads setting on heavy OLTP
|
||||
ad-hoc workloads to conserve resources
|
||||
Low Use memory optimized virtual machine sizes for the best AzSqlVmSize Microsoft Ruleset 0.1.202
|
||||
performance of SQL Server workloads
|
||||
```
|
||||
|
||||
|
||||
@@ -18,5 +18,5 @@ The following steps are required to enable [xp_cmdshell](https://docs.microsoft.
|
||||
|
||||
``` sql
|
||||
EXECUTE sp_configure 'xp_cmdshell', 0;
|
||||
RECONFIGURE
|
||||
RECONFIGURE
|
||||
```
|
||||
|
||||
@@ -11,7 +11,7 @@ Takes all data rows returned from probes or previous transformations and calcula
|
||||
|
||||
## Grouping
|
||||
|
||||
By default aggregate functions are applied to all data records returned by probes or previous transformations. Optionally, aggregates may be applied to groups of rows having the same value(s) in selected column(s). it works much like `GROUP BY` T-SQL clause.
|
||||
By default aggregate functions are applied to all data records returned by probes or previous transformations. Optionally, aggregates may be applied to groups of rows having the same value(s) in selected column(s). it works much like `GROUP BY` T-SQL clause.
|
||||
|
||||
## Example 1
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ The following JSON object represents a complete rule set using performance count
|
||||
"type": "performance",
|
||||
"counters": {
|
||||
"transactions_sec": {
|
||||
"type": "rate",
|
||||
"type": "rate",
|
||||
"instance": "_Total"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ Condition expressions can be any expressions or an array of expressions. An arra
|
||||
{"@version": "10.50.0"},
|
||||
{"@memroySize": 4096}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: The short version for "OR" works only as condition expressions while the short version for "AND" works everywhere. The following expressions are equivalent:
|
||||
|
||||
@@ -199,7 +199,7 @@ The following JSON object represents a complete rule set using performance count
|
||||
"type": "performance",
|
||||
"counters": {
|
||||
"transactions_sec": {
|
||||
"type": "rate",
|
||||
"type": "rate",
|
||||
"instance": "_Total"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,20 @@ The following is an example of a rule:
|
||||
```json
|
||||
{
|
||||
//Target describes a SQL Server object the check is supposed to run against
|
||||
"target":
|
||||
{
|
||||
"target":
|
||||
{
|
||||
//This check targets an object of the Database type
|
||||
"type": "Database",
|
||||
|
||||
//Applies to SQL Server 2016 and higher
|
||||
//Another example: "[12.0,13.0)" reads as "any SQL Server version >= 12.0 and < 13.0"
|
||||
"version": "[13.0,)",
|
||||
"version": "[13.0,)",
|
||||
|
||||
//Applies to SQL Server on Windows and Linux
|
||||
"platform": "Windows, Linux",
|
||||
"platform": "Windows, Linux",
|
||||
|
||||
//Applies to SQL on Premises and Azure SQL Managed Instance. Here you can also filter specific editions of SQL Server
|
||||
"engineEdition": "OnPremises, ManagedInstance",
|
||||
"engineEdition": "OnPremises, ManagedInstance",
|
||||
|
||||
//Applies to any database excluding master, tempdb, and msdb
|
||||
"name": { "not": "/^(master|tempdb|model)$/" }
|
||||
@@ -50,15 +50,15 @@ The following is an example of a rule:
|
||||
|
||||
//Usually, it's for recommendation what user should do if the rule raises up an alert
|
||||
"message": "Make sure Query Store actual operation mode is 'Read Write' to keep your performance analysis accurate",
|
||||
|
||||
|
||||
//Reference material
|
||||
"helpLink": "https://docs.microsoft.com/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store",
|
||||
|
||||
//List of probes that are used to get the required data for this check. See below to know more about probes.
|
||||
"probes": [ "Custom_DatabaseConfiguration" ],
|
||||
"probes": [ "Custom_DatabaseConfiguration" ],
|
||||
|
||||
//Condition object is to define "good" and "bad" state, the latter is when the rule should raise an alert. When the condition is true, it means that the checked object complies with the best practice or policy. Otherwise, the rule raises an alert (it actually adds its message to the resulting set of recommendations)
|
||||
"condition":
|
||||
"condition":
|
||||
{
|
||||
//It means that the variable came from the probe should be equal to 2
|
||||
"equal": [ "@query_store_state", 2 ]
|
||||
@@ -77,11 +77,11 @@ The following is an example of a probe:
|
||||
//Probe name is used to reference the probe from a rule
|
||||
//Probe can have a few implementations that will be used for different targets
|
||||
//This probe has two implementations for different version of SQL Server
|
||||
"Custom_DatabaseConfiguration":
|
||||
"Custom_DatabaseConfiguration":
|
||||
[
|
||||
{
|
||||
//Probe uses a T-SQL query to get the required data. Use 'CLR' for assemblies.
|
||||
"type": "SQL",
|
||||
"type": "SQL",
|
||||
|
||||
//Probes have their own target, usually to separate implementation for different versions, editions, or platforms. Probe targets work the same way as rule targets do.
|
||||
"target":
|
||||
@@ -95,7 +95,7 @@ The following is an example of a probe:
|
||||
},
|
||||
|
||||
//Implementation object with a T-SQL query. This probe is used in many rules, that's why the query return so many fields
|
||||
"implementation":
|
||||
"implementation":
|
||||
{
|
||||
"query": "SELECT db.is_auto_create_stats_on, db.is_auto_update_stats_on, 0 AS query_store_state, db.collation_name, (SELECT collation_name FROM master.sys.databases (NOLOCK) WHERE database_id = 1) AS master_collation, db.is_auto_close_on, db.is_auto_shrink_on, db.page_verify_option, db.is_db_chaining_on, NULL AS is_auto_create_stats_incremental_on, db.is_trustworthy_on, db.is_parameterization_forced FROM [sys].[databases] (NOLOCK) AS db WHERE db.[name]=@TargetName"
|
||||
}
|
||||
@@ -104,19 +104,19 @@ The following is an example of a probe:
|
||||
//This implementation object is to get the required data from SQL Server 2014 (look at target.version)
|
||||
{
|
||||
"type": "SQL",
|
||||
"target":
|
||||
"target":
|
||||
{
|
||||
"type": "Database",
|
||||
"version": "[12.0, 13.0)",
|
||||
"platform": "Windows, Linux",
|
||||
"engineEdition": "OnPremises, ManagedInstance"
|
||||
},
|
||||
"implementation":
|
||||
"implementation":
|
||||
{
|
||||
"query": "SELECT db.is_auto_create_stats_on, db.is_auto_update_stats_on, 0 AS query_store_state, db.collation_name, (SELECT collation_name FROM master.sys.databases (NOLOCK) WHERE database_id = 1) AS master_collation, db.is_auto_close_on, db.is_auto_shrink_on, db.page_verify_option, db.is_db_chaining_on, db.is_auto_create_stats_incremental_on, db.is_trustworthy_on, db.is_parameterization_forced FROM [sys].[databases] (NOLOCK) AS db WHERE db.[name]=@TargetName"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
//This implementation object is to get the required data from SQL Server 2016 and up (look at target.version)
|
||||
{
|
||||
"type": "SQL",
|
||||
@@ -127,10 +127,10 @@ The following is an example of a probe:
|
||||
"platform": "Windows, Linux",
|
||||
"engineEdition": "OnPremises, ManagedInstance"
|
||||
},
|
||||
"implementation":
|
||||
"implementation":
|
||||
{
|
||||
//Use this key if your query requires to run on a database that is being assessed (it's a replacement for 'USE <DATABASENAME>;')
|
||||
"useDatabase": true,
|
||||
"useDatabase": true,
|
||||
"query": "SELECT db.is_auto_create_stats_on, db.is_auto_update_stats_on, (SELECT CAST(actual_state AS DECIMAL) FROM [sys].[database_query_store_options]) AS query_store_state, db.collation_name, (SELECT collation_name FROM master.sys.databases (NOLOCK) WHERE database_id = 1) AS master_collation, db.is_auto_close_on, db.is_auto_shrink_on, db.page_verify_option, db.is_db_chaining_on, db.is_auto_create_stats_incremental_on, db.is_trustworthy_on, db.is_parameterization_forced FROM [sys].[databases] (NOLOCK) AS db WHERE db.[name]=@TargetName"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,32 +5,32 @@ You can silence specific rules when they aren't applied to your environment or u
|
||||
The following is an example consisting of three rules. The first rule shows how to disable the check by specifying its ID, the second example disables all checks that have the **TraceFlag** tag, and the third example disables all checks from the default ruleset using the **DefaultRuleset** tag for databases **DBName1** and **DBName2**.
|
||||
|
||||
```json
|
||||
{
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"version": "0.2",
|
||||
"name": "Custom Overrides",
|
||||
"name": "Custom Overrides",
|
||||
"rules":
|
||||
[
|
||||
[
|
||||
{
|
||||
"id": "LatestCU",
|
||||
"id": "LatestCU",
|
||||
"itemType": "override",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"id": ["TraceFlag"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"id": ["TraceFlag"],
|
||||
"itemType": "override",
|
||||
"enabled": false
|
||||
},
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"id": ["DefaultRuleset"],
|
||||
"itemType": "override",
|
||||
"targetFilter":
|
||||
"targetFilter":
|
||||
{
|
||||
"type": "Database",
|
||||
"name": [ "DBName1", "DBName2" ]
|
||||
},
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -35,7 +35,7 @@ Let's say you need to change this rule to validate backups that were created mor
|
||||
"name": "Backup Policy",
|
||||
|
||||
//Sets the override for the specified rule
|
||||
"rules":
|
||||
"rules":
|
||||
[
|
||||
{
|
||||
//Sets the type, which is 'override'
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": "1.0",
|
||||
"version": "1.1",
|
||||
"name": "Custom Ruleset",
|
||||
"rules":[
|
||||
"rules":[
|
||||
{
|
||||
"target": {
|
||||
"type": "Database",
|
||||
@@ -34,7 +34,7 @@
|
||||
"platform": [ "Windows", "Linux" ]
|
||||
},
|
||||
"implementation": {
|
||||
"useDatabase": true,
|
||||
"useDatabase": true,
|
||||
"query": "SELECT (total - used)/total AS space_available_rel, total as space_total, used as space_used, total - used as space_available FROM ( SELECT SUM(a.size)/128.0 AS total, SUM(fileproperty(a.name,'SpaceUsed'))/128.0 AS used FROM dbo.sysfiles a WHERE groupid <> 0)data;"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": "1.0",
|
||||
"version": "1.2",
|
||||
"name": "Custom Ruleset 2",
|
||||
"rules":[
|
||||
"rules":[
|
||||
{
|
||||
"id": "DBSpaceAvailable",
|
||||
"itemType": "override",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Release notes for SQL Assessment API
|
||||
|
||||
This article provides details about updates, improvements, and bug fixes for the current and previous versions of SQL Assessment API.
|
||||
This article provides details about updates, improvements, and bug fixes for the current and previous versions of SQL Assessment API.
|
||||
|
||||
To start working with the API, install the SQL Assessment Extention to Azure Data Studio or utilize either the SqlServer module or SMO.
|
||||
|
||||
@@ -268,9 +268,9 @@ Version: SqlServer PowerShell module wasn't updated, SqlManagementObjects (SMO)
|
||||
|
||||
### Bug fixes and improvements
|
||||
|
||||
- Updated the 'LatestCU' rule with the latest CU versions
|
||||
- Updated the 'ReplErrors24H' rule to collect information for all 'Publisher' databases
|
||||
- Fixed an issue with local variables used for displaying extended details in messages
|
||||
- Updated the 'LatestCU' rule with the latest CU versions
|
||||
- Updated the 'ReplErrors24H' rule to collect information for all 'Publisher' databases
|
||||
- Fixed an issue with local variables used for displaying extended details in messages
|
||||
- Fixed an issue with the wrong Linux target type for the 'PriorityBoostOn' rule
|
||||
|
||||
## December 2020 - 1.0.302
|
||||
@@ -311,7 +311,7 @@ Version: SqlServer module 21.1.18226, SqlManagementObjects (SMO) package wasn't
|
||||
|
||||
- Added new types of probes in addition to SQL and EXTERNAL: CMDSHELL, WMI, REGISTRY, POWERSHELL
|
||||
- Enabling/disabling database checks for particular SQL Server instances (by instance name)
|
||||
- Added 40 rules, including
|
||||
- Added 40 rules, including
|
||||
- Ad Hoc Distributed Queries are enabled
|
||||
- Affinity Mask and Affinity I/O Mask overlapping
|
||||
- Auto Soft NUMA should be enabled
|
||||
|
||||
@@ -22,7 +22,7 @@ SELECT @Cores = hyperthread_ratio FROM sys.dm_os_sys_info;
|
||||
SELECT @Edition = CONVERT(NVARCHAR(20), SERVERPROPERTY('Edition'))
|
||||
SELECT @SQLVersion = CONVERT(NVARCHAR(50), SERVERPROPERTY('ProductVersion'))
|
||||
|
||||
SELECT SERVERPROPERTY('ServerName') AS [name],
|
||||
SELECT SERVERPROPERTY('ServerName') AS [name],
|
||||
CASE LEFT(@SQLVersion,4) WHEN '10.0' THEN '2008'
|
||||
WHEN '10.5' THEN '2008R2'
|
||||
WHEN '11.0' THEN '2012'
|
||||
@@ -31,7 +31,7 @@ SELECT SERVERPROPERTY('ServerName') AS [name],
|
||||
WHEN '14.0' THEN '2017'
|
||||
WHEN '15.0' THEN '2019'
|
||||
ELSE 'Other'
|
||||
END AS [version],
|
||||
END AS [version],
|
||||
LEFT(@Edition,CHARINDEX(' ', @Edition,0)-1) AS edition,
|
||||
@Cores AS cores,
|
||||
@HostType AS hostType;
|
||||
@@ -40,7 +40,7 @@ SELECT SERVERPROPERTY('ServerName') AS [name],
|
||||
|
||||
## <a name="ps"></a> Powershell
|
||||
|
||||
To collect registration information from **all instances in a single machine**, you can use the example Powershell script [EOS_DataGenerator_LocalDiscovery.ps1](./scripts/EOS_DataGenerator_LocalDiscovery.ps1). Can be used in an Azure VM, on-premises physical server or on-premises VM.
|
||||
To collect registration information from **all instances in a single machine**, you can use the example Powershell script [EOS_DataGenerator_LocalDiscovery.ps1](./scripts/EOS_DataGenerator_LocalDiscovery.ps1). Can be used in an Azure VM, on-premises physical server or on-premises VM.
|
||||
|
||||
**Note**: Verify if the **Host Type** is correct for your SQL Server instance before uploading the CSV file.
|
||||
|
||||
|
||||
+32
-32
@@ -3,20 +3,20 @@
|
||||
Run the following command: .\EOS_DataGenerator_InputList.ps1
|
||||
|
||||
Disclaimer
|
||||
The sample scripts are not supported under any Microsoft standard support program or service.
|
||||
The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including,
|
||||
without limitation, any implied warranties of merchantability or of fitness for a particular purpose.
|
||||
The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
|
||||
In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable
|
||||
for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of
|
||||
business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation,
|
||||
even if Microsoft has been advised of the possibility of such damages.
|
||||
The sample scripts are not supported under any Microsoft standard support program or service.
|
||||
The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including,
|
||||
without limitation, any implied warranties of merchantability or of fitness for a particular purpose.
|
||||
The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
|
||||
In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable
|
||||
for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of
|
||||
business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation,
|
||||
even if Microsoft has been advised of the possibility of such damages.
|
||||
#>
|
||||
|
||||
$SQLServerList = Read-Host "Input file must be in the current script path. Enter input SQL Server List filename"
|
||||
|
||||
If ([string]::IsNullOrEmpty($SQLServerList) ) {
|
||||
Throw "Parameter missing: Input file"
|
||||
If ([string]::IsNullOrEmpty($SQLServerList) ) {
|
||||
Throw "Parameter missing: Input file"
|
||||
} Else {
|
||||
$scriptFolder = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$SQLServerList = $scriptFolder + "\" + $SQLServerList
|
||||
@@ -25,8 +25,8 @@ If ([string]::IsNullOrEmpty($SQLServerList) ) {
|
||||
|
||||
$CSVfilename = Read-Host "Output file will be saved in the current script path. Enter a file name for the CSV output"
|
||||
|
||||
If ([string]::IsNullOrEmpty($CSVfilename) ) {
|
||||
Throw "Parameter missing: Output file"
|
||||
If ([string]::IsNullOrEmpty($CSVfilename) ) {
|
||||
Throw "Parameter missing: Output file"
|
||||
} Else {
|
||||
If ($CSVfilename -notlike "*.csv") { $CSVfilename = $CSVfilename + ".csv" }
|
||||
|
||||
@@ -40,7 +40,7 @@ Function Get-Info {
|
||||
Param( [Parameter(Mandatory = $TRUE, ValueFromPipeline = $TRUE)] [String] $ServerName )
|
||||
|
||||
Process {
|
||||
|
||||
|
||||
Write-Host "`nConnecting to $ServerName..."
|
||||
|
||||
# Get SQL Server instance's data
|
||||
@@ -68,8 +68,8 @@ Function Get-Info {
|
||||
|
||||
Write-Host "|- Querying $ServerName..."
|
||||
|
||||
While ( $dr.Read() ) {
|
||||
$SQLEdition = $dr.GetValue(0);
|
||||
While ( $dr.Read() ) {
|
||||
$SQLEdition = $dr.GetValue(0);
|
||||
$Version = $dr.GetValue(1);
|
||||
$MachineName = $dr.GetValue(2);
|
||||
}
|
||||
@@ -82,15 +82,15 @@ Function Get-Info {
|
||||
Elseif ($Version -eq "10.5"){
|
||||
$OutVersion = '2008R2'}
|
||||
Elseif ($Version -eq "11.0"){
|
||||
$OutVersion = '2012'}
|
||||
$OutVersion = '2012'}
|
||||
Elseif ($Version -eq "12.0"){
|
||||
$OutVersion = '2014'}
|
||||
Elseif ($Version -eq "13.0"){
|
||||
$OutVersion = '2016'}
|
||||
$OutVersion = '2016'}
|
||||
Elseif ($Version -eq "14.0"){
|
||||
$OutVersion = '2017'}
|
||||
$OutVersion = '2017'}
|
||||
Elseif ($Version -eq "15.0"){
|
||||
$OutVersion = '2019'}
|
||||
$OutVersion = '2019'}
|
||||
Else {
|
||||
$OutVersion = 'Unknown'}
|
||||
|
||||
@@ -98,9 +98,9 @@ Function Get-Info {
|
||||
$dr.Close()
|
||||
$sqlconn.Close()
|
||||
|
||||
#Get processors information
|
||||
#Get processors information
|
||||
$CPU = Get-WmiObject -ComputerName $MachineName -class Win32_Processor
|
||||
|
||||
|
||||
#Get Computer model information
|
||||
$Manufacturer = (Get-WmiObject -ComputerName $MachineName -class Win32_ComputerSystem).Manufacturer
|
||||
|
||||
@@ -110,20 +110,20 @@ Function Get-Info {
|
||||
$HostType = 'Virtual Machine'}
|
||||
Else {
|
||||
$HostType = 'Physical Server'}
|
||||
|
||||
|
||||
#Reset number of cores and use count for the CPUs counting
|
||||
$CPUs = 0
|
||||
$Cores = 0
|
||||
|
||||
|
||||
ForEach ( $Processor in $CPU ) {
|
||||
|
||||
$CPUs = $CPUs + 1
|
||||
|
||||
#count the total number of cores
|
||||
$CPUs = $CPUs + 1
|
||||
|
||||
#count the total number of cores
|
||||
$Cores = $Cores + $Processor.NumberOfCores
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
$InfoRecord = New-Object -TypeName PSObject -Property @{
|
||||
Name = $ServerName;
|
||||
HostType = $HostType;
|
||||
@@ -132,7 +132,7 @@ Function Get-Info {
|
||||
Version = $Version;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Write-Output $InfoRecord
|
||||
}
|
||||
}
|
||||
@@ -142,6 +142,6 @@ Get-Content $SQLServerList | Foreach-Object {Get-Info $_ } `
|
||||
| Select-Object "name", "version", "edition", "cores", "hostType" `
|
||||
| ConvertTo-Csv -NoTypeInformation `
|
||||
| % { $_ -Replace '"', ""} `
|
||||
| Out-File -FilePath $CSVfilename -Encoding UTF8 #-NoClobber #-Append
|
||||
|
||||
| Out-File -FilePath $CSVfilename -Encoding UTF8 #-NoClobber #-Append
|
||||
|
||||
Write-Host -ForegroundColor Yellow "`nDone!"
|
||||
|
||||
+36
-36
@@ -1,24 +1,24 @@
|
||||
<#
|
||||
Discovers local SQL Server instance names.
|
||||
Discovers local SQL Server instance names.
|
||||
Run the following command: .\EOS_DataGenerator_LocalDiscovery.ps1
|
||||
|
||||
Disclaimer
|
||||
The sample scripts are not supported under any Microsoft standard support program or service.
|
||||
The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including,
|
||||
without limitation, any implied warranties of merchantability or of fitness for a particular purpose.
|
||||
The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
|
||||
In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable
|
||||
for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of
|
||||
business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation,
|
||||
even if Microsoft has been advised of the possibility of such damages.
|
||||
The sample scripts are not supported under any Microsoft standard support program or service.
|
||||
The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including,
|
||||
without limitation, any implied warranties of merchantability or of fitness for a particular purpose.
|
||||
The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
|
||||
In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable
|
||||
for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of
|
||||
business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation,
|
||||
even if Microsoft has been advised of the possibility of such damages.
|
||||
#>
|
||||
|
||||
Clear-Variable -name subscriptionId
|
||||
|
||||
$CSVfilename = Read-Host "Output file will be saved in the current script path. Enter a file name for the CSV output"
|
||||
|
||||
If ( [string]::IsNullOrEmpty($CSVfilename) ) {
|
||||
Throw "Parameter missing: Output file"
|
||||
If ( [string]::IsNullOrEmpty($CSVfilename) ) {
|
||||
Throw "Parameter missing: Output file"
|
||||
} Else {
|
||||
If ($CSVfilename -notlike "*.csv") { $CSVfilename = $CSVfilename + ".csv" }
|
||||
|
||||
@@ -37,7 +37,7 @@ If ( $IsAzureVM -eq "Y" ) {
|
||||
Try {
|
||||
Install-Module -Name Az -AllowClobber -Scope CurrentUser
|
||||
Import-Module Az
|
||||
}
|
||||
}
|
||||
Catch {
|
||||
# Error if not connected
|
||||
Throw "Could not install Azure PS module"
|
||||
@@ -47,7 +47,7 @@ If ( $IsAzureVM -eq "Y" ) {
|
||||
Write-Host -ForegroundColor Yellow "Connecting to Azure account..."
|
||||
Try {
|
||||
Connect-AzAccount -Subscription $subscriptionName
|
||||
}
|
||||
}
|
||||
Catch {
|
||||
# Error if not installed
|
||||
Throw "Could not connect to Azure account"
|
||||
@@ -69,16 +69,16 @@ If ( $IsAzureVM -eq "Y" ) {
|
||||
Elseif ($IsAzureVMOS -like "*2008*"){
|
||||
$OutVersion = '2008'}
|
||||
Elseif ($IsAzureVMOS -like "*2012 R2*"){
|
||||
$OutVersion = '2012 R2'}
|
||||
$OutVersion = '2012 R2'}
|
||||
Elseif ($IsAzureVMOS -like "*2012*"){
|
||||
$OutVersion = '2012'}
|
||||
Elseif ($IsAzureVMOS -like "*2016*"){
|
||||
$OutVersion = '2016'}
|
||||
$OutVersion = '2016'}
|
||||
Elseif ($IsAzureVMOS -like "*2019*"){
|
||||
$OutVersion = '2019'}
|
||||
$OutVersion = '2019'}
|
||||
|
||||
$IsAzureVMOS = $OutVersion
|
||||
}
|
||||
}
|
||||
Catch {
|
||||
Throw "Could not get Azure VM information"
|
||||
Break
|
||||
@@ -95,7 +95,7 @@ Function Get-SQLInstance {
|
||||
Try {
|
||||
$ServerNames = $services.Name | ForEach-Object {$env:computername + "\" + ($_).Replace("MSSQL`$","")}
|
||||
If ($ServerNames -like "*MSSQLSERVER") { $ServerNames = $env:computername }
|
||||
}
|
||||
}
|
||||
Catch {
|
||||
# Error if none found
|
||||
Throw "No SQL Server instances found"
|
||||
@@ -113,7 +113,7 @@ Function Get-Info {
|
||||
[String] $IsAzureVMOS )
|
||||
|
||||
Process {
|
||||
|
||||
|
||||
Write-Host "`nConnecting to $ServerName..."
|
||||
|
||||
# Get SQL Server instance's data
|
||||
@@ -141,8 +141,8 @@ Function Get-Info {
|
||||
|
||||
Write-Host "|- Querying $ServerName..."
|
||||
|
||||
While ( $dr.Read() ) {
|
||||
$SQLEdition = $dr.GetValue(0);
|
||||
While ( $dr.Read() ) {
|
||||
$SQLEdition = $dr.GetValue(0);
|
||||
$Version = $dr.GetValue(1);
|
||||
}
|
||||
|
||||
@@ -154,15 +154,15 @@ Function Get-Info {
|
||||
Elseif ($Version -eq "10.5"){
|
||||
$OutVersion = '2008R2'}
|
||||
Elseif ($Version -eq "11.0"){
|
||||
$OutVersion = '2012'}
|
||||
$OutVersion = '2012'}
|
||||
Elseif ($Version -eq "12.0"){
|
||||
$OutVersion = '2014'}
|
||||
Elseif ($Version -eq "13.0"){
|
||||
$OutVersion = '2016'}
|
||||
$OutVersion = '2016'}
|
||||
Elseif ($Version -eq "14.0"){
|
||||
$OutVersion = '2017'}
|
||||
$OutVersion = '2017'}
|
||||
Elseif ($Version -eq "15.0"){
|
||||
$OutVersion = '2019'}
|
||||
$OutVersion = '2019'}
|
||||
Else {
|
||||
$OutVersion = 'Unknown'}
|
||||
|
||||
@@ -170,7 +170,7 @@ Function Get-Info {
|
||||
$dr.Close()
|
||||
$sqlconn.Close()
|
||||
|
||||
#Get processors information
|
||||
#Get processors information
|
||||
$CPU = Get-WmiObject -ComputerName $env:computername -class Win32_Processor
|
||||
|
||||
#Get Computer model information
|
||||
@@ -184,17 +184,17 @@ Function Get-Info {
|
||||
$HostType = 'Virtual Machine'}
|
||||
Else {
|
||||
$HostType = 'Physical Server'}
|
||||
|
||||
|
||||
#Reset number of cores and use count for the CPUs counting
|
||||
$CPUs = 0
|
||||
$Cores = 0
|
||||
|
||||
|
||||
ForEach ( $Processor in $CPU ) {
|
||||
$CPUs = $CPUs + 1
|
||||
|
||||
#count the total number of cores
|
||||
$Cores = $Cores + $Processor.NumberOfCores
|
||||
}
|
||||
$CPUs = $CPUs + 1
|
||||
|
||||
#count the total number of cores
|
||||
$Cores = $Cores + $Processor.NumberOfCores
|
||||
}
|
||||
|
||||
If ( [string]::IsNullOrEmpty($subscriptionId) ) {
|
||||
$InfoRecord = New-Object -TypeName PSObject -Property @{
|
||||
@@ -223,18 +223,18 @@ Function Get-Info {
|
||||
}
|
||||
|
||||
#Loop through the server list and get information about SQL Server instances
|
||||
If ( [string]::IsNullOrEmpty($subscriptionId) ) {
|
||||
If ( [string]::IsNullOrEmpty($subscriptionId) ) {
|
||||
Get-SQLInstance | Foreach-Object {Get-Info $_ $subscriptionId $resourceGroup $IsAzureVMName $IsAzureVMOS} `
|
||||
| Select-Object "name", "version", "edition", "cores", "hostType" `
|
||||
| ConvertTo-Csv -NoTypeInformation `
|
||||
| % { $_ -Replace '"', ""} `
|
||||
| Out-File -FilePath $CSVfilename #-Encoding UTF8 #-NoClobber #-Append
|
||||
| Out-File -FilePath $CSVfilename #-Encoding UTF8 #-NoClobber #-Append
|
||||
} Else {
|
||||
Get-SQLInstance | Foreach-Object {Get-Info $_ $subscriptionId $resourceGroup $IsAzureVMName $IsAzureVMOS} `
|
||||
| Select-Object "name", "version", "edition", "cores", "hostType", "subscriptionId", "resourceGroup", "azureVmName", "azureVmOS" `
|
||||
| ConvertTo-Csv -NoTypeInformation `
|
||||
| % { $_ -Replace '"', ""} `
|
||||
| Out-File -FilePath $CSVfilename -Encoding UTF8 #-NoClobber #-Append
|
||||
| Out-File -FilePath $CSVfilename -Encoding UTF8 #-NoClobber #-Append
|
||||
}
|
||||
|
||||
Write-Host -ForegroundColor Yellow "`nDone!"
|
||||
+2
-2
@@ -12,7 +12,7 @@ SELECT @Cores = hyperthread_ratio FROM sys.dm_os_sys_info;
|
||||
SELECT @Edition = CONVERT(NVARCHAR(20), SERVERPROPERTY('Edition'))
|
||||
SELECT @SQLVersion = CONVERT(NVARCHAR(50), SERVERPROPERTY('ProductVersion'))
|
||||
|
||||
SELECT SERVERPROPERTY('ServerName') AS [name],
|
||||
SELECT SERVERPROPERTY('ServerName') AS [name],
|
||||
CASE LEFT(@SQLVersion,4) WHEN '10.0' THEN '2008'
|
||||
WHEN '10.5' THEN '2008R2'
|
||||
WHEN '11.0' THEN '2012'
|
||||
@@ -21,7 +21,7 @@ SELECT SERVERPROPERTY('ServerName') AS [name],
|
||||
WHEN '14.0' THEN '2017'
|
||||
WHEN '15.0' THEN '2019'
|
||||
ELSE 'Other'
|
||||
END AS [version],
|
||||
END AS [version],
|
||||
LEFT(@Edition,CHARINDEX(' ', @Edition,0)-1) AS edition,
|
||||
@Cores AS cores,
|
||||
@HostType AS hostType;
|
||||
|
||||
Reference in New Issue
Block a user