Сценарий SQL Server для удаления учетных записей больше не в Active Directory

8

У нас есть SQL Server 2000, который вскоре будет перенесен на SQL Server 2005. Он имеет годы создания учетных записей проверки подлинности Windows, которые больше не существуют в Active Directory, что не позволяет мастеру копирования базы данных создавать эти учетные записи на новом сервере.

Существует ли сценарий или какой-либо автоматический способ удаления учетных записей, которых больше нет в нашей Active Directory?


РЕДАКТИРОВАТЬ: просто чтобы быть понятным, имена входа должны быть удалены на SQL Server 2000, который не поддерживает DROP LOGINкоманду.

Отдельно, ручное удаление имен входа в SQL Server 2000 было бы (я думаю) выполнено, exec sp_droplogin 'loginname'но по моему, имя для входа не может быть найдено, использую ли я «domain \ loginname» или «loginname»

Просто чтобы добавить к путанице, exec sp_revokelogin 'domain\loginname'действительно работает.

РЕДАКТИРОВАТЬ 2: наконец, решил проблему. Многие из проблемных входов в систему были программно добавлены в базу данных, и хотя они работали в том смысле, что пользователь мог подключаться, имя пользователя и имя входа NT имели несоответствие входов с префиксом домена, когда SQL Server не ожидал домена, и наоборот наоборот.

Чтобы решить эту проблему, я изменил процедуру sp_droplogin, чтобы убрать одну из проверок, которая давала ошибку.

Я принимаю свой собственный ответ, как он работает в SQL Server 2000.


источник

Ответы:

6

То, что я закончил делать, это перечисление учетных записей с:

    exec sp_validatelogins

И работает

    exec sp_dropuser loginname
    exec sp_droplogin loginname

по результатам.


источник
4

Согласно моему первоначальному комментарию, похоже, что SUSER_SIDфункция просто захватывает любой sid, который был записан при создании имени входа, и фактически не запрашивает Active Directory (имеет смысл, поскольку это может быть дорого - я даже пытался перезапустить службу сервера).

Вот консольное приложение C #, которое выполняет задачу, позволяя вам проверять имена входа, которые будут отброшены до того, как они действительно будут удалены.

Для запуска этого приложения требуется .NET 3.5 или выше, и теоретически его можно поместить в сценарий PowerShell (мне гораздо удобнее прямое программирование).

Чтобы удалить все учетные записи локальных / машинных учетных записей пользователей с сервера, вам нужно запустить это приложение на серверном компьютере и жестко закодировать ContextTypeпеременную (у меня это так для тестирования на моем домашнем компьютере, не подключенном к домену). ). В противном случае вы можете запустить его с любого компьютера в том же домене, что и сервер, который также имеет доступ к серверу.

Я собираюсь опубликовать это в своем блоге после того, как изменил параметры и немного очистил код, поэтому, когда я это сделаю, я отредактирую этот пост. Но это поможет вам начать сейчас.

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;
            }
        }
    }
}
Джон Сайгель
источник
Я был на других проектах и ​​не имел возможности попробовать это до сих пор. Я думаю, что я сталкиваюсь с тем, что SQL Server 2005 установлен на сервере, отличном от SQL Server 2000, а функции sys.syslog и DROP LOGIN не поддерживаются SQL Server 2000 - база данных не будет передаваться на SQL Server 2005 из-за сбоев создания входа в систему.
@emgee: Оооо, я полностью бросил мяч на этом. Сожалею. Надеюсь, понятно, куда можно вставить команду для сброса имени входа для SQL Server 2000. У меня не было экземпляра для проверки, когда я писал это.
Джон Зигель
Не беспокойтесь, модификации были достаточно просты.
4

Вы можете использовать xp_logininfo для этого процесса. Эта расширенная хранимая процедура может использоваться для предоставления информации из Active Directory для имен входа Windows в SQL Server. Процедура возвращает ошибку, если имени входа нет, поэтому мы можем поместить вокруг него блок TRY / CATCH, чтобы обеспечить SQL для имен входа, которые больше не действительны при ошибках процедуры:

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

Чтобы скрипт работал, вам нужно установить переменную @domain в любой домен, который вы проверяете. Запрос курсора будет фильтроваться только по логинам Windows (не по группам) в этом домене. Вы получите результаты запроса для всех действительных имен входа, но операторы отбрасывания будут напечатаны с сообщениями. Я пошел с подходом печати вместо того, чтобы фактически выполнить SQL, таким образом, вы можете просмотреть и проверить результаты прежде, чем фактически сбросить логины.

Обратите внимание, этот скрипт будет создавать только ваши дроп-логины. Пользователи по-прежнему должны быть удалены из соответствующих баз данных. Соответствующая логика может быть добавлена ​​в этот скрипт по мере необходимости. Кроме того, это необходимо будет выполнить в вашей среде SQL 2005, поскольку эта логика не поддерживается в SQL 2000.

Майк Фал
источник
1
Осторожно! Если учетная запись службы SQL Server является локальной учетной записью, Xp_logininfoбудет возвращена ошибка 0x5, что означает, что доступ запрещен для действительной учетной записи домена. Это приводит к удалению каждой учетной записи домена. sp_validateloginsХранимая процедура выдаст одинаковые результаты, независимо от того, является ли учетная запись службы SQL Server локальной или доменной учетной записью.
Гили
0

Вы можете сделать сброс и воссоздать в транзакции, как это:

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  

Если вы получаете ошибку: Windows NT user or group 'DOMAIN\testuser' not found. Check the name again.ваш логин Windows больше не существует. Однако существует множество причин, по которым само удаление не будет выполнено (например, разрешения, предоставленные для входа в систему). Вам нужно будет следить за этим вручную.

Себастьян Майн
источник
TRY ... CATCHбыла введена в SQL 2005. stackoverflow.com/questions/5552530/sql-server-2000-try-catch
Джон Зейгель
Это правильно. Я не видел этого ограничения. (Полагаю, я начал читать во второй строке ...) Вы, вероятно, все еще можете использовать этот метод, просто проверяя @@ ERROR вместо использования try catch. Однако у меня нет такой старой версии SQL Server, чтобы проверить это.
Себастьян Майн