Explorar o código

Merge pull request #220 from microsoft/alex/hybridtoolkitbook

Azure SQL Hybrid Cloud Toolkit Jupyter Book addition
Alex Ma %!s(int64=5) %!d(string=hai) anos
pai
achega
4c2594cef7
Modificáronse 79 ficheiros con 8616 adicións e 0 borrados
  1. 63 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/.gitattributes
  2. 161 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl.ipynb
  3. 37 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl.sln
  4. 26 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/ADPControl.csproj
  5. 244 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/AzureResourceManagerActivity.cs
  6. 214 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/BatchActivity.cs
  7. 103 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/HttpSurface.cs
  8. 104 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/Orchestrator.cs
  9. 79 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/StorageActivity.cs
  10. 16 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/host.json
  11. 12 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/ActionType.cs
  12. 22 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/App.config
  13. 98 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/BatchWrapper.csproj
  14. 34 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/Constants.cs
  15. 38 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/Payload.cs
  16. 178 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/Program.cs
  17. 10 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/packages.config
  18. 27 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/.vscode/launch.json
  19. 42 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/.vscode/tasks.json
  20. 12 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/ActionType.cs
  21. 39 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/Constants.cs
  22. 38 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/Payload.cs
  23. 162 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/Program.cs
  24. 14 0
      SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/SqlPackageWrapper.csproj
  25. 1 0
      SQL-Hybrid-Cloud-Toolkit/Components/readme.md
  26. 4 0
      SQL-Hybrid-Cloud-Toolkit/README.md
  27. 2 0
      SQL-Hybrid-Cloud-Toolkit/_config.yml
  28. 80 0
      SQL-Hybrid-Cloud-Toolkit/_data/toc.yml
  29. 4 0
      SQL-Hybrid-Cloud-Toolkit/assets/css/styles.scss
  30. 4 0
      SQL-Hybrid-Cloud-Toolkit/assets/custom/custom.css
  31. 1 0
      SQL-Hybrid-Cloud-Toolkit/assets/custom/custom.js
  32. 25 0
      SQL-Hybrid-Cloud-Toolkit/assets/html/index.html
  33. 15 0
      SQL-Hybrid-Cloud-Toolkit/assets/html/search_form.html
  34. 1 0
      SQL-Hybrid-Cloud-Toolkit/assets/images/copy-button.svg
  35. 81 0
      SQL-Hybrid-Cloud-Toolkit/assets/images/edit-button.svg
  36. 19 0
      SQL-Hybrid-Cloud-Toolkit/assets/images/logo_binder.svg
  37. 1 0
      SQL-Hybrid-Cloud-Toolkit/assets/images/logo_jupyterhub.svg
  38. BIN=BIN
      SQL-Hybrid-Cloud-Toolkit/assets/images/sqlserver.png
  39. 7 0
      SQL-Hybrid-Cloud-Toolkit/assets/js/anchor.min.js
  40. 58 0
      SQL-Hybrid-Cloud-Toolkit/assets/js/ga.js
  41. 0 0
      SQL-Hybrid-Cloud-Toolkit/assets/js/lunr/lunr.min.js
  42. 150 0
      SQL-Hybrid-Cloud-Toolkit/assets/js/scripts.js
  43. 0 0
      SQL-Hybrid-Cloud-Toolkit/assets/js/tocbot.min.js
  44. 4 0
      SQL-Hybrid-Cloud-Toolkit/assets/js/turbolinks.js
  45. BIN=BIN
      SQL-Hybrid-Cloud-Toolkit/content/Assessments/CMS.png
  46. 96 0
      SQL-Hybrid-Cloud-Toolkit/content/Assessments/compatibility-assessment.ipynb
  47. 10 0
      SQL-Hybrid-Cloud-Toolkit/content/Assessments/readme.md
  48. 207 0
      SQL-Hybrid-Cloud-Toolkit/content/Assessments/sql-server-assessment.ipynb
  49. 78 0
      SQL-Hybrid-Cloud-Toolkit/content/ConnectionDialogue.ipynb
  50. 5 0
      SQL-Hybrid-Cloud-Toolkit/content/LICENSE.md
  51. 35 0
      SQL-Hybrid-Cloud-Toolkit/content/appendices.md
  52. BIN=BIN
      SQL-Hybrid-Cloud-Toolkit/content/data-portability/VisualBootstrapperNB.PNG
  53. 560 0
      SQL-Hybrid-Cloud-Toolkit/content/data-portability/export-sql-server.ipynb
  54. 530 0
      SQL-Hybrid-Cloud-Toolkit/content/data-portability/import-sql-server.ipynb
  55. 20 0
      SQL-Hybrid-Cloud-Toolkit/content/data-portability/readme.md
  56. 643 0
      SQL-Hybrid-Cloud-Toolkit/content/data-portability/setup-adp.ipynb
  57. 37 0
      SQL-Hybrid-Cloud-Toolkit/content/glossary.md
  58. 39 0
      SQL-Hybrid-Cloud-Toolkit/content/hadr/add-passive-secondary.ipynb
  59. 369 0
      SQL-Hybrid-Cloud-Toolkit/content/hadr/backup-to-blob.ipynb
  60. 10 0
      SQL-Hybrid-Cloud-Toolkit/content/hadr/readme.md
  61. 168 0
      SQL-Hybrid-Cloud-Toolkit/content/networking/download-VpnClient.ipynb
  62. 225 0
      SQL-Hybrid-Cloud-Toolkit/content/networking/p2svnet-creation.ipynb
  63. 13 0
      SQL-Hybrid-Cloud-Toolkit/content/networking/readme.md
  64. 372 0
      SQL-Hybrid-Cloud-Toolkit/content/networking/s2svnet-creation.ipynb
  65. 39 0
      SQL-Hybrid-Cloud-Toolkit/content/offline-migration/db-to-MI.ipynb
  66. 339 0
      SQL-Hybrid-Cloud-Toolkit/content/offline-migration/db-to-SQLDB.ipynb
  67. 271 0
      SQL-Hybrid-Cloud-Toolkit/content/offline-migration/db-to-VM.ipynb
  68. 34 0
      SQL-Hybrid-Cloud-Toolkit/content/offline-migration/instance-to-MI.ipynb
  69. 262 0
      SQL-Hybrid-Cloud-Toolkit/content/offline-migration/instance-to-VM.ipynb
  70. 16 0
      SQL-Hybrid-Cloud-Toolkit/content/offline-migration/readme.md
  71. 201 0
      SQL-Hybrid-Cloud-Toolkit/content/prereqs.ipynb
  72. 239 0
      SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqldb-azCli.ipynb
  73. 243 0
      SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqldb.ipynb
  74. 339 0
      SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqlmi.ipynb
  75. 557 0
      SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqlvm-azCli.ipynb
  76. 383 0
      SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqlvm.ipynb
  77. 9 0
      SQL-Hybrid-Cloud-Toolkit/content/provisioning/readme.md
  78. 35 0
      SQL-Hybrid-Cloud-Toolkit/content/readme.md
  79. 2 0
      SQL-Hybrid-Cloud-Toolkit/requirements.txt

+ 63 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/.gitattributes

@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain

+ 161 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl.ipynb

@@ -0,0 +1,161 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# 0.  Preparing the Credential"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "140ce3a4-9596-47b5-ad22-87c3bd2057f6"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$functionKey = 'yourAzureFunctionKey'\r\n",
+                "$Login = 'yourSqlServerLogin'\r\n",
+                "$Password = 'yourSqlServerPassword'\r\n",
+                "\r\n",
+                "$headers = @{\r\n",
+                "    'x-functions-key' = $functionKey\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "36fa6902-7640-462d-bc2e-6b49e9aaa0d9",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# 1.  Calling the ADP Orchestrator (Export)\r\n",
+                "## 1.1 Submit the Export request to the source Azure SQL Server."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "40c4517b-8145-4af1-bbbb-3be3b9b9a8a0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$Url = 'https://adpcontrol.azurewebsites.net/api/subscriptions/0009fc4d-e310-4e40-8e63-c48a23e9cdc1/resourceGroups/seanadp01/Export'\n",
+                "\n",
+                "$Body = @{\n",
+                "    batchAccountUrl = 'https://adp.eastus.batch.azure.com'\n",
+                "    storageAccountName = 'adp01batch'\n",
+                "    sourceSqlServerResourceGroupName = 'SeanADP01Source'\n",
+                "    sourceSqlServerName = 'adpsvr01'\n",
+                "    userName = $Login \n",
+                "    password = $Password \n",
+                "}\n",
+                "\n",
+                "$json = $Body | ConvertTo-Json\n",
+                "$exportResponse = Invoke-RestMethod -Method 'Post' -Headers $headers -Uri $Url -Body $json -ContentType 'application/json'\n",
+                "$exportResponse"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7e1d3261-5e61-4106-8063-7cd58ffd0cf1",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## 1.2 Getting the Operation Status"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "52204c15-abad-4ce5-8629-d290332f730b"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Invoke-RestMethod -Method 'Get' -Uri $exportResponse.statusQueryGetUri"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a0ba1261-3a26-4168-b149-1b9e44939432",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# 2.  Calling the ADP Orchestrator (Import)\r\n",
+                "## 2.1 Submit the Import request to the target Azure SQL Server."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2c862275-c380-476a-ab3d-a9aacdca963b"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$Url = 'https://adpcontrol.azurewebsites.net/api/subscriptions/0009fc4d-e310-4e40-8e63-c48a23e9cdc1/resourceGroups/seanadp01/Import'\r\n",
+                "$Body = @{\r\n",
+                "    batchAccountUrl = 'https://adp.eastus.batch.azure.com'\r\n",
+                "    storageAccountName = 'adp01batch'\r\n",
+                "    containerName = 'adpsvr01-0428061710'\r\n",
+                "    targetSqlServerResourceGroupName = 'SeanADP01Target'\r\n",
+                "    targetSqlServerName = 'adpsvr03'\r\n",
+                "    userName = $Login \r\n",
+                "    password = $Password \r\n",
+                "}\r\n",
+                "\r\n",
+                "$json = $Body | ConvertTo-Json\r\n",
+                "$importResponse = Invoke-RestMethod -Method 'Post' -Headers $headers -Uri $Url -Body $json -ContentType 'application/json'\r\n",
+                "$importResponse"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "315859aa-e452-4ab3-acb5-92c7c8bd5857",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## 2.2 Getting the Operation Status"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5da3293b-7d10-4315-8106-79e56cd657ea"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Invoke-RestMethod -Method 'Get' -Uri $importResponse.statusQueryGetUri"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "328d3a27-4cdf-4623-a8c5-8230487efbed",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 37 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl.sln

@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29920.165
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ADPControl", "ADPControl\ADPControl.csproj", "{6309D4C5-F118-4C89-A67E-E557CA41ABA2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BatchWrapper", "BatchWrapper\BatchWrapper.csproj", "{E64CD92E-2D2C-48F1-B6B1-A7AF819B06F1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlPackageWrapper", "SqlPackageWrapper\SqlPackageWrapper.csproj", "{A19335D3-9D80-43BE-9351-C3C3704689B0}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{6309D4C5-F118-4C89-A67E-E557CA41ABA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6309D4C5-F118-4C89-A67E-E557CA41ABA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6309D4C5-F118-4C89-A67E-E557CA41ABA2}.Release|Any CPU.ActiveCfg = Debug|Any CPU
+		{6309D4C5-F118-4C89-A67E-E557CA41ABA2}.Release|Any CPU.Build.0 = Debug|Any CPU
+		{E64CD92E-2D2C-48F1-B6B1-A7AF819B06F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E64CD92E-2D2C-48F1-B6B1-A7AF819B06F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E64CD92E-2D2C-48F1-B6B1-A7AF819B06F1}.Release|Any CPU.ActiveCfg = Debug|Any CPU
+		{E64CD92E-2D2C-48F1-B6B1-A7AF819B06F1}.Release|Any CPU.Build.0 = Debug|Any CPU
+		{A19335D3-9D80-43BE-9351-C3C3704689B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A19335D3-9D80-43BE-9351-C3C3704689B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A19335D3-9D80-43BE-9351-C3C3704689B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A19335D3-9D80-43BE-9351-C3C3704689B0}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {3DB5F0CB-1D26-4E33-801D-7BBAE823C39D}
+	EndGlobalSection
+EndGlobal

+ 26 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/ADPControl.csproj

@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Azure.Batch" Version="13.0.0" />
+    <PackageReference Include="Microsoft.Azure.Management.ResourceManager" Version="3.7.0-preview" />
+    <PackageReference Include="Microsoft.Azure.Management.Storage" Version="17.0.0" />
+    <PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.5.0" />
+    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.2.2" />
+    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.7" />
+    <PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.21" />
+    <PackageReference Include="Microsoft.Rest.ClientRuntime.Azure" Version="3.3.19" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Update="host.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="local.settings.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+    </None>
+  </ItemGroup>
+</Project>

+ 244 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/AzureResourceManagerActivity.cs

@@ -0,0 +1,244 @@
+using Microsoft.Azure.Management.ResourceManager;
+using Microsoft.Azure.Management.ResourceManager.Models;
+using Microsoft.Azure.Management.Storage;
+using Microsoft.Azure.Management.Storage.Models;
+using Microsoft.Azure.Services.AppAuthentication;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask;
+using Microsoft.Extensions.Logging;
+using Microsoft.Rest;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Auth;
+using Microsoft.WindowsAzure.Storage.Blob;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using static ADPControl.HttpSurface;
+
+namespace ADPControl
+{
+    public static class AzureResourceManagerActivity
+    {
+        private const string ArmTemplateFileName = "template.json";
+
+        private static string[] AllowedImportSubServerResourceTypes = new string[] { 
+            "Microsoft.Sql/servers/firewallRules",
+            "Microsoft.Sql/servers/databases",
+            "Microsoft.Sql/servers/elasticPools",
+            //"Microsoft.Sql/servers/keys",
+            //"Microsoft.Sql/servers/databases/transparentDataEncryption",
+            "Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies",
+            "Microsoft.Sql/servers/administrators"
+        };
+
+        // Deploy the ARM template
+        [FunctionName(nameof(BeginDeployArmTemplateForImport))]
+        public static async Task<string> BeginDeployArmTemplateForImport([ActivityTrigger] ImportRequest request, ILogger log)
+        {
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+            TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
+
+            ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
+            StorageManagementClient storageMgmtClient = new StorageManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
+
+            // Get the storage account keys for a given account and resource group
+            IList<StorageAccountKey> acctKeys = storageMgmtClient.StorageAccounts.ListKeys(request.ResourceGroupName, request.StorageAccountName).Keys;
+
+            // Get a Storage account using account creds:
+            StorageCredentials storageCred = new StorageCredentials(request.StorageAccountName, acctKeys.FirstOrDefault().Value);
+            CloudStorageAccount linkedStorageAccount = new CloudStorageAccount(storageCred, true);
+            CloudBlobContainer container = linkedStorageAccount
+                                                .CreateCloudBlobClient()
+                                                .GetContainerReference(request.ContainerName);
+
+            CloudBlockBlob blob = container.GetBlockBlobReference(ArmTemplateFileName);
+            string json = await blob.DownloadTextAsync();
+
+            JObject originalTemplate = JObject.Parse(json);
+            JObject importTemplate = UpdateArmTemplateForImport(originalTemplate, request);
+
+            var deployParams = new Deployment
+            {
+                Properties = new DeploymentProperties
+                {
+                    Mode = DeploymentMode.Incremental,
+                    Template = importTemplate
+                }
+            };
+
+            string deploymentName = request.TargetSqlServerName + "_" + DateTime.UtcNow.ToFileTimeUtc();
+
+            try
+            {
+                await resourcesClient.Deployments.BeginCreateOrUpdateAsync(request.TargetSqlServerResourceGroupName, deploymentName, deployParams);                
+            }
+            catch (Exception ex)
+            {
+                log.LogError(ex.ToString());
+                throw ex;
+            }
+
+            return deploymentName;
+        }
+
+        // Get the ARM deployment status
+        [FunctionName(nameof(GetArmDeploymentForImport))]
+        public static async Task<string> GetArmDeploymentForImport([ActivityTrigger] (Guid, string, string) input)
+        {
+            Guid subscriptionId = input.Item1;
+            string resourceGroupName = input.Item2;
+            string deploymentName = input.Item3;
+
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+            TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
+            ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = subscriptionId.ToString() };
+
+            DeploymentExtended result = await resourcesClient.Deployments.GetAsync(resourceGroupName, deploymentName);
+            return result.Properties.ProvisioningState;
+        }
+
+        // Get the ARM template without the parameter of the resource name
+        [FunctionName(nameof(GetArmTemplateForExportSkipParameterization))]
+        public static async Task<dynamic> GetArmTemplateForExportSkipParameterization([ActivityTrigger] ExportRequest request, ILogger log)
+        {
+            log.LogInformation("GetArmTemplateForExportSkipParameterization: entering");
+
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+            TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
+            if (tokenArmCredential != null)
+            {
+                log.LogInformation("GetArmTemplateForExportSkipParameterization: acquired access token");
+                ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
+
+                string sourceSqlServerResourceId = string.Format("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Sql/servers/{2}", request.SubscriptionId, request.SourceSqlServerResourceGroupName, request.SourceSqlServerName);
+                ResourceGroupExportResult exportedTemplate = resourcesClient.ResourceGroups.ExportTemplate(request.SourceSqlServerResourceGroupName, new ExportTemplateRequest(new List<string> { sourceSqlServerResourceId }, "SkipResourceNameParameterization"));
+
+                log.LogInformation("GetArmTemplateForExportSkipParameterization: server template exported.  Size: {0} bytes", exportedTemplate.Template.ToString().Length);
+                dynamic template = (dynamic)exportedTemplate.Template;
+
+                // Filtering the list of databases
+                dynamic databases = template.resources.SelectTokens("$.[?(@.type == 'Microsoft.Sql/servers/databases')]");
+                int numberOfDatabases = 0;
+                foreach (var db in databases)
+                {
+                    numberOfDatabases++;
+                }
+                log.LogInformation("GetArmTemplateForExportSkipParameterization: exiting with database list.  Databases count: {0}", numberOfDatabases);
+
+                return databases;
+            }
+
+            log.LogInformation("GetArmTemplateForExportSkipParameterization: exiting with empty database list");
+            return null;
+        }
+
+        // Get the ARM template without the parameter of the resource name
+        [FunctionName(nameof(GetArmTemplateForImportSkipParameterization))]
+        public static async Task<dynamic> GetArmTemplateForImportSkipParameterization([ActivityTrigger] ImportRequest request, ILogger log)
+        {
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+            TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
+            if (tokenArmCredential != null)
+            {
+                log.LogInformation("GetArmTemplateForImportSkipParameterization: acquired access token");
+                ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
+
+                string sourceSqlServerResourceId = string.Format("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Sql/servers/{2}", request.SubscriptionId, request.TargetSqlServerResourceGroupName, request.TargetSqlServerName);
+                ResourceGroupExportResult exportedTemplate = resourcesClient.ResourceGroups.ExportTemplate(request.TargetSqlServerResourceGroupName, new ExportTemplateRequest(new List<string> { sourceSqlServerResourceId }, "SkipResourceNameParameterization"));
+                
+                log.LogInformation("GetArmTemplateForImportSkipParameterization: server template exported.  Size: {0} bytes", exportedTemplate.Template.ToString().Length);
+                dynamic template = (dynamic)exportedTemplate.Template;
+
+                // Filtering the list of databases
+                dynamic databases = template.resources.SelectTokens("$.[?(@.type == 'Microsoft.Sql/servers/databases')]");
+                
+                int numberOfDatabases = 0;
+                foreach (var db in databases)
+                {
+                    numberOfDatabases++;
+                }
+                log.LogInformation("GetArmTemplateForExportSkipParameterization: exiting with database list.  Databases count: {0}", numberOfDatabases);
+                return databases;
+            }
+
+            return null;
+        }
+
+        [FunctionName(nameof(GetArmTemplateForExport))]
+        public static async Task<dynamic> GetArmTemplateForExport([ActivityTrigger] ExportRequest request)
+        {
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+            TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
+
+            ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
+
+            string sourceSqlServerResourceId = string.Format("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Sql/servers/{2}", request.SubscriptionId, request.SourceSqlServerResourceGroupName, request.SourceSqlServerName);
+            ResourceGroupExportResult exportedTemplate = resourcesClient.ResourceGroups.ExportTemplate(request.SourceSqlServerResourceGroupName, new ExportTemplateRequest(new List<string> { sourceSqlServerResourceId }, "IncludeParameterDefaultValue"));
+            return exportedTemplate.Template;
+        }
+
+        private static JObject UpdateArmTemplateForImport(JObject originalTemplate, ImportRequest request)
+        {
+            string serverNameParameterName = null;
+
+            // Go through every parameter to find the property name is like 'server_%_name'
+            using (JsonTextReader reader = new JsonTextReader(new StringReader(originalTemplate["parameters"].ToString())))
+            {
+                while (reader.Read())
+                {
+                    if (reader.TokenType.ToString().Equals("PropertyName")
+                       && reader.ValueType.ToString().Equals("System.String")
+                       && reader.Value.ToString().StartsWith("servers_")
+                       && reader.Value.ToString().EndsWith("_name"))
+                    {
+                        serverNameParameterName = reader.Value.ToString();
+                        break;
+                    }
+                }
+            }
+
+            // 1. Replacing the default value to the target server name, appending to the new template
+            originalTemplate["parameters"][serverNameParameterName]["defaultValue"] = request.TargetSqlServerName;
+            JObject serverNameParameterValue = (JObject)originalTemplate["parameters"][serverNameParameterName];
+
+            // 2. Cleanup all the parameters except the updated server name
+            ((JObject)originalTemplate["parameters"]).RemoveAll();
+            ((JObject)originalTemplate["parameters"]).Add(serverNameParameterName, serverNameParameterValue);
+
+            // 3. Adjust the servers resource by adding password after the login
+            JObject server = (JObject)originalTemplate["resources"]
+                .SelectToken("$.[?(@.type == 'Microsoft.Sql/servers')]");
+
+            server.Remove("identity");
+
+            JObject serverProperties = (JObject)server["properties"];
+            serverProperties.Property("administratorLogin")
+                .AddAfterSelf(new JProperty("administratorLoginPassword", request.SqlAdminPassword));
+
+            JArray newResources = new JArray();
+
+            // 4. Getting the whitelisted resources and adding them to the new template later.
+            foreach (string resourceType in AllowedImportSubServerResourceTypes)
+            {
+                List<JToken> resources = originalTemplate["resources"]
+                    .SelectTokens(string.Format("$.[?(@.type == '{0}')]", resourceType)).ToList();
+                newResources.Add(resources);
+            }
+
+            // 5. Clean up all the resources excepted the new server and whitelisted resource type.
+            ((JArray)originalTemplate["resources"]).Clear();
+            ((JArray)originalTemplate["resources"]).Add(server);
+
+            foreach (var resource in newResources)
+            {
+                ((JArray)originalTemplate["resources"]).Add(resource);
+            }
+
+            return originalTemplate;
+        }
+    }
+}

+ 214 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/BatchActivity.cs

@@ -0,0 +1,214 @@
+using Microsoft.Azure.Batch;
+using Microsoft.Azure.Batch.Auth;
+using Microsoft.Azure.Batch.Common;
+using Microsoft.Azure.Services.AppAuthentication;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using static ADPControl.HttpSurface;
+
+namespace ADPControl
+{
+    public static class BatchActivity
+    {
+        // Batch resource settings
+        private const string PoolVMSize = "Standard_D8s_v3";
+        private const string PoolId = PoolVMSize;
+        private const int PoolNodeCount = 2;
+        private const string AppPackageName = "SqlPackageWrapper";
+        public const string AppPackageVersion = "1";
+
+        [FunctionName(nameof(CreateBatchPoolAndExportJob))]
+        public static async Task<string> CreateBatchPoolAndExportJob([ActivityTrigger] ExportRequest request, ILogger log)
+        {
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+
+            // Get a Batch client using function identity
+            BatchTokenCredentials batchCred = new BatchTokenCredentials(request.BatchAccountUrl, await azureServiceTokenProvider.GetAccessTokenAsync("https://batch.core.windows.net/"));
+
+            string jobId = request.SourceSqlServerName + "-Export-" + DateTime.UtcNow.ToString("MMddHHmmss");
+            using (BatchClient batchClient = BatchClient.Open(batchCred))
+            {
+                ImageReference imageReference = CreateImageReference();
+                VirtualMachineConfiguration vmConfiguration = CreateVirtualMachineConfiguration(imageReference);
+
+                await CreateBatchPoolIfNotExist(batchClient, vmConfiguration, request.VNetSubnetId);
+                await CreateBatchJob(batchClient, jobId, log);
+            }
+
+            return jobId;
+        }
+
+        [FunctionName(nameof(CreateBatchPoolAndImportJob))]
+        public static async Task<string> CreateBatchPoolAndImportJob([ActivityTrigger] ImportRequest request, ILogger log)
+        {
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+
+            // Get a Batch client using function identity
+            BatchTokenCredentials batchCred = new BatchTokenCredentials(request.BatchAccountUrl, await azureServiceTokenProvider.GetAccessTokenAsync("https://batch.core.windows.net/"));
+
+            string jobId = request.TargetSqlServerName + "-Import-" + DateTime.UtcNow.ToString("MMddHHmmss");
+            using (BatchClient batchClient = BatchClient.Open(batchCred))
+            {
+                ImageReference imageReference = CreateImageReference();
+                VirtualMachineConfiguration vmConfiguration = CreateVirtualMachineConfiguration(imageReference);
+
+                await CreateBatchPoolIfNotExist(batchClient, vmConfiguration, request.VNetSubnetId);
+                await CreateBatchJob(batchClient, jobId, log);
+            }
+
+            return jobId;
+        }
+
+        public static async Task<CloudJob> CreateBatchJob(BatchClient batchClient, string jobId, ILogger log)
+        {
+            // Create a Batch job
+            log.LogInformation("Creating job [{0}]...", jobId);
+            CloudJob job = null;
+
+            try
+            {
+                job = batchClient.JobOperations.CreateJob(jobId, new PoolInformation { PoolId = PoolId });
+                job.OnAllTasksComplete = OnAllTasksComplete.TerminateJob;
+
+                // Commit the job to the Batch service
+                await job.CommitAsync();
+
+                log.LogInformation($"Created job {jobId}");
+
+                // Obtain the bound job from the Batch service
+                await job.RefreshAsync();
+            }
+            catch (BatchException be)
+            {
+                // Accept the specific error code JobExists as that is expected if the job already exists
+                if (be.RequestInformation?.BatchError?.Code == BatchErrorCodeStrings.JobExists)
+                {
+                    log.LogWarning("The job {0} already existed when we tried to create it", jobId);
+                }
+                else
+                {
+                    log.LogError("Exception creating job: {0}", be.Message);
+                    throw be; // Any other exception is unexpected
+                }
+            }
+
+            return job;
+        }
+
+        // Create the Compute Pool of the Batch Account
+        public static async Task CreateBatchPoolIfNotExist(BatchClient batchClient, VirtualMachineConfiguration vmConfiguration, string vnetSubnetId)
+        {
+            Console.WriteLine("Creating pool [{0}]...", PoolId);
+
+            try
+            {
+                CloudPool pool = batchClient.PoolOperations.CreatePool(
+                    poolId: PoolId,
+                    targetDedicatedComputeNodes: PoolNodeCount,
+                    virtualMachineSize: PoolVMSize,
+                    virtualMachineConfiguration: vmConfiguration);
+
+                // Specify the application and version to install on the compute nodes
+                pool.ApplicationPackageReferences = new List<ApplicationPackageReference>
+                {
+                    new ApplicationPackageReference {
+                        ApplicationId = AppPackageName,
+                        Version = AppPackageVersion }
+                };
+
+                // Initial the first data disk for each VM in the pool
+                StartTask startTask = new StartTask("cmd /c Powershell -command \"Get-Disk | Where partitionstyle -eq 'raw' | sort number | Select-Object -first 1 |" +
+                    " Initialize-Disk -PartitionStyle MBR -PassThru | New-Partition -UseMaximumSize -DriveLetter F |" +
+                    " Format-Volume -FileSystem NTFS -NewFileSystemLabel data1 -Confirm:$false -Force\"");
+
+                startTask.MaxTaskRetryCount = 1;
+                startTask.UserIdentity = new UserIdentity(new AutoUserSpecification(AutoUserScope.Pool, ElevationLevel.Admin));
+                startTask.WaitForSuccess = true;
+
+                pool.StartTask = startTask;
+
+                // Create the Pool within the vnet subnet if it's specified.
+                if (vnetSubnetId != null)
+                {
+                    pool.NetworkConfiguration = new NetworkConfiguration();
+                    pool.NetworkConfiguration.SubnetId = vnetSubnetId;
+                }
+
+                await pool.CommitAsync();
+                await pool.RefreshAsync();
+            }
+            catch (BatchException be)
+            {
+                // Accept the specific error code PoolExists as that is expected if the pool already exists
+                if (be.RequestInformation?.BatchError?.Code == BatchErrorCodeStrings.PoolExists)
+                {
+                    Console.WriteLine("The pool {0} already existed when we tried to create it", PoolId);
+                }
+                else
+                {
+                    throw; // Any other exception is unexpected
+                }
+            }
+        }
+
+        public static VirtualMachineConfiguration CreateVirtualMachineConfiguration(ImageReference imageReference)
+        {
+            VirtualMachineConfiguration config = new VirtualMachineConfiguration(
+                imageReference: imageReference,
+                nodeAgentSkuId: "batch.node.windows amd64");
+
+            config.DataDisks = new List<DataDisk>();
+            config.DataDisks.Add(new DataDisk(0, 2048, CachingType.ReadOnly, StorageAccountType.PremiumLrs));
+
+            return config;
+        }
+
+        public static ImageReference CreateImageReference()
+        {
+            return new ImageReference(
+                publisher: "MicrosoftWindowsServer",
+                offer: "WindowsServer",
+                sku: "2019-datacenter-smalldisk",
+                version: "latest");
+        }
+
+        public static void CreateBatchTasks(string action, string jobId, string containerUrl, string batchAccountUrl, string sqlServerName, string accessToken, dynamic databases, ILogger log)
+        {
+            // Get a Batch client using function identity
+            log.LogInformation("CreateBatchTasks: entering");
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+            BatchTokenCredentials batchCred = new BatchTokenCredentials(batchAccountUrl, azureServiceTokenProvider.GetAccessTokenAsync("https://batch.core.windows.net/").Result);
+            using (BatchClient batchClient = BatchClient.Open(batchCred))
+            {
+                // For each database, submit the Exporting job to Azure Batch Compute Pool.
+                log.LogInformation("CreateBatchTasks: enumerating databases");
+                List<CloudTask> tasks = new List<CloudTask>();
+                foreach (var db in databases)
+                {
+                    string serverDatabaseName = db.name.ToString();
+                    string logicalDatabase = serverDatabaseName.Remove(0, sqlServerName.Length + 1);
+
+                    log.LogInformation("CreateBatchTasks: creating task for database {0}", logicalDatabase);
+                    string taskId = sqlServerName + "_" + logicalDatabase;
+                    string command = string.Format("cmd /c %AZ_BATCH_APP_PACKAGE_{0}#{1}%\\BatchWrapper {2}", AppPackageName.ToUpper(), AppPackageVersion, action);
+                    command += string.Format(" {0} {1} {2} {3} {4}", sqlServerName, logicalDatabase, accessToken, AppPackageName.ToUpper(), AppPackageVersion);
+                    string taskCommandLine = string.Format(command);
+
+                    CloudTask singleTask = new CloudTask(taskId, taskCommandLine);
+                    singleTask.EnvironmentSettings = new[] { new EnvironmentSetting("JOB_CONTAINER_URL", containerUrl) };
+
+                    Console.WriteLine(string.Format("Adding task {0} to job ...", taskId));
+                    tasks.Add(singleTask);
+                }
+
+                // Add all tasks to the job.
+                batchClient.JobOperations.AddTask(jobId, tasks);
+            }
+            log.LogInformation("CreateBatchTasks: exiting");
+        }
+    }
+}

+ 103 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/HttpSurface.cs

@@ -0,0 +1,103 @@
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask;
+using Microsoft.Azure.WebJobs.Extensions.Http;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace ADPControl
+{
+    public static class HttpSurface
+    {
+        public class ExportRequest
+        {
+            public Guid SubscriptionId { get; set; }
+
+            public string ResourceGroupName { get; set; }
+
+            public string SourceSqlServerResourceGroupName { get; set; }
+
+            public string SourceSqlServerName { get; set; }
+
+            public string BatchAccountUrl { get; set; }
+
+            public string StorageAccountName { get; set; }
+
+            public string AccessToken { get; set; }
+
+            public string VNetSubnetId { get; set; }
+        }
+
+        public class ImportRequest
+        {
+            public Guid SubscriptionId { get; set; }
+
+            public string ResourceGroupName { get; set; }
+
+            public string TargetSqlServerResourceGroupName { get; set; }
+
+            public string TargetSqlServerName { get; set; }
+
+            public string TargetAccessToken { get; set; }
+
+            public string BatchAccountUrl { get; set; }
+
+            public string StorageAccountName { get; set; }
+
+            public string ContainerName { get; set; }
+
+            public string SqlAdminPassword { get; set; }
+
+            public string VNetSubnetId { get; set; }
+        }
+
+        [FunctionName("Export")]
+        public static async Task<HttpResponseMessage> PostExport(
+            [HttpTrigger(AuthorizationLevel.Function, "post", Route = "subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/Export")]
+            HttpRequestMessage req,
+            [DurableClient] IDurableOrchestrationClient starter,
+            ILogger log,
+            Guid subscriptionId,
+            string resourceGroupName)
+        {
+            log.LogInformation("C# HTTP trigger function processed an Export request.");
+            ExportRequest request = await req.Content.ReadAsAsync<ExportRequest>();
+
+            request.SubscriptionId = subscriptionId;
+            request.ResourceGroupName = resourceGroupName;
+
+            if (request.SourceSqlServerResourceGroupName == null)
+                request.SourceSqlServerResourceGroupName = resourceGroupName;
+
+            string instanceId = await starter.StartNewAsync(nameof(Orchestrator.RunExportOrchestrator), request);
+
+            log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
+            return starter.CreateCheckStatusResponse(req, instanceId);
+        }
+
+        [FunctionName("Import")]
+        public static async Task<HttpResponseMessage> PostImport(
+            [HttpTrigger(AuthorizationLevel.Function, "post", Route = "subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/Import")]
+            HttpRequestMessage req,
+            [DurableClient] IDurableOrchestrationClient starter,
+            ILogger log,
+            Guid subscriptionId,
+            string resourceGroupName)
+        {
+            log.LogInformation("C# HTTP trigger function processed an Import request.");
+            ImportRequest request = await req.Content.ReadAsAsync<ImportRequest>();
+
+            request.SubscriptionId = subscriptionId;
+            request.ResourceGroupName = resourceGroupName;
+
+            if (request.TargetSqlServerResourceGroupName == null)
+                request.TargetSqlServerResourceGroupName = resourceGroupName;
+
+            string instanceId = await starter.StartNewAsync(nameof(Orchestrator.RunImportOrchestrator), request);
+
+            log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
+            return starter.CreateCheckStatusResponse(req, instanceId);
+        }
+    }
+}

+ 104 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/Orchestrator.cs

@@ -0,0 +1,104 @@
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using static ADPControl.HttpSurface;
+
+namespace ADPControl
+{
+    public static class Orchestrator
+    {
+        // The Import Orchestrator
+        [FunctionName(nameof(RunImportOrchestrator))]
+        public static async Task RunImportOrchestrator(
+            [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
+        {
+            log.LogInformation("RunImportOrchestrator: entering");
+
+            try {
+
+                ImportRequest importRequest = context.GetInput<ImportRequest>();
+                // Deploy the ARM template to Create empty SQL resource
+                string deploymentName = await context.CallActivityAsync<string>(nameof(AzureResourceManagerActivity.BeginDeployArmTemplateForImport), importRequest);
+                while (true)
+                {
+                    log.LogInformation("RunImportOrchestrator: starting ARM deployment");
+                    string status = await context.CallActivityAsync<string>(nameof(AzureResourceManagerActivity.GetArmDeploymentForImport), (importRequest.SubscriptionId, importRequest.TargetSqlServerResourceGroupName, deploymentName));
+                    if (status == "Succeeded")
+                    {
+                        log.LogInformation("RunImportOrchestrator: ARM deployment succeeded");
+                        break;
+                    }
+                    else if (status == "Failed")
+                    {
+                        log.LogInformation("RunImportOrchestrator: ARM deployment failed");
+                        throw new Exception("Failed ARM Deployment");
+                    }
+
+                    // Orchestration sleeps until this time.
+                    var nextCheck = context.CurrentUtcDateTime.AddSeconds(10);
+
+                    if (!context.IsReplaying) { log.LogInformation($"RunImportOrchestrator: Replaying ARM deployment, next check at {nextCheck}."); }
+                    await context.CreateTimer(nextCheck, CancellationToken.None);
+                }
+
+                log.LogInformation("RunImportOrchestrator: Enumerating databases");
+                var databases = await context.CallActivityAsync<dynamic>(nameof(AzureResourceManagerActivity.GetArmTemplateForImportSkipParameterization), importRequest);
+
+                // Create BatchPool And Job
+                log.LogInformation("RunImportOrchestrator: Creating batch pool and import job");
+                string jobId = await context.CallActivityAsync<string>(nameof(BatchActivity.CreateBatchPoolAndImportJob), importRequest);
+
+                string containerUrl = await context.CallActivityAsync<string>(nameof(StorageActivity.GettingJobContainerUrl), (importRequest.SubscriptionId, importRequest.ResourceGroupName, importRequest.StorageAccountName, importRequest.ContainerName));
+
+                log.LogInformation("RunImportOrchestrator: Creating import database tasks");
+                BatchActivity.CreateBatchTasks("Import", jobId, containerUrl, importRequest.BatchAccountUrl, importRequest.TargetSqlServerName, importRequest.TargetAccessToken, databases, log);
+                
+                // create output values
+                Tuple<string, string>[] outputValues = {
+                    Tuple.Create("Orchestration progress:", "Complete"),
+                    Tuple.Create("deploymentName", deploymentName),
+                    Tuple.Create("jobId", jobId),
+                    Tuple.Create("containerUrl", containerUrl)
+                };
+                context.SetOutput(outputValues);
+            }
+            finally {
+                log.LogInformation("RunImportOrchestrator: exiting");
+            }
+        }
+
+        // The Export Orchestrator
+        [FunctionName(nameof(RunExportOrchestrator))]
+        public static async Task RunExportOrchestrator(
+            [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
+        {
+            ExportRequest exportRequest = context.GetInput<ExportRequest>();
+
+            // Getting the ARM template Skip ResourceName Parameterization.
+            var databases = await context.CallActivityAsync<dynamic>(nameof(AzureResourceManagerActivity.GetArmTemplateForExportSkipParameterization), exportRequest);
+
+            // Getting the ARM template.
+            dynamic Template = await context.CallActivityAsync<dynamic>(nameof(AzureResourceManagerActivity.GetArmTemplateForExport), exportRequest);
+            string json = JsonConvert.SerializeObject(Template);
+
+            // Create BatchPool And Job
+            string jobId = await context.CallActivityAsync<string>(nameof(BatchActivity.CreateBatchPoolAndExportJob), exportRequest);
+
+            string containerUrl = await context.CallActivityAsync<string>(nameof(StorageActivity.GettingJobContainerUrl), (exportRequest.SubscriptionId, exportRequest.ResourceGroupName, exportRequest.StorageAccountName, jobId));
+            await context.CallActivityAsync<string>(nameof(StorageActivity.UploadingArmTemplate), (containerUrl, json));
+
+            BatchActivity.CreateBatchTasks("Export", jobId, containerUrl, exportRequest.BatchAccountUrl, exportRequest.SourceSqlServerName, exportRequest.AccessToken, databases, log);
+        
+            // create output values
+            Tuple<string, string>[] outputValues = {
+                Tuple.Create("jobId", jobId),
+                Tuple.Create("containerUrl", containerUrl)
+            };
+            context.SetOutput(outputValues);
+        }
+    }
+}

+ 79 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/StorageActivity.cs

@@ -0,0 +1,79 @@
+using Microsoft.Azure.Management.Storage;
+using Microsoft.Azure.Management.Storage.Models;
+using Microsoft.Azure.Services.AppAuthentication;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask;
+using Microsoft.Extensions.Logging;
+using Microsoft.Rest;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Auth;
+using Microsoft.WindowsAzure.Storage.Blob;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace ADPControl
+{
+    public static class StorageActivity
+    {
+        private const string ArmTemplateFileName = "template.json";
+
+        [FunctionName(nameof(GettingJobContainerUrl))]
+        public static string GettingJobContainerUrl([ActivityTrigger] (Guid, string, string, string) input, ILogger log)
+        {
+            Guid SubscriptionId = input.Item1;
+            String ResourceGroupName = input.Item2;
+            String StorageAccountName = input.Item3;
+            String ContainerName = input.Item4;
+
+            var azureServiceTokenProvider = new AzureServiceTokenProvider();
+            TokenCredentials tokenArmCredential = new TokenCredentials(azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/").Result);
+            StorageManagementClient storageMgmtClient = new StorageManagementClient(tokenArmCredential) { SubscriptionId = SubscriptionId.ToString() };
+
+            // Get the storage account keys for a given account and resource group
+            IList<StorageAccountKey> acctKeys = storageMgmtClient.StorageAccounts.ListKeys(ResourceGroupName, StorageAccountName).Keys;
+
+            // Get a Storage account using account creds:
+            StorageCredentials storageCred = new StorageCredentials(StorageAccountName, acctKeys.FirstOrDefault().Value);
+            CloudStorageAccount linkedStorageAccount = new CloudStorageAccount(storageCred, true);
+
+            bool createContainer = false;
+            // Normalize the container name for the Export action.
+            if (ContainerName.Contains("-Export-"))
+            {
+                ContainerName = ContainerName.Replace("Export-", "");
+                createContainer = true;
+            }
+
+            CloudBlobContainer container = linkedStorageAccount.CreateCloudBlobClient().GetContainerReference(ContainerName);
+
+            if(createContainer) 
+                container.CreateIfNotExistsAsync().Wait();
+
+            string containerUrl = container.Uri.ToString() +
+                                        container.GetSharedAccessSignature(new SharedAccessBlobPolicy()
+                                        {
+                                            Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.List,
+                                            SharedAccessExpiryTime = DateTime.UtcNow.AddDays(7)
+                                        });
+            return containerUrl;
+        }
+
+        [FunctionName(nameof(UploadingArmTemplate))]
+        public static void UploadingArmTemplate([ActivityTrigger] (string, string) input, ILogger log)
+        {
+            string containerUrl = input.Item1;
+            string json = input.Item2;
+
+            CloudBlobContainer container = new CloudBlobContainer(new Uri(containerUrl));
+            CloudBlockBlob blob = container.GetBlockBlobReference(ArmTemplateFileName);
+            blob.Properties.ContentType = "application/json";
+            using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
+            {
+                blob.UploadFromStreamAsync(stream).Wait();
+            }
+        }
+    }
+}

+ 16 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/ADPControl/host.json

@@ -0,0 +1,16 @@
+{
+  "version": "2.0",
+  "extensions": {
+    "durableTask": {
+      "hubName": "adp"
+    }
+  },
+  "logging": {
+    "applicationInsights": {
+      "samplingExcludedTypes": "Request",
+      "samplingSettings": {
+        "isEnabled": true
+      }
+    }
+  }
+}

+ 12 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/ActionType.cs

@@ -0,0 +1,12 @@
+namespace BatchWrapper
+{
+    /// <summary>
+    /// The type of sqlpackage action to perform.
+    /// </summary>
+    public enum ActionType
+    {
+        DefaultInvalid = -1,
+        Export,
+        Import
+    }
+}

+ 22 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/App.config

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Azure.Batch" publicKeyToken="31bf3856ad364e35" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-3.0.5.0" newVersion="3.0.5.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 98 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/BatchWrapper.csproj

@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{E64CD92E-2D2C-48F1-B6B1-A7AF819B06F1}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>BatchWrapper</RootNamespace>
+    <AssemblyName>BatchWrapper</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <LangVersion>8.0</LangVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Azure.Batch.Conventions.Files, Version=3.5.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Azure.Batch.Conventions.Files.3.5.1\lib\net461\Microsoft.Azure.Batch.Conventions.Files.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.WindowsAzure.Storage, Version=9.3.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\packages\WindowsAzure.Storage.9.3.3\lib\net45\Microsoft.WindowsAzure.Storage.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Net" />
+    <Reference Include="System.Net.Http.WebRequest" />
+    <Reference Include="System.Runtime" />
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ActionType.cs" />
+    <Compile Include="Constants.cs" />
+    <Compile Include="Payload.cs" />
+    <Compile Include="Program.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Properties\" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 34 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/Constants.cs

@@ -0,0 +1,34 @@
+namespace BatchWrapper
+{
+    /// <summary>
+    /// Constants for the batch wrapper.
+    /// </summary>
+    public static class Constants
+    {
+        /// <summary>
+        /// Environment variable names present or needed during the batch task execution.
+        /// </summary>
+        public static class EnvironmentVariableNames
+        {
+            /// <summary>
+            /// Path to the directory containing the sqlpackage exe.
+            /// </summary>
+            internal const string AppPackagePrefix = "AZ_BATCH_APP_PACKAGE";
+
+            /// <summary>
+            /// Path to the working directory assigned to the batch task.
+            /// </summary>
+            internal const string TaskWorkingDir = "AZ_BATCH_TASK_WORKING_DIR";
+
+            /// <summary>
+            /// Path to the working directory assigned to the batch task.
+            /// </summary>
+            internal const string AzBatchTaskId = "AZ_BATCH_TASK_ID";
+
+            /// <summary>
+            /// Path to the working directory assigned to the batch task.
+            /// </summary>
+            internal const string JobContainerUrl = "JOB_CONTAINER_URL";
+        }
+    }
+}

+ 38 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/Payload.cs

@@ -0,0 +1,38 @@
+namespace BatchWrapper
+{
+    /// <summary>
+    /// The top-level object stored in the key vault for an import/export operation.
+    /// </summary>
+    public sealed class Payload
+    {
+        /// <summary>
+        /// The Name of sqlpackage to use for performing the import/export operation.
+        /// </summary>
+        public string ApplicatonPackageName{ get; set; }
+
+        /// <summary>
+        /// The Version of sqlpackage to use for performing the import/export operation.
+        /// </summary>
+        public string ApplicatonPackageVersion { get; set; }
+
+        /// <summary>
+        /// The type of sqlpackage action to perform.
+        /// </summary>
+        public ActionType Action { get; set; }
+
+        /// <summary>
+        /// The logical server name to export from or import to.
+        /// </summary>
+        public string LogicalServerName { get; set; }
+
+        /// <summary>
+        /// The database name to export from or import to.
+        /// </summary>
+        public string DatabaseName { get; set; }
+
+        /// <summary>
+        /// The server admin username.
+        /// </summary>
+        public string AccessToken { get; set; }
+    }
+}

+ 178 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/Program.cs

@@ -0,0 +1,178 @@
+using Microsoft.Azure.Batch.Conventions.Files;
+using Microsoft.WindowsAzure.Storage.Blob;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BatchWrapper
+{
+    public static class Program
+    {
+        private static string dataDirectory = "F:\\data";
+        private static string tempDirectory = "F:\\temp";
+        private static string[] directories = { dataDirectory, tempDirectory };
+
+        private static readonly TimeSpan stdoutFlushDelay = TimeSpan.FromSeconds(3);
+
+        private static void WriteLine(string message) => WriteLineInternal(Console.Out, message);
+        private static void WriteErrorLine(string message) => WriteLineInternal(Console.Error, message);
+        private static void WriteLineInternal(TextWriter writer, string message)
+        {
+            var lines = message?.Split('\n') ?? new string[0];
+            foreach (var line in lines)
+            {
+                writer.WriteLine($"[{DateTime.UtcNow:u}] {line?.TrimEnd()}");
+            }
+        }
+
+        public static async Task<int> Main(string[] args)
+        {
+            var assembly = typeof(Program).Assembly;
+            WriteLine($"{assembly.ManifestModule.Name} v{assembly.GetName().Version.ToString(3)}");
+
+            // Get the command payload 
+            var payload = new Payload();
+
+            if (args.Length > 0)
+            {
+                payload.Action = (ActionType)Enum.Parse(typeof(ActionType), args[0]);
+                payload.LogicalServerName = args[1] + ".database.windows.net";
+                payload.DatabaseName = args[2];
+                payload.AccessToken = args[3];
+                payload.ApplicatonPackageName = args[4];
+                payload.ApplicatonPackageVersion = args[5];
+            }
+
+            // Cleanup folders
+            foreach (string dir in directories)
+            {
+                if (Directory.Exists(dir))
+                {
+                    Directory.Delete(dir, true);
+                }
+
+                Directory.CreateDirectory(dir);
+            }
+
+            string sqlPackageBacpacFile = Path.Combine(dataDirectory, payload.DatabaseName + ".bacpac");
+            string sqlPackageLogPath = payload.DatabaseName + ".log";
+
+            var targetDir = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.AppPackagePrefix + "_" + payload.ApplicatonPackageName + "#" + payload.ApplicatonPackageVersion);
+            var workingDir = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.TaskWorkingDir);
+
+            string taskId = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.AzBatchTaskId);
+            string jobContainerUrl = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.JobContainerUrl);
+
+            // Build the import/export command
+            var cmdBuilder = new StringBuilder();
+            cmdBuilder.Append($"/Action:{payload.Action}");
+            cmdBuilder.Append(" /MaxParallelism:16");
+            cmdBuilder.Append(String.Format(" /DiagnosticsFile:{0}", sqlPackageLogPath));
+            cmdBuilder.Append(" /p:CommandTimeout=604800");
+
+            switch (payload.Action)
+            {
+                case ActionType.Export:
+                    cmdBuilder.Append($" /SourceServerName:{payload.LogicalServerName}");
+                    cmdBuilder.Append($" /SourceDatabaseName:{payload.DatabaseName}");
+                    cmdBuilder.Append($" /AccessToken:{payload.AccessToken}");
+                    cmdBuilder.Append($" /TargetFile:{sqlPackageBacpacFile}");
+                    cmdBuilder.Append($" /SourceTimeout:30");
+                    cmdBuilder.Append(String.Format(" /p:TempDirectoryForTableData=\"{0}\"", tempDirectory));
+                    cmdBuilder.Append(" /p:VerifyFullTextDocumentTypesSupported=false");
+                    break;
+
+                case ActionType.Import:
+                    cmdBuilder.Append($" /TargetServerName:{payload.LogicalServerName}");
+                    cmdBuilder.Append($" /TargetDatabaseName:{payload.DatabaseName}");
+                    cmdBuilder.Append($" /AccessToken:{payload.AccessToken}");
+                    cmdBuilder.Append($" /TargetTimeout:30");
+                    cmdBuilder.Append($" /SourceFile:{sqlPackageBacpacFile}");
+                    break;
+
+                default:
+                    throw new ArgumentException($"Invalid action type: {payload.Action}");
+            }
+
+            if (payload.Action == ActionType.Import)
+            {
+                WriteLine(string.Format("Downloading {0} bacpac file to {1}", payload.DatabaseName, sqlPackageBacpacFile));
+                CloudBlobContainer container = new CloudBlobContainer(new Uri(jobContainerUrl));
+                CloudBlockBlob blob = container.GetBlockBlobReference(String.Format("$JobOutput/{0}.bacpac", payload.DatabaseName));
+                blob.DownloadToFile(sqlPackageBacpacFile, FileMode.CreateNew);
+
+                if (File.Exists(sqlPackageBacpacFile))
+                {
+                    WriteLine(string.Format("Downloaded {0} bacpac file to {1}", payload.DatabaseName, sqlPackageBacpacFile));
+                }
+                else
+                {
+                    throw new Exception(string.Format("{0} didn't download", sqlPackageBacpacFile));
+                }
+            }
+
+            // Perform the import/export process
+            var startTime = DateTimeOffset.UtcNow;
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    WorkingDirectory = workingDir,
+                    FileName = Path.Combine(targetDir, "sqlpackage.exe"),
+                    Arguments = cmdBuilder.ToString(),
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+                    RedirectStandardOutput = true,
+                    RedirectStandardError = true
+                }
+            };
+
+            process.OutputDataReceived += (s, e) => WriteLine(e.Data);
+            process.ErrorDataReceived += (s, e) => WriteErrorLine(e.Data);
+            process.Start();
+            process.BeginOutputReadLine();
+            process.BeginErrorReadLine();
+            process.WaitForExit();
+
+            WriteLine(String.Format("SqlPackage.exe exited with code: {0}", process.ExitCode));
+
+            if (payload.Action == ActionType.Export)
+            {
+                if (File.Exists(sqlPackageBacpacFile))
+                {
+                    WriteLine(string.Format("Downloaded {0} bacpac file to {1}", payload.DatabaseName, sqlPackageBacpacFile));
+                }
+                else
+                {
+                    throw new Exception(string.Format("{0} didn't downloaded", sqlPackageBacpacFile));
+                }
+
+                // Persist the Job Output 
+                JobOutputStorage jobOutputStorage = new JobOutputStorage(new Uri(jobContainerUrl));
+
+                await jobOutputStorage.SaveAsync(JobOutputKind.JobOutput, sqlPackageLogPath);
+                WriteLine(String.Format("Uploaded {0} to job account", sqlPackageLogPath));
+
+                await jobOutputStorage.SaveAsync(JobOutputKind.JobOutput, sqlPackageBacpacFile, payload.DatabaseName + ".bacpac");
+                WriteLine(String.Format("Uploaded {0} to job account", sqlPackageBacpacFile));
+            }
+
+            // We are tracking the disk file to save our standard output, but the node agent may take
+            // up to 3 seconds to flush the stdout stream to disk. So give the file a moment to catch up.
+            await Task.Delay(stdoutFlushDelay);
+
+            // Cleanup folders
+            foreach (string dir in directories)
+            {
+                if (Directory.Exists(dir))
+                {
+                    Directory.Delete(dir, true);
+                }
+            }
+
+            return process.ExitCode;
+        }
+    }
+}

+ 10 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/BatchWrapper/packages.config

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Azure.Batch" version="13.0.0" targetFramework="net472" />
+  <package id="Microsoft.Azure.Batch.Conventions.Files" version="3.5.1" targetFramework="net472" />
+  <package id="Microsoft.Azure.KeyVault.Core" version="3.0.5" targetFramework="net472" />
+  <package id="Microsoft.Rest.ClientRuntime" version="2.3.21" targetFramework="net472" />
+  <package id="Microsoft.Rest.ClientRuntime.Azure" version="3.3.19" targetFramework="net472" />
+  <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net472" />
+  <package id="WindowsAzure.Storage" version="9.3.3" targetFramework="net472" />
+</packages>

+ 27 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/.vscode/launch.json

@@ -0,0 +1,27 @@
+{
+   // Use IntelliSense to find out which attributes exist for C# debugging
+   // Use hover for the description of the existing attributes
+   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+   "version": "0.2.0",
+   "configurations": [
+        {
+            "name": ".NET Core Launch (console)",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build",
+            // If you have changed target frameworks, make sure to update the program path.
+            "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/SqlPackageWrapper.dll",
+            "args": [],
+            "cwd": "${workspaceFolder}",
+            // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+            "console": "internalConsole",
+            "stopAtEntry": false
+        },
+        {
+            "name": ".NET Core Attach",
+            "type": "coreclr",
+            "request": "attach",
+            "processId": "${command:pickProcess}"
+        }
+    ]
+}

+ 42 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/.vscode/tasks.json

@@ -0,0 +1,42 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "build",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "build",
+                "${workspaceFolder}/SqlPackageWrapper.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "publish",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "publish",
+                "${workspaceFolder}/SqlPackageWrapper.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "watch",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "watch",
+                "run",
+                "${workspaceFolder}/SqlPackageWrapper.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "problemMatcher": "$msCompile"
+        }
+    ]
+}

+ 12 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/ActionType.cs

@@ -0,0 +1,12 @@
+namespace SqlPackageWrapper
+{
+    /// <summary>
+    /// The type of sqlpackage action to perform.
+    /// </summary>
+    public enum ActionType
+    {
+        DefaultInvalid = -1,
+        Export,
+        Import
+    }
+}

+ 39 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/Constants.cs

@@ -0,0 +1,39 @@
+namespace SqlPackageWrapper
+{
+    /// <summary>
+    /// Constants for the batch wrapper.
+    /// </summary>
+    public static class Constants
+    {
+        /// <summary>
+        /// Environment variable names present or needed during the batch task execution.
+        /// </summary>
+        public static class EnvironmentVariableNames
+        {
+            /// <summary>
+            /// Path to the directory containing the batch wrapper exe.
+            /// </summary>
+            public const string WrapperLocation = "AZ_BATCH_APP_PACKAGE_BATCHWRAPPER";
+
+            /// <summary>
+            /// Path to the directory containing the sqlpackage exe.
+            /// </summary>
+            internal const string SqlPackageLocation = "AZ_BATCH_APP_PACKAGE_SQLPACKAGE";
+
+            /// <summary>
+            /// Path to the working directory assigned to the batch task.
+            /// </summary>
+            internal const string TaskWorkingDir = "AZ_BATCH_TASK_WORKING_DIR";
+
+            /// <summary>
+            /// Path to the working directory assigned to the batch task.
+            /// </summary>
+            internal const string AzBatchTaskId = "AZ_BATCH_TASK_ID";
+
+            /// <summary>
+            /// Path to the working directory assigned to the batch task.
+            /// </summary>
+            internal const string JobContainerUrl = "JOB_CONTAINER_URL";
+        }
+    }
+}

+ 38 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/Payload.cs

@@ -0,0 +1,38 @@
+namespace SqlPackageWrapper
+{
+    /// <summary>
+    /// The top-level object stored in the key vault for an import/export operation.
+    /// </summary>
+    public sealed class Payload
+    {
+        /// <summary>
+        /// The version of sqlpackage to use for performing the import/export operation.
+        /// </summary>
+        public string SqlPackageVersion { get; set; }
+
+        /// <summary>
+        /// The type of sqlpackage action to perform.
+        /// </summary>
+        public ActionType Action { get; set; }
+
+        /// <summary>
+        /// The logical server name to export from or import to.
+        /// </summary>
+        public string LogicalServerName { get; set; }
+
+        /// <summary>
+        /// The database name to export from or import to.
+        /// </summary>
+        public string DatabaseName { get; set; }
+
+        /// <summary>
+        /// The server admin username.
+        /// </summary>
+        public string Username { get; set; }
+
+        /// <summary>
+        /// The server admin password.
+        /// </summary>
+        public string Password { get; set; }
+    }
+}

+ 162 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/Program.cs

@@ -0,0 +1,162 @@
+using Microsoft.Azure.Batch.Conventions.Files;
+using Microsoft.WindowsAzure.Storage.Blob;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SqlPackageWrapper
+{
+    public static class Program
+    {
+        private static string dataDirectory = "F:\\data";
+        private static string tempDirectory = "F:\\temp";
+        private static string[] directories = { dataDirectory, tempDirectory };
+
+        private static readonly TimeSpan stdoutFlushDelay = TimeSpan.FromSeconds(3);
+
+        private static void WriteLine(string message) => WriteLineInternal(Console.Out, message);
+        private static void WriteErrorLine(string message) => WriteLineInternal(Console.Error, message);
+        private static void WriteLineInternal(TextWriter writer, string message)
+        {
+            var lines = message?.Split('\n') ?? new string[0];
+            foreach (var line in lines)
+            {
+                writer.WriteLine($"[{DateTime.UtcNow:u}] {line?.TrimEnd()}");
+            }
+        }
+
+        public static async Task<int> Main(string[] args)
+        {
+            var assembly = typeof(Program).Assembly;
+            WriteLine($"{assembly.ManifestModule.Name} v{assembly.GetName().Version.ToString(3)}");
+
+            // Get the command payload 
+            var payload = new Payload();
+
+            if (args.Length > 0)
+            {
+                payload.Action = (ActionType)Enum.Parse(typeof(ActionType), args[0]);
+                payload.LogicalServerName = args[1] + ".database.windows.net";
+                payload.DatabaseName = args[2];
+                payload.Username = args[3];
+                payload.Password = args[4];
+                payload.SqlPackageVersion = args[5];
+            }
+
+            // Cleanup folders
+            foreach (string dir in directories)
+            {
+                if (Directory.Exists(dir))
+                {
+                    Directory.Delete(dir, true);
+                }
+
+                Directory.CreateDirectory(dir);
+            }
+
+            string sqlPackageDataPath = Path.Combine(dataDirectory, payload.DatabaseName + ".bacpac");
+            string sqlPackageLogPath = Path.Combine(dataDirectory, payload.DatabaseName + ".log");
+
+            var targetDir = Environment.GetEnvironmentVariable($"{Constants.EnvironmentVariableNames.SqlPackageLocation}#{payload.SqlPackageVersion}");
+            var workingDir = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.TaskWorkingDir);
+
+            string taskId = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.AzBatchTaskId);
+            string jobContainerUrl = Environment.GetEnvironmentVariable(Constants.EnvironmentVariableNames.JobContainerUrl);
+
+            // Build the import/export command
+            var cmdBuilder = new StringBuilder();
+            cmdBuilder.Append($"/Action:{payload.Action}");
+            cmdBuilder.Append(" /MaxParallelism:16");
+            cmdBuilder.Append(String.Format(" /DiagnosticsFile:{0}", sqlPackageLogPath));
+            cmdBuilder.Append(" /p:CommandTimeout=86400");
+
+            switch (payload.Action)
+            {
+                case ActionType.Export:
+                    cmdBuilder.Append($" /SourceServerName:{payload.LogicalServerName}");
+                    cmdBuilder.Append($" /SourceDatabaseName:{payload.DatabaseName}");
+                    cmdBuilder.Append($" /SourceUser:{payload.Username}");
+                    cmdBuilder.Append($" /SourcePassword:{payload.Password}");
+                    cmdBuilder.Append($" /TargetFile:{sqlPackageDataPath}");
+                    cmdBuilder.Append(String.Format(" /p:TempDirectoryForTableData=\"{0}\"", tempDirectory));
+                    cmdBuilder.Append(" /p:VerifyFullTextDocumentTypesSupported=false");
+                    break;
+
+                case ActionType.Import:
+                    cmdBuilder.Append($" /TargetServerName:{payload.LogicalServerName}");
+                    cmdBuilder.Append($" /TargetDatabaseName:{payload.DatabaseName}");
+                    cmdBuilder.Append($" /TargetUser:{payload.Username}");
+                    cmdBuilder.Append($" /TargetPassword:{payload.Password}");
+                    cmdBuilder.Append($" /SourceFile:{sqlPackageDataPath}");
+                    break;
+
+                default:
+                    throw new ArgumentException($"Invalid action type: {payload.Action}");
+            }
+
+            if (payload.Action == ActionType.Import)
+            {
+                WriteLine(string.Format("Downloading {0} bacpac file to {1}", payload.DatabaseName, sqlPackageDataPath));
+                CloudBlobContainer container = new CloudBlobContainer(new Uri(jobContainerUrl));
+                CloudBlockBlob blob = container.GetBlockBlobReference(String.Format("$JobOutput/{0}.bacpac", payload.DatabaseName));
+                await blob.DownloadToFileAsync(sqlPackageDataPath, FileMode.CreateNew);
+                WriteLine(string.Format("Downloaded {0} bacpac file to {1}", payload.DatabaseName, sqlPackageDataPath));
+                
+                await Task.Delay(stdoutFlushDelay);
+            }
+
+            // Perform the import/export process
+            var startTime = DateTimeOffset.UtcNow;
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    WorkingDirectory = workingDir,
+                    FileName = Path.Combine(targetDir, "sqlpackage.exe"),
+                    Arguments = cmdBuilder.ToString(),
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+                    RedirectStandardOutput = true,
+                    RedirectStandardError = true
+                }
+            };
+            process.OutputDataReceived += (s, e) => WriteLine(e.Data);
+            process.ErrorDataReceived += (s, e) => WriteErrorLine(e.Data);
+            process.Start();
+            process.BeginOutputReadLine();
+            process.BeginErrorReadLine();
+            process.WaitForExit();
+
+            WriteLine(String.Format("SqlPackage.exe exited with code: {0}", process.ExitCode));
+
+            if (payload.Action == ActionType.Export)
+            {
+                // Persist the Job Output 
+                JobOutputStorage jobOutputStorage = new JobOutputStorage(new Uri(jobContainerUrl));
+
+                await jobOutputStorage.SaveAsync(JobOutputKind.JobOutput, sqlPackageLogPath, payload.DatabaseName + ".log");
+                WriteLine(String.Format("Uploaded {0} to job account", sqlPackageLogPath));
+
+                await jobOutputStorage.SaveAsync(JobOutputKind.JobOutput, sqlPackageDataPath, payload.DatabaseName + ".bacpac");
+                WriteLine(String.Format("Uploaded {0} to job account", sqlPackageDataPath));
+
+                // We are tracking the disk file to save our standard output, but the node agent may take
+                // up to 3 seconds to flush the stdout stream to disk. So give the file a moment to catch up.
+                await Task.Delay(stdoutFlushDelay);
+            }
+
+            // Cleanup folders
+            foreach (string dir in directories)
+            {
+                if (Directory.Exists(dir))
+                {
+                    Directory.Delete(dir, true);
+                }
+            }
+
+            return process.ExitCode;
+        }
+    }
+}

+ 14 - 0
SQL-Hybrid-Cloud-Toolkit/Components/ADP/SqlPackageWrapper/SqlPackageWrapper.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.1;net452</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Azure.Batch" Version="13.0.0" />
+    <PackageReference Include="Microsoft.Azure.Batch.Conventions.Files" Version="3.5.1" />
+    <PackageReference Include="Microsoft.Azure.Management.Batch" Version="11.0.0" />
+  </ItemGroup>
+
+</Project>

+ 1 - 0
SQL-Hybrid-Cloud-Toolkit/Components/readme.md

@@ -0,0 +1 @@
+Folder for helper components used by notebooks in the Hybrid Cloud Toolkit

+ 4 - 0
SQL-Hybrid-Cloud-Toolkit/README.md

@@ -0,0 +1,4 @@
+# Azure SQL Hybrid Cloud Toolkit
+This Jupyter book contains utilities designed to help manage a SQL hybrid cloud environment.
+
+* [Cloud Toolkit Readme](content/readme.md) - Information on individual notebooks in this toolkit.

+ 2 - 0
SQL-Hybrid-Cloud-Toolkit/_config.yml

@@ -0,0 +1,2 @@
+title: Azure SQL Hybrid Cloud Toolkit
+description: A collection of notebooks to help deploy, migrate and manage SQL instances and databases in a Hybrid Cloud environment.

+ 80 - 0
SQL-Hybrid-Cloud-Toolkit/_data/toc.yml

@@ -0,0 +1,80 @@
+- title: Welcome
+  url: /readme
+  not_numbered: true
+- title: Prerequisites and Initial Setup
+  url: /prereqs
+  not_numbered: true
+
+- title: Search
+  search: true
+
+- title: Assessment
+  url: /Assessments/readme
+  not_numbered: true
+  expand_sections: false
+  sections:
+  - title: SQL Server Assessment Tool
+    url: Assessments/sql-server-assessment
+  - title: Compatibility Assessment
+    url: Assessments/compatibility-assessment
+- title: Networking
+  url: /networking/readme
+  not_numbered: true
+  expand_sections: false
+  sections:
+  - title: Download VPN Client Certificate
+    url: networking/download-VpnClient
+  - title: Create Point-to-Site VPN
+    url: networking/p2svnet-creation
+  - title: Create Site-to-Site VPN
+    url: networking/s2svnet-creation
+- title: Provisioning
+  url: /provisioning/readme
+  not_numbered: true
+  expand_sections: false
+  sections:
+  - title: Create Azure SQL Virtual Machine
+    url: provisioning/create-sqlvm
+  - title: Create Azure SQL Managed Instance
+    url: provisioning/create-sqlmi
+  - title: Create Azure SQL Database
+    url: provisioning/create-sqldb
+- title: Data Portability
+  url: /data-portability/readme
+  not_numbered: true
+  expand_sections: false
+  sections:
+  - title: Setup Data Portability
+    url: data-portability/setup-adp
+  - title: Export Azure SQL Server
+    url: data-portability/export-sql-server
+  - title: Import Azure SQL Server
+    url: data-portability/import-sql-server
+- title: High Availability and Disaster Recovery
+  url: /hadr/readme
+  not_numbered: true
+  expand_sections: false
+  sections:
+  - title: Backup Database to Blob Storage
+    url: hadr/backup-to-blob
+  - title: Add Azure Passive Secondary Replica
+    url: hadr/add-passive-secondary
+- title: Offline Migration
+  url: /offline-migration/readme
+  not_numbered: true
+  expand_sections: false
+  sections:
+  - title: Migrate Instance to Azure SQL VM
+    url: offline-migration/instance-to-VM
+  - title: Migrate Database to Azure SQL VM
+    url: offline-migration/db-to-VM
+  - title: Migrate Instance to Azure SQL MI
+    url: offline-migration/instance-to-MI
+  - title: Migrate Database to Azure SQL MI
+    url: offline-migration/db-to-MI
+  - title: Migrate Database to Azure SQL DB
+    url: offline-migration/db-to-SQLDB
+- title: Glossary
+  url: /glossary
+- title: Appendices
+  url: /appendices

+ 4 - 0
SQL-Hybrid-Cloud-Toolkit/assets/css/styles.scss

@@ -0,0 +1,4 @@
+---
+---
+
+@import 'main';

+ 4 - 0
SQL-Hybrid-Cloud-Toolkit/assets/custom/custom.css

@@ -0,0 +1,4 @@
+/* Put your custom CSS here */
+.left {
+    margin-left: 0px;
+}

+ 1 - 0
SQL-Hybrid-Cloud-Toolkit/assets/custom/custom.js

@@ -0,0 +1 @@
+// Put your custom javascript here

+ 25 - 0
SQL-Hybrid-Cloud-Toolkit/assets/html/index.html

@@ -0,0 +1,25 @@
+---
+permalink: /index.html
+title: "Index"
+layout: none
+---
+
+<!-- The index page should simply re-direct to the first chapter -->
+{% for chapter in site.data.toc %}
+{% unless chapter.external %}
+  {% comment %}This ensures that the first link we re-direct to isn't an external site {% endcomment %}
+  {% assign redirectURL = chapter.url | relative_url %}
+  {% break %}
+{% endunless %}
+{% endfor %}
+<!DOCTYPE html>
+<html lang="en-US">
+  <meta charset="utf-8">
+  <title>Redirecting&hellip;</title>
+  <link rel="canonical" href="{{ redirectURL }}">
+  <script>location="{{ redirectURL }}"</script>
+  <meta http-equiv="refresh" content="0; url={{ redirectURL }}">
+  <meta name="robots" content="noindex">
+  <h1>Redirecting&hellip;</h1>
+  <a href="{{ redirectURL }}">Click here if you are not redirected.</a>
+</html>

+ 15 - 0
SQL-Hybrid-Cloud-Toolkit/assets/html/search_form.html

@@ -0,0 +1,15 @@
+---
+permalink: /search
+title: "Search the site"
+search_page: true
+---
+
+<div class="search-content__inner-wrap">
+    <input type="text" id="lunr_search" class="search-input" tabindex="-1" placeholder="'Enter your search term...''" />
+    <div id="results" class="results"></div>
+</div>
+
+<script>
+    // Add the lunr store since we will now search it
+    {% include search/lunr/lunr-store.js %}
+</script>

+ 1 - 0
SQL-Hybrid-Cloud-Toolkit/assets/images/copy-button.svg

@@ -0,0 +1 @@
+<svg aria-hidden="true" data-prefix="far" data-icon="copy" class="svg-inline--fa fa-copy fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="#777" d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"></path></svg>

+ 81 - 0
SQL-Hybrid-Cloud-Toolkit/assets/images/edit-button.svg

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   id="svg2157"
+   height="600"
+   width="600"
+   version="1.0"
+   sodipodi:docname="edit-button.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <defs
+     id="defs12" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1001"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="0.39333333"
+     inkscape:cx="300"
+     inkscape:cy="300"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <metadata
+     id="metadata2162">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     style="fill:#ffffff">
+    <g
+       id="g3765"
+       stroke="#a2a9b1"
+       fill="none"
+       style="fill:none;stroke:#6c7681;stroke-opacity:1;stroke-linecap:round;paint-order:normal;stroke-linejoin:miter;stroke-width:40;stroke-miterlimit:5;stroke-dasharray:none">
+      <path
+         id="rect2990"
+         d="m70.064 422.35 374.27-374.26 107.58 107.58-374.26 374.27-129.56 21.97z"
+         stroke-width="30"
+         style="fill:none;stroke:#6c7681;stroke-opacity:1;stroke-linecap:round;paint-order:normal;stroke-linejoin:miter;stroke-width:40;stroke-miterlimit:5;stroke-dasharray:none" />
+      <path
+         id="path3771"
+         d="m70.569 417.81 110.61 110.61"
+         stroke-width="25"
+         style="fill:none;stroke:#6c7681;stroke-opacity:1;stroke-linecap:round;paint-order:normal;stroke-linejoin:miter;stroke-width:40;stroke-miterlimit:5;stroke-dasharray:none" />
+      <path
+         id="path3777"
+         d="m491.47 108.37-366.69 366.68"
+         stroke-width="25"
+         style="fill:none;stroke:#6c7681;stroke-opacity:1;stroke-linecap:round;paint-order:normal;stroke-linejoin:miter;stroke-width:40;stroke-miterlimit:5;stroke-dasharray:none" />
+      <path
+         id="path3763"
+         d="m54.222 507.26 40.975 39.546"
+         stroke-width="25"
+         style="fill:none;stroke:#6c7681;stroke-opacity:1;stroke-linecap:round;paint-order:normal;stroke-linejoin:miter;stroke-width:40;stroke-miterlimit:5;stroke-dasharray:none" />
+    </g>
+  </g>
+</svg>

+ 19 - 0
SQL-Hybrid-Cloud-Toolkit/assets/images/logo_binder.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 44.4 44.4" style="enable-background:new 0 0 44.4 44.4;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:none;stroke:#F5A252;stroke-width:5;stroke-miterlimit:10;}
+	.st1{fill:none;stroke:#579ACA;stroke-width:5;stroke-miterlimit:10;}
+	.st2{fill:none;stroke:#E66581;stroke-width:5;stroke-miterlimit:10;}
+</style>
+<title>logo</title>
+<g>
+	<path class="st0" d="M33.9,6.4c3.6,3.9,3.4,9.9-0.5,13.5s-9.9,3.4-13.5-0.5s-3.4-9.9,0.5-13.5l0,0C24.2,2.4,30.2,2.6,33.9,6.4z"/>
+	<path class="st1" d="M35.1,27.3c2.6,4.6,1.1,10.4-3.5,13c-4.6,2.6-10.4,1.1-13-3.5s-1.1-10.4,3.5-13l0,0
+		C26.6,21.2,32.4,22.7,35.1,27.3z"/>
+	<path class="st2" d="M25.9,17.8c2.6,4.6,1.1,10.4-3.5,13s-10.4,1.1-13-3.5s-1.1-10.4,3.5-13l0,0C17.5,11.7,23.3,13.2,25.9,17.8z"/>
+	<path class="st1" d="M19.2,26.4c3.1-4.3,9.1-5.2,13.3-2.1c1.1,0.8,2,1.8,2.7,3"/>
+	<path class="st0" d="M19.9,19.4c-3.6-3.9-3.4-9.9,0.5-13.5s9.9-3.4,13.5,0.5"/>
+</g>
+</svg>

+ 1 - 0
SQL-Hybrid-Cloud-Toolkit/assets/images/logo_jupyterhub.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="38.73" height="50" viewBox="0 0 38.73 50"><defs><style>.cls-1{fill:#767677;}.cls-2{fill:#f37726;}.cls-3{fill:#9e9e9e;}.cls-4{fill:#616262;}.cls-5{font-size:17.07px;fill:#fff;font-family:Roboto-Regular, Roboto;}</style></defs><title>logo_jupyterhub</title><g id="Canvas"><path id="path7_fill" data-name="path7 fill" class="cls-1" d="M39.51,3.53a3,3,0,0,1-1.7,2.9A3,3,0,0,1,34.48,6a3,3,0,0,1-.82-3.26,3,3,0,0,1,1.05-1.41A3,3,0,0,1,37.52.86a2.88,2.88,0,0,1,1,.6,3,3,0,0,1,.7.93,3.18,3.18,0,0,1,.28,1.14Z" transform="translate(-1.87 -0.69)"/><path id="path8_fill" data-name="path8 fill" class="cls-2" d="M21.91,38.39c-8,0-15.06-2.87-18.7-7.12a19.93,19.93,0,0,0,37.39,0C37,35.52,30,38.39,21.91,38.39Z" transform="translate(-1.87 -0.69)"/><path id="path9_fill" data-name="path9 fill" class="cls-2" d="M21.91,10.78c8,0,15.05,2.87,18.69,7.12a19.93,19.93,0,0,0-37.39,0C6.85,13.64,13.86,10.78,21.91,10.78Z" transform="translate(-1.87 -0.69)"/><path id="path10_fill" data-name="path10 fill" class="cls-3" d="M10.88,46.66a3.86,3.86,0,0,1-.52,2.15,3.81,3.81,0,0,1-1.62,1.51,3.93,3.93,0,0,1-2.19.34,3.79,3.79,0,0,1-2-.94,3.73,3.73,0,0,1-1.14-1.9,3.79,3.79,0,0,1,.1-2.21,3.86,3.86,0,0,1,1.33-1.78,3.92,3.92,0,0,1,3.54-.53,3.85,3.85,0,0,1,2.14,1.93,3.74,3.74,0,0,1,.37,1.43Z" transform="translate(-1.87 -0.69)"/><path id="path11_fill" data-name="path11 fill" class="cls-4" d="M4.12,9.81A2.18,2.18,0,0,1,2.9,9.48a2.23,2.23,0,0,1-.84-1A2.26,2.26,0,0,1,1.9,7.26a2.13,2.13,0,0,1,.56-1.13,2.18,2.18,0,0,1,2.36-.56,2.13,2.13,0,0,1,1,.76,2.18,2.18,0,0,1,.42,1.2A2.22,2.22,0,0,1,4.12,9.81Z" transform="translate(-1.87 -0.69)"/></g><text class="cls-5" transform="translate(5.24 30.01)">Hub</text></svg>

BIN=BIN
SQL-Hybrid-Cloud-Toolkit/assets/images/sqlserver.png


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 7 - 0
SQL-Hybrid-Cloud-Toolkit/assets/js/anchor.min.js


+ 58 - 0
SQL-Hybrid-Cloud-Toolkit/assets/js/ga.js

@@ -0,0 +1,58 @@
+(function(){var $c=function(a){this.w=a||[]};$c.prototype.set=function(a){this.w[a]=!0};$c.prototype.encode=function(){for(var a=[],b=0;b<this.w.length;b++)this.w[b]&&(a[Math.floor(b/6)]^=1<<b%6);for(b=0;b<a.length;b++)a[b]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".charAt(a[b]||0);return a.join("")+"~"};var vd=new $c;function J(a){vd.set(a)}var Td=function(a){a=Dd(a);a=new $c(a);for(var b=vd.w.slice(),c=0;c<a.w.length;c++)b[c]=b[c]||a.w[c];return(new $c(b)).encode()},Dd=function(a){a=a.get(Gd);ka(a)||(a=[]);return a};var ea=function(a){return"function"==typeof a},ka=function(a){return"[object Array]"==Object.prototype.toString.call(Object(a))},qa=function(a){return void 0!=a&&-1<(a.constructor+"").indexOf("String")},D=function(a,b){return 0==a.indexOf(b)},sa=function(a){return a?a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,""):""},ra=function(){for(var a=O.navigator.userAgent+(M.cookie?M.cookie:"")+(M.referrer?M.referrer:""),b=a.length,c=O.history.length;0<c;)a+=c--^b++;return[hd()^La(a)&2147483647,Math.round((new Date).getTime()/
+1E3)].join(".")},ta=function(a){var b=M.createElement("img");b.width=1;b.height=1;b.src=a;return b},ua=function(){},K=function(a){if(encodeURIComponent instanceof Function)return encodeURIComponent(a);J(28);return a},L=function(a,b,c,d){try{a.addEventListener?a.addEventListener(b,c,!!d):a.attachEvent&&a.attachEvent("on"+b,c)}catch(e){J(27)}},f=/^[\w\-:/.?=&%!]+$/,wa=function(a,b,c){a&&(c?(c="",b&&f.test(b)&&(c=' id="'+b+'"'),f.test(a)&&M.write("<script"+c+' src="'+a+'">\x3c/script>')):(c=M.createElement("script"),
+c.type="text/javascript",c.async=!0,c.src=a,b&&(c.id=b),a=M.getElementsByTagName("script")[0],a.parentNode.insertBefore(c,a)))},be=function(a,b){return E(M.location[b?"href":"search"],a)},E=function(a,b){return(a=a.match("(?:&|#|\\?)"+K(b).replace(/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1")+"=([^&#]*)"))&&2==a.length?a[1]:""},xa=function(){var a=""+M.location.hostname;return 0==a.indexOf("www.")?a.substring(4):a},de=function(a,b){var c=a.indexOf(b);if(5==c||6==c)if(a=a.charAt(c+b.length),"/"==a||"?"==a||
+""==a||":"==a)return!0;return!1},ya=function(a,b){var c=M.referrer;if(/^(https?|android-app):\/\//i.test(c)){if(a)return c;a="//"+M.location.hostname;if(!de(c,a))return b&&(b=a.replace(/\./g,"-")+".cdn.ampproject.org",de(c,b))?void 0:c}},za=function(a,b){if(1==b.length&&null!=b[0]&&"object"===typeof b[0])return b[0];for(var c={},d=Math.min(a.length+1,b.length),e=0;e<d;e++)if("object"===typeof b[e]){for(var g in b[e])b[e].hasOwnProperty(g)&&(c[g]=b[e][g]);break}else e<a.length&&(c[a[e]]=b[e]);return c};var ee=function(){this.keys=[];this.values={};this.m={}};ee.prototype.set=function(a,b,c){this.keys.push(a);c?this.m[":"+a]=b:this.values[":"+a]=b};ee.prototype.get=function(a){return this.m.hasOwnProperty(":"+a)?this.m[":"+a]:this.values[":"+a]};ee.prototype.map=function(a){for(var b=0;b<this.keys.length;b++){var c=this.keys[b],d=this.get(c);d&&a(c,d)}};var O=window,M=document,va=function(a,b){return setTimeout(a,b)};var F=window,Ea=document,G=function(a){var b=F._gaUserPrefs;if(b&&b.ioo&&b.ioo()||a&&!0===F["ga-disable-"+a])return!0;try{var c=F.external;if(c&&c._gaUserPrefs&&"oo"==c._gaUserPrefs)return!0}catch(g){}a=[];b=Ea.cookie.split(";");c=/^\s*AMP_TOKEN=\s*(.*?)\s*$/;for(var d=0;d<b.length;d++){var e=b[d].match(c);e&&a.push(e[1])}for(b=0;b<a.length;b++)if("$OPT_OUT"==decodeURIComponent(a[b]))return!0;return!1};var Ca=function(a){var b=[],c=M.cookie.split(";");a=new RegExp("^\\s*"+a+"=\\s*(.*?)\\s*$");for(var d=0;d<c.length;d++){var e=c[d].match(a);e&&b.push(e[1])}return b},zc=function(a,b,c,d,e,g){e=G(e)?!1:eb.test(M.location.hostname)||"/"==c&&vc.test(d)?!1:!0;if(!e)return!1;b&&1200<b.length&&(b=b.substring(0,1200));c=a+"="+b+"; path="+c+"; ";g&&(c+="expires="+(new Date((new Date).getTime()+g)).toGMTString()+"; ");d&&"none"!==d&&(c+="domain="+d+";");d=M.cookie;M.cookie=c;if(!(d=d!=M.cookie))a:{a=Ca(a);
+for(d=0;d<a.length;d++)if(b==a[d]){d=!0;break a}d=!1}return d},Cc=function(a){return encodeURIComponent?encodeURIComponent(a).replace(/\(/g,"%28").replace(/\)/g,"%29"):a},vc=/^(www\.)?google(\.com?)?(\.[a-z]{2})?$/,eb=/(^|\.)doubleclick\.net$/i;var oc,Id=/^.*Version\/?(\d+)[^\d].*$/i,ne=function(){if(void 0!==O.__ga4__)return O.__ga4__;if(void 0===oc){var a=O.navigator.userAgent;if(a){var b=a;try{b=decodeURIComponent(a)}catch(c){}if(a=!(0<=b.indexOf("Chrome"))&&!(0<=b.indexOf("CriOS"))&&(0<=b.indexOf("Safari/")||0<=b.indexOf("Safari,")))b=Id.exec(b),a=11<=(b?Number(b[1]):-1);oc=a}else oc=!1}return oc};var Fa,Ga,fb,Ab,ja=/^https?:\/\/[^/]*cdn\.ampproject\.org\//,Ub=[],ic=function(){Z.D([ua])},tc=function(a,b){var c=Ca("AMP_TOKEN");if(1<c.length)return J(55),!1;c=decodeURIComponent(c[0]||"");if("$OPT_OUT"==c||"$ERROR"==c||G(b))return J(62),!1;if(!ja.test(M.referrer)&&"$NOT_FOUND"==c)return J(68),!1;if(void 0!==Ab)return J(56),va(function(){a(Ab)},0),!0;if(Fa)return Ub.push(a),!0;if("$RETRIEVING"==c)return J(57),va(function(){tc(a,b)},1E4),!0;Fa=!0;c&&"$"!=c[0]||(xc("$RETRIEVING",3E4),setTimeout(Mc,
+3E4),c="");return Pc(c,b)?(Ub.push(a),!0):!1},Pc=function(a,b,c){if(!window.JSON)return J(58),!1;var d=O.XMLHttpRequest;if(!d)return J(59),!1;var e=new d;if(!("withCredentials"in e))return J(60),!1;e.open("POST",(c||"https://ampcid.google.com/v1/publisher:getClientId")+"?key=AIzaSyA65lEHUEizIsNtlbNo-l2K18dT680nsaM",!0);e.withCredentials=!0;e.setRequestHeader("Content-Type","text/plain");e.onload=function(){Fa=!1;if(4==e.readyState){try{200!=e.status&&(J(61),Qc("","$ERROR",3E4));var d=JSON.parse(e.responseText);
+d.optOut?(J(63),Qc("","$OPT_OUT",31536E6)):d.clientId?Qc(d.clientId,d.securityToken,31536E6):!c&&d.alternateUrl?(Ga&&clearTimeout(Ga),Fa=!0,Pc(a,b,d.alternateUrl)):(J(64),Qc("","$NOT_FOUND",36E5))}catch(ca){J(65),Qc("","$ERROR",3E4)}e=null}};d={originScope:"AMP_ECID_GOOGLE"};a&&(d.securityToken=a);e.send(JSON.stringify(d));Ga=va(function(){J(66);Qc("","$ERROR",3E4)},1E4);return!0},Mc=function(){Fa=!1},xc=function(a,b){if(void 0===fb){fb="";for(var c=id(),d=0;d<c.length;d++){var e=c[d];if(zc("AMP_TOKEN",
+encodeURIComponent(a),"/",e,"",b)){fb=e;return}}}zc("AMP_TOKEN",encodeURIComponent(a),"/",fb,"",b)},Qc=function(a,b,c){Ga&&clearTimeout(Ga);b&&xc(b,c);Ab=a;b=Ub;Ub=[];for(c=0;c<b.length;c++)b[c](a)};var oe=function(){return(Ba||"https:"==M.location.protocol?"https:":"http:")+"//www.google-analytics.com"},Da=function(a){this.name="len";this.message=a+"-8192"},ba=function(a,b,c){c=c||ua;if(2036>=b.length)wc(a,b,c);else if(8192>=b.length)x(a,b,c)||wd(a,b,c)||wc(a,b,c);else throw ge("len",b.length),new Da(b.length);},pe=function(a,b,c,d){d=d||ua;wd(a+"?"+b,"",d,c)},wc=function(a,b,c){var d=ta(a+"?"+b);d.onload=d.onerror=function(){d.onload=null;d.onerror=null;c()}},wd=function(a,b,c,d){var e=O.XMLHttpRequest;
+if(!e)return!1;var g=new e;if(!("withCredentials"in g))return!1;a=a.replace(/^http:/,"https:");g.open("POST",a,!0);g.withCredentials=!0;g.setRequestHeader("Content-Type","text/plain");g.onreadystatechange=function(){if(4==g.readyState){if(d)try{var a=g.responseText;if(1>a.length)ge("xhr","ver","0"),c();else if("1"!=a.charAt(0))ge("xhr","ver",String(a.length)),c();else if(3<d.count++)ge("xhr","tmr",""+d.count),c();else if(1==a.length)c();else{var b=a.charAt(1);if("d"==b)pe("https://stats.g.doubleclick.net/j/collect",
+d.U,d,c);else if("g"==b){var e="https://www.google.%/ads/ga-audiences".replace("%","com");wc(e,d.google,c);var w=a.substring(2);if(w)if(/^[a-z.]{1,6}$/.test(w)){var ha="https://www.google.%/ads/ga-audiences".replace("%",w);wc(ha,d.google,ua)}else ge("tld","bcc",w)}else ge("xhr","brc",b),c()}}catch(ue){ge("xhr","rsp"),c()}else c();g=null}};g.send(b);return!0},x=function(a,b,c){return O.navigator.sendBeacon?O.navigator.sendBeacon(a,b)?(c(),!0):!1:!1},ge=function(a,b,c){1<=100*Math.random()||G("?")||
+(a=["t=error","_e="+a,"_v=j68","sr=1"],b&&a.push("_f="+b),c&&a.push("_m="+K(c.substring(0,100))),a.push("aip=1"),a.push("z="+hd()),wc("https://www.google-analytics.com/u/d",a.join("&"),ua))};var h=function(a){var b=O.gaData=O.gaData||{};return b[a]=b[a]||{}};var Ha=function(){this.M=[]};Ha.prototype.add=function(a){this.M.push(a)};Ha.prototype.D=function(a){try{for(var b=0;b<this.M.length;b++){var c=a.get(this.M[b]);c&&ea(c)&&c.call(O,a)}}catch(d){}b=a.get(Ia);b!=ua&&ea(b)&&(a.set(Ia,ua,!0),setTimeout(b,10))};function Ja(a){if(100!=a.get(Ka)&&La(P(a,Q))%1E4>=100*R(a,Ka))throw"abort";}function Ma(a){if(G(P(a,Na)))throw"abort";}function Oa(){var a=M.location.protocol;if("http:"!=a&&"https:"!=a)throw"abort";}
+function Pa(a){try{O.navigator.sendBeacon?J(42):O.XMLHttpRequest&&"withCredentials"in new O.XMLHttpRequest&&J(40)}catch(c){}a.set(ld,Td(a),!0);a.set(Ac,R(a,Ac)+1);var b=[];Qa.map(function(c,d){d.F&&(c=a.get(c),void 0!=c&&c!=d.defaultValue&&("boolean"==typeof c&&(c*=1),b.push(d.F+"="+K(""+c))))});b.push("z="+Bd());a.set(Ra,b.join("&"),!0)}
+function Sa(a){var b=P(a,gd)||oe()+"/collect",c=a.get(qe),d=P(a,fa);!d&&a.get(Vd)&&(d="beacon");if(c)pe(b,P(a,Ra),c,a.get(Ia));else if(d){c=d;d=P(a,Ra);var e=a.get(Ia);e=e||ua;"image"==c?wc(b,d,e):"xhr"==c&&wd(b,d,e)||"beacon"==c&&x(b,d,e)||ba(b,d,e)}else ba(b,P(a,Ra),a.get(Ia));b=a.get(Na);b=h(b);c=b.hitcount;b.hitcount=c?c+1:1;b=a.get(Na);delete h(b).pending_experiments;a.set(Ia,ua,!0)}
+function Hc(a){(O.gaData=O.gaData||{}).expId&&a.set(Nc,(O.gaData=O.gaData||{}).expId);(O.gaData=O.gaData||{}).expVar&&a.set(Oc,(O.gaData=O.gaData||{}).expVar);var b=a.get(Na);if(b=h(b).pending_experiments){var c=[];for(d in b)b.hasOwnProperty(d)&&b[d]&&c.push(encodeURIComponent(d)+"."+encodeURIComponent(b[d]));var d=c.join("!")}else d=void 0;d&&a.set(m,d,!0)}function cd(){if(O.navigator&&"preview"==O.navigator.loadPurpose)throw"abort";}
+function yd(a){var b=O.gaDevIds;ka(b)&&0!=b.length&&a.set("&did",b.join(","),!0)}function vb(a){if(!a.get(Na))throw"abort";};var hd=function(){return Math.round(2147483647*Math.random())},Bd=function(){try{var a=new Uint32Array(1);O.crypto.getRandomValues(a);return a[0]&2147483647}catch(b){return hd()}};function Ta(a){var b=R(a,Ua);500<=b&&J(15);var c=P(a,Va);if("transaction"!=c&&"item"!=c){c=R(a,Wa);var d=(new Date).getTime(),e=R(a,Xa);0==e&&a.set(Xa,d);e=Math.round(2*(d-e)/1E3);0<e&&(c=Math.min(c+e,20),a.set(Xa,d));if(0>=c)throw"abort";a.set(Wa,--c)}a.set(Ua,++b)};var Ya=function(){this.data=new ee},Qa=new ee,Za=[];Ya.prototype.get=function(a){var b=$a(a),c=this.data.get(a);b&&void 0==c&&(c=ea(b.defaultValue)?b.defaultValue():b.defaultValue);return b&&b.Z?b.Z(this,a,c):c};var P=function(a,b){a=a.get(b);return void 0==a?"":""+a},R=function(a,b){a=a.get(b);return void 0==a||""===a?0:1*a};Ya.prototype.set=function(a,b,c){if(a)if("object"==typeof a)for(var d in a)a.hasOwnProperty(d)&&ab(this,d,a[d],c);else ab(this,a,b,c)};
+var ab=function(a,b,c,d){if(void 0!=c)switch(b){case Na:wb.test(c)}var e=$a(b);e&&e.o?e.o(a,b,c,d):a.data.set(b,c,d)},bb=function(a,b,c,d,e){this.name=a;this.F=b;this.Z=d;this.o=e;this.defaultValue=c},$a=function(a){var b=Qa.get(a);if(!b)for(var c=0;c<Za.length;c++){var d=Za[c],e=d[0].exec(a);if(e){b=d[1](e);Qa.set(b.name,b);break}}return b},yc=function(a){var b;Qa.map(function(c,d){d.F==a&&(b=d)});return b&&b.name},S=function(a,b,c,d,e){a=new bb(a,b,c,d,e);Qa.set(a.name,a);return a.name},cb=function(a,
+b){Za.push([new RegExp("^"+a+"$"),b])},T=function(a,b,c){return S(a,b,c,void 0,db)},db=function(){};var gb=qa(window.GoogleAnalyticsObject)&&sa(window.GoogleAnalyticsObject)||"ga",jd=/^(?:utma\.)?\d+\.\d+$/,kd=/^amp-[\w.-]{22,64}$/,Ba=!1,hb=T("apiVersion","v"),ib=T("clientVersion","_v");S("anonymizeIp","aip");var jb=S("adSenseId","a"),Va=S("hitType","t"),Ia=S("hitCallback"),Ra=S("hitPayload");S("nonInteraction","ni");S("currencyCode","cu");S("dataSource","ds");var Vd=S("useBeacon",void 0,!1),fa=S("transport");S("sessionControl","sc","");S("sessionGroup","sg");S("queueTime","qt");var Ac=S("_s","_s");
+S("screenName","cd");var kb=S("location","dl",""),lb=S("referrer","dr"),mb=S("page","dp","");S("hostname","dh");var nb=S("language","ul"),ob=S("encoding","de");S("title","dt",function(){return M.title||void 0});cb("contentGroup([0-9]+)",function(a){return new bb(a[0],"cg"+a[1])});var pb=S("screenColors","sd"),qb=S("screenResolution","sr"),rb=S("viewportSize","vp"),sb=S("javaEnabled","je"),tb=S("flashVersion","fl");S("campaignId","ci");S("campaignName","cn");S("campaignSource","cs");
+S("campaignMedium","cm");S("campaignKeyword","ck");S("campaignContent","cc");var ub=S("eventCategory","ec"),xb=S("eventAction","ea"),yb=S("eventLabel","el"),zb=S("eventValue","ev"),Bb=S("socialNetwork","sn"),Cb=S("socialAction","sa"),Db=S("socialTarget","st"),Eb=S("l1","plt"),Fb=S("l2","pdt"),Gb=S("l3","dns"),Hb=S("l4","rrt"),Ib=S("l5","srt"),Jb=S("l6","tcp"),Kb=S("l7","dit"),Lb=S("l8","clt"),Mb=S("timingCategory","utc"),Nb=S("timingVar","utv"),Ob=S("timingLabel","utl"),Pb=S("timingValue","utt");
+S("appName","an");S("appVersion","av","");S("appId","aid","");S("appInstallerId","aiid","");S("exDescription","exd");S("exFatal","exf");var Nc=S("expId","xid"),Oc=S("expVar","xvar"),m=S("exp","exp"),Rc=S("_utma","_utma"),Sc=S("_utmz","_utmz"),Tc=S("_utmht","_utmht"),Ua=S("_hc",void 0,0),Xa=S("_ti",void 0,0),Wa=S("_to",void 0,20);cb("dimension([0-9]+)",function(a){return new bb(a[0],"cd"+a[1])});cb("metric([0-9]+)",function(a){return new bb(a[0],"cm"+a[1])});S("linkerParam",void 0,void 0,Bc,db);
+var ld=S("usage","_u"),Gd=S("_um");S("forceSSL",void 0,void 0,function(){return Ba},function(a,b,c){J(34);Ba=!!c});var ed=S("_j1","jid"),ia=S("_j2","gjid");cb("\\&(.*)",function(a){var b=new bb(a[0],a[1]),c=yc(a[0].substring(1));c&&(b.Z=function(a){return a.get(c)},b.o=function(a,b,g,ca){a.set(c,g,ca)},b.F=void 0);return b});
+var Qb=T("_oot"),dd=S("previewTask"),Rb=S("checkProtocolTask"),md=S("validationTask"),Sb=S("checkStorageTask"),Uc=S("historyImportTask"),Tb=S("samplerTask"),Vb=S("_rlt"),Wb=S("buildHitTask"),Xb=S("sendHitTask"),Vc=S("ceTask"),zd=S("devIdTask"),Cd=S("timingTask"),Ld=S("displayFeaturesTask"),oa=S("customTask"),V=T("name"),Q=T("clientId","cid"),n=T("clientIdTime"),xd=T("storedClientId"),Ad=S("userId","uid"),Na=T("trackingId","tid"),U=T("cookieName",void 0,"_ga"),W=T("cookieDomain"),Yb=T("cookiePath",
+void 0,"/"),Zb=T("cookieExpires",void 0,63072E3),Hd=T("cookieUpdate",void 0,!0),$b=T("legacyCookieDomain"),Wc=T("legacyHistoryImport",void 0,!0),ac=T("storage",void 0,"cookie"),bc=T("allowLinker",void 0,!1),cc=T("allowAnchor",void 0,!0),Ka=T("sampleRate","sf",100),dc=T("siteSpeedSampleRate",void 0,1),ec=T("alwaysSendReferrer",void 0,!1),I=T("_gid","_gid"),la=T("_gcn"),Kd=T("useAmpClientId"),ce=T("_gclid"),fe=T("_gt"),he=T("_ge",void 0,7776E6),ie=T("_gclsrc"),je=T("storeGac",void 0,!0),gd=S("transportUrl"),
+Md=S("_r","_r"),qe=S("_dp"),Ud=S("allowAdFeatures",void 0,!0);function X(a,b,c,d){b[a]=function(){try{return d&&J(d),c.apply(this,arguments)}catch(e){throw ge("exc",a,e&&e.name),e;}}};var Od=function(){this.V=100;this.$=this.fa=!1;this.oa="detourexp";this.groups=1},Ed=function(a){var b=new Od,c;if(b.fa&&b.$)return 0;b.$=!0;if(a){if(b.oa&&void 0!==a.get(b.oa))return R(a,b.oa);if(0==a.get(dc))return 0}if(0==b.V)return 0;void 0===c&&(c=Bd());return 0==c%b.V?Math.floor(c/b.V)%b.groups+1:0};function fc(){var a,b;if((b=(b=O.navigator)?b.plugins:null)&&b.length)for(var c=0;c<b.length&&!a;c++){var d=b[c];-1<d.name.indexOf("Shockwave Flash")&&(a=d.description)}if(!a)try{var e=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");a=e.GetVariable("$version")}catch(g){}if(!a)try{e=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"),a="WIN 6,0,21,0",e.AllowScriptAccess="always",a=e.GetVariable("$version")}catch(g){}if(!a)try{e=new ActiveXObject("ShockwaveFlash.ShockwaveFlash"),a=e.GetVariable("$version")}catch(g){}a&&
+(e=a.match(/[\d]+/g))&&3<=e.length&&(a=e[0]+"."+e[1]+" r"+e[2]);return a||void 0};var aa=function(a){var b=Math.min(R(a,dc),100);return La(P(a,Q))%100>=b?!1:!0},gc=function(a){var b={};if(Ec(b)||Fc(b)){var c=b[Eb];void 0==c||Infinity==c||isNaN(c)||(0<c?(Y(b,Gb),Y(b,Jb),Y(b,Ib),Y(b,Fb),Y(b,Hb),Y(b,Kb),Y(b,Lb),va(function(){a(b)},10)):L(O,"load",function(){gc(a)},!1))}},Ec=function(a){var b=O.performance||O.webkitPerformance;b=b&&b.timing;if(!b)return!1;var c=b.navigationStart;if(0==c)return!1;a[Eb]=b.loadEventStart-c;a[Gb]=b.domainLookupEnd-b.domainLookupStart;a[Jb]=b.connectEnd-
+b.connectStart;a[Ib]=b.responseStart-b.requestStart;a[Fb]=b.responseEnd-b.responseStart;a[Hb]=b.fetchStart-c;a[Kb]=b.domInteractive-c;a[Lb]=b.domContentLoadedEventStart-c;return!0},Fc=function(a){if(O.top!=O)return!1;var b=O.external,c=b&&b.onloadT;b&&!b.isValidLoadTime&&(c=void 0);2147483648<c&&(c=void 0);0<c&&b.setPageReadyTime();if(void 0==c)return!1;a[Eb]=c;return!0},Y=function(a,b){var c=a[b];if(isNaN(c)||Infinity==c||0>c)a[b]=void 0},Fd=function(a){return function(b){if("pageview"==b.get(Va)&&
+!a.I){a.I=!0;var c=aa(b),d=0<E(b.get(kb),"gclid").length;(c||d)&&gc(function(b){c&&a.send("timing",b);d&&a.send("adtiming",b)})}}};var hc=!1,mc=function(a){if("cookie"==P(a,ac)){if(a.get(Hd)||P(a,xd)!=P(a,Q)){var b=1E3*R(a,Zb);ma(a,Q,U,b)}ma(a,I,la,864E5);if(a.get(je)){var c=a.get(ce);if(c){var d=Math.min(R(a,he),1E3*R(a,Zb));d=Math.min(d,1E3*R(a,fe)+d-(new Date).getTime());a.data.set(he,d);b={};var e=a.get(fe),g=a.get(ie),ca=kc(P(a,Yb)),l=lc(P(a,W)),k=P(a,Na);g&&"aw.ds"!=g?b&&(b.ua=!0):(c=["1",e,Cc(c)].join("."),0<d&&(b&&(b.ta=!0),zc("_gac_"+Cc(k),c,ca,l,k,d)));le(b)}}else J(75);if(a="none"===lc(P(a,W)))a=M.location.hostname,
+a=eb.test(a)||vc.test(a);a&&J(30)}},ma=function(a,b,c,d){var e=nd(a,b);if(e){c=P(a,c);var g=kc(P(a,Yb)),ca=lc(P(a,W)),l=P(a,Na);if("auto"!=ca)zc(c,e,g,ca,l,d)&&(hc=!0);else{J(32);for(var k=id(),w=0;w<k.length;w++)if(ca=k[w],a.data.set(W,ca),e=nd(a,b),zc(c,e,g,ca,l,d)){hc=!0;return}a.data.set(W,"auto")}}},nc=function(a){if("cookie"==P(a,ac)&&!hc&&(mc(a),!hc))throw"abort";},Yc=function(a){if(a.get(Wc)){var b=P(a,W),c=P(a,$b)||xa(),d=Xc("__utma",c,b);d&&(J(19),a.set(Tc,(new Date).getTime(),!0),a.set(Rc,
+d.R),(b=Xc("__utmz",c,b))&&d.hash==b.hash&&a.set(Sc,b.R))}},nd=function(a,b){b=Cc(P(a,b));var c=lc(P(a,W)).split(".").length;a=jc(P(a,Yb));1<a&&(c+="-"+a);return b?["GA1",c,b].join("."):""},Xd=function(a,b){return na(b,P(a,W),P(a,Yb))},na=function(a,b,c){if(!a||1>a.length)J(12);else{for(var d=[],e=0;e<a.length;e++){var g=a[e];var ca=g.split(".");var l=ca.shift();("GA1"==l||"1"==l)&&1<ca.length?(g=ca.shift().split("-"),1==g.length&&(g[1]="1"),g[0]*=1,g[1]*=1,ca={H:g,s:ca.join(".")}):ca=kd.test(g)?
+{H:[0,0],s:g}:void 0;ca&&d.push(ca)}if(1==d.length)return J(13),d[0].s;if(0==d.length)J(12);else{J(14);d=Gc(d,lc(b).split(".").length,0);if(1==d.length)return d[0].s;d=Gc(d,jc(c),1);1<d.length&&J(41);return d[0]&&d[0].s}}},Gc=function(a,b,c){for(var d=[],e=[],g,ca=0;ca<a.length;ca++){var l=a[ca];l.H[c]==b?d.push(l):void 0==g||l.H[c]<g?(e=[l],g=l.H[c]):l.H[c]==g&&e.push(l)}return 0<d.length?d:e},lc=function(a){return 0==a.indexOf(".")?a.substr(1):a},id=function(){var a=[],b=xa().split(".");if(4==b.length){var c=
+b[b.length-1];if(parseInt(c,10)==c)return["none"]}for(c=b.length-2;0<=c;c--)a.push(b.slice(c).join("."));a.push("none");return a},kc=function(a){if(!a)return"/";1<a.length&&a.lastIndexOf("/")==a.length-1&&(a=a.substr(0,a.length-1));0!=a.indexOf("/")&&(a="/"+a);return a},jc=function(a){a=kc(a);return"/"==a?1:a.split("/").length},le=function(a){a.ta&&J(77);a.na&&J(74);a.pa&&J(73);a.ua&&J(69)};function Xc(a,b,c){"none"==b&&(b="");var d=[],e=Ca(a);a="__utma"==a?6:2;for(var g=0;g<e.length;g++){var ca=(""+e[g]).split(".");ca.length>=a&&d.push({hash:ca[0],R:e[g],O:ca})}if(0!=d.length)return 1==d.length?d[0]:Zc(b,d)||Zc(c,d)||Zc(null,d)||d[0]}function Zc(a,b){if(null==a)var c=a=1;else c=La(a),a=La(D(a,".")?a.substring(1):"."+a);for(var d=0;d<b.length;d++)if(b[d].hash==c||b[d].hash==a)return b[d]};var od=new RegExp(/^https?:\/\/([^\/:]+)/),pd=/(.*)([?&#])(?:_ga=[^&#]*)(?:&?)(.*)/,me=/(.*)([?&#])(?:_gac=[^&#]*)(?:&?)(.*)/;function Bc(a){var b=a.get(Q),c=a.get(I)||"";b="_ga=2."+K(pa(c+b,0)+"."+c+"-"+b);if((c=a.get(ce))&&a.get(je)){var d=R(a,fe);1E3*d+R(a,he)<=(new Date).getTime()?(J(76),a=""):(J(44),a="&_gac=1."+K([pa(c,0),d,c].join(".")))}else a="";return b+a}
+function Ic(a,b){var c=new Date,d=O.navigator,e=d.plugins||[];a=[a,d.userAgent,c.getTimezoneOffset(),c.getYear(),c.getDate(),c.getHours(),c.getMinutes()+b];for(b=0;b<e.length;++b)a.push(e[b].description);return La(a.join("."))}function pa(a,b){var c=new Date,d=O.navigator,e=c.getHours()+Math.floor((c.getMinutes()+b)/60);return La([a,d.userAgent,d.language||"",c.getTimezoneOffset(),c.getYear(),c.getDate()+Math.floor(e/24),(24+e)%24,(60+c.getMinutes()+b)%60].join("."))}
+var Dc=function(a){J(48);this.target=a;this.T=!1};Dc.prototype.ca=function(a,b){if(a.tagName){if("a"==a.tagName.toLowerCase()){a.href&&(a.href=qd(this,a.href,b));return}if("form"==a.tagName.toLowerCase())return rd(this,a)}if("string"==typeof a)return qd(this,a,b)};
+var qd=function(a,b,c){var d=pd.exec(b);d&&3<=d.length&&(b=d[1]+(d[3]?d[2]+d[3]:""));(d=me.exec(b))&&3<=d.length&&(b=d[1]+(d[3]?d[2]+d[3]:""));a=a.target.get("linkerParam");var e=b.indexOf("?");d=b.indexOf("#");c?b+=(-1==d?"#":"&")+a:(c=-1==e?"?":"&",b=-1==d?b+(c+a):b.substring(0,d)+c+a+b.substring(d));b=b.replace(/&+_ga=/,"&_ga=");return b=b.replace(/&+_gac=/,"&_gac=")},rd=function(a,b){if(b&&b.action)if("get"==b.method.toLowerCase()){a=a.target.get("linkerParam").split("&");for(var c=0;c<a.length;c++){var d=
+a[c].split("="),e=d[1];d=d[0];for(var g=b.childNodes||[],ca=!1,l=0;l<g.length;l++)if(g[l].name==d){g[l].setAttribute("value",e);ca=!0;break}ca||(g=M.createElement("input"),g.setAttribute("type","hidden"),g.setAttribute("name",d),g.setAttribute("value",e),b.appendChild(g))}}else"post"==b.method.toLowerCase()&&(b.action=qd(a,b.action))};
+Dc.prototype.S=function(a,b,c){function d(c){try{c=c||O.event;a:{var d=c.target||c.srcElement;for(c=100;d&&0<c;){if(d.href&&d.nodeName.match(/^a(?:rea)?$/i)){var g=d;break a}d=d.parentNode;c--}g={}}("http:"==g.protocol||"https:"==g.protocol)&&sd(a,g.hostname||"")&&g.href&&(g.href=qd(e,g.href,b))}catch(k){J(26)}}var e=this;this.T||(this.T=!0,L(M,"mousedown",d,!1),L(M,"keyup",d,!1));c&&L(M,"submit",function(b){b=b||O.event;if((b=b.target||b.srcElement)&&b.action){var c=b.action.match(od);c&&sd(a,c[1])&&
+rd(e,b)}})};function sd(a,b){if(b==M.location.hostname)return!1;for(var c=0;c<a.length;c++)if(a[c]instanceof RegExp){if(a[c].test(b))return!0}else if(0<=b.indexOf(a[c]))return!0;return!1}function ke(a,b){return b!=Ic(a,0)&&b!=Ic(a,-1)&&b!=Ic(a,-2)&&b!=pa(a,0)&&b!=pa(a,-1)&&b!=pa(a,-2)};var p=/^(GTM|OPT)-[A-Z0-9]+$/,q=/;_gaexp=[^;]*/g,r=/;((__utma=)|([^;=]+=GAX?\d+\.))[^;]*/g,Aa=/^https?:\/\/[\w\-.]+\.google.com(:\d+)?\/optimize\/opt-launch\.html\?.*$/,t=function(a){function b(a,b){b&&(c+="&"+a+"="+K(b))}var c="https://www.google-analytics.com/gtm/js?id="+K(a.id);"dataLayer"!=a.B&&b("l",a.B);b("t",a.target);b("cid",a.clientId);b("cidt",a.ka);b("gac",a.la);b("aip",a.ia);a.sync&&b("m","sync");b("cycle",a.G);a.qa&&b("gclid",a.qa);Aa.test(M.referrer)&&b("cb",String(hd()));return c};var Jd=function(a,b,c){this.aa=b;(b=c)||(b=(b=P(a,V))&&"t0"!=b?Wd.test(b)?"_gat_"+Cc(P(a,Na)):"_gat_"+Cc(b):"_gat");this.Y=b;this.ra=null},Rd=function(a,b){var c=b.get(Wb);b.set(Wb,function(b){Pd(a,b,ed);Pd(a,b,ia);var d=c(b);Qd(a,b);return d});var d=b.get(Xb);b.set(Xb,function(b){var c=d(b);if(se(b)){if(ne()!==H(a,b)){J(80);var e={U:re(a,b,1),google:re(a,b,2),count:0};pe("https://stats.g.doubleclick.net/j/collect",e.U,e)}else ta(re(a,b,0));b.set(ed,"",!0)}return c})},Pd=function(a,b,c){!1===b.get(Ud)||
+b.get(c)||("1"==Ca(a.Y)[0]?b.set(c,"",!0):b.set(c,""+hd(),!0))},Qd=function(a,b){se(b)&&zc(a.Y,"1",b.get(Yb),b.get(W),b.get(Na),6E4)},se=function(a){return!!a.get(ed)&&a.get(Ud)},re=function(a,b,c){var d=new ee,e=function(a){$a(a).F&&d.set($a(a).F,b.get(a))};e(hb);e(ib);e(Na);e(Q);e(ed);if(0==c||1==c)e(Ad),e(ia),e(I);d.set($a(ld).F,Td(b));var g="";d.map(function(a,b){g+=K(a)+"=";g+=K(""+b)+"&"});g+="z="+hd();0==c?g=a.aa+g:1==c?g="t=dc&aip=1&_r=3&"+g:2==c&&(g="t=sr&aip=1&_r=4&slf_rd=1&"+g);return g},
+H=function(a,b){null===a.ra&&(a.ra=1===Ed(b),a.ra&&J(33));return a.ra},Wd=/^gtm\d+$/;var fd=function(a,b){a=a.b;if(!a.get("dcLoaded")){var c=new $c(Dd(a));c.set(29);a.set(Gd,c.w);b=b||{};var d;b[U]&&(d=Cc(b[U]));b=new Jd(a,"https://stats.g.doubleclick.net/r/collect?t=dc&aip=1&_r=3&",d);Rd(b,a);a.set("dcLoaded",!0)}};var Sd=function(a){if(!a.get("dcLoaded")&&"cookie"==a.get(ac)){var b=new Jd(a);Pd(b,a,ed);Pd(b,a,ia);Qd(b,a);if(se(a)){var c=ne()!==H(b,a);a.set(Md,1,!0);c?(J(79),a.set(gd,oe()+"/j/collect",!0),a.set(qe,{U:re(b,a,1),google:re(b,a,2),count:0},!0)):a.set(gd,oe()+"/r/collect",!0)}}};var Lc=function(){var a=O.gaGlobal=O.gaGlobal||{};return a.hid=a.hid||hd()};var ad,bd=function(a,b,c){if(!ad){var d=M.location.hash;var e=O.name,g=/^#?gaso=([^&]*)/;if(e=(d=(d=d&&d.match(g)||e&&e.match(g))?d[1]:Ca("GASO")[0]||"")&&d.match(/^(?:!([-0-9a-z.]{1,40})!)?([-.\w]{10,1200})$/i))zc("GASO",""+d,c,b,a,0),window._udo||(window._udo=b),window._utcp||(window._utcp=c),a=e[1],wa("https://www.google.com/analytics/web/inpage/pub/inpage.js?"+(a?"prefix="+a+"&":"")+hd(),"_gasojs");ad=!0}};var wb=/^(UA|YT|MO|GP)-(\d+)-(\d+)$/,pc=function(a){function b(a,b){d.b.data.set(a,b)}function c(a,c){b(a,c);d.filters.add(a)}var d=this;this.b=new Ya;this.filters=new Ha;b(V,a[V]);b(Na,sa(a[Na]));b(U,a[U]);b(W,a[W]||xa());b(Yb,a[Yb]);b(Zb,a[Zb]);b(Hd,a[Hd]);b($b,a[$b]);b(Wc,a[Wc]);b(bc,a[bc]);b(cc,a[cc]);b(Ka,a[Ka]);b(dc,a[dc]);b(ec,a[ec]);b(ac,a[ac]);b(Ad,a[Ad]);b(n,a[n]);b(Kd,a[Kd]);b(je,a[je]);b(hb,1);b(ib,"j68");c(Qb,Ma);c(oa,ua);c(dd,cd);c(Rb,Oa);c(md,vb);c(Sb,nc);c(Uc,Yc);c(Tb,Ja);c(Vb,Ta);
+c(Vc,Hc);c(zd,yd);c(Ld,Sd);c(Wb,Pa);c(Xb,Sa);c(Cd,Fd(this));Kc(this.b);Jc(this.b,a[Q]);this.b.set(jb,Lc());bd(this.b.get(Na),this.b.get(W),this.b.get(Yb))},Jc=function(a,b){var c=P(a,U);a.data.set(la,"_ga"==c?"_gid":c+"_gid");if("cookie"==P(a,ac)){hc=!1;c=Ca(P(a,U));c=Xd(a,c);if(!c){c=P(a,W);var d=P(a,$b)||xa();c=Xc("__utma",d,c);void 0!=c?(J(10),c=c.O[1]+"."+c.O[2]):c=void 0}c&&(hc=!0);if(d=c&&!a.get(Hd))if(d=c.split("."),2!=d.length)d=!1;else if(d=Number(d[1])){var e=R(a,Zb);d=d+e<(new Date).getTime()/
+1E3}else d=!1;d&&(c=void 0);c&&(a.data.set(xd,c),a.data.set(Q,c),c=Ca(P(a,la)),(c=Xd(a,c))&&a.data.set(I,c));if(a.get(je)&&(c=a.get(ce),d=a.get(ie),!c||d&&"aw.ds"!=d)){c={};if(M){d=[];e=M.cookie.split(";");for(var g=/^\s*_gac_(UA-\d+-\d+)=\s*(.+?)\s*$/,ca=0;ca<e.length;ca++){var l=e[ca].match(g);l&&d.push({ja:l[1],value:l[2]})}e={};if(d&&d.length)for(g=0;g<d.length;g++)(ca=d[g].value.split("."),"1"!=ca[0]||3!=ca.length)?c&&(c.na=!0):ca[1]&&(e[d[g].ja]?c&&(c.pa=!0):e[d[g].ja]=[],e[d[g].ja].push({timestamp:ca[1],
+qa:ca[2]}));d=e}else d={};d=d[P(a,Na)];le(c);d&&0!=d.length&&(c=d[0],a.data.set(fe,c.timestamp),a.data.set(ce,c.qa))}}if(a.get(Hd))a:if(d=be("_ga",a.get(cc)))if(a.get(bc))if(c=d.indexOf("."),-1==c)J(22);else{e=d.substring(0,c);g=d.substring(c+1);c=g.indexOf(".");d=g.substring(0,c);g=g.substring(c+1);if("1"==e){if(c=g,ke(c,d)){J(23);break a}}else if("2"==e){c=g.indexOf("-");e="";0<c?(e=g.substring(0,c),c=g.substring(c+1)):c=g.substring(1);if(ke(e+c,d)){J(53);break a}e&&(J(2),a.data.set(I,e))}else{J(22);
+break a}J(11);a.data.set(Q,c);if(c=be("_gac",a.get(cc)))c=c.split("."),"1"!=c[0]||4!=c.length?J(72):ke(c[3],c[1])?J(71):(a.data.set(ce,c[3]),a.data.set(fe,c[2]),J(70))}else J(21);b&&(J(9),a.data.set(Q,K(b)));a.get(Q)||((b=(b=O.gaGlobal&&O.gaGlobal.vid)&&-1!=b.search(jd)?b:void 0)?(J(17),a.data.set(Q,b)):(J(8),a.data.set(Q,ra())));a.get(I)||(J(3),a.data.set(I,ra()));mc(a)},Kc=function(a){var b=O.navigator,c=O.screen,d=M.location;a.set(lb,ya(a.get(ec),a.get(Kd)));if(d){var e=d.pathname||"";"/"!=e.charAt(0)&&
+(J(31),e="/"+e);a.set(kb,d.protocol+"//"+d.hostname+e+d.search)}c&&a.set(qb,c.width+"x"+c.height);c&&a.set(pb,c.colorDepth+"-bit");c=M.documentElement;var g=(e=M.body)&&e.clientWidth&&e.clientHeight,ca=[];c&&c.clientWidth&&c.clientHeight&&("CSS1Compat"===M.compatMode||!g)?ca=[c.clientWidth,c.clientHeight]:g&&(ca=[e.clientWidth,e.clientHeight]);c=0>=ca[0]||0>=ca[1]?"":ca.join("x");a.set(rb,c);a.set(tb,fc());a.set(ob,M.characterSet||M.charset);a.set(sb,b&&"function"===typeof b.javaEnabled&&b.javaEnabled()||
+!1);a.set(nb,(b&&(b.language||b.browserLanguage)||"").toLowerCase());a.data.set(ce,be("gclid",!0));a.data.set(ie,be("gclsrc",!0));a.data.set(fe,Math.round((new Date).getTime()/1E3));if(d&&a.get(cc)&&(b=M.location.hash)){b=b.split(/[?&#]+/);d=[];for(c=0;c<b.length;++c)(D(b[c],"utm_id")||D(b[c],"utm_campaign")||D(b[c],"utm_source")||D(b[c],"utm_medium")||D(b[c],"utm_term")||D(b[c],"utm_content")||D(b[c],"gclid")||D(b[c],"dclid")||D(b[c],"gclsrc"))&&d.push(b[c]);0<d.length&&(b="#"+d.join("&"),a.set(kb,
+a.get(kb)+b))}};pc.prototype.get=function(a){return this.b.get(a)};pc.prototype.set=function(a,b){this.b.set(a,b)};var qc={pageview:[mb],event:[ub,xb,yb,zb],social:[Bb,Cb,Db],timing:[Mb,Nb,Pb,Ob]};pc.prototype.send=function(a){if(!(1>arguments.length)){if("string"===typeof arguments[0]){var b=arguments[0];var c=[].slice.call(arguments,1)}else b=arguments[0]&&arguments[0][Va],c=arguments;b&&(c=za(qc[b]||[],c),c[Va]=b,this.b.set(c,void 0,!0),this.filters.D(this.b),this.b.data.m={})}};
+pc.prototype.ma=function(a,b){var c=this;u(a,c,b)||(v(a,function(){u(a,c,b)}),y(String(c.get(V)),a,void 0,b,!0))};var rc=function(a){if("prerender"==M.visibilityState)return!1;a();return!0},z=function(a){if(!rc(a)){J(16);var b=!1,c=function(){if(!b&&rc(a)){b=!0;var d=c,e=M;e.removeEventListener?e.removeEventListener("visibilitychange",d,!1):e.detachEvent&&e.detachEvent("onvisibilitychange",d)}};L(M,"visibilitychange",c)}};var td=/^(?:(\w+)\.)?(?:(\w+):)?(\w+)$/,sc=function(a){if(ea(a[0]))this.u=a[0];else{var b=td.exec(a[0]);null!=b&&4==b.length&&(this.c=b[1]||"t0",this.K=b[2]||"",this.C=b[3],this.a=[].slice.call(a,1),this.K||(this.A="create"==this.C,this.i="require"==this.C,this.g="provide"==this.C,this.ba="remove"==this.C),this.i&&(3<=this.a.length?(this.X=this.a[1],this.W=this.a[2]):this.a[1]&&(qa(this.a[1])?this.X=this.a[1]:this.W=this.a[1])));b=a[1];a=a[2];if(!this.C)throw"abort";if(this.i&&(!qa(b)||""==b))throw"abort";
+if(this.g&&(!qa(b)||""==b||!ea(a)))throw"abort";if(ud(this.c)||ud(this.K))throw"abort";if(this.g&&"t0"!=this.c)throw"abort";}};function ud(a){return 0<=a.indexOf(".")||0<=a.indexOf(":")};var Yd,Zd,$d,A;Yd=new ee;$d=new ee;A=new ee;Zd={ec:45,ecommerce:46,linkid:47};
+var u=function(a,b,c){b==N||b.get(V);var d=Yd.get(a);if(!ea(d))return!1;b.plugins_=b.plugins_||new ee;if(b.plugins_.get(a))return!0;b.plugins_.set(a,new d(b,c||{}));return!0},y=function(a,b,c,d,e){if(!ea(Yd.get(b))&&!$d.get(b)){Zd.hasOwnProperty(b)&&J(Zd[b]);if(p.test(b)){J(52);a=N.j(a);if(!a)return!0;c=d||{};d={id:b,B:c.dataLayer||"dataLayer",ia:!!a.get("anonymizeIp"),sync:e,G:!1};a.get("&gtm")==b&&(d.G=!0);var g=String(a.get("name"));"t0"!=g&&(d.target=g);G(String(a.get("trackingId")))||(d.clientId=
+String(a.get(Q)),d.ka=Number(a.get(n)),c=c.palindrome?r:q,c=(c=M.cookie.replace(/^|(; +)/g,";").match(c))?c.sort().join("").substring(1):void 0,d.la=c,d.qa=E(a.b.get(kb)||"","gclid"));a=d.B;c=(new Date).getTime();O[a]=O[a]||[];c={"gtm.start":c};e||(c.event="gtm.js");O[a].push(c);c=t(d)}!c&&Zd.hasOwnProperty(b)?(J(39),c=b+".js"):J(43);c&&(c&&0<=c.indexOf("/")||(c=(Ba||"https:"==M.location.protocol?"https:":"http:")+"//www.google-analytics.com/plugins/ua/"+c),d=ae(c),a=d.protocol,c=M.location.protocol,
+("https:"==a||a==c||("http:"!=a?0:"http:"==c))&&B(d)&&(wa(d.url,void 0,e),$d.set(b,!0)))}},v=function(a,b){var c=A.get(a)||[];c.push(b);A.set(a,c)},C=function(a,b){Yd.set(a,b);b=A.get(a)||[];for(var c=0;c<b.length;c++)b[c]();A.set(a,[])},B=function(a){var b=ae(M.location.href);if(D(a.url,"https://www.google-analytics.com/gtm/js?id="))return!0;if(a.query||0<=a.url.indexOf("?")||0<=a.path.indexOf("://"))return!1;if(a.host==b.host&&a.port==b.port)return!0;b="http:"==a.protocol?80:443;return"www.google-analytics.com"==
+a.host&&(a.port||b)==b&&D(a.path,"/plugins/")?!0:!1},ae=function(a){function b(a){var b=(a.hostname||"").split(":")[0].toLowerCase(),c=(a.protocol||"").toLowerCase();c=1*a.port||("http:"==c?80:"https:"==c?443:"");a=a.pathname||"";D(a,"/")||(a="/"+a);return[b,""+c,a]}var c=M.createElement("a");c.href=M.location.href;var d=(c.protocol||"").toLowerCase(),e=b(c),g=c.search||"",ca=d+"//"+e[0]+(e[1]?":"+e[1]:"");D(a,"//")?a=d+a:D(a,"/")?a=ca+a:!a||D(a,"?")?a=ca+e[2]+(a||g):0>a.split("/")[0].indexOf(":")&&
+(a=ca+e[2].substring(0,e[2].lastIndexOf("/"))+"/"+a);c.href=a;d=b(c);return{protocol:(c.protocol||"").toLowerCase(),host:d[0],port:d[1],path:d[2],query:c.search||"",url:a||""}};var Z={ga:function(){Z.f=[]}};Z.ga();Z.D=function(a){var b=Z.J.apply(Z,arguments);b=Z.f.concat(b);for(Z.f=[];0<b.length&&!Z.v(b[0])&&!(b.shift(),0<Z.f.length););Z.f=Z.f.concat(b)};Z.J=function(a){for(var b=[],c=0;c<arguments.length;c++)try{var d=new sc(arguments[c]);d.g?C(d.a[0],d.a[1]):(d.i&&(d.ha=y(d.c,d.a[0],d.X,d.W)),b.push(d))}catch(e){}return b};
+Z.v=function(a){try{if(a.u)a.u.call(O,N.j("t0"));else{var b=a.c==gb?N:N.j(a.c);if(a.A){if("t0"==a.c&&(b=N.create.apply(N,a.a),null===b))return!0}else if(a.ba)N.remove(a.c);else if(b)if(a.i){if(a.ha&&(a.ha=y(a.c,a.a[0],a.X,a.W)),!u(a.a[0],b,a.W))return!0}else if(a.K){var c=a.C,d=a.a,e=b.plugins_.get(a.K);e[c].apply(e,d)}else b[a.C].apply(b,a.a)}}catch(g){}};var N=function(a){J(1);Z.D.apply(Z,[arguments])};N.h={};N.P=[];N.L=0;N.answer=42;var uc=[Na,W,V];
+N.create=function(a){var b=za(uc,[].slice.call(arguments));b[V]||(b[V]="t0");var c=""+b[V];if(N.h[c])return N.h[c];a:{if(b[Kd]){J(67);if(b[ac]&&"cookie"!=b[ac]){var d=!1;break a}if(void 0!==Ab)b[Q]||(b[Q]=Ab);else{b:{d=String(b[W]||xa());var e=String(b[Yb]||"/"),g=Ca(String(b[U]||"_ga"));d=na(g,d,e);if(!d||jd.test(d))d=!0;else if(d=Ca("AMP_TOKEN"),0==d.length)d=!0;else{if(1==d.length&&(d=decodeURIComponent(d[0]),"$RETRIEVING"==d||"$OPT_OUT"==d||"$ERROR"==d||"$NOT_FOUND"==d)){d=!0;break b}d=!1}}if(d&&
+tc(ic,String(b[Na]))){d=!0;break a}}}d=!1}if(d)return null;b=new pc(b);N.h[c]=b;N.P.push(b);return b};N.remove=function(a){for(var b=0;b<N.P.length;b++)if(N.P[b].get(V)==a){N.P.splice(b,1);N.h[a]=null;break}};N.j=function(a){return N.h[a]};N.getAll=function(){return N.P.slice(0)};
+N.N=function(){"ga"!=gb&&J(49);var a=O[gb];if(!a||42!=a.answer){N.L=a&&a.l;N.loaded=!0;var b=O[gb]=N;X("create",b,b.create);X("remove",b,b.remove);X("getByName",b,b.j,5);X("getAll",b,b.getAll,6);b=pc.prototype;X("get",b,b.get,7);X("set",b,b.set,4);X("send",b,b.send);X("requireSync",b,b.ma);b=Ya.prototype;X("get",b,b.get);X("set",b,b.set);if("https:"!=M.location.protocol&&!Ba){a:{b=M.getElementsByTagName("script");for(var c=0;c<b.length&&100>c;c++){var d=b[c].src;if(d&&0==d.indexOf("https://www.google-analytics.com/analytics")){b=
+!0;break a}}b=!1}b&&(Ba=!0)}(O.gaplugins=O.gaplugins||{}).Linker=Dc;b=Dc.prototype;C("linker",Dc);X("decorate",b,b.ca,20);X("autoLink",b,b.S,25);C("displayfeatures",fd);C("adfeatures",fd);a=a&&a.q;ka(a)?Z.D.apply(N,a):J(50)}};N.da=function(){for(var a=N.getAll(),b=0;b<a.length;b++)a[b].get(V)};var da=N.N,Nd=O[gb];Nd&&Nd.r?da():z(da);z(function(){Z.D(["provide","render",ua])});function La(a){var b=1,c;if(a)for(b=0,c=a.length-1;0<=c;c--){var d=a.charCodeAt(c);b=(b<<6&268435455)+d+(d<<14);d=b&266338304;b=0!=d?b^d>>21:b}return b};})(window);

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
SQL-Hybrid-Cloud-Toolkit/assets/js/lunr/lunr.min.js


+ 150 - 0
SQL-Hybrid-Cloud-Toolkit/assets/js/scripts.js

@@ -0,0 +1,150 @@
+/**
+ * Site-wide JS that sets up:
+ *
+ * [1] MathJax rendering on navigation
+ * [2] Sidebar toggling
+ * [3] Sidebar scroll preserving
+ * [4] Keyboard navigation
+ * [5] Right sidebar scroll highlighting
+ */
+
+const togglerId = 'js-sidebar-toggle'
+const textbookId = 'js-textbook'
+const togglerActiveClass = 'is-active'
+const textbookActiveClass = 'js-show-sidebar'
+const mathRenderedClass = 'js-mathjax-rendered'
+const icon_path = document.location.origin + `${site_basename}assets`;
+
+const getToggler = () => document.getElementById(togglerId)
+const getTextbook = () => document.getElementById(textbookId)
+
+// [1] Run MathJax when Turbolinks navigates to a page.
+// When Turbolinks caches a page, it also saves the MathJax rendering. We mark
+// each page with a CSS class after rendering to prevent double renders when
+// navigating back to a cached page.
+document.addEventListener('turbolinks:load', () => {
+  const textbook = getTextbook()
+  if (window.MathJax && !textbook.classList.contains(mathRenderedClass)) {
+    MathJax.Hub.Queue(['Typeset', MathJax.Hub])
+    textbook.classList.add(mathRenderedClass)
+  }
+})
+
+/**
+ * [2] Toggles sidebar and menu icon
+ */
+const toggleSidebar = () => {
+  const toggler = getToggler()
+  const textbook = getTextbook()
+
+  if (textbook.classList.contains(textbookActiveClass)) {
+    textbook.classList.remove(textbookActiveClass)
+    toggler.classList.remove(togglerActiveClass)
+  } else {
+    textbook.classList.add(textbookActiveClass)
+    toggler.classList.add(togglerActiveClass)
+  }
+}
+
+/**
+ * Keep the variable below in sync with the tablet breakpoint value in
+ * _sass/inuitcss/tools/_tools.mq.scss
+ *
+ */
+const autoCloseSidebarBreakpoint = 740
+
+// Set up event listener for sidebar toggle button
+const sidebarButtonHandler = () => {
+  getToggler().addEventListener('click', toggleSidebar)
+
+  /**
+   * Auto-close sidebar on smaller screens after page load.
+   *
+   * Having the sidebar be open by default then closing it on page load for
+   * small screens gives the illusion that the sidebar closes in response
+   * to selecting a page in the sidebar. However, it does cause a bit of jank
+   * on the first page load.
+   *
+   * Since we don't want to persist state in between page navigation, this is
+   * the best we can do while optimizing for larger screens where most
+   * viewers will read the textbook.
+   *
+   * The code below assumes that the sidebar is open by default.
+   */
+  if (window.innerWidth < autoCloseSidebarBreakpoint) toggleSidebar()
+}
+
+initFunction(sidebarButtonHandler);
+
+/**
+ * [3] Preserve sidebar scroll when navigating between pages
+ */
+let sidebarScrollTop = 0
+const getSidebar = () => document.getElementById('js-sidebar')
+
+document.addEventListener('turbolinks:before-visit', () => {
+  sidebarScrollTop = getSidebar().scrollTop
+})
+
+document.addEventListener('turbolinks:load', () => {
+  getSidebar().scrollTop = sidebarScrollTop
+})
+
+/**
+ * Focus textbook page by default so that user can scroll with spacebar
+ */
+const focusPage = () => {
+  document.querySelector('.c-textbook__page').focus()
+}
+
+initFunction(focusPage);
+
+/**
+ * [4] Use left and right arrow keys to navigate forward and backwards.
+ */
+const LEFT_ARROW_KEYCODE = 37
+const RIGHT_ARROW_KEYCODE = 39
+
+const getPrevUrl = () => document.getElementById('js-page__nav__prev').href
+const getNextUrl = () => document.getElementById('js-page__nav__next').href
+const initPageNav = (event) => {
+  const keycode = event.which
+
+  if (keycode === LEFT_ARROW_KEYCODE) {
+    Turbolinks.visit(getPrevUrl())
+  } else if (keycode === RIGHT_ARROW_KEYCODE) {
+    Turbolinks.visit(getNextUrl())
+  }
+};
+
+var keyboardListener = false;
+const initListener = () => {
+  if (keyboardListener === false) {
+    document.addEventListener('keydown', initPageNav)
+    keyboardListener = true;
+  }
+}
+initFunction(initListener);
+
+/**
+ * [5] Right sidebar scroll highlighting
+ */
+
+highlightRightSidebar = function() {
+  var position = document.querySelector('.c-textbook__page').scrollTop;
+  position = position + (window.innerHeight / 4);  // + Manual offset
+
+  // Highlight the "active" menu item
+  document.querySelectorAll('.c-textbook__content h2, .c-textbook__content h3').forEach((header, index) => {
+      var target = header.offsetTop;
+      var id = header.id;
+      if (position >= target) {
+        var query = 'ul.toc__menu a[href="#' + id + '"]';
+        document.querySelectorAll('ul.toc__menu li').forEach((item) => {item.classList.remove('active')});
+        document.querySelectorAll(query).forEach((item) => {item.parentElement.classList.add('active')});
+    }
+  });
+  document.querySelector('.c-textbook__page').addEventListener('scroll', highlightRightSidebar);
+};
+
+initFunction(highlightRightSidebar);

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
SQL-Hybrid-Cloud-Toolkit/assets/js/tocbot.min.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 4 - 0
SQL-Hybrid-Cloud-Toolkit/assets/js/turbolinks.js


BIN=BIN
SQL-Hybrid-Cloud-Toolkit/content/Assessments/CMS.png


+ 96 - 0
SQL-Hybrid-Cloud-Toolkit/content/Assessments/compatibility-assessment.ipynb

@@ -0,0 +1,96 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# Migration Compatibility Assessment\n",
+                "Use dmacmd.exe to assess databases in an unattended mode, and output the result to JSON or CSV file. This method is especially useful when assessing several databases or huge databases.\n",
+                "\n",
+                "## Notebook Variables\n",
+                "\n",
+                "| Line | Variable | Description |\n",
+                "| --- | --- | --- |\n",
+                "| 1 | ExecutableFile | Path to DmaCmd.exe file, usually _\"C:\\\\Program Files\\\\Microsoft Data Migration Assistant\\\\DmaCmd.exe\"_ if installed to default location |\n",
+                "| 2 | AssessmentName | Unique name for assessment |\n",
+                "| 3 | Server | Target SQL Server |\n",
+                "| 4 | InitialCatalog | Name of the database for the specified server |\n",
+                "| 5 | ResultPath | Path and name of the file to store results in json format |"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6764dd37-fb1f-400d-8f2b-70bc36fc3b61"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$ExecutableFile = \"C:\\Program Files\\Microsoft Data Migration Assistant\\DmaCmd.exe\"  # Update if different\r\n",
+                "$AssessmentName = \"\"\r\n",
+                "$Server         = \"\"\r\n",
+                "$InitialCatalog = \"\"\r\n",
+                "$ResultPath     = \"\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d81972c1-3b0b-47d9-b8a3-bc5ab4001a34"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### **Single-database assessment using Windows authentication and running compatibility rules**\r\n",
+                "\r\n",
+                "This command assess the single databse using authentication as Windows and save the result in specified path.\r\n",
+                "It requires Sql Server name & Database name in order to assess"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "146181e8-c0e5-4fab-b4a4-40276762ff9f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "& $ExecutableFile  /AssessmentName=$AssessmentName  /AssessmentDatabases=\"Server=$Server;Initial Catalog=$InitialCatalog;Integrated Security=true\"  /AssessmentEvaluateCompatibilityIssues /AssessmentOverwriteResult /AssessmentResultJson=$ResultPath"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c3abe0ea-e971-4058-9c85-9f2d909a9291"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "_Get-Content_ cmdlet gets the content of the item at the location specified by the path, such as the text in a file or the content of a function. For files, the content is read one line at a time and returns a collection of objects, each of which represents a line of content."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4b1c8811-8b78-4543-bfa3-f4879510795b"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-Content -Path $ResultPath"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7e69969a-0d53-4a00-830c-3ed41318234a"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 10 - 0
SQL-Hybrid-Cloud-Toolkit/content/Assessments/readme.md

@@ -0,0 +1,10 @@
+# Assessments
+
+[Home](../readme.md)
+
+Preparing for the cloud requires a crawl-walk-run mentality. The first step, or crawl, towards hybrid migration is determining the fitness of existing on-premise resources. An assessment is an analysis performed against a chosen SQL Server object such as a Server or Database instance. It is recommended to fix any issues found by the analysis prior to migrating a database from on-premise to Azure. 
+
+## Notebooks in this Chapter
+- [SQL Server Best Practices Assessment](sql-server-assessment.ipynb) - demonstrates the use of the [SQL Server Assessment API](https://docs.microsoft.com/en-us/sql/sql-assessment-api/sql-assessment-api-overview), a tool to review the configuration of a SQL Server and Databases for best practices. 
+
+- [Compatibility Assessment](compatibility-assessment.ipynb) - Analzye an on-premises SQL Server instance or database for compatibility for migration to SQL Azure. The assessment will provide guidance on features not currently supported in Azure and remediation actions that can be taken to prepare for migration.

+ 207 - 0
SQL-Hybrid-Cloud-Toolkit/content/Assessments/sql-server-assessment.ipynb

@@ -0,0 +1,207 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# SQL Server Assessment Tool\n",
+                "\n",
+                "Unlike other notebooks, **do not execute all cells of this notebook!** \n",
+                "\n",
+                "A single assessment may take awhile so fill out the variables and execute the cell that matches the desired environment to perform the assessment needed. Only one of these cells needs to be executed after the variables are defined.\n",
+                "\n",
+                "## Notebook Variables\n",
+                "\n",
+                "| Line | Variable | Description |\n",
+                "| ---- | -------- | ----------- |\n",
+                "| 1 | ServerInstance | Name of the SQL Server instance |\n",
+                "| 2 | Group | (Optional) Name of the server group, if known | "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "86ecfb01-8c38-4a99-92a8-687d8ec7f4b0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$ServerInstance = \"\"\r\n",
+                "$Group          = \"\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "db21129e-9bda-4db9-8d61-d2b264a3cad8"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Notebook Steps\r\n",
+                "1.  Ensure that the proper APIs and modules are installed per the <a href=\"../prereqs.ipynb\">prerequisites</a> notebook\r\n",
+                "2.  Define a service instance and group corresponding to the SQL Server instances to be assessed\r\n",
+                "3.  Choose an example below that corresponds to the appropriate task\r\n",
+                "4.  Execute only that example's code block and wait for results\r\n",
+                "5.  Fix any recommended issues and rerun Assessment API until clear"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "541f6806-f8d2-4fc5-a8fb-6d42947d1a64"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Example 1 Get-SqlInstance\r\n",
+                "Pipe the output of the Get-SqlInstance cmdlet to the _Get-SqlAssessmentItem_ cmdlet to pass the instance object to it to perform an assessment on the entire instance. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c6f94c36-0566-4963-acb8-4a419758d26e"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-SqlInstance -ServerInstance $serverInstance | Get-SqlAssessmentItem"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "420e135e-0190-476b-812d-f716ec619ed3"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Example 2 - Get-Item\r\n",
+                "Get a list of available databases in the instance using the _Get-Item_ cmdlet and pipe it to _Get-SqlAssessmentItem_. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "30bcf8ed-cfd4-4e3b-b634-acdafab13437"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-Item SQLSERVER:\\SQL\\$serverInstance\\default | Get-SqlAssessmentItem"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4525cbe7-719a-4cc3-8ebd-5279731c3979"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Example 3 - Get-SqlDatabase\r\n",
+                "Or, use the _Get-SqlDatabase_ cmdlet without specifying a name to get a full list of database objects."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "def37aca-fe6e-48ad-9794-09fd9ba77740"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-SqlDatabase -ServerInstance $serverInstance | Get-SqlAssessmentItem"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "da57fa74-86b1-4d2f-a419-4035c10f0d3a"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Example 4 - Export Assessment to SQL Table\r\n",
+                "Invoke assessment for the instance and save the results to a SQL table by piping the output of the _Get-SqlInstance_ cmdlet to the _Invoke-SqlAssessment_ cmdlet. The results are piped to the _Write-SqlTableData_ cmdlet. The _Invoke-Assessment_ cmdlet is run with the -**FlattenOutput** parameter in this example. This parameter makes the output suitable for the _Write-SqlTableData_ cmdlet. The latter raises an error if the parameter is omitted."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "aeaa588a-a3a2-4bc3-9b4b-794427b77649"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-SqlInstance -ServerInstance $serverInstance |\r\n",
+                "Invoke-SqlAssessment -FlattenOutput |\r\n",
+                "Write-SqlTableData -ServerInstance $serverInstance -DatabaseName SQLAssessmentDemo -SchemaName Assessment -TableName Results -Force"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "28ce8df2-1da8-4462-8e91-62646642d4b1"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Example 5 - SQL Server (SSMS) Locally Registered Servers\r\n",
+                "To gather locally saved SQL registered servers, use the SQL Server PowerShell module installed from the [Pre-requisites notebook](..\\prereqs.ipynb). "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "3a14a231-2ab3-45db-9afa-fbd369f4ec0f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Import-Module SqlServer\r\n",
+                "$RegisteredServers = Get-ChildItem SQLSERVER:\\SQLRegistration -Recurse | Where-Object {$_.IsLocal -eq $True}\r\n",
+                "$RegisteredServers.Refresh() \r\n",
+                "$RegisteredServers | Format-List -Property *"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c373d5c0-3eaa-4196-8cd4-2e7d9d1896a1",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Example 5 - Specifying a Central Management Server\r\n",
+                "Many large organizations use [Registered Servers](https://docs.microsoft.com/en-us/sql/ssms/register-servers/register-servers) to organize their networks within editors like SSMS or ADS. To take advantage of running the SQL Server Assessment API against all or a specific Registered Server Group, specify the CMS instance below. \r\n",
+                "\r\n",
+                "![](CMS.png)\r\n",
+                "\r\n",
+                "Take advantage of the [dbatools CMS module](https://dbatools.io/cms/) for this task. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cd3b800e-003f-432c-88aa-5738aba11378"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Get a list of all servers stored on the CMS instance \r\n",
+                "# Note that this list is pipable to all dbatools commands\r\n",
+                "\r\n",
+                "if($SqlInstance -And $Group){\r\n",
+                "  Get-DbaRegServer -SqlInstance $SqlInstance -Group $Group\r\n",
+                "}else{\r\n",
+                "  Get-DbaRegServer -SqlInstance $SqlInstance\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4a7bd412-5099-47b4-a737-455e01b0c172"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 78 - 0
SQL-Hybrid-Cloud-Toolkit/content/ConnectionDialogue.ipynb

@@ -0,0 +1,78 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "python3",
+            "display_name": "Python 3"
+        },
+        "language_info": {
+            "name": "python",
+            "version": "3.6.6",
+            "mimetype": "text/x-python",
+            "codemirror_mode": {
+                "name": "ipython",
+                "version": 3
+            },
+            "pygments_lexer": "ipython3",
+            "nbconvert_exporter": "python",
+            "file_extension": ".py"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "code",
+            "source": [
+                "import pandas,sys,os,getpass,json,html,time\r\n",
+                "from string import Template"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "1887c716-6e0c-41d1-9d67-cfa93884c0d6"
+            },
+            "outputs": [],
+            "execution_count": 1
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "sql_password = \"\"\r\n",
+                "sql_port = \"\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f3de6ea8-1ea8-43d6-9277-836b57d85845"
+            },
+            "outputs": [],
+            "execution_count": 2
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "from IPython.display import *\r\n",
+                "connectionParameter = '{\"serverName\":\"localhost,' + sql_port + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":\"sa\",\"password\":' + json.dumps(sql_password) + '}'\r\n",
+                "display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server</font></a><br/>'))\r\n",
+                "display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8e5e0b41-a27d-4a73-9ba6-c0d3bd7a9a2f"
+            },
+            "outputs": [
+{
+    "data": {
+        "text/plain": "<IPython.core.display.HTML object>",
+        "text/html": "<br/><a href=\"command:azdata.connect?{&quot;serverName&quot;:&quot;localhost,&quot;,&quot;providerName&quot;:&quot;MSSQL&quot;,&quot;authenticationType&quot;:&quot;SqlLogin&quot;,&quot;userName&quot;:&quot;sa&quot;,&quot;password&quot;:&quot;&quot;}\"><font size=\"3\">Click here to connect to SQL Server</font></a><br/>"
+    },
+    "metadata": {},
+    "output_type": "display_data"
+}, {
+    "data": {
+        "text/plain": "<IPython.core.display.HTML object>",
+        "text/html": "<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>"
+    },
+    "metadata": {},
+    "output_type": "display_data"
+}
+],
+            "execution_count": 3
+        }
+    ]
+}

+ 5 - 0
SQL-Hybrid-Cloud-Toolkit/content/LICENSE.md

@@ -0,0 +1,5 @@
+# License for this book
+
+All content in this book (ie, any files and content in the `content/` folder)
+is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/)
+(CC BY-SA 4.0) license.

+ 35 - 0
SQL-Hybrid-Cloud-Toolkit/content/appendices.md

@@ -0,0 +1,35 @@
+# Appendices
+[Home](readme.md)
+
+## Appendix: Locations
+See the <a href="https://azure.microsoft.com/en-us/global-infrastructure/locations/">Azure locations</a> page for a complete list of Azure regions along with their general physical location. The following is a list of common North American location settings for this guide:
+
+### Regions
+| Setting | Location |
+| ------------ | --------- |
+| Central US | Iowa |
+| East US | Virginia |
+| East US 2 | Virginia |
+| North Central US | Illinois |
+| South Central US | Texas |
+| West US 2 | Washington |
+| West Central US | Wyoming |
+| West US | California | 
+| Canada Central | Toronto |
+| Canada East | Quebec City |
+| Brazil South | Sao Paulo |
+| Mexico Central | Queretaro |
+
+## Appendix: Storage SKUs
+Use these as values for provisioning storage skus. 
+
+Data for table taken from <a href="https://docs.microsoft.com/en-us/rest/api/storagerp/srp_sku_types">SKU Types</a> page but is subject to change. Not all skus are listed here. SKU type names are case-sensitive.
+
+| Name | Description |
+| -----| ----------- |
+| Standard_LRS | standard locally redundant storage |
+| Standard_GRS | standard geo-replicated storage |
+| Standard_RAGRS | standard read-access geo replicated storage |
+| Standard_ZRS | standard zone redundant storage |
+| Premium_LRS | Premium - provisioned IO locally redundant |
+| Premium_ZRS | Premium - provisioned IO locally redundant |

BIN=BIN
SQL-Hybrid-Cloud-Toolkit/content/data-portability/VisualBootstrapperNB.PNG


+ 560 - 0
SQL-Hybrid-Cloud-Toolkit/content/data-portability/export-sql-server.ipynb

@@ -0,0 +1,560 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# Export Existing Azure SQL Server Resources\r\n",
+                "Export notebook that will utilize the ADP resources\r\n",
+                "\r\n",
+                "## Notebook Variables\r\n",
+                "| Line | Variable | Description |\r\n",
+                "| -- | -- | -- |\r\n",
+                "| 1 | AdpSubscription | Azure Subscription ID/Name for the ADP Resource Group # Both RG are assumed to be in the same subscription |\r\n",
+                "| 2 | AdpResourceGroup | Azure Resource Group which contains the ADP Resources | \r\n",
+                "| 3 | SourceResourceGroup | Azure ResourceGroup where the sql server to be exported exists | \r\n",
+                "| 4 | LogicalSQLServerName | Logical sql server name of the sql server to be exported | \r\n",
+                "| 5 | StorageAccount | target storage account to store exported files # any storage account, but must be in the same RG as the ADP resources | \r\n",
+                "| 6 | AdpFunc | |\r\n",
+                "| 7 | AdpBatch | | \r\n",
+                "| 8 | AdpVNET | | "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b72d138a-566f-4161-b7a6-7264487e446c"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$AdpSubscription    = \"\"\r\n",
+                "$AdpResourceGroup   = \"\"\r\n",
+                "$SourceResourceGroup= \"\"\r\n",
+                "$LogicalSQLServer   = \"\"\r\n",
+                "$StorageAccount     = \"\"\r\n",
+                "$AdpFunc            = $AdpResourceGroup + \"Control\"\r\n",
+                "$AdpBatch           = $AdpResourceGroup.ToLower() + \"batch\"\r\n",
+                "$AdpVNET            = $AdpResourceGroup + \"Vnet\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "417edc0e-1107-4a27-a4cf-e921f79b3f6a",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Steps\r\n",
+                "Gather input:\r\n",
+                "* [ ] Connect to Azure Subscription\r\n",
+                "* [ ] Choose Resource Group (Read access required)\r\n",
+                "* [ ] Choose Microsoft SQL Server Resources to export\r\n",
+                "* [ ] Choose/Create Migration Storage \r\n",
+                "* [ ] Install Application + Data Portability function (orchestrator service)\r\n",
+                "* [ ] Install ADP Azure Batch processing pipeline\r\n",
+                "* [ ] Store SqlPackage.exe in Migration Storage for orchestrator to hand to Az Batch\r\n",
+                "\r\n",
+                "Execute:\r\n",
+                "* [ ] Check all pre-requisites\r\n",
+                "* [ ] Kick off orchestrator service\r\n",
+                "\r\n",
+                "Monitor:\r\n",
+                "* [ ] Check export status."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a9da248a-20f1-4574-bd04-7324e70c05a3"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Notebook Functions\r\n",
+                "Defines logical functions for the rest of the notebook. Function blocks are combined in a single cell that can be collapsed for readability or expanded for further examination. Nothing is executed until called later in the notebook. As a result, this cell is a requirement for any of the other cells below it. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b70909ed-1863-4882-bacc-a9956993268e"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Expand cell to view framework\r\n",
+                "\r\n",
+                "function Login-Azure\r\n",
+                "{    \r\n",
+                "    # query azure locations to test for existing az login session exists with valid access tocken\r\n",
+                "    $azureLocations = az account list-locations -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    \r\n",
+                "    if (!$azureLocations){ #If there are no az locations, there is no existing az login session\r\n",
+                "        $subscriptions = az login -o JSON | ConvertFrom-Json      # Login   \r\n",
+                "    }\r\n",
+                "    else {\r\n",
+                "        $subscriptions = az account list -o JSON | ConvertFrom-Json # getting subscriptions for the user to use in gridview\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    if(![string]::IsNullOrWhiteSpace($AdpSubscription)) #If there is a subscription specified by user in the variables section\r\n",
+                "    {\r\n",
+                "        $specified_Subscription= az account show --subscription $AdpSubscription -o json |ConvertFrom-Json \r\n",
+                "        if (!$specified_Subscription) #if specified subscription is not valid\r\n",
+                "        {       \r\n",
+                "            $currentUser= az ad signed-in-user show --query \"{displayName:displayName,UPN:userPrincipalName}\" -o json|ConvertFrom-Json # get current logged in user infomration\r\n",
+                "            Write-Host \"Refer below for the list of subscriptions for logged in account '$($currentUser.UPN)'`n\"\r\n",
+                "            az account list --query \"[].{Name:name,SubscriptionID:id}\" -o table          # list subscriptions under current logged in account\r\n",
+                "        }\r\n",
+                "        else { # if specified subscription is valid\r\n",
+                "            Write-Output \"Using subscription... '$($specified_Subscription.name)' ... '$($specified_Subscription.id)'\" \r\n",
+                "        }\r\n",
+                "    }\r\n",
+                "    else { # if no subscription is specified, users are given a gridview to select subscription from\r\n",
+                "\r\n",
+                "        $selectedSubscription = $subscriptions | Select-Object -Property Name, Id | Out-GridView -PassThru\r\n",
+                "        $SubscriptionId = $selectedSubscription.Id\r\n",
+                "        $Subscription = $selectedSubscription.Name   \r\n",
+                "        $AdpSubscription = $subscription  \r\n",
+                "        Write-Output \"Using subscription... '$AdpSubscription' ... '$SubscriptionId'\"    \r\n",
+                "    }    \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Verify-ADPResources\r\n",
+                "{    \r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ADPResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$FunctionName,  \r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BatchAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VNetName,\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ApplicationName=\"SqlPackageWrapper\",  \r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ApplicationPackageVersionName=\"1\",\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$SubNetName=\"default\" \r\n",
+                "    )    \r\n",
+                "\r\n",
+                "# validate Subscription\r\n",
+                "$specified_Subscription= az account show --subscription $Subscription -o json | ConvertFrom-Json\r\n",
+                "if(!$specified_Subscription){\r\n",
+                "    $currentUser= az ad signed-in-user show --query \"{displayName:displayName,UPN:userPrincipalName}\" -o json|ConvertFrom-Json # get current logged in user information\r\n",
+                "    Write-Host \"Refer below for the list of subscriptions for logged in account '$($currentUser.UPN)'`n\"\r\n",
+                "    az account list --query \"[].{Name:name,SubscriptionID:id}\" -o table          # list subscriptions under current logged in account\r\n",
+                "    return } \r\n",
+                "# validate ResourceGroup    \r\n",
+                "$specified_ResourceGroup= az group show -n $ADPResourceGroupName --subscription $Subscription -o json | ConvertFrom-Json\r\n",
+                "if(!$specified_ResourceGroup) {        \r\n",
+                "        return\r\n",
+                "        } \r\n",
+                "\r\n",
+                "$Installed = [ordered]@{} # ordered hash to store status of installation\r\n",
+                "$countError=0\r\n",
+                "\r\n",
+                "#Verify if VNet exists       \r\n",
+                "$specified_VNet= az network vnet show -n $VNetName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null  |ConvertFrom-Json   \r\n",
+                "if(!$specified_VNet) {\r\n",
+                "    $Installed['VNET']=\"Not Found\"\r\n",
+                "    $countError++\r\n",
+                "}   \r\n",
+                "else { \r\n",
+                "    $existingVnetSubnet =  az network vnet subnet show -n $SubNetName --vnet-name $VNetName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null |ConvertFrom-Json\r\n",
+                "    if(!$existingVnetSubnet){\r\n",
+                "        $Installed['VNET']=\"Default Subnet under\"+ $VNetName + \"Not Found\"\r\n",
+                "        $countError++\r\n",
+                "        }\r\n",
+                "    else {\r\n",
+                "    $Installed['VNET']=\"Installed\"\r\n",
+                "    }\r\n",
+                " }\r\n",
+                "\r\n",
+                "#Verify if FunctionApp Exists\r\n",
+                "$specified_FunctionApp = az functionapp show -n $FunctionName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "if(!$specified_FunctionApp)\r\n",
+                "{\r\n",
+                "    $Installed['FunctionApp']=\"Not Installed\"\r\n",
+                "    $countError++\r\n",
+                "}\r\n",
+                "else\r\n",
+                "{\r\n",
+                "    $Installed['FunctionApp']=\"Installed\"\r\n",
+                "} \r\n",
+                "\r\n",
+                "#check if Batch account exists\r\n",
+                "$specified_BatchAccount = az batch account show -n $BatchAccountName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "if(!$specified_BatchAccount)\r\n",
+                "{\r\n",
+                "    $Installed['Batch']=\"Not Installed\"\r\n",
+                "    $countError++\r\n",
+                "}\r\n",
+                "else\r\n",
+                "{\r\n",
+                "    $appPackageInstalled = az batch application package show --application-name $ApplicationName --version-name $ApplicationPackageVersionName -n $BatchAccountName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $connectedToStorage= $specified_BatchAccount.autoStorage \r\n",
+                "    if($connectedToStorage -and $appPackageInstalled){ # BatchAccount connected to storageaccount and  applicationpackage is installed\r\n",
+                "            $Installed['Batch']=\"Installed\"\r\n",
+                "            $Installed['Batch_ApplicationPackage']=\"Installed\"\r\n",
+                "            $Installed['Batch_StorageAccount']=\"Connected to storage- \"+$connectedToStorage.storageAccountId.Split(\"/\")[-1]\r\n",
+                "        }\r\n",
+                "    if(!$connectedToStorage)\r\n",
+                "    {\r\n",
+                "        $Installed['Batch_StorageAccount']='Not Found'\r\n",
+                "        $countError++\r\n",
+                "    }  \r\n",
+                "    if(!$appPackageInstalled)\r\n",
+                "    {\r\n",
+                "        $Installed['Batch_ApplicationPackage']=\"Not Found\"\r\n",
+                "        $countError++\r\n",
+                "    }    \r\n",
+                "}\r\n",
+                "\r\n",
+                "if ($countError -gt 0){\r\n",
+                "    Write-Output \"ADP Resources are not installed correctly. Please refer the list below and use the Bootstrap NB to install ADP Resources\"\r\n",
+                "}\r\n",
+                "$Installed\r\n",
+                "if ($countError -eq 0){\r\n",
+                "    Write-Output \"`nFound all ADP Resources.\"\r\n",
+                "}\r\n",
+                "}\r\n",
+                "\r\n",
+                "function Prepare-InputForExportFunction\r\n",
+                "{    \r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ADPResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$FunctionName,  \r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BatchAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VNetName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SourceRGName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$StorageAccountName\r\n",
+                "    )\r\n",
+                "    \r\n",
+                "    $InputResult = @{}\r\n",
+                "    # Build Header \r\n",
+                "    ## get Function key\r\n",
+                "    $FunctionAppID =az functionapp show -n $FunctionName -g $ADPResourceGroupName --subscription $Subscription --query \"[id]\" -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $DefaultHostKey = az rest --method post --uri \"$FunctionAppID/host/default/listKeys?api-version=2018-11-01\" --query \"[functionKeys.default]\" -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    ## Build Json Object for Headers\r\n",
+                "    $headers = @{\r\n",
+                "        'x-functions-key' = $DefaultHostKey\r\n",
+                "    }\r\n",
+                "    $InputResult['Header']=$headers\r\n",
+                "\r\n",
+                "    # Build string for Function URL \r\n",
+                "    $specified_Subscription= az account show --subscription $Subscription -o json |ConvertFrom-Json #Get SpecifiedSubscriptionID\r\n",
+                "    $SubscriptionID= $specified_Subscription.id\r\n",
+                "    $FunctionUrl = 'https://'+ $FunctionName +'.azurewebsites.net/api/subscriptions/'+ $SubscriptionID +'/resourceGroups/' + $ADPResourceGroupName + '/Export'\r\n",
+                "    $InputResult['FunctionURL']=$FunctionUrl\r\n",
+                "\r\n",
+                "    # Set parameter variables for Body\r\n",
+                "    ## Get BatchAccountURL \r\n",
+                "    $specified_Batch = az batch account show -n $BatchAccountName -g $ADPResourceGroupName  --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $BatchAccountURL = 'https://' + $specified_Batch.accountEndpoint      \r\n",
+                "\r\n",
+                "    ## Get default SubNet ID for specified VNet\r\n",
+                "    $specified_VNet_SubNet = az network vnet subnet show -g $ADPResourceGroupName --vnet-name $VNetName -n 'default' --subscription $Subscription -o JSON |ConvertFrom-Json\r\n",
+                "    $VNetSubNetID = $specified_VNet_SubNet.id\n",
+                "\n",
+                "    ## Create access token to source sql server\n",
+                "    $sourceAccessToken = az account get-access-token --resource=https://database.windows.net --query accessToken\n",
+                "    $sourceAccessToken\r\n",
+                "\r\n",
+                "    ## Build JSon object for Body\r\n",
+                "    $Body = @{\r\n",
+                "        batchAccountUrl = $BatchAccountURL\r\n",
+                "        storageAccountName = $StorageAccountName  # any storage account, not neccessarily the one connected to the batch account\r\n",
+                "        sourceSqlServerResourceGroupName = $SourceRGName \r\n",
+                "        sourceSqlServerName = $SQLServerName \n",
+                "        accessToken= $sourceAccessToken\r\n",
+                "        VNetSubnetId= $VNetSubNetID\r\n",
+                "    }\r\n",
+                "    $json = $Body | ConvertTo-Json\r\n",
+                "    $InputResult['Body']=$json\r\n",
+                "\r\n",
+                "    $InputResult\r\n",
+                "}\r\n",
+                "\r\n",
+                "function Provision-FuncRBAC {\r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param (\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Subscription,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$ResourceGroupName,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$FunctionName,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$ScopeRGName,\r\n",
+                "        [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string]$Role=\"Contributor\"\r\n",
+                "    )\r\n",
+                "\r\n",
+                "    # Get the scope resource group's ID\r\n",
+                "    $scopeID = az group show --resource-group $ScopeRGName --subscription $Subscription --query \"[id]\" -o JSON | ConvertFrom-Json \r\n",
+                "    if(!$scopeID) {\r\n",
+                "        Write-Output \"Provision-FuncRBAC failed.\" \r\n",
+                "        return }\r\n",
+                "    else { Write-Output \"Found scope '$ScopeRGName' with ID... '$scopeID'\"\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    # Get the az function principal id\r\n",
+                "    $app_PrincipalID = az functionapp show -n $FunctionName --resource-group $ResourceGroupName --subscription $Subscription --query \"[identity.principalId]\" -o JSON  | ConvertFrom-Json  \r\n",
+                "    if(!$app_PrincipalID) {\r\n",
+                "        Write-Output \"Provision-FuncRBAC failed.\" \r\n",
+                "        return }\r\n",
+                "    else {  Write-Output \"Found principal id of Azure function '$FunctionName'... '$app_PrincipalID'\"\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    # Verify if a role assignment has been created for function\r\n",
+                "    $app_RoleAssignmentDefinition= az role assignment list --subscription $Subscription --assignee $app_PrincipalID --scope $scopeID --query \"[].roleDefinitionName\" -o JSON 2>$null  | ConvertFrom-Json\r\n",
+                "\r\n",
+                "    if($app_RoleAssignmentDefinition -eq $Role)\r\n",
+                "    {\r\n",
+                "        Write-Output \"Found Role Assignment for Principal ID.. '$app_PrincipalID' with Role.. '$app_RoleAssignmentDefinition' . No work needed\"\r\n",
+                "    }\r\n",
+                "    else\r\n",
+                "    {\r\n",
+                "        # Continue to setup RBAC, once we verify an assignment is not setup and all the resources exist\r\n",
+                "        Write-Output \"Creating new role assignment by running: 'az functionapp identity assign -n $FunctionName --role $Role -g $ResourceGroupName --scope $scopeID --subscription $Subscription'\"\r\n",
+                "        Write-Warning \"If your account does not have the access to assign new roles as Owner or User Access Administrator for the resource group, than you will need to contact your Azure AD Administrator to assign a service principle using the commands above\"\r\n",
+                "        az functionapp identity assign -n $FunctionName --role $Role -g $ResourceGroupName --scope $scopeID --subscription $Subscription \r\n",
+                "    }\r\n",
+                "}\r\n",
+                "Write-Host \"Helper Functions Created successfully\"  "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "af70490a-c6cb-4086-99ff-c3527b4315ed",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Connect to Azure Account\r\n",
+                "Run the below cell to login to an Azure account. <b>Be sure to check the Windows Taskbar for a login dialog box underneath the notebook or other windows or by pressing Alt+TAB.</b>"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "55f9b3b2-9ce0-4607-9a45-384279f5d16f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Login-Azure "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4a577303-83b9-48f8-95ba-e4f74c7440c9"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify ADP Resources \r\n",
+                "Verify if ADP resources exists in specified Resource Group"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a7e4bef8-3db6-4e68-8eb8-5a1feb4b3ac5"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Verify-ADPResources -Subscription $AdpSubscription -ADPResourceGroupName $AdpResourceGroup `\r\n",
+                "                     -BatchAccountName $AdpBatch -FunctionName $AdpFunc -VNetName $AdpVNET "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8185f2ea-d368-42c5-9246-bc1871affc63"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify RBAC of Azure Function\r\n",
+                "Roles based access control is a function of Azure that assigns services to a role with a specific access scope (or area of access). The ADP Orchestrator function requires Contributor access over the Resource Group where the SQL Server to be exported exists. The function below will attempt to create the role assignment. Any user executing this notebook will need to have Owner or User Access Administrator permissions to the Resource Group to assign the permission. Otherwise, contact your Azure AD Administrator. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e919b0e3-34d6-40aa-8b29-cf92b943954c"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Provision-FuncRBAC -FunctionName $AdpFunc -ScopeRGName $SourceResourceGroup -ResourceGroupName $AdpResourceGroup -Subscription $AdpSubscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7678701e-ec40-43d9-baff-fd1cdabba1cd"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify Access to Source Server\r\n",
+                "\r\n",
+                "Before scheduling the export, validate the source SQL Server is accessible."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e8310d46-b7ed-4012-a993-1515c2d17e66"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$sqlServer = az sql server show --name $LogicalSQLServerName --resource-group $SourceResourceGroup --subscription $AdpSubscription -o JSON | ConvertFrom-JSON\r\n",
+                "if ($sqlServer)\r\n",
+                "{\r\n",
+                "    Write-Host \"Source SQL Server: \" $sqlServer.name\r\n",
+                "    if ($sqlServer.state -eq \"Ready\")\r\n",
+                "    {\r\n",
+                "        Write-Host \"State: Ready\"\r\n",
+                "    }\r\n",
+                "    else\r\n",
+                "    {\r\n",
+                "        Write-Host \"ERROR: Source server is not in Ready state.  Current state is: \" $sqlServer.state\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    $sqlAzureAdmin = az sql server ad-admin list --server $LogicalSQLServerName --resource-group $SourceResourceGroup --subscription $AdpSubscription -o JSON | ConvertFrom-JSON\r\n",
+                "    if ($sqlAzureAdmin)\r\n",
+                "    {\r\n",
+                "        Write-Host \"Azure AD admin set to\" $sqlAzureAdmin.login\r\n",
+                "        Write-Host \"SUCCESS: source server accessible and properly configured.\"\r\n",
+                "    }\r\n",
+                "    else\r\n",
+                "    {\r\n",
+                "        Write-Host \"ERROR: Source server has no Azure AD administrator configured.  The Data Portability solution requires Azure Active Directory to be configured on source servers to provide secure access.\"\r\n",
+                "    }\r\n",
+                "\r\n",
+                "}\r\n",
+                "else \r\n",
+                "{\r\n",
+                "    Write-Host \"ERROR: Source server \" $sqlServer.name \"not found or current account lacks access to resource.\"\r\n",
+                "    Write-Host \"Validate input settings:\"\r\n",
+                "    Write-Host \"Resource group: \" $SourceResourceGroup\r\n",
+                "    Write-Host \"Subscription: \" $AdpSubscription\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5e217942-b9b0-47d0-a9b7-79b5db93ea55"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Prepare input variable for Orchestrator Export Function"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "bc9acbcc-f5e1-4642-b1e3-677fca704dfc"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$InputForExportFunction = Prepare-InputForExportFunction -Subscription $AdpSubscription -ADPResourceGroupName $AdpResourceGroup `\r\n",
+                "                             -BatchAccountName $AdpBatch -FunctionName $AdpFunc -VNetName $AdpVNET -SourceRGName $SourceResourceGroup `\r\n",
+                "                             -SqlServerName $LogicalSQLServerName  -StorageAccountName $StorageAccount\r\n",
+                "Write-Host \"Setting parameter variables for Export Function Call...\"\r\n",
+                "$InputForExportFunction.Header\r\n",
+                "$InputForExportFunction.FunctionURL\r\n",
+                "$InputForExportFunction.Body"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "9e6f142c-3295-4c12-9b9f-588c7c283b2d"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Start Export of SQL Server \r\n",
+                "Run the cell to start export operation of specified sql server"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d6a9e5c3-bd3c-4f06-8adc-c7751e33b4cf"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$ExportResponse = Invoke-RestMethod -Method 'Post' -Headers $InputForExportFunction.Header -Uri $InputForExportFunction.FunctionURL -Body $InputForExportFunction.Body -ContentType 'application/json'\r\n",
+                "$ExportResponse"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2e0167f6-b91c-4d65-a6e6-a1ef0bada436"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Get Status of Export Operation\r\n",
+                "Run the cell to get export operation status"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "34812090-faa7-4375-8e76-72defea6d57a"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$statusCheckResponse = Invoke-RestMethod -Method 'Get' -Uri $ExportResponse.statusQueryGetUri\r\n",
+                "\r\n",
+                "\r\n",
+                "Write-Host \"Orchestrator Request: \" $statusCheckResponse.name\r\n",
+                "Write-Host \"`tOrchestrator Status: \" $statusCheckResponse.runtimeStatus\r\n",
+                "\r\n",
+                "$outputParams = $statusCheckResponse.output \r\n",
+                "if ($outputParams)\r\n",
+                "{\r\n",
+                "    $batchJobID = $outputParams.Item2[0]\r\n",
+                "    $containerUrl = $outputParams.Item2[1]\r\n",
+                "\r\n",
+                "    Write-Host \"`tCreated Export Batch Job ID: \" $batchJobId\r\n",
+                "    Write-Host \"`tExport container URL: \" $containerUrl\r\n",
+                "\r\n",
+                "    $azBatchLogin = az batch account login --name $AdpBatch --resource-group $AdpResourceGroup -o JSON | ConvertFrom-Json\r\n",
+                "    $jobStatus = az batch job show --job-id $batchJobID -o JSON | ConvertFrom-Json\r\n",
+                "    Write-Host \"Export Job running on Pool: \" $jobStatus.poolInfo.poolId\r\n",
+                "    Write-Host \"`tExport Request Status: \" $jobStatus.state\r\n",
+                "\r\n",
+                "    $taskList = az batch task list --job-id $batchJobId -o JSON | ConvertFrom-Json\r\n",
+                "    if ($taskList)\r\n",
+                "    {\r\n",
+                "        foreach ($task in $taskList)\r\n",
+                "        {\r\n",
+                "            Write-Host \"`tDatabase Export Task ID: \" $task.id \r\n",
+                "            Write-Host \"`t`tStatus: \" $task.state\r\n",
+                "            $taskExecution = $task.executionInfo\r\n",
+                "            if ($taskExecution)\r\n",
+                "            {\r\n",
+                "                Write-Host \"`t`tResult: \" $taskExecution.result\r\n",
+                "            }\r\n",
+                "        }\r\n",
+                "    }\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "de949b83-195d-455b-ab63-5607cad7c9dd"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 530 - 0
SQL-Hybrid-Cloud-Toolkit/content/data-portability/import-sql-server.ipynb

@@ -0,0 +1,530 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# Import Existing Azure SQL Server Resources\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "baa1b762-f2a9-461f-a126-49f0d3b5e4f0"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "The notebook will help accomplish the below steps as a part of Importing existing Azure SQL Server Resources\n",
+                "\n",
+                "-   [ ]  Connect to Azure Subscription\n",
+                "-   [ ]  Choose Resource Group (Read access required)\n",
+                "-   [ ]  Choose Microsoft SQL Server Resources to import\n",
+                "-   [ ]  Choose/Create Migration Storage\n",
+                "-   [ ]  Install Application + Data Portability function\n",
+                "-   [ ]  Install ADP Azure Batch processing pipeline\n",
+                "-   [ ]  Store SqlPackage.exe in Migration Storage for ADP function to hand to Az Batch\n",
+                "\n",
+                "Execute:\n",
+                "\n",
+                "-   [ ]  Check all prerequisites\n",
+                "-   [ ]  Kick off ADP service\n",
+                "\n",
+                "Monitor:\n",
+                "\n",
+                "-   [ ]  Check import status."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "1ccbf203-d568-408c-a641-7e5cfa93802a"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Set Variables for the Notebook"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "09f648ab-67e8-4127-94bd-649d5e970321"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# ADP Resource \r\n",
+                "$AdpSubscription     = \"\"                     # Azure Subscription ID/Name # The bacpac files and ADP Resources are assumed to be in the same subscription\r\n",
+                "$AdpResourceGroup    = \"\"                                            # Azure Resource Group which contains the ADP Resources\r\n",
+                "\r\n",
+                "# SQL Server \r\n",
+                "$TargetResourceGroupName    = \"\"                                                # Azure ResourceGroup into which the sql server backup needs to be restored\r\n",
+                "$StorageAccountName         = \"\"\r\n",
+                "$ContainerName              = \"\"\r\n",
+                "$LogicalSQLServerName       = \"\"                                                # New sql server name\r\n",
+                "$LSqlServerPassword          = \"\"\r\n",
+                "\r\n",
+                "# Set Variables for ADP Resources\r\n",
+                "$AdpFunc            = $AdpResourceGroup + \"Control\"   \r\n",
+                "$AdpBatch           = $AdpResourceGroup.ToLower() + \"batch\"\r\n",
+                "$AdpVNET            = $AdpResourceGroup + \"Vnet\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "01888595-0d1c-445b-ba85-dd12caa30192",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Notebook Functions\r\n",
+                "Defines logical functions for the rest of the notebook. Function blocks are combined in a single cell that can be collapsed for readability or expanded for further examination. Nothing is executed until called later in the notebook. As a result, this cell is a requirement for any of the other cells below it. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4a35cf8e-6598-43e8-a0da-2c0c369df548"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Expand cell to view framework\r\n",
+                "\r\n",
+                "function Login-Azure\r\n",
+                "{    \r\n",
+                "    # query azure locations to test for existing az login session exists with valid access tocken\r\n",
+                "    $azureLocations = az account list-locations -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    \r\n",
+                "    if (!$azureLocations){ #If there are no az locations, there is no existing az login session\r\n",
+                "        $subscriptions = az login -o JSON | ConvertFrom-Json      # Login   \r\n",
+                "    }\r\n",
+                "    else {\r\n",
+                "        $subscriptions = az account list -o JSON | ConvertFrom-Json # getting subscriptions for the user to use in gridview\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    if(![string]::IsNullOrWhiteSpace($AdpSubscription)) #If there is a subscription specified by user in the variables section\r\n",
+                "    {\r\n",
+                "        $specified_Subscription= az account show --subscription $AdpSubscription -o json |ConvertFrom-Json \r\n",
+                "        if (!$specified_Subscription) #if specified subscription is not valid\r\n",
+                "        {       \r\n",
+                "            $currentUser= az ad signed-in-user show --query \"{displayName:displayName,UPN:userPrincipalName}\" -o json|ConvertFrom-Json # get current logged in user infomration\r\n",
+                "            Write-Host \"Refer below for the list of subscriptions for logged in account '$($currentUser.UPN)'`n\"\r\n",
+                "            az account list --query \"[].{Name:name,SubscriptionID:id}\" -o table          # list subscriptions under current logged in account\r\n",
+                "        }\r\n",
+                "        else { # if specified subscription is valid\r\n",
+                "            Write-Output \"Using subscription... '$($specified_Subscription.name)' ... '$($specified_Subscription.id)'\" \r\n",
+                "        }\r\n",
+                "    }\r\n",
+                "    else { # if no subscription is specified, users are given a gridview to select subscription from\r\n",
+                "\r\n",
+                "        $selectedSubscription = $subscriptions | Select-Object -Property Name, Id | Out-GridView -PassThru\r\n",
+                "        $SubscriptionId = $selectedSubscription.Id\r\n",
+                "        $Subscription = $selectedSubscription.Name   \r\n",
+                "        $AdpSubscription = $subscription  \r\n",
+                "        Write-Output \"Using subscription... '$AdpSubscription' ... '$SubscriptionId'\"    \r\n",
+                "    }    \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Verify-ADPResources\r\n",
+                "{    \r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ADPResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$FunctionName,  \r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BatchAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VNetName,\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ApplicationName=\"SqlPackageWrapper\",  \r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ApplicationPackageVersionName=\"1\",\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$SubNetName=\"default\" \r\n",
+                "    )    \r\n",
+                "\r\n",
+                "# validate Subscription\r\n",
+                "$specified_Subscription= az account show --subscription $Subscription -o json | ConvertFrom-Json\r\n",
+                "if(!$specified_Subscription){\r\n",
+                "    $currentUser= az ad signed-in-user show --query \"{displayName:displayName,UPN:userPrincipalName}\" -o json|ConvertFrom-Json # get current logged in user information\r\n",
+                "    Write-Host \"Refer below for the list of subscriptions for logged in account '$($currentUser.UPN)'`n\"\r\n",
+                "    az account list --query \"[].{Name:name,SubscriptionID:id}\" -o table          # list subscriptions under current logged in account\r\n",
+                "    return } \r\n",
+                "# validate ResourceGroup    \r\n",
+                "$specified_ResourceGroup= az group show -n $ADPResourceGroupName --subscription $Subscription -o json | ConvertFrom-Json\r\n",
+                "if(!$specified_ResourceGroup) {        \r\n",
+                "        return\r\n",
+                "        } \r\n",
+                "\r\n",
+                "$Installed = [ordered]@{} # ordered hash to store status of installation\r\n",
+                "$countError=0\r\n",
+                "\r\n",
+                "#Verify if VNet exists       \r\n",
+                "$specified_VNet= az network vnet show -n $VNetName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null  |ConvertFrom-Json   \r\n",
+                "if(!$specified_VNet) {\r\n",
+                "    $Installed['VNET']=\"Not Found\"\r\n",
+                "    $countError++\r\n",
+                "}   \r\n",
+                "else { \r\n",
+                "    $existingVnetSubnet =  az network vnet subnet show -n $SubNetName --vnet-name $VNetName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null |ConvertFrom-Json\r\n",
+                "    if(!$existingVnetSubnet){\r\n",
+                "        $Installed['VNET']=\"Default Subnet under\"+ $VNetName + \"Not Found\"\r\n",
+                "        $countError++\r\n",
+                "        }\r\n",
+                "    else {\r\n",
+                "    $Installed['VNET']=\"Installed\"\r\n",
+                "    }\r\n",
+                " }\r\n",
+                "\r\n",
+                "#Verify if FunctionApp Exists\r\n",
+                "$specified_FunctionApp = az functionapp show -n $FunctionName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "if(!$specified_FunctionApp)\r\n",
+                "{\r\n",
+                "    $Installed['FunctionApp']=\"Not Installed\"\r\n",
+                "    $countError++\r\n",
+                "}\r\n",
+                "else\r\n",
+                "{\r\n",
+                "    $Installed['FunctionApp']=\"Installed\"\r\n",
+                "} \r\n",
+                "\r\n",
+                "#check if Batch account exists\r\n",
+                "$specified_BatchAccount = az batch account show -n $BatchAccountName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "if(!$specified_BatchAccount)\r\n",
+                "{\r\n",
+                "    $Installed['Batch']=\"Not Installed\"\r\n",
+                "    $countError++\r\n",
+                "}\r\n",
+                "else\r\n",
+                "{\r\n",
+                "    $appPackageInstalled = az batch application package show --application-name $ApplicationName --version-name $ApplicationPackageVersionName -n $BatchAccountName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $connectedToStorage= $specified_BatchAccount.autoStorage \r\n",
+                "    if($connectedToStorage -and $appPackageInstalled){ # BatchAccount connected to storageaccount and  applicationpackage is installed\r\n",
+                "            $Installed['Batch']=\"Installed\"\r\n",
+                "            $Installed['Batch_ApplicationPackage']=\"Installed\"\r\n",
+                "            $Installed['Batch_StorageAccount']=\"Connected to storage- \"+$connectedToStorage.storageAccountId.Split(\"/\")[-1]\r\n",
+                "        }\r\n",
+                "    if(!$connectedToStorage)\r\n",
+                "    {\r\n",
+                "        $Installed['Batch_StorageAccount']='Not Found'\r\n",
+                "        $countError++\r\n",
+                "    }  \r\n",
+                "    if(!$appPackageInstalled)\r\n",
+                "    {\r\n",
+                "        $Installed['Batch_ApplicationPackage']=\"Not Found\"\r\n",
+                "        $countError++\r\n",
+                "    }    \r\n",
+                "}\r\n",
+                "if ($countError -gt 0){\r\n",
+                "    Write-Output \"ADP Resources are not installed correctly. Please refer the list below and use the Bootstrap NB to install ADP Resources\"\r\n",
+                "}\r\n",
+                "$Installed\r\n",
+                "if ($countError -eq 0){\r\n",
+                "    Write-Output \"`nFound all ADP Resources.\"\r\n",
+                "}\r\n",
+                "}\r\n",
+                "\r\n",
+                "function Prepare-InputForImportFunction\r\n",
+                "{    \r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ADPResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$FunctionName,  \r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BatchAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BackupFiles_StorageAccount,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BackupFiles_ContainerName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VNetName,        \r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$TargetRGName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerPassword\r\n",
+                "    )\r\n",
+                "    \r\n",
+                "    $Result = @{}\r\n",
+                "    # Build Header \r\n",
+                "    ## get Function key\r\n",
+                "    $FunctionAppID =az functionapp show -n $FunctionName -g $ADPResourceGroupName --subscription $Subscription --query \"[id]\" -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $DefaultHostKey = az rest --method post --uri \"$FunctionAppID/host/default/listKeys?api-version=2018-11-01\" --query \"[functionKeys.default]\" -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    ## Build Json Object for Headers\r\n",
+                "    $headers = @{\r\n",
+                "        'x-functions-key' = $DefaultHostKey\r\n",
+                "    }\r\n",
+                "    $Result['Header']=$headers\r\n",
+                "\r\n",
+                "    # Build string for Function URL \r\n",
+                "    $specified_Subscription= az account show --subscription $Subscription -o json |ConvertFrom-Json #Get SpecifiedSubscriptionID\r\n",
+                "    $SubscriptionID= $specified_Subscription.id\r\n",
+                "    $FunctionUrl = 'https://'+ $FunctionName +'.azurewebsites.net/api/subscriptions/'+ $SubscriptionID +'/resourceGroups/' + $ADPResourceGroupName + '/Import'\r\n",
+                "    $Result['FunctionURL']=$FunctionUrl\r\n",
+                "\r\n",
+                "    # Set parameter variables for Body\r\n",
+                "    ## Get BatchAccountURL \r\n",
+                "    $specified_Batch = az batch account show -n $BatchAccountName -g $ADPResourceGroupName  --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $BatchAccountURL = 'https://' + $specified_Batch.accountEndpoint\r\n",
+                "\r\n",
+                "    ## Get default SubNet ID for specified VNet\r\n",
+                "    $specified_VNet_SubNet = az network vnet subnet show -g $ADPResourceGroupName --vnet-name $VNetName -n 'default' --subscription $Subscription -o JSON |ConvertFrom-Json\r\n",
+                "    $VNetSubNetID = $specified_VNet_SubNet.id\r\n",
+                "\r\n",
+                "    ## Create access token to source sql server\r\n",
+                "    $targetAccessToken = az account get-access-token --resource=https://database.windows.net --query accessToken\r\n",
+                "    $targetAccessToken\r\n",
+                "\r\n",
+                "    ## Build JSon object for Body\r\n",
+                "    $Body = @{\r\n",
+                "        batchAccountUrl = $BatchAccountURL\r\n",
+                "        VNetSubnetId= $VNetSubNetID\r\n",
+                "        storageAccountName = $BackupFiles_StorageAccount\r\n",
+                "        containerName = $BackupFiles_ContainerName\r\n",
+                "        targetSqlServerResourceGroupName = $TargetRGName\r\n",
+                "        targetSqlServerName = $SQLServerName \r\n",
+                "        userName = $SqlServerLogin \r\n",
+                "        targetAccessToken = $targetAccessToken\r\n",
+                "        sqlAdminPassword = $SqlServerPassword\r\n",
+                "    }\r\n",
+                "    $json = $Body | ConvertTo-Json\r\n",
+                "    $Result['Body']=$json\r\n",
+                "\r\n",
+                "    $Result\r\n",
+                "    \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Provision-FuncRBAC {\r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param (\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Subscription,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$ResourceGroupName,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$FunctionName,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$ScopeRGName,\r\n",
+                "        [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string]$Role=\"Contributor\"\r\n",
+                "    )\r\n",
+                "\r\n",
+                "    # Get the scope resource group's ID\r\n",
+                "    $scopeID = az group show --resource-group $ScopeRGName --subscription $Subscription --query \"[id]\" -o JSON | ConvertFrom-Json \r\n",
+                "    if(!$scopeID) {\r\n",
+                "        Write-Output \"Provision-FuncRBAC failed.\" \r\n",
+                "        return }\r\n",
+                "    else { Write-Output \"Found scope '$ScopeRGName' with ID... '$scopeID'\"\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    # Get the az function principal id\r\n",
+                "    $app_PrincipalID = az functionapp show -n $FunctionName --resource-group $ResourceGroupName --subscription $Subscription --query \"[identity.principalId]\" -o JSON  | ConvertFrom-Json  \r\n",
+                "    if(!$app_PrincipalID) {\r\n",
+                "        Write-Output \"Provision-FuncRBAC failed.\" \r\n",
+                "        return }\r\n",
+                "    else {  Write-Output \"Found principal id of Azure function '$FunctionName'... '$app_PrincipalID'\"\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    # Verify if a role assignment has been created for function\r\n",
+                "    $app_RoleAssignmentDefinition= az role assignment list --subscription $Subscription --assignee $app_PrincipalID --scope $scopeID --query \"[].roleDefinitionName\" -o JSON 2>$null  | ConvertFrom-Json\r\n",
+                "\r\n",
+                "    if($app_RoleAssignmentDefinition -eq $Role)\r\n",
+                "    {\r\n",
+                "        Write-Output \"Found Role Assignment for Principal ID.. '$app_PrincipalID' with Role.. '$app_RoleAssignmentDefinition' . No work needed\"\r\n",
+                "    }\r\n",
+                "    else\r\n",
+                "    {\r\n",
+                "        # Continue to setup RBAC, once we verify an assignment is not setup and all the resources exist\r\n",
+                "        Write-Output \"Creating new role assignment by running: 'az functionapp identity assign -n $FunctionName --role $Role -g $ResourceGroupName --scope $scopeID --subscription $Subscription'\"\r\n",
+                "        Write-Warning \"If your account does not have the access to assign new roles as Owner or User Access Administrator for the resource group, than you will need to contact your Azure AD Administrator to assign a service principle using the commands above\"\r\n",
+                "        az functionapp identity assign -n $FunctionName --role $Role -g $ResourceGroupName --scope $scopeID --subscription $Subscription \r\n",
+                "    }\r\n",
+                "}\r\n",
+                "Write-Host \"Helper Functions Created successfully\"  "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4730aec5-7aa6-4a2a-baf4-696dc74aa898",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Connect to Azure Account\r\n",
+                "Run the below cell to login to an Azure account. Be sure to check the Windows Taskbar for a login dialog box underneath the notebook or other windows or by pressing Alt+TAB."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5cd37536-c2c5-4b97-8383-fa761d7cbda3"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Login-Azure "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e9cd7ac2-ff0b-43b4-baf8-b71d3128885c"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify ADP Resources \r\n",
+                "Verify if ADP resources exists in specified Resource Group"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "bec05e08-67ba-4071-8459-5b32dc7f876a"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Verify-ADPResources -Subscription $AdpSubscription -ADPResourceGroupName $AdpResourceGroup `\r\n",
+                "                     -BatchAccountName $AdpBatch -FunctionName $AdpFunc -VNetName $AdpVNET "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e89f6eb9-fcbc-4b7d-bcd1-37f1eb52cc02",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify RBAC of Azure Function\r\n",
+                "Roles based access control is a function of Azure that assigns services to a role with a specific access scope (or area of access). The ADP Orchestrator function requires Contributor access over the Resource Group where the SQL Server to be exported exists. The function below will attempt to create the role assignment. Any user executing this notebook will need to have Owner or User Access Administrator permissions to the Resource Group to assign the permission. Otherwise, contact your Azure AD Administrator. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "0c95bb17-b3cf-4a8b-8aa6-690ac6139e37"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Provision-FuncRBAC -FunctionName $AdpFunc -ScopeRGName $TargetResourceGroupName -ResourceGroupName $AdpResourceGroup -Subscription $AdpSubscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c374e57c-51ec-4a3f-9966-1e50cefc8510"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Prepare input variable for Orchestrator Import Function"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b517742f-fa3d-4a4f-9ec0-b320c71738d4"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$InputForImportFunction = Prepare-InputForImportFunction -Subscription $AdpSubscription -ADPResourceGroupName $AdpResourceGroup `\r\n",
+                "                             -BatchAccountName $AdpBatch -FunctionName $AdpFunc -TargetRGName $TargetResourceGroupName `\r\n",
+                "                             -VNetName $AdpVNET -BackupFiles_StorageAccount $StorageAccountName -BackupFiles_ContainerName $ContainerName `\r\n",
+                "                             -SqlServerName $LogicalSQLServerName -SqlServerPassword $LSqlServerpassword\r\n",
+                "Write-Host \"Setting parameter variables for Import Function Call...\"\r\n",
+                "$InputForImportFunction.Header\r\n",
+                "$InputForImportFunction.FunctionURL\r\n",
+                "$InputForImportFunction.Body"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "bfba288e-3466-4c57-9f3c-5281753601cf",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Start Import of SQL Server \r\n",
+                "Run the cell to start import from specified backup files"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8f615b19-1e1d-405f-9f4a-ad7cc303487a"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$importResponse = Invoke-RestMethod -Method 'Post' -Headers $InputForImportFunction.Header -Uri $InputForImportFunction.FunctionURL -Body $InputForImportFunction.Body -ContentType 'application/json'\r\n",
+                "$importResponse"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7e251aa5-7a92-4212-8c81-394c2058f1fa",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Get Status of Import Operation\r\n",
+                "Run the cell to get import operation status"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cb1988a9-9797-49f0-8ddb-1634df48f027"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$statusCheckResponse = Invoke-RestMethod -Method 'Get' -Uri $importResponse.statusQueryGetUri\r\n",
+                "\r\n",
+                "\r\n",
+                "Write-Host \"Orchestrator Request: \" $statusCheckResponse.name\r\n",
+                "Write-Host \"`tOrchestrator Status: \" $statusCheckResponse.runtimeStatus\r\n",
+                "\r\n",
+                "$outputParams = $statusCheckResponse.output \r\n",
+                "if ($outputParams)\r\n",
+                "{\r\n",
+                "    $batchJobID = $outputParams.Item2[2]\r\n",
+                "    $containerUrl = $outputParams.Item2[3]\r\n",
+                "\r\n",
+                "    Write-Host \"`tCreated Import Batch Job ID: \" $batchJobId\r\n",
+                "    $azBatchLogin = az batch account login --name $AdpBatch --resource-group $AdpResourceGroup -o JSON | ConvertFrom-Json\r\n",
+                "    $jobStatus = az batch job show --job-id $batchJobID -o JSON | ConvertFrom-Json\r\n",
+                "    Write-Host \"Import Job running on Pool: \" $jobStatus.poolInfo.poolId\r\n",
+                "    Write-Host \"`Import Request Status: \" $jobStatus.state\r\n",
+                "\r\n",
+                "    $taskList = az batch task list --job-id $batchJobId -o JSON | ConvertFrom-Json\r\n",
+                "    if ($taskList)\r\n",
+                "    {\r\n",
+                "        foreach ($task in $taskList)\r\n",
+                "        {\r\n",
+                "            Write-Host \"`tDatabase Import Task ID: \" $task.id \r\n",
+                "            Write-Host \"`t`tStatus: \" $task.state\r\n",
+                "            $taskExecution = $task.executionInfo\r\n",
+                "            if ($taskExecution)\r\n",
+                "            {\r\n",
+                "                Write-Host \"`t`tResult: \" $taskExecution.result\r\n",
+                "            }\r\n",
+                "        }\r\n",
+                "    }\r\n",
+                "}\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "3c59dc7b-2648-46ee-b57e-d9e99580a093",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 20 - 0
SQL-Hybrid-Cloud-Toolkit/content/data-portability/readme.md

@@ -0,0 +1,20 @@
+# Data Portability 
+[Home](../readme.md)
+
+Notebooks in this chapter perform a data migration using a custom Azure function that can be deployed to an Azure subscription. It enables [Azure Batch](https://azure.microsoft.com/en-us/services/batch) computing of a complex SQL Server migration to and from a single Resource Group. Azure Batch is a process that runs large-scale parallel and high-performance computing (HPC) batch jobs efficiently in Azure. This greatly reduces the processing required locally which should prevent long execution times, timeouts and retries. Importing and exporting data to and from Azure is supported for multiple SQL database instances. Data is imported and exported to and from standard SQL backup formats (*.bacpac) which "encapsulates the database schema as well as the data stored in the database" ([Microsoft Docs](https://docs.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications)).
+
+## Notebooks in this Chapter
+
+- [Azure Data Portability Setup](setup-adp.ipynb) - Configure and install a custom Azure function to migrate data to and from Azure <br/>
+<img width="25%" src="VisualBootstrapperNB.PNG"/>
+
+- [Export Sql Server](export-sql-server.ipynb) - from SQL Azure to a standard SQL backup format
+
+- [Import Sql Server](import-sql-server.ipynb) - from SQL backup format to Azure
+
+## Steps 
+1. The Azure function must first be deployed using the setup notebook
+2. Open the notebook for the desired migration path (import or export)
+3. Configure and execute notebook
+4. Monitor progress with periodic notebook queries
+5. Verify data has been imported/exported by reviewing the storage account for the migrated Resource Group

+ 643 - 0
SQL-Hybrid-Cloud-Toolkit/content/data-portability/setup-adp.ipynb

@@ -0,0 +1,643 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# Application and Data Portability Setup Notebook\n",
+                "\n",
+                "This notebook provisions SQL ADP services to an Azure Subscription for migration purposes such as importing and exporting Azure SQL Server database(s). The following scenarios are covered:\n",
+                "\n",
+                "-   [x]  Azure to Azure migration\n",
+                "-   [x]  Azure to Air-gapped Azure (highly secured Azure offer)\n",
+                "-   [x]  Azure to !Azure (3rd party or on-premises)\n",
+                "\n",
+                "This bootstrapper is designed to be ran from a client workstation or laptop with minimal modification. The notebook will attempt to locate three different Azure Resources in a customer subscription. If not found it will install the following to a given Resource Group. If the resources group specified doesn't exist, it will create a new one:\n",
+                "\n",
+                "1.  A **Custom Azure ADP Function** to orchestrate the Azure Batch and Storage (see \\[Github\\](........\\\\Program Files\\\\Azure Data Studio\\\\resources\\\\app\\\\out\\\\vs\\\\code\\\\electron-browser\\\\workbench\\\\workbench.html))\n",
+                "2.  An Azure **Storage Account** for storage\n",
+                "3.  An **Azure Batch** resource\n",
+                "\n",
+                "This notebook will help accomplish the below steps as a part of Bootstrapping an ADP Resource Group\n",
+                "\n",
+                "![](VisualBootstrapperNB.PNG)\n",
+                "\n",
+                "1.  Set Environment Variables\n",
+                "2.  Define Notebook Functions\n",
+                "3.  MS Azure Login\n",
+                "4.  Validate/ Create ADP Resource Group\n",
+                "5.  Provision Virtual Network\n",
+                "6.  Provision ADP Storage Account\n",
+                "7.  Provision ADP Portability Function\n",
+                "8.  Provision ADP Batch Account\n",
+                "9.  Provision Contributor access (RBAC) to Portability function over ADP Resource Group\n",
+                "\n",
+                "Setup _will not_ perform tasks on existing Azure assets such as Microsoft SQL Servers, databases, VMs, etc. Other notebooks cover the <u><a href=\"import-sql-server.ipynb\" rel=\"nofollow\">import</a></u> or <u><a href=\"export-sql-server.ipynb\" rel=\"nofollow\">export</a></u> operations. This notebook will only check for the existence of these ADP resources and their setup."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c5d49b77-973b-47bc-92ef-7bcfe05e4885"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Setup client environment variables that the rest of the notebook will use\r\n",
+                "$AdpResourceGroup    = \"\"                                           # Target Resource Group to bootstrap with ADP components - A new one will be created if the specified Resource Group doesn't exist\r\n",
+                "$AdpRegion        = \"eastus\"                                                # Region/Location of the resource group to be bootstrapped\r\n",
+                "\r\n",
+                "# Derived settings\r\n",
+                "$AdpSubscription     = \"\"                                                      # Target Azure Subscription Name or ID to bootstrap data portability resources\r\n",
+                "$AdpFunc             = $AdpResourceGroup + \"Control\"\r\n",
+                "$AdpStorage          = $AdpResourceGroup.ToLower() + \"storage\"\r\n",
+                "$AdpBatch            = $AdpResourceGroup.ToLower() + \"batch\"\r\n",
+                "$AdpVNET             = $AdpResourceGroup + \"VNet\"\r\n",
+                "\r\n",
+                "# Bootstrapper URLs - Update with the recommended toolkit version and build\r\n",
+                "$BaseToolkitUrl         =   \"https://hybridtoolkit.blob.core.windows.net/components\"\r\n",
+                "$ReleaseVersion         =   \"0.13\"\r\n",
+                "$BuildNumber            =   \"74938\"\r\n",
+                "$AdpDownloadUrl =   \"$BaseToolkitUrl/$ReleaseVersion/ADPControl-$BuildNumber.zip\"\r\n",
+                "$AdpWrapperUrl =   \"$BaseToolkitUrl/$ReleaseVersion/BatchWrapper-$BuildNumber.zip\"\r\n",
+                "\r\n",
+                "Write-Output \"Setting the Environment:\"\r\n",
+                "Get-ChildItem Env: | Where-Object Name -Match \"BOOTSTRAP\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5896a9a8-5ba8-4888-967a-10c3ceaccf12",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Define Helper Functions\r\n",
+                "Defines logical functions for the rest of the notebook. Function blocks are combined in a single cell that can be collapsed for readability or expanded for further examination. Nothing is executed until called later in the notebook. As a result, this cell is a requirement for any of the other cells below it. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7df7c213-9e97-46e4-acb5-547993b9b03a"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Expand cell to view framework\r\n",
+                "\r\n",
+                "function Get-ConnectionStringProperties\r\n",
+                "{\r\n",
+                "    param([Parameter (Mandatory=$true)] [string]$ConnectionString)\r\n",
+                "    $hash = @{}\r\n",
+                "    $attributes = $ConnectionString.Split(\";\");  \r\n",
+                "    foreach($at in $attributes)\r\n",
+                "    {\r\n",
+                "        $attributeKeyValue=$at.Split(\"=\",2)    \r\n",
+                "        $hash[$attributeKeyValue[0]]=$attributeKeyValue[1]    \r\n",
+                "    }$hash    \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Login-Azure\r\n",
+                "{    \r\n",
+                "    # query azure locations to test for existing az login session exists with valid access tocken\r\n",
+                "    $azureLocations = az account list-locations -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    \r\n",
+                "    if (!$azureLocations){ #If there are no az locations, there is no existing az login session\r\n",
+                "        $subscriptions = az login -o JSON | ConvertFrom-Json      # Login   \r\n",
+                "    }\r\n",
+                "    else {\r\n",
+                "        $subscriptions = az account list -o JSON | ConvertFrom-Json # getting subscriptions for the user to use in gridview\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    if(![string]::IsNullOrWhiteSpace($AdpSubscription)) #If there is a subscription specified by user in the variables section\r\n",
+                "    {\r\n",
+                "        $specified_Subscription= az account show --subscription $AdpSubscription -o json |ConvertFrom-Json \r\n",
+                "        if (!$specified_Subscription) #if specified subscription is not valid\r\n",
+                "        {       \r\n",
+                "            $currentUser= az ad signed-in-user show --query \"{displayName:displayName,UPN:userPrincipalName}\" -o json|ConvertFrom-Json # get current logged in user infomration\r\n",
+                "            Write-Host \"Refer below for the list of subscriptions for logged in account '$($currentUser.UPN)'`n\"\r\n",
+                "            az account list --query \"[].{Name:name,SubscriptionID:id}\" -o table          # list subscriptions under current logged in account\r\n",
+                "        }\r\n",
+                "        else { # if specified subscription is valid\r\n",
+                "            Write-Output \"Using subscription... '$($specified_Subscription.name)' ... '$($specified_Subscription.id)'\" \r\n",
+                "        }\r\n",
+                "    }\r\n",
+                "    else { # if no subscription is specified, users are given a gridview to select subscription from\r\n",
+                "\r\n",
+                "        $selectedSubscription = $subscriptions | Select-Object -Property Name, Id | Out-GridView -PassThru\r\n",
+                "        $SubscriptionId = $selectedSubscription.Id\r\n",
+                "        $Subscription = $selectedSubscription.Name   \r\n",
+                "        $AdpSubscription = $subscription  \r\n",
+                "        Write-Output \"Using subscription... '$AdpSubscription' ... '$SubscriptionId'\"    \r\n",
+                "    }    \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Bootstrap-AzResourceGroup\r\n",
+                "{\r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ResourceGroupLocation    \r\n",
+                "    )   \r\n",
+                "     \r\n",
+                "    #Validating parameter $Subscription\r\n",
+                "    $specified_Subscription = az account show --subscription $Subscription -o json |ConvertFrom-Json\r\n",
+                "    if(!$specified_Subscription){\r\n",
+                "        $currentUser= az ad signed-in-user show --query \"{displayName:displayName,UPN:userPrincipalName}\" -o json|ConvertFrom-Json # get current logged in user infomration\r\n",
+                "        Write-Host \"Refer below for the list of subscriptions for logged in account '$($currentUser.UPN)'`n\"\r\n",
+                "        az account list --query \"[].{Name:name,SubscriptionID:id}\" -o table          # list subscriptions under current logged in account\r\n",
+                "        return\r\n",
+                "        }\r\n",
+                "    #Validating parameter $ResourceGroupLocation\r\n",
+                "    $specified_RGLocation = az account list-locations --query \"[?name == '$ResourceGroupLocation']\" -o JSON | ConvertFrom-Json\r\n",
+                "    if(!$specified_RGLocation) {\r\n",
+                "        Write-Host \"ResourceGroupLocation '$ResourceGroupLocation' not found. Refer below for the list of locations for the account:`n\" \r\n",
+                "        az account list-locations --query \"[].{Locations: name}\" -o table\r\n",
+                "        return\r\n",
+                "    }\r\n",
+                "    # check if Resource group already exists under specified subscription\r\n",
+                "    $existingResourceGroup = az group show -n $ResourceGroupName --subscription $Subscription -o JSON 2>$null  | ConvertFrom-Json\r\n",
+                "\r\n",
+                "    if ($existingResourceGroup){ #Found specified resource group        \r\n",
+                "        Write-Output \"Found Resource Group '$($existingResourceGroup.name)'. \"   \r\n",
+                "        } \r\n",
+                "    else { \r\n",
+                "        # Resource group not found under specified subscription. Create new Resource group \r\n",
+                "        Write-Output \"Resource group '$($ResourceGroupName)' not found. `nCreating new resource group ... \"\r\n",
+                "        $newResourceGroup = az group create -n $ResourceGroupName -l $ResourceGroupLocation --subscription $Subscription -o JSON | ConvertFrom-Json\r\n",
+                "        Write-Output \"Created Resource Group '$($newResourceGroup.name)'\"\r\n",
+                "        }  \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Bootstrap-AzVirtualNetwork\r\n",
+                "{    \r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VNetName,  \r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$SubNetName = \"default\"         \r\n",
+                "    )\r\n",
+                "    \r\n",
+                "    #Validating parameter $ResourceGroupName\r\n",
+                "    $specified_RG = az group show -n $ResourceGroupName --subscription $Subscription 2>$null\r\n",
+                "    if(!$specified_RG) {\r\n",
+                "        Write-Host \"Invalid Input: ResourceGroupName. Resource Group $ResourceGroupName not found\"\r\n",
+                "        return\r\n",
+                "        }   \r\n",
+                "    \r\n",
+                "     #Check if default subnet exists under specified Vnet \r\n",
+                "     $existingVnetSubnet =  az network vnet subnet show -n $SubNetName --vnet-name $VNetName -g $ResourceGroupName --subscription $Subscription -o JSON |ConvertFrom-Json\r\n",
+                "     if ($existingVnetSubnet){ #Found default subnet under specified VNet\r\n",
+                "         Write-Output \"Found default Subnet under specified VNet. `nSubNet ID '$($existingVnetSubnet.id)'\"\r\n",
+                "     } \r\n",
+                "     else { \r\n",
+                "         #VNet or defaut subnet not found under specified resource group. Create new VNet with default Subnet /Add default subnet to existing VNet\r\n",
+                "         Write-Output \"Creating new Virtual network with default Subnet ID ... \"\r\n",
+                "         $newVNet = az network vnet create --name \"$AdpVNET\" --resource-group $AdpResourceGroup --subscription $AdpSubscription  --subnet-name $SubNetName  -o JSON  |ConvertFrom-Json #vnet create/Update command: Bug: In this command, the output variable is not getting converted to PS objects.\r\n",
+                "         $newVNet =  az network vnet subnet show -g $AdpResourceGroup --vnet-name $AdpVNET -n $SubNetName --subscription $AdpSubscription -o JSON |ConvertFrom-Json # added this line due to above bug\r\n",
+                "         Write-Output \"Created VNet with default Subnet - ID: '$($newVNet.id)'\"\r\n",
+                "     }\r\n",
+                "}\r\n",
+                "\r\n",
+                "function Bootstrap-AzStorageAccount\r\n",
+                "{    \r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$StorageAccountName,\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$StorageAccountKind = \"StorageV2\",\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$StorageAccountSKU = \"Standard_LRS\"           \r\n",
+                "    )\r\n",
+                "\r\n",
+                "    $ListStorageAccountKind = \"BlobStorage\", \"BlockBlobStorage\", \"FileStorage\", \"Storage\", \"StorageV2\"\r\n",
+                "    $ListStorageAccountSKU = \"Premium_LRS\", \"Premium_ZRS\", \"Standard_GRS\", \"Standard_GZRS\", \"Standard_LRS\", \"Standard_RAGRS\", \"Standard_RAGZRS\", \"Standard_ZRS\"\r\n",
+                "\r\n",
+                "    \r\n",
+                "     #Validating parameter $ResourceGroupName\r\n",
+                "     $specified_RG = az group show -n $ResourceGroupName --subscription $Subscription 2>$null\r\n",
+                "     if(!$specified_RG) {\r\n",
+                "         Write-Host \"Invalid Input: ResourceGroupName. Resource Group $ResourceGroupName not found\"\r\n",
+                "         return\r\n",
+                "         } \r\n",
+                "    #Validating parameter $StorageAccountKind\r\n",
+                "    if($StorageAccountKind -notin $ListStorageAccountKind) {\r\n",
+                "        Write-Host \"StorageAccountKind '$StorageAccountKind' not valid. Accepted values: $ListStorageAccountKind\"  \r\n",
+                "        return\r\n",
+                "        }\r\n",
+                "    #Validating parameter $StorageAccountSKU\r\n",
+                "    if($StorageAccountSKU -notin $ListStorageAccountSKU) {\r\n",
+                "        Write-Host \"StorageAccountSKU '$StorageAccountSKU' not valid. Accepted values: $ListStorageAccountSKU\"  \r\n",
+                "        return\r\n",
+                "        }\r\n",
+                "    \r\n",
+                "    #Check if storage account exists under specified resource group    \r\n",
+                "    $existingStorageAccount = az storage account show -n $StorageAccountName -g $ResourceGroupName --subscription $Subscription  -o JSON| ConvertFrom-Json\r\n",
+                "\r\n",
+                "    if ($existingStorageAccount){ #Found specified storage account\r\n",
+                "        Write-Output \"Found storage account '$($existingStorageAccount.name)'\"\r\n",
+                "    } \r\n",
+                "    else { \r\n",
+                "        #Storage account not found under specified resource group. Create new storage account\r\n",
+                "        Write-Output \"Creating new storage account ... \"\r\n",
+                "        $newStorageAccount = az storage account create -n $StorageAccountName --kind $StorageAccountKind --sku $StorageAccountSKU -g $ResourceGroupName --subscription $Subscription -o JSON | ConvertFrom-Json\r\n",
+                "        Write-Output \"Created Storage Account '$($newStorageAccount.name)'\"\r\n",
+                "    }\r\n",
+                "}\r\n",
+                "\r\n",
+                "function Bootstrap-AzFunctionApp\r\n",
+                "{\r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$FunctionName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$StorageAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$FunctionAppPackageURL,\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$FunctionAppVersion=\"3\",\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ConsumptionPlanLocation         \r\n",
+                "    )   \r\n",
+                "     #Validating parameter $ResourceGroupName\r\n",
+                "     $specified_RG = az group show -n $ResourceGroupName --subscription $Subscription 2>$null\r\n",
+                "     if(!$specified_RG) {\r\n",
+                "         Write-Host \"Invalid Input: ResourceGroupName. Resource Group $ResourceGroupName not found\"\r\n",
+                "         return\r\n",
+                "         } \r\n",
+                "    #Validating parameter StorageAccountName\r\n",
+                "    $specified_storageAccount = az storage account show -n $StorageAccountName -g $ResourceGroupName --subscription $Subscription 2>$null\r\n",
+                "    if(!$specified_storageAccount){ \r\n",
+                "        Write-Host \"Invalid Input: StorageAccount name. Storage Account $StorageAccountName not found\"                  \r\n",
+                "        return\r\n",
+                "    }\r\n",
+                "    #Validating parameter FunctionAppVersion\r\n",
+                "    if($FunctionAppVersion -notin (2,3)){\r\n",
+                "        Write-Host \"FunctionAppVersion '$FunctionAppVersion' not valid. Accepted values: 2,3\"  \r\n",
+                "        return\r\n",
+                "    }\r\n",
+                "    #Validating parameter ConsumptionPlanLocation\r\n",
+                "    $specified_Location = az functionapp list-consumption-locations --query \"[?name == '$ConsumptionPlanLocation']\" -o JSON | ConvertFrom-Json \r\n",
+                "    if(!$specified_Location) { \r\n",
+                "        Write-Host \"ComsumptionPlanLocation '$ConsumptionPlanLocation' not valid. Refer below for available options: `n\" \r\n",
+                "        az functionapp list-consumption-locations --query \"[].{ConsumptionPlanLocations: name}\" -o table\r\n",
+                "        return\r\n",
+                "    }\r\n",
+                "    \r\n",
+                "    #Check if functionapp exists within the specified resource group\r\n",
+                "    $existingFunctionApp = az functionapp show -n $FunctionName -g $ResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "\r\n",
+                "    if ($existingFunctionApp){  #ensure that function application is linked to the specified storage account\r\n",
+                "\r\n",
+                "        #retrieve connection string of storage account linked to FunctionApp and check if the connected storage account is equal to the storage account passed to here\r\n",
+                "        $funcApp_Storage_ConnString= az functionapp config appsettings list -n $FunctionName -g $ResourceGroupName --subscription $Subscription --query \"[?name == 'AzureWebJobsStorage'].value\" -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "        $connectionString = Get-ConnectionStringProperties -ConnectionString \"$funcApp_Storage_ConnString\"\r\n",
+                "        $isConnected = $connectionString.AccountName -eq $StorageAccountName\r\n",
+                "\r\n",
+                "        if($isConnected) { \r\n",
+                "            Write-Output \"Found FunctionApp '$FunctionName'. `nConnected storage account is '$StorageAccountName'\"\r\n",
+                "        } \r\n",
+                "        else {\r\n",
+                "            Write-Output \"Found FunctionApp '$FunctionName'. `nConnected storage account is NOT '$StorageAccountName', but '$($connectionString.AccountName)' \"\r\n",
+                "        }\r\n",
+                "    } \r\n",
+                "    else { #Provision FunctionApp \r\n",
+                "\r\n",
+                "        $newFuncApp= az functionapp create  --name $FunctionName --resource-group $ResourceGroupName --subscription $Subscription --storage-account $StorageAccountName --functions-version $FunctionAppVersion --consumption-plan-location $ConsumptionPlanLocation -o JSON | ConvertFrom-JSON \r\n",
+                "        Write-Output \"`nCreated Function Application '$($newFuncApp.name)'\"\r\n",
+                "        $AppInsightsInstrumentationKey =  az functionapp config appsettings list --name $FunctionName --resource-group $ResourceGroupName --subscription $Subscription --query \"[?name == 'APPINSIGHTS_INSTRUMENTATIONKEY'].value\" -o JSON | ConvertFrom-JSON        \r\n",
+                "        az functionapp config appsettings set --name $FunctionName --settings \"WEBSITE_RUN_FROM_PACKAGE=$FunctionAppPackageURL\" \"APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=$AppInsightsInstrumentationKey\" `\r\n",
+                "                                               --resource-group $ResourceGroupName --subscription $Subscription -o none \r\n",
+                "        Write-Output \"Set '$($newFuncApp.name)' to run from package '$FunctionAppPackageURL'\"\r\n",
+                "        az functionapp identity assign -n $FunctionName -g $ResourceGroupName --subscription $Subscription -o none  \r\n",
+                "        Write-Output \"Enabled system assigned managed identity for  '$($newFuncApp.name)'\"        \r\n",
+                "    }\r\n",
+                "}\r\n",
+                "\r\n",
+                "function Bootstrap-AzBatchAccount\r\n",
+                "{\r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BatchAccountLocation,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BatchAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$StorageAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ApplicationPackageURL,\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ApplicationName=\"SqlPackageWrapper\",  \r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ApplicationPackageVersionName=\"1\"   \r\n",
+                "    )\r\n",
+                "    #Validating parameter $ResourceGroupName\r\n",
+                "    $specified_RG = az group show -n $ResourceGroupName --subscription $Subscription 2>$null\r\n",
+                "    if(!$specified_RG) {\r\n",
+                "        Write-Host \"Invalid Input: ResourceGroupName. Resource Group $ResourceGroupName not found\"\r\n",
+                "        return\r\n",
+                "        } \r\n",
+                "   #Validating parameter StorageAccountName\r\n",
+                "   $specified_storageAccount = az storage account show -n $StorageAccountName -g $ResourceGroupName --subscription $Subscription 2>$null\r\n",
+                "   if(!$specified_storageAccount){ \r\n",
+                "       Write-Host \"Invalid Input: StorageAccount name. Storage Account $StorageAccountName not found\"                  \r\n",
+                "       return\r\n",
+                "   }\r\n",
+                "    #Validating parameter $BatchAccountLocation\r\n",
+                "    $specified_BLocation = az account list-locations --query \"[?name == '$BatchAccountLocation']\" -o JSON | ConvertFrom-Json\r\n",
+                "    if(!$specified_BLocation) {\r\n",
+                "        Write-Host \"BatchAccountLocation '$BatchAccountLocation' not found. Refer below for the list of locations for the account:`n\" \r\n",
+                "        az account list-locations --query \"[].{Locations: name}\" -o table\r\n",
+                "        return\r\n",
+                "        }    \r\n",
+                "\r\n",
+                "    $SQLPackageWrapperLocal = \"$Env:USERPROFILE\" + \"\\Downloads\\batchApplicationPackage.zip\"   #path to download application package\r\n",
+                "    $downloaded= Test-Path $SQLPackageWrapperLocal \r\n",
+                "\r\n",
+                "    #check if Batch account exists in the specified resource group\r\n",
+                "    $existingBatchAccount = az batch account show -n $BatchAccountName -g $ResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "\r\n",
+                "    if ($existingBatchAccount){  #Batch Account exists, \r\n",
+                "        Write-output \"Found Batch Account '$BatchAccountName'\"\r\n",
+                "\r\n",
+                "        #checking if storage account is set for Batch Account\r\n",
+                "        #$existingStorageAccount = az storage account show -n $StorageAccountName -g $ResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json        # check if storage account set is ADP Storage A/c\r\n",
+                "        $connectedToStorage= $existingBatchAccount.autoStorage        #.storageAccountId -eq $existingStorageAccount.id \r\n",
+                "\r\n",
+                "        # check if latest application package is installed       \r\n",
+                "        $appPackageInstalled = az batch application package show --application-name $ApplicationName --version-name $ApplicationPackageVersionName -n $BatchAccountName -g $ResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "\r\n",
+                "        if($connectedToStorage -and $appPackageInstalled){ # BatchAccount connected to specified storageaccount and latest applicationpackage is installed\r\n",
+                "            $connectedStorageAccName = $existingBatchAccount.autoStorage.storageAccountId.Split(\"/\")[-1]\r\n",
+                "            Write-Output \"Already connected to Storage account '$connectedStorageAccName' `nFound latest application package installed. `nReusing existing Resources\"\r\n",
+                "        }\r\n",
+                "        else {\r\n",
+                "            if(!$connectedToStorage){ # Connect to ADPStorageAccount               \r\n",
+                "\r\n",
+                "                az batch account set --storage-account $StorageAccountName -n $BatchAccountName --resource-group $ResourceGroupName --subscription $Subscription -o none | ConvertFrom-JSON \r\n",
+                "                Write-Output \"No Storage Account Found. Connected to storage account '$StorageAccountName'\"\r\n",
+                "            }\r\n",
+                "            if(!$appPackageInstalled) { #application package is not installed, installing application package               \r\n",
+                "                if(!$downloaded) { Invoke-WebRequest -Uri $ApplicationPackageURL -OutFile $SQLPackageWrapperLocal } #downloads package to current user's downloads folder\r\n",
+                "                az batch application package create --application-name $ApplicationName --version-name $ApplicationPackageVersionName --package-file $SQLPackageWrapperLocal --name $BatchAccountName --resource-group $ResourceGroupName --subscription $Subscription -o none\r\n",
+                "                Write-Output \"No Application package found. Installed application package under '$ApplicationName'\"\r\n",
+                "            }\r\n",
+                "            Write-Output \"Batch Account '$BatchAccountName' is provisioned as required\"\r\n",
+                "        }\r\n",
+                "    } else { #Provision Batch Account\r\n",
+                "\r\n",
+                "        $newBatchAccount = az batch account create -n $BatchAccountName -l $BatchAccountLocation --resource-group $ResourceGroupName --subscription $Subscription -o JSON | ConvertFrom-JSON\r\n",
+                "        Write-Output \"Created Batch Account '$($newBatchAccount.name)'\" \r\n",
+                "        az batch account set --storage-account $StorageAccountName -n $BatchAccountName --resource-group $ResourceGroupName --subscription $Subscription -o none   \r\n",
+                "        Write-Output \"Set Storage Account to '$StorageAccountName'\"       \r\n",
+                "        if(!$downloaded) { Invoke-WebRequest -Uri $ApplicationPackageURL -OutFile $SQLPackageWrapperLocal } #downloads package to current user's downloads folder\r\n",
+                "        az batch application package create --application-name $ApplicationName --version-name $ApplicationPackageVersionName --package-file $SQLPackageWrapperLocal --name $BatchAccountName --resource-group $ResourceGroupName --subscription $Subscription -o none  \r\n",
+                "        Write-Output \"Installed application package '$ApplicationName'\"              \r\n",
+                "        \r\n",
+                "    }    \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Bootstrap-FuncRBAC {\r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param (\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Subscription,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$ResourceGroupName,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$AzFunctionName,\r\n",
+                "        [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string]$Role=\"Contributor\"\r\n",
+                "    )\r\n",
+                "\r\n",
+                "    # Get the resource group's scope\r\n",
+                "    $rg = az group show --resource-group $ResourceGroupName --subscription $Subscription | ConvertFrom-Json\r\n",
+                "    if (!$rg) {\r\n",
+                "        Write-Error \"Failed to setup RBAC for $AzFunctionName... Resource Group '$ResourceGroupName' not found on subscription '$Subscription'\"\r\n",
+                "        exit\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    $scope = $rg.id\r\n",
+                "    Write-Output \"Found scope... $scope\"\r\n",
+                "    \r\n",
+                "    # Get the az function principal id\r\n",
+                "    $app = az functionapp show -n $AzFunctionName --resource-group $ResourceGroupName --subscription $Subscription | ConvertFrom-Json\r\n",
+                "    if (!$app) {\r\n",
+                "        Write-Error \"Failed to setup RBAC for $AzFunctionName... Az function not found!\"\r\n",
+                "        exit\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    $prid = $app.identity.principalId\r\n",
+                "    Write-Output \"Found principal id of Azure function... $prid\"\r\n",
+                "\r\n",
+                "    # Verify if a role assignment has been created\r\n",
+                "    $assignments = az role assignment list --subscription $Subscription -g $ResourceGroupName | ConvertFrom-Json\r\n",
+                "\r\n",
+                "    # Get the correct assignment\r\n",
+                "    $svcPrincipal = $assignments | Where-Object {($_.scope -eq $scope) -and ($_.principalType -eq \"ServicePrincipal\") -and ($_.principalId -eq $prid)}\r\n",
+                "    $id = $svcPrincipal.id\r\n",
+                "    $def = $svcPrincipal.roleDefinitionName\r\n",
+                "\r\n",
+                "    Write-Output \"Found service principal role... '$def'\"    \r\n",
+                "    # Check if the right role is assigned\r\n",
+                "    if ($def -eq $Role)\r\n",
+                "    {            \r\n",
+                "        Write-Output \"Service Principle '$prid' found with '$Role' role. No work needed\"\r\n",
+                "        return\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    # Continue to setup RBAC, once we verify an assignment is not setup and all the resources exist\r\n",
+                "    Write-Output \"Creating new role assignment by running: 'az functionapp identity assign -g $ResourceGroupName -n $AzFunctionName --scope $scope --subscription $Subscription'\"\r\n",
+                "    Write-Warning \"If your account does not have the access to assign new roles as Owner or User Access Administrator for the resource group, than you will need to contact your Azure AD Administrator to assign a service principle using the commands above\"    \r\n",
+                "    az functionapp identity assign -g $ResourceGroupName -n $AzFunctionName --scope $scope --subscription $Subscription \r\n",
+                "}\r\n",
+                "\r\n",
+                "Write-Host \"Helper Functions Created successfully\"  "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f524afff-ccca-4e19-94a1-dbe21aee2241",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Connect to Azure and Select Subscription\r\n",
+                "Run the below cell to login to an Azure account. <b>Be sure to check the Windows Taskbar for a subscription selection dialog box.</b>\r\n",
+                "\r\n",
+                "_Note: the dialog box window may appear behind the active Azure Data Studio window._"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4952284c-dec7-4df3-9a31-044d795ef279"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Login-Azure"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "112d7a12-f66d-41c0-8a1c-0b234506d5ef",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create Resource Group\r\n",
+                "The Data Portability Notebooks provision several resources to enable import and export of Azure SQL DB databases and Azure SQL Managed Instance databases.  These resources include Storage, Azure Batch, Azure Function resources and their dependent Azure resources.  All resources created by this notebook are associated with a common Azure Resource Group to enable easy management of resources related to Data Portability.\r\n",
+                "\r\n",
+                "_Note: this notebook will reuse the Resource Group specified if it already exists._"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8298d187-3021-485c-b4d4-442da9aadd53"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Bootstrap-AzResourceGroup -ResourceGroupName $AdpResourceGroup -ResourceGroupLocation $AdpRegion -Subscription $AdpSubscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "9beb8d22-4560-4c7e-917b-5a3c0d58e1a2",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Provision Virtual Network\r\n",
+                "\r\n",
+                "The Data Portability resources include Virtual Machines created to execute the database import and export operations.  To provision this in a secure mode, this notebook will provision a Virtual Network to attach the Virtual Machines to.  These VMs do not need to be directly accessed so no publicly addressable ports should be created."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8fd75088-78fa-4cf3-b0f2-166813d0bed1"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Bootstrap-AzVirtualNetwork -VNetName $AdpVNET -ResourceGroupName $AdpResourceGroup -Subscription $AdpSubscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d014a6a6-57ff-4de7-8210-b3360bf34daa"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Provision Storage Account\r\n",
+                "\r\n",
+                "The Data Portability resources require a Storage Account to attach the Virtual Machines to.  This storage account is used for maintaining log files and other Azure Batch related information.  This Storage Account can be the same or different than the Storage Acount used during Import and Export operations to store the logical database (.bacpac) files."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4e91a3f7-a676-4a4e-9d6f-3a343fcc0bc0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Bootstrap-AzStorageAccount -StorageAccountName $AdpStorage -ResourceGroupName $AdpResourceGroup -Subscription $AdpSubscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "290498ee-3f31-4395-adab-a5fa93d28c80",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Provision Data Portability Orchestrator\r\n",
+                "\r\n",
+                "The Data Portability resources include an orchestration component which coordinates the import and export operations.  The orchestrator is built as an Azure Function and provides the REST APIs that the Import and Export notebooks use to either import or export an Azure SQL DB Server and its databases."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d1291c22-e5b7-42b3-bdd9-8a8d13fd3165"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Bootstrap-AzFunctionApp -FunctionName $AdpFunc -StorageAccountName $AdpStorage -FunctionAppPackageURL $AdpDownloadUrl `\r\n",
+                "    -ConsumptionPlanLocation $AdpRegion -ResourceGroupName $AdpResourceGroup -Subscription $AdpSubscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6fc2b5ec-c16f-4eb7-b2f9-c8c680d9a2df",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Provision Hosted SqlPackage Application\r\n",
+                "To Data Portability solution leverages SqlPackage and DacFx to perform the import and export of each database.  To host SqlPackage the solution uses an Azure Batch application.  Azure Batch provides the ability to create a pool of Virtual Machines that can perform the import and export operations.  This notebook will provision a pool of 2 virutal machines.  The machines are not actually allocated until the first Import or Export operation is performed using the Import or Export notebooks.  "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "126a5abd-a3f4-46e0-b311-8cd1b8639127"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Bootstrap-AzBatchAccount -BatchAccountName $AdpBatch -StorageAccountName $AdpStorage -BatchAccountLocation $AdpRegion `\r\n",
+                "    -ApplicationPackageURL $AdpWrapperUrl -ResourceGroupName $AdpResourceGroup  -Subscription $AdpSubscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "489733c4-1162-479b-82b4-b0c18954b25b",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Grant Data Portability Controller Access to Data Portability Resources\r\n",
+                "The Data Portability orchestrator requires Contributor access to the other Data Portability resources created.  To do this, Azure RBAC (role based access control) is used to grant access to the orchestrator so that it can communicate with the hosted SqlPackage application.  The function below will verify a Service Principal was created with the proper scope or attempt to create the role assignment. Any user executing this notebook will need to have Owner or User Access Administrator permissions to the Resource Group to assign the permission. Otherwise, contact your Azure AD Administrator. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "3d03a987-4bec-4c0b-afdb-e6ae3c828252"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Bootstrap-FuncRBAC -AzFunctionName $AdpFunc -ResourceGroupName $AdpResourceGroup -Subscription $AdpSubscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "75882d3a-2004-4304-ab8f-e5146e14500c",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 37 - 0
SQL-Hybrid-Cloud-Toolkit/content/glossary.md

@@ -0,0 +1,37 @@
+# Glossary
+[Home](readme.md)
+
+A list of terms and their definitions can be found below
+
+* **ADS** - *Azure Data Studio* is a desktop tool for managing Azure Data resources in the cloud, on-premises, or hybrid environments. 
+* **ADS Notebook** - Feature of ADS that enables creating, sharing text alongside code blocks for execution with various Notebook kernels.
+* **API** - *application programming interface*, a standard way for computers to interact programmatically such as through a web service or other function. 
+* **Azure** - a cloud computing service created by Microsoft.
+* **Azure Batch** - an Azure service that enables high performance parallel computing tasks in the cloud.
+* **Azure Blob Storage** - an Azure storage service used to store unstructured data as Binary Large Objects or BLOBs
+* **Azure CLI** - Azure *command line interface*, a useful desktop library to interact with Azure resources from the command line
+* **Azure Region** - Azure data centers are separated by geography into regions in order to ensure low-latency and security
+* **Azure Resource Group** - a logical container of Azure resources for management of Azure resources within it 
+* **Azure SQL Database** - an Azure based version of SQL database offered as a PaaS 
+* **Azure Storage Account** - provides a unique namespace in Azure which forms the basis for addressing objects in Azure Storage
+* **Azure Storage SKU** - a *stock keeping unit* or SKU is a product unit designation that distinguishes between the product and other products based on its attributes such as manufacturer, description, and product type. (see <a href="appendices.md">Appendices</a> for a list of Storage SKU types) 
+* **Azure Subscription** - associates a user with Azure resources created by that user
+* **CLI** - *command line interface*, allows execution of programs using textual processing line-by-line which is useful for automation using scripts or pasted input. 
+* **HADR** - *High Availability, Disaster Recovery* HA is the degree which a system is functional for customers. DR is a set of processes, techniques, and methodologies that ensure fault tolerance following natural and man-made disasters.
+* **IaaS** - *infrastructure-as-a-Service* a cloud service that allows provisioning and managing of infrastructure over the web
+* **Jupyter Book** - A collection of Jupyter Notebooks bound with a table of contents to define a notebook structure.
+* **Jupyter Kernel** - a wrapper program that handles requests for executing and inspecting code in a Notebook
+* **Juptyer Notebook** - an open source web application that is the underlying technology for ADS Notebooks that features live code, equations, and visualizations alongside narrative text. 
+* **Juptyer Project** - a non-profit, OSS project that supports interactive data science computing across all programming languages
+* **Markdown** - lightweight markup language with text formatting widely used in web documents
+* **Microsoft PowerShell** - a task automation and configuration management framework. ADS Notebooks feature a PowerShell kernel in order to interface with the PS environment. PS is often used by systems engineers and admins. 
+* **OSS** - *open source software* is a decentralized software development model that encourages open collaboration and sharing.   
+* **PaaS** - *Platform-as-a-Service* is a cloud service that allows the use of an application without the complexity of building and maintaining the hardware infrastructure required by the application
+* **Point-to-Site VPN** - a VPN solution that allows a secure connection from an individual client computer to a remote network on the VPN. 
+* **Python** - an interpreted high level OSS programming language often used by data scientists 
+* **Site-to-Site VPN** - a VPN connection created between two sites, usually over the public Internet or a WAN connection. Normally requires a network adminstrator to setup and configure. 
+* **SQL Assessment API** - evaluates a SQL instance configuration for best practices
+* **SQL Virtual Machine** - an IaaS Azure offer that provisions and manages virtual machine with SQL Server installed
+* **SQL Managed Instance** - a PaaS Azure offer for SQL Server that is ran on Azure infrastructure. Microsoft will manage the complexities of the infrastructure for the user  
+* **SMO** - SQL Management Objects are "objects designed for programmatic management of Microsoft SQL Server" ([Microsoft](https://docs.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/overview-smo))
+* **VPN** - a *virtual private network* is a collection of computing resources that organizes and extends a private network configuration over the public Internet, normally using some kind of encryption for security and privacy. 

+ 39 - 0
SQL-Hybrid-Cloud-Toolkit/content/hadr/add-passive-secondary.ipynb

@@ -0,0 +1,39 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "python3",
+            "display_name": "Python 3"
+        },
+        "language_info": {
+            "name": "python",
+            "version": "3.6.6",
+            "mimetype": "text/x-python",
+            "codemirror_mode": {
+                "name": "ipython",
+                "version": 3
+            },
+            "pygments_lexer": "ipython3",
+            "nbconvert_exporter": "python",
+            "file_extension": ".py"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Add Azure Passive Secondary Replica\n",
+                "============================================\n",
+                "\n",
+                "Description\n",
+                "-----------\n",
+                "\n",
+                "Notebook to walkthrough extending an on-premises Availability Group with an Azure Passive Secondary Replica."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a7c75090-5d5f-4a1b-8712-461a0921f4ad"
+            }
+        }
+    ]
+}

+ 369 - 0
SQL-Hybrid-Cloud-Toolkit/content/hadr/backup-to-blob.ipynb

@@ -0,0 +1,369 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Backup Database Azure Blob Storage\n",
+                "==================================\n",
+                "\n",
+                "Description\n",
+                "-----------\n",
+                "\n",
+                "Notebook to walk through Azure Storage configuration, key generation, and scripting of a backup to blob storage command for on-premises SQL Server Database."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6af59d69-ade7-480a-b33e-52a86fe5bfd3"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Steps of this procedure include:\r\n",
+                "1. Connect to Azure subscription\r\n",
+                "2. Provision resource group for SQL VM migration\r\n",
+                "3. Create a storage account\r\n",
+                "4. Create container for Blobs\r\n",
+                "5. Get Azure storage account key\r\n",
+                "6. Get Azure storage container Uri\r\n",
+                "7. Create Sql credentials on Sql server instances\r\n",
+                "8. Backup database on specific instance of Sql Server\r\n",
+                "9. Remove Sql credentials which were created on Sql server instances"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b911ea4f-a3d8-4ac1-bff6-6c5eb1b514f9"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "#Set Parameters\r\n",
+                "$Location = \"\"                           # Specify the valid Location such as 'West US 2','EASTUS' etc...\r\n",
+                "$ResourceGroup = \"\"                      # Resource group name(It should be of alphabets case insensitive)\r\n",
+                "$SkuName = \"\"                            # Specify the valid SKU...Such as 'Standard_LRS'\r\n",
+                "\r\n",
+                "$SqlPath = \"sqlserver:\\sql\\$($env:COMPUTERNAME)\"      #This script will generate Sql Path"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7ad525ec-4993-4e14-9677-4f77433b2123"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Sign in to Azure </b>\r\n",
+                "Sign in to your Azure Subscription with the _Connect-AzAccount_ command and follow the on-screen directions."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5dd9519d-3957-46ef-8988-440a043535b2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Connect-AzAccount"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "18c920f2-a19a-49d6-9766-2d7539f6fe43"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Get Subscription</b>\r\n",
+                "Below command will open a _**Dialouge Box**_ with list of subscriptions. Selecting one of those will set that Subscription for rest of the commands."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cc6cb8c6-76b7-41a0-ab26-7713e72c2f7d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$Subscription = Get-AzSubscription | Out-GridView -PassThru\r\n",
+                "Set-AzContext -SubscriptionName $Subscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "741c35fa-7923-4200-8c3a-497d62b4ae66"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "_If you don't know which Location you want to use, you can list the available Locations. Display the list of Locations by using the following code example and find the one you want to use. This example uses eastus. Store the Location in a variable and use the variable so you can change it in one place._"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "57de9eb7-a20b-4e9c-bf7b-ed6f2a838bee"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-AzLocation | select Location\r\n",
+                "$Location"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6b0ea659-6dcd-48e1-a0d3-47b4a3ea9d66"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "\r\n",
+                "### <b>Create a resource group</b>\r\n",
+                "Create an Azure resource group with _New-AzResourceGroup_. A resource group is a logical container into which Azure resources are deployed and managed."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "455a6002-e5d5-4cb0-9146-8d417917c751"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Create Azure resource group, if necessary\r\n",
+                "$ResourceGroup = Get-AzResourceGroup -Name $ResourceGroup\r\n",
+                "\r\n",
+                "if (!$ResourceGroup)\r\n",
+                "{\r\n",
+                "    # Need to create a new resource group\r\n",
+                "    Write-Output \"Resource Group $ResourceGroup does not exist. Creating...\"\r\n",
+                "    $ResourceGroup = New-AzResourceGroup -Name $RG1 -Location $Location\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d48948e1-3136-4e42-91e7-77a3301ae97d"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Create a storage account</b>\r\n",
+                "Create a standard, general-purpose storage account with LRS replication by using _New-AzStorageAccount_. Next, get the storage account context that defines the storage account you want to use. When acting on a storage account, reference the context instead of repeatedly passing in the credentials. Use the following example to create a storage account called storageaccountazure with locally redundant storage (LRS) and blob encryption (enabled by default)."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "246d062f-e7d1-4f42-ba9f-153a12a286e4"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$StorageAccountName = \"storageaccountazure\"\r\n",
+                "$StorageAccount = New-AzStorageAccount -ResourceGroupName $ResourceGroup  -Name $StorageAccountName  -SkuName $SkuName -Location $Location\r\n",
+                "\r\n",
+                "$Ctx = $StorageAccount.Context"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "17e3421b-a80a-455c-bd6a-25daaff46514"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Create a container</b>\r\n",
+                "Blobs are always uploaded into a container. You can organize groups of blobs like the way you organize your files on your computer in folders.\r\n",
+                "Set the container name, then create the container by using _New-AzStorageContainer_. Set the permissions to blob to allow public access of the files. The container name in this example is quickstartblobs."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "82503754-84ea-47b7-bcaf-31e5012da008"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$ContainerName = \"quickstartblobs\"\r\n",
+                "New-AzStorageContainer -Name $ContainerName -Context $Ctx -Permission blob"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c4557040-5ff7-4f31-9243-01b850270b90",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Get Azure Storage Acount Key</b>\r\n",
+                "This script will get the key for Storage Account which is been created."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f48b23bf-01bc-48df-afed-ad3eea69c41d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$StorageAccountKey = `\r\n",
+                "    (Get-AzStorageAccountKey `\r\n",
+                "    -ResourceGroupName $ResourceGroup `\r\n",
+                "    -Name $StorageAccountName).Value[0]"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e450bc4a-3373-4c7e-b0d0-f334712298b2"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Get Azure Storage Container Uri</b>\r\n",
+                "The following script can be used to get the Uri of Storage container."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5f7e616b-1e9f-4640-b61f-78d4327c5562"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "    $StorageUri = (Get-AzStorageAccount -ResourceGroupName $ResourceGroup -Name $StorageAccountName | Get-AzStorageContainer | Where-Object { $_.Name -eq $AzureContainerName }).CloudBlobContainer.Uri.AbsoluteUri "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "615d8e6e-2cbf-4001-8da0-1826185a06bf"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Create a SQL credential on all Instances of SQL Server </b>\r\n",
+                "The following script can be used to create a generic SQL credential on all the Instances of SQL Server on a computer. If there's already an existing credential with the same name on one of the Instances of the computer, the script shows the error and continues."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "000a1320-fecf-4e0b-8d1f-00ab39fc44ce"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "set-executionpolicy remotesigned\r\n",
+                "# load the sqlps module\r\n",
+                "import-module sqlps  \r\n",
+                "  \r\n",
+                "# set parameters\r\n",
+                "$SecureString = ConvertTo-SecureString $StorageAccountKey -AsPlainText -Force  \r\n",
+                "$CredentialName = \"myCredential-$(Get-Random)\"\r\n",
+                "\r\n",
+                "Write-Host \"Generate credential: \" $CredentialName\r\n",
+                "  \r\n",
+                "#cd to sql server and get Instances  \r\n",
+                "cd $SqlPath\r\n",
+                "$Instances = Get-ChildItem\r\n",
+                "\r\n",
+                "#loop through Instances and create a SQL credential, output any errors\r\n",
+                "foreach ($Instance in $Instances)  {\r\n",
+                "    try {\r\n",
+                "        $Path = \"$($SqlPath)\\$($Instance.DisplayName)\\credentials\"\r\n",
+                "        New-SqlCredential -Name $CredentialName -Identity $StorageAccountName -Secret $SecureString -Path $Path -ea Stop | Out-Null\r\n",
+                "        Write-Host \"...generated credential $($Path)\\$($CredentialName).\"  }\r\n",
+                "    catch { Write-Host $_.Exception.Message } }"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f59b2416-a41f-43c0-85ea-1e113e6f4221",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Full backup for system Databases on a specific Instance of SQL Server </b>\r\n",
+                "The full script can be used to back up the master and the msdb Databases on a named Instance of SQL Server. The same script can be used for any Instance by changing the Instance parameter value. SQL Server's default Instance is named DEFAULT."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cca91ca6-bf10-4e32-9b4f-80133049c1b5"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "set-executionpolicy remotesigned \r\n",
+                "import-module sqlps  \r\n",
+                "\r\n",
+                "$InstanceName = \"DEFAULT\"\r\n",
+                "$BackupUrlContainer = \"https://$StorageAccountName.blob.core.windows.net/$ContainerName/\"  \r\n",
+                "\r\n",
+                "Write-Host \"Backup Database: \" $InstanceName \" to \" $BackupUrlContainer\r\n",
+                "  \r\n",
+                "cd \"$($SqlPath)\\$($InstanceName)\"\r\n",
+                "\r\n",
+                "#loop through Instance and backup specific Databases\r\n",
+                "$Databases = \"master\"  \r\n",
+                "foreach ($Database in $Databases) {\r\n",
+                "    try {\r\n",
+                "        Write-Host \"...starting backup: \" $Database\r\n",
+                "        Backup-SqlDatabase -Database $Database -BackupContainer $BackupUrlContainer -SqlCredential $CredentialName -Compression On\r\n",
+                "        Write-Host \"...backup complete.\" }\r\n",
+                "    catch { Write-Host $_.Exception.Message } }"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "9b21ab69-5be2-4d09-ac4b-3d43521a1188"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Remove a SQL credential from Instances of SQL Server </b>\r\n",
+                "The following script can be used to remove a specific credential from all the Instances of SQL Server installed on the computer. If the credential does not exist on a specific Instance, an error message is displayed, and the script continues until all Instances are checked."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "63b41df3-6fe6-421e-9587-e5d65a0a5592"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "set-executionpolicy remotesigned \r\n",
+                "import-module sqlps\r\n",
+                "\r\n",
+                "Write-Host \"Delete credential: \" $CredentialName\r\n",
+                "\r\n",
+                "cd $SqlPath\r\n",
+                "$Instances = Get-ChildItem\r\n",
+                "\r\n",
+                "#loop through Instances and delete a SQL credential\r\n",
+                "foreach ($Instance in $Instances)  {\r\n",
+                "    try {\r\n",
+                "        $Path = \"$($SqlPath)\\$($Instance.DisplayName)\\credentials\\$($CredentialName)\"\r\n",
+                "        Remove-SqlCredential -Path $Path -ea Stop | Out-Null\r\n",
+                "        Write-Host \"...deleted credential $($Path).\"  }\r\n",
+                "    catch { Write-Host $_.Exception.Message } }"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e195e874-6178-4f87-b5e8-94524cbc6ea1"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 10 - 0
SQL-Hybrid-Cloud-Toolkit/content/hadr/readme.md

@@ -0,0 +1,10 @@
+# High Availability and Disaster Recovery
+
+[Home](../readme.md)
+
+Notebooks to help with HADR tasks in a Hybrid Cloud environment.
+
+## Notebooks in this Chapter
+- [Backup Database to Blob Storage](backup-to-blob.ipynb)
+
+- [Add Azure Passive Secondary Replica](add-passive-secondary.ipynb)

+ 168 - 0
SQL-Hybrid-Cloud-Toolkit/content/networking/download-VpnClient.ipynb

@@ -0,0 +1,168 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create and install VPN client configuration\r\n",
+                "Point-to-Site connections use certificates to authenticate. This article shows how to create a self-signed root certificate and generate client certificates using PowerShell on Windows 10 or Windows Server 2016. If needs are for different certificate instructions, see [Certificates - Linux or Certificates - MakeCert](https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-certificates-point-to-site-linux).\r\n",
+                "\r\n",
+                "The steps performed in this article are on a computer running Windows 10 or Windows Server 2016. The PowerShell cmdlets that used to generate certificates are part of the operating system and do not work on other versions of Windows. The Windows 10 or Windows Server 2016 computer is only needed to generate the certificates. Once the certificates are generated, it can be uploaded or installed on any supported client operating system."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7bf685b8-e375-47ed-93a3-5072c6a27235"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$RG = \"\"                 # Name of intended Resource Group\r\n",
+                "$GWName = \"\"             # VPN Gateway name\r\n",
+                "$P2SRootCertName = \"\"    # Name of Root Certificate. For example 'P2SRootCert2.cer'\r\n",
+                "$filePathForCert = \"\"    # Path of  the certificate file. For example 'D:\\Downloads\\rootcert2.cer'"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8eab9fb0-1e66-4a34-8e32-cf0644b157d9"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### **Create a self-signed root certificate**\r\n",
+                "_New-SelfSignedCertificate_ cmdlet is used to create a self-signed root certificate. For additional parameter information, see [New-SelfSignedCertificate](https://technet.microsoft.com/itpro/powershell/windows/pkiclient/new-selfsignedcertificate).\r\n",
+                "\r\n",
+                "Use the following example to create the self-signed root certificate. The following example creates a self-signed root certificate named 'P2SRootCert' that is automatically installed in 'Certificates-Current User\\Personal\\Certificates'. certificate can be viewed by opening certmgr.msc, or Manage User Certificates."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "656a1830-ee0b-4a3f-aa54-0eb3435d730a"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$cert = New-SelfSignedCertificate -Type Custom -KeySpec Signature `\r\n",
+                "-Subject \"CN=P2SRootCert\" -KeyExportPolicy Exportable `\r\n",
+                "-HashAlgorithm sha256 -KeyLength 2048 `\r\n",
+                "-CertStoreLocation \"Cert:\\CurrentUser\\My\" -KeyUsageProperty Sign -KeyUsage CertSign"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4348e0f6-957b-47f6-99db-8a4d4f6f4d25"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### **Generate a client certificate**\r\n",
+                "Each client computer that connects to a VNet using Point-to-Site must have a client certificate installed. A client certificate can be generated from the self-signed root certificate, and then it can export and install the client certificate. If the client certificate is not installed, authentication fails.\r\n",
+                "This example continues from the previous section and uses the declared '$cert' variable.\r\n",
+                "\r\n",
+                "Modify and run the example to generate a client certificate. If the following example is executed without modifying it, the result is a client certificate named 'P2SChildCert'. If name of the child certificate needs to be something else,then modify the CN value. Do not change the TextExtension when running this example. The client certificate which is generated is automatically installed in 'Certificates - Current User\\Personal\\Certificates' on computer."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a5a31ec0-2ead-4b41-9817-e81ea70082c0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "New-SelfSignedCertificate -Type Custom -DnsName P2SChildCert -KeySpec Signature `\r\n",
+                "-Subject \"CN=P2SChildCert\" -KeyExportPolicy Exportable `\r\n",
+                "-HashAlgorithm sha256 -KeyLength 2048 `\r\n",
+                "-CertStoreLocation \"Cert:\\CurrentUser\\My\" `\r\n",
+                "-Signer $cert -TextExtension @(\"2.5.29.37={text}1.3.6.1.5.5.7.3.2\")"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c993fb71-9c27-4b70-8477-3a0003825db4",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### **Replace the file path variable with file path to the exported root certificate, and then run the variable cmdlets:**"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5df2cb79-0fe4-414e-a1f4-9f2270634d54"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2($filePathForCert)\r\n",
+                "$CertBase64_3 = [system.convert]::ToBase64String($cert.RawData)\r\n",
+                "$p2srootcert = New-AzVpnClientRootCertificate -Name $P2SRootCertName -PublicCertData $CertBase64_3"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f75c1f83-82ca-4005-903e-935a787c1a5a"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### **Upload the public key information to Azure**\r\n",
+                "_New-AzVpnClientRootCertificate_ cmdlet creates a new VPN client root certificate"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8e6f2cba-020a-4fe0-ad1c-95528fbb54b0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                " New-AzVpnClientRootCertificate -VpnClientRootCertificateName $P2SRootCertName `\r\n",
+                "    -VirtualNetworkGatewayName $GWName -ResourceGroupName $RG `\r\n",
+                "    -PublicCertData $CertBase64_3"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2448dc49-ce6e-49f2-8abf-f824461738e6",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### **Verify that the root certificate uploaded**\r\n",
+                "_Get-AzVpnClientRootCertificate_ cmdlet gets information about VPN root certificates"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "20c00239-cd9f-4515-9275-c8797890744c"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-AzVpnClientRootCertificate -ResourceGroupName $RG -VirtualNetworkGatewayName $GWName"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "9f714fa9-f3ec-4a75-b736-05ec9b8948c0"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 225 - 0
SQL-Hybrid-Cloud-Toolkit/content/networking/p2svnet-creation.ipynb

@@ -0,0 +1,225 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Create Point to Site Virtual Private Network (VPN)\n",
+                "==================================\n",
+                "As part of the migration process, if you do not already have an Azure VPN and Gateway setup, this notebook will help you get one created and verify you have the connectivity necessary to securely host your new Azure SQL Server Virtual Machines. \n",
+                "\n",
+                "If a S2S connection is needed instead of a P2S, then see [Create and manage S2S VPN connections using PowerShell](https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-tutorial-vpnconnection-powershell) for more information. S2S may be out of scope for a data user and can be attempted by a network administrator.\n",
+                "\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6af59d69-ade7-480a-b33e-52a86fe5bfd3"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "There are a handful of steps you have to walk through to get your resource group and VPN configured.\r\n",
+                "\r\n",
+                "Steps of this procedure include:\r\n",
+                "1. Connect to Azure subscription\r\n",
+                "1. Provision resource group for VPN\r\n",
+                "2. Create VPN\r\n",
+                "3. Provision IP address for Gateway\r\n",
+                "4. Create Gateway\r\n",
+                "\r\n",
+                "<b>NOTE: Fill all the below variables with names in order to proceed ahead. </b>"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2dfbdfb6-adbd-4b3c-8eeb-76237b8145cb"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$RG1         = \"\"                     # Resource group name(It should be of alphabets case insensitive)\r\n",
+                "$VNet1       = \"\"                     # VPN name of your choice(It should be of alphabets case insensitive)\r\n",
+                "$Location1   = \"\"                     # Specify the valid location such as 'West US 2','EASTUS' etc...\r\n",
+                "$FESubnet1   = \"\"                     # Frontend subnet name of your choice(It should be of alphabets case insensitive)\r\n",
+                "$VNet1Prefix = \"\"                     # VNet default I.P format would be like this '10.1.0.0/16'\r\n",
+                "$FEPrefix1   = \"\"                     # Frontend Gateway default IP format would be '10.1.0.0/24'\r\n",
+                "$GwPrefix1   = \"\"                     # Default value for Gateway subnet would be '10.1.255.0/27'\r\n",
+                "$Gw1         = \"\"                     # Gateway name of your choice (It should be of alphabets case insensitive)\r\n",
+                "$GwIP1       = \"\"                     # Gateway IP name of your choice(It should be of alphabets case insensitive)\r\n",
+                "$GwIPConf1   = \"\"                     # Gateway IP Config name of your choice(It would be alphanumeric case insensitive)"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "424c911f-fb75-44f9-902d-c06dedf9eaf6"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## <b>Connect to Azure Account</b>"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "39c51f44-acf4-4142-af82-b5daa4d3b53f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Connect-AzAccount"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "32849f41-ef18-4f8c-b4c3-4863db3329df"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## <b>Get Subscription </b>\r\n",
+                "Below command will open a Dialouge Box with list of subscriptions.\r\n",
+                "Selecting one of those will set that subscription for rest of the commands."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "1c8c15f8-80af-463a-8113-a71b1a4725ea"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$subscription = Get-AzSubscription | Out-GridView -PassThru\r\n",
+                "Set-AzContext -SubscriptionName $subscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cf04dfea-4a33-455b-83e8-753d75383f41",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## <b>Create the resource group </b>\r\n",
+                "Create a resource group with the _New-AzResourceGroup_ command. An Azure resource group is a logical container into which Azure resources are deployed and managed. A resource group must be created first."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "200961d3-879b-4325-8db4-e08854a36e4d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Create Azure resource group, if necessary\r\n",
+                "$RG1 = Get-AzResourceGroup -Name $RG1\r\n",
+                "\r\n",
+                "if (!$RG1)\r\n",
+                "{\r\n",
+                "    # Need to create a new resource group\r\n",
+                "    Write-Output \"Resource Group $RG1 does not exist. Creating...\"\r\n",
+                "    $RG1 = New-AzResourceGroup -Name $RG1 -Location $Location1\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "dabfb7d5-aa62-48a6-bc81-a82814befec7"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## <b>Create a VPN</b>\r\n",
+                "Azure gateway provides cross-premises connectivity and P2S VPN server functionality for your VPN. Add a gateway to an existing VPN or create a new VPN and the gateway. Notice that the example specifies the name of the gateway subnet specifically. You must always specify the name of the gateway subnet as \"GatewaySubnet\" in order for it to function properly. This example creates a new VPN with two subnets: Frontend and GatewaySubnet using New-AzVirtualNetworkSubnetConfig and New-AzVirtualNetwork:"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "629badb8-5338-4418-bdac-6e91af6c732b"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$fesub1 = New-AzVirtualNetworkSubnetConfig -Name $FESubnet1 -AddressPrefix $FEPrefix1\r\n",
+                "$gwsub1 = New-AzVirtualNetworkSubnetConfig -Name 'GatewaySubnet' -AddressPrefix $GwPrefix1\r\n",
+                "$vnet   = New-AzVirtualNetwork `\r\n",
+                "            -Name $VNet1 `\r\n",
+                "            -ResourceGroupName $RG1 `\r\n",
+                "            -Location $Location1 `\r\n",
+                "            -AddressPrefix $VNet1Prefix `\r\n",
+                "            -Subnet $fesub1,$gwsub1"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "86ad1860-0e92-47b3-b198-1eac6187efe2"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## <b>Request a public IP address for the gateway</b>\r\n",
+                "Azure VPN gateways communicate with your on-premises VPN devices over the Internet to performs IKE (Internet Key Exchange) negotiation and establish IPsec tunnels. Create and assign a public IP address to your gateway as shown in the example below with New-AzPublicIpAddress and New-AzVirtualNetworkGatewayIpConfig:\r\n",
+                "\r\n",
+                " #### Important :\r\n",
+                " Currently, you can only use a Dynamic public IP address for the gateway. Static IP address is not supported on Azure VPN gateways."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e68e487a-9fe1-43e1-9201-ad6c2abcf81f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$gwpip    = New-AzPublicIpAddress -Name $GwIP1 -ResourceGroupName $RG1 `\r\n",
+                "              -Location $Location1 -AllocationMethod Dynamic\r\n",
+                "$subnet   = Get-AzVirtualNetworkSubnetConfig -Name 'GatewaySubnet' `\r\n",
+                "              -VirtualNetwork $vnet\r\n",
+                "$gwipconf = New-AzVirtualNetworkGatewayIpConfig -Name $GwIPConf1 `\r\n",
+                "              -Subnet $subnet -PublicIpAddress $gwpip"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "96800b54-48a8-463b-886c-3d0e96f29765"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## <b>Create a gateway </b>\r\n",
+                "\r\n",
+                "A gateway can take 45 minutes or more to create. Once the gateway creation has completed, you can create a connection between your VPN and another VNet. Or create a connection between your VPN and an on-premises location. Create a gateway using the New-AzVirtualNetworkGateway cmdlet."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6b4924b6-35b4-49c1-a2c8-fc45349ff09d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "New-AzVirtualNetworkGateway -Name $Gw1 -ResourceGroupName $RG1 `\r\n",
+                "  -Location $Location1 -IpConfigurations $gwipconf -GatewayType Vpn `\r\n",
+                "  -VpnType RouteBased -GatewaySku VpnGw1"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e046ac0a-39fc-48e8-a7f8-26adb3a188e1"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 13 - 0
SQL-Hybrid-Cloud-Toolkit/content/networking/readme.md

@@ -0,0 +1,13 @@
+# Networking
+[Home](../readme.md)
+
+This chapter contains notebooks to configure and make a secure network connection in an Azure hybrid cloud environment. 
+
+<img width="50%" src="https://docs.microsoft.com/en-us/azure/vpn-gateway/media/point-to-site-about/p2s.png">
+
+## Notebooks in this Chapter
+- [Download VPN Client Certificate](download-VpnClient.ipynb) - Used to install certificates that encrypt communication between on-site and Azure services
+
+- [Create Point-to-Site VPN](p2svnet-creation.ipynb) - Enables secure **Point-to-Site** (P2S) communication between a virtual private network in Azure and local resources. P2S is used by individuals and small groups for remote connectivity. A Point-to-Site (P2S) VPN gateway connection lets you create a secure connection to your VPN from an individual client computer. A P2S connection is established by starting it from the client computer. This solution is useful for telecommuters who want to connect to Azure VNets from a remote location, such as from home or a conference. P2S VPN is also a useful solution to use instead of S2S VPN when you have only a few clients that need to connect to a virtual network.
+
+- [Create Site-to-Site VPN](s2svnet-creation.ipynb) - **Site-to-site** (S2S) is normally used by organizations that want greater control between on-premise and cloud resources using a VPN gateway. A S2S VPN gateway connection is used to connect your on-premises network to an Azure virtual network over an IPsec/IKE (IKEv1 or IKEv2) VPN tunnel. This type of connection requires a VPN device located on-premises that has an externally facing public IP address assigned to it. For more information about VPN gateways, see [About VPN gateway](https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-about-vpngateways) and [Create and manage S2S VPN connections using PowerShell](https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-tutorial-vpnconnection-powershell "https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-tutorial-vpnconnection-powershell"). **NOTE:** *May require the help of a Network Administrator or similar role to setup a secure Gateway*.

+ 372 - 0
SQL-Hybrid-Cloud-Toolkit/content/networking/s2svnet-creation.ipynb

@@ -0,0 +1,372 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a Site-to-Site Virtual Private Network in Azure\n",
+                "\n",
+                "* * *\n",
+                "\n",
+                "This notebook uses [PowerShell Az.Network cmdlets](https://docs.microsoft.com/en-us/powershell/module/az.network/ \"https://docs.microsoft.com/en-us/powershell/module/az.network/\") to create a S2S VPN gateway connection from an on-premises network to an Azure Virtual Network (VPN).\n",
+                "\n",
+                "![](https://docs.microsoft.com/en-us/azure/vpn-gateway/media/vpn-gateway-tutorial-vpnconnection-powershell/site-to-site-diagram.png)\n",
+                "\n",
+                "Use these parameters to create the environment or to better understand the examples in this notebook.\n",
+                "\n",
+                "| Line # | Name | Example | Description |\n",
+                "| --- | --- | --- | --- |\n",
+                "| 1 | **Subscription** | \"\" | Name or ID (guid) of Azure Subscription to setup S2S VPN in. |\n",
+                "| 2 | **ResourceGroup** | \"TestRG1\" | Name of new or existing resource group (RG). An Azure Resource Group is a collection of Azure resources that share the same permissions, policies, etc. In this case, the Resource Group for the virtual network is specified.  |\n",
+                "| 3 | **VnetName** | \"VNet1\" | Alphanumeric value represents the name of the Azure resource to create. |\n",
+                "| 4 | **Location** | \"East US\" | Value representing the region or location of the RG. See [Azure Geographies](https://azure.microsoft.com/en-us/global-infrastructure/geographies/ \"https://azure.microsoft.com/en-us/global-infrastructure/geographies/\") for more information. |\n",
+                "| 5 | **AddressSpace** | \"10.1.0.0/16\" | Defines a range of IP prefixes in the remote subnet. |\n",
+                "| 6 | **SubnetName** | \"Frontend\" |  |\n",
+                "| 7 | **Subnet** | \"10.1.0.0/24\" |  |\n",
+                "| 8 | **GatewaySubnet** | \"10.1.255.0/27\" | Specify the number of IP addresses that the subnet contains. The number of IP addresses needed depends on the VPN gateway configuration created. Some configurations require more IP addresses than others. It is recommended to create a gateway subnet that uses a /27 or /28. |\n",
+                "| 9 | **LocalNetworkGatewayName** | \"Site1\" |  |\n",
+                "| 10 | **LNGPublicIP** | \"192.168.29.46\" |  |\n",
+                "| 11 | **LocalAddressPrefix** | \"10.101.0.0/24, 10.101.1.0/24\" |  |\n",
+                "| 12 | **GatewayName** | \"GatewaySubnet\" |  |\n",
+                "| 13 | **PublicIP** | \"VNet1GWPIP\" |  |\n",
+                "| 14 | **GatewayIPConfig** | \"gwipconfig1\" |  |\n",
+                "| 15 | **VPNType** | \"RouteBased\" |  |\n",
+                "| 16 | **GatewayType** | \"Vpn\" |  |\n",
+                "| 17 | **ConnectionName** | \"VNet1toSite1\" |  |"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "130ad787-0d85-4edb-9cab-62824de6993f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$Subscription               = \"\"\r\n",
+                "$ResourceGroup              = \"\"\r\n",
+                "$VnetName                   = \"\"\r\n",
+                "$Location                   = \"\"\r\n",
+                "$AddressSpace               = \"\"\r\n",
+                "$SubnetName                 = \"\"\r\n",
+                "$Subnet                     = \"\"\r\n",
+                "$GatewaySubnet              = \"\"\r\n",
+                "$LocalNetworkGatewayName    = \"\"\r\n",
+                "$LNGPublicIP                = \"\"\r\n",
+                "$LocalAddressPrefixes       = \"\"\r\n",
+                "$GatewayName                = \"\"\r\n",
+                "$PublicIP                   = \"\"\r\n",
+                "$GatewayIPConfig            = \"\"\r\n",
+                "$VPNType                    = \"\"\r\n",
+                "$GatewayType                = \"\"\r\n",
+                "$ConnectionName             = \"\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f883288a-fc6a-4b0f-9215-6b771dc72b8d"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "There are a handful of steps to setup a resource group and configure the VPN:\n",
+                "\n",
+                "1.  Create or specify a resource group\n",
+                "2.  Create a virtual network\n",
+                "3.  Create a subnet configuration\n",
+                "4.  Set the subnet configuration for the virtual network\n",
+                "5.  Add a gateway subnet\n",
+                "6.  Set the subnet configuration for the virtual network\n",
+                "7.  Request a public IP address\n",
+                "8.  Create the gateway IP address configuration\n",
+                "9.  Create the VPN gateway\n",
+                "10.  Create the local network gateway\n",
+                "11.  Create the VPN connection"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a84bd796-4838-480a-83c5-48305870d2f0"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a resource group"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "009ae97a-8d89-45bb-9f0f-63ab3943f151"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "New-AzResourceGroup -Name $ResourceGroup -Location $Location"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "0e792bfb-5775-437c-b084-47593e5ce314"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a virtual network\n",
+                "\n",
+                "This example creates a virtual network and a gateway subnet. If the virtual network is already present then gateway subnet is needed, see [To add a gateway subnet to a virtual network which is already created](https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-create-site-to-site-rm-powershell#gatewaysubnet \"https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-create-site-to-site-rm-powershell#gatewaysubnet\"). When creating a virtual network, make sure that the address spaces don't overlap any of the address spaces that are in on-premises network."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "53145be6-8eb0-4857-9029-888b55a12e85"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$virtualNetwork = New-AzVirtualNetwork `\r\n",
+                "  -ResourceGroupName $ResourceGroup `\r\n",
+                "  -Location $Location `\r\n",
+                "  -Name $VnetName `\r\n",
+                "  -AddressPrefix $AddressSpace"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f473191d-5bdd-4678-925d-bd26cf0fe53d"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a subnet configuration"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "eaa39a66-f89d-4669-bcce-6e205c3ef5a1"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$subnetConfig = Add-AzVirtualNetworkSubnetConfig `\r\n",
+                "  -Name $SubnetName `\r\n",
+                "  -AddressPrefix $Subnet `\r\n",
+                "  -VirtualNetwork $virtualNetwork"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cfbee82d-5950-426a-8887-06cc023b8dfd"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Set the subnet configuration for the virtual network"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d271f4fd-045e-4920-a384-9894c226e666"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$virtualNetwork | Set-AzVirtualNetwork"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6bee9590-7149-4233-954e-25a897800aaa"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Add a gateway subnet\r\n",
+                "The virtual network gateway uses specific subnet called the gateway subnet. The gateway subnet is part of the virtual network IP address range that you specify when configuring your virtual network. It contains the IP addresses that the virtual network gateway resources and services use. The subnet must be named 'GatewaySubnet' in order for Azure to deploy the gateway resources. You can't specify a different subnet to deploy the gateway resources to. If you don't have a subnet named 'GatewaySubnet', when you create your VPN gateway, it will fail."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "27e2625c-ebef-4355-8667-c817c52dbf3a"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$vnet = Get-AzVirtualNetwork -ResourceGroupName $ResourceGroup -Name $VnetName\r\n",
+                "Add-AzVirtualNetworkSubnetConfig -Name $GatewayName -AddressPrefix $GatewaySubnet -VirtualNetwork $vnet"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4f13a28f-e6be-424a-942f-a8562c104787"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Set the subnet configuration for the virtual network"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "25aed948-7fcd-440f-bb37-bb677cae5c79"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$vnet | Set-AzVirtualNetwork"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2b0cbaff-5aea-4029-9603-5eb7c75688b3"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Request a public IP address\r\n",
+                "A VPN gateway must have a Public IP address. It first requests the IP address resource, and then refer to it when creating virtual network gateway. The IP address is dynamically assigned to the resource when the VPN gateway is created.\r\n",
+                "\r\n",
+                "VPN Gateway currently only supports Dynamic Public IP address allocation. It cannot request a Static Public IP address assignment. However, this does not mean that the IP address will change after it has been assigned to created VPN gateway. The only time the Public IP address changes is when the gateway is deleted and re-created. It doesn't change across resizing, resetting, or other internal maintenance/upgrades of created VPN gateway."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c664e2c9-d9fc-4913-aaf8-8e3b3d40d7a8"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$gwpip= New-AzPublicIpAddress -Name $PublicIP -ResourceGroupName $ResourceGroup -Location $Location `\r\n",
+                " -AllocationMethod Dynamic"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "1d8eef52-b085-4eb3-8130-0c16232d89b1"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create the gateway IP address configuration\r\n",
+                "The gateway configuration defines the subnet (the 'GatewaySubnet') and the public IP address to use. Use the following example to create gateway configuration:"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a351a10f-54a6-4a78-be15-38c96d94bae0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$vnet = Get-AzVirtualNetwork -Name $VnetName -ResourceGroupName $ResourceGroup\r\n",
+                "$subnet = Get-AzVirtualNetworkSubnetConfig -Name $GatewayName -VirtualNetwork $vnet\r\n",
+                "$gwipconfig = New-AzVirtualNetworkGatewayIpConfig -Name $GatewayIPConfig -SubnetId $subnet.Id -PublicIpAddressId $gwpip.Id"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b1f1662a-21a0-48b6-b5b6-5f9a15e1d662"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a gateway\r\n",
+                "A gateway can take 45 minutes or more to create. Once the gateway creation has completed, you can create a connection between your VPN and another VNet. Or create a connection between your VPN and an on-premises location. Create a gateway using the _New-AzVirtualNetworkGateway_ cmdlet."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b9adcd02-bb2a-4c57-b831-68f74f1d2fdc"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "New-AzVirtualNetworkGateway -Name $GatewayName -ResourceGroupName $ResourceGroup `\r\n",
+                " -Location $Location -IpConfigurations $gwipconfig -GatewayType $GatewayType `\r\n",
+                " -VpnType $VPNType -GatewaySku VpnGw1"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "9235b9b3-7cbe-4a4d-bc5e-f82e3e34bd4b"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create the local network gateway\r\n",
+                "The local network gateway (LNG) typically refers to on-premises location. It is not the same as a virtual network gateway. Give the site a name by which Azure can refer to it, then specify the IP address of the on-premises VPN device to the connection to be created. Also specify the IP address prefixes that will be routed through the VPN gateway to the VPN device. The address prefixes specified are the prefixes located on on-premises network. If on-premises network changes, it can easily update the prefixes."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5452ac24-2bcf-475d-8284-8ea2cfce8ac3"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "New-AzLocalNetworkGateway -Name $LocalNetworkGatewayName -ResourceGroupName $ResourceGroup `\r\n",
+                " -Location $Location -GatewayIpAddress '192.168.29.46' -AddressPrefix @('10.101.0.0/24','10.101.1.0/24')"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "0e212918-89aa-474e-ad6d-73e8cefc3b25"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create the VPN connection\r\n",
+                "Create the Site-to-Site VPN connection between virtual network gateway and VPN device. Be sure to replace the values. The shared key must match the value used for VPN device configuration. Notice that the '-ConnectionType' for Site-to-Site is IPsec."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "08ffbbd7-deba-4472-950f-b92c83a0dbc2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$gateway1 = Get-AzVirtualNetworkGateway -Name $GatewayName -ResourceGroupName $ResourceGroup\r\n",
+                "$local = Get-AzLocalNetworkGateway -Name $LocalNetworkGatewayName -ResourceGroupName $ResourceGroup\r\n",
+                "New-AzVirtualNetworkGatewayConnection -Name $ConnectionName -ResourceGroupName $ResourceGroup `\r\n",
+                " -Location $Location -VirtualNetworkGateway1 $gateway1 -LocalNetworkGateway2 $local `\r\n",
+                " -ConnectionType IPsec -RoutingWeight 10 -SharedKey 'abc123'"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c7d2e8dc-97ee-4764-8a39-704dfd1cd815"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify the VPN connection\r\n",
+                "Connection can be verified if it is succeeded by using the _Get-AzVirtualNetworkGatewayConnection_ cmdlet, with or without '-Debug'."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2dd64b80-488d-4ada-9805-ef7fe59d4058"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-AzVirtualNetworkGatewayConnection -Name $ConnectionName -ResourceGroupName $ResourceGroup"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "03acce02-74fb-48eb-a38d-f2dde1a2e85e"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 39 - 0
SQL-Hybrid-Cloud-Toolkit/content/offline-migration/db-to-MI.ipynb

@@ -0,0 +1,39 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "python3",
+            "display_name": "Python 3"
+        },
+        "language_info": {
+            "name": "python",
+            "version": "3.7.8",
+            "mimetype": "text/x-python",
+            "codemirror_mode": {
+                "name": "ipython",
+                "version": 3
+            },
+            "pygments_lexer": "ipython3",
+            "nbconvert_exporter": "python",
+            "file_extension": ".py"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Migrate a Database to a Azure SQL Managed Instance\n",
+                "=============================================\n",
+                "\n",
+                "Description\n",
+                "-----\n",
+                "\n",
+                "Copies the database from an on-premises SQL instance to an Azure SQL Managed Instance."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5353c044-9920-478b-b1f8-e98119b73a21"
+            }
+        }
+    ]
+}

+ 339 - 0
SQL-Hybrid-Cloud-Toolkit/content/offline-migration/db-to-SQLDB.ipynb

@@ -0,0 +1,339 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Migrate SQL Server Instance to Azure SQL Server VM\n",
+                "================================"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f706da59-22c3-4317-bf41-c00dde794097"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Load Required Modules\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "67e288c6-63df-475e-9cad-bab323d30c4e"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Import-Module dbatools\r\n",
+                "Import-Module Az.Resources\r\n",
+                "Import-Module Az.Storage"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "26df73fa-6f4f-40b4-8c47-10ce7e2db404",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Choose Migration Source\r\n",
+                "\r\n",
+                "Required parameters:\r\n",
+                "\r\n",
+                "- Server Name\r\n",
+                "- Database Name\r\n",
+                "- "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "1e70b806-c94d-4be2-87cf-ad73fb85821d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$Credential = Get-Credential -Message \"Type the name and password of the local administrator account.\"\r\n",
+                "$Credential"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "41faae8f-6245-4acb-88d5-dbb0b92ad7f5"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Variables\r\n",
+                "\r\n",
+                "## Global\r\n",
+                "$Location = \"West US 2\"\r\n",
+                "$ResourceGroupName = \"sqlmig\"\r\n",
+                "\r\n",
+                "## Storage\r\n",
+                "$StorageName = $ResourceGroupName + \"storage\"\r\n",
+                "$StorageSku = \"Premium_LRS\"\r\n",
+                "\r\n",
+                "## Network\r\n",
+                "$InterfaceName = $ResourceGroupName + \"ServerInterface\"\r\n",
+                "$NsgName = $ResourceGroupName + \"nsg\"\r\n",
+                "$VNetName = $ResourceGroupName + \"VNet\"\r\n",
+                "$SubnetName = \"Default\"\r\n",
+                "$VNetAddressPrefix = \"10.0.0.0/16\"\r\n",
+                "$VNetSubnetAddressPrefix = \"10.0.0.0/24\"\r\n",
+                "$TCPIPAllocationMethod = \"Dynamic\"\r\n",
+                "$DomainName = $ResourceGroupName\r\n",
+                "\r\n",
+                "##Compute\r\n",
+                "$VMName = $ResourceGroupName + \"VM\"\r\n",
+                "$ComputerName = $ResourceGroupName + \"Server\"\r\n",
+                "$VMSize = \"Standard_DS13_v2\"\r\n",
+                "$OSDiskName = $VMName + \"OSDisk\"\r\n",
+                "\r\n",
+                "##Image\r\n",
+                "$PublisherName = \"MicrosoftSQLServer\"\r\n",
+                "$OfferName = \"SQL2017-WS2016\"\r\n",
+                "$Sku = \"SQLDEV\"\r\n",
+                "$Version = \"latest\"\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7e251e62-b47c-4800-986d-b71be8bc0a21"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Resource Group\r\n",
+                "New-AzResourceGroup -Name $ResourceGroupName -Location $Location\r\n",
+                "$ResourceGroupName\r\n",
+                "$Location"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "eea4904c-55f0-47a4-81a3-5daf0a864687"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Storage\r\n",
+                "$StorageAccount = New-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageName -SkuName $StorageSku -Kind \"Storage\" -Location $Location\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b19f1450-0283-4772-862a-3e59b65d0e75"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$StorageAccount = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageName"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "42602cef-76ed-4c91-a2f0-a7a8a6132eff"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Network\r\n",
+                "$SubnetConfig = New-AzVirtualNetworkSubnetConfig -Name $SubnetName -AddressPrefix $VNetSubnetAddressPrefix\r\n",
+                "$VNet = New-AzVirtualNetwork -Name $VNetName -ResourceGroupName $ResourceGroupName -Location $Location -AddressPrefix $VNetAddressPrefix -Subnet $SubnetConfig\r\n",
+                "$PublicIp = New-AzPublicIpAddress -Name $InterfaceName -ResourceGroupName $ResourceGroupName -Location $Location -AllocationMethod $TCPIPAllocationMethod -DomainNameLabel $DomainName\r\n",
+                "#$NsgRuleRDP = New-AzNetworkSecurityRuleConfig -Name \"RDPRule\" -Protocol Tcp -Direction Inbound -Priority 1000 -SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange 3389 -Access Allow\r\n",
+                "$NsgRuleSQL = New-AzNetworkSecurityRuleConfig -Name \"MSSQLRule\"  -Protocol Tcp -Direction Inbound -Priority 1001 -SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange 1433 -Access Allow\r\n",
+                "#$Nsg = New-AzNetworkSecurityGroup -ResourceGroupName $ResourceGroupName -Location $Location -Name $NsgName -SecurityRules $NsgRuleRDP,$NsgRuleSQL\r\n",
+                "$Nsg = New-AzNetworkSecurityGroup -ResourceGroupName $ResourceGroupName -Location $Location -Name $NsgName -SecurityRules $NsgRuleSQL\r\n",
+                "$Interface = New-AzNetworkInterface -Name $InterfaceName -ResourceGroupName $ResourceGroupName -Location $Location -SubnetId $VNet.Subnets[0].Id -PublicIpAddressId $PublicIp.Id -NetworkSecurityGroupId $Nsg.Id\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "0435fd83-3e9c-4929-930a-2b7022db3f99"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$StorageAccount"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "37e1869b-eb9f-465a-87d3-78120e4c6d06"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Compute\r\n",
+                "$VirtualMachine = New-AzVMConfig -VMName $VMName -VMSize $VMSize\r\n",
+                "#$Credential = Get-Credential -Message \"Type the name and password of the local administrator account.\"\r\n",
+                "$VirtualMachine = Set-AzVMOperatingSystem -VM $VirtualMachine -Windows -ComputerName $ComputerName -Credential $Credential -ProvisionVMAgent -EnableAutoUpdate #-TimeZone = $TimeZone\r\n",
+                "$VirtualMachine = Add-AzVMNetworkInterface -VM $VirtualMachine -Id $Interface.Id\r\n",
+                "#$OSDiskUri = $StorageAccount.PrimaryEndpoints.Blob.ToString() + \"vhds/\" + $OSDiskName + \".vhd\"\r\n",
+                "#$VirtualMachine = Set-AzVMOSDisk -VM $VirtualMachine -Name $OSDiskName -VhdUri $OSDiskUri -Caching ReadOnly -CreateOption FromImage\r\n",
+                "$VirtualMachine = Set-AzVMSourceImage -VM $VirtualMachine -PublisherName $PublisherName -Offer $OfferName -Skus $Sku -Version $Version"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d0a4da84-d591-436e-8b6c-d4cb2787a6e2"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Create the VM in Azure\r\n",
+                "New-AzVM -ResourceGroupName $ResourceGroupName -Location $Location -VM $VirtualMachine\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "70967b75-1ef7-4d69-aad9-0184cfb44aa9"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Add the SQL IaaS Extension, and choose the license type\r\n",
+                "New-AzSqlVM -ResourceGroupName $ResourceGroupName -Name $VMName -Location $Location -LicenseType \"PAYG\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a8db01b1-a009-4367-bd39-77187482afc3"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Verify No Active Connections"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "81259d7e-62ac-4cdd-9e1b-2cb4ddb3d3b2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "\r\n",
+                "$ServerName = \"sqltools2017-3\"\r\n",
+                "$DatabaseName = \"Keep_WideWorldImporters\"\r\n",
+                "\r\n",
+                "Get-DbaProcess -SqlInstance $ServerName -Database $DatabaseName | \r\n",
+                "Select Host, login, Program"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "28393e59-4ea1-4f0f-8f9f-8a504f15e723"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Create Target SQL Server VM"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "07d076d0-abf3-496c-8ecb-f85102c4104b"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6f190c2d-7361-4db8-819d-29087eae8aaa"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Create temporary resources for data movement"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cc18027e-4636-465d-abaf-f3de88fea406"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$location = \"westus\"\r\n",
+                "$resourceGroup = \"temp-sqlmigration\"\r\n",
+                "$blobStorageAccount = \"temp-sqlmigration\"\r\n",
+                "$containerName = \"backups\"\r\n",
+                "\r\n",
+                "\r\n",
+                "\r\n",
+                "New-AzResourceGroup"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f7d53cb1-a55d-4634-95f7-d3e8cf9fab52"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$targetLogin = Get-Credential -Message \"Login to target SQL Server instance as:\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c3dbd1a7-5514-4fdc-9430-736c92e875a4"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$myGwIp = Get-AzPublicIpAddress -Name $GwIP1 -ResourceGroup $RG1\r\n",
+                "$myGwIp.IpAddress"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "bc6e8330-95bb-44a5-a117-020f657cad2b"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a61ffe69-0929-4246-8ad0-846f540f4e0c"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 271 - 0
SQL-Hybrid-Cloud-Toolkit/content/offline-migration/db-to-VM.ipynb

@@ -0,0 +1,271 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Migrate SQL Server Database to Azure SQL Server VM\n",
+                "================================"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f706da59-22c3-4317-bf41-c00dde794097"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Source SQL Instance\r\n",
+                "The following code is used to specify the source SQL Server instance.  Data and Server objects will be copied from this server to the target SQL Server instance.  In the following code cell set the following parameters:\r\n",
+                "\r\n",
+                "*Note: the notebook currently is setup for SQL Authentication.  Future updates will add support for multiple authentication types.*\r\n",
+                "\r\n",
+                "|Parameter|Description|\r\n",
+                "|---|---|\r\n",
+                "|sourceServerName| The name or IP address of the source instance|\r\n",
+                "|sourceLogin| sql login to connect to source instance with |\r\n",
+                "\r\n",
+                "*Note: source password should be set in the environment variable SQLMIG_SourcePassword.  This is to avoid persisting the environment variable in the notebook file.*\r\n",
+                "\r\n",
+                "Edit the code below to specify the above parameters to test connectivity to the source instance.\r\n",
+                "\r\n",
+                "\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2ac081f4-853a-4381-a303-e6ca557503fb"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$sourceServerName = 'sqltools2016-3'\r\n",
+                "$sourceLogin = 'migtest'\r\n",
+                "\r\n",
+                "## TEMP - REMOVE BEFORE PUSHING CHANGES\r\n",
+                "$env:SQLMIG_SourcePassword = 'Yukon900'\r\n",
+                "\r\n",
+                "## PowerShell Environment \r\n",
+                "$sourceLoginPassword = ConvertTo-SecureString $env:SQLMIG_SourcePassword -AsPlaintext -Force\r\n",
+                "$sourceCredential = New-Object System.Management.Automation.PSCredential ('migtest', $sourceLoginPassword)\r\n",
+                "$sourceTest = Test-DbaConnection -SqlInstance $sourceServerName -SqlCredential $sourceCredential\r\n",
+                "$sourceTest\r\n",
+                "$sourceConnection = Connect-DbaInstance -SqlInstance $sourceServerName -SqlCredential $sourceCredential"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "76a50416-b804-46ae-a49c-99baaeb31f7d"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Target SQL Instance\r\n",
+                "The following code is used to specify the target SQL Server instance.  This is the SQL Server instance that Data and Server objects will be copied to.  In the following code cell set the following parameters:\r\n",
+                "\r\n",
+                "*Note: the notebook currently is setup for SQL Authentication.  Future updates will add support for multiple authentication types.*\r\n",
+                "\r\n",
+                "|Parameter|Description|\r\n",
+                "|---|---|\r\n",
+                "|targetServerName| The name or IP address of the target instance|\r\n",
+                "|targetLogin| sql login to connect to target instance with |\r\n",
+                "\r\n",
+                "*Note: the target login password should be set in the environment variable SQLMIG_TargetPassword.  Thisis to avoid persisting the environment variable in the notebook file.*\r\n",
+                "\r\n",
+                "Edit the code below to specify the above parameters to test connectivity to the target instance."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "43751f97-d545-4e84-ac99-109c719a048d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$targetServerName = 'sqlmig.westus2.cloudapp.azure.com'\r\n",
+                "$targetLogin = 'cloudsa'\r\n",
+                "\r\n",
+                "## TEMP - REMOVE BEFORE PUSHING CHANGES\r\n",
+                "$env:SQLMIG_TargetPassword = 'Yukon900Yukon900'\r\n",
+                "\r\n",
+                "## PowerShell Environment \r\n",
+                "$targetLoginPassword = ConvertTo-SecureString $env:SQLMIG_TargetPassword -AsPlaintext -Force\r\n",
+                "$targetCredential = New-Object System.Management.Automation.PSCredential ('migtest', $targetLoginPassword)\r\n",
+                "$targetTest = Test-DbaConnection -SqlInstance $targetServerName -SqlCredential $targetCredential\r\n",
+                "$targetTest\r\n",
+                "$targetConnection = Connect-DbaInstance -SqlInstance $targetServerName -SqlCredential $targetCredential"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4f74315c-1147-4fe3-8959-13eb24cb0957"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Login to Microsoft Azure\r\n",
+                "To configure and provision resources you must log into your Azure account and set the current subscription that is being used for the target SQL Server instance.  The following code will help you connect your account and choose the correct subscription.  When presented with the list of subscriptions, click on the desired subscription and press OK."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2ffbcba3-934e-4498-87ed-7030124d3af2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "#Connect-AzAccount\r\n",
+                "$migrationSubscription = Get-AzSubscription | Select-Object -Property Name, Id | Out-GridView -PassThru\r\n",
+                "Set-AzContext -SubscriptionId $migrationSubscription.Id"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8b23dcba-8d28-44eb-abbb-c7a54ef6b003",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify No Active Connections"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "81259d7e-62ac-4cdd-9e1b-2cb4ddb3d3b2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "#TODO - filter connected proceesses for user connections\r\n",
+                "\r\n",
+                "\r\n",
+                "Get-DbaProcess -SqlInstance $SourceServerName -SqlCredential $sourceLogin | \r\n",
+                "Select Host, login, Program"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "28393e59-4ea1-4f0f-8f9f-8a504f15e723",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Temporary Storage for Data Movement\r\n",
+                "\r\n",
+                "Offline data migration attempts to use backup to URL and restore from URL as the mechanism for moving data from the source instance to the target instance.  This code will check existance of the specified storage account and container to use for data migration. If the resources do not exist they will be created."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cc18027e-4636-465d-abaf-f3de88fea406"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$resourceGroup = \"sqlmig\"\r\n",
+                "$blobStorageAccount = \"tempsqlmigstorage\"\r\n",
+                "$containerName = \"backups\"\r\n",
+                "$location = \"West US 2\"\r\n",
+                "\r\n",
+                "# Storage Account\r\n",
+                "$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroup -Name $blobStorageAccount\r\n",
+                "if ($storageAccount -eq $null)\r\n",
+                "{\r\n",
+                "    # specified storage account does not yet exist, attempt to create it\r\n",
+                "    $storageAccount = New-AzStorageAccount -ResourceGroupName $resourceGroup -Name $blobStorageAccount -Location $location -SkuName Standard_LRS -Kind StorageV2\r\n",
+                "}\r\n",
+                "$storageAccount\r\n",
+                "\r\n",
+                "# Container\r\n",
+                "$storageContext = $storageAccount.Context\r\n",
+                "$storageContainer = Get-AzStorageContainer -Name $containerName -Context $storageContext\r\n",
+                "if ($storageContainer -eq $null)\r\n",
+                "{\r\n",
+                "    #specified storage container does not yet exist, attempt to create it\r\n",
+                "    $storageContainer = New-AzStorageContainer -Name $containerName -Context $storageContext -Permission Container\r\n",
+                "}\r\n",
+                "$storageContainer\r\n",
+                "\r\n",
+                "# Provide source instance with SAS token for blob access\r\n",
+                "$sourceSAS = (New-AzStorageAccountSASToken -Service Blob -ResourceType Object -Permission \"rw\" -Context $storageContext).TrimStart('?')\r\n",
+                "$sourceCred = New-DbaCredential -SqlInstance $sourceConnection -Name \"https://$blobStorageAccount.blob.core.windows.net/$containerName\" -Identity \"SHARED ACCESS SIGNATURE\" -SecurePassword (ConvertTo-SecureString $sourceSAS -AsPlainText -Force) -Force\r\n",
+                "$sourceCred\r\n",
+                "\r\n",
+                "$targetSAS = (New-AzStorageAccountSASToken -Service Blob -ResourceType Object -Permission \"rw\" -Context $storageContext).TrimStart('?') # -ResourceType Container,Object\r\n",
+                "$targetCred = New-DbaCredential -SqlInstance $targetConnection -Name \"https://$blobStorageAccount.blob.core.windows.net/$containerName\" -Identity \"SHARED ACCESS SIGNATURE\" -SecurePassword (ConvertTo-SecureString $targetSAS -AsPlainText -Force) -Force\r\n",
+                "$targetCred\r\n",
+                "\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f7d53cb1-a55d-4634-95f7-d3e8cf9fab52"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Copy Databases to Target Server\r\n",
+                "\r\n",
+                "The following code will present a list of the databases from the source SQL Server instance.  Select the list of databases to copy and press OK.  The Copy-DbaDatabase CMDLET will take a backup of each database using the azure storage account information above.  Each database backup will then be restored from the blob storage account.  The database restore will use server defaults for database file location and structure.\r\n",
+                "\r\n",
+                "If the database being restored already exists on the target instance, the *Force* parameter determines the behavior of the notebook:\r\n",
+                "\r\n",
+                "|Force|Description|\r\n",
+                "|---|---|\r\n",
+                "|$true| Overwrite the existing database on the target instance|\r\n",
+                "|$false| Do not restore the database if it already exists on the target instance|\r\n",
+                "\r\n",
+                "*Note: This can be a very long running process based on the size of the databases being copied.  The notebook should be allowed to run until the CMDLET completes.*\r\n",
+                "\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a82b0092-53a1-4dc3-8d73-16fae8c59ff7"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$databasesToCopy = Get-DbaDatabase -SqlInstance $sourceConnection | Select-Object -Property Name | Out-GridView -PassThru\r\n",
+                "$databaseList = New-Object System.Collections.ArrayList\r\n",
+                "foreach ($db in $databasesToCopy)\r\n",
+                "{\r\n",
+                "    $databaseList.Add($db.Name)    \r\n",
+                "}\r\n",
+                "\r\n",
+                "$copyDatabaseParams = @{\r\n",
+                "    Database = $databaseList\r\n",
+                "    Source = $sourceConnection\r\n",
+                "    Destination = $targetConnection\r\n",
+                "    BackupRestore = $true\r\n",
+                "    SharedPath =  \"https://$blobStorageAccount.blob.core.windows.net/$containerName\"\r\n",
+                "    Force = $true\r\n",
+                "    Verbose = $false     \r\n",
+                "}\r\n",
+                "\r\n",
+                "Copy-DbaDatabase @copyDatabaseParams"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5614e073-0a94-45af-8432-e7a6bf9121ea"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 34 - 0
SQL-Hybrid-Cloud-Toolkit/content/offline-migration/instance-to-MI.ipynb

@@ -0,0 +1,34 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Migrate SQL Server Instance to Azure SQL Managed Instance\n",
+                "=============================================\n",
+                "\n",
+                "Description\n",
+                "-----\n",
+                "\n",
+                "clone the configuration and data of a sql instance into a managed instance\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "43600853-57b3-4e60-a2a9-a28fb82af386"
+            }
+        }
+    ]
+}

+ 262 - 0
SQL-Hybrid-Cloud-Toolkit/content/offline-migration/instance-to-VM.ipynb

@@ -0,0 +1,262 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Migrate SQL Server Instance to Azure SQL Server VM\n",
+                "================================"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f706da59-22c3-4317-bf41-c00dde794097"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Source SQL Instance\r\n",
+                "The following code is used to specify the source SQL Server instance.  Data and Server objects will be copied from this server to the target SQL Server instance.  The code below will first prompt for the name of the source SQL Server instance.  Secondarily, the code will use a secure credential prompt for the login credentials for the source instance. \r\n",
+                "\r\n",
+                "*Note: the notebook currently is setup for SQL Authentication.  Future updates will add support for multiple authentication types.*\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2ac081f4-853a-4381-a303-e6ca557503fb"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# set the name or IP address of the source SQL Server instance\n",
+                "Add-Type -Assemblyname PresentationFramework\n",
+                "$sourceInfoBox = New-Object AnyBox.AnyBox\n",
+                "\n",
+                "$sourceInfoBox.Prompts = New-AnyBoxPrompt -Name \"serverName\" -Message \"Source SQL Instance name or IP address:\" -ValidateNotEmpty\n",
+                "$sourceInfoBox.Buttons = New-AnyBoxButton -Name 'submit' -Text \"OK\" -IsDefault\n",
+                "\n",
+                "$sourceInfoBox.Topmost = $true\n",
+                "$sourceInfoBox.WindowStyle = 'None'\n",
+                "\n",
+                "$sourceInfoResponse = $sourceInfoBox | Show-AnyBox\n",
+                "$sourceServerName = $sourceInfoResponse['serverName']\n",
+                "\n",
+                "# prompt for username and pw for authentication - supports SQL Authentication currently\n",
+                "$sourceCredential = Get-Credential -Message \"Enter Source Login Credentials\"\n",
+                "\n",
+                "# test the connectivity and display instance information\n",
+                "$sourceTest = Test-DbaConnection -SqlInstance $sourceServerName -SqlCredential $sourceCredential\n",
+                "$sourceTest\n",
+                "\n",
+                "# sourceConnection is used in the SQL migration cell below\n",
+                "$sourceConnection = Connect-DbaInstance -SqlInstance $sourceServerName -SqlCredential $sourceCredential"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "76a50416-b804-46ae-a49c-99baaeb31f7d",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Target SQL Instance\r\n",
+                "The following code is used to specify the target SQL Server instance.  Data and Server objects will be copied to this server from the source SQL Server instance.  The code below will first prompt for the name of the target SQL Server instance.  Secondarily, the code will use a secure credential prompt for the login credentials for the target instance. \r\n",
+                "\r\n",
+                "*Note: the notebook currently is setup for SQL Authentication.  Future updates will add support for multiple authentication types.*\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "43751f97-d545-4e84-ac99-109c719a048d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# set the name or IP address of the source SQL Server instance\r\n",
+                "Add-Type -Assemblyname PresentationFramework\r\n",
+                "$targetInfoBox = New-Object AnyBox.AnyBox\r\n",
+                "\r\n",
+                "$targetInfoBox.Prompts = New-AnyBoxPrompt -Name \"serverName\" -Message \"Target SQL Instance name or IP address:\" -ValidateNotEmpty\r\n",
+                "$targetInfoBox.Buttons = New-AnyBoxButton -Name 'submit' -Text \"OK\" -IsDefault\r\n",
+                "\r\n",
+                "$targetInfoBox.Topmost = $true\r\n",
+                "$targetInfoBox.WindowStyle = 'None'\r\n",
+                "\r\n",
+                "$targetInfoResponse = $targetInfoBox | Show-AnyBox\r\n",
+                "$targetServerName = $targetInfoResponse['serverName']\r\n",
+                "\r\n",
+                "# prompt for username and pw for authentication - supports SQL Authentication currently\r\n",
+                "$targetCredential = Get-Credential -Message \"Enter Target Login Credentials\"\r\n",
+                "\r\n",
+                "## PowerShell Environment \r\n",
+                "$targetTest = Test-DbaConnection -SqlInstance $targetServerName -SqlCredential $targetCredential\r\n",
+                "$targetTest\r\n",
+                "$targetConnection = Connect-DbaInstance -SqlInstance $targetServerName -SqlCredential $targetCredential"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4f74315c-1147-4fe3-8959-13eb24cb0957"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Login to Microsoft Azure\r\n",
+                "To configure and provision resources you must log into your Azure account and set the current subscription that is being used for the target SQL Server instance.  The following code will help you connect your account and choose the correct subscription.  When presented with the list of subscriptions, click on the desired subscription and press OK."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2ffbcba3-934e-4498-87ed-7030124d3af2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Connect-AzAccount\r\n",
+                "$migrationSubscription = Get-AzSubscription | Select-Object -Property Name, Id | Out-GridView -PassThru\r\n",
+                "Set-AzContext -SubscriptionId $migrationSubscription.Id"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8b23dcba-8d28-44eb-abbb-c7a54ef6b003",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify No Active Connections"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "81259d7e-62ac-4cdd-9e1b-2cb4ddb3d3b2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "#TODO - filter connected proceesses for user connections\r\n",
+                "\r\n",
+                "\r\n",
+                "Get-DbaProcess -SqlInstance $SourceServerName -SqlCredential $sourceLogin | \r\n",
+                "Select Host, login, Program"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "28393e59-4ea1-4f0f-8f9f-8a504f15e723",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Temporary Storage for Data Movement\r\n",
+                "\r\n",
+                "Offline data migration attempts to use backup to URL and restore from URL as the mechanism for moving data from the source instance to the target instance.  This code will check existance of the specified storage account and container to use for data migration. If the resources do not exist they will be created."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cc18027e-4636-465d-abaf-f3de88fea406"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# TODO - add interactive support for these values\r\n",
+                "$resourceGroup = \"sqlmig\"\r\n",
+                "$blobStorageAccount = \"tempsqlmigstorage\"\r\n",
+                "$containerName = \"backups\"\r\n",
+                "$location = \"West US 2\"\r\n",
+                "\r\n",
+                "# Storage Account\r\n",
+                "$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroup -Name $blobStorageAccount\r\n",
+                "if ($storageAccount -eq $null)\r\n",
+                "{\r\n",
+                "    # specified storage account does not yet exist, attempt to create it\r\n",
+                "    $storageAccount = New-AzStorageAccount -ResourceGroupName $resourceGroup -Name $blobStorageAccount -Location $location -SkuName Standard_LRS -Kind StorageV2\r\n",
+                "}\r\n",
+                "$storageAccount\r\n",
+                "\r\n",
+                "# Container\r\n",
+                "$storageContext = $storageAccount.Context\r\n",
+                "$storageContainer = Get-AzStorageContainer -Name $containerName -Context $storageContext\r\n",
+                "if ($storageContainer -eq $null)\r\n",
+                "{\r\n",
+                "    #specified storage container does not yet exist, attempt to create it\r\n",
+                "    $storageContainer = New-AzStorageContainer -Name $containerName -Context $storageContext -Permission Container\r\n",
+                "}\r\n",
+                "$storageContainer\r\n",
+                "\r\n",
+                "# Provide source instance with SAS token for blob access\r\n",
+                "$sourceSAS = (New-AzStorageAccountSASToken -Service Blob -ResourceType Object -Permission \"rw\" -Context $storageContext).TrimStart('?')\r\n",
+                "$sourceCred = New-DbaCredential -SqlInstance $sourceConnection -Name \"https://$blobStorageAccount.blob.core.windows.net/$containerName\" -Identity \"SHARED ACCESS SIGNATURE\" -SecurePassword (ConvertTo-SecureString $sourceSAS -AsPlainText -Force) -Force\r\n",
+                "$sourceCred\r\n",
+                "\r\n",
+                "$targetSAS = (New-AzStorageAccountSASToken -Service Blob -ResourceType Object -Permission \"rw\" -Context $storageContext).TrimStart('?') # -ResourceType Container,Object\r\n",
+                "$targetCred = New-DbaCredential -SqlInstance $targetConnection -Name \"https://$blobStorageAccount.blob.core.windows.net/$containerName\" -Identity \"SHARED ACCESS SIGNATURE\" -SecurePassword (ConvertTo-SecureString $targetSAS -AsPlainText -Force) -Force\r\n",
+                "$targetCred\r\n",
+                "\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f7d53cb1-a55d-4634-95f7-d3e8cf9fab52",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Start the Server Migration\r\n",
+                "\r\n",
+                "The follow code executes the migration of objects from the source SQL Server instance to the target SQL Server instance.  There are a couple of options of interest:\r\n",
+                "\r\n",
+                "|Parameter|Description|\r\n",
+                "|---|---|\r\n",
+                "|Verbose|Include detailed logging information during the migration process|\r\n",
+                "|WhatIf|Display what the migration process is going to do but do **not** actually perform the migration|\r\n",
+                "\r\n",
+                "The above parameters can be turned off by changing the $true value following the parameter to $false in the code cell below.  "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "518662fe-3ccf-4ead-80a7-3b890a394975"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$migrationParams = @{\r\n",
+                "    Verbose = $true\r\n",
+                "    WhatIf = $true\r\n",
+                "    Source = $sourceConnection\r\n",
+                "    Destination = $targetConnection\r\n",
+                "    BackupRestore = $true\r\n",
+                "    SharedPath = \"https://$blobStorageAccount.blob.core.windows.net/$containerName\" \r\n",
+                "}\r\n",
+                "\r\n",
+                "Start-DbaMigration @migrationParams"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b3a291f0-d9c4-438d-8b34-2c42a1a3b373",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 16 - 0
SQL-Hybrid-Cloud-Toolkit/content/offline-migration/readme.md

@@ -0,0 +1,16 @@
+# Offline Migration
+[Home](../readme.md)
+
+This chapter contains a set of notebooks useful for doing offline migration of databases and SQL instances to Azure. For instance migration, these notebooks assume the Azure SQL Virtual Machine, Azure SQL Managed Instance, or Azure SQL DB Server have already been created. 
+
+## Notebooks in this Chapter
+- [Migrate Instance to Azure SQL VM](instance-to-VM.ipynb)
+
+- [Migrate Database to Azure SQL VM](db-to-VM.ipynb)
+
+- [Migrate Instance to Azure SQL MI](instance-to-MI.ipynb)
+
+- [Migrate Database to Azure SQL MI](db-to-MI.ipynb)
+
+- [Migrate Database to Azure SQL DB](db-to-SQLDB.ipynb)
+

+ 201 - 0
SQL-Hybrid-Cloud-Toolkit/content/prereqs.ipynb

@@ -0,0 +1,201 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# Prerequisites and Initial Setup\n",
+                "\n",
+                "Most of the notebooks in the Azure SQL Hybrid Cloud Toolkit use either Microsoft PowerShell or Python for their script functionality. This notebook will perform the local machine configuration necessary to execute the various notebooks successfully.\n",
+                "\n",
+                "## Microsoft PowerShell Version\n",
+                "\n",
+                "Some notebooks use a [PowerShell Notebook kernel](https://youtu.be/BhbiAINQBYE) that requires PS v5.1 or greater."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "26e96caf-0241-43ee-a44b-9743265919d4"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Check that PowerShell >= v5.1 and Azure Command Line Interface (Az CLI) are installed\r\n",
+                "if ($PSVersionTable.PSVersion.Major -lt 5)\r\n",
+                "{\r\n",
+                "    Write-Warning \"Some notebooks require a greater version of PowerShell.\"\r\n",
+                "}\r\n",
+                "else {\r\n",
+                "    $psVersion = $PSVersionTable.PSVersion.ToString()\r\n",
+                "    Write-Output \"Found PS version... $psVersion\"\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "3ce85087-8cd5-49fc-8e67-00c996e95fbe",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## PowerShellGet and PowerShell Gallery Modules\n",
+                "\n",
+                "If you do not have PS Nuget provider installed, then please see [Installing PowerShellGet](https://docs.microsoft.com/en-us/powershell/scripting/gallery/installing-psget). The initial setup for the PowerShellGet module may require elevated permissions and is recommended to be done in a separate PowerShell command prompt. \n",
+                "\n",
+                "The modules required by the toolkit are available on the [PowerShell Gallery](https://www.powershellgallery.com/), a central repository for sharing PS modules and scripts. The setup code in this notebook will create a temporary call to install required PowerShell modules in the current user scope. If the modules have not already been installed, the following code will install them with a new gallery registration setup as trusted to enable installation of modules without prompting. After installing the modules, the gallery registration will be removed.\n",
+                "\n",
+                "| Module | Description |\n",
+                "| --- | --- |\n",
+                "| dbatools | Provides high-level cmdlets that perform SQL management operations. These CMDLETs have extensive validation tests and configuration options. |\n",
+                "| Az.Accounts | Provides cmdlets to configure subscription and account settings |\n",
+                "| Az.Resources | Provides cmdlets to access and provision Microsoft Azure resources |\n",
+                "| Az.Storage | Provides cmdlets to simplify configuration and access to Azure storage resources |\n",
+                "| Az.Network | Networking cmdlets for Azure Resource Manager |\n",
+                "| Az.Compute | Compute service cmdlets |\n",
+                "| AnyBox | Provides GUI input box support (Windows only) |\n",
+                "| SqlServer | Provides cmdlets to automate database development and server administration, as well as both multidimensional and tabular cube processing |"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "836a217a-b348-420e-b525-e06580f717b0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# need to check if nuget package provider is installed\r\n",
+                "$nuget=(Get-PackageProvider -Name NuGet)\r\n",
+                "\r\n",
+                "if (!$nuget)\r\n",
+                "{\r\n",
+                "    Get-PackageProvider -Name NuGet -ForceBootstrap\r\n",
+                "    Import-PackageProvider -Name NuGet -RequiredVersion 2.8.5.201\r\n",
+                "}\r\n",
+                "\r\n",
+                "#temporary reposistory registration\r\n",
+                "Register-PSRepository -Name PSGalleryTemp -SourceLocation https://www.powershellgallery.com/api/v2/ -PublishLocation https://www.powershellgallery.com/api/v2/package/ -ScriptSourceLocation https://www.powershellgallery.com/api/v2/items/psscript/ -ScriptPublishLocation https://www.powershellgallery.com/api/v2/package/ -InstallationPolicy Trusted -PackageManagementProvider NuGet\r\n",
+                "\r\n",
+                "#required modules for this notebook\r\n",
+                "$requiredModules = @(\"dbatools\", \"Az.Accounts\", \"Az.Resources\", \"Az.Storage\", \"Az.Network\", \"Az.Compute\", \"AnyBox\",\"SqlServer\")\r\n",
+                "\r\n",
+                "#check each module and install if necessary\r\n",
+                "foreach ($moduleName in $requiredModules) {\r\n",
+                "    if (!(Get-Module -ListAvailable -Name $moduleName)) {\r\n",
+                "        Install-Module -Name $moduleName -Scope CurrentUser -AllowClobber -Repository PSGalleryTemp\r\n",
+                "    }\r\n",
+                "} \r\n",
+                "\r\n",
+                "#remove temporary gallery registration\r\n",
+                "Unregister-PSRepository -Name PSGalleryTemp\r\n",
+                "\r\n",
+                "# Output any missing modules\r\n",
+                "foreach ($moduleName in $requiredModules) {\r\n",
+                "       $module = Get-InstalledModule $moduleName\r\n",
+                "    if (!($module)) {\r\n",
+                "          Write-Warning \"The specified module is not installed...$moduleName\"\r\n",
+                "    }\r\n",
+                "    else{\r\n",
+                "        $module\r\n",
+                "    }\r\n",
+                "}   "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e9c3177c-fde7-4c62-8220-62622620f9b4"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## SQL Assessment cmdlets and SMO extension\n",
+                "\n",
+                "SQL Assessment API is part of the SQL Server Management Objects (SMO) and can be used with the SQL Server PowerShell module. Because installing the modules may require a local Administrator account's permission, it cannot be done automatically with this Notebook. The **Assessments** Notebooks require the following:\n",
+                "\n",
+                "-   [Install SMO](https://docs.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/installing-smo?view=sql-server-ver15)\n",
+                "-   [Install SQL Server PowerShell module](https://docs.microsoft.com/en-us/sql/powershell/download-sql-server-ps-module?view=sql-server-ver15)\n",
+                "\n",
+                "## Compatibility Assessment Tool - Data Migration Assistant\n",
+                "\n",
+                "The Compatibility Assessment Notebook requires the Data Migration Assistant tool to be installed in order to execute. The installation link would be [Data Migration Assistant download](https://www.microsoft.com/en-us/download/confirmation.aspx?id=53595)\n",
+                "\n",
+                "With version 2.1 and above, when installation of Data Migration Assistant is successful, it will install dmacmd.exe in _%ProgramFiles%\\\\Microsoft Data Migration Assistant_ folder."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "1b49a7e5-a773-4104-8f88-bd2ea3c806a3"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Azure CLI\n",
+                "\n",
+                "Some notebooks also require the latest version of Azure command line interface (CLI). To install, see [https://aka.ms/installazurecliwindows](https://aka.ms/installazurecliwindows). Update instructions can be found at [https://aka.ms/doc/UpdateAzureCliMsi](https://aka.ms/doc/UpdateAzureCliMsi)"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e032f30b-5ca7-47bf-bf5e-1bd36d2763f8"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Check for Azure CLI\r\n",
+                "$azCli = (az --version)\r\n",
+                "if ($azCli) {        \r\n",
+                "    $azVersion = $azCli[0].Split()[-1]\r\n",
+                "    if ($azVersion -eq \"*\") {\r\n",
+                "        $azVersion = $azCli[0].Split()[-2]\r\n",
+                "    }\r\n",
+                "    Write-Output \"Found Az CLI version... $azVersion\"\r\n",
+                "}\r\n",
+                "else {\r\n",
+                "    Write-Warning \"Some notebooks require the Az CLI. Please install if to run these notebooks.\"\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "398e7abe-5a60-477c-8d04-0545389614b0"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## PowerShell ADS Extension\n",
+                "\n",
+                "It may also be useful to install the official release of the PowerShell ADS extension by following the steps in the [Azure Data Studio documentation](vscode-webview://extensioneditor/electron-browser/docs.microsoft.com/en-us/sql/azure-data-studio/extensions). Installing this extension is optional but will enable syntax highlighting and intellisense within ADS. \n",
+                "\n",
+                "In the Extensions pane, search for \"PowerShell\" extension and install it there. It is also possible to install a VSIX package from the [PowerShell extension's Releases page](https://github.com/PowerShell/vscode-powershell/releases) through the command line:\n",
+                "\n",
+                "`azuredatastudio --install-extension PowerShell-<version>.vsix`"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "1b4aadb8-753b-43e4-8582-d05e51edbb7f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# azuredatastudio --install-extension PowerShell-v2020.9.0-preview.vsix     # Uncomment to install a specific version"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "86ff5c4f-bf3b-4ca0-aba0-edf592106958"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 239 - 0
SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqldb-azCli.ipynb

@@ -0,0 +1,239 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Create Azure SQL Database\r\n",
+                "==================================\r\n",
+                "\r\n",
+                "Description\r\n",
+                "-----------\r\n",
+                "This Azure CLI script example creates database in Azure SQL Database and configures a server-level firewall rule. After the script has been successfully run, the database can be accessed from all Azure services and the configured IP address\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c9e3ccf4-199a-486d-9e19-7f448bd03f00"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Steps of this procedure include:\r\n",
+                "1. Connect to Azure subscription\r\n",
+                "1. Provision resource group for SQL Managed Instance\r\n",
+                "2. Create Sql Server\r\n",
+                "3. Provision firewall rules to allow access\r\n",
+                "4. Create Sql Database"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "ac87ba4f-d818-4fb3-8041-ee29ffd17294"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "#### Sample values for below code block\r\n",
+                "| Variables | Sample Values |\r\n",
+                "| ------------ | --------- |\r\n",
+                "| $Env:BOOTSTRAP_Subscription | Subscription Name or ID  |\r\n",
+                "| $Env:BOOTSTRAP_ResourceGroup | Intended Resource Group Name |\r\n",
+                "| $location | Valid location from Azure... See appendix at bottom |\r\n",
+                "| $randomIdentifier | Simple text... For example \"random123\"|\r\n",
+                "| $server | Name of intended Sql Server in simple text... For example \"server\" |\r\n",
+                "| $database | Name of intended database in simple text... For example \"database\" |\r\n",
+                "| $login | Sql Database Login Name... For example \"sampleLogin\" |\r\n",
+                "| \"password\" | This could be alphanumeric charecters of choice | \r\n",
+                "| $startIP | Intended Start Ip...For example 165.197.220.224|\r\n",
+                "| $endIP | Intended End Ip...For example 165.197.220.225 |\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a199a24e-220c-4a8b-a090-37e38e725ab3"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$Env:BOOTSTRAP_Subscription     = \"\"                     # Azure Subscription ID/Name for the ADP Resource Group # Both RG are assumed to be in the same subscription\r\n",
+                "$Env:BOOTSTRAP_ResourceGroup    = \"\"                     # Azure Resource Group which contains the ADP Resources\r\n",
+                "\r\n",
+                "# SQL Server \r\n",
+                "$location  =\"\"\r\n",
+                "$randomIdentifier =\"\"\r\n",
+                "\r\n",
+                "$server=\"server-$randomIdentifier\"\r\n",
+                "$database=\"database-$randomIdentifier\"\r\n",
+                "\r\n",
+                "$login=\"\"\r\n",
+                "$password=\"\"\r\n",
+                "\r\n",
+                "$startIP=\"\"\r\n",
+                "$endIP=\"\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "0b2af740-99df-4f44-8cb7-fd00a78f8b9a",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Connect to Azure and Select Subscription\r\n",
+                "Run the below cell to login to an Azure account. <b>Be sure to check the Windows Taskbar for a subscription selection dialog box.</b>\r\n",
+                "\r\n",
+                "_Note: the dialog box window may appear behind the active Azure Data Studio window._"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "acd95a93-f820-48cc-8699-975964225658"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "az login\r\n",
+                "az account set --subscription $Env:BOOTSTRAP_Subscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2a11cdb2-9549-4dab-a2fe-f0fd37474b6d"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create Resource Group\r\n",
+                "The Data Portability Notebooks provision several resources to enable import and export of Azure SQL DB databases and Azure SQL Managed Instance databases.  These resources include Storage, Azure Batch, Azure Function resources and their dependent Azure resources.  All resources created by this notebook are associated with a common Azure Resource Group to enable easy management of resources related to Data Portability.\r\n",
+                "\r\n",
+                "_Note: this notebook will reuse the Resource Group specified if it already exists._"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a3a0c72c-04cb-4bb2-8dbc-4d96e7482ce9"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "echo \"Creating $resource...\"\r\n",
+                "az group create --name $Env:BOOTSTRAP_ResourceGroup --location \"$location\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a9272913-cb61-4536-b89a-7826baa7465d"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create Sql Server\r\n",
+                "To create an Azure SQL Database using the Azure CLI 2.0,create an Azure SQL Server first, which can be done by running the following command"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "70e04ce4-c5ff-4cde-9a21-05fac7275dbf"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "echo \"Creating $server in $location...\"\r\n",
+                "az sql server create --name $server --resource-group $Env:BOOTSTRAP_ResourceGroup --location \"$location\" --admin-user $login --admin-password $password"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "dbd81221-d61e-4441-ad6d-bb4dcef91f0c"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Configure Firewall Rule\r\n",
+                "\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "22bbc194-c946-493d-ab65-2750246596c3"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "echo \"Configuring firewall...\"\r\n",
+                "az sql server firewall-rule create --resource-group $Env:BOOTSTRAP_ResourceGroup --server $server -n AllowYourIp --start-ip-address $startIP --end-ip-address $endIP"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7dbbedfd-32cc-467c-b65a-aaf9ece946b7"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create Database on server\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b002d01a-6b2d-4b45-bd77-0d139fbe5503"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "echo \"Creating $database on $server...\"\r\n",
+                "az sql db create --resource-group $Env:BOOTSTRAP_ResourceGroup --server $server --name $database --sample-name AdventureWorksLT --edition GeneralPurpose --family Gen5 --capacity 2 --zone-redundant false # zone redundancy is only supported on premium and business critical service tiers"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "dd8d1b41-8ee7-4823-9655-a9d871b9fe97"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Appendices\r\n",
+                "These sections are not vital for the execution of this notebook but provide valuable background information.\r\n",
+                "\r\n",
+                "### Appendix: Locations\r\n",
+                "See the <a href=\"https://azure.microsoft.com/en-us/global-infrastructure/locations/\">Azure locations</a> page for a complete list of Azure regions along with their general physical location. The following is a list of common North American location settings for this guide:\r\n",
+                "\r\n",
+                "#### US Regions\r\n",
+                "| Setting | Location |\r\n",
+                "| ------------ | --------- |\r\n",
+                "| Central US | Iowa |\r\n",
+                "| East US | Virginia |\r\n",
+                "| East US 2 | Virginia |\r\n",
+                "| North Central US | Illinois |\r\n",
+                "| South Central US | Texas |\r\n",
+                "| West US 2 | Washington |\r\n",
+                "| West Central US | Wyoming |\r\n",
+                "| West US | California | \r\n",
+                "| Canada Central | Toronto |\r\n",
+                "| Canada East | Quebec City |\r\n",
+                "| Brazil South | Sao Paulo |\r\n",
+                "| Mexico Central | Queretaro |"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "52dd52ff-cabd-40cc-9b34-5a5ebd7ef0c3"
+            }
+        }
+    ]
+}

+ 243 - 0
SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqldb.ipynb

@@ -0,0 +1,243 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Create Azure SQL Database\n",
+                "==================================\n",
+                "\n",
+                "Description\n",
+                "-----------\n",
+                "This notebook will help you get created single Azure SQL Database inside Azure SQL Server and configure a server-level firewall rule. For more information see <a href=https://docs.microsoft.com/en-us/azure/azure-sql/database/scripts/create-and-configure-database-powershell>Use PowerShell to create a single database and configure a server-level firewall rule</a>\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6af59d69-ade7-480a-b33e-52a86fe5bfd3"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Steps of this procedure include:\r\n",
+                "1. Connect to Azure subscription\r\n",
+                "1. Provision resource group for SQL Managed Instance\r\n",
+                "2. Create Sql Server\r\n",
+                "3. Provision firewall rules to allow access\r\n",
+                "4. Create Sql Database"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d5346c50-c03b-4e3a-983f-7b4b22c78319"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Resource Group \r\n",
+                "$resourceGroupName  =   \"\"                # Name of the resource group to create in the current subscription\r\n",
+                "$location           =   \"\"                # Name of location (see Appendix for a list of location settings)\r\n",
+                "\r\n",
+                "# SQL Server\r\n",
+                "$serverName = \"mysqlserver-$(Get-Random)\"\r\n",
+                "$adminLogin = \"\"                          # Username of Sql server. Default would be 'azureuser'\r\n",
+                "$password = \"\"                            # Desired password for Sql Server\r\n",
+                "\r\n",
+                "# SQL Database\r\n",
+                "$databaseName = \"\"                        # Name of database to be created\r\n",
+                "\r\n",
+                "# The ip address range that you want to allow to access your server\r\n",
+                "$startIp = \"0.0.0.0\"\r\n",
+                "$endIp = \"0.0.0.0\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c5c06fd6-8e47-4abb-808a-edc8b1c2d690"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Connect to Azure </b>\r\n",
+                "Below command will open a _Dialouge Box_ asking your account credentials."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e34334a7-0d55-4c18-8c0a-1c4a673629cd"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Connect-AzAccount"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "96800b54-48a8-463b-886c-3d0e96f29765"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Get Subscription</b>\r\n",
+                "Below command will open a _Dialouge Box_ with list of subscriptions. Selecting one of those will set that subscription for rest of the commands."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "ed6b781d-ce7e-4b51-a7ec-1eeeb2032c73"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$subscription = Get-AzSubscription | Out-GridView -PassThru\r\n",
+                "Set-AzContext -SubscriptionName $subscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "17b57956-98cf-44de-9ab5-348469ddabf4"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Create the New Resource Group</b>\r\n",
+                "_New-AzResourceGroup_ command will create new resource group in given subscription."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "3ecc2a29-fb77-4f7f-8901-e9c5c71ce1a2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Create Azure resource group, if necessary\r\n",
+                "$rg = Get-AzResourceGroup | Where ResourceGroupName -eq $resourceGroupName\r\n",
+                "\r\n",
+                "if (!$rg)\r\n",
+                "{\r\n",
+                "    # Need to create a new resource group\r\n",
+                "    Write-Output \"Resource Group $resourceGroupName does not exist. Creating...\"\r\n",
+                "    $rg = New-AzResourceGroup -Name $resourceGroupName -Location $location\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "4837690a-2204-49ab-8a19-414a8ce782b6"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Create a SQL server with a system wide unique server name</b>\r\n",
+                "_New-AzSqlServer_ will create Sql Server with required configurations."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2d951526-40dc-49cc-8668-c393eaf58000"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Write-Output \"Creating SqlServer with name $serverName ....\"\r\n",
+                "New-AzSqlServer -ResourceGroupName $resourceGroupName `\r\n",
+                "   -ServerName $serverName `\r\n",
+                "   -Location $location `\r\n",
+                "   -SqlAdministratorCredentials $(New-Object -TypeName System.Management.Automation.PSCredential `\r\n",
+                "   -ArgumentList $adminLogin, $(ConvertTo-SecureString -String $password -AsPlainText -Force))"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c45757ac-6a58-468d-a04c-04504f8a2e0e"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Create a server firewall rule that allows access from the specified IP range</b>\r\n",
+                "_New-AzSqlServerFirewallRule_ creates a new firewall rule for sql server"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "ba895abf-3176-48b5-9e49-a060b3f74370"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Write-Output \"Configuring firewall for Sql Server...\"\r\n",
+                "New-AzSqlServerFirewallRule -ResourceGroupName $resourceGroupName `\r\n",
+                "   -ServerName $ServerName `\r\n",
+                "   -FirewallRuleName \"AllowedIPs\" -StartIpAddress $startIp -EndIpAddress $endIp"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "ceae5670-292f-4c45-9c10-4ac85baf2d07"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b>Create SQL Database</b>\r\n",
+                "_New-AzSqlDatabase_ command will create new database in the server."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b460ca8f-65a7-4d6c-94b7-6d7dd9655fad"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Write-Output \"Creating a gen5 2 vCore database...\"\r\n",
+                "$database = New-AzSqlDatabase  -ResourceGroupName $resourceGroupName `\r\n",
+                "   -ServerName $ServerName `\r\n",
+                "   -DatabaseName $databaseName `\r\n",
+                "   -Edition GeneralPurpose `\r\n",
+                "   -VCore 2 `\r\n",
+                "   -ComputeGeneration Gen5 `\r\n",
+                "   -MinimumCapacity 2"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "dc3b2f6f-83ac-4a4d-9d81-2f534e90913e"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### <b> Get Database details </b>\r\n",
+                "_Get-AzSqlDatabase_ command gets database details on the server specified."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "0b35ed4f-1786-4102-a09a-a6a360fd20f2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-AzSqlDatabase -ResourceGroupName $resourceGroupName -ServerName $serverName -DatabaseName $databaseName"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5001bf24-5f3f-434e-abf6-a5c21af4aa32"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 339 - 0
SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqlmi.ipynb

@@ -0,0 +1,339 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# Create Azure SQL Managed Instance\n",
+                "\n",
+                "## Description\n",
+                "\n",
+                "This PowerShell script example creates a managed instance in a dedicated subnet within a new virtual network. It also configures a route table and a network security group for the virtual network. Once the script has been successfully run, the managed instance can be accessed from within the virtual network or from an on-premises environment. See [Configure Azure VM to connect to Azure SQL Database Managed Instance](https://docs.microsoft.com/en-us/azure/azure-sql/managed-instance/connect-vm-instance-configure) and [Configure a point-to-site connection to Azure SQL Managed Instance from on-premises](https://docs.microsoft.com/en-us/azure/azure-sql/managed-instance/point-to-site-p2s-configure).\n",
+                "\n",
+                "<img width=\"50%\" src=https://docs.microsoft.com/en-us/azure/sql-database/media/sql-database-managed-instance/key-features.png />"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2dcca704-2945-4763-8cbe-116b90696f9c"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Steps of this procedure include:\r\n",
+                "1. Connect to Azure subscription\r\n",
+                "1. Provision resource group for SQL Managed Instance\r\n",
+                "2. Configure Network Settings\r\n",
+                "3. Create credentials to be used for Managed Instance\r\n",
+                "4. Create Managed Instance"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "dd1cfc56-2919-416b-968d-cdf3345dcb1c"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$NSnetworkModels = \"Microsoft.Azure.Commands.Network.Models\"\r\n",
+                "$NScollections = \"System.Collections.Generic\"\r\n",
+                "\r\n",
+                "# Set the resource group name and Location for your managed instance\r\n",
+                "$ResourceGroupName = \"myResourceGroup-$(Get-Random)\"\r\n",
+                "$Location = \"eastus2\"\r\n",
+                "\r\n",
+                "# Set the networking values for your managed instance\r\n",
+                "$VNetName = \"myVnet-$(Get-Random)\"\r\n",
+                "$VNetAddressPrefix = \"10.0.0.0/16\"\r\n",
+                "$DefaultSubnetName = \"myDefaultSubnet-$(Get-Random)\"\r\n",
+                "$DefaultSubnetAddressPrefix = \"10.0.0.0/24\"\r\n",
+                "$MiSubnetName = \"myMISubnet-$(Get-Random)\"\r\n",
+                "$MiSubnetAddressPrefix = \"10.0.0.0/24\"\r\n",
+                "\r\n",
+                "#Set the managed instance name for the new managed instance\r\n",
+                "$InstanceName = \"myMIName-$(Get-Random)\"\r\n",
+                "# Set the admin login and password for your managed instance\r\n",
+                "$MiAdminSqlLogin = \"SqlAdmin\"\r\n",
+                "$MiAdminSqlPassword = \"ChangeYourAdminPassword1\"\r\n",
+                "\r\n",
+                "# Set the managed instance service tier, compute level, and License mode\r\n",
+                "$Edition = \"General Purpose\"\r\n",
+                "$VCores = 8\r\n",
+                "$MaxStorage = 256\r\n",
+                "$ComputeGeneration = \"Gen5\"\r\n",
+                "$License = \"LicenseIncluded\" #\"BasePrice\" or LicenseIncluded if you have don't have SQL Server licence that can be used for AHB discount\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e574921c-3611-47d7-a872-564d07030815"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Connect to Azure Account"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2ab3812a-cb4f-44a1-94d4-a0681a05606f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Connect-AzAccount"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b137afd5-512a-4261-90c0-b43dbf1724ff"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Get Subscription\r\n",
+                "Below command will open a Dialouge Box with list of subscriptions.\r\n",
+                "Selecting one of those will set that subscription for rest of the commands."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d3af080f-c3d1-4a04-a168-78f8bfce247b"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$subscription = Get-AzSubscription | Out-GridView -PassThru\r\n",
+                "Set-AzContext -SubscriptionName $subscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6356291d-96e4-4cbb-bae8-27e81f6e0bb8"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create the resource group\r\n",
+                "Create a resource group with the _New-AzResourceGroup_ command. An Azure resource group is a logical container into which Azure resources are deployed and managed. A resource group must be created first:"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c8a4555b-8b80-41f9-9e8f-eaab3b419030"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Create Azure resource group, if necessary\r\n",
+                "$resourceGroup = Get-AzResourceGroup -Name $ResourceGroupName\r\n",
+                "\r\n",
+                "if (!$resourceGroup)\r\n",
+                "{\r\n",
+                "    # Need to create a new resource group\r\n",
+                "    Write-Output \"Resource Group $RG1 does not exist. Creating...\"\r\n",
+                "    $resourceGroup = New-AzResourceGroup -Name $RG1 -Location $Location -Tag @{Owner=\"SQLDB-Samples\"}\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "920c904f-6c6b-4e59-8305-f34b44297a64"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Configure virtual network, subnets, network security group, and routing table"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2ed3ccec-ab94-4c20-aa74-6d67c3db014d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$networkSecurityGroupMiManagementService = New-AzNetworkSecurityGroup `\r\n",
+                "                      -Name 'myNetworkSecurityGroupMiManagementService' `\r\n",
+                "                      -ResourceGroupName $ResourceGroupName `\r\n",
+                "                      -Location $Location\r\n",
+                "\r\n",
+                "$routeTableMiManagementService = New-AzRouteTable `\r\n",
+                "                      -Name 'myRouteTableMiManagementService' `\r\n",
+                "                      -ResourceGroupName $ResourceGroupName `\r\n",
+                "                      -Location $Location\r\n",
+                "\r\n",
+                "$virtualNetwork = New-AzVirtualNetwork `\r\n",
+                "                      -ResourceGroupName $ResourceGroupName `\r\n",
+                "                      -Location $Location `\r\n",
+                "                      -Name $VNetName `\r\n",
+                "                      -AddressPrefix $VNetAddressPrefix\r\n",
+                "\r\n",
+                "                  Add-AzVirtualNetworkSubnetConfig `\r\n",
+                "                      -Name $MiSubnetName `\r\n",
+                "                      -VirtualNetwork $virtualNetwork `\r\n",
+                "                      -AddressPrefix $MiSubnetAddressPrefix `\r\n",
+                "                      -NetworkSecurityGroup $networkSecurityGroupMiManagementService `\r\n",
+                "                      -RouteTable $routeTableMiManagementService |\r\n",
+                "                  Set-AzVirtualNetwork\r\n",
+                "\r\n",
+                "$virtualNetwork = Get-AzVirtualNetwork -Name $VNetName -ResourceGroupName $ResourceGroupName\r\n",
+                "\r\n",
+                "$subnet= $virtualNetwork.Subnets[0]"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "1f7cc6ec-ef04-4ffe-85d5-6ec40c5b2d1c"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a delegation"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a55da861-b130-451f-a017-4a033123e76d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$subnet.Delegations = New-Object \"$NScollections.List``1[$NSnetworkModels.PSDelegation]\"\r\n",
+                "$delegationName = \"dgManagedInstance\" + (Get-Random -Maximum 1000)\r\n",
+                "$delegation = New-AzDelegation -Name $delegationName -ServiceName \"Microsoft.Sql/managedInstances\"\r\n",
+                "$subnet.Delegations.Add($delegation)\r\n",
+                "\r\n",
+                "Set-AzVirtualNetwork -VirtualNetwork $virtualNetwork\r\n",
+                "\r\n",
+                "$miSubnetConfigId = $subnet.Id\r\n",
+                "\r\n",
+                "\r\n",
+                "\r\n",
+                "$allowParameters = @{\r\n",
+                "    Access = 'Allow'\r\n",
+                "    Protocol = 'Tcp'\r\n",
+                "    Direction= 'Inbound'\r\n",
+                "    SourcePortRange = '*'\r\n",
+                "    SourceAddressPrefix = 'VirtualNetwork'\r\n",
+                "    DestinationAddressPrefix = '*'\r\n",
+                "}\r\n",
+                "$denyInParameters = @{\r\n",
+                "    Access = 'Deny'\r\n",
+                "    Protocol = '*'\r\n",
+                "    Direction = 'Inbound'\r\n",
+                "    SourcePortRange = '*'\r\n",
+                "    SourceAddressPrefix = '*'\r\n",
+                "    DestinationPortRange = '*'\r\n",
+                "    DestinationAddressPrefix = '*'\r\n",
+                "}\r\n",
+                "$denyOutParameters = @{\r\n",
+                "    Access = 'Deny'\r\n",
+                "    Protocol = '*'\r\n",
+                "    Direction = 'Outbound'\r\n",
+                "    SourcePortRange = '*'\r\n",
+                "    SourceAddressPrefix = '*'\r\n",
+                "    DestinationPortRange = '*'\r\n",
+                "    DestinationAddressPrefix = '*'\r\n",
+                "}\r\n",
+                "\r\n",
+                "Get-AzNetworkSecurityGroup `\r\n",
+                "        -ResourceGroupName $ResourceGroupName `\r\n",
+                "        -Name \"myNetworkSecurityGroupMiManagementService\" |\r\n",
+                "    Add-AzNetworkSecurityRuleConfig `\r\n",
+                "        @allowParameters `\r\n",
+                "        -Priority 1000 `\r\n",
+                "        -Name \"allow_tds_inbound\" `\r\n",
+                "        -DestinationPortRange 1433 |\r\n",
+                "    Add-AzNetworkSecurityRuleConfig `\r\n",
+                "        @allowParameters `\r\n",
+                "        -Priority 1100 `\r\n",
+                "        -Name \"allow_redirect_inbound\" `\r\n",
+                "        -DestinationPortRange 11000-11999 |\r\n",
+                "    Add-AzNetworkSecurityRuleConfig `\r\n",
+                "        @denyInParameters `\r\n",
+                "        -Priority 4096 `\r\n",
+                "        -Name \"deny_all_inbound\" |\r\n",
+                "    Add-AzNetworkSecurityRuleConfig `\r\n",
+                "        @denyOutParameters `\r\n",
+                "        -Priority 4096 `\r\n",
+                "        -Name \"deny_all_outbound\" |\r\n",
+                "    Set-AzNetworkSecurityGroup"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "cb4292f6-1a78-43ec-81cb-505ebd791527"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create credentials"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e83be748-e6ec-46e1-b056-18be20185195"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$secpassword = ConvertTo-SecureString $MiAdminSqlPassword -AsPlainText -Force\r\n",
+                "$credential = New-Object System.Management.Automation.PSCredential ($MiAdminSqlLogin, $secpassword)\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "63bca2ea-fbf8-4e2d-8f58-a2b7bd913f6e"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create managed instance"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d17b701e-4c36-423c-b297-602879022621"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### This script will take a minimum of 3 hours to create a new managed instance in a new virtual network. \r\n",
+                "### A second managed instance is created much faster."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "3fdb03c6-3404-4ab6-9795-c2293cbea693"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "New-AzSqlInstance -Name $InstanceName `\r\n",
+                "                      -ResourceGroupName $ResourceGroupName -Location $Location -SubnetId $miSubnetConfigId `\r\n",
+                "                      -AdministratorCredential $credential `\r\n",
+                "                      -StorageSizeInGB $MaxStorage -VCore $VCores -Edition $Edition `\r\n",
+                "                      -ComputeGeneration $ComputeGeneration -LicenseType $License\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e3b55f89-d795-4d98-a24d-ade53cf53648"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 557 - 0
SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqlvm-azCli.ipynb

@@ -0,0 +1,557 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "# Create Azure Sql Virtual Machine using Az-Cli"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "420658c9-5177-4220-9b7e-34429d2e526d"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## PS Version and Modules\r\n",
+                "This notebook uses a <a href=\"https://youtu.be/BhbiAINQBYE\">PowerShell Notebook kernel</a> (YouTube video) and requires PS v5.1 or greater. It also uses the latest version of Azure CLI. To install, see https://aka.ms/installazurecliwindows. Update instructions can be found at https://aka.ms/doc/UpdateAzureCliMsi"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e0d6402d-4f1c-4e37-923e-1e9483431587"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Check that PowerShell >= v5.1 and Azure Command Line Interface (Az CLI) are installed\r\n",
+                "if ($PSVersionTable.PSVersion.Major -lt 5)\r\n",
+                "{\r\n",
+                "    Write-Warning \"Please install latest version of PowerShell. Do not continue executing the rest of the notebook.\"\r\n",
+                "}\r\n",
+                "else {\r\n",
+                "    $psVersion = $PSVersionTable.PSVersion.ToString()\r\n",
+                "    Write-Output \"Found PS version... $psVersion\"\r\n",
+                "    # Check for Azure CLI\r\n",
+                "    $azCli = (&az --version)    \r\n",
+                "    if ($azCli) {        \r\n",
+                "        $azVersion = $azCli[0].Split()[-1]\r\n",
+                "        Write-Output \"Found Az CLI version... $azVersion\"\r\n",
+                "    }\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "5a6893cf-e29d-4855-8771-0101bb6e55cd",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Set Variables for the Notebook"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "8998da5f-e4aa-4b32-aadf-51077748c56d"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# ADP Resource \r\n",
+                "$Env:BOOTSTRAP_Subscription     = \"\"                     # Azure Subscription ID/Name # The bacpac files and ADP Resources are assumed to be in the same subscription\r\n",
+                "$Env:BOOTSTRAP_ResourceGroup    = \"TestResourceGroup\"                     # Azure Resource Group which contains the ADP Resources\r\n",
+                "\r\n",
+                "# SQL Server \r\n",
+                "$VMName                     = \"TestVM\"                             # Name of the Virtual Machine to be created\r\n",
+                "$ImageSku                   = \"Enterprise\"                             # Choose your Image sku (see appendix)\r\n",
+                "$Location                   = \"East US\"                             # see Appendix for a list of location settings\r\n",
+                "$VmUsername                 = \"\"                                    # Username of Virtual Machine to be created\r\n",
+                "$VmPassword                 = \"\"                                    # Password of Virtual Machine to be created\r\n",
+                "\r\n",
+                "# Set Variables for ADP Resources\r\n",
+                "$Env:BOOTSTRAP_FUNC            = $Env:BOOTSTRAP_ResourceGroup + \"Control\"   \r\n",
+                "$Env:BOOTSTRAP_BATCH           = $Env:BOOTSTRAP_ResourceGroup.ToLower() + \"batch\"\r\n",
+                "$Env:BOOTSTRAP_VNET            = $Env:BOOTSTRAP_ResourceGroup + \"Vnet\""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a6f7e1bb-6517-4c43-a63b-d70a5e8627ba",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Notebook Functions\r\n",
+                "Defines logical functions for the rest of the notebook. Function blocks are combined in a single cell that can be collapsed for readability or expanded for further examination. Nothing is executed until called later in the notebook. As a result, this cell is a requirement for any of the other cells below it. "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c0f151bc-d1a3-4d41-8c93-b67f7a068be0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Expand cell to view framework\r\n",
+                "\r\n",
+                "function Login-Azure\r\n",
+                "{    \r\n",
+                "    # query azure locations to test for existing az login session exists with valid access tocken\r\n",
+                "    $azureLocations = az account list-locations -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    \r\n",
+                "    if (!$azureLocations){ #If there are no az locations, there is no existing az login session\r\n",
+                "        $subscriptions = az login -o JSON | ConvertFrom-Json      # Login   \r\n",
+                "    }\r\n",
+                "    else {\r\n",
+                "        $subscriptions = az account list -o JSON | ConvertFrom-Json # getting subscriptions for the user to use in gridview\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    if(![string]::IsNullOrWhiteSpace($Env:BOOTSTRAP_Subscription)) #If there is a subscription specified by user in the variables section\r\n",
+                "    {\r\n",
+                "        $specified_Subscription= az account show --subscription $Env:BOOTSTRAP_Subscription -o json |ConvertFrom-Json \r\n",
+                "        if (!$specified_Subscription) #if specified subscription is not valid\r\n",
+                "        {       \r\n",
+                "            $currentUser= az ad signed-in-user show --query \"{displayName:displayName,UPN:userPrincipalName}\" -o json|ConvertFrom-Json # get current logged in user infomration\r\n",
+                "            Write-Host \"Refer below for the list of subscriptions for logged in account '$($currentUser.UPN)'`n\"\r\n",
+                "            az account list --query \"[].{Name:name,SubscriptionID:id}\" -o table          # list subscriptions under current logged in account\r\n",
+                "        }\r\n",
+                "        else { # if specified subscription is valid\r\n",
+                "            Write-Output \"Using subscription... '$($specified_Subscription.name)' ... '$($specified_Subscription.id)'\" \r\n",
+                "        }\r\n",
+                "    }\r\n",
+                "    else { # if no subscription is specified, users are given a gridview to select subscription from\r\n",
+                "\r\n",
+                "        $selectedSubscription = $subscriptions | Select-Object -Property Name, Id | Out-GridView -PassThru\r\n",
+                "        $SubscriptionId = $selectedSubscription.Id\r\n",
+                "        $Subscription = $selectedSubscription.Name   \r\n",
+                "        $Env:BOOTSTRAP_Subscription = $subscription  \r\n",
+                "        Write-Output \"Using subscription... '$Env:BOOTSTRAP_Subscription' ... '$SubscriptionId'\"    \r\n",
+                "    }    \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Verify-ADPResources\r\n",
+                "{    \r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ADPResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$FunctionName,  \r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BatchAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VNetName,\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ApplicationName=\"SqlPackageWrapper\",  \r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ApplicationPackageVersionName=\"1\",\r\n",
+                "        [Parameter (Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$SubNetName=\"default\" \r\n",
+                "    )    \r\n",
+                "\r\n",
+                "# validate Subscription\r\n",
+                "$specified_Subscription= az account show --subscription $Subscription -o json | ConvertFrom-Json\r\n",
+                "if(!$specified_Subscription){\r\n",
+                "    $currentUser= az ad signed-in-user show --query \"{displayName:displayName,UPN:userPrincipalName}\" -o json|ConvertFrom-Json # get current logged in user information\r\n",
+                "    Write-Host \"Refer below for the list of subscriptions for logged in account '$($currentUser.UPN)'`n\"\r\n",
+                "    az account list --query \"[].{Name:name,SubscriptionID:id}\" -o table          # list subscriptions under current logged in account\r\n",
+                "    return } \r\n",
+                "# validate ResourceGroup    \r\n",
+                "$specified_ResourceGroup= az group show -n $ADPResourceGroupName --subscription $Subscription -o json | ConvertFrom-Json\r\n",
+                "if(!$specified_ResourceGroup) {        \r\n",
+                "        return\r\n",
+                "        } \r\n",
+                "\r\n",
+                "$Installed = [ordered]@{} # ordered hash to store status of installation\r\n",
+                "$countError=0\r\n",
+                "\r\n",
+                "#Verify if VNet exists       \r\n",
+                "$specified_VNet= az network vnet show -n $VNetName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null  |ConvertFrom-Json   \r\n",
+                "if(!$specified_VNet) {\r\n",
+                "    $Installed['VNET']=\"Not Found\"\r\n",
+                "    $countError++\r\n",
+                "}   \r\n",
+                "else { \r\n",
+                "    $existingVnetSubnet =  az network vnet subnet show -n $SubNetName --vnet-name $VNetName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null |ConvertFrom-Json\r\n",
+                "    if(!$existingVnetSubnet){\r\n",
+                "        $Installed['VNET']=\"Default Subnet under\"+ $VNetName + \"Not Found\"\r\n",
+                "        $countError++\r\n",
+                "        }\r\n",
+                "    else {\r\n",
+                "    $Installed['VNET']=\"Installed\"\r\n",
+                "    }\r\n",
+                " }\r\n",
+                "\r\n",
+                "#Verify if FunctionApp Exists\r\n",
+                "$specified_FunctionApp = az functionapp show -n $FunctionName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "if(!$specified_FunctionApp)\r\n",
+                "{\r\n",
+                "    $Installed['FunctionApp']=\"Not Installed\"\r\n",
+                "    $countError++\r\n",
+                "}\r\n",
+                "else\r\n",
+                "{\r\n",
+                "    $Installed['FunctionApp']=\"Installed\"\r\n",
+                "} \r\n",
+                "\r\n",
+                "#check if Batch account exists\r\n",
+                "$specified_BatchAccount = az batch account show -n $BatchAccountName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "if(!$specified_BatchAccount)\r\n",
+                "{\r\n",
+                "    $Installed['Batch']=\"Not Installed\"\r\n",
+                "    $countError++\r\n",
+                "}\r\n",
+                "else\r\n",
+                "{\r\n",
+                "    $appPackageInstalled = az batch application package show --application-name $ApplicationName --version-name $ApplicationPackageVersionName -n $BatchAccountName -g $ADPResourceGroupName --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $connectedToStorage= $specified_BatchAccount.autoStorage \r\n",
+                "    if($connectedToStorage -and $appPackageInstalled){ # BatchAccount connected to storageaccount and  applicationpackage is installed\r\n",
+                "            $Installed['Batch']=\"Installed\"\r\n",
+                "            $Installed['Batch_ApplicationPackage']=\"Installed\"\r\n",
+                "            $Installed['Batch_StorageAccount']=\"Connected to storage- \"+$connectedToStorage.storageAccountId.Split(\"/\")[-1]\r\n",
+                "        }\r\n",
+                "    if(!$connectedToStorage)\r\n",
+                "    {\r\n",
+                "        $Installed['Batch_StorageAccount']='Not Found'\r\n",
+                "        $countError++\r\n",
+                "    }  \r\n",
+                "    if(!$appPackageInstalled)\r\n",
+                "    {\r\n",
+                "        $Installed['Batch_ApplicationPackage']=\"Not Found\"\r\n",
+                "        $countError++\r\n",
+                "    }    \r\n",
+                "}\r\n",
+                "if ($countError -gt 0){\r\n",
+                "    Write-Output \"ADP Resources are not installed correctly. Please refer the list below and use the Bootstrap NB to install ADP Resources\"\r\n",
+                "}\r\n",
+                "$Installed\r\n",
+                "if ($countError -eq 0){\r\n",
+                "    Write-Output \"`nFound all ADP Resources.\"\r\n",
+                "}\r\n",
+                "}\r\n",
+                "\r\n",
+                "function Prepare-InputForImportFunction\r\n",
+                "{    \r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param(\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Subscription,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ADPResourceGroupName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$FunctionName,  \r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BatchAccountName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BackupFiles_StorageAccount,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BackupFiles_ContainerName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VNetName,        \r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$TargetRGName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName,\r\n",
+                "        [Parameter (Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerPassword\r\n",
+                "    )\r\n",
+                "    \r\n",
+                "    $Result = @{}\r\n",
+                "    # Build Header \r\n",
+                "    ## get Function key\r\n",
+                "    $FunctionAppID =az functionapp show -n $FunctionName -g $ADPResourceGroupName --subscription $Subscription --query \"[id]\" -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $DefaultHostKey = az rest --method post --uri \"$FunctionAppID/host/default/listKeys?api-version=2018-11-01\" --query \"[functionKeys.default]\" -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    ## Build Json Object for Headers\r\n",
+                "    $headers = @{\r\n",
+                "        'x-functions-key' = $DefaultHostKey\r\n",
+                "    }\r\n",
+                "    $Result['Header']=$headers\r\n",
+                "\r\n",
+                "    # Build string for Function URL \r\n",
+                "    $specified_Subscription= az account show --subscription $Subscription -o json |ConvertFrom-Json #Get SpecifiedSubscriptionID\r\n",
+                "    $SubscriptionID= $specified_Subscription.id\r\n",
+                "    $FunctionUrl = 'https://'+ $FunctionName +'.azurewebsites.net/api/subscriptions/'+ $SubscriptionID +'/resourceGroups/' + $ADPResourceGroupName + '/Import'\r\n",
+                "    $Result['FunctionURL']=$FunctionUrl\r\n",
+                "\r\n",
+                "    # Set parameter variables for Body\r\n",
+                "    ## Get BatchAccountURL \r\n",
+                "    $specified_Batch = az batch account show -n $BatchAccountName -g $ADPResourceGroupName  --subscription $Subscription -o JSON 2>$null | ConvertFrom-Json\r\n",
+                "    $BatchAccountURL = 'https://' + $specified_Batch.accountEndpoint\r\n",
+                "\r\n",
+                "    ## Get default SubNet ID for specified VNet\r\n",
+                "    $specified_VNet_SubNet = az network vnet subnet show -g $ADPResourceGroupName --vnet-name $VNetName -n 'default' --subscription $Subscription -o JSON |ConvertFrom-Json\r\n",
+                "    $VNetSubNetID = $specified_VNet_SubNet.id\r\n",
+                "\r\n",
+                "    ## Create access token to source sql server\r\n",
+                "    $targetAccessToken = az account get-access-token --resource=https://database.windows.net --query accessToken\r\n",
+                "    $targetAccessToken\r\n",
+                "\r\n",
+                "    ## Build JSon object for Body\r\n",
+                "    $Body = @{\r\n",
+                "        batchAccountUrl = $BatchAccountURL\r\n",
+                "        VNetSubnetId= $VNetSubNetID\r\n",
+                "        storageAccountName = $BackupFiles_StorageAccount\r\n",
+                "        containerName = $BackupFiles_ContainerName\r\n",
+                "        targetSqlServerResourceGroupName = $TargetRGName\r\n",
+                "        targetSqlServerName = $SQLServerName \r\n",
+                "        userName = $SqlServerLogin \r\n",
+                "        targetAccessToken = $targetAccessToken\r\n",
+                "        sqlAdminPassword = $SqlServerPassword\r\n",
+                "    }\r\n",
+                "    $json = $Body | ConvertTo-Json\r\n",
+                "    $Result['Body']=$json\r\n",
+                "\r\n",
+                "    $Result\r\n",
+                "    \r\n",
+                "}\r\n",
+                "\r\n",
+                "function Provision-FuncRBAC {\r\n",
+                "    [CmdletBinding()]\r\n",
+                "    param (\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Subscription,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$ResourceGroupName,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$FunctionName,\r\n",
+                "        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$ScopeRGName,\r\n",
+                "        [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string]$Role=\"Contributor\"\r\n",
+                "    )\r\n",
+                "\r\n",
+                "    # Get the scope resource group's ID\r\n",
+                "    $scopeID = az group show --resource-group $ScopeRGName --subscription $Subscription --query \"[id]\" -o JSON | ConvertFrom-Json \r\n",
+                "    if(!$scopeID) {\r\n",
+                "        Write-Output \"Provision-FuncRBAC failed.\" \r\n",
+                "        return }\r\n",
+                "    else { Write-Output \"Found scope '$ScopeRGName' with ID... '$scopeID'\"\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    # Get the az function principal id\r\n",
+                "    $app_PrincipalID = az functionapp show -n $FunctionName --resource-group $ResourceGroupName --subscription $Subscription --query \"[identity.principalId]\" -o JSON  | ConvertFrom-Json  \r\n",
+                "    if(!$app_PrincipalID) {\r\n",
+                "        Write-Output \"Provision-FuncRBAC failed.\" \r\n",
+                "        return }\r\n",
+                "    else {  Write-Output \"Found principal id of Azure function '$FunctionName'... '$app_PrincipalID'\"\r\n",
+                "    }\r\n",
+                "\r\n",
+                "    # Verify if a role assignment has been created for function\r\n",
+                "    $app_RoleAssignmentDefinition= az role assignment list --subscription $Subscription --assignee $app_PrincipalID --scope $scopeID --query \"[].roleDefinitionName\" -o JSON 2>$null  | ConvertFrom-Json\r\n",
+                "\r\n",
+                "    if($app_RoleAssignmentDefinition -eq $Role)\r\n",
+                "    {\r\n",
+                "        Write-Output \"Found Role Assignment for Principal ID.. '$app_PrincipalID' with Role.. '$app_RoleAssignmentDefinition' . No work needed\"\r\n",
+                "    }\r\n",
+                "    else\r\n",
+                "    {\r\n",
+                "        # Continue to setup RBAC, once we verify an assignment is not setup and all the resources exist\r\n",
+                "        Write-Output \"Creating new role assignment by running: 'az functionapp identity assign -n $FunctionName --role $Role -g $ResourceGroupName --scope $scopeID --subscription $Subscription'\"\r\n",
+                "        Write-Warning \"If your account does not have the access to assign new roles as Owner or User Access Administrator for the resource group, than you will need to contact your Azure AD Administrator to assign a service principle using the commands above\"\r\n",
+                "        az functionapp identity assign -n $FunctionName --role $Role -g $ResourceGroupName --scope $scopeID --subscription $Subscription \r\n",
+                "    }\r\n",
+                "}\r\n",
+                "Write-Host \"Helper Functions Created successfully\"  "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "ea9ddf9c-4c09-4db3-94ea-e01bc5f105c9",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Connect to Azure Account\r\n",
+                "Run the below cell to login to an Azure account. <b>Be sure to check the Windows Taskbar for a login dialog box underneath the notebook or other windows or by pressing Alt+TAB.</b>"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2a5a155f-556d-451d-a0d4-b802017946df"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Login-Azure"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "77067078-7ec5-4d07-803b-5c52dfcf06f0",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Verify ADP Resources \r\n",
+                "Verify if ADP resources exists in specified Resource Group"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "717960de-4baf-4b78-bcec-3a8e386b42b7"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Verify-ADPResources -Subscription $Env:BOOTSTRAP_Subscription -ADPResourceGroupName $Env:BOOTSTRAP_ResourceGroup `\r\n",
+                "                     -BatchAccountName $Env:BOOTSTRAP_BATCH -FunctionName $Env:BOOTSTRAP_FUNC -VNetName $Env:BOOTSTRAP_VNET "
+            ],
+            "metadata": {
+                "azdata_cell_guid": "22e8a9ed-d2e0-46eb-92b0-8c1108b60b67",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a resource group\r\n",
+                "\r\n",
+                "In Azure, all resources are allocated in a resource management group. Resource groups provide logical groupings of resources that make them easier to work with as a collection"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "73e7c7f9-f518-4b0a-87d1-d43c6a10cbad"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$rsgExists = az group exists -n $Env:BOOTSTRAP_ResourceGroup\r\n",
+                "if ($rsgExists -eq 'false') {\r\n",
+                "    az group create -l $Location -n $Env:BOOTSTRAP_ResourceGroup\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7e95158c-9ddc-4b5e-b289-2086a2d03c15",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a virtual machine\r\n",
+                "\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "222d6b8d-9789-4416-b512-63b1ce6f11e0"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "az vm create -n sql -g $Env:BOOTSTRAP_ResourceGroup --image MicrosoftSQLServer:SQL2017-WS2016:Standard:14.0.1000200 --location $Location --admin-username $VmUsername --admin-password $VmPassword --verbose"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "37ab908d-839a-45e9-89cf-78ed2452debe",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Get VM information with queries\r\n",
+                "Now that a VM has been created, detailed information about it can be retrieved. The common command for getting information from a resource is _show_"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "9de4dd69-c9e3-4e10-89f1-cc785ea712b4"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "az vm show --name TutorialVM1 --resource-group TutorialResources"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "51a4a092-1697-476c-934f-75a1a30dab7f",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Lot of information can be seen, which can be difficult to parse visually. The returned JSON contains information on authentication, network interfaces, storage, and more. Most importantly, it contains the Azure object IDs for resources that the VM is connected to. Object IDs allow accessing these resources directly to get more information about the VM's configuration and capabilities.\r\n",
+                "\r\n",
+                "In order to extract the object ID , the --query argument is used. Queries are written in the JMESPath query language. Start with getting the network interface controller (NIC) object ID"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "902e834e-bbbf-40f4-80db-48d3b4b90f99"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "az vm show --name TutorialVM1 \\\r\n",
+                "  --resource-group TutorialResources \\\r\n",
+                "  --query 'networkProfile.networkInterfaces[].id' \\\r\n",
+                "  --output tsv"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2bc8e0c7-d936-42ce-9e6a-e66207cd78f0",
+                "tags": [
+                    "hide_input"
+                ]
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Appendix: Locations\r\n",
+                "See the <a href=\"https://azure.microsoft.com/en-us/global-infrastructure/locations/\">Azure locations</a> page for a complete list of Azure regions along with their general physical location. The following is a list of common North American location settings for this guide:\r\n",
+                "\r\n",
+                "#### US Regions\r\n",
+                "| Setting | Location |\r\n",
+                "| ------------ | --------- |\r\n",
+                "| Central US | Iowa |\r\n",
+                "| East US | Virginia |\r\n",
+                "| East US 2 | Virginia |\r\n",
+                "| North Central US | Illinois |\r\n",
+                "| South Central US | Texas |\r\n",
+                "| West US 2 | Washington |\r\n",
+                "| West Central US | Wyoming |\r\n",
+                "| West US | California | \r\n",
+                "| Canada Central | Toronto |\r\n",
+                "| Canada East | Quebec City |\r\n",
+                "| Brazil South | Sao Paulo |\r\n",
+                "| Mexico Central | Queretaro |\r\n",
+                "\r\n",
+                "### Appendix: Storage Skus\r\n",
+                "Use these as values for provisioning storage skus. \r\n",
+                "\r\n",
+                "Data for table taken from <a href=\"https://docs.microsoft.com/en-us/rest/api/storagerp/srp_sku_types\">SKU Types</a> page but is subject to change. Not all skus are listed here. SKU type names are case-sensitive.\r\n",
+                "\r\n",
+                "| Name |\r\n",
+                "| -----|\r\n",
+                "| Developer |\r\n",
+                "| Enterprise |\r\n",
+                "| Express |\r\n",
+                "| Standard |\r\n",
+                "| Web |"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "a40b46e4-4b92-4ada-b022-4342ce1b35f2"
+            }
+        }
+    ]
+}

+ 383 - 0
SQL-Hybrid-Cloud-Toolkit/content/provisioning/create-sqlvm.ipynb

@@ -0,0 +1,383 @@
+{
+    "metadata": {
+        "kernelspec": {
+            "name": "powershell",
+            "display_name": "PowerShell"
+        },
+        "language_info": {
+            "name": "powershell",
+            "codemirror_mode": "shell",
+            "mimetype": "text/x-sh",
+            "file_extension": ".ps1"
+        }
+    },
+    "nbformat_minor": 2,
+    "nbformat": 4,
+    "cells": [
+        {
+            "cell_type": "markdown",
+            "source": [
+                "Create Azure SQL Virtual Machine\n",
+                "============================================\n",
+                "\n",
+                "Description\n",
+                "-----------\n",
+                "For more information about other Azure PowerShell options for creating SQL VMs, see the <a href=\"https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sql/virtual-machines-windows-ps-sql-create\">Provisioning guide for SQL Server VMs with Azure PowerShell</a>. See also <a href=\"https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sql/quickstart-sql-vm-create-powershell\">Quickstart guide</a> to creating a SQL Server VM with Azure PowerShell.\n",
+                "\n",
+                "\n",
+                "Steps of this procedure include:\n",
+                "1. Connect to Azure subscription\n",
+                "1. Provision resource group for SQL VM migration\n",
+                "2. Create a storage account\n",
+                "3. Configure Network Settings\n",
+                "3. Provision SQL VM\n",
+                "4. Configure SQL VM IaaS agent"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e479b550-d6bd-49c5-965a-34a7d1d16412"
+            }
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "#### <b> *Enter all the values in quotes *</b>"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "37db2e50-dcde-4dd5-820c-7dc11212f1eb"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Resource Group  \r\n",
+                "$ResourceGroupName  =   \"\"                             # Name of the resource group to create in the current subscription\r\n",
+                "$Location           =   \"\"                             # see Appendix for a list of location settings\r\n",
+                "\r\n",
+                "# Compute\r\n",
+                "$VMName             =   \"\"                             # VM to create\r\n",
+                "$PublisherName      =   \"\"                             # Name of Publisher, Default would be 'MicrosoftSQLServer'\r\n",
+                "$Version            =   \"\"                             # Version of VM, Default would be 'latest'\r\n",
+                "\r\n",
+                "# Storage\r\n",
+                "$StorageAccountName =   $ResourceGroupName + \"_storage\"\r\n",
+                "$StorageSku         =   \"\"                             # Choose your storage sku (see appendix)\r\n",
+                "$StorageName        =   \"sqlstorage\" + (Get-Random -Minimum 1 -Maximum 100)\r\n",
+                "\r\n",
+                "# VM Password\r\n",
+                "$secureVMPassword   =   \"\"                             # Create the password  for VM."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b9aff9cc-a3af-41cb-a2a5-35f36b2bcc55",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Connect to Azure Account"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b06fee5e-355d-47fc-8c1f-41294756cc87"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Connect-AzAccount"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "7dbdc638-d6cd-4ebe-9268-3efc18d3c415"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Get Subscription\r\n",
+                "Below command will open a _Dialouge Box_ with list of subscriptions. Selecting one of those will set that subscription for rest of the commands."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b58f1048-3e9d-4888-bda0-4d0443a11c97"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$subscription = Get-AzSubscription | Out-GridView -PassThru\r\n",
+                "Set-AzContext -SubscriptionName $subscription"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "0cc44e68-3810-46f4-b29c-e6ad4321e384"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create Azure Resource Group"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c17dd324-5c55-484f-8a25-2a5a7e43633e"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Create Azure resource group, if necessary\r\n",
+                "$rg = Get-AzResourceGroup | Where ResourceGroupName -eq $ResourceGroupName\r\n",
+                "\r\n",
+                "if (!$rg)\r\n",
+                "{\r\n",
+                "    # Need to create a new resource group\r\n",
+                "    Write-Output \"Resource Group $ResourceGroupName does not exist. Creating...\"\r\n",
+                "    $rg = New-AzResourceGroup -Name $ResourceGroupName -Location $Location\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2c37ef31-5f47-4918-a2b3-05e11aab28da"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create a storage account\r\n",
+                "VMs require storage resources for OS, SQL data and logs. Create a new storage account as a place for it."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "11b78695-40d3-45d7-8e3c-32d086ddf94a"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$StorageAccount = Get-AzStorageAccount | Where StorageAccountName -eq $StorageAccountName\r\n",
+                "\r\n",
+                "if (!$StorageAccount)\r\n",
+                "{\r\n",
+                "    Write-Output \"Storage Account $StorageName does not exist. Creating...\"\r\n",
+                "    $StorageAccount = New-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageName -SkuName $StorageSku -Kind \"Storage\" -Location $Location\r\n",
+                "}"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "f992bf91-a84a-40c2-813b-cb778907370d",
+                "tags": []
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Configure network settings\r\n",
+                "Create a virtual network, subnet, and a public IP address. These resources are used to provide network connectivity to the virtual machine and connect it to the internet."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "202634eb-7edf-4ff4-8486-fffbda45dbc8"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$SubnetName = $ResourceGroupName + \"subnet\"\r\n",
+                "$VnetName = $ResourceGroupName + \"vnet\"\r\n",
+                "$PipName = $ResourceGroupName + $(Get-Random)\r\n",
+                "\r\n",
+                "# Create a subnet configuration\r\n",
+                "$SubnetConfig = New-AzVirtualNetworkSubnetConfig -Name $SubnetName -AddressPrefix 192.168.1.0/24\r\n",
+                "\r\n",
+                "# Create a virtual network\r\n",
+                "$Vnet = New-AzVirtualNetwork -ResourceGroupName $ResourceGroupName -Location $Location `\r\n",
+                "   -Name $VnetName -AddressPrefix 192.168.0.0/16 -Subnet $SubnetConfig\r\n",
+                "\r\n",
+                "# Create a public IP address and specify a DNS name\r\n",
+                "$Pip = New-AzPublicIpAddress -ResourceGroupName $ResourceGroupName -Location $Location `\r\n",
+                "   -AllocationMethod Static -IdleTimeoutInMinutes 4 -Name $PipName"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "af88cdae-1a62-4990-9231-094481c9337d"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "2. Create a network security group. Configure rules to allow remote desktop (RDP) and SQL Server connections."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "3b25e16e-b150-4a2e-80dc-66f2d18b43fb"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Rule to allow remote desktop (RDP)\r\n",
+                "$NsgRuleRDP = New-AzNetworkSecurityRuleConfig -Name \"RDPRule\" -Protocol Tcp `\r\n",
+                "   -Direction Inbound -Priority 1000 -SourceAddressPrefix * -SourcePortRange * `\r\n",
+                "   -DestinationAddressPrefix * -DestinationPortRange 3389 -Access Allow\r\n",
+                "\r\n",
+                "#Rule to allow SQL Server connections on port 1433\r\n",
+                "$NsgRuleSQL = New-AzNetworkSecurityRuleConfig -Name \"MSSQLRule\"  -Protocol Tcp `\r\n",
+                "   -Direction Inbound -Priority 1001 -SourceAddressPrefix * -SourcePortRange * `\r\n",
+                "   -DestinationAddressPrefix * -DestinationPortRange 1433 -Access Allow\r\n",
+                "\r\n",
+                "# Create the network security group\r\n",
+                "$NsgName = $ResourceGroupName + \"nsg\"\r\n",
+                "$Nsg = New-AzNetworkSecurityGroup -ResourceGroupName $ResourceGroupName `\r\n",
+                "   -Location $Location -Name $NsgName `\r\n",
+                "   -SecurityRules $NsgRuleRDP,$NsgRuleSQL"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "debe940d-0d0f-4540-be5b-4d6495d338e1"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "3. Create the network interface."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "d44de03c-d4f2-48ef-8a60-507069d6c08e"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "$InterfaceName = $ResourceGroupName + \"int\"\r\n",
+                "$Interface = New-AzNetworkInterface -Name $InterfaceName `\r\n",
+                "   -ResourceGroupName $ResourceGroupName -Location $Location `\r\n",
+                "   -SubnetId $VNet.Subnets[0].Id -PublicIpAddressId $Pip.Id `\r\n",
+                "   -NetworkSecurityGroupId $Nsg.Id"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6dbb3ea0-b52f-4ed2-bd24-59096d134e88"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create the SQL VM\r\n",
+                "1. Define your credentials to sign in to the VM. The username is \"azureadmin\". Make sure you change <password> before running the command."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "b25dca9e-269b-45db-8cdf-efa53e2213d2"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Define a credential object\r\n",
+                "$SecurePassword = ConvertTo-SecureString $secureVMPassword `\r\n",
+                "   -AsPlainText -Force\r\n",
+                "$Cred = New-Object System.Management.Automation.PSCredential (\"azureadmin\", $securePassword)"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "40d0c992-860a-4b83-8104-16ec7e6e7983"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "### Appendix: Azure SQL VM Offers\r\n",
+                "Run the following command to get updated list of offers for Microsoft SQL Server in your location. These settings can be used for the OfferName associated with this migration. Configure it for your purposes.\r\n",
+                "\r\n",
+                "Note that the SQL Version is first then appended with an operating system version. E.g.: \"WS2019\" means Windows Server 2019. Along with various versions of Windows Servers, there are also enterprise Linux versions such as RedHat Enterprise, Suse Enterprise, and Ubuntu. Some versions are BYOL (Bring Your Own License) aka <a href=\"https://azure.microsoft.com/en-us/pricing/hybrid-benefit/\">Hybrid Benefit</a>."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "ce2daf93-7c20-4073-babc-53c9187e5691"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Get-AzVMImageOffer -Location $Location -Publisher $PublisherName | Select Offer"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "e5808cbf-e6c6-4abe-8e1b-3c282bc7667c"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "2. Create a virtual machine configuration object and then create the VM. The following command creates a SQL Server 2017 Developer Edition VM on Windows Server 2016."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "2038172f-3f29-499c-ad68-88a1d96ead1f"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Create a virtual machine configuration\r\n",
+                "\r\n",
+                "$VMConfig = New-AzVMConfig -VMName $VMName -VMSize Standard_DS13_V2 |\r\n",
+                "   Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $Cred -ProvisionVMAgent -EnableAutoUpdate |\r\n",
+                "   Set-AzVMSourceImage -PublisherName \"MicrosoftSQLServer\" -Offer \"SQL2017-WS2016\" -Skus \"SQLDEV\" -Version \"latest\" |\r\n",
+                "   Add-AzVMNetworkInterface -Id $Interface.Id\r\n",
+                "\r\n",
+                ""
+            ],
+            "metadata": {
+                "azdata_cell_guid": "6625d7b2-6c97-432e-b5f5-be4ca93017ae"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Create Virtual Machine"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "c42ec570-331a-46ea-b358-b05e47320967"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "# Create the VM\r\n",
+                "New-AzVM -ResourceGroupName $ResourceGroupName -Location $Location -VM $VMConfig"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "05fa1f3d-94e1-480f-ad20-d3006bafc6ac"
+            },
+            "outputs": [],
+            "execution_count": null
+        },
+        {
+            "cell_type": "markdown",
+            "source": [
+                "## Install the SQL IaaS Agent\r\n",
+                "To get portal integration and SQL VM features, you must install the SQL Server IaaS Agent Extension. To install the agent on the new VM, run the following command after the VM is created."
+            ],
+            "metadata": {
+                "azdata_cell_guid": "ef07b3d1-3e2d-45f0-b9d3-fb00be2a7da9"
+            }
+        },
+        {
+            "cell_type": "code",
+            "source": [
+                "Set-AzVMSqlServerExtension -ResourceGroupName $ResourceGroupName -VMName $VMName -name \"SQLIaasExtension\" -version \"2.0\" -Location $Location"
+            ],
+            "metadata": {
+                "azdata_cell_guid": "bb3b5436-c34b-44b3-b631-ea60c9dcf16a"
+            },
+            "outputs": [],
+            "execution_count": null
+        }
+    ]
+}

+ 9 - 0
SQL-Hybrid-Cloud-Toolkit/content/provisioning/readme.md

@@ -0,0 +1,9 @@
+# Provisioning
+[Home](../readme.md)
+
+This chapter contains Notebooks that help provision new Azure SQL resources that can be used as migration targets for existing on-premises SQL instances and databases. Use alongside the planning notebooks to use existing resources as the basis for the best type of resource to create and how it should be configured.  You can use the notebooks and configure the settings manually or provide a provisioning plan created by the [Create Provisioning Plan](../provisioning/provisioning-plan.ipynb) notebook.
+
+## Notebooks in this Chapter
+- [Create Azure SQL Virtual Machine](create-sqlvm.ipynb) - SQL Server on Azure Virtual Machines enables to use full versions of SQL Server in the cloud without having to manage any on-premises hardware. The virtual machine image gallery allows to create a SQL Server VM with the right version, edition, and operating system
+- [Create Azure SQL Managed Instance](create-sqlmi.ipynb) - Azure SQL Managed Instance is the intelligent, scalable, cloud database service that combines the broadest SQL Server engine compatibility with all the benefits of a fully managed and evergreen platform as a service. An instance is a copy of the sqlservr.exe executable that runs as an operating system service
+- [Create Azure SQL Database](create-sqldb.ipynb) - Azure SQL Database is Microsoft's fully managed cloud relational database service in Microsoft Azure. It shares the same code base as traditional SQL Servers but with Microsoft's Cloud first strategy the newest features of SQL Server are actually released to Azure SQL Database first. Use this notebook when a need is systematic collection of data that stores data in tables

+ 35 - 0
SQL-Hybrid-Cloud-Toolkit/content/readme.md

@@ -0,0 +1,35 @@
+# Welcome to the Azure SQL Hybrid Cloud Toolkit!
+## Chapters
+* [Prerequisites and Initial Setup](prereqs.ipynb) - Notebook installation of required modules.
+
+* [Assessments](Assessments/readme.md) - Notebooks that contain examples to determine whether a given database or SQL Server instance is ready to migrate by utilizing SQL Assessments. SQL instances are scanned based on a "best practices" set of rules. 
+
+* [Networking](networking/readme.md) - Setup secure Point-to-Site (P2S) or Site-to-Site (S2S) network connectivity to Microsoft Azure using a Virtual Private Network (VPN). This notebook serves as a building block for other notebooks as communicating securely between on-premise and Azure is essential for many tasks.
+
+* [Provisioning](provisioning/readme.md) - Creating and communicating with SQL Resources in Microsoft Azure. Includes common tasks such as creating SQL Virtual Machines or SQL Managed Instances in the cloud.
+
+* [Data Portability](data-portability/readme.md) - Install a custom Azure function to facilitate importing and exporting cloud resources. The solution uses parallel tasks in Azure Batch to perform data storage work. Azure Batch is a process that runs large-scale parallel and high-performance computing jobs efficiently in Azure. 
+
+* [High Availability and Disaster Recovery](hadr/readme.md) - Notebooks to leverage Azure SQL for business continuity in a hybrid cloud environment.
+
+* [Offline Migration](offline-migration/readme.md) - Notebooks to perform various migrations.
+
+* [Glossary](glossary.md) - set of defined terms.
+
+* [Appendices](appendices.md) - misc info.
+
+## About
+
+The **Azure SQL Hybrid Cloud Toolkit** is a [Jupyter Book](https://jupyterbook.org/intro.html) extension of [Azure Data Studio](https://docs.microsoft.com/en-us/sql/azure-data-studio/download-azure-data-studio) (ADS) designed to help [Azure SQL Database](https://azure.microsoft.com/en-us/services/sql-database/) and ADS users deploy, migrate and configure for a hybrid cloud environment. The toolkit was designed with and intended to be executed within ADS. This is to ensure the best possible user experience for those without vast knowledge of Azure services while adhering closely to the software _best practices_ standards required by experienced cloud users. 
+
+## Goals and Methodology
+The toolkit better positions a customer with regards to planning, migrating, and thriving in a hybrid cloud environment by:
+
+* Providing SQL Azure users with reliable free software and content that is well-written and executable
+* Greatly simplifying the integration of Azure Data services into an existing environment
+* Positioning Azure to be the natural cloud services choice with a low-friction experience 
+* Notebooks are executable by a normal user (unless otherwise specificed) on minimal hardware
+* Most notebooks require some configuration. If so, the proper configurations should be clearly located towards the top of the notebook or cell, whichever is most appropriate
+* By design, Notebooks are written to be executed from top-to-bottom. Therefore, each notebook has a specific task to perform and should focus only on that task. It may contain several cells to execute but it will adhere to the one-task per notebook paradigm
+
+**NOTE:** Executing notebooks could potentially create new Azure Resources which may incur charges to the Azure Subscription. Make sure the repercussions of executing any cells are understood.

+ 2 - 0
SQL-Hybrid-Cloud-Toolkit/requirements.txt

@@ -0,0 +1,2 @@
+# Requirements for the demo notebooks
+# Useful for MyBinder configuration

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio