Friday, October 7, 2011

Automated Source Control: A Librarian For Your Database Objects, Part 2

I assume that by now you've tried the script from Part 1, and have seen how easy it is to script out your database objects using Powershell.  The resulting scripts are the standard DDL scripts that you can generate from Management Studio, and for most of us, are just fine right out of the box.

What if, however, we need to customize those scripts?  In my environment, our software deployments (including the database scripts) are automated, and we need certain elements to be included in the scripts for the automation to function properly.  I also like to include a certain degree of fail-safe in the scripts, to prevent accidentally deploying an object to the wrong database, or possibly wiping out a table full of data.

Turns out that it's a snap to customize the scripts that are generated, using simple text piping, just like you've been doing in batch files for a decade or two.  Simply insert the following into the script from Part 1.  For your convenience, the entire script is repeated at the end of this post.

I'll resume this series after SQL Pass.  I'm not attending, but I will be taking some time off next week, and with all of the buzz around the conference, nobody will be paying attention to little ol' me anyway!

    #Add commented USE statement for deployment validation tool
    "USE $dbname" >  "$SavePath\$TypeFolder\$ScriptFile.SQL"

    #Add existing object handler (drop procedure, table exists, etc..)
    if ($TypeFolder -eq "StoredProcedure") {
    "IF OBJECT_ID('$ScriptFile', 'P') IS NOT NULL
        BEGIN
        
        DROP PROCEDURE $ScriptFile
        
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    } elseif ($TypeFolder -eq "View") {
    "IF OBJECT_ID('$ScriptFile', 'V') IS NOT NULL
        BEGIN
        
        DROP VIEW $ScriptFile
        
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    } elseif ($TypeFolder -eq "UserDefinedFunction") {
    "IF OBJECT_ID('$ScriptFile', 'FN') IS NOT NULL OR OBJECT_ID('$ScriptFile', 'TF') IS NOT NULL
        BEGIN
        
        DROP FUNCTION $ScriptFile
        
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    } elseif ($TypeFolder -eq "Trigger") {
    "IF OBJECT_ID('$ScriptFile', 'TR') IS NOT NULL
        BEGIN
        
        DROP TRIGGER $ScriptFile
        
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    } elseif ($TypeFolder -eq "Table") {
    "IF OBJECT_ID('$ScriptFile', 'U') IS NOT NULL
        BEGIN
        
        IF EXISTS(SELECT * FROM $ScriptFile)
            BEGIN
            
            USE tempdb
            RAISERROR('Table exists and contains data, cannot proceed.', 16, 1)
            RETURN
            
            END
        ELSE
            BEGIN
            
            DROP TABLE $ScriptFile
            
            END
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    }

The complete script now looks like this:

#Set server and database names here
$server = "REALServer"
$dbname = "REALDB"

#Create SMO connection to DB
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | out-null
    $SMOserver = New-Object ('Microsoft.SqlServer.Management.Smo.Server') -argumentlist $server
    $db = $SMOserver.databases[$dbname]
 
#Build collection of objects to script
$Objects = $db.Tables
$Objects += $db.Views
$Objects += $db.StoredProcedures
$Objects += $db.UserDefinedFunctions

#Set destination for generated scripts
$SavePath = "C:\temp\db\Source\" + $($dbname)

#Loop through the object collection
foreach ($ScriptThis in $Objects | where {!($_.IsSystemObject)}) {
	#Create Scripter object
    $scriptr = new-object ('Microsoft.SqlServer.Management.Smo.Scripter') ($SMOserver)
	
	#Set options for generated scripts
    $scriptr.Options.AppendToFile = $True
    $scriptr.Options.AllowSystemObjects = $False
    $scriptr.Options.ClusteredIndexes = $True
    $scriptr.Options.DriAll = $True
    $scriptr.Options.ScriptDrops = $False
    $scriptr.Options.IncludeHeaders = $False
    $scriptr.Options.ToFileOnly = $True
    $scriptr.Options.Indexes = $True
    $scriptr.Options.Permissions = $True
    $scriptr.Options.WithDependencies = $False

	#Create destination folder and subfolder, if necessary
    $TypeFolder=$ScriptThis.GetType().Name
    if ((Test-Path -Path "$SavePath\$TypeFolder") -eq "true")
            {"Scripting Out $TypeFolder $ScriptThis"}
        else {new-item -type directory -name "$TypeFolder"-path "$SavePath"}
    $ScriptFile = $ScriptThis -replace "\[|\]"

	#Set output filename for script
    $scriptr.Options.FileName = "$SavePath\$TypeFolder\$ScriptFile.SQL"

    #Add commented USE statement for deployment validation tool
    "USE $dbname" >  "$SavePath\$TypeFolder\$ScriptFile.SQL"

    #Add existing object handler (drop procedure, table exists, etc..)
    if ($TypeFolder -eq "StoredProcedure") {
    "IF OBJECT_ID('$ScriptFile', 'P') IS NOT NULL
        BEGIN
        
        DROP PROCEDURE $ScriptFile
        
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    } elseif ($TypeFolder -eq "View") {
    "IF OBJECT_ID('$ScriptFile', 'V') IS NOT NULL
        BEGIN
        
        DROP VIEW $ScriptFile
        
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    } elseif ($TypeFolder -eq "UserDefinedFunction") {
    "IF OBJECT_ID('$ScriptFile', 'FN') IS NOT NULL OR OBJECT_ID('$ScriptFile', 'TF') IS NOT NULL
        BEGIN
        
        DROP FUNCTION $ScriptFile
        
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    } elseif ($TypeFolder -eq "Trigger") {
    "IF OBJECT_ID('$ScriptFile', 'TR') IS NOT NULL
        BEGIN
        
        DROP TRIGGER $ScriptFile
        
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    } elseif ($TypeFolder -eq "Table") {
    "IF OBJECT_ID('$ScriptFile', 'U') IS NOT NULL
        BEGIN
        
        IF EXISTS(SELECT * FROM $ScriptFile)
            BEGIN
            
            USE tempdb
            RAISERROR('Table exists and contains data, cannot proceed.', 16, 1)
            RETURN
            
            END
        ELSE
            BEGIN
            
            DROP TABLE $ScriptFile
            
            END
        END
    GO
    " >> "$SavePath\$TypeFolder\$ScriptFile.SQL"
    }

	#Generate script for this object
    $scriptr.Script($ScriptThis)
}

#Second loop through tables to script triggers
foreach ($Table in $db.Tables | where {!($_.IsSystemObject)}) {
	#Loop through triggers on this table
    foreach ($ScriptThis in $Table.Triggers) {
		#Create Scripter object
        $scriptr = new-object ('Microsoft.SqlServer.Management.Smo.Scripter') ($SMOserver)
		
		#Set options for generated scripts
        $scriptr.Options.AppendToFile = $True
        $scriptr.Options.AllowSystemObjects = $False
        $scriptr.Options.ClusteredIndexes = $True
        $scriptr.Options.DriAll = $True
        $scriptr.Options.ScriptDrops = $False
        $scriptr.Options.IncludeHeaders = $False
        $scriptr.Options.ToFileOnly = $True
        $scriptr.Options.Indexes = $True
        $scriptr.Options.Permissions = $True
        $scriptr.Options.WithDependencies = $False
         
		#Create destination folder and subfolder, if necessary
		$TypeFolder=$ScriptThis.GetType().Name
        if ((Test-Path -Path "$SavePath\Trigger") -eq "true")
                {"Scripting Out Trigger $ScriptThis"}
            else {new-item -type directory -name "Trigger"-path "$SavePath"}
        $ScriptFile = $ScriptThis -replace "\[|\]"

		#Set output filename for script
        $scriptr.Options.FileName = "$SavePath\Trigger\$ScriptFile.SQL"

		#Add commented USE statement for deployment validation tool
		"USE $dbname" >  "$SavePath\$TypeFolder\$ScriptFile.SQL"

        #Add existing object handler (drop trigger, etc..)
        "IF OBJECT_ID('$ScriptFile', 'TR') IS NOT NULL
            BEGIN
            
            DROP TRIGGER $ScriptFile
            
            END
        GO
        " >> "$SavePath\Trigger\$ScriptFile.SQL"

		#Generate script for this trigger
        $scriptr.Script($ScriptThis)
	}
}

0 comments:

Post a Comment