Click here to Skip to main content
Licence BSD
First Posted 8 Mar 2009
Views 40,417
Downloads 1,288
Bookmarked 76 times

Transferring backup files from a remote SQL Server instance to a local machine without network shares, FTP, HTTP

By | 8 Mar 2009 | Article
This article is about transferring backups without raising the privileges of SQL Server, or using custom copying tools.
 
Part of The SQL Zone sponsored by
See Also

Who should read it?

If you have clients with small databases (< 1GB), they have no administrator employed, they are using the Express version of SQL Server, and you want to add some crazy thing to your backup techniques portfolio, read-on :-)

Introduction

A few days ago, a question was asked if my program could backup all (I have embedded backup functionality in my program) data on a specific computer on a company network. The most obvious thing to do was to share a folder on that computer and place the backups on that folder, but the trick was to do it without raising the privileges of SQL Server. The second thing: making a batch file that would copy the backups from one computer to another, but... people wanted to make everything as simple as possible. So... that solution was written :-) (well, not exactly that, this is the basic version of what I wrote).

How the stuff works

I tried to accomplish the task without using anything that is not a standard part of a SQL Server installation; so, I didn't use xp_cmdshell. The code does several steps to accomplish the task.

  1. Make a backup on a swap file on the server
  2. Create a temporary table with a varbinary field
  3. Insert the backup into the table
  4. Fetch column data using ADO.NET
  5. Save the stream in a file

The whole magic is done in the method DoLocalBackup which takes two parameters: the path to the catalog on the remote server where we want to create our swap file, and the path to the folder on our local machine where we want to store our backups.

Step 1

In this step, we're going to make a backup into our swap file which is going to be reused the next time we invoke DoLocalBackup, so we don't need to use xp_cmdshell to delete it (since it will be recreated). We will use FORMAT, INIT to make the swap, and COPY_ONLY to ensure we did not mess up with the other backup plan.

_sql = String.Format("BACKUP DATABASE {0} TO DISK " + 
       "= N'{1}\\{0}.bak' WITH FORMAT, COPY_ONLY, INIT, " + 
       "NAME = N'{0} - Full Database Backup', SKIP ", 
       _dbname, AremoteTempPath, _dbname);
_command.CommandText = _sql;
_command.ExecuteNonQuery();

Step 2

In this step, we will create our temporary table to store the backup information from our swap file.

_sql = String.Format("IF OBJECT_ID('tempdb..##{0}') IS " + 
                     "NOT NULL DROP TABLE ##{0}", temporaryTableName);
_command.CommandText = _sql;
_command.ExecuteNonQuery();
_sql = String.Format("CREATE TABLE ##{0} (bck VARBINARY(MAX))", 
                     temporaryTableName);
_command.CommandText = _sql;
_command.ExecuteNonQuery();

Step 3

Now, we're going to load our backup information into a temporary table. I used OPENROWSET with BULK and it worked prefectly. You can do some fun stuff with OPENROWSET, it's really worth reading about it.

_sql = String.Format("INSERT INTO ##{0} SELECT bck.* FROM OPENROWSET" + 
       "(BULK '{1}\\{2}.bak',SINGLE_BLOB) bck", 
       temporaryTableName, AremoteTempPath, _dbname);
_command.CommandText = _sql;
_command.ExecuteNonQuery();

Steps 4 and 5

Fun stuff :-). We're getting one row from the temporary table and fetching it as a stream of bytes. This is kind of tricky because MSDN tell us that we should set the size as GetUpperBound(0). Correct me if I'm wrong, but that is possibly an error because when you do that, you won't copy the last byte, and later the the file might be corrupted (especially if some CRC calculations are taking place). So I just added + 1, and my backups restore flawlessly :-).

_sql = String.Format("SELECT bck FROM ##{0}", temporaryTableName);
SqlDataAdapter da = new SqlDataAdapter(_sql, _conn);
DataSet ds = new DataSet();
da.Fill(ds);
DataRow dr = ds.Tables[0].Rows[0];
byte[] backupFromServer = new byte[0];
backupFromServer = (byte[])dr["bck"];
int aSize = new int();
aSize = backupFromServer.GetUpperBound(0) + 1;

FileStream fs = new FileStream(String.Format("{0}\\{1}", 
                AlocalPath, fileName), FileMode.OpenOrCreate, FileAccess.Write);
fs.Write(backupFromServer, 0, aSize);
fs.Close();

The complete backup method

public void DoLocalBackup(string AremoteTempPath, string AlocalPath)
{
    try
    {
        if (_conn == null)
            return;
        SqlCommand _command = new SqlCommand();
        _command.Connection = _conn;
        // nice filename on local side, so we know when backup was done
        string fileName = _dbname + DateTime.Now.Year.ToString() + "-" +
            DateTime.Now.Month.ToString() + "-" +
            DateTime.Now.Day.ToString() + "-" + 
                DateTime.Now.Millisecond.ToString() + ".bak";
        // we invoke this method to ensure we didnt mess up with other programs
        string temporaryTableName = findUniqueTemporaryTableName();
         
        string _sql;

        _sql = String.Format("BACKUP DATABASE {0} TO DISK = N'{1}\\{0}.bak' " + 
               "WITH FORMAT, COPY_ONLY, INIT, NAME = N'{0} - Full Database " + 
               "Backup', SKIP ", _dbname, AremoteTempPath, _dbname);
        _command.CommandText = _sql;
        _command.ExecuteNonQuery();
        _sql = String.Format("IF OBJECT_ID('tempdb..##{0}') IS " + 
               "NOT NULL DROP TABLE ##{0}", temporaryTableName);
        _command.CommandText = _sql;
        _command.ExecuteNonQuery();
        _sql = String.Format("CREATE TABLE ##{0} (bck VARBINARY(MAX))", 
                             temporaryTableName);
        _command.CommandText = _sql;
        _command.ExecuteNonQuery();
        _sql = String.Format("INSERT INTO ##{0} SELECT bck.* FROM " + 
               "OPENROWSET(BULK '{1}\\{2}.bak',SINGLE_BLOB) bck", 
               temporaryTableName, AremoteTempPath, _dbname);
        _command.CommandText = _sql;
        _command.ExecuteNonQuery();
        _sql = String.Format("SELECT bck FROM ##{0}", temporaryTableName);
        SqlDataAdapter da = new SqlDataAdapter(_sql, _conn);
        DataSet ds = new DataSet();
        da.Fill(ds);
        DataRow dr = ds.Tables[0].Rows[0];
        byte[] backupFromServer = new byte[0];
        backupFromServer = (byte[])dr["bck"];
        int aSize = new int();
        aSize = backupFromServer.GetUpperBound(0) + 1;

        FileStream fs = new FileStream(String.Format("{0}\\{1}", 
                        AlocalPath, fileName), FileMode.OpenOrCreate, 
                        FileAccess.Write);
        fs.Write(backupFromServer, 0, aSize);
        fs.Close();

        _sql = String.Format("DROP TABLE ##{0}", temporaryTableName);
        _command.CommandText = _sql;
        _command.ExecuteNonQuery();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
        throw;
    }
}

Some other code that I use

This article uses components from my other article. It basically adds a label for every control you put on a form. In my humble opinion, a great tool if you do not enjoy placing labels every time you use controls. It does some boring tasks automagically :-) So if you have some spare time, read this article, vote it, leave a comment. Feedback is greatly appreciated.

Points of interest

Well, I surely learned one thing: do not believe blindly in documentation :-). I spent some time trying to fix that 1 byte error :) (I think that SQL Server does some CRC calculations). Also, what I like about this solution is that it saves me time. No backup and copy, just press a button.

History

  • 7 March 2009 - First version.

License

This article, along with any associated source code and files, is licensed under The BSD License

About the Author

Adrian Pasik

Software Developer
AP-System
Poland Poland

Member

Born in Kielce, Poland in 1983
 
Back in the days i programmed C++ projects for pure fun. In 2000 i was administrator of one network with 128 kbit bandwidth to the internet. Since we couldnt afford Microsoft NT software for our server i dived into the fantastic world of FLOSS. I started interesting in security of computing and of course did some reasearch in that field. Later on i did few commercial projects in PHP and MySQL. Soon after that i drop MySQL in order to store my data in PostgreSQL. Did also few exec's of OpenGL + GLSL to know what my graphics card is capable of. Started C#( and generally went back to Windows programming) in early 2007 and feeling this trend will last for some time...
 
Since begining of 2010 i work as an independent contractor.
 
I specialize at C#, mostly in Win Forms. I have some knowledge of ASP.NET but so far i didnt have some exciting project in this technology.
 
Speaking of 3rd party components I have been with DevExpress and I recommend it, except of the productivity plugins which are in my humble opinion not so good, but You will find people that like them.
If You want good productivity plugin, use Jetbrains ReSharper.
 
I'm fan of TDD since early 2011.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
Questionfile larger than the ram Pinmembertymczasowy332:49 28 Dec '11  
GeneralMy vote of 5 PinmemberIman Abidi5:10 30 Nov '11  
QuestionVery Innovative Pinmemberafsharm9:29 15 Oct '11  
GeneralMy vote of 5 Pinmembermammadkoma23:40 15 Oct '10  
GeneralThat's great but... Pinmembertongngocoanh22:11 19 Sep '10  
GeneralSQL 2000 Server Pinmemberpask9:46 13 May '09  
GeneralRe: SQL 2000 Server PinmemberAdrian Pasik9:59 13 May '09  
GeneralRe: SQL 2000 Server Pinmemberpask8:21 16 May '09  
Generalvery smart approach... PinmemberMember 68435322:03 1 May '09  
Questionerror message: you do not have permission to use the bulk load statement PinmemberMember 475805515:31 15 Apr '09  
AnswerRe: error message: you do not have permission to use the bulk load statement PinmemberAdrian Pasik18:26 15 Apr '09  
GeneralRe: error message: you do not have permission to use the bulk load statement PinmemberMember 475805518:34 15 Apr '09  
GeneralBrawo PinmemberPL0123:53 26 Mar '09  
QuestionWhat about restore DB PinmemberFalcon4:56 20 Mar '09  
AnswerRe: What about restore DB PinmemberAdrian Pasik5:10 20 Mar '09  
GeneralRe: What about restore DB PinmemberFalcon23:37 22 Mar '09  
GeneralRe: What about restore DB PinmemberAdrian Pasik5:40 23 Mar '09  
AnswerRe: What about restore DB PinmemberA7mad_23:27 2 Mar '12  
GeneralJIT PinmemberJamal Alqabandi17:16 16 Mar '09  
GeneralNeat idea PinmemberAWdrius23:25 12 Mar '09  
GeneralWithout temp table Pinmembermkornreder5:25 12 Mar '09  
Hello,
very good article.
I modified it a little bit so that I don't need a temp table.
It is possible to comment out all sql-statements for the temp table
and to use following statement:
_sql = String.Format("SELECT bck.* FROM OPENROWSET(BULK '{0}\\{1}.bak',SINGLE_BLOB) bck", AremoteTempPath, _dbname);
I also had to change the following line
backupFromServer = (byte[])dr["bck"];
to
backupFromServer = (byte[])dr[0];
Thats's it.
Have a look on it.
GeneralRe: Without temp table PinmemberAdrian Pasik5:18 13 Mar '09  
GeneralBackup size limit Pinmemberalexruf22:36 8 Mar '09  
GeneralRe: Backup size limit PinmemberAdrian Pasik23:37 8 Mar '09  
GeneralRe: Backup size limit PinmemberRNOVAK8:13 16 Aug '11  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120529.1 | Last Updated 8 Mar 2009
Article Copyright 2009 by Adrian Pasik
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid