Script SQL Server pour supprimer les comptes qui ne sont plus dans Active Directory

8

Nous avons un SQL Server 2000 qui sera prochainement migré vers SQL Server 2005. Il a des années de comptes d'authentification Windows créés qui n'existent plus dans Active Directory, ce qui empêche l'Assistant Copie de base de données de créer ces comptes sur le nouveau serveur.

Existe-t-il un script ou un moyen automatisé de supprimer les comptes qui n'existent plus dans notre Active Directory?


EDIT: Juste pour être clair, les connexions à supprimer sont sur SQL Server 2000, qui ne prend pas en charge la DROP LOGINcommande.

Séparément, la suppression manuelle des connexions dans SQL Server 2000 serait (je pense) effectuée, exec sp_droplogin 'loginname'mais sur la mienne, le nom de connexion est introuvable, que j'utilise «domaine \ nom de connexion» ou «nom de connexion»

Pour ajouter à la confusion, exec sp_revokelogin 'domain\loginname'cela semble fonctionner.

EDIT 2: Enfin résolu le problème. Un grand nombre des ouvertures de session problématiques ont été ajoutées par programme à la base de données, et bien qu'elles fonctionnent dans le sens où un utilisateur pouvait se connecter, le nom d'utilisateur par rapport au nom de connexion NT comportait une incompatibilité des ouvertures de session avec préfixe de domaine lorsque SQL Server n'attendait aucun domaine, et vice-versa inversement.

Pour résoudre ce problème, j'ai modifié la procédure sp_droplogin pour supprimer l'une des vérifications erronées.

J'accepte ma propre réponse car elle fonctionne dans SQL Server 2000.


la source

Réponses:

6

J'ai fini par lister les comptes avec:

    exec sp_validatelogins

Et courir

    exec sp_dropuser loginname
    exec sp_droplogin loginname

sur les résultats.


la source
4

D'après mon commentaire d'origine, il semble que la SUSER_SIDfonction ne saisit que le sid enregistré lors de la création de la connexion et ne fait pas de requête sur Active Directory (cela a du sens, car cela pourrait être coûteux - j'ai même essayé de redémarrer le service serveur).

Voici une application console C # qui accomplit la tâche, vous permettant d'auditer les connexions qui seront supprimées avant qu'elles ne soient réellement supprimées.

Cette application nécessite .NET 3.5 ou supérieur pour fonctionner, et en théorie, elle pourrait être placée dans un script PowerShell (je suis beaucoup plus à l'aise avec la programmation directe).

Pour supprimer toutes les connexions des comptes d'utilisateurs locaux / machine du serveur, vous devrez exécuter cette application sur la machine serveur et coder en dur la ContextTypevariable (je l'ai comme ça pour tester sur mon ordinateur personnel non lié au domaine ). Sinon, vous pouvez l'exécuter à partir de n'importe quelle machine du même domaine que le serveur, qui a également accès au serveur.

Je vais poster ceci sur mon blog après avoir externalisé les paramètres et nettoyé un peu le code, donc quand je fais ça, je vais éditer ce post. Mais cela vous permettra de commencer maintenant.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.DirectoryServices.AccountManagement;
using System.Security.Principal;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string connectionString = @"Data Source=.\SQL2008R2DEV;Initial Catalog=master;Integrated Security=SSPI;";
            ContextType domainContext = Environment.UserDomainName == Environment.MachineName ? ContextType.Machine : ContextType.Domain;

            IList<string> deletedPrincipals;

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();

                deletedPrincipals = _GetDeletedPrincipalsFromServer(conn, domainContext);
            }

            if (deletedPrincipals.Count > 0)
            {
                Console.WriteLine("Logins that will be dropped:");

                foreach (string loginName in deletedPrincipals)
                    Console.WriteLine(loginName);

                Console.WriteLine();
                Console.WriteLine("Press Enter to continue.");
                Console.ReadLine();
            }
            else
                Console.WriteLine("No logins with deleted principals.");

            if (deletedPrincipals.Count > 0)
            {
                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    conn.Open();

                    _DropDeletedPrincipalLoginsFromServer(conn, deletedPrincipals);
                }

                Console.WriteLine("Logins dropped successfully.");
            }

            Console.WriteLine();
            Console.WriteLine("Press Enter to continue.");
            Console.ReadLine();
        }

        private static void _DropDeletedPrincipalLoginsFromServer(IDbConnection conn, IList<string> loginNames)
        {
            if (loginNames.Count == 0)
                return;


            StringBuilder sb = new StringBuilder();

            foreach (string loginName in loginNames)
                sb.AppendFormat("DROP LOGIN {0};", loginName);  // This was escaped on the way out of SQL Server


            IDbTransaction transaction = conn.BeginTransaction();

            IDbCommand cmd = conn.CreateCommand();
            cmd.Transaction = transaction;
            cmd.CommandText = sb.ToString();

            try
            {
                cmd.ExecuteNonQuery();

                transaction.Commit();
            }
            catch
            {
                try
                {
                    transaction.Rollback();
                }
                catch { }

                throw;
            }
        }

        private static IList<string> _GetDeletedPrincipalsFromServer(IDbConnection conn, ContextType domainContext)
        {
            List<string> results = new List<string>();

            IDbCommand cmd = conn.CreateCommand();
            cmd.CommandText = "SELECT sid, QUOTENAME(loginname) AS LoginName FROM sys.syslogins WHERE isntname = 1;";

            IDataReader dr = null;

            try
            {
                dr = cmd.ExecuteReader(CommandBehavior.SingleResult);

                while (dr.Read())
                {
                    if (!_PrincipalExistsBySid((byte[])dr["sid"], domainContext))
                        results.Add((string)dr["LoginName"]);
                }
            }
            finally
            {
                if ((dr != null) && !dr.IsClosed)
                    dr.Close();
            }

            return results;
        }

        private static bool _PrincipalExistsBySid(byte[] principalSid, ContextType domainContext)
        {
            SecurityIdentifier sid = new SecurityIdentifier(principalSid, 0);

            if (sid.IsWellKnown) return true;

            using (PrincipalContext pc = new PrincipalContext(domainContext))
            {
                return AuthenticablePrincipal.FindByIdentity(pc, IdentityType.Sid, sid.Value) != null;
            }
        }
    }
}
Jon Seigel
la source
J'étais sur d'autres projets et je n'ai pas eu l'occasion d'essayer jusqu'à présent. Je pense que ce que je rencontre est que SQL Server 2005 est installé sur un serveur différent de SQL Server 2000, et les fonctions sys.syslog et DROP LOGIN ne sont pas prises en charge par SQL Server 2000 - la base de données ne sera pas transférée vers SQL Server 2005 en raison des échecs de création de connexion.
@emgee: Ohhh, j'ai totalement laissé tomber le ballon sur celui-là. Désolé. J'espère qu'il est clair où vous pouvez insérer la commande pour supprimer la connexion pour SQL Server 2000. Je n'avais pas d'instance à tester lorsque j'ai écrit cela.
Jon Seigel
Pas de soucis, les modifications ont été assez faciles.
4

Vous pouvez exploiter xp_logininfo pour ce processus. Cette procédure stockée étendue peut être utilisée pour fournir des informations à partir des connexions Active Directory pour Windows dans SQL Server. La procédure renvoie une erreur si aucune connexion n'existe, nous pouvons donc placer un bloc TRY / CATCH autour pour fournir SQL pour les connexions qui ne sont plus valides lorsque les erreurs de procédure:

declare @user sysname
declare @domain varchar(100)

set @domain = 'foo'

declare recscan cursor for
select name from sys.server_principals
where type = 'U' and name like @domain+'%'

open recscan 
fetch next from recscan into @user

while @@fetch_status = 0
begin
    begin try
        exec xp_logininfo @user
    end try
    begin catch
        --Error on xproc because login doesn't exist
        print 'drop login '+convert(varchar,@user)
    end catch

    fetch next from recscan into @user
end

close recscan
deallocate recscan

Avec la façon dont le script fonctionne, vous devrez définir la variable @domain sur le domaine que vous vérifiez. La requête du curseur ne filtrera que sur les connexions Windows (pas sur les groupes) dans ce domaine. Vous obtiendrez les résultats de la requête pour toutes les connexions valides, mais les instructions drop seront imprimées avec les messages. J'ai opté pour l'approche d'impression au lieu d'exécuter réellement le SQL afin que vous puissiez consulter et valider les résultats avant de supprimer les connexions.

Remarque, ce script ne créera que vos relevés de connexion de dépôt. Les utilisateurs devront toujours être supprimés des bases de données respectives. La logique appropriée peut être ajoutée à ce script si nécessaire. En outre, cela devra être exécuté dans votre environnement SQL 2005, car cette logique n'est pas prise en charge dans SQL 2000.

Mike Fal
la source
1
Il faut se méfier! Si le compte de service SQL Server est un compte local, Xp_logininforenvoie l'erreur 0x5, ce qui signifie que l'accès est refusé, pour un compte de domaine valide. Cela entraîne la suppression de chaque compte de domaine répertorié. La sp_validateloginsprocédure stockée produira les mêmes résultats, que le compte de service SQL Server soit un compte local ou un compte de domaine.
Gili
0

Vous pouvez faire un drop et recréer dans une transaction comme celle-ci:

BEGIN TRAN
BEGIN TRY
DROP LOGIN [DOMAIN\testuser]
CREATE LOGIN [DOMAIN\testuser] FROM WINDOWS;
END TRY
BEGIN CATCH
  SELECT ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_LINE();
END CATCH
ROLLBACK  

Si l'erreur que vous obtenez est la suivante: Windows NT user or group 'DOMAIN\testuser' not found. Check the name again.votre identifiant Windows n'existe plus. Cependant, il existe de nombreuses raisons pour lesquelles la suppression elle-même échouera (par exemple, les autorisations accordées par la connexion). Vous devrez les suivre manuellement.

Sebastian Meine
la source
TRY ... CATCHa été introduit dans SQL 2005. stackoverflow.com/questions/5552530/sql-server-2000-try-catch
Jon Seigel
C'est exact. Je n'ai pas vu cette restriction. (Je suppose que j'ai commencé à lire dans la deuxième ligne ...) Vous pouvez probablement encore utiliser cette méthode, en vérifiant simplement @@ ERROR au lieu d'utiliser le try catch. Cependant, je n'ai pas d'installation SQL Server aussi ancienne pour tester cela.
Sebastian Meine