Using SMO to restore a database from a .bak file - sql-server

I tried a few solutions and below is the most straight forward one but I get the error below
Logical file 'RestoredProcessMananger' is not part of database 'RestoredProcessMananger'. Use RESTORE FILELISTONLY to list the logical file names.
RESTORE DATABASE is terminating abnormally.
What am I doing wrong ? The idea is to create DLL to be used by other programs the allow a baseline database to be reloaded on a server overwriting whatever is there..
ServerConnection connection = new ServerConnection("xxx", "sa", "srv$xxx");
Server svr =new Server(connection);
Restore res = new Restore();
res.Database = "RestoredProcessMananger";
res.Action = RestoreActionType.Database;
res.Devices.AddDevice(#"C:\temp\ProcessManager.bak", DeviceType.File);
res.ReplaceDatabase = true;
res.RelocateFiles.Add(new RelocateFile("RestoredProcessMananger", _
#"c:\ProcessManager2.mdf"));
res.RelocateFiles.Add(new RelocateFile("RestoredProcessMananger_Log", _
#"c:\ProcessManager2_log.ldf"));
res.SqlRestore(svr);
svr.Refresh();
EDIT 1: fixed
public static void RestoreDatabase(string Server, //sqlserver //from CONFIG
string BackupFilePath, //where the bak file I want to restore //from CONFIG
string destinationDatabaseName, //what the restored database will be called //from CONFIG
string DatabaseFolder, //where the data/log files for the destination (break into 2 variables (2 different locations)) (get from GetDatabaseMDFFilePathName)
string DatabaseFileName, //the destination MDF file name (get from GetDatabaseMDFFilePathName)
string DatabaseLogFileName) //the destination LDF file name (get from GetDatabaseMDFFilePathName)
{
Server myServer = GetDatabases("xxx");
Restore myRestore = new Restore();
myRestore.Database = destinationDatabaseName;
Database currentDb = myServer.Databases[destinationDatabaseName];
if (currentDb != null)
myServer.KillAllProcesses(destinationDatabaseName);
myRestore.Devices.AddDevice(BackupFilePath, DeviceType.File);
string DataFileLocation = DatabaseFolder + "\\" + destinationDatabaseName + ".mdf";
string LogFileLocation = DatabaseFolder + "\\" + destinationDatabaseName + "_log.ldf";
myRestore.RelocateFiles.Add(new RelocateFile(DatabaseFileName, DataFileLocation));
myRestore.RelocateFiles.Add(new RelocateFile(DatabaseLogFileName, LogFileLocation));
myRestore.ReplaceDatabase = true;
myRestore.PercentCompleteNotification = 10;
myRestore.SqlRestore(myServer);
currentDb = myServer.Databases[destinationDatabaseName];
currentDb.SetOnline();
}

The error indicates that RestoredProcessMananger is not the name of a logical file contained in your backup file.
Have you used RESTORE FILELISTONLY ... to list the files contained in the .BAK file?
For instance, if your backup file is named C:\MyBackupFile.bak you would need to run the following query from SQL Server Management Studio (or SQLCMD, etc):
RESTORE FILELISTONLY FROM DISK='C:\MyBackupFile.bak';
This will provide a list of the logical files contained in the backup file. You will then need to pass the name of one of these files into your RelocateFile code.

Related

Export password protected xlsx file and e-mail it

Currently I'm working with SSIS package that is executing a Stored Procedure and generating an .XLSX file with the results of the query.
What I'm needing to do is to encrypt the .xlsx file. it could be done either encrypting the file after being populated with the SSIS package, or by putting a password on the .xlsx file beforehand and opening it (reading password protected file) and exporting data to it.
*I know that password protected files are not super safe, but for this case I only need it to be password protected for compliance.
I was investigating with SSIS and I believe I can do it with a powershell script that can be run using an "Execute Process Task" tool from SSIS, please correct me if I'm wrong on this.
Update: I'm executing with an "Execute Process Task" a PowerShell script (script.ps1):
Set objExcel = CreateObject(“Excel.Application”)
objExcel.Visible = True
objExcel.DisplayAlerts = FALSE
Set objWorkbook = objExcel.Workbooks.Add
Set objWorksheet = objWorkbook.Worksheets(1)
objWorksheet.Cells(1, 1).Value = Now
objWorkbook.SaveAs “C:\Scripts\Test.xlsx”,,”Password123”
objExcel.Quit
However here I don't knowhow to point to the Excel file I created with the package to password protect it, am I missing something?
this is what my package design looks in SSIS:
And this is the detail of the "Execute Process Task" called "Lock excel file generated":
*This comes from source: https://techcommunity.microsoft.com/t5/SQL-Server-Integration-Services/Run-PowerShell-scripts-in-SSIS/ba-p/388340
You can create encrypted Excel spreadsheets in Powershell.
You'd create a scheduled task to fire of a Powershell script, along the lines of
Set objExcel = CreateObject(“Excel.Application”)
objExcel.Visible = True
objExcel.DisplayAlerts = FALSE
Set objWorkbook = objExcel.Workbooks.Add
Set objWorksheet = objWorkbook.Worksheets(1)
//Whatever you do to populate the workbook
Set filename = [System.IO.Path]::GetRandomFileName()
objWorkbook.SaveAs filename,,”%reTG54w”
objExcel.Quit

Passing filename from SSIS script task

I'm attempting to create a SSIS package that loads a flat file into a SQL server table.
I've been able to piece the loading functionality together. I'm currently stuck on passing the filename if it's found from the script task back to a variable where I'd like to use it in the flat file connection string.
Public Sub Main()
'
Dim di As DirectoryInfo = New DirectoryInfo("\\winshare\iFile\Cors2\AAA\AAA Employee Incentive Source Data\")
Dim fi As FileInfo() = di.GetFiles("AAA Full PreReg Report*.csv")
If fi.Length > 0 Then
Dts.Variables("User::fileExists").Value = True
Dts.Variables("User::FileName").Value = fi.name
Else
Dts.Variables("User::fileExists").Value = False
End If
' Add your code here
'
Dts.TaskResult = ScriptResults.Success
End Sub
I'm seeking help with
Dts.Variables("User::FileName").Value = fi.name
Why won't this work?
Thanks
If you are looking to get the first file in the directory then you can use the following line of code:
Dts.Variables("User::FileName").Value = fi(0).name
But If you are looking to loop over files then i recommend using the Foreach loop container to loop over files and store each file name within a variable:
SSIS - How to loop through files in folder and get path+file names and finally execute stored Procedure with parameter as Path + Filename
FAQ - How to loop through files in a specified folder, load one by one and move to archive folder using SSIS

Filepicker VBA to select file and store the file name not working

I am trying to run the following in order to get the file name that the user selects. The file is an .mdf file that is attached previously to an SQL server. But when I run it, a window comes out and says I don't have permission to open the file. I know it is because it is being used in SQL, because if I don't attach it in the SQL server it runs without a problem.
The thing is that I need the mdf in SQL before running the vba code and I just need the file name. Is there a way to store the file name without "opening" it?
Function GetDB() As String
Dim db As Office.FileDialog
Dim fileName As String
Set db = Application.FileDialog(msoFileDialogFilePicker)
With db
.Title = "Select a Database"
.AllowMultiSelect = False
.InitialFileName = Application.DefaultFilePath
Application.DisplayAlerts = False
If .Show = True Then
fileName = Mid(.SelectedItems(1), InStrRev(.SelectedItems(1), "\") + 1)
End If
End With
End Function
replace
sItem = .SelectedItems(1)
with:
GetDB = .SelectedItems(1)
I ended up setting and ADODB Connection to get the databases directly from the server without having the "The file is in use" issue.

Sql server restore stuck in "restoring" state

If I restore from Sql Server no problem, but if I do it through my application, the database is stuck in "restoring state".
I found some advice saying to put noRecovery = false, but this didn't change anything.
If I remove the "with move" option, it works: after the restore the DB is in a normal state.
The thing that I would like to understand is: does "with move" modify a sql server table?
Because if I launch the restore the first time without "with move" it says that he could not find the specified path. Otherwise, if I launch the restore with this option, and then one second time without it, it works. So there must be some tables that sql server uses to map the logical name with a physical path, how can I modify this table?
Here is the code:
SqlConnection sqlConnection = new SqlConnection(string.Format("Data Source={0};Initial Catalog={1};Integrated Security=True", database.SqlServerId, database.Name));
ServerConnection connection = new ServerConnection(sqlConnection);
Server sqlServer = new Server(connection);
Restore rstDatabase = new Restore();
rstDatabase.Action = RestoreActionType.Database;
rstDatabase.Database = backupFile.Name;
BackupDeviceItem bkpDevice = new BackupDeviceItem(backupFile.FileName, DeviceType.File);
rstDatabase.Devices.Add(bkpDevice);
rstDatabase.ReplaceDatabase = true;
rstDatabase.NoRecovery = false;
string dbLogicalName = "";
string logLogicalName = "";
sqlConnection.Open();
SqlCommand command = new SqlCommand(string.Format("RESTORE FILELISTONLY FROM DISK = '{0}'", backupFile.FileName), sqlConnection);
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
if (reader.GetString(2) == "D")
dbLogicalName = reader.GetString(0);
if (reader.GetString(2) == "L")
logLogicalName = reader.GetString(0);
}
}
reader.Close();
rstDatabase.RelocateFiles.Add(new RelocateFile(dbLogicalName, backupFile.DatabaseFile));
rstDatabase.RelocateFiles.Add(new RelocateFile(logLogicalName, backupFile.LogsFile));
//Restore
rstDatabase.SqlRestore(sqlServer);
rstDatabase.Devices.Remove(bkpDevice);
sqlConnection.Close();
connection.Disconnect();
The thing that I would like to understand is: does "with move" modify a sql server table?
Not necessarily, unless you modify the default databases path in the instance, which you could do in SSMS following these steps:
https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/view-or-change-the-default-locations-for-data-and-log-files
But as someone said, try to fix that on your own application. Do not attempt to modify this on the system database. It could bring unexpected results if you do that.
I think you know that, but WITH MOVE tells the SQL Server to reallocate the restored database in different path than the default path.

Sql SMO: How to get path of database physical file name?

I am trying to return the physical file path of a database's mdf/ldf files.
I have tried using the following code:
Server srv = new Server(connection);
Database database = new Database(srv, dbName);
string filePath = database.PrimaryFilePath;
However this throws an exception "'database.PrimaryFilePath' threw an exception of type 'Microsoft.SqlServer.Management.Smo.PropertyNotSetException' - even though the database I'm running this against exists, and its mdf file is located in c:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL
What am I doing wrong?
Usually the problem is with the DefaultFile property being null. The default data file is where the data files are stored on the instance of SQL Server unless otherwise specified in the FileName property. If no other default location has been specified the property will return an empty string.
So, this property brings back nothing (empty string) if you didn't set the default location.
A workaround is to check the DefaultFile property, if it returns an empty string use SMO to get the master database then use the Database.PrimaryFilePath property to retrieve the Default Data File Location (since it hasn't changed)
Since you say the problem is with your PrimaryFilePath:
Confirm that your connection is open
Confirm that other properties are available
This is how I do it, prepared for multiple file names. Access database.LogFiles to get the same list of log file names:
private static IList<string> _GetAttachedFileNames(Database database)
{
var fileNames = new List<string>();
foreach (FileGroup group in database.FileGroups)
foreach (DataFile file in group.Files)
fileNames.Add(file.FileName);
return fileNames;
}
Server srv = new Server(connection);
DatabaseCollection dbc = svr.Databases;
Database database = dbc["dbName"];
string filePath = database.PrimaryFilePath;
I think the easiest approach would be to run sql script on your sql server instance which will always return you correct data and log file paths. The following sql will do the trick
SELECT
db.name AS DBName,
(select mf.Physical_Name FROM sys.master_files mf where mf.type_desc = 'ROWS' and db.database_id = mf.database_id ) as DataFile,
(select mf.Physical_Name FROM sys.master_files mf where mf.type_desc = 'LOG' and db.database_id = mf.database_id ) as LogFile
FROM sys.databases db
order by DBName
You can still execute this sql using SMO if you want to, which will return you a dataset and then you can extract that information.
var result = new List();
var server = new Server( serverInstanceName );
var data = server.Databases[ "master" ].ExecuteWithResults(sql);
foreach ( DataRow row in data.Tables[ 0 ].Rows )
result.Add( new DatabaseInfo( row[ "DBName" ].ToString(), row[ "DataFile" ].ToString(), row[ "LogFile" ].ToString() ) );
return result;
If you will use this snippet then make sure to create a DatabaseInfo class which will store the information returned from Sql server instance.
using Smo = Microsoft.SqlServer.Management.Smo;
public string GetDataBasePath(string strDatabaseName)
{
ServerConnection srvConn = new ServerConnection();
srvConn.ConnectionString = "<your connection string goes here>";
Server srv = new Server(srvConn);
foreach (Smo.Database db in srv.Databases)
{
if (string.Compare(strDatabaseName, db.Name, true) == 0)
{
return db.PrimaryFilePath;
}
}
return string.Empty;
}

Resources