Script do SQL Server para excluir contas que não estão mais no Active Directory

8

Temos um SQL Server 2000 que em breve será migrado para o SQL Server 2005. Há anos de contas de autenticação do Windows criadas que não existem mais no Active Directory, o que impede o Assistente de Cópia de Banco de Dados de criar essas contas no novo servidor.

Existe um script ou alguma maneira automatizada de excluir as contas que não existem mais em nosso Active Directory?


EDIT: Apenas para esclarecer, os logins que precisam ser excluídos estão no SQL Server 2000, que não oferece suporte ao DROP LOGINcomando.

Separadamente, excluir manualmente os logons no SQL Server 2000 (acho) seria feito, exec sp_droplogin 'loginname'mas no meu, o nome de login não pode ser encontrado, se eu uso 'domain \ loginname' ou 'loginname'

Apenas para aumentar a confusão, exec sp_revokelogin 'domain\loginname'parece funcionar.

EDIT 2: Finalmente resolvido o problema. Muitos dos logons que eram problemáticos foram adicionados programaticamente ao banco de dados e, embora trabalhassem no sentido de que um usuário pudesse se conectar, o nome de usuário versus o nome de login do NT apresentavam uma incompatibilidade de logons com prefixo de domínio quando o SQL Server não esperava domínio e vice-versa. versa.

Para resolver isso, modifiquei o procedimento sp_droplogin para executar uma das verificações que estavam com erro.

Estou aceitando minha própria resposta, pois funciona no SQL Server 2000.


fonte

Respostas:

6

O que acabei de fazer é listar as contas com:

    exec sp_validatelogins

E correndo

    exec sp_dropuser loginname
    exec sp_droplogin loginname

nos resultados.


fonte
4

De acordo com o meu comentário original, parece que a SUSER_SIDfunção pega apenas o sid que foi gravado quando o logon foi criado e não consulta o Active Directory (faz sentido, pois isso pode ser caro - até tentei reiniciar o serviço do servidor).

Aqui está um aplicativo de console em C # que realiza a tarefa, permitindo auditar os logins que serão eliminados antes que eles sejam eliminados.

Este aplicativo requer a execução do .NET 3.5 ou superior e, em teoria, poderia ser colocado em um script do PowerShell (estou muito mais à vontade com a programação direta).

Para remover qualquer logon de contas de usuário local / de máquina do servidor, você precisará executar esse aplicativo na máquina do servidor e codificar a ContextTypevariável (eu a tenho assim para testar no meu computador doméstico que não ingressou no domínio) ) Caso contrário, você poderá executá-lo a partir de qualquer máquina no mesmo domínio que o servidor, que também tenha acesso ao servidor.

Vou postar isso no meu blog depois de externalizar os parâmetros e limpar um pouco o código; portanto, quando fizer isso, editarei este post. Mas isso fará você começar agora.

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
fonte
Eu participei de outros projetos e não tive chance de tentar isso até agora. Acho que o que estou encontrando é o SQL Server 2005 instalado em um servidor diferente do SQL Server 2000, e as funções sys.syslog e DROP LOGIN não são suportadas pelo SQL Server 2000 - o banco de dados não será transferido para o SQL Server 2005 por causa das falhas na criação de logon.
@ Emgee: Ohhh, deixei cair totalmente a bola nessa. Desculpa. Espero que esteja claro onde você pode inserir o comando para descartar o logon do SQL Server 2000. Não tive uma instância para testar quando escrevi isso.
21813 Jon Seigel
Não se preocupe, as modificações foram fáceis o suficiente.
4

Você pode aproveitar o xp_logininfo para esse processo. Esse procedimento armazenado estendido pode ser usado para fornecer informações dos logons do Active Directory para Windows no SQL Server. O procedimento retornará um erro se não houver login, para que possamos colocar um bloco TRY / CATCH ao seu redor para fornecer SQL para logins que não são mais válidos quando o procedimento erros:

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

Com a maneira como o script funciona, você precisará definir a variável @domain para qualquer que seja o domínio que está verificando. A consulta do cursor filtrará apenas os logons do Windows (não grupos) nesse domínio. Você obterá resultados da consulta para todos os logins válidos, mas as instruções drop serão impressas com as mensagens. Eu fui com a abordagem de impressão em vez de realmente executar o SQL, para que você possa revisar e validar os resultados antes de interromper os logins.

Observe que esse script criará apenas suas estatísticas de login drop. Os usuários ainda precisarão ser removidos dos respectivos bancos de dados. A lógica apropriada pode ser adicionada a esse script, conforme necessário. Além disso, isso precisará ser executado no seu ambiente SQL 2005, pois essa lógica não é suportada no SQL 2000.

Mike Fal
fonte
11
Cuidado! Se a conta de serviço do SQL Server for uma conta local, Xp_logininforetornará o erro 0x5, o que significa acesso negado, para uma conta de domínio válida. Isso resulta em todas as contas de domínio listadas para serem descartadas. O sp_validateloginsprocedimento armazenado produzirá os mesmos resultados, independentemente da conta de serviço do SQL Server ser uma conta local ou de domínio.
Gili
0

Você pode soltar e recriar uma transação 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  

Se o erro que você receber for este: o Windows NT user or group 'DOMAIN\testuser' not found. Check the name again.seu login do Windows não existe mais. No entanto, existem vários motivos pelos quais a queda falhará (por exemplo, permissões concedidas pelo login). Você precisará acompanhar os manualmente.

Sebastian Meine
fonte
TRY ... CATCHfoi introduzido no SQL 2005. stackoverflow.com/questions/5552530/sql-server-2000-try-catch
Jon Seigel
Está correto. Eu não vi essa restrição. (Acho que comecei a ler na segunda linha ...) Você provavelmente ainda pode usar esse método, apenas marcando @@ ERROR em vez de usar o try catch. No entanto, não tenho uma instalação do SQL Server antiga disponível para testar isso.
Sebastian Meine