SQL Server脚本删除Active Directory中不再存在的帐户


8

我们有一个SQL Server 2000,不久将迁移到SQL Server2005。它具有多年创建的Windows身份验证帐户,这些帐户在Active Directory中已不存在,这会阻止复制数据库向导在新服务器上创建这些帐户。

是否存在脚本或某种自动删除活动目录中不再存在的帐户的方法?


编辑:为了清楚起见,需要删除的登录名在SQL Server 2000上,它不支持该DROP LOGIN命令。

另外,可以手动删除SQL Server 2000中的登录名(我认为),exec sp_droplogin 'loginname'但是在我的情况下,无论我使用“ domain \ loginname”还是“ loginname”,都找不到登录名。

只是增加了混乱,exec sp_revokelogin 'domain\loginname'看起来确实有效。

编辑2:终于解决了问题。许多有问题的登录都以编程方式添加到数据库中,尽管它们在某种意义上可以以用户可以连接的方式工作,但是当SQL Server预期没有域时,用户名与NT登录名的域前缀登录不匹配,反之,反之亦然。

为解决此问题,我修改了sp_droplogin过程以取出其中一个出错的检查。

我接受我自己的答案,因为它可以在SQL Server 2000中使用。

Answers:


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安装在不同的服务器上,并且SQL Server 2000不支持sys.syslog和DROP LOGIN函数-数据库不会转移到SQL Server 2005年,因为登录创建失败。

@emgee:哦,我完全丢下了那个球。抱歉。希望您可以在哪里插入命令以删除SQL Server 2000的登录名。在编写此代码时,我没有要测试的实例。
乔恩·塞格尔

不用担心,修改很容易。

4

您可以利用xp_logininfo进行此过程。此扩展的存储过程可用于为SQL Server中的Windows登录提供来自Active Directory的信息。如果不存在登录名,该过程将返回一个错误,因此我们可以在其周围放置一个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登录名(而不是组)过滤。您将获得所有有效登录名的查询结果,但drop语句将随消息一起打印。我采用了打印方法,而不是实际执行SQL,因此您可以在实际删除登录名之前查看并验证结果。

请注意,此脚本只会创建您的投递登录声明。用户仍然需要从相应的数据库中删除。可以根据需要将适当的逻辑添加到此脚本中。此外,这将需要在您的SQL 2005环境中运行,因为SQL 2000不支持此逻辑。


1
谨防!如果SQL Server服务帐户是本地帐户,Xp_logininfo则对于有效的域帐户,将返回错误0x5,这意味着访问被拒绝。这将导致列出的每个域帐户都删除。sp_validatelogins无论SQL Server服务帐户是本地帐户还是域帐户,存储过程都将产生相同的结果。
吉利2015年

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登录名将不复存在。但是,丢弃本身将失败的原因有很多(例如,登录名授予的权限)。您将需要手动进行后续操作。



那是正确的。我没有看到该限制。(我想我从第二行开始阅读...)您可能仍然可以使用此方法,只需检查@@ ERROR而不是使用try catch。但是,我没有可用的SQL Server安装来进行测试。
塞巴斯蒂安·梅恩
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.