I love SQL-DMO.
I love JScript.
They're Russian! :)
Anyway, I just finished hacking out this script that generates a script of my database.
The goal is to have a set of scripts similar to this for setting up customer specific OLAP databases, and managing database version control in subversion, along with fully automated installations/updates (via nant) of the database schema on development, staging, and even production machines.
You can check it out if you're interested by running it from the command-line, like this:
cscript.exe //D script.js /server:(local) /database:northwind /file:test.sql
John.
--
/////////////////////////////////////////////////////////////////
// Command-Line Options
// ====================
//
var COMMAND_LINE_OPTIONS = new Array(
new CommandLineOption(
"server",
"ServerName",
"Database server to query.",
false,
"(local)"
),
new CommandLineOption(
"database",
"DatabaseName",
"Name of database on server.",
true
),
new CommandLineOption(
"user",
"Username",
"Database username, or blank for NTLM.",
false,
null
),
new CommandLineOption(
"pass",
"Password",
"Database account password, if not NTLM.",
false,
null
),
new CommandLineOption(
"file",
"Filename",
"The file to script the database to.",
true
),
new CommandLineOption(
"debug",
"IsDebuggable",
"Can this script throw for JIT debugging?",
false,
false
)
);
/////////////////////////////////////////////////////////////////
// Constants
// =========
//
var DEBUG = false;
/////////////////////////////////////////////////////////////////
// Class Definitions
// =================
//
var SQLDMOScript = new SQLDMOScript();
var SQLDMOScript2 = new SQLDMOScript2();
var SQLDMOXfrFile = new SQLDMOXfrFile();
var Environment = new Environment();
var Debug = new Debug();
var Console = WScript.StdOut;
/////////////////////////////////////////////////////////////////
// Entry Point
// ===========
//
function Main( args ) {
var serverName = args.ServerName;
var databaseName = args.DatabaseName;
var username = args.Username;
var password = args.Password;
var filename = args.Filename;
var server = new ActiveXObject( "SQLDMO.SQLServer" );
if ( username == null ) {
server.Connect( serverName );
}
else {
server.Connect( serverName, username, password );
}
var database = server.Databases.Item( databaseName, "dbo" );
var scriptType = SQLDMOScript.Default ||
SQLDMOScript.Indexes ||
SQLDMOScript.ToFileOnly ||
SQLDMOScript.IncludeHeaders ||
SQLDMOScript.IncludeIfNotExists;
var script2Type = SQLDMOScript2.Default;
var xfrFile = SQLDMOXfrFile.SingleFile;
var transfer = new ActiveXObject( "SQLDMO.Transfer2" );
transfer.ScriptType = scriptType;
transfer.Script2Type = script2Type;
transfer.IncludeDB = true;
transfer.IncludeDependencies = true;
transfer.CopyAllFunctions = true;
transfer.CopyAllObjects = true;
database.ScriptTransfer(
transfer,
xfrFile,
filename
);
}
/////////////////////////////////////////////////////////////////
// SQLDMO Script Enumeration
// =========================
//
// (an example of enumeration emulation :)
//
// When setting the ScriptType argument specifying multiple
// behaviors, combine values using an OR logical operator.
// Use these values to set ScriptType.
//
// see: Script Method ( SQL-DMO, Books Online )
//
function SQLDMOScript() {
// Generate Transact-SQL database privilege defining script.
// Database permissions grant or deny statement execution rights.
this.DatabasePermissions = 32;
// SQLDMOScript_PrimaryObject.
this.Default = 4;
// Generate Transact-SQL to remove referenced component.
// Script tests for existence prior attempt to remove component.
this.Drops = 1;
// Generated script is prefixed with a header containing date
// and time of generation and other descriptive information.
this.IncludeHeaders = 131072;
// Transact-SQL creating a component is prefixed by a check for
// existence. When script is executed, component is created
// only when a copy of the named component does not exist.
this.IncludeIfNotExists = 4096;
// SQLDMOScript_ClusteredIndexes,
// SQLDMOScript_NonClusteredIndexes, and
// SQLDMOScript_DRIIndexes combined using an OR logical operator.
// Applies to both table and view objects.
this.Indexes = 73736;
// Individual Transact-SQL statements in the script are not
// delimited using the connection-specific command terminator.
// By default, individual Transact-SQL statements are delimited.
this.NoCommandTerm = 32768;
// Include Transact-SQL privilege defining statements when
// scripting database objects.
this.ObjectPermissions = 2;
// Object names in Transact-SQL generated to remove an object are
// qualified by the owner of the referenced object. Transact-SQL
// generated to create the referenced object qualify the object
// name using the current object owner.
this.OwnerQualify = 262144;
// SQLDMOScript_ObjectPermissions and
// SQLDMOScript_DatabasePermissions combined using an OR logical
// operator.
this.Permissions = 34;
// Generate Transact-SQL creating the referenced component.
this.PrimaryObject = 4;
// When scripting object creation for a table or user-defined
// data type, convert specification of timestamp data type to
// binary(8).
this.TimestampToBinary = 524288;
// Most SQL-DMO object scripting methods specify both a return
// value and an optional output file. When used, and an output
// file is specified, the method does not return the script to
// the caller, but only writes the script to the output file.
this.ToFileOnly = 64;
// Use quote characters to delimit identifier parts when
// scripting object names.
this.UseQuotedIdentifiers = -1;
}
/////////////////////////////////////////////////////////////////
// SQLDMO Script2 Enumeration
// ==========================
//
// When setting the Script2Type argument specifying multiple
// behaviors, combine values using an OR logical operator.
// Use these values to set Script2Type.
//
// see: Script Method ( SQL-DMO, Books Online )
//
function SQLDMOScript2() {
// Disable features available in instances of SQL Server 2000
// so that output is compatible with an instance of SQL Server
// version 7.0. Disabled features are:
// Column-level collation
// User-defined functions
// Extended properties
// Instead of triggers on tables and views
// Indexes on views
// Indexes on computed columns
// Descending indexes
// Default is OFF
this.SevenOnly = 16777216; // 70Only
// Generate Transact-SQL script creating SQLServerAgent
// service jobs and alerts.
this.AgentAlertJob = 2048;
// When scripting an alert, generate script creating
// notifications for the alert.
this.AgentNotify = 1024;
// Generated script file uses multibyte characters.
// Code page 1252 is used to determine character meaning.
this.AnsiFile = 2;
// Generate Transact-SQL SET ANSI_PADDING ON and
// SET ANSI_PADDDING OFF statements before and after
// CREATE TABLE statements in the generated script.
// Applies only when scripting references a SQL Server table.
this.AnsiPadding = 1;
// No scripting options specified.
this.Default = 0;
// Encrypt passwords with script. When specified,
// SQLDMOScript2_UnicodeFile must be specified as well.
this.EncryptPWD = 128;
// Ignore all SQLDMO_SCRIPT_TYPE settings. Use to script
// extended property settings only. Script may require
// editing prior to running on destination database.
this.ExtendedOnly = 67108864;
// Include extended property scripting as part of object
// scripting.
this.ExtendedProperty = 4194304;
// Command batch includes Transact-SQL statements creating
// Microsoft Search full-text catalogs.
this.FullTextCat = 2097152;
// Generated script includes statements defining Microsoft
// Search full-text indexing. Applies only when scripting
// references a SQL Server table. Include security
// identifiers for logons scripted.
this.FullTextIndex = 524288;
// Disable the job at the end of script creation.
// SQLDMOScript2_PrimaryObject must also be specified.
this.JobDisable = 33554432;
// Include security identifiers for logins scripted.
this.LoginSID = 8192;
// Generated script creates replication implementing
// triggers as system objects. Applies only when
// scripting replication articles.
this.MarkTriggers = 32;
// Do not script the collation clause if source is later
// tha SQL Server version 7.0. The default is to generate
// collation.
this.NoCollation = 8388608;
// Generated script does not include 'ON
// clause directing filegroup use. Applies only when
// scripting references a SQL Server table.
this.NoFG = 16;
// Do not script hypothetical indexes used to implement
// the CREATE STATISTICS statement. Applies only when
// scripting references a SQL Server table.
this.NoWhatIfIndexes = 512;
// Generated script output file is a Unicode-character text
// file.
this.UnicodeFile = 4;
}
/////////////////////////////////////////////////////////////////
// SQLDMO SQLDMOXfrFile Enumeration
// ================================
//
// Setting the ScriptFileMode argument affects interpretation
// of the ScriptFile argument. When setting ScriptFileMode,
// use these values, setting ScriptFile as described.
//
// see: SQL Server Books Online, SQL-DMO, ScriptTransfer
//
function SQLDMOXfrFile() {
// SQLDMOXfrFile_SummaryFiles.
this.Default = 1;
// Command batch is written to one file. Specify the file
// name using the ScriptFile argument. If a path is not
// included in the file name, the file is created in the
// directory indicated by the client computer environment
// variable TEMP.
this.SingleFile = 2;
// Command batch is written to multiple files, one file
// for each SQL Server component transferred. Specify a
// path using the ScriptFile argument. If a path is not
// specified, the files are created in the directory
// indicated by the client computer environment variable
// TEMP.
this.SingleFilePerObject = 4;
// Command batch is written to one file. Command batch
// contents are organized by object type. Specify the
// file name using the ScriptFile argument. If a path
// is not included in the file name, the file is created
// in the directory indicated by the client computer
// environment variable TEMP.
this.SingleSummaryFile = 8;
// Command batch is written to multiple files, one file
// for each kind of object transferred. For example,
// generate a file for user-defined data types and a
// separate file for tables. Specify a path using the
// ScriptFile argument. If a path is not specified, the
// files are created in the directory indicated by the
// client computer environment variable TEMP.
this.SummaryFiles = 1;
}
/////////////////////////////////////////////////////////////////
// Environment
// ===========
//
//
// Constructor
//
function Environment() {
this.GetCommandLineArguments = Environment_GetCommandLineArguments;
}
//
// Method Definitions
//
function Environment_GetCommandLineArguments() {
var args = WScript.Arguments.Named;
var result = new Array();
for ( i in COMMAND_LINE_OPTIONS ) {
var option = COMMAND_LINE_OPTIONS[ i ];
var value = args.Item( option.CommandLineName );
var hasValue = args.Exists( option.CommandLineName );
if ( option.IsRequired && ! hasValue ) {
throw "Command-line exception.";
}
else if ( option.IsRequired || hasValue ) {
result[ option.PropertyName ] = value;
}
else {
result[ option.PropertyName ] = option.DefaultValue;
}
}
return result;
}
//
// Private Types
//
function CommandLineOption(
commandLineName,
propertyName,
description,
isRequired,
defaultValue
) {
this.CommandLineName = commandLineName;
this.PropertyName = propertyName;
this.Description = description;
this.IsRequired = isRequired;
this.DefaultValue = defaultValue;
}
/////////////////////////////////////////////////////////////////
// Debug
// =====
//
//
// Constructor
//
function Debug() {
this.Assert = Debug_Assert;
}
//
// Method Definitions
//
function Debug_Assert( condition, message ) {
if ( ! condition ) {
var e = "Assertion violated: " + message;
Console.WriteLine( e );
if ( DEBUG ) {
throw e;
}
else {
WScript.Quit();
}
}
}
/////////////////////////////////////////////////////////////////
// Bootstrap
// =========
//
Bootstrap();
function Bootstrap() {
var args;
try {
args = Environment.GetCommandLineArguments();
}
catch ( ex ) {}
if ( args == null ) {
try {
ShowUsage();
}
catch ( ex ) {}
return;
}
// set debug level
try {
if ( args.IsDebuggable == true ) {
DEBUG = true;
}
}
catch ( ex ) {}
if ( DEBUG ) {
// call entry point
Main( args );
}
else {
try {
Main( args );
}
catch ( ex ) {
try {
Console.WriteLine( "Script failed. " + ex );
}
catch ( ex ) {}
}
}
}
function ShowUsage() {
Console.WriteLine( "TODO: ShowUsage." );
}
You are a total bad ass - but you knew that right? :)
BTW, I haven't tried the following tool yet, but I had to write something like it last year to get OLAP cubes into version controlled, deployable bits. Perhaps you can find it useful.
http://www.microsoft.com/downloads/details.aspx?FamilyID=8d9e7a70-eef4-44c3-a0c5-deece0f8b4b4&displaylang=en
If you think that's bad, you should see what I've done since! Hint [1]. :)
Btw, my 'OLAP' database is just in the sense of 'Online Analytical Processing', not so much in the sense of 'cubes'. (i.e. I'm not using Analysis services atm, just a denormalised schema w/ read-only data).
John.
[1]
I've basically created a set of 'com' wrappers and '.net emulation' classes, along with some bootstraping that automatically 'compiles' and 'links' a script (soon to also 'deploy' to a 'gac'.. ;)
I think it's pretty cool, but I only started this on the w/e, so I haven't got that much done yet, and some of my code still has that "I've been hacking on it for 24 hours straight" look about it.. :P
My source file from above now looks like this:
/////////////////////////////////////////////////////////////////
// Script
// ======
//
// This script creates a T-SQL file with the commands to create
// a database.
//
// Example usage:
// cscript generate-client-template-script.js /server:(local) /database:northwind /file:test.sql
//
var JSCIPTLIB_PATH = "u:\\jscriptlib\\0.10";
var using = new Array(
"com.sqldmo",
"net.system",
"net.system.diagnostics"
);
/////////////////////////////////////////////////////////////////
// Command-Line Options
// ====================
//
var options = new Array(
new CommandLineOption(
"server",
"ServerName",
"Database server to query.",
false,
"(local)"
),
new CommandLineOption(
"database",
"DatabaseName",
"Name of database on server.",
true
),
new CommandLineOption(
"user",
"Username",
"Database username, or blank for NTLM.",
false,
null
),
new CommandLineOption(
"pass",
"Password",
"Database account password, if not NTLM.",
false,
null
),
new CommandLineOption(
"file",
"Filename",
"The file to script the database to.",
true
),
new CommandLineOption(
"debug",
"IsDebuggable",
"Can this script throw for JIT debugging?",
false,
false
)
);
/////////////////////////////////////////////////////////////////
// Entry Point
// ===========
//
function Main( args ) {
var serverName = args.ServerName;
var databaseName = args.DatabaseName;
var username = args.Username;
var password = args.Password;
var filename = args.Filename;
var server = SQLServer.Connect( serverName, username, password );
var database = server.Databases.Item( databaseName, "dbo" );
var scriptType = SQLDMOScript.Default ||
SQLDMOScript.Indexes ||
SQLDMOScript.ToFileOnly ||
SQLDMOScript.IncludeHeaders ||
SQLDMOScript.IncludeIfNotExists;
var script2Type = SQLDMOScript2.Default;
var xfrFile = SQLDMOXfrFile.SingleFile;
var transfer = new ActiveXObject( "SQLDMO.Transfer2" );
transfer.ScriptType = scriptType;
transfer.Script2Type = script2Type;
//transfer.IncludeDB = true;
transfer.IncludeDependencies = true;
transfer.CopyAllFunctions = true;
transfer.CopyAllObjects = true;
database.ScriptTransfer(
transfer,
xfrFile,
filename
);
}
/////////////////////////////////////////////////////////////////
// Bootstrap
// =========
//
function Bootstrap() {
var bootstrap_filepath = JSCIPTLIB_PATH + "\\lib\\bootstrap.js";
var bootstrap_fso = new ActiveXObject( "Scripting.FileSystemObject" );
var bootstrap_file = bootstrap_fso.OpenTextFile( bootstrap_filepath, 1 );
var bootstrap_script = bootstrap_file.ReadAll();
bootstrap_file.Close();
var bootstrap_path = "";
for ( i = 0; i < JSCIPTLIB_PATH.length; i++ ) {
bootstrap_path += JSCIPTLIB_PATH.charAt( i );
if ( JSCIPTLIB_PATH.charAt( i ) == "\\" ) {
bootstrap_path += "\\";
}
}
var bootstrap_using = "new Array(";
for ( i = 0; i < using.length - 1; i++ ) {
bootstrap_using += "\"" + using[ i ] + "\",\n";
}
bootstrap_using += "\"" + using[ using.length - 1 ] + "\")";
bootstrap_script = "DoBootstrap(\"" + bootstrap_path + "\", " +
bootstrap_using + "); " + bootstrap_script;
var scriptPath = eval( bootstrap_script );
// run the new script
var arguments = "";
for ( i in options ) {
var optionName = options[i].CommandLineName;
if ( WScript.Arguments.Named.Exists( optionName ) ) {
var optionValue = WScript.Arguments.Named.Item( optionName );
if ( optionValue.indexOf( " " ) == -1 ) {
arguments += " /" + options[i].CommandLineName + ":" +
optionValue + "";
}
else {
arguments += " /" + options[i].CommandLineName + ":\"" +
optionValue + "\"";
}
}
}
var shell = new ActiveXObject( "WScript.Shell" );
var shellCommand = "%comspec% /c %SystemRoot%\\System32\\cscript.exe //D //NOLOGO //E:jscript " + scriptPath + arguments;
WScript.StdOut.WriteLine( shellCommand );
var scriptShell = shell.Exec( shellCommand );
do {
line = scriptShell.StdOut.ReadLine();
WScript.StdOut.WriteLine( line );
} while ( ! scriptShell.StdOut.AtEndOfStream );
bootstrap_fso.DeleteFile( scriptPath );
}
function CommandLineOption(
commandLineName,
propertyName,
description,
isRequired,
defaultValue
) {
this.CommandLineName = commandLineName;
this.PropertyName = propertyName;
this.Description = description;
this.IsRequired = isRequired;
this.DefaultValue = defaultValue;
}
Bootstrap();
---
This is a directory listing of the bits that I've written so far for 'linking':
U:\jscriptlib>dir /s
Volume in drive U is FILES-2
Volume Serial Number is [TOP SECRET]
Directory of U:\jscriptlib
2005-01-23 12:00 <DIR> .
2005-01-23 12:00 <DIR> ..
2005-01-23 09:48 <DIR> 0.10
0 File(s) 0 bytes
Directory of U:\jscriptlib\0.10
2005-01-23 09:48 <DIR> .
2005-01-23 09:48 <DIR> ..
2005-01-23 11:00 <DIR> com
2005-01-23 11:35 <DIR> lib
2005-01-23 11:00 <DIR> net
0 File(s) 0 bytes
Directory of U:\jscriptlib\0.10\com
2005-01-23 11:00 <DIR> .
2005-01-23 11:00 <DIR> ..
2005-01-23 16:09 <DIR> sqldmo
0 File(s) 0 bytes
Directory of U:\jscriptlib\0.10\com\sqldmo
2005-01-23 16:09 <DIR> .
2005-01-23 16:09 <DIR> ..
2005-01-23 16:58 432 init.js
2005-01-23 16:58 104 interface.js
2005-01-23 16:45 3,120 SQLDMOScript.js
2005-01-23 16:45 3,713 SQLDMOScript2.js
2005-01-23 16:45 1,989 SQLDMOXfrFile.js
2005-01-23 17:09 1,851 SQLServer.js
6 File(s) 11,209 bytes
Directory of U:\jscriptlib\0.10\lib
2005-01-23 11:35 <DIR> .
2005-01-23 11:35 <DIR> ..
2005-01-23 16:56 2,856 bootstrap.js
2005-01-23 11:11 <DIR> console
2005-01-23 11:11 <DIR> file
2005-01-23 14:08 2,071 framework.js
2005-01-23 11:44 943 global.js
3 File(s) 5,870 bytes
Directory of U:\jscriptlib\0.10\lib\console
2005-01-23 11:11 <DIR> .
2005-01-23 11:11 <DIR> ..
0 File(s) 0 bytes
Directory of U:\jscriptlib\0.10\lib\file
2005-01-23 11:11 <DIR> .
2005-01-23 11:11 <DIR> ..
0 File(s) 0 bytes
Directory of U:\jscriptlib\0.10\net
2005-01-23 11:00 <DIR> .
2005-01-23 11:00 <DIR> ..
2005-01-23 11:37 <DIR> system
0 File(s) 0 bytes
Directory of U:\jscriptlib\0.10\net\system
2005-01-23 11:37 <DIR> .
2005-01-23 11:37 <DIR> ..
2005-01-23 11:16 479 Console.js
2005-01-23 12:37 <DIR> diagnostics
2005-01-23 12:38 87 init.js
2005-01-23 12:37 55 interface.js
3 File(s) 621 bytes
Directory of U:\jscriptlib\0.10\net\system\diagnostics
2005-01-23 12:37 <DIR> .
2005-01-23 12:37 <DIR> ..
2005-01-23 11:16 479 Debug.js
2005-01-23 12:37 31 init.js
2005-01-23 12:37 45 interface.js
3 File(s) 555 bytes
Total Files Listed:
15 File(s) 18,255 bytes
29 Dir(s) 49,928,261,632 bytes free
---
This is the bootstrap.js file, at the present time, it basically builds a new script that links in all the bits missing from the main script. I'll be able to do better when I've got some more time.
/////////////////////////////////////////////////////////////////
// Bootstrap
// =========
//
var jscriptlib;
var namespaces;
function DoBootstrap( thePath, theNamespaces ) {
jscriptlib = thePath;
namespaces = theNamespaces;
var fso = new ActiveXObject( "Scripting.FileSystemObject" );
var scriptPath = GetScriptFilePath( fso );
var script;
if ( fso.FileExists( scriptPath ) ) {
script = fso.OpenTextFile( scriptPath, 2 );
}
else {
script = fso.CreateTextFile( scriptPath )
}
// read in the globals
WriteScript( fso, jscriptlib + "\\lib\\global.js", script );
// for each using directive
for ( i in namespaces ) {
// read in the libraries
WriteLibraries( fso, NamespaceToFolder( namespaces[ i ] ), script );
}
// for each using directive
for ( i in namespaces ) {
// read in the interfaces
WriteScript( fso, NamespaceToFolder( namespaces[ i ] ) + "interface.js", script );
}
// for each using directive
for ( i in namespaces ) {
// read in the initialization
WriteScript( fso, NamespaceToFolder( namespaces[ i ] ) + "init.js", script );
}
// read in the script file
var file = fso.OpenTextFile( WScript.ScriptFullName, 1 );
var line = "";
while ( line.indexOf( "// Bootstrap" ) != 0 ) {
line = file.ReadLine();
script.WriteLine( line );
}
file.Close();
script.WriteLine( "" );
script.WriteLine( "" );
// read in the framework
WriteScript( fso, jscriptlib + "\\lib\\framework.js", script );
script.Close();
return scriptPath;
}
function GetScriptFilePath( fso ) {
// return "c:\\temp.js";
return fso.GetTempName();
}
function NamespaceToFolder( namespace ) {
var folders = namespace.split( "." );
var result = jscriptlib;
for ( i in folders ) {
result += "\\" + folders[ i ];
}
return result + "\\";
}
function WriteScript( fso, path, script ) {
var file = fso.OpenTextFile( path, 1 );
while ( ! file.AtEndOfStream ) {
script.WriteLine( file.ReadLine() );
}
file.Close();
script.WriteLine( "" );
script.WriteLine( "" );
}
function WriteLibraries( fso, folderPath, script ) {
var folder = fso.GetFolder( folderPath );
for (
enumerator = new Enumerator( folder.Files );
! enumerator.atEnd();
enumerator.moveNext()
) {
var file = enumerator.item();
if ( file.Name != "interface.js" && file.Name != "init.js" ) {
WriteScript( fso, folderPath + file.Name, script );
}
}
}
---
Grrr. I hate how it loses the spaces.. :(
Sweet...
Your god