// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // - Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // - Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // - Neither the name of the Outercurve Foundation nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using System; using System.Text.RegularExpressions; using System.Data; using System.Data.SqlClient; namespace WebsitePanel.Setup { /// /// Sql utils class. /// public sealed class SqlUtils { /// /// Initializes a new instance of the class. /// private SqlUtils() { } public static string BuildDbServerMasterConnectionString(string dbServer, string dbLogin, string dbPassw) { return BuildDbServerConnectionString(dbServer, "master", dbLogin, dbPassw); } public static string BuildDbServerConnectionString(string dbServer, string dbName, string dbLogin, string dbPassw) { if (String.IsNullOrEmpty(dbLogin) && String.IsNullOrEmpty(dbPassw)) { return String.Format("Server={0};Database={1};Integrated Security=SSPI;", dbServer, dbName); } else { return String.Format("Server={0};Database={1};User id={2};Password={3};", dbServer, dbName, dbLogin, dbPassw); } } /// /// Check sql connection. /// /// Connection string. /// True if connecion is valid, otherwise false. internal static bool CheckSqlConnection(string connectionString) { SqlConnection conn = new SqlConnection(connectionString); try { conn.Open(); } catch { return false; } conn.Close(); return true; } /// /// Gets the version of SQL Server instance. /// /// Connection string. /// True if connecion is valid, otherwise false. internal static string GetSqlServerVersion(string connectionString) { SqlConnection conn = new SqlConnection(connectionString); try { SqlCommand cmd = new SqlCommand("SELECT SERVERPROPERTY('productversion')", conn); conn.Open(); SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); string version = "unknown"; if (reader.HasRows) { reader.Read(); version = reader[0].ToString(); reader.Close(); } return version; } finally { if (conn != null && conn.State == ConnectionState.Open) conn.Close(); } } /// /// Gets the security mode of SQL Server. /// /// Connection string. /// 1 - Windows Authentication mode, 0 - Mixed mode. internal static int GetSqlServerSecurityMode(string connectionString) { SqlConnection conn = new SqlConnection(connectionString); int mode = 0; try { SqlCommand cmd = new SqlCommand("SELECT SERVERPROPERTY('IsIntegratedSecurityOnly')", conn); conn.Open(); SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); if (reader.HasRows) { reader.Read(); mode = Convert.ToInt32(reader[0]); reader.Close(); } } catch { } finally { if (conn != null && conn.State == ConnectionState.Open) conn.Close(); } return mode; } /// /// Get databases. /// /// Connection string. /// Databases. internal static string[] GetDatabases(string connectionString) { DataSet ds = ExecuteQuery(connectionString, "SELECT Name FROM master..sysdatabases ORDER BY Name"); string[] ret = new string[ds.Tables[0].Rows.Count]; for (int i = 0; i < ds.Tables[0].Rows.Count; i++) { ret[i] = (string)ds.Tables[0].Rows[i]["Name"]; } return ret; } internal static int ExecuteStoredProcedure(string connectionString, string name, params SqlParameter[] commandParameters) { using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand cmd = new SqlCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = name; cmd.Connection = connection; cmd.CommandTimeout = 300; //attach parameters if (commandParameters != null) { foreach (SqlParameter p in commandParameters) { if (p != null) { // Check for derived output value with no value assigned if ((p.Direction == ParameterDirection.InputOutput || p.Direction == ParameterDirection.Input) && (p.Value == null)) { p.Value = DBNull.Value; } cmd.Parameters.Add(p); } } } int ret = cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); connection.Close(); return ret; } } private static int ExecuteNonQuery(string connectionString, string commandText) { SqlConnection conn = null; try { conn = new SqlConnection(connectionString); SqlCommand cmd = new SqlCommand(commandText, conn); cmd.CommandTimeout = 300; conn.Open(); int ret = cmd.ExecuteNonQuery(); return ret; } finally { // close connection if required if (conn != null && conn.State == ConnectionState.Open) conn.Close(); } } internal static DataSet ExecuteQuery(string connectionString, string commandText) { SqlConnection conn = null; try { conn = new SqlConnection(connectionString); SqlDataAdapter adapter = new SqlDataAdapter(commandText, conn); DataSet ds = new DataSet(); adapter.Fill(ds); return ds; } finally { // close connection if required if (conn != null && conn.State == ConnectionState.Open) conn.Close(); } } /// /// Checks if the database exists. /// /// Database name. /// Connection string. /// Returns True if the database exists. internal static bool DatabaseExists(string connectionString, string databaseName) { return (ExecuteQuery(connectionString, String.Format("select name from master..sysdatabases where name = '{0}'", databaseName)).Tables[0].Rows.Count > 0); } /// /// Creates database. /// /// Connection string. /// Database name. internal static void CreateDatabase(string connectionString, string databaseName) { // create database in the default location string commandText = string.Format("CREATE DATABASE [{0}] COLLATE Latin1_General_CI_AS;", databaseName); // create database ExecuteNonQuery(connectionString, commandText); // grant users access //UpdateDatabaseUsers(database.Name, database.Users); } /// /// Creates database user. /// /// Connection string. /// User name /// Password. /// Default database. internal static bool CreateUser(string connectionString, string userName, string password, string database) { bool userCreated = false; if (!UserExists(connectionString, userName)) { ExecuteNonQuery(connectionString, String.Format("CREATE LOGIN {0} WITH PASSWORD='{1}', DEFAULT_DATABASE={2}, CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF", userName, password, database)); userCreated = true; } AddUserToDatabase(connectionString, database, userName); return userCreated; } private static void AddUserToDatabase(string connectionString, string databaseName, string user) { if (user == "sa") return; // grant database access try { ExecuteNonQuery(connectionString, string.Format("USE {0};EXEC sp_grantdbaccess '{1}';", databaseName, user)); } catch (SqlException ex) { if (ex.Number == 15023) { // the user already exists in the database // so, try to auto fix his login in the database ExecuteNonQuery(connectionString, string.Format("USE {0};EXEC sp_change_users_login 'Auto_Fix', '{1}';", databaseName, user)); } else { throw new Exception("Can't add user to database", ex); } } // add database owner ExecuteNonQuery(connectionString, string.Format("USE {0};EXEC sp_addrolemember 'db_owner', '{1}';", databaseName, user)); } /// /// Checks whether specified user exists. /// /// Connection string /// User name. /// True if specified user exists, otherwise false. internal static bool UserExists(string connectionString, string username) { return (ExecuteQuery(connectionString, string.Format("select name from master..syslogins where name = '{0}'", username)).Tables[0].Rows.Count > 0); } /// /// Checks whether specified login exists. /// /// Connection string /// Login name. /// True if specified login exists, otherwise false. internal static bool LoginExists(string connectionString, string loginName) { return (ExecuteQuery(connectionString, string.Format("SELECT * FROM sys.server_principals WHERE name = N'{0}'", loginName)).Tables[0].Rows.Count > 0); } /// /// Deletes login /// /// Connection string /// Login name internal static void DeleteLogin(string connectionString, string loginName) { // drop login if (LoginExists(connectionString, loginName)) ExecuteNonQuery(connectionString, String.Format("DROP LOGIN [{0}]", loginName)); } /// /// Deletes database user /// /// Connection string /// Username internal static void DeleteUser(string connectionString, string username) { // remove user from databases string[] userDatabases = GetUserDatabases(connectionString, username); foreach (string database in userDatabases) RemoveUserFromDatabase(connectionString, database, username); // close all user connection CloseUserConnections(connectionString, username); // drop login ExecuteNonQuery(connectionString, String.Format("EXEC sp_droplogin '{0}'", username)); } /// /// Deletes database /// /// Connection string /// Database name internal static void DeleteDatabase(string connectionString, string databaseName) { // remove all users from database string[] users = GetDatabaseUsers(connectionString, databaseName); foreach (string user in users) { RemoveUserFromDatabase(connectionString, databaseName, user); } // close all connection CloseDatabaseConnections(connectionString, databaseName); // drop database ExecuteNonQuery(connectionString, String.Format("DROP DATABASE {0}", databaseName)); } private static string[] GetDatabaseUsers(string connectionString, string databaseName) { string cmdText = String.Format(@" select su.name FROM {0}..sysusers as su inner JOIN master..syslogins as sl on su.sid = sl.sid where su.hasdbaccess = 1 AND su.islogin = 1 AND su.issqluser = 1 AND su.name <> 'dbo'", databaseName); DataView dvUsers = ExecuteQuery(connectionString, cmdText).Tables[0].DefaultView; string[] users = new string[dvUsers.Count]; for (int i = 0; i < dvUsers.Count; i++) { users[i] = (string)dvUsers[i]["Name"]; } return users; } private static string[] GetUserDatabaseObjects(string connectionString, string databaseName, string user) { DataView dvObjects = ExecuteQuery(connectionString, String.Format("select so.name from {0}..sysobjects as so" + " inner join {1}..sysusers as su on so.uid = su.uid" + " where su.name = '{2}'", databaseName, databaseName, user)).Tables[0].DefaultView; string[] objects = new string[dvObjects.Count]; for (int i = 0; i < dvObjects.Count; i++) { objects[i] = (string)dvObjects[i]["Name"]; } return objects; } private static string[] GetUserDatabases(string connectionString, string username) { string cmdText = String.Format(@" DECLARE @Username varchar(100) SET @Username = '{0}' CREATE TABLE #UserDatabases ( Name nvarchar(100) collate database_default ) DECLARE @DbName nvarchar(100) DECLARE DatabasesCursor CURSOR FOR SELECT name FROM master..sysdatabases WHERE (status & 256) = 0 AND (status & 512) = 0 OPEN DatabasesCursor WHILE (10 = 10) BEGIN --LOOP 10: thru Databases FETCH NEXT FROM DatabasesCursor INTO @DbName --print @DbName IF (@@fetch_status <> 0) BEGIN DEALLOCATE DatabasesCursor BREAK END DECLARE @sql nvarchar(1000) SET @sql = 'if exists (select ''' + @DbName + ''' from [' + @DbName + ']..sysusers where name = ''' + @Username + ''') insert into #UserDatabases (Name) values (''' + @DbName + ''')' EXECUTE(@sql) END SELECT Name FROM #UserDatabases DROP TABLE #UserDatabases ", username); DataView dvDatabases = ExecuteQuery(connectionString, cmdText).Tables[0].DefaultView; string[] databases = new string[dvDatabases.Count]; for (int i = 0; i < dvDatabases.Count; i++) databases[i] = (string)dvDatabases[i]["Name"]; return databases; } private static void CloseDatabaseConnections(string connectionString, string databaseName) { DataView dv = ExecuteQuery(connectionString, String.Format(@"SELECT spid FROM master..sysprocesses WHERE dbid = DB_ID('{0}') AND spid > 50 AND spid <> @@spid", databaseName)).Tables[0].DefaultView; // kill processes for (int i = 0; i < dv.Count; i++) { KillProcess(connectionString, (short)(dv[i]["spid"])); } } private static void CloseUserConnections(string connectionString, string userName) { DataView dv = ExecuteQuery(connectionString, String.Format(@"SELECT spid FROM master..sysprocesses WHERE loginame = '{0}'", userName)).Tables[0].DefaultView; // kill processes for (int i = 0; i < dv.Count; i++) { KillProcess(connectionString, (short)(dv[i]["spid"])); } } private static void KillProcess(string connectionString, short spid) { try { ExecuteNonQuery(connectionString, String.Format("KILL {0}", spid)); } catch (SqlException) { } } private static void RemoveUserFromDatabase(string connectionString, string databaseName, string user) { // change ownership of user's objects string[] userObjects = GetUserDatabaseObjects(connectionString, databaseName, user); foreach (string userObject in userObjects) { try { ExecuteNonQuery(connectionString, String.Format("USE {0};EXEC sp_changeobjectowner '{1}.{2}', 'dbo'", databaseName, user, userObject)); } catch (SqlException ex) { if (ex.Number == 15505) { // Cannot change owner of object 'user.ObjectName' or one of its child objects because // the new owner 'dbo' already has an object with the same name. // try to rename object before changing owner string renamedObject = user + DateTime.Now.Ticks + "_" + userObject; ExecuteNonQuery(connectionString, String.Format("USE {0};EXEC sp_rename '{1}.{2}', '{3}'", databaseName, user, userObject, renamedObject)); // change owner ExecuteNonQuery(connectionString, String.Format("USE {0};EXEC sp_changeobjectowner '{1}.{2}', 'dbo'", databaseName, user, renamedObject)); } else { throw new Exception("Can't change database object owner", ex); } } } // revoke db access ExecuteNonQuery(connectionString, String.Format("USE {0};EXEC sp_revokedbaccess '{1}';", databaseName, user)); } internal static bool IsValidDatabaseName(string name) { if (name == null || name.Trim().Length == 0 || name.Length > 128) return false; return Regex.IsMatch(name, "^[0-9A-z_@#$]+$", RegexOptions.Singleline); } internal static string BackupDatabase(string connectionString, string databaseName) { string bakFile = databaseName + ".bak"; // backup database ExecuteNonQuery(connectionString, String.Format(@"BACKUP DATABASE [{0}] TO DISK = N'{1}'", // WITH INIT, NAME = '{2}' databaseName, bakFile)); return bakFile; } internal static void BackupDatabase(string connectionString, string databaseName, out string bakFile, out string position) { bakFile = databaseName + ".bak"; position = "1"; string backupName = "Backup " + DateTime.Now.ToString("yyyyMMddHHmmss"); // backup database ExecuteNonQuery(connectionString, String.Format(@"BACKUP DATABASE [{0}] TO DISK = N'{1}' WITH NAME = '{2}'", // WITH INIT, NAME = '{2}' databaseName, bakFile, backupName)); //define last position in backup set string query = string.Format("RESTORE HEADERONLY FROM DISK = N'{0}'", bakFile); DataSet ds = ExecuteQuery(connectionString, query); query = string.Format("BackupName = '{0}'", backupName); DataRow[] rows = ds.Tables[0].Select(query, "Position DESC"); if (rows != null && rows.Length > 0) position = rows[0]["Position"].ToString(); } internal static void RestoreDatabase(string connectionString, string databaseName, string bakFile) { // close current database connections CloseDatabaseConnections(connectionString, databaseName); // restore database ExecuteNonQuery(connectionString, String.Format(@"RESTORE DATABASE [{0}] FROM DISK = '{1}' WITH REPLACE", databaseName, bakFile)); } internal static void RestoreDatabase(string connectionString, string databaseName, string bakFile, string position) { // close current database connections CloseDatabaseConnections(connectionString, databaseName); // restore database ExecuteNonQuery(connectionString, String.Format(@"RESTORE DATABASE [{0}] FROM DISK = '{1}' WITH FILE = {2}, REPLACE", databaseName, bakFile, position)); } } }