mirror of
https://github.com/Microsoft/sql-server-samples.git
synced 2025-12-08 14:58:54 +00:00
Merge branch 'microsoft:master' into segovoni/samples/manage/polyBase/external-table
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -4937,9 +4937,9 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
|
||||
"integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"requires": {
|
||||
"jake": "^10.8.5"
|
||||
}
|
||||
@@ -12762,9 +12762,9 @@
|
||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
|
||||
},
|
||||
"tar": {
|
||||
"version": "6.1.15",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz",
|
||||
"integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"requires": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
|
||||
@@ -731,7 +731,7 @@
|
||||
},
|
||||
"partHeader": {
|
||||
"title": "SQL Server Instances by Location",
|
||||
"subtitle": "SQL Servers instasnces by \"Location\" tag values"
|
||||
"subtitle": "SQL Servers instances by \"Location\" tag values"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -785,7 +785,7 @@
|
||||
"content": {}
|
||||
},
|
||||
"partHeader": {
|
||||
"title": "SQL Server Instasnces by Data Center",
|
||||
"title": "SQL Server Instances by Data Center",
|
||||
"subtitle": "SQL Servers instances by \"Datacenter\" tag values."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,14 +678,14 @@
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"value": "resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | where properties.detectedProperties.mssqldiscovered == true\r\n | extend serverId = tolower(tostring(id))\r\n | extend serverName = name\r\n | extend serverProvisioningState = tostring(properties.provisioningState)\r\n | extend serverServerStatus = tostring(properties.status)\r\n | extend serverProcessorCount = properties.detectedProperties.processorCount\r\n | extend serverCoreCount = toint(properties.detectedProperties.coreCount)\r\n | extend serverLogicalCoreCount = toint(properties.detectedProperties.logicalCoreCount)\r\n | join kind= leftouter (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where properties.type in (\"WindowsAgent.SqlServer\",\"LinuxAgent.SqlServer\")\r\n | extend extensionId = tolower(tostring(id))\r\n | extend extensionServerId = tolower(tostring (iff(id contains \"/extensions/WindowsAgent.SqlServer\" or id contains \"/extensions/LinuxAgent.SqlServer\", substring(id, 0, indexof(id, \"/extensions/\")), \"\")))\r\n | extend extensionProvisioningState = tostring(properties.provisioningState) \r\n | extend extensionLicenseType = tostring(properties.settings.LicenseType)\r\n )\r\n on $left.serverId == $right.extensionServerId\r\n | join kind= leftouter (\r\n resources\r\n | where type == 'microsoft.azurearcdata/sqlserverinstances' \r\n | extend sqlId = tolower(tostring(id))\r\n | extend sqlServerId = tolower(tostring(properties.containerResourceId))\r\n | extend sqlName = name\r\n | extend sqlProvisioningState = tostring(properties.provisioningState)\r\n | extend sqlServerStatus = tostring(properties.status)\r\n | extend sqlVersion = tostring(properties.version)\r\n | extend sqlLicenseType = tostring(properties.licenseType)\r\n | extend sqlEdition = tostring(properties.edition)\r\n | extend sqlvcore = toint(properties.vCore)\r\n )\r\n on $left.extensionServerId == $right.sqlServerId\r\n | where isnotempty(sqlServerId)\r\n //| project sqlEdition, serverName, sqlName, sqlLicenseType, extensionLicenseType, serverCoreCount, sqlvcore\r\n | where sqlLicenseType in (\"Paid\", \"ServerCAL\")\r\n | where sqlEdition in (\"Enterprise\", \"Standard\")\r\n | summarize count(sqlName), min(serverCoreCount), sum(sqlvcore) by serverName, extensionLicenseType\r\n | summarize [\"Number of Servers\"] = count(serverName), [\"Number of SQL Servers\"] = sum(count_sqlName), \r\n [\"Number of Windows Cores\"] = sum(min_serverCoreCount), [\"Number of SQL Cores\"] = sum(sum_sqlvcore) \r\n by [\"Extension License Type\"] = extensionLicenseType",
|
||||
"value": "resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | where properties.detectedProperties.mssqldiscovered == true\r\n | extend serverId = tolower(tostring(id))\r\n | extend serverName = name\r\n | extend serverProvisioningState = tostring(properties.provisioningState)\r\n | extend serverServerStatus = tostring(properties.status)\r\n | extend serverProcessorCount = properties.detectedProperties.processorCount\r\n | extend serverCoreCount = toint(properties.detectedProperties.coreCount)\r\n | extend serverLogicalCoreCount = toint(properties.detectedProperties.logicalCoreCount)\r\n | join kind= leftouter (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where properties.type in (\"WindowsAgent.SqlServer\",\"LinuxAgent.SqlServer\")\r\n | extend extensionId = tolower(tostring(id))\r\n | extend extensionServerId = tolower(tostring (iff(id contains \"/extensions/WindowsAgent.SqlServer\" or id contains \"/extensions/LinuxAgent.SqlServer\", substring(id, 0, indexof(id, \"/extensions/\")), \"\")))\r\n | extend extensionProvisioningState = tostring(properties.provisioningState) \r\n | extend extensionLicenseType = tostring(properties.settings.LicenseType)\r\n )\r\n on $left.serverId == $right.extensionServerId\r\n | join kind= leftouter (\r\n resources\r\n | where type == 'microsoft.azurearcdata/sqlserverinstances' \r\n | extend sqlId = tolower(tostring(id))\r\n | extend sqlServerId = tolower(tostring(properties.containerResourceId))\r\n | extend sqlName = name\r\n | extend sqlProvisioningState = tostring(properties.provisioningState)\r\n | extend sqlServerStatus = tostring(properties.status)\r\n | extend sqlVersion = tostring(properties.version)\r\n | extend sqlLicenseType = tostring(properties.licenseType)\r\n | extend sqlEdition = tostring(properties.edition)\r\n | extend sqlvcore = toint(properties.vCore)\r\n )\r\n on $left.extensionServerId == $right.sqlServerId\r\n | where isnotempty(sqlServerId)\r\n //| project sqlEdition, serverName, sqlName, sqlLicenseType, extensionLicenseType, serverCoreCount, sqlvcore\r\n | where sqlLicenseType in (\"Paid\", \"ServerCAL\")\r\n | where sqlEdition in (\"Enterprise\", \"Standard\")\r\n | summarize count(sqlName), min(serverCoreCount) by serverName, extensionLicenseType\r\n | summarize [\"Number of Servers\"] = count(serverName), [\"Number of SQL Servers\"] = sum(count_sqlName), \r\n [\"Number of Windows Cores\"] = sum(min_serverCoreCount) \r\n by [\"Extension License Type\"] = extensionLicenseType",
|
||||
"isOptional": true
|
||||
}
|
||||
],
|
||||
"type": "Extension/HubsExtension/PartType/ArgQueryGridTile",
|
||||
"settings": {},
|
||||
"partHeader": {
|
||||
"title": "Number of Cores",
|
||||
"title": "Number of Cores for Production Servers Only (EE/SE)",
|
||||
"subtitle": "SQL License in (Paid and ServerCAL)"
|
||||
}
|
||||
}
|
||||
@@ -1011,6 +1011,60 @@
|
||||
"subtitle": "SQL Server 2014 ESU"
|
||||
}
|
||||
}
|
||||
},
|
||||
"19": {
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 38,
|
||||
"colSpan": 18,
|
||||
"rowSpan": 4
|
||||
},
|
||||
"metadata": {
|
||||
"inputs": [
|
||||
{
|
||||
"name": "partTitle",
|
||||
"value": "Query 2",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"value": "resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | extend serverId = tolower(tostring(id))\r\n | extend serverName = name\r\n | extend serverSubscriptionId = subscriptionId\r\n | extend serverLocation = location\r\n | extend serverResourceGroup = resourceGroup\r\n | extend serverTags = tags\r\n | extend serverProvisioningState = tostring(properties.provisioningState)\r\n | extend serverServerStatus = tostring(properties.status)\r\n | extend serverDisplayName = tostring(properties.displayName)\r\n | extend serverMssqldiscovered = properties.detectedProperties.mssqldiscovered\r\n | extend serverComputerName = tostring(properties.osProfile.computerName)\r\n | project serverId, \r\n [\"Server Name\"] = serverName, [\"Display Name\"] = serverDisplayName, [\"Computer Name\"] = serverComputerName, \r\n [\"Server State\"] = serverProvisioningState, [\"Server Status\"] = serverServerStatus, [\"MSSQL Discovered\"] = serverMssqldiscovered,\r\n [\"Subscription Id\"] = serverSubscriptionId, [\"Location\"] = serverLocation, [\"Resource Group\"] = serverResourceGroup, [\"Server Tags\"] = serverTags\r\n | join kind= leftouter (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where properties.type in (\"WindowsAgent.SqlServer\",\"LinuxAgent.SqlServer\")\r\n | extend extensionId = tolower(tostring(id))\r\n | extend extensionServerId = tolower(tostring (iff(id contains \"/extensions/WindowsAgent.SqlServer\" or id contains \"/extensions/LinuxAgent.SqlServer\", \r\n substring(id, 0, indexof(id, \"/extensions/\")), \r\n \"\")))\r\n | extend extensionProvisioningState = tostring(properties.provisioningState) \r\n | extend extensionType = tostring(properties.type)\r\n | extend extensionVersion = properties.instanceView.typeHandlerVersion\r\n | parse properties with * 'SQL Server Extension Agent: ' sqlAgentStatus ';' *\r\n | parse properties with * 'uploadStatus : ' uploadStatus ';' *\r\n | extend extensionSQLAgentStatus = iff(isnotempty(sqlAgentStatus),sqlAgentStatus,\"Unhealthy\")\r\n | extend extensionUploadStatus = iff(isnotempty(uploadStatus),uploadStatus,\"Missing SQL Agent\")\r\n | extend extensionLicenseType = tostring(properties.settings.LicenseType)\r\n | project extensionId, extensionServerId,\r\n [\"Extension State\"] = extensionProvisioningState, [\"SQLExtension Status\"] = extensionSQLAgentStatus, [\"DPS Status\"] = extensionUploadStatus, \r\n [\"Extension Name\"] = extensionType, [\"Extension Version\"] = extensionVersion,\r\n [\"Extension License\"] = extensionLicenseType\r\n )\r\n on $left.serverId == $right.extensionServerId\r\n | summarize uploadStatusBreakdown = count() by [\"DPS Status\"]",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "chartType",
|
||||
"value": 1,
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "isShared",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "queryId",
|
||||
"value": "",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "formatResults",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "queryScope",
|
||||
"value": {
|
||||
"scope": 0,
|
||||
"values": []
|
||||
},
|
||||
"isOptional": true
|
||||
}
|
||||
],
|
||||
"type": "Extension/HubsExtension/PartType/ArgQueryChartTile",
|
||||
"settings": {},
|
||||
"partHeader": {
|
||||
"title": "Data Processing Service (DPS) Status",
|
||||
"subtitle": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,6 +741,60 @@
|
||||
"subtitle": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"14": {
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 30,
|
||||
"colSpan": 19,
|
||||
"rowSpan": 4
|
||||
},
|
||||
"metadata": {
|
||||
"inputs": [
|
||||
{
|
||||
"name": "partTitle",
|
||||
"value": "Query 1",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "chartType",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "isShared",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "queryId",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "formatResults",
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "queryScope",
|
||||
"value": {
|
||||
"scope": 0,
|
||||
"values": []
|
||||
},
|
||||
"isOptional": true
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"value": "resources\r\n | where type =~ 'microsoft.hybridcompute/machines/extensions'\r\n | where properties.type in ('WindowsAgent.SqlServer','LinuxAgent.SqlServer')\r\n | parse id with * '/providers/Microsoft.HybridCompute/machines/' machineName '/extensions/' *\r\n | parse properties with * 'uploadStatus : ' uploadStatus ';' *\r\n | project uploadStatus, subscriptionId, resourceGroup, machineName\r\n | where uploadStatus !in ('OK') //comment this out to see all upload stats\r\n | order by uploadStatus desc",
|
||||
"isOptional": true
|
||||
}
|
||||
],
|
||||
"type": "Extension/HubsExtension/PartType/ArgQueryGridTile",
|
||||
"settings": {
|
||||
"content": {}
|
||||
},
|
||||
"partHeader": {
|
||||
"title": "Servers with Errors communicating with Azure DPS Service",
|
||||
"subtitle": "For more information see https://learn.microsoft.com/en-us/sql/sql-server/azure-arc/troubleshoot-telemetry-endpoint"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ Private policies As Policy() = Nothing
|
||||
Private srcSiteUrl As String = Nothing
|
||||
Private snkSiteUrl As String = Nothing
|
||||
|
||||
Private logFilePath As String = "MigrationLog.txt"
|
||||
Private logFilePath As String = "MigrationLog.csv"
|
||||
|
||||
''''''''''''''''''''''''''''''''''''''''''''''
|
||||
Sub Main()
|
||||
@@ -241,8 +241,8 @@ Sub Main()
|
||||
RsSnk.CreateFolder(folderName, parentFolder, Nothing)
|
||||
Catch er As Exception
|
||||
If (er.Message.Contains("Microsoft.ReportingServices.Diagnostics.Utilities.ItemAlreadyExistsException"))
|
||||
Console.ForegroundColor = ConsoleColor.Yellow
|
||||
Console.WriteLine("Folder already exists.")
|
||||
Console.ForegroundColor = ConsoleColor.Gray
|
||||
Console.WriteLine("Folder already exists: " + folderName)
|
||||
Console.ResetColor()
|
||||
Else
|
||||
Throw ' Unknown Exception
|
||||
@@ -478,7 +478,7 @@ Function MigrateDataSource(srcItem As CatalogItem) As CatalogItem
|
||||
Console.WriteLine(warningMsg)
|
||||
Console.ResetColor()
|
||||
|
||||
LogErrorToFile("CATALOGITEM", "Warning", warningMsg)
|
||||
LogErrorToFile("CATALOGITEM", "Warning: " + result.Path, warningMsg)
|
||||
|
||||
End If
|
||||
|
||||
@@ -567,7 +567,7 @@ Function MigrateCatalogItem(srcItem As CatalogItem) As CatalogItem
|
||||
Console.WriteLine(warningMsg)
|
||||
Console.ResetColor()
|
||||
|
||||
LogErrorToFile("CATALOGITEM", "Warning", warningMsg)
|
||||
LogErrorToFile("CATALOGITEM", "Warning: " + result.TypeName, warningMsg)
|
||||
End If
|
||||
End If
|
||||
Next
|
||||
@@ -742,7 +742,7 @@ Sub MigrateReportSubscriptions(snkReport As CatalogItem, srcReport As CatalogIte
|
||||
Console.Write(description)
|
||||
If Not dataRetrievalPlan Is Nothing And TypeOf dataRetrievalPlan.Item Is DataSourceReference Then
|
||||
Dim item As DataSourceReference = dataRetrievalPlan.Item
|
||||
Dim ref As String = GetSnkPath(item.Reference)
|
||||
Dim ref As String = GetSnkPathRef(item.Reference)
|
||||
item.Reference = ref
|
||||
End If
|
||||
RsSnk.CreateDataDrivenSubscription(snkReport.Path, extensionSettings, dataRetrievalPlan, description, eventType, GetMatchData(matchData, RsSnk), parameters)
|
||||
@@ -756,7 +756,7 @@ Sub MigrateReportSubscriptions(snkReport As CatalogItem, srcReport As CatalogIte
|
||||
Console.WriteLine(warningMsg)
|
||||
Console.ResetColor()
|
||||
|
||||
LogErrorToFile("SUBSCRIPTIONS", "Warning", warningMsg)
|
||||
LogErrorToFile("SUBSCRIPTIONS", "Warning: " + snkReport.Path, warningMsg)
|
||||
|
||||
End If
|
||||
End If
|
||||
@@ -795,9 +795,9 @@ Sub MigrateReportSubscriptions(snkReport As CatalogItem, srcReport As CatalogIte
|
||||
Console.WriteLine(warningMsg)
|
||||
Console.ResetColor()
|
||||
|
||||
LogErrorToFile("SUBSCRIPTIONS", "Warning", warningMsg)
|
||||
LogErrorToFile("SUBSCRIPTIONS", "Warning: " + snkReport.Path, warningMsg)
|
||||
|
||||
End If
|
||||
End If
|
||||
Next
|
||||
|
||||
Catch ex As Exception
|
||||
@@ -990,10 +990,19 @@ End Sub
|
||||
|
||||
'Helper function to link snk reference with relative paths
|
||||
Function GetSnkPathRef(srcPath As String) As String
|
||||
Dim snkPath = srcPath
|
||||
If srcSiteUrl isNot Nothing
|
||||
snkPath = srcPath.Remove(0, srcSiteUrl.LastIndexOf("/"))
|
||||
End If
|
||||
Dim snkPath = srcPath
|
||||
|
||||
If Not SrcIsNative Then
|
||||
'SharePoint integrated mode, will assume /sites server relative URL delimeter
|
||||
If srcSiteUrl isNot Nothing
|
||||
Dim delimeter = "/sites/"
|
||||
snkPath = srcPath.Remove(0, srcSiteUrl.IndexOf(delimeter) + delimeter.Length-1)
|
||||
End If
|
||||
Else
|
||||
'Treat path reference like other scenarios if not intergated mode
|
||||
snkPath = GetSnkPath(srcPath)
|
||||
End If
|
||||
|
||||
Return snkPath
|
||||
End Function
|
||||
|
||||
@@ -1189,12 +1198,18 @@ Sub LogErrorToFile(source As String, errItem as String, message As String)
|
||||
Dim writer as StreamWriter
|
||||
|
||||
Try
|
||||
Dim fileExists As Boolean = Not File.Exists(logFilePath) OrElse New FileInfo(logFilePath).Length = 0
|
||||
|
||||
' Create a StreamWriter object
|
||||
writer = New StreamWriter(logFilePath, True)
|
||||
|
||||
If fileExists Then
|
||||
writer.WriteLine("DateTime, Source, ErrorItem, ErrorMessage")
|
||||
End If
|
||||
|
||||
' Format log message with timestamp
|
||||
Dim timeStamp As String = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
Dim formattedMessage as String = String.Format("{0}, {1}, {2}, ""{3}""", timeStamp, source, errItem, message.Replace(Environment.NewLine, ""))
|
||||
Dim formattedMessage as String = String.Format("{0}, {1}, {2}, ""{3}""", timeStamp, source, errItem, message.Replace(Environment.NewLine, " "))
|
||||
|
||||
' Write the log entry to file
|
||||
writer.WriteLine(formattedMessage)
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.10.4" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.11.0" />
|
||||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.5.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.4" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="3.0.0" />
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Azure.Core" Version="1.35.0" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.10.2" />
|
||||
<PackageReference Include="Azure.Core" Version="1.38.0" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.11.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.6.1" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.3" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="3.0.0" />
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.10.2" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.11.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.6" />
|
||||
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.6.1" />
|
||||
|
||||
@@ -30,7 +30,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>. If not specified all subscriptions will be scanned|
|
||||
|-ResourceGroup |resource_group_name|Optional: Limits the scope to a specific resource group|
|
||||
|-MachineName |machine_name|Optional: Limits the scope to a specific machine|
|
||||
|-LicenceType | "Paid", "PAYG" or "LicenseOnly"| Optional: Sets the license type to the specified value |
|
||||
|-LicenseType | "Paid", "PAYG" or "LicenseOnly"| Optional: Sets the license type to the specified value |
|
||||
|-EnableESU | "Yes", "No" | Optional. Enables the ESU policy the value is "Yes" or disables it if the value is "No". To enable, the license type must be "Paid" or "PAYG"|
|
||||
|-Force| |Optional. Forces the change of the license type to the specified value on all installed extensions. If -Force is not specified, the -LicenseType value is set only if undefined. Ignored if -LicenseType is not specified|
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
# Enter parameters
|
||||
$subscriptionId = Read-Host "<Subscription ID>"
|
||||
$resourceGroup = Read-Host "<Resource Group>"
|
||||
$vmName = Read-Host "<Virtual machine name>"
|
||||
|
||||
# Set resource details
|
||||
$resourceType = "Microsoft.Compute/virtualMachines"
|
||||
$resourceId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/$resourceType/$vmName"
|
||||
|
||||
# Get Azure access token
|
||||
$accessToken = az account get-access-token --query accessToken -o tsv
|
||||
|
||||
# Invoke Azure Monitor Metrics API
|
||||
function Get-Metrics {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[string]$accessToken,
|
||||
[string]$resourceId,
|
||||
[string]$metricNames,
|
||||
[string]$apiVersion = "2023-10-01"
|
||||
)
|
||||
try {
|
||||
$startTime = (Get-Date).AddHours(-24).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
|
||||
$endTime = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
|
||||
$timespan = "$startTime/$endTime"
|
||||
Write-Verbose "Evaluating timespan: $timespan"
|
||||
$uri = "https://management.azure.com$resourceId/providers/Microsoft.Insights/metrics?api-version=$apiVersion&metricnames=$metricNames&aggregation=maximum&interval=PT1M×pan=$timespan"
|
||||
$headers = @{ "Authorization" = "Bearer $accessToken"; "Content-Type" = "application/json" }
|
||||
|
||||
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
|
||||
if ($response) {
|
||||
Write-Verbose "API response successfully retrieved."
|
||||
return $response
|
||||
} else {
|
||||
Write-Error "No response from API."
|
||||
}
|
||||
} catch {
|
||||
Write-Error "Error retrieving metrics: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Check if data disk latency violates thresholds
|
||||
function Check-Latency {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Object]$metrics,
|
||||
|
||||
[Parameter()]
|
||||
[int]$latencyThreshold = 500,
|
||||
|
||||
[Parameter()]
|
||||
[int]$consecutiveCount = 5
|
||||
)
|
||||
$violationTimes = @()
|
||||
foreach ($metric in $metrics.value) {
|
||||
if ($metric.name.value -eq "Data Disk Latency") {
|
||||
$count = 0
|
||||
foreach ($dataPoint in $metric.timeseries[0].data) {
|
||||
if ($dataPoint.maximum -gt $latencyThreshold) {
|
||||
$count++
|
||||
if ($count -ge $consecutiveCount) {
|
||||
$violationTimes += $dataPoint.timeStamp
|
||||
$count = 0 # Reset count after recording a violation
|
||||
}
|
||||
} else {
|
||||
$count = 0 # Reset count if the sequence is broken
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($violationTimes.Count -gt 0) {
|
||||
Write-Verbose "Latency violations detected."
|
||||
return @{ "Flag" = $true; "Times" = $violationTimes }
|
||||
} else {
|
||||
Write-Verbose "No latency violations detected."
|
||||
return @{ "Flag" = $false }
|
||||
}
|
||||
}
|
||||
|
||||
# Check metrics other than latency to evaluate for throttling
|
||||
function Check-OtherMetricsThrottled {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Object]$metrics,
|
||||
|
||||
[Parameter()]
|
||||
[int]$PercentageThreshold = 90,
|
||||
|
||||
[Parameter()]
|
||||
[int]$consecutiveCount = 5
|
||||
)
|
||||
$violatedMetrics = @()
|
||||
foreach ($metric in $metrics.value) {
|
||||
$count = 0
|
||||
foreach ($dataPoint in $metric.timeseries[0].data) {
|
||||
if ($dataPoint.maximum -gt $PercentageThreshold) {
|
||||
$count++
|
||||
if ($count -ge $consecutiveCount) {
|
||||
$violatedMetrics += @{ "Metric" = $metric.name.localizedValue; "Time" = $dataPoint.timeStamp; "Value" = $dataPoint.maximum }
|
||||
break
|
||||
}
|
||||
} else {
|
||||
$count = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($violatedMetrics.Count -gt 0) {
|
||||
Write-Verbose "Other metrics violations detected."
|
||||
} else {
|
||||
Write-Verbose "No other metrics violations detected."
|
||||
}
|
||||
return $violatedMetrics
|
||||
}
|
||||
|
||||
# Compare times for latency & other throttled metrics. Logs the volations with values & timestamps
|
||||
function CompareTimes {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Hashtable]$latencyResult,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Array]$otherMetrics
|
||||
)
|
||||
foreach ($metric in $otherMetrics) {
|
||||
$otherDateTime = [DateTime]$metric["Time"]
|
||||
$isWithinFiveMinutes = $false
|
||||
$closestLatencyTime = $null
|
||||
$closestTimeDifference = [int]::MaxValue
|
||||
|
||||
foreach ($latencyTime in $latencyResult.Times) {
|
||||
$latencyDateTime = [DateTime]$latencyTime
|
||||
$timeDifference = [Math]::Abs(($otherDateTime - $latencyDateTime).TotalMinutes)
|
||||
|
||||
if ($timeDifference -le 5) {
|
||||
$isWithinFiveMinutes = $true
|
||||
if ($timeDifference -lt $closestTimeDifference) {
|
||||
$closestTimeDifference = $timeDifference
|
||||
$closestLatencyTime = $latencyTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isWithinFiveMinutes) {
|
||||
if ($otherDateTime -lt $closestLatencyTime) {
|
||||
Write-Host "`n $($metric["Metric"]) limit was hit before latency spiked at $closestLatencyTime with value $($metric["Value"]). `n"
|
||||
} else {
|
||||
Write-Host "`n $($metric["Metric"]) hit its limit with value $($metric["Value"]) at $($metric["Time"])."
|
||||
Write-Host "Latency spiked at $closestLatencyTime before $($metric["Metric"]) hit its limit `n"
|
||||
}
|
||||
} else {
|
||||
Write-Host "`n Metric: $($metric["Metric"]) exceeded its threshold with a value of $($metric["Value"]) at $($metric["Time"]), but this was not within 5 minutes of any latency spikes."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Prompt user for latency threshold
|
||||
$latencyThreshold = Read-Host "Enter Latency Threshold (default is 500)"
|
||||
if (-not [int]::TryParse($latencyThreshold, [ref]0)) {
|
||||
$latencyThreshold = 500 # Use default if invalid input
|
||||
Write-Host "No valid input provided. Using Default 500ms for disk latency threshold"
|
||||
}
|
||||
|
||||
# Execute main logic
|
||||
$latencyMetrics = Get-Metrics -accessToken $accessToken -resourceId $resourceId -metricNames "Data Disk Latency"
|
||||
$latencyResult = Check-Latency -metrics $latencyMetrics -latencyThreshold $latencyThreshold
|
||||
|
||||
if ($latencyResult.Flag) {
|
||||
|
||||
# If latency is flagged, check for other metrics. If there is no disk latency, machine is likely not throttled but only at high consumption
|
||||
Write-Verbose "Checking the following metrics: Data Disk Bandwidth Consumed Percentage,Data Disk IOPS Consumed Percentage,VM Cached Bandwidth Consumed Percentage,VM Cached IOPS Consumed Percentage,VM Uncached Bandwidth Consumed Percentage,VM Uncached IOPS Consumed Percentage"
|
||||
|
||||
$DiskVMMetrics = Get-Metrics -accessToken $accessToken -resourceId $resourceId -metricNames "Data Disk Bandwidth Consumed Percentage,Data Disk IOPS Consumed Percentage,VM Cached Bandwidth Consumed Percentage,VM Cached IOPS Consumed Percentage,VM Uncached Bandwidth Consumed Percentage,VM Uncached IOPS Consumed Percentage"
|
||||
|
||||
$additionalMetrics = Check-OtherMetricsThrottled -metrics $DiskVMMetrics
|
||||
|
||||
if ($additionalMetrics.Count -gt 0) {
|
||||
CompareTimes $latencyResult $additionalMetrics
|
||||
} else {
|
||||
Write-Host "No metrics violations detected besides latency."
|
||||
}
|
||||
} else {
|
||||
Write-Host "No latency issues detected."
|
||||
}
|
||||
Reference in New Issue
Block a user