Script di SQL Server per eliminare gli account non più in Active Directory


8

Abbiamo un SQL Server 2000 che verrà presto migrato su SQL Server 2005. Sono stati creati anni di account di autenticazione di Windows che non esistono più in Active Directory, il che impedisce alla procedura guidata Copia database di creare questi account sul nuovo server.

Esiste uno script o un modo automatico di eliminare gli account che non esistono più nel nostro Active Directory?


EDIT: solo per essere chiari, gli accessi che devono essere eliminati sono su SQL Server 2000, che non supporta il DROP LOGINcomando.

Separatamente, l'eliminazione manuale degli accessi in SQL Server 2000 sarebbe (credo) eseguita con exec sp_droplogin 'loginname'me, ma sul mio, il nome di accesso non può essere trovato, sia che io usi "dominio \ nomeutente" o "nome utente"

Solo per aggiungere confusione, exec sp_revokelogin 'domain\loginname'sembra funzionare.

EDIT 2: finalmente risolto il problema. Molti degli accessi problematici sono stati aggiunti a livello di codice al database e mentre funzionavano nel senso che un utente poteva connettersi, il nome utente e il nome di accesso NT presentavano una mancata corrispondenza degli accessi con prefisso di dominio quando SQL Server non prevedeva alcun dominio e vice versa.

Per risolvere questo problema, ho modificato la procedura sp_droplogin per eliminare uno dei controlli che presentavano errori.

Sto accettando la mia risposta come funziona in SQL Server 2000.

Risposte:


6

Quello che ho finito di fare è elencare gli account con:

    exec sp_validatelogins

E correndo

    exec sp_dropuser loginname
    exec sp_droplogin loginname

sui risultati.


4

Secondo il mio commento originale, sembra che la SUSER_SIDfunzione afferri qualunque sid sia stato registrato al momento della creazione dell'accesso, e in realtà non interroga Active Directory (ha senso, dato che potrebbe essere costoso - ho anche provato a riavviare il servizio server).

Ecco un'applicazione console C # che esegue l'attività, che consente di controllare gli accessi che verranno eliminati prima che vengano effettivamente eliminati.

Questa app richiede .NET 3.5 o versioni successive per funzionare e in teoria potrebbe essere inserita in uno script PowerShell (sono molto più a mio agio con la programmazione diretta).

Per rimuovere eventuali accessi di account utente locali / macchina dal server, è necessario eseguire questa applicazione sul computer server e inserire nel codice la ContextTypevariabile (ce l'ho per i test sul mio computer di casa non appartenente al dominio ). Altrimenti, è possibile eseguirlo da qualsiasi macchina nello stesso dominio del server, che ha anche accesso al server.

Pubblicherò questo sul mio blog dopo aver esternalizzato i parametri e ripulito un po 'il codice, quindi quando lo faccio, modificherò questo post. Ma questo ti farà iniziare ora.

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;
            }
        }
    }
}

Ero fuori per altri progetti e non ho avuto occasione di provarlo fino ad ora. Penso che ciò che sto eseguendo sia che SQL Server 2005 sia installato su un server diverso da SQL Server 2000 e le funzioni sys.syslog e DROP LOGIN non sono supportate da SQL Server 2000 - il database non verrà trasferito a SQL Server 2005 a causa degli errori di creazione dell'accesso.

@emgee: Ohhh ho completamente lasciato cadere la palla su quello. Scusate. Spero che sia chiaro dove è possibile inserire il comando per eliminare l'accesso per SQL Server 2000. Non ho avuto un'istanza per testare quando ho scritto questo.
Jon Seigel,

Nessun problema, le modifiche sono state abbastanza facili.

4

Puoi sfruttare xp_logininfo per questo processo. Questa stored procedure estesa può essere utilizzata per fornire informazioni dagli accessi ad Active Directory per Windows in SQL Server. La procedura restituisce un errore se non esiste alcun accesso, quindi possiamo inserire un blocco TRY / CATCH attorno a esso per fornire SQL per accessi che non sono più validi quando si verificano errori della procedura:

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 il modo in cui funziona lo script, dovrai impostare la variabile @domain su qualunque sia il dominio che stai controllando. La query del cursore verrà filtrata solo sugli accessi a Windows (non sui gruppi) all'interno di quel dominio. Otterrai i risultati della query per tutti gli accessi validi, ma le dichiarazioni di rilascio verranno stampate con i messaggi. Sono andato con l'approccio di stampa invece di eseguire effettivamente l'SQL in modo da poter rivedere e convalidare i risultati prima di eliminare effettivamente gli accessi.

Nota, questo script creerà solo le tue dichiarazioni di login drop. Gli utenti dovranno comunque essere rimossi dai rispettivi database. La logica appropriata può essere aggiunta a questo script, se necessario. Inoltre, sarà necessario eseguirlo nel proprio ambiente SQL 2005, poiché questa logica non è supportata in SQL 2000.


1
Attenzione! Se l'account del servizio SQL Server è un account locale, Xp_logininfoverrà restituito l'errore 0x5, che significa accesso negato, per un account di dominio valido. Ciò comporta la caduta di tutti gli account di dominio elencati. La sp_validateloginsprocedura memorizzata produrrà gli stessi risultati se l'account del servizio SQL Server è un account locale o un account di dominio.
Gili,

0

Puoi fare un drop e ricrearlo in una transazione come questa:

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 l'errore che ricevi è questo: il Windows NT user or group 'DOMAIN\testuser' not found. Check the name again.tuo login di Windows non esiste più. Tuttavia, ci sono molte ragioni per cui il drop stesso fallirà (ad es. Autorizzazioni concesse dall'accesso). Dovrai dare seguito a quelli manualmente.


TRY ... CATCHè stato introdotto in SQL 2005. stackoverflow.com/questions/5552530/sql-server-2000-try-catch
Jon Seigel

È corretto. Non ho visto quella restrizione. (Immagino di aver iniziato a leggere nella seconda riga ...) Probabilmente puoi ancora usare questo metodo, semplicemente controllando @@ ERROR invece di usare il catch try. Tuttavia, non ho un'installazione di SQL Server così vecchia disponibile per testare questo.
Sebastian Meine,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.