July 2006 - Posts

Cleaning up abandoned logins

In my shop we have a lot of web sites, each using their own SQL Server database. Many of these sites are for conferences and often once the conference is over the site is no longer needed. As a result my servers have had a lot of databases created, logins created, then databases deleted, but the logins don't always get deleted because developers will use existing logins for new sites (sometimes), so I'm not always sure what logins can go.

Over time this gets a bit ugly, so I decided to write a program using SMO to evaluate a server and create a script to drop logins that haven't been assigned rights in any existing database. First I created a couple of classes, one for Users and one for Logins:

Public Class dbUser
    Public name As String
    Sub New(ByVal newName As String)
        name = newName
    End Sub
End Class
Public Class dbLogin
    Public name As String
    Sub New(ByVal newName As String)
        name = newName
    End Sub
End Class

The first thing I did was connect to the server, and I've posted that code before so I won't do so again, but then defined some variables as follows:

        Dim strUserNm As String			'String to hold the user name
        Dim colUsers As New Collection		'Server-wide collection of users
        Dim strLoginNm As String		'String to hold the login name
        Dim colLogins As New Collection		'Server-wide collection of logins
        Dim dbColl As DatabaseCollection	'SMO Database Collection
        Dim dbCurr As Database			'SMO Database object
        Dim logColl As LoginCollection		'SMO Login Collection
        Dim logCurr As Login			'SMO Login Object
        Dim objUser As dbUser			'Instance of the User Class
        Dim objLogin As dbLogin			'Instance of the Login Class
        Dim file As System.IO.StreamWriter	'File to receive the output script

Now, I loop through the databases on the server, and if I haven't already added the current user to a server-wide collection of users, I add it. Then, I loop through the server logins collection, populating my collection of logins.

        dbColl = srvMgmtServer.Databases		'Get the collection of databases from the server
        For Each dbCurr In dbColl			'Loop through the databases
            Dim colDBUsers As UserCollection		'SMO User Collection
            Dim usrDBUser As User			'SMO User Object
            colDBUsers = dbCurr.Users			'Get the collection of users for the database
            For Each usrDBUser In colDBUsers		'Loop through the users
                strUserNm = usrDBUser.Name		'Get the users name
                If Not (colUsers.Contains(usrDBUser.Name)) Then		'Test to see if the user is already in the collection
                    colUsers.Add(New dbUser(strUserNm), strUserNm)	'If not, add it
                End If
            Next
        Next

        logColl = srvMgmtServer.Logins			'Get the collection of logins for the server
        For Each logCurr In logColl			'Loop through the logins
            strLoginNm = logCurr.Name			'Get the name of the login
            colLogins.Add(New dbLogin(strLoginNm), strLoginNm)	'Add it to the collection
        Next

Now I remove from my logins collection every value that exists in the server-wide users collection, effectively leaving me with a collection of logins with no database access. (This isn't entirely true - I could have server-level logins in here, and do.

        For Each objUser In colUsers			'Loop through the Users collection
            If colLogins.Contains(objUser.name) Then	'See if it exists in the logins collection
                colLogins.Remove(objUser.name)		'Remove it if it does
            End If
        Next

Finally I write the names in the logins collection to a file, building it as a SQL Script, which I can run on the server when I'm ready:

        file = My.Computer.FileSystem.OpenTextFileWriter("C:\CleanLogins.sql", True)
        For Each objLogin In colLogins			'Loop through the remaining logins
            file.WriteLine("drop login " + objLogin.name)	'Write the DROP LOGIN line
            file.WriteLine("go")			'Each drop is in its own batch
        Next

Before I run the script I review it to remove logins I know need to remain, but I've automated the process of knowing what logins are no longer used, improving the security of my servers.

Allen

SMO making life easier

This was kind of cool. I was answering a question in the MSDN SMO forum about copying database objects from one database to another. I'd had trouble with this myself so I "took on" the question to try and resolve it for myself as well as for the person posing the question.

I have an application for which I need to build a new database for each new website we create, and the schema and some basic data remain the same for each site, so we've got a default database with everything we need to start a new site. I needed a tool to copy the contents of that database to a new database, but wanted to set the size of the new database per anticipated usage.

I showed in a previous post how to create a new database in a specified location with defined sizes. The code I show here allows me to copy an existing database to the newly created database.

        Dim db As Database

        db = srv.Databases("AdventureWorks")
        Dim strDBName As String
        strDBName = "TestDatabase"

        'Define a Transfer object and set the required options and properties.
        Dim xfr As Transfer
        xfr = New Transfer(db)
        xfr.CopyAllObjects = True
        xfr.CopyAllUsers = True
        xfr.Options.WithDependencies = True
        xfr.Options.ContinueScriptingOnError = True
        xfr.DestinationDatabase = strDBName
        xfr.DestinationServer = srv.Name
        xfr.DestinationLoginSecure = True
        xfr.CopySchema = True
        'Script the transfer.
        xfr.TransferData()

I'm now well on the way to finishing my application code to auto-generate new databases when I get a request for a new site.

Allen

One Brick at a Time

I'm reminded of the song from the Broadway musical "Barnum", where P.T.'s wife teaches him to build a city by starting with one brick at a time.

A user in the MSDN forums was trying to use SMO to create a table with indexes, and was frustrated with the exception when trying to create the index. The problem was that he hadn't yet called the Create() method on the underlying table, so there was no table on which to create the index. I pulled the following code out of examples in Books Online to provide him his answer:

        Dim srv As Server
        srv = New Server("MyServer")
        'Reference the AdventureWorks database.
        Dim db As Database
        db = srv.Databases("AdventureWorks")
        'Define a Table object variable by supplying the parent database and table name in the constructor. 
        Dim tb As Table
        tb = New Table(db, "Test_Table")
        'Add various columns to the table.
        Dim col1 As Column
        col1 = New Column(tb, "Name", DataType.NChar(50))
        col1.Collation = "Latin1_General_CI_AS"
        col1.Nullable = True
        tb.Columns.Add(col1)
        Dim col2 As Column
        col2 = New Column(tb, "ID", DataType.Int)
        col2.Identity = True
        col2.IdentitySeed = 1
        col2.IdentityIncrement = 1
        tb.Columns.Add(col2)
        Dim col3 As Column
        col3 = New Column(tb, "Value", DataType.Real)
        tb.Columns.Add(col3)
        Dim col4 As Column
        col4 = New Column(tb, "Date", DataType.DateTime)
        col4.Nullable = False
        tb.Columns.Add(col4)
        'Create the table on the instance of SQL Server.
        tb.Create()
        Dim idx As Index
        idx = New Index(tb, "TestIndex")
        'Add indexed columns to the index.
        Dim icol1 As IndexedColumn
        icol1 = New IndexedColumn(idx, "ID", True)
        idx.IndexedColumns.Add(icol1)
        idx.IndexKeyType = IndexKeyType.DriUniqueKey
        idx.IsClustered = False
        idx.FillFactor = 50
        'Create the index on the instance of SQL Server.
        idx.Create()

Once the table exists, SMO has no problem creating the index and all is well.

Allen

More Certifications

I haven't spent much time blogging because I've been studying for the last certification exam, and this morning I passed 70-442, so I can now add IT Professional:Database Developer to my business card. Phew!

As the boss says, "now get back to work!" (grin)

Allen