Active Directoryに存在しないアカウントを削除するSQL Serverスクリプト


8

SQL Server 2000があり、間もなくSQL Server 2005に移行されます。ActiveDirectoryに存在しない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ログイン名はドメインプレフィックスのログオンが一致しませんでした。その逆。

これを解決するために、エラーになったチェックの1つを削除するように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のログインを削除するコマンドをどこに挿入できるかが明確であることを願っています。これを書いたときにテストするインスタンスがありませんでした。
Jon Seigel 2013年

心配はいりません。変更は十分簡単でした。

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 2000ではサポートされていないため、SQL 2005環境で実行する必要があります。


1
注意してください!SQL Serverサービスアカウントがローカルアカウントの場合、Xp_logininfoエラー0x5が返されます。これは、有効なドメインアカウントに対してアクセスが拒否されたことを意味します。これにより、すべてのドメインアカウントが削除対象としてリストsp_validateloginsされます。SQLServer サービスアカウントがローカルアカウントでもドメインアカウントでも、ストアドプロシージャは同じ結果を生成します。
ギリ

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 ... CATCHSQL 2005で導入されました。stackoverflow.com
questions / 5552530 /

それは正しいです。私はその制限を見ませんでした。(私は2行目から読み始めたと思います...)おそらくこのメソッドを使用できます。trycatchを使用する代わりに@@ ERRORをチェックするだけです。ただし、これをテストするために古いSQL Serverインストールがありません。
Sebastian Meine 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.