diff --git a/Packages.props b/Packages.props
index d0b1e6b4..882c9ca6 100644
--- a/Packages.props
+++ b/Packages.props
@@ -28,6 +28,7 @@
+
diff --git a/bin/nuget/Microsoft.SqlServer.Migration.Logins.1.0.20221103.24.nupkg b/bin/nuget/Microsoft.SqlServer.Migration.Logins.1.0.20221103.24.nupkg
new file mode 100644
index 00000000..7eb80001
Binary files /dev/null and b/bin/nuget/Microsoft.SqlServer.Migration.Logins.1.0.20221103.24.nupkg differ
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
index ee92b620..3658ceed 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
+++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
@@ -53,6 +53,7 @@
+
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/StartLoginMigration.cs b/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/StartLoginMigration.cs
new file mode 100644
index 00000000..41ecdb05
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/Contracts/StartLoginMigration.cs
@@ -0,0 +1,135 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using Microsoft.SqlServer.DataCollection.Common.Contracts.OperationsInfrastructure;
+using Microsoft.SqlTools.Hosting.Protocol.Contracts;
+using System.Collections.Generic;
+
+namespace Microsoft.SqlTools.ServiceLayer.Migration.Contracts
+{
+ ///
+ /// Represents the steps in login migration.
+ ///
+ public enum LoginMigrationStep
+ {
+ ///
+ /// Run pre-migration validations
+ ///
+ StartValidations,
+
+ ///
+ /// Step to hash passwords and migrate logins
+ ///
+ MigrateLogins,
+
+ ///
+ /// Step to establish users and logins from source to target
+ ///
+ EstablishUserMapping,
+
+
+ ///
+ /// Step to migrate server roles
+ ///
+ MigrateServerRoles,
+
+ ///
+ /// Step to establish roles
+ ///
+ EstablishServerRoleMapping,
+
+ ///
+ /// Step to map all the grant/deny permissions for logins
+ ///
+ SetLoginPermissions,
+
+ ///
+ /// Step to map all server roles grant/deny permissions
+ ///
+ SetServerRolePermissions
+ }
+
+ public class StartLoginMigrationParams
+ {
+ ///
+ /// Connection string to connect to source
+ ///
+ public string SourceConnectionString { get; set; }
+
+ ///
+ /// Connection string to connect to target
+ ///
+ public string TargetConnectionString { get; set; }
+
+ ///
+ /// List of logins to migrate
+ ///
+ public List LoginList { get; set; }
+
+ ///
+ /// Azure active directory domain name (required for Windows Auth)
+ ///
+ public string AADDomainName{ get; set; }
+ }
+
+ public class LoginMigrationResult
+ {
+ ///
+ /// Start time of the assessment
+ ///
+ public IDictionary> ExceptionMap { get; set; }
+
+ ///
+ /// The login migration step that just completed
+ ///
+ public LoginMigrationStep CompletedStep { get; set; }
+
+ ///
+ /// How long this step took
+ ///
+ public string ElapsedTime{ get; set; }
+ }
+
+ public class StartLoginMigrationRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("migration/startloginmigration");
+ }
+
+ public class ValidateLoginMigrationRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("migration/validateloginmigration");
+ }
+
+ public class MigrateLoginsRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("migration/migratelogins");
+ }
+
+ public class EstablishUserMappingRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("migration/establishusermapping");
+ }
+ public class MigrateServerRolesAndSetPermissionsRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("migration/migrateserverrolesandsetpermissions");
+ }
+
+ public class LoginMigrationNotification
+ {
+ public static readonly
+ EventType Type =
+ EventType.Create("migration/loginmigrationnotification");
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs b/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs
index f0c9e035..1afb7fcd 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/MigrationService.cs
@@ -24,6 +24,7 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.Migration.Contracts;
using Microsoft.SqlTools.Utility;
+using Microsoft.SqlServer.Migration.Logins;
using Microsoft.SqlServer.Migration.SkuRecommendation.Aggregation;
using Microsoft.SqlServer.Migration.SkuRecommendation.Models.Sql;
using Microsoft.SqlServer.Migration.SkuRecommendation;
@@ -39,8 +40,12 @@ using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy.AzureSqlMa
using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy.AzureSqlDatabase;
using Microsoft.SqlServer.Migration.SkuRecommendation.Models;
using Microsoft.SqlServer.Migration.SkuRecommendation.Utils;
+using Microsoft.SqlServer.DataCollection.Common.Contracts.OperationsInfrastructure;
+using System.Threading;
+using Microsoft.SqlServer.Migration.Logins.Contracts;
using Microsoft.SqlServer.Migration.Assessment.Common.Models;
using Microsoft.SqlServer.Migration.Assessment.Common.Utils;
+using Microsoft.SqlTools.ServiceLayer.Migration.Utils;
namespace Microsoft.SqlTools.ServiceLayer.Migration
{
@@ -121,6 +126,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
this.ServiceHost.SetRequestHandler(StopPerfDataCollectionRequest.Type, HandleStopPerfDataCollectionRequest);
this.ServiceHost.SetRequestHandler(RefreshPerfDataCollectionRequest.Type, HandleRefreshPerfDataCollectionRequest);
this.ServiceHost.SetRequestHandler(GetSkuRecommendationsRequest.Type, HandleGetSkuRecommendationsRequest);
+ this.ServiceHost.SetRequestHandler(StartLoginMigrationRequest.Type, HandleStartLoginMigration);
+ this.ServiceHost.SetRequestHandler(ValidateLoginMigrationRequest.Type, HandleValidateLoginMigration);
+ this.ServiceHost.SetRequestHandler(MigrateLoginsRequest.Type, HandleMigrateLogins);
+ this.ServiceHost.SetRequestHandler(EstablishUserMappingRequest.Type, HandleEstablishUserMapping);
+ this.ServiceHost.SetRequestHandler(MigrateServerRolesAndSetPermissionsRequest.Type, HandleMigrateServerRolesAndSetPermissions);
}
///
@@ -325,6 +335,231 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
}
}
+ internal async Task HandleStartLoginMigration(
+ StartLoginMigrationParams parameters,
+ RequestContext requestContext)
+ {
+ try
+ {
+ ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
+ null, parameters.LoginList, parameters.AADDomainName);
+
+ IDictionary> exceptionMap = new Dictionary>();
+
+ exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) );
+ exceptionMap.AddExceptions( await loginMigration.MigrateLogins(CancellationToken.None) );
+ exceptionMap.AddExceptions( loginMigration.MigrateServerRoles(CancellationToken.None) );
+ exceptionMap.AddExceptions( loginMigration.EstablishUserMapping(CancellationToken.None) );
+ exceptionMap.AddExceptions( await loginMigration.EstablishServerRoleMapping(CancellationToken.None) );
+ exceptionMap.AddExceptions( loginMigration.SetLoginPermissions(CancellationToken.None) );
+ exceptionMap.AddExceptions( loginMigration.SetServerRolePermissions(CancellationToken.None) );
+
+ LoginMigrationResult results = new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap
+ };
+
+ await requestContext.SendResult(results);
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e.ToString());
+ }
+ }
+
+ internal async Task HandleValidateLoginMigration(
+ StartLoginMigrationParams parameters,
+ RequestContext requestContext)
+ {
+ try
+ {
+ ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
+ null, parameters.LoginList, parameters.AADDomainName);
+
+ IDictionary> exceptionMap = new Dictionary>();
+ Stopwatch stopWatch = new Stopwatch();
+ stopWatch.Start();
+ exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) );
+ stopWatch.Stop();
+ TimeSpan elapsedTime = stopWatch.Elapsed;
+
+ LoginMigrationResult results = new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.StartValidations,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+
+ };
+
+ await requestContext.SendResult(results);
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e.ToString());
+ }
+ }
+
+ internal async Task HandleMigrateLogins(
+ StartLoginMigrationParams parameters,
+ RequestContext requestContext)
+ {
+ try
+ {
+ ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
+ null, parameters.LoginList, parameters.AADDomainName);
+
+ IDictionary> exceptionMap = new Dictionary>();
+ Stopwatch stopWatch = new Stopwatch();
+ stopWatch.Start();
+ exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) );
+ exceptionMap.AddExceptions( await loginMigration.MigrateLogins(CancellationToken.None) );
+ stopWatch.Stop();
+ TimeSpan elapsedTime = stopWatch.Elapsed;
+
+ LoginMigrationResult results = new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.MigrateLogins,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+ };
+
+ await requestContext.SendResult(results);
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e.ToString());
+ }
+ }
+
+ internal async Task HandleEstablishUserMapping(
+ StartLoginMigrationParams parameters,
+ RequestContext requestContext)
+ {
+ try
+ {
+ ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
+ null, parameters.LoginList, parameters.AADDomainName);
+
+ IDictionary> exceptionMap = new Dictionary>();
+
+ Stopwatch stopWatch = new Stopwatch();
+ stopWatch.Start();
+ exceptionMap.AddExceptions( await loginMigration.StartValidations(CancellationToken.None) );
+ exceptionMap.AddExceptions( loginMigration.EstablishUserMapping(CancellationToken.None) );
+ stopWatch.Stop();
+ TimeSpan elapsedTime = stopWatch.Elapsed;
+
+ LoginMigrationResult results = new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.EstablishUserMapping,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+ };
+
+ await requestContext.SendResult(results);
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e.ToString());
+ }
+ }
+
+ internal async Task HandleMigrateServerRolesAndSetPermissions(
+ StartLoginMigrationParams parameters,
+ RequestContext requestContext)
+ {
+ try
+ {
+ ILoginsMigration loginMigration = new LoginsMigration(parameters.SourceConnectionString, parameters.TargetConnectionString,
+ null, parameters.LoginList, parameters.AADDomainName);
+
+ IDictionary> exceptionMap = new Dictionary>();
+ Stopwatch stopWatch = new Stopwatch();
+ stopWatch.Start();
+ exceptionMap.AddExceptions(await loginMigration.StartValidations(CancellationToken.None));
+ stopWatch.Stop();
+ TimeSpan elapsedTime = stopWatch.Elapsed;
+
+ await this.ServiceHost.SendEvent(
+ LoginMigrationNotification.Type,
+ new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.StartValidations,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+ });
+
+ stopWatch.Restart();
+ exceptionMap.AddExceptions(loginMigration.MigrateServerRoles(CancellationToken.None));
+ stopWatch.Stop();
+ elapsedTime = stopWatch.Elapsed;
+
+ await this.ServiceHost.SendEvent(
+ LoginMigrationNotification.Type,
+ new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.MigrateServerRoles,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+ });
+
+ stopWatch.Restart();
+ exceptionMap.AddExceptions(await loginMigration.EstablishServerRoleMapping(CancellationToken.None));
+ stopWatch.Stop();
+ elapsedTime = stopWatch.Elapsed;
+
+ await this.ServiceHost.SendEvent(
+ LoginMigrationNotification.Type,
+ new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.EstablishServerRoleMapping,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+ });
+
+ stopWatch.Restart();
+ exceptionMap.AddExceptions(loginMigration.SetLoginPermissions(CancellationToken.None));
+ stopWatch.Stop();
+ elapsedTime = stopWatch.Elapsed;
+
+ await this.ServiceHost.SendEvent(
+ LoginMigrationNotification.Type,
+ new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.SetLoginPermissions,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+ });
+
+ stopWatch.Restart();
+ exceptionMap.AddExceptions(loginMigration.SetServerRolePermissions(CancellationToken.None));
+ stopWatch.Stop();
+ elapsedTime = stopWatch.Elapsed;
+
+ await this.ServiceHost.SendEvent(
+ LoginMigrationNotification.Type,
+ new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.SetServerRolePermissions,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+ });
+
+ LoginMigrationResult results = new LoginMigrationResult()
+ {
+ ExceptionMap = exceptionMap,
+ CompletedStep = LoginMigrationStep.SetServerRolePermissions,
+ ElapsedTime = MigrationServiceHelper.FormatTimeSpan(elapsedTime)
+ };
+
+ await requestContext.SendResult(results);
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e.ToString());
+ }
+ }
+
internal RecommendationResultSet GenerateBaselineRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters) {
RecommendationResultSet resultSet = new RecommendationResultSet();
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Migration/Utils/ExtensionMethods.cs b/src/Microsoft.SqlTools.ServiceLayer/Migration/Utils/ExtensionMethods.cs
new file mode 100644
index 00000000..d355b313
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/Utils/ExtensionMethods.cs
@@ -0,0 +1,31 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.SqlServer.DataCollection.Common.Contracts.OperationsInfrastructure;
+
+namespace Microsoft.SqlTools.ServiceLayer.Migration.Utils
+{
+ internal static class ExtensionMethods
+ {
+ public static void AddExceptions(this IDictionary> exceptionMap1, IDictionary> exceptionMap2)
+ {
+ foreach (var keyValuePair2 in exceptionMap2)
+ {
+ // If the dictionary already contains the key then merge them
+ if (exceptionMap1.ContainsKey(keyValuePair2.Key))
+ {
+ foreach (var value in keyValuePair2.Value)
+ {
+ exceptionMap1[keyValuePair2.Key].Append(value);
+ }
+ continue;
+ }
+ exceptionMap1.Add(keyValuePair2);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Migration/Utils/MigrationServiceHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Migration/Utils/MigrationServiceHelper.cs
new file mode 100644
index 00000000..3c4541d6
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Migration/Utils/MigrationServiceHelper.cs
@@ -0,0 +1,17 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+
+namespace Microsoft.SqlTools.ServiceLayer.Migration.Utils
+{
+ internal static class MigrationServiceHelper
+ {
+ public static string FormatTimeSpan(TimeSpan ts)
+ {
+ return String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
+ }
+ }
+}