615 lines
20 KiB
C#
615 lines
20 KiB
C#
// Copyright (c) 2011, 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
|
|
{
|
|
/// <summary>
|
|
/// Sql utils class.
|
|
/// </summary>
|
|
public sealed class SqlUtils
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the class.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check sql connection.
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string.</param>
|
|
/// <returns>True if connecion is valid, otherwise false.</returns>
|
|
internal static bool CheckSqlConnection(string connectionString)
|
|
{
|
|
SqlConnection conn = new SqlConnection(connectionString);
|
|
try
|
|
{
|
|
conn.Open();
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
|
|
conn.Close();
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the version of SQL Server instance.
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string.</param>
|
|
/// <returns>True if connecion is valid, otherwise false.</returns>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the security mode of SQL Server.
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string.</param>
|
|
/// <returns>1 - Windows Authentication mode, 0 - Mixed mode.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get databases.
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string.</param>
|
|
/// <returns>Databases.</returns>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the database exists.
|
|
/// </summary>
|
|
/// <param name="databaseName">Database name.</param>
|
|
/// <param name="connectionString">Connection string.</param>
|
|
/// <returns>Returns True if the database exists.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates database.
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string.</param>
|
|
/// <param name="databaseName">Database name.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates database user.
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string.</param>
|
|
/// <param name="userName">User name</param>
|
|
/// <param name="password">Password.</param>
|
|
/// <param name="database">Default database.</param>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether specified user exists.
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string</param>
|
|
/// <param name="username">User name.</param>
|
|
/// <returns>True if specified user exists, otherwise false.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether specified login exists.
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string</param>
|
|
/// <param name="loginName">Login name.</param>
|
|
/// <returns>True if specified login exists, otherwise false.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes login
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string</param>
|
|
/// <param name="loginName">Login name</param>
|
|
internal static void DeleteLogin(string connectionString, string loginName)
|
|
{
|
|
// drop login
|
|
if (LoginExists(connectionString, loginName))
|
|
ExecuteNonQuery(connectionString, String.Format("DROP LOGIN [{0}]", loginName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes database user
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string</param>
|
|
/// <param name="username">Username</param>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes database
|
|
/// </summary>
|
|
/// <param name="connectionString">Connection string</param>
|
|
/// <param name="databaseName">Database name</param>
|
|
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));
|
|
|
|
}
|
|
}
|
|
}
|