diff --git a/samples/features/azure-arc/workbooks/Azure Arc Sql Databases.workbook b/samples/features/azure-arc/workbooks/Azure Arc Sql Databases.workbook new file mode 100644 index 0000000000..585f8428a2 --- /dev/null +++ b/samples/features/azure-arc/workbooks/Azure Arc Sql Databases.workbook @@ -0,0 +1,129 @@ +{ + "version": "Notebook/1.0", + "items": [ + { + "type": 1, + "content": { + "json": "# Azure Arc SQL Databases" + }, + "name": "text - 3" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type == \"microsoft.azurearcdata/sqlserverinstances/databases\"\r\n| extend compatibilityLevel = tostring(properties['compatibilityLevel'])\r\n| summarize Databases = count(compatibilityLevel) by compatibilityLevel", + "size": 3, + "title": "Count of Databases by Compatibility Level", + "queryType": 1, + "resourceType": "microsoft.resources/tenants", + "crossComponentResources": [ + "value::tenant" + ], + "visualization": "piechart", + "chartSettings": { + "showMetrics": false, + "showLegend": true + } + }, + "customWidth": "33", + "name": "Count of Databases by Compatibility Level" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type == \"microsoft.azurearcdata/sqlserverinstances/databases\"\r\n| extend recoveryMode = tostring(properties.recoveryMode)\r\n| summarize Databases = count(recoveryMode) by recoveryMode", + "size": 3, + "title": "Count of Databases by Recovery Model", + "queryType": 1, + "resourceType": "microsoft.resources/tenants", + "crossComponentResources": [ + "value::tenant" + ], + "visualization": "piechart", + "chartSettings": { + "showMetrics": false, + "showLegend": true + } + }, + "customWidth": "33", + "name": "Query 2" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type == \"microsoft.azurearcdata/sqlserverinstances/databases\"\r\n| extend lastFullBackup = iff(isnull(properties['backupInformation']['lastFullBackup']),'False','True')\r\n| summarize Databases = count(lastFullBackup) by lastFullBackup", + "size": 3, + "title": "Count of Databases by Full Backup", + "queryType": 1, + "resourceType": "microsoft.resources/tenants", + "crossComponentResources": [ + "value::tenant" + ], + "visualization": "piechart", + "chartSettings": { + "showMetrics": false, + "showLegend": true, + "seriesLabelSettings": [ + { + "seriesName": "True", + "color": "green" + }, + { + "seriesName": "False", + "color": "redBright" + } + ] + } + }, + "customWidth": "33", + "name": "Query 2 - Copy" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type == \"microsoft.azurearcdata/sqlserverinstances/databases\"\r\n| extend compatibilityLevel = tostring(properties['compatibilityLevel'])\r\n| extend lastFullBackup = properties['backupInformation']['lastFullBackup']\r\n| extend lastLogBackup = properties['backupInformation']['lastLogBackup']\r\n| extend Instance = split(['id'],'/Databases')[0]\r\n| project Instance, Database = id ,lastFullBackup = format_datetime(todatetime(lastFullBackup), \"yyyy-MM-dd HH:mm:ss\"),lastLogBackup = format_datetime(todatetime(lastLogBackup), \"yyyy-MM-dd HH:mm:ss\"),compatibilityLevel,recoveryMode = properties.recoveryMode", + "size": 3, + "title": "Databases Backup State", + "queryType": 1, + "resourceType": "microsoft.resources/tenants", + "crossComponentResources": [ + "value::tenant" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "Instance", + "formatter": 13, + "formatOptions": { + "linkTarget": "Resource", + "showIcon": true + } + }, + { + "columnMatch": "Database", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "56.714ch" + } + } + ], + "rowLimit": 1000, + "filter": true + }, + "chartSettings": { + "showMetrics": false, + "showLegend": true + } + }, + "name": "Query2" + } + ], + "fallbackResourceIds": [ + "azure monitor" + ], + "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" +} \ No newline at end of file diff --git a/samples/features/azure-arc/workbooks/Azure Arc Sql Servers - Best Practices Assessment.workbook b/samples/features/azure-arc/workbooks/Azure Arc Sql Servers - Best Practices Assessment.workbook new file mode 100644 index 0000000000..cccc343aa7 --- /dev/null +++ b/samples/features/azure-arc/workbooks/Azure Arc Sql Servers - Best Practices Assessment.workbook @@ -0,0 +1,759 @@ +{ + "version": "Notebook/1.0", + "items": [ + { + "type": 1, + "content": { + "json": "# Azure Arc SQL Servers - Best Practices Assessment" + }, + "name": "text - 2" + }, + { + "type": 9, + "content": { + "version": "KqlParameterItem/1.0", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "parameters": [ + { + "id": "5ccbaa77-2a42-4a07-a877-b5a3a6297703", + "version": "KqlParameterItem/1.0", + "name": "Subscription", + "type": 6, + "isRequired": true, + "multiSelect": true, + "quote": "'", + "delimiter": ",", + "typeSettings": { + "additionalResourceOptions": [ + "value::all" + ], + "includeAll": true, + "showDefault": false + }, + "timeContext": { + "durationMs": 86400000 + }, + "value": [ + "value::all" + ] + }, + { + "id": "f932fba5-395b-423d-a9b0-538714e1e553", + "version": "KqlParameterItem/1.0", + "name": "LogAnalitycsWorkpace", + "type": 5, + "query": "where type =~ 'microsoft.operationalinsights/workspaces'\r\n| project id", + "crossComponentResources": [ + "{Subscription}" + ], + "typeSettings": { + "additionalResourceOptions": [] + }, + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources", + "value": "/subscriptions/df77550b-74e4-4b51-a312-a1116f915de8/resourceGroups/RG-LAWS/providers/Microsoft.OperationalInsights/workspaces/LAWS-PRO-01" + }, + { + "id": "95f19603-3001-41ca-8340-621f3887a3b3", + "version": "KqlParameterItem/1.0", + "name": "ServerName", + "type": 2, + "description": "Azure Arc ServerName", + "multiSelect": true, + "quote": "'", + "delimiter": ",", + "query": "SqlAssessment_CL\r\n| extend asmt = parse_csv(RawData)\r\n| extend TargetType=case(asmt[6] == 1, \"Server\", asmt[6] == 2, \"Database\", \"\")\r\n| where TargetType == \"Server\"\r\n| extend ServerName = replace_string(tostring(asmt[7]),\"\\\\\",\"#\")\r\n| distinct ServerName", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "typeSettings": { + "additionalResourceOptions": [ + "value::all" + ], + "showDefault": false + }, + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "value": [ + "value::all" + ] + }, + { + "id": "9a4fed8e-66a1-4149-a9e5-e3df62e95407", + "version": "KqlParameterItem/1.0", + "name": "TimeRange", + "label": "Time Range", + "type": 4, + "isRequired": true, + "typeSettings": { + "selectableValues": [ + { + "durationMs": 3600000 + }, + { + "durationMs": 14400000 + }, + { + "durationMs": 43200000 + }, + { + "durationMs": 86400000 + }, + { + "durationMs": 172800000 + }, + { + "durationMs": 259200000 + }, + { + "durationMs": 604800000 + }, + { + "durationMs": 1209600000 + }, + { + "durationMs": 2419200000 + }, + { + "durationMs": 2592000000 + }, + { + "durationMs": 5184000000 + }, + { + "durationMs": 7776000000 + } + ] + }, + "value": { + "durationMs": 2592000000 + } + } + ], + "style": "pills", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "parameters - 7" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "SqlAssessment_CL\r\n| extend asmt = parse_csv(RawData)\r\n| extend\r\n AsmtId=tostring(asmt[1]),\r\n CheckId=tostring(asmt[2]),\r\n DisplayString=asmt[3],\r\n Description=tostring(asmt[4]),\r\n HelpLink=asmt[5],\r\n TargetType=case(asmt[6] == 1, \"Server\", asmt[6] == 2, \"Database\", \"\"),\r\n TargetName=tostring(asmt[7]), \r\n Severity=case(asmt[8] == 30, \"High\", asmt[8] == 20, \"Medium\", asmt[8] == 10, \"Low\", asmt[8] == 0, \"Information\", asmt[8] == 1, \"Warning\", asmt[8] == 2, \"Critical\", \"Passed\"),\r\n Message=tostring(asmt[9]),\r\n TagsArr=split(tostring(asmt[10]), \",\"),\r\n Sev = toint(asmt[8])\r\n | order by CheckId, _ResourceId\r\n | join kind=inner\r\n (\r\n SqlAssessment_CL\r\n | extend asmt = parse_csv(RawData)\r\n | extend\r\n AsmtId=tostring(asmt[1]) \r\n | summarize arg_max(TimeGenerated, *) by _ResourceId\r\n | project AsmtId\r\n ) on AsmtId\r\n |where Sev >= 0 \r\n and TargetType == \"Server\"\r\n | extend ServerName = case(TargetType == \"Server\", tostring(asmt[7]), tostring(split(tostring(asmt[7]),\":\",0)[0]))\r\n | extend ServerNameFilter = replace_string(ServerName,\"\\\\\",\"#\")\r\n | where ServerNameFilter in ({ServerName})\r\n | order by Sev, CheckId\r\n | summarize count() by TargetName \r\n", + "size": 3, + "title": "Issues by server", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "visualization": "piechart", + "chartSettings": { + "seriesLabelSettings": [ + { + "seriesName": "Protected", + "color": "green" + }, + { + "seriesName": "Unknown", + "color": "gray" + }, + { + "seriesName": "Not Protected", + "color": "redBright" + } + ] + } + }, + "customWidth": "33", + "name": "query - 4 - Copy" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "SqlAssessment_CL\r\n| extend asmt = parse_csv(RawData)\r\n| extend\r\n AsmtId=tostring(asmt[1]),\r\n CheckId=tostring(asmt[2]),\r\n DisplayString=asmt[3],\r\n Description=tostring(asmt[4]),\r\n HelpLink=asmt[5],\r\n TargetType=case(asmt[6] == 1, \"Server\", asmt[6] == 2, \"Database\", \"\"),\r\n TargetName=tostring(asmt[7]), \r\n Severity=case(asmt[8] == 30, \"High\", asmt[8] == 20, \"Medium\", asmt[8] == 10, \"Low\", asmt[8] == 0, \"Information\", asmt[8] == 1, \"Warning\", asmt[8] == 2, \"Critical\", \"Passed\"),\r\n Message=tostring(asmt[9]),\r\n TagsArr=split(tostring(asmt[10]), \",\"),\r\n Sev = toint(asmt[8])\r\n | order by CheckId, _ResourceId\r\n | join kind=inner\r\n (\r\n SqlAssessment_CL\r\n | extend asmt = parse_csv(RawData)\r\n | extend\r\n AsmtId=tostring(asmt[1]) \r\n | summarize arg_max(TimeGenerated, *) by _ResourceId\r\n | project AsmtId\r\n ) on AsmtId\r\n |where Sev >= 0 \r\n and TargetType == \"Database\"\r\n | extend ServerName = case(TargetType == \"Server\", tostring(asmt[7]), tostring(split(tostring(asmt[7]),\":\",0)[0]))\r\n | extend ServerNameFilter = replace_string(ServerName,\"\\\\\",\"#\")\r\n | where ServerNameFilter in ({ServerName})\r\n | order by Sev, CheckId\r\n | summarize count() by TargetName \r\n", + "size": 3, + "title": "Issues by Database", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "visualization": "piechart" + }, + "customWidth": "33", + "name": "query - 4 - Copy - Copy" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "SqlAssessment_CL\r\n| extend asmt = parse_csv(RawData)\r\n| extend\r\n AsmtId=tostring(asmt[1]),\r\n CheckId=tostring(asmt[2]),\r\n DisplayString=asmt[3],\r\n Description=tostring(asmt[4]),\r\n HelpLink=asmt[5],\r\n TargetType=case(asmt[6] == 1, \"Server\", asmt[6] == 2, \"Database\", \"\"),\r\n TargetName=tostring(asmt[7]), \r\n Severity=case(asmt[8] == 30, \"High\", asmt[8] == 20, \"Medium\", asmt[8] == 10, \"Low\", asmt[8] == 0, \"Information\", asmt[8] == 1, \"Warning\", asmt[8] == 2, \"Critical\", \"Passed\"),\r\n Message=tostring(asmt[9]),\r\n TagsArr=split(tostring(asmt[10]), \",\"),\r\n Sev = toint(asmt[8])\r\n | order by CheckId, _ResourceId\r\n | join kind=inner\r\n (\r\n SqlAssessment_CL\r\n | extend asmt = parse_csv(RawData)\r\n | extend\r\n AsmtId=tostring(asmt[1])\r\n | summarize arg_max(TimeGenerated, *) by _ResourceId\r\n | project AsmtId\r\n ) on AsmtId\r\n | where Sev >= 0\r\n | extend TargetType=case(asmt[6] == 1, \"Server\", asmt[6] == 2, \"Database\", \"\")\r\n | extend ServerName = case(TargetType == \"Server\", tostring(asmt[7]), tostring(split(tostring(asmt[7]),\":\",0)[0]))\r\n | extend ServerNameFilter = replace_string(ServerName,\"\\\\\",\"#\")\r\n | where ServerNameFilter in ({ServerName})\r\n | summarize count() by Severity, Sev\r\n | order by Sev", + "size": 3, + "title": "Issues by severity", + "timeContextFromParameter": "TimeRange", + "exportFieldName": "Severity", + "exportParameterName": "severity", + "exportDefaultValue": "", + "showExportToExcel": true, + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "visualization": "table", + "gridSettings": { + "formatters": [ + { + "columnMatch": "Severity", + "formatter": 8, + "formatOptions": { + "min": 0, + "max": 40, + "palette": "coldHot" + } + }, + { + "columnMatch": "Sev", + "formatter": 8, + "formatOptions": { + "min": 0, + "max": 40, + "palette": "coldHot" + } + } + ] + }, + "chartSettings": { + "seriesLabelSettings": [ + { + "seriesName": "Medium", + "color": "orange" + }, + { + "seriesName": "Low", + "color": "blue" + }, + { + "seriesName": "High", + "color": "redBright" + }, + { + "seriesName": "Information", + "color": "blueDark" + } + ], + "ySettings": { + "numberFormatSettings": { + "unit": 0, + "options": { + "style": "decimal", + "useGrouping": true + } + } + } + } + }, + "customWidth": "50", + "name": "query - 4" + }, + { + "type": 1, + "content": { + "json": "\"drawing\"\r\n\"drawing\"" + }, + "customWidth": "33", + "name": "text - 9" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "\r\nSqlAssessment_CL\r\n| extend asmt = parse_csv(RawData)\r\n| extend\r\n AsmtId=tostring(asmt[1]),\r\n CheckId=tostring(asmt[2]),\r\n DisplayString=asmt[3],\r\n Description=tostring(asmt[4]),\r\n HelpLink=asmt[5],\r\n TargetType=case(asmt[6] == 1, \"Server\", asmt[6] == 2, \"Database\", \"\"),\r\n TargetName=tostring(asmt[7]), \r\n Severity=case(asmt[8] == 30, \"High\", asmt[8] == 20, \"Medium\", asmt[8] == 10, \"Low\", asmt[8] == 0, \"Information\", asmt[8] == 1, \"Warning\", asmt[8] == 2, \"Critical\", \"Passed\"),\r\n Message=tostring(asmt[9]),\r\n TagsArr=split(tostring(asmt[10]), \",\"),\r\n Sev = toint(asmt[8])\r\n | order by CheckId, _ResourceId\r\n | join kind=inner\r\n (\r\n SqlAssessment_CL\r\n | extend asmt = parse_csv(RawData)\r\n | extend\r\n AsmtId=tostring(asmt[1]) \r\n | summarize arg_max(TimeGenerated, *) by _ResourceId\r\n | project AsmtId\r\n ) on AsmtId\r\n |where Sev >= 0\r\n | extend ServerName = case(TargetType == \"Server\", tostring(asmt[7]), tostring(split(tostring(asmt[7]),\":\",0)[0]))\r\n | extend ServerNameFilter = replace_string(ServerName,\"\\\\\",\"#\")\r\n | where ServerNameFilter in ({ServerName})\r\n and Severity == \"{severity}\"\r\n | extend SQLInstance = iff(TargetType == \"Database\",case(ServerName contains \"\\\\\",strcat(split(tolower(_ResourceId),\"microsoft.hybridcompute\")[0],\"Microsoft.AzureArcData/SqlServerInstances/\",replace_string(ServerName, \"\\\\\",\"_\")),strcat(split(tolower(_ResourceId),\"microsoft.hybridcompute\")[0],\"Microsoft.AzureArcData/SqlServerInstances/\",replace_string(ServerName, \"\\\\\",\"_\"))),\"\")\r\n | extend Database = iff(TargetType == \"Database\",case(ServerName contains \"\\\\\",strcat(split(tolower(_ResourceId),\"microsoft.hybridcompute\")[0],\"Microsoft.AzureArcData/SqlServerInstances/\",replace_string(ServerName, \"\\\\\",\"_\"),\"/Databases/\",tostring(split(TargetName,\":\")[1])),strcat(split(tolower(_ResourceId),\"microsoft.hybridcompute\")[0],\"Microsoft.AzureArcData/SqlServerInstances/\",replace_string(ServerName, \"\\\\\",\"_\"),\"/Databases/\",tostring(split(TargetName,\":\")[1]))),\"\")\r\n | order by Sev, CheckId\r\n | project TimeGenerated,TargetType, ArcServer =_ResourceId, SQLInstance, Database, HelpLink, Sev, Severity, CheckId, Description, AsmtId\r\n", + "size": 0, + "showAnalytics": true, + "title": "Issues by Severity Level: {severity}", + "timeContextFromParameter": "TimeRange", + "exportedParameters": [ + { + "fieldName": "Description", + "parameterName": "Description", + "parameterType": 1 + }, + { + "fieldName": "Message", + "parameterName": "Message", + "parameterType": 1 + } + ], + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "TimeGenerated", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "22.7143ch" + } + }, + { + "columnMatch": "TargetType", + "formatter": 18, + "formatOptions": { + "thresholdsOptions": "icons", + "thresholdsGrid": [ + { + "operator": "==", + "thresholdValue": "Server", + "representation": "resource", + "text": "{0}{1}" + }, + { + "operator": "Default", + "thresholdValue": null, + "representation": "ResourceFlat", + "text": "{0}" + } + ], + "customColumnWidthSetting": "14.7143ch" + } + }, + { + "columnMatch": "ArcServer", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "16.1429ch" + } + }, + { + "columnMatch": "SQLInstance", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "23.5714ch" + } + }, + { + "columnMatch": "Database", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "31.2857ch" + } + }, + { + "columnMatch": "HelpLink", + "formatter": 5 + }, + { + "columnMatch": "Sev", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "9ch" + } + }, + { + "columnMatch": "Severity", + "formatter": 18, + "formatOptions": { + "thresholdsOptions": "icons", + "thresholdsGrid": [ + { + "operator": "==", + "thresholdValue": "High", + "representation": "Sev0", + "text": "{0}" + }, + { + "operator": "==", + "thresholdValue": "Medium", + "representation": "Sev1", + "text": "{0}" + }, + { + "operator": "==", + "thresholdValue": "Low", + "representation": "Sev2", + "text": "{0}" + }, + { + "operator": "==", + "thresholdValue": "Information", + "representation": "Sev3", + "text": "{0}" + }, + { + "operator": "Default", + "thresholdValue": null, + "representation": "success", + "text": "{0}{1}" + } + ], + "customColumnWidthSetting": "16.5ch" + } + }, + { + "columnMatch": "CheckId", + "formatter": 18, + "formatOptions": { + "linkColumn": "HelpLink", + "linkTarget": "Url", + "thresholdsOptions": "icons", + "thresholdsGrid": [ + { + "operator": "Default", + "thresholdValue": null, + "representation": "Hyperlink", + "text": "{0}{1}" + } + ], + "customColumnWidthSetting": "16ch" + } + }, + { + "columnMatch": "Description", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "86.5714ch" + } + } + ], + "rowLimit": 500, + "filter": true, + "labelSettings": [ + { + "columnId": "SQLInstance", + "label": "SQL Instance" + } + ] + } + }, + "name": "query - 8" + }, + { + "type": 9, + "content": { + "version": "KqlParameterItem/1.0", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "parameters": [ + { + "id": "ced45d66-52da-4f9c-b473-6781309a78cc", + "version": "KqlParameterItem/1.0", + "name": "CheckId", + "type": 2, + "isRequired": true, + "query": "\r\nSqlAssessment_CL\r\n| extend asmt = parse_csv(RawData)\r\n| extend\r\n AsmtId=tostring(asmt[1]),\r\n CheckId=tostring(asmt[2]),\r\n DisplayString=asmt[3],\r\n Description=tostring(asmt[4]),\r\n HelpLink=asmt[5],\r\n TargetType=case(asmt[6] == 1, \"Server\", asmt[6] == 2, \"Database\", \"\"),\r\n TargetName=tostring(asmt[7]), \r\n Severity=case(asmt[8] == 30, \"High\", asmt[8] == 20, \"Medium\", asmt[8] == 10, \"Low\", asmt[8] == 0, \"Information\", asmt[8] == 1, \"Warning\", asmt[8] == 2, \"Critical\", \"Passed\"),\r\n Message=tostring(asmt[9]),\r\n TagsArr=split(tostring(asmt[10]), \",\"),\r\n Sev = toint(asmt[8])\r\n | order by CheckId, _ResourceId\r\n | join kind=inner\r\n (\r\n SqlAssessment_CL\r\n | extend asmt = parse_csv(RawData)\r\n | extend\r\n AsmtId=tostring(asmt[1]) \r\n | summarize arg_max(TimeGenerated, *) by _ResourceId\r\n | project AsmtId\r\n ) on AsmtId\r\n |where Sev >= 0\r\n | extend ServerName = case(TargetType == \"Server\", tostring(asmt[7]), tostring(split(tostring(asmt[7]),\":\",0)[0]))\r\n | extend ServerNameFilter = replace_string(ServerName,\"\\\\\",\"#\")\r\n | where ServerNameFilter in ({ServerName})\r\n and Severity == \"{severity}\"\r\n | distinct (CheckId)\r\n|order by CheckId asc\r\n\r\n\r\n ", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "typeSettings": { + "additionalResourceOptions": [ + "value::1" + ], + "showDefault": false + }, + "timeContext": { + "durationMs": 2592000000 + }, + "defaultValue": "value::1", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + } + ], + "style": "pills", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "paarameter_CheckId" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "SqlAssessment_CL\r\n| extend asmt = parse_csv(RawData)\r\n| extend\r\n AsmtId=tostring(asmt[1]),\r\n CheckId=tostring(asmt[2]),\r\n DisplayString=asmt[3],\r\n Description=tostring(asmt[4]),\r\n HelpLink=asmt[5],\r\n TargetType=case(asmt[6] == 1, \"Server\", asmt[6] == 2, \"Database\", \"\"),\r\n TargetName=tostring(asmt[7]), \r\n Severity=case(asmt[8] == 30, \"High\", asmt[8] == 20, \"Medium\", asmt[8] == 10, \"Low\", asmt[8] == 0, \"Information\", asmt[8] == 1, \"Warning\", asmt[8] == 2, \"Critical\", \"Passed\"),\r\n Message=tostring(asmt[9]),\r\n TagsArr=split(tostring(asmt[10]), \",\"),\r\n Sev = toint(asmt[8])\r\n | order by CheckId, _ResourceId\r\n | join kind=inner\r\n (\r\n SqlAssessment_CL\r\n | extend asmt = parse_csv(RawData)\r\n | extend\r\n AsmtId=tostring(asmt[1]) \r\n | summarize arg_max(TimeGenerated, *) by _ResourceId\r\n | project AsmtId\r\n ) on AsmtId\r\n | extend ServerName = case(TargetType == \"Server\", tostring(asmt[7]), tostring(split(tostring(asmt[7]),\":\",0)[0]))\r\n | extend ServerNameFilter = replace_string(ServerName,\"\\\\\",\"#\")\r\n | where ServerNameFilter in ({ServerName})\r\n | where CheckId == \"{CheckId}\"\r\n |where Sev >= 0\r\n | order by Sev, CheckId\r\n| project TimeGenerated, CheckId,Display = DisplayString,TargetName,Severity,TargetType,Description,Message,HelpLink,TagsArr", + "size": 0, + "timeContextFromParameter": "TimeRange", + "exportedParameters": [ + { + "fieldName": "Description", + "parameterName": "Description", + "parameterType": 1 + }, + { + "fieldName": "Message", + "parameterName": "Message", + "parameterType": 1 + }, + { + "fieldName": "HelpLink", + "parameterName": "HelpLink", + "parameterType": 1 + }, + { + "fieldName": "TagsArr", + "parameterName": "TagsArr", + "parameterType": 1 + } + ], + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalitycsWorkpace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "TimeGenerated", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "21.2857ch" + } + }, + { + "columnMatch": "CheckId", + "formatter": 18, + "formatOptions": { + "linkColumn": "HelpLink", + "linkTarget": "Url", + "linkLabel": "", + "linkIsContextBlade": false, + "thresholdsOptions": "icons", + "thresholdsGrid": [ + { + "operator": "Default", + "thresholdValue": null, + "representation": "Hyperlink", + "text": "{0}{1}" + } + ], + "customColumnWidthSetting": "21.5714ch" + } + }, + { + "columnMatch": "Display", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "42.2857ch" + } + }, + { + "columnMatch": "TargetName", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "26.7143ch" + } + }, + { + "columnMatch": "Severity", + "formatter": 18, + "formatOptions": { + "thresholdsOptions": "icons", + "thresholdsGrid": [ + { + "operator": "==", + "thresholdValue": "High", + "representation": "Sev0", + "text": "{0}" + }, + { + "operator": "==", + "thresholdValue": "Medium", + "representation": "Sev1", + "text": "{0}" + }, + { + "operator": "==", + "thresholdValue": "Low", + "representation": "Sev2", + "text": "{0}" + }, + { + "operator": "==", + "thresholdValue": "Information", + "representation": "Sev3", + "text": "{0}" + }, + { + "operator": "Default", + "thresholdValue": null, + "representation": "success", + "text": "{0}{1}" + } + ], + "customColumnWidthSetting": "11.1429ch" + } + }, + { + "columnMatch": "TargetType", + "formatter": 18, + "formatOptions": { + "thresholdsOptions": "icons", + "thresholdsGrid": [ + { + "operator": "==", + "thresholdValue": "Server", + "representation": "resource", + "text": "{0}{1}" + }, + { + "operator": "Default", + "thresholdValue": null, + "representation": "ResourceFlat", + "text": "{0}{1}" + } + ], + "customColumnWidthSetting": "16ch" + } + }, + { + "columnMatch": "Description", + "formatter": 5 + }, + { + "columnMatch": "Message", + "formatter": 5 + }, + { + "columnMatch": "HelpLink", + "formatter": 5 + }, + { + "columnMatch": "TagsArr", + "formatter": 5 + }, + { + "columnMatch": "DisplayString", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "46.2857ch" + } + } + ] + } + }, + "customWidth": "65", + "name": "query - 0" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "title": " Description", + "items": [ + { + "type": 1, + "content": { + "json": "{Description}", + "style": "info" + }, + "name": "Description", + "styleSettings": { + "showBorder": true + } + } + ] + }, + "name": "Description" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "title": " Message", + "items": [ + { + "type": 1, + "content": { + "json": "{Message}", + "style": "warning" + }, + "name": "text - 1" + } + ] + }, + "name": "Message" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "title": "Tags", + "items": [ + { + "type": 1, + "content": { + "json": "{TagsArr}" + }, + "name": "Tags" + } + ] + }, + "name": "Tags" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "title": " Help Link", + "items": [ + { + "type": 1, + "content": { + "json": "{HelpLink}" + }, + "name": "text - 2" + } + ] + }, + "name": "HelpLink" + } + ] + }, + "customWidth": "35", + "conditionalVisibility": { + "parameterName": "Message", + "comparison": "isNotEqualTo", + "value": "" + }, + "name": "Details", + "styleSettings": { + "showBorder": true + } + } + ] + }, + "name": "Issues" + } + ], + "fallbackResourceIds": [ + "azure monitor" + ], + "fromTemplateId": "sentinel-UserWorkbook", + "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" +} \ No newline at end of file diff --git a/samples/features/azure-arc/workbooks/Azure Arc Sql Servers.workbook b/samples/features/azure-arc/workbooks/Azure Arc Sql Servers.workbook new file mode 100644 index 0000000000..b5c1e6ec09 --- /dev/null +++ b/samples/features/azure-arc/workbooks/Azure Arc Sql Servers.workbook @@ -0,0 +1,396 @@ +{ + "version": "Notebook/1.0", + "items": [ + { + "type": 1, + "content": { + "json": "# Azure Arc SQL Server Instances" + }, + "name": "text - 2" + }, + { + "type": 9, + "content": { + "version": "KqlParameterItem/1.0", + "parameters": [ + { + "id": "5ccbaa77-2a42-4a07-a877-b5a3a6297703", + "version": "KqlParameterItem/1.0", + "name": "Subscription", + "type": 6, + "isRequired": true, + "multiSelect": true, + "quote": "'", + "delimiter": ",", + "typeSettings": { + "additionalResourceOptions": [ + "value::all" + ], + "includeAll": true, + "showDefault": false + }, + "timeContext": { + "durationMs": 86400000 + }, + "value": [ + "value::all" + ] + }, + { + "id": "43934901-a569-47a2-ab4e-116d2293204c", + "version": "KqlParameterItem/1.0", + "name": "ResourceGroup", + "label": "Resource Group", + "type": 2, + "isRequired": true, + "isGlobal": true, + "multiSelect": true, + "quote": "'", + "delimiter": ",", + "query": "Resources\r\n| summarize Count = count() by subscriptionId, resourceGroup\r\n| order by Count desc\r\n| extend Rank = row_number()\r\n| project label = resourceGroup", + "crossComponentResources": [ + "{Subscription}" + ], + "value": [ + "value::all" + ], + "typeSettings": { + "additionalResourceOptions": [ + "value::all" + ], + "showDefault": false + }, + "timeContext": { + "durationMs": 86400000 + }, + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources" + }, + { + "id": "4b0fd3d8-de3e-41b8-88d9-7e704404ad6b", + "version": "KqlParameterItem/1.0", + "name": "ServerName", + "label": "Server Name Filter", + "type": 1, + "value": "" + }, + { + "version": "KqlParameterItem/1.0", + "name": "SQLInstance", + "type": 1, + "value": "", + "id": "2df50306-76d2-4686-abaf-78f57430e75a" + } + ], + "style": "pills", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "parameters - 7" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type =~ 'microsoft.hybridcompute/machines'\r\n| where properties.detectedProperties.mssqldiscovered == \"true\" and properties.status != \"Expired\"\r\n| count\r\n| extend Tittle=\"Number of Azure Arc SQL Servers:\"", + "size": 4, + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources", + "crossComponentResources": [ + "{Subscription}" + ], + "visualization": "tiles", + "tileSettings": { + "titleContent": { + "columnMatch": "Tittle", + "formatter": 1 + }, + "leftContent": { + "columnMatch": "Count", + "formatter": 12, + "formatOptions": { + "palette": "auto" + } + }, + "showBorder": true, + "size": "auto" + } + }, + "name": "query - 8" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type =~ \"Microsoft.AzureArcData/sqlServerInstances\"\r\n| extend SQLInstance = id, AzureArcServer = tolower(tostring(properties.containerResourceId))\r\n| join kind=inner (resources\r\n| where type in ('microsoft.hybridcompute/machines')\r\n| where properties.status != \"Expired\"\r\n| extend id = tolower(id)) on $left.AzureArcServer == $right.id\r\n| where SQLInstance like \"{SQLInstance}\"\r\n| where AzureArcServer like \"{ServerName}\"\r\n| extend version = tostring(properties.version)\r\n| summarize count() by tostring(version)\r\n| sort by version", + "size": 3, + "title": "SQL Versions", + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources", + "crossComponentResources": [ + "{Subscription}" + ], + "visualization": "piechart" + }, + "customWidth": "25", + "name": "query - 4" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type =~ \"Microsoft.AzureArcData/sqlServerInstances\"\r\n| extend SQLInstance = id, AzureArcServer = tolower(tostring(properties.containerResourceId))\r\n| join kind=inner (resources\r\n| where type in ('microsoft.hybridcompute/machines')\r\n| where properties.status != \"Expired\"\r\n| extend id = tolower(id)) on $left.AzureArcServer == $right.id\r\n| where SQLInstance like \"{SQLInstance}\"\r\n| where AzureArcServer like \"{ServerName}\"\r\n| extend azureDefenderStatus = tostring(properties.azureDefenderStatus)\r\n| summarize count() by tostring(azureDefenderStatus)\r\n| sort by azureDefenderStatus", + "size": 3, + "title": "Azure Defender Status", + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources", + "crossComponentResources": [ + "{Subscription}" + ], + "visualization": "piechart", + "chartSettings": { + "seriesLabelSettings": [ + { + "seriesName": "Protected", + "color": "green" + }, + { + "seriesName": "Unknown", + "color": "gray" + }, + { + "seriesName": "Not Protected", + "color": "redBright" + } + ] + } + }, + "customWidth": "25", + "name": "query - 4 - Copy" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type =~ \"Microsoft.AzureArcData/sqlServerInstances\"\r\n| extend SQLInstance = id, AzureArcServer = tolower(tostring(properties.containerResourceId))\r\n| join kind=inner (resources\r\n| where type in ('microsoft.hybridcompute/machines')\r\n| where properties.status != \"Expired\"\r\n| extend id = tolower(id)) on $left.AzureArcServer == $right.id\r\n| where SQLInstance like \"{SQLInstance}\"\r\n| where AzureArcServer like \"{ServerName}\"\r\n| extend edition = tostring(properties.edition)\r\n| summarize count() by tostring(edition)\r\n| sort by edition", + "size": 3, + "title": "SQL Editions", + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources", + "crossComponentResources": [ + "{Subscription}" + ], + "visualization": "piechart", + "chartSettings": { + "seriesLabelSettings": [ + { + "seriesName": "Protected", + "color": "green" + }, + { + "seriesName": "Unknown", + "color": "gray" + }, + { + "seriesName": "Not Protected", + "color": "redBright" + } + ] + } + }, + "customWidth": "25", + "name": "query - 4 - Copy - Copy" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type =~ \"Microsoft.AzureArcData/sqlServerInstances\"\r\n| extend SQLInstance = id, AzureArcServer = tolower(tostring(properties.containerResourceId))\r\n| join kind=inner (resources\r\n| where type in ('microsoft.hybridcompute/machines')\r\n| where properties.status != \"Expired\"\r\n| extend id = tolower(id)) on $left.AzureArcServer == $right.id\r\n| where SQLInstance like \"{SQLInstance}\"\r\n| where AzureArcServer like \"{ServerName}\"\r\n| extend licenseType = tostring(properties.licenseType)\r\n| summarize count() by tostring(licenseType)\r\n| sort by licenseType", + "size": 3, + "title": "LicenseType", + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources", + "crossComponentResources": [ + "{Subscription}" + ], + "visualization": "piechart" + }, + "customWidth": "25", + "name": "query - 4 - Copy - Copy" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type =~ \"Microsoft.AzureArcData/sqlServerInstances\"\r\n| extend SQLInstance = id, AzureArcServer = tolower(tostring(properties.containerResourceId))\r\n| join kind=inner (resources\r\n| where type in ('microsoft.hybridcompute/machines')\r\n| where properties.status != \"Expired\"\r\n| extend model = tostring(properties.detectedProperties.model)\r\n| extend id = tolower(id)) on $left.AzureArcServer == $right.id\r\n| where SQLInstance like \"{SQLInstance}\"\r\n| where AzureArcServer like \"{ServerName}\"\r\n| extend createdAt = format_datetime(todatetime(systemData.createdAt), 'yyyy-MM-dd hh:mm:ss')\r\n| extend version = tostring(properties.version)\r\n| extend edition = tostring(properties.edition)\r\n| extend currentVersion = tostring(properties.currentVersion)\r\n| extend licenseType = tostring(properties.licenseType)\r\n| extend azureDefenderStatus = tostring(properties.azureDefenderStatus)\r\n| extend patchLevel = tostring(properties.patchLevel)\r\n| extend vcores = toint(properties.vCore)\r\n| project createdAt,AzureArcServer,SQLInstance,version,currentVersion,patchLevel,edition,model,vcores,subscriptionId,licenseType,azureDefenderStatus\r\n", + "size": 0, + "showAnalytics": true, + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources", + "crossComponentResources": [ + "{Subscription}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "createdAt", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "21.1429ch" + } + }, + { + "columnMatch": "AzureArcServer", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "20.1429ch" + } + }, + { + "columnMatch": "SQLInstance", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "23.4286ch" + } + }, + { + "columnMatch": "version", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "19ch" + } + }, + { + "columnMatch": "currentVersion", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "17.4286ch" + } + }, + { + "columnMatch": "patchLevel", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "17ch" + } + }, + { + "columnMatch": "edition", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "11.4286ch" + } + }, + { + "columnMatch": "model", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "18.4286ch" + } + }, + { + "columnMatch": "vcores", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "9.7143ch" + } + }, + { + "columnMatch": "subscriptionId", + "formatter": 13, + "formatOptions": { + "linkTarget": "Resource", + "showIcon": true, + "customColumnWidthSetting": "26.5714ch" + } + }, + { + "columnMatch": "licenseType", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "14.4286ch" + } + }, + { + "columnMatch": "azureDefenderStatus", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "25ch" + } + } + ], + "rowLimit": 500, + "filter": true + } + }, + "name": "query - 1" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "resources\r\n| where type =~ \"microsoft.hybridcompute/machines\"\r\n| where properties.mssqlDiscovered == true\r\n| extend id = tolower(id)\r\n| join kind=leftouter (resources\r\n| where type =~ \"microsoft.hybridcompute/machines/extensions\"\r\n| where properties.type in (\"WindowsAgent.SqlServer\", \"AzureMonitorWindowsAgent\")\r\n| extend ArcServerResourceId = tolower(tostring(split(id, \"/extensions\")[0]))\r\n| project ArcServerResourceId, ExtensionType = tostring(properties.type), ExtensionState = tostring(properties.provisioningState)\r\n| summarize SqlExtensionInstalled = countif(ExtensionType == \"WindowsAgent.SqlServer\" and ExtensionState == \"Succeeded\"), AMAExtensionInstalled = countif(ExtensionType == \"AzureMonitorWindowsAgent\" and ExtensionState == \"Succeeded\") by ArcServerResourceId)\r\non $left.id == $right.ArcServerResourceId\r\n| join kind=leftouter (resources\r\n| where type =~ \"microsoft.azurearcdata/sqlserverinstances\"\r\n| project ArcServerId = tolower(tostring(properties.containerResourceId)), SqlServerId = id, InstanceName = properties.instanceName, Version = properties.version\r\n| summarize ArcSqlServerInstances = make_list(split(SqlServerId, \"/\")[8]) by ArcServerId)\r\non $left.id == $right.ArcServerId\r\n| project ArcServer = id, SqlExtensionInstalled = (SqlExtensionInstalled==1), AMAExtensionInstalled = (AMAExtensionInstalled == 1), ArcSqlServerInstances\r\n", + "size": 0, + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources", + "crossComponentResources": [ + "{Subscription}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "SqlExtensionInstalled", + "formatter": 18, + "formatOptions": { + "thresholdsOptions": "icons", + "thresholdsGrid": [ + { + "operator": "==", + "thresholdValue": "0", + "representation": "Important", + "text": "" + }, + { + "operator": "Default", + "thresholdValue": null, + "representation": "success", + "text": "" + } + ] + } + }, + { + "columnMatch": "AMAExtensionInstalled", + "formatter": 18, + "formatOptions": { + "thresholdsOptions": "icons", + "thresholdsGrid": [ + { + "operator": "==", + "thresholdValue": "0", + "representation": "2", + "text": "" + }, + { + "operator": "Default", + "thresholdValue": null, + "representation": "success", + "text": "" + } + ] + } + } + ], + "rowLimit": 500, + "filter": true + } + }, + "name": "query - 0" + } + ], + "fallbackResourceIds": [ + "/subscriptions/59b301d0-77eb-4a76-aba5-53cb5731c342/resourcegroups/rg-sentinel/providers/microsoft.operationalinsights/workspaces/laws-trsentinel" + ], + "fromTemplateId": "sentinel-UserWorkbook", + "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" +} \ No newline at end of file diff --git a/samples/features/azure-arc/workbooks/README.md b/samples/features/azure-arc/workbooks/README.md new file mode 100644 index 0000000000..ef440aa661 --- /dev/null +++ b/samples/features/azure-arc/workbooks/README.md @@ -0,0 +1,22 @@ +![](../../../../media/solutions-microsoft-logo-small.png) + +# Azure Arc SQL Server Workbooks +This section contains workbooks to use with Azure Arc SQL Servers. Please follow this article from Microsoft Learn for instruccion about how to install them : +[Creating an Azure Workbook](https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-create-workbook#create-a-new-azure-workbook) +![](./img/Dashboard.png) + +## Azure Arc SQL Instances + +![AzureSqlServerInstances](img/AzureSqlServerInstances.png) + +## Azure Arc SQL Databases +![AzureArcSqlDatabases](img/AzureArcSqlDatabases.png) + + +## Azure Arc SQL Best Practices Assessment +![AzureSQLServerBPA](img/AzureSqlServerBPAa.png) +![AzureSQLServerBPA](img/AzureSqlServerBPAb.png) + + +## Disclaimers +The code included in this sample is not intended to be a set of best practices on how to build scalable enterprise grade applications. This is beyond the scope of this quick start sample. diff --git a/samples/features/azure-arc/workbooks/img/AzureArcSqlDatabases.png b/samples/features/azure-arc/workbooks/img/AzureArcSqlDatabases.png new file mode 100644 index 0000000000..c536506ab8 Binary files /dev/null and b/samples/features/azure-arc/workbooks/img/AzureArcSqlDatabases.png differ diff --git a/samples/features/azure-arc/workbooks/img/AzureSqlServerBPAa.png b/samples/features/azure-arc/workbooks/img/AzureSqlServerBPAa.png new file mode 100644 index 0000000000..f3e03f9f32 Binary files /dev/null and b/samples/features/azure-arc/workbooks/img/AzureSqlServerBPAa.png differ diff --git a/samples/features/azure-arc/workbooks/img/AzureSqlServerBPAb.png b/samples/features/azure-arc/workbooks/img/AzureSqlServerBPAb.png new file mode 100644 index 0000000000..0fc740c105 Binary files /dev/null and b/samples/features/azure-arc/workbooks/img/AzureSqlServerBPAb.png differ diff --git a/samples/features/azure-arc/workbooks/img/AzureSqlServerInstances.png b/samples/features/azure-arc/workbooks/img/AzureSqlServerInstances.png new file mode 100644 index 0000000000..d7546dfe08 Binary files /dev/null and b/samples/features/azure-arc/workbooks/img/AzureSqlServerInstances.png differ diff --git a/samples/manage/azure-arc-enabled-sql-server/report-reclass&extensionstatus/Get-SQLAzureArcReclassReport.ps1 b/samples/manage/azure-arc-enabled-sql-server/report-reclass&extensionstatus/Get-SQLAzureArcReclassReport.ps1 new file mode 100644 index 0000000000..0c03520f73 --- /dev/null +++ b/samples/manage/azure-arc-enabled-sql-server/report-reclass&extensionstatus/Get-SQLAzureArcReclassReport.ps1 @@ -0,0 +1,139 @@ +#Requires -Modules Az.ResourceGraph, Az.ConnectedMachine, Az.Accounts +# This script returns a report of the instances that should return SQL Reclass from the Azure Arc servers perspective +# It also returns all the extension status from Arc Servers that has mssqldiscovered to true + +#region Sample queries + +# Get SQL Azure Arc Servers +$queryArcSQLServers = @" +resources +| where type =~ 'microsoft.hybridcompute/machines' +| where properties.detectedProperties.mssqldiscovered == "true" +"@ + +# Get SQL Instances +$queryArcSQLServerinstances = @' +resources +| where ['type'] == "microsoft.azurearcdata/sqlserverinstances" +| project containerResourceId = properties.containerResourceId, instanceName = properties.instanceName, edition = properties.edition, version = properties.version, licenseType = properties.licenseType +'@ +#Get SQL Instances +$queryArcServerextensions = @" +resources +| where type == 'microsoft.hybridcompute/machines/extensions' +| where properties.type == "WindowsAgent.SqlServer" +"@ +#endregion + + +#region Report of servers generating SQL Reclass +# The following query returns all the SQL Enterprise and Standard Instances, whose Azure Arc server exists and it is not in Expired State +# The query returns the created time, version, licensetype and vcores +$queryglobal = @" +resources +| where type in ('microsoft.hybridcompute/machines') +| extend Status = properties.status, id = tolower(id), AzureArcServerName = name +| where properties.status != "Expired" +| join kind = fullouter +(resources +| where type == 'microsoft.hybridcompute/machines/extensions' +| where properties.type == "WindowsAgent.SqlServer" +| project id=tolower(id),AzureArcServerid = tolower(tostring(split(id,'/extensions/',0)[0])), ExVersion = properties.typeHandlerVersion, provisioningState = tostring(properties.provisioningState) +| order by provisioningState) on `$left.['id'] == `$right.AzureArcServerid +| join kind=inner ( +resources +| where type =~ "Microsoft.AzureArcData/sqlServerInstances" +| extend edition = tostring(properties.edition) +| where edition in ('Standard','Enterprise') +| extend AzureArcServerid = tolower(tostring(properties.containerResourceId)) +| extend SQLInstanceName = name +| extend createdAt = format_datetime(todatetime(systemData.createdAt), 'yyyy-MM-dd hh:mm:ss') +| extend version = tostring(properties.version) +| extend licenseType = tostring(properties.licenseType) +| extend vcores = toint(properties.vCore)) on AzureArcServerid +| where isnotempty(['id']) +| project createdAt,subscriptionId,resourceGroup,AzureArcServerName,Status,SQLInstanceName,version,edition,vcores,licenseType,ExVersion,provisioningState +"@ + +#KQL Global Query results +Write-Host "Querying servers that are generating SQL Reclass .. `n" -ForegroundColor Green +$ResultsGlobal = Search-AzGraph -Query $queryglobal -First 1000 + + +# View LicenseType property directly from Azure Arc Extension and merge the data in a single table + +Write-Host "Querying extension using powershell. This might take a while .. `n" -ForegroundColor Green +$ResultsGlobalGrouped = $ResultsGlobal | Group-Object -Property subscriptionid +$results = foreach ($group in $ResultsGlobalGrouped) { + #Get-AzSubscription -SubscriptionId $group.Name -WarningAction SilentlyContinue + $Subscription = Select-AzSubscription -SubscriptionId $group.Name -WarningAction SilentlyContinue + Write-Host "Getting SQL extension information from Subscription:" -ForegroundColor Green + Write-Host "$($Subscription.Subscription.Name) $($Subscription.Subscription.id)" -ForegroundColor Yellow + foreach ($arcobject in $group.Group ) { + $machinename = $arcobject.AzureArcServerName + $ResourceGroupName = $arcobject.resourceGroup + $licensetype = Get-AzConnectedMachineExtension -MachineName $machinename -ResourceGroupName $ResourceGroupName -Name WindowsAgent.SqlServer -ErrorAction SilentlyContinue | Select-Object -ExpandProperty setting | ConvertFrom-Json | Select-Object -ExpandProperty LicenseType -ErrorAction SilentlyContinue + $properties = [ordered]@{ + createdAt = $arcobject.createdAt + subscriptionId = $arcobject.subscriptionId + resourceGroup = $arcobject.resourceGroup + AzureArcServerName = $arcobject.AzureArcServerName + Status = $arcobject.Status + SQLInstanceName = $arcobject.SQLInstanceName + version = $arcobject.version + edition = $arcobject.edition + vcores = $arcobject.vcores + licenseTypeinGraph = $arcobject.licenseType + licenseTypeInExt = $licensetype + ExVersion = $arcobject.ExVersion + provisioningState = $arcobject.provisioningState + + } + New-Object -TypeName psobject -Property $properties + + } +} +# Variable $results contains all the info + +# The following exports in a CSV file the servers that are effectively generating ACR (2008 is not suported) +$global:ArcSQLServerinstanceswithReclass = $results | where version -notlike *2008* +$ArcSQLServerinstanceswithReclass | Export-Csv -Path .\ArcSQLServerinstanceswithReclass.csv -Force -NoTypeInformation +Write-Host "Results where exported to file 'ArcSQLServerinstanceswithReclass.csv' in the local folder" -ForegroundColor Green +#endregion + +############################### + +#region Report of SQL extension Status + +# The following query returns all the SQL extensions from servers that has mssqldiscovered set to True +# The query returns the Arc Server with its subscription, resource group, status, extension version and provisioning state + +$queryExtensionSatus = @" +resources +| where type =~ 'microsoft.hybridcompute/machines' +| where properties.detectedProperties.mssqldiscovered == "true" +| extend id = tolower(id), ResourceGroup = split (id,'/',4)[0], Status = properties.status +| join kind = inner (resources +| where type == 'microsoft.hybridcompute/machines/extensions' +| where properties.type == "WindowsAgent.SqlServer" +| extend provisioningState = tostring(properties.provisioningState), ExVersion = properties.typeHandlerVersion +| extend AzureArcServerid = tolower(tostring(split(id,'/extensions/',0)[0]))) on `$left.id == `$right.AzureArcServerid +| project subscriptionId,ResourceGroup,name,Status,ExVersion,provisioningState +| order by ['provisioningState'] asc +"@ + +Write-Host "`nQuerying status of the SQL Extensions in azure Arc servers .. `n" -ForegroundColor Green +$global:SQLExtensionStatus = Search-AzGraph -Query $queryExtensionSatus -First 1000 + +# The following exports in a CSV file the status of the SQL extension in the Azure Arc Servers +$SQLExtensionStatus | Export-Csv -Path .\SQLExtensionStatus.csv -Force -NoTypeInformation +Write-Host "Results where exported to file 'SQLExtensionStatus.csv' in the local folder" -ForegroundColor Green + +# region samples +# Servers with the license not set to Paid: +#$ArcSQLServerinstanceswithReclass | where licenseTypeinGraph -ne "Paid" | Select-Object subscriptionId,resourceGroup,AzureArcServerName,Status -Unique + +#Extensionstofix +#$ExtensionStatus | Out-GridView + +#endregion \ No newline at end of file diff --git a/samples/manage/azure-arc-enabled-sql-server/report-reclass&extensionstatus/README.md b/samples/manage/azure-arc-enabled-sql-server/report-reclass&extensionstatus/README.md new file mode 100644 index 0000000000..cd72763e51 --- /dev/null +++ b/samples/manage/azure-arc-enabled-sql-server/report-reclass&extensionstatus/README.md @@ -0,0 +1,68 @@ +--- +services: Azure Arc-enabled SQL Server +platforms: Azure +author: racarb +ms.date: 24/8/2023 +--- + +## Azure Arc SQL Reclass Report +# Overview + +This script provides a report of the Azure Arc SQL resources that generate SQL Reclass, including the following information from each resource in a CSV format: + + + +| **Promerty**                                                                     | **Description** | +|:--|:--| +|createdAt|The time when the Azure Arc SQL resource was created| +|subscriptionId|Id of the subscription +|resourceGroup|Resource group name +|AzureArcServerName|Azure Arc server where the SQL instance is in +|Status|Status of the SQL Instance Resource +|SQLInstanceName|SQL Instance name +|version|Version of the SQL instance +|edition| Edition of the SQL instance +|vcores|Number of cores +|licenseTypeinGraph| Licence setting's value, from Azure Graph perspective +|licenseTypeInExt|License setting's value, from the extension perspective (Powershell query) +|ExVersion| Version of the WindowsAgent.SqlServer extension +|provisioningState|Provision state of the WindowsAgent.SqlServer extension + +The file is stored in the local folder with the name ***ArcSQLServerinstanceswithReclass.csv*** + +
+ + +Aditionaly, another CSV is created with the information of all the WindowsAgent.SqlServer extensions from azure Arc Servers + +| **Promerty**                                                                     | **Description** | +|:--|:--| +|subscriptionId|Id of the subscription +|resourceGroup|Resource group name +|name|Azure Arc server where the SQL extension is installed +|Status|Status of the SQL extension +|ExVersion|Version of the SQL extension +|provisioningState|Provision state of the WindowsAgent.SqlServer extension + +The information is stored in the local folder with the name ***SQLExtensionStatus.csv*** + +
+ +# Prerequisites + +- The following powershell modules are needed: *Az.ResourceGraph, Az.ConnectedMachine, Az.Accounts* +- You must have at least a *Reader* role in each subscription from wich you want to extract the information +- You must be connected to Azure AD and logged in to your Azure account. If your account have access to multiple tenants, make sure to log in with a specific tenant ID. + + +# Launching the script + +The script doesn't have any parameter and it is launched as is: + +## Example 1 + +The following example gets a report from the subcriptions wich the users have access to + +```PowerShell +.\Get-SQLAzureArcReclassReport.ps1 +```