Script de SQL Server para eliminar cuentas que ya no están en Active Directory

8

Tenemos un SQL Server 2000 que pronto se migrará a SQL Server 2005. Cuenta con años de cuentas de autenticación de Windows creadas que ya no existen en Active Directory, lo que impide que el Asistente para copiar bases de datos cree estas cuentas en el nuevo servidor.

¿Existe un script o alguna forma automatizada de eliminar las cuentas que ya no existen en nuestro Active Directory?


EDITAR: para ser claros, los inicios de sesión que deben eliminarse están en SQL Server 2000, que no admite el DROP LOGINcomando.

Por separado, la eliminación manual de los inicios de sesión en SQL Server 2000 (creo) se haría con, exec sp_droplogin 'loginname'pero en el mío, no se puede encontrar el nombre de inicio de sesión, ya sea que use 'domain \ loginname' o 'loginname'

Solo para agregar a la confusión, exec sp_revokelogin 'domain\loginname'parece funcionar.

EDIT 2: Finalmente resolvió el problema. Muchos de los inicios de sesión que eran problemáticos se agregaron mediante programación a la base de datos, y aunque funcionaban en el sentido de que un usuario podía conectarse, el nombre de usuario frente al nombre de inicio de sesión NT no coincidía con los inicios de sesión prefijados de dominio cuando SQL Server no esperaba dominio, y viceversa. viceversa

Para resolver esto, modifiqué el procedimiento sp_droplogin para eliminar una de las comprobaciones que producían errores.

Estoy aceptando mi propia respuesta, ya que funciona en SQL Server 2000.


fuente

Respuestas:

6

Lo que terminé haciendo es enumerar las cuentas con:

    exec sp_validatelogins

Y corriendo

    exec sp_dropuser loginname
    exec sp_droplogin loginname

en los resultados.


fuente
4

Según mi comentario original, parece que la SUSER_SIDfunción solo toma cualquier sid que se registró cuando se creó el inicio de sesión, y en realidad no consulta Active Directory (tiene sentido, ya que podría ser costoso, incluso intenté reiniciar el servicio del servidor).

Aquí hay una aplicación de consola C # que realiza la tarea, permitiéndole auditar los inicios de sesión que se eliminarán antes de que realmente se eliminen.

Esta aplicación requiere .NET 3.5 o superior para ejecutarse, y en teoría se podría incluir en un script de PowerShell (estoy mucho más cómodo con la programación directa).

Para eliminar todos los inicios de sesión de las cuentas de usuario locales / de la máquina del servidor, deberá ejecutar esta aplicación en la máquina del servidor y codificar la ContextTypevariable (lo tengo así para probar en mi computadora hogareña no unida al dominio) ) De lo contrario, puede ejecutarlo desde cualquier máquina en el mismo dominio que el servidor, que también tiene acceso al servidor.

Voy a publicar esto en mi blog después de externalizar los parámetros y limpiar un poco el código, así que cuando lo haga, editaré esta publicación. Pero esto te ayudará a comenzar ahora.

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
fuente
Estaba en otros proyectos y no tuve la oportunidad de probar esto hasta ahora. Creo que me estoy encontrando con que SQL Server 2005 está instalado en un servidor diferente al de SQL Server 2000, y las funciones sys.syslog y DROP LOGIN no son compatibles con SQL Server 2000: la base de datos no se transferirá a SQL Server 2005 debido a fallas en la creación de inicio de sesión.
@emgee: Ohhh, dejé caer la pelota por completo sobre eso. Lo siento. Espero que esté claro dónde puede insertar el comando para soltar el inicio de sesión para SQL Server 2000. No tenía una instancia para probar cuando escribí esto.
Jon Seigel
No se preocupe, las modificaciones fueron bastante fáciles.
4

Puede aprovechar xp_logininfo para este proceso. Este procedimiento almacenado extendido se puede usar para proporcionar información de Active Directory para inicios de sesión de Windows en SQL Server. El procedimiento devuelve un error si no existe un inicio de sesión, por lo que podemos poner un bloque TRY / CATCH a su alrededor para proporcionar SQL para inicios de sesión que ya no son válidos cuando el procedimiento falla:

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

Con la forma en que funciona el script, deberá establecer la variable @dominio en el dominio que esté comprobando. La consulta del cursor solo se filtrará en los inicios de sesión de Windows (no en los grupos) dentro de ese dominio. Obtendrá resultados de consultas para todos los inicios de sesión válidos, pero las declaraciones de caída se imprimirán con los mensajes. Seguí el enfoque de impresión en lugar de ejecutar realmente el SQL para que pueda revisar y validar los resultados antes de soltar los inicios de sesión.

Tenga en cuenta que este script solo creará sus declaraciones de inicio de sesión. Los usuarios aún deberán ser eliminados de las respectivas bases de datos. La lógica apropiada se puede agregar a este script según sea necesario. Además, esto deberá ejecutarse en su entorno SQL 2005, ya que esta lógica no es compatible con SQL 2000.

Mike Fal
fuente
1
¡Tener cuidado! Si la cuenta de servicio de SQL Server es una cuenta local, Xp_logininfodevolverá el error 0x5, que significa acceso denegado, para una cuenta de dominio válida. Esto da como resultado que todas las cuentas de dominio enumeradas se eliminen. El sp_validateloginsprocedimiento almacenado producirá los mismos resultados tanto si la cuenta de servicio de SQL Server es una cuenta local como una cuenta de dominio.
Gili
0

Puede hacer una caída y recrear en una transacción como esta:

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 el error que obtiene es este: Windows NT user or group 'DOMAIN\testuser' not found. Check the name again.entonces su inicio de sesión de Windows ya no existe. Sin embargo, hay varias razones por las que la caída fallará (por ejemplo, permisos otorgados por el inicio de sesión). Deberá hacer un seguimiento de ellos manualmente.

Sebastian Meine
fuente
TRY ... CATCHse introdujo en SQL 2005. stackoverflow.com/questions/5552530/sql-server-2000-try-catch
Jon Seigel
Eso es correcto. No vi esa restricción. (Supongo que comencé a leer en la segunda línea ...) Probablemente aún pueda usar este método, simplemente marcando @@ ERROR en lugar de usar el try catch. Sin embargo, no tengo una instalación de SQL Server tan antigua disponible para probar esto.
Sebastian Meine