SQL server powershell war chest – advanced connection provider

in a previous post I covered a basic connection provider, which is nice, but has its limitations. biggest one being that you always have to supply credentials. If you’re like most DBAs, chances are that you usually work on several servers in different environments and entering the login info all the time can get tedious. Of course integrated authentication helps a bit, but still, for those that still use SQL logins it could be a bit of a nuisance. All of this was a problem until my friend Jorge Satorre showed me a beautiful solution that I will share with you here.
First of all, let’s start from the basics – saving your password for future reuse. This blog post may come in handly. Jorge likes Powershell Cookbook (I believe it’s this one). In any case, first step is to save a password to a file. It’s a bit of a tango twist, but not too confusing. First we create an object with the following properties – AuthenticationType, Username and SecurePassword. AuthenticationType can be integrated or regular:

$object = "" | Select-Object AuthenticationType,Username,SecurePassword

the twist happens when saving a password. We take it from a regular string to a secure string:

convertto-securestring "P@ssW0rD!" -asplaintext -force

then we encrypt the secure string for storage and export to an xml file:

$object = "" | Select-Object AuthenticationType,Username,SecurePassword
$object.AuthenticationType = "regular"
$object.Username = "superuser"
$object.Password = convertto-securestring "P@ssW0rD!" -asplaintext -force | ConvertFrom-SecureString
$object |  Export-Clixml ./ServerA.xml

we can create a file for each server and store them in a save location. As you will see in the connection script I store my server authentication files in c:\powershell\ss.
We’re almost done. Now our get_connection script just needs two params – a database name and a server.

param(
	  [string] $database = "MYdb",
	  [string] $dataSource="ServerA"
     )

all we have to do is to read the credential file and construct a connection string accordingly. If the AuthenticationType is “regular” we will also read the username/password pair.

       $Path = "c:\powershell\ss\$dataSource.XML"

        # Import credential file
        $import = Import-Clixml $Path 

if ($import.AuthenticationType -eq "Integrated")

{
$authentication = "Integrated Security=SSPI;"
}
else
{
        # Test for valid import
        if ( !$import.UserName -or !$import.Password ) {
                Throw "Input is not a valid ExportedPSCredential object, exiting."
        }

		$username = $import.Username

		        # Decrypt the password and store as a SecureString object for safekeeping
        $SecurePass = $import.Password | ConvertTo-SecureString

        # Build the new credential object
        $Credential = New-Object System.Management.Automation.PSCredential $Username, $SecurePass

	    $plainCred = $credential.GetNetworkCredential()
    	$authentication = ("uid={0};pwd={1};" -f $plainCred.Username,$plainCred.Password)
}

$connectionString = "Persist Security Info=True;Initial Catalog=$database;Data Source=$dataSource;$authentication"

that’s it. Now all of your servers are at your fingertips. Download the get_connection script.

Automatically deploying data dude projects with vsdbcmd.exe

In a previous post, I talked about automatically building a database project outside of Visual Studio. Just for the sake of a feeling of completeness, I will also add a script for deploying the said project to a database of your choice.

There are many ways to deploy a database project. See for example this msdn reference for examples of deploying without having to specify a connection string on a command line, or Justin’s blog on using TFS build templates, or jamie thomson on using msbuild scripts.  For my part, I’m driven by a desire to accomplish as much as possible with doing as little as possible. So, after I run the build script and all is well, I want to re-use the build output files without modifying them at all.

Build the output directory should contain the following files:

  • dbname.dbschema
  • dbname.deploymanifest
  • dbname.sqlcmdvars
  • dbname.sqldeployment
  • dbname.sqlsettings
  • dbname.PostDeployment.sql
  • dbname.PreDeployment.sql

they should contain all the info that we need to run vsdbcmd.exe. we’ll just need to add few params:

  • connections string
  • manifest location
  • target db name

that’s all really. We’ll need to add some basic params just so that vsdbcmd.exe knows what we want from it, such as:

  • action (deploy)
  • deploy to database
  • verbose/silent mode

As you may have guessed, I’m going to use powershell to run it:

 $connStr="Persist Security Info=True;Initial Catalog=""dbname"";Data Source=127.0.0.1;uid=""user"";pwd=""password"";"
$pr1="/Action:Deploy";
$pr2="/dd:+";
$pr3="/cs:"+$connStr
$pr4="/manifest:"""+$buildOutput+"\"+$dbname+"\"+$dbname+".deploymanifest"" "
$pr5="/p:TargetDatabase="""+$dbname+""" "
$pr6="/q:+"  

Check out this msdn page for more info on individual parameters. $buildOutput  is the path to the build output files. If I want to deploy a bunch of databased, I could turn $buildOutput  and $dbname into parameters and loop through the project directories. But  I don’t find myself deploying many databases at once. If an environment really needs to be refreshed, it’s better to do it from a CI tool, so that there is a record of action and so on.

You may have noticed that I put each param into a separate variable. This is because of a strange quirk of Powershell – vsdbcmd.exe combination. You can’t just create one long string with parameters and execute vsdbcmd with it. Vsdbcmd will think that the entire string is an /Action command. From a cmd batch file it works fine.

In any case, all’s left is to specify a path to vsdbcmd and execute it

$ex="C:\Program Files\Microsoft Visual Studio 10.0\VSTSDB\Deploy\vsdbcmd.exe"
#execute the deploy command
& $ex $pr1 $pr2 $pr3 $pr4 $pr5 $pr6;

That’s it really. As simple as it gets. If something went wrong vsdbcmd will complain about it. If you want to be a bit more proactive about it, you can follow Gert D’s   suggestion with a Powershell twist:

#check if succeeded or not
if ($?) {Write-Host "database ",$dbname," successfully deployed"}
	else {Write-Host "deployment for ",$dbname," failed"}

Download a sample powershell script.