Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

GBackupSolution - Visual Studio Extension VSX 2008/2010

0.00/5 (No votes)
17 Mar 2010 7  
Visual Studio 2008/2010 Extension for backing-up your solution to Gmail.

GbackupSolution015.jpg

Contents

Introduction

When searching a way to backup my source codes, I found a great application at CodeProject to do this kind of job:

I love the idea of an automated, daily, off-site backup and then send the backup file to Gmail for storing with their amazing services. But I think it’s dangerous to backup your source codes to Gmail without password, and it will save out times more than if this tool were written as an extension of Visual Studio so that we can backup anytime we want. Therefore, I began to find an add-in which can do the same but the pity is that there’s no add-in like that. BTW, I found some great projects (about Visual Studio Add-ins) in backing-up your solution to store locally:

  • ProjectZip 1.6: Zip up the source code for your latest CodeProject article.
  • ZipStudio: Zip up a Visual Studio solution directly from the Visual Studio Solution Explorer window.
  • SolutionZipper: VS 2005 Add-in Cleans and Zips a Solution in One Step.

In the other hand, one of them does not have some options or features that I usually use for my job so I decided to make a new one. And I also show you how to create an add-in for Visual Studio step-by-step because this is the first time I wrote an Visual studio extension so I want to share with you guys all experiences that I have when creating this add-in. Who know if you guys can make some amazing extension that help our programmer life be better :).

Features

If you guys used to use/are using DPack extension , you must love the way it does to backup your solution/project. It just takes enough files in your source code tree (not include /bin, /obj folder… or files that are not related to your solution/project). But I want it can do more than that, like password protection for the zip file, or support external projects/references with remapping path (solution directory relative) so that you can open the backup solution file and Press F5, it will run without any errors. And yes, it also supports to send the backup file to Gmail or other assigned email for storing purpose. . If you need/want these functions like me, GBackupSolution will be the best choice for you, trust me, you will love it immediately! It’ll help you to save your times, simplify your task just by a single click. And more important, it not only supports Visual Studio 2010 but also Visual Studio 2005/2008.

Currently, GBackupSolution has these following features:

  1. A menu tool has 2 sub-menus with shortcut.
  2. Save all working file prior to backup.
  3. Just backup only (locally) or send to mail.
  4. Prompt for backup file name or result.
  5. Backup features:
    • Just backup enough file structure (source codes, included content, references).
    • Supports file name & backup path with format macros.
    • Supports password protection & compression level.
    • Supports external references. 
    • Supports backup your setting/user files.
    • The zip file has a perfect directory hierarchy.
  6. Send mail features:
    • Supports send backup file to Gmail.
    • Supports send to other email (with owner in CC).
    • Supports subject with format macros.
    • Very details in HTML message with all information.

Screenshot - HTML message with full information.

And now, please download and enjoy this add-in and I will show you how to create a same one.

Prerequisite

You can use both of Visual Studio 2010 editions (Beta 2 or RC) to follow this article. That mean you need a Visual Studio 2010 and its related SDK before starting. You can download them at :

OK, just install and let's go!

Using the code

Create a basic add-in

If this is the first time you make an extension ( add-in ) like me before, then there are some articles I recommend you need to read first: Using VS Add-in Wizard to create a basic add-in (See: How to: Create an Add-in ). And how to make a tool menu with sub-menus for your extension at: Developing a Visual Studio Add-in to enforce company’s standard modification history format.

After that, try to create a same one with two articles above, your code will look like this:

public class Connect : IDTExtensibility2, IDTCommandTarget
{
	CommandBar cmbGbackup;

	public Connect(){}

	public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
	{
		if(connectMode == ext_ConnectMode.ext_cm_UISetup)
		{
			...

			try
			{
				// Searching if submenu already exists
				for (int iloop = 1; iloop <= toolsPopup.CommandBar.Controls.Count; iloop++)
				{
					if (toolsPopup.CommandBar.Controls[iloop].Caption == "GBackupSolution")
					{
						var op = (CommandBarPopup)toolsPopup.CommandBar.Controls[iloop];
						cmbGbackup = op.CommandBar;
						break;
					}
				}

				//Add a command to the Commands collection:
				Command cmdBackupNow = commands.AddNamedCommand2(_addInInstance, "BackupNow", "Backup solution now", "Press Shift + Alt + G to backup now.", false, 1, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
				Command cmdOptions = commands.AddNamedCommand2(_addInInstance, "Options", "Options...", "Executes the command for Options...", true, 13, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

				if (cmbGbackup == null)
					cmbGbackup = (CommandBar) commands.AddCommandBar("GBackupSolution", vsCommandBarType.vsCommandBarTypeMenu, toolsPopup.CommandBar, 1);

				//Add a control for the command to the tools menu:
				if ((cmdOptions != null) && (toolsPopup != null))
				{
					cmdOptions.AddControl(cmbGbackup, 1);
					// a default keybinding specified
					var bindings = (object[])cmdOptions.Bindings;
					if (0 >= bindings.Length)
					{
						// there is no preexisting key binding, so add the default
						bindings = new object[1];
						bindings[0] = "Global::SHIFT+ALT+O";
						cmdOptions.Bindings = bindings;
					}
				}

				//Add a control for the command to the tools menu:
				if ((cmdBackupNow != null) && (toolsPopup != null))
				{
					cmdBackupNow.AddControl(cmbGbackup, 1);

					var bindings = (object[])cmdBackupNow.Bindings;
					if (0 >= bindings.Length)
					{
					   bindings = new object[1];
						bindings[0] = "Global::SHIFT+ALT+G";
						cmdBackupNow.Bindings = bindings;
					}
				}                    
			}
			catch(ArgumentException){...}
		}
	}

	public void OnDisconnection(){...}
	public void QueryStatus(){...}

	public void Exec(...)
	{
		handled = false;
		if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
		{
			if (commandName == "GBackupSolution.Connect.BackupNow") {...}

			if (commandName == "GBackupSolution.Connect.Options"){...}
		}
	}    
}
	

Press F5 to run this Add-in, your menu will appear like:

Screenshot - Tool menu with sub-menus

Create a setting class to store your settings

The next thing to do is that we need a class so that we can Save/Load out setting on Visual Studio like your Gmail username/password or the password of backup file ... Let create a class with structure like this ( I called it ClsSetting class ):

Screenshot - Setting Class Diagram

This class can load or save all its properties to/from a string using XML:

public String SaveToString()
{
	var ser = new XmlSerializer(typeof(ClsSetting));
	var writer = new MemoryStream();
	ser.Serialize(writer, this);
	return Convert.ToBase64String(writer.GetBuffer());
}

public static ClsSetting LoadFromString(String s)
{
	byte[] data = Convert.FromBase64String(s);
	var ser = new XmlSerializer(typeof(ClsSetting));
	var reader = new MemoryStream(data);

	// The first time using add-in so load the default settings
	var settings = ser.Deserialize(reader) as ClsSetting ?? LoadDefault();
	
	return settings;
}

public static ClsSetting LoadDefault()
{
	return new ClsSetting
	{
		GmailSubject = "[$N] - $Date",
		BackupPromptFileName = true,
		BackupPath = @"$P\Backup",
		BackupName = @"$N_$Date.zip",
		BackupCompresionLvl = 6,
		OtherSaveAll = true,
		OtherBackupLocally = true,
		OtherPromptDone = true
	};
}

A question here that how we can persist these setting along Visual Studio configuration ? I will explain it in next step :).

Create options page form 

Now add a new windows form to your project (Right click on your Project -> Add -> Windows Form and put a name like FrmOption.cs). Then create an interface like this:

Screenshot - FrmOption Design

And here it how we save/load the setting, we use DTE2.Global properties to store our setting here:

		
private DTE2 applicationObject;
private ClsSetting settings;

public FrmOption(DTE2 passedDTE)
{
	InitializeComponent();

	applicationObject = passedDTE;

	settings = applicationObject.Globals.get_VariableExists("GBackupSolutionData") ? ClsSetting.LoadFromString(applicationObject.Globals["GBackupSolutionData"] as string) : ClsSetting.LoadDefault();
}

Note that: We send an instance of Visual Studio by DTE2 passedDTE parameter. In the connect.cs file, this is how we call this Option form:

		
if (commandName == "GBackupSolution.Connect.Options")
{
	var frmOption = new FrmOption(_applicationObject);
	frmOption.ShowDialog();
	handled = true;
	return;
}

When we click Save button, this will call the function to save out current settings to global configuration of Visual Studio:

		
private void btnSave_Click(object sender, EventArgs e)
{
	settings.GmailUsername = txtGmailUser.Text;
	settings.GmailPassword = txtGmailPassword.Text;
	settings.GmailSubject = txtGmailSubject.Text;
	...

	// Save data to global config
	applicationObject.Globals["GBackupSolutionData"] = settings.SaveToString();
	applicationObject.Globals.set_VariablePersists("GBackupSolutionData", true);

}

Now, we've done in creating an Option Page form and know how to save/load our own settings form Visual Studio Global Configuration. Next step we will dig into our primary goal.

Create class for backup purpose

Add a new class to your project (Right click on your Project -> Add -> Class and put a name like ClsBackup.cs). Please take a look at the ClsBackup diagram here:

Screenshot - ClsBackup Diagram

There are there primary functions in this class: GetAllItems(), DoZip(), SendToGmail()

		
private void DoBackUp()
{
	// Step1 : Get all items.
	if (GetAllItems())
	{
		// Step2 : Zip all items.
		if (DoZip())
		{
			// Step3 : Send to Gmail.
			if (!settings.OtherBackupLocally)
				SendToGmail();
			else if (settings.OtherPromptDone)
			{
				applicationObject.StatusBar.Text = "Backup solution to file successfully!";
				MessageBox.Show("Backup solution to file successfully!", "Successfully!", MessageBoxButtons.OK,
								MessageBoxIcon.Information);
			}
		}
		else
		{
			applicationObject.StatusBar.Text = "FAILED to backup solution to file!";
			MessageBox.Show("FAILED to backup solution to file!", "FAILED!", MessageBoxButtons.OK,
							MessageBoxIcon.Information);
		}
	}            
}

And the ItemFile class is presented for a file in your solution. Each ItemFile has a GetDetailInfo() function to calculate total lines, character and some infos of a file:

		
private void GetDetailInfo()
{
	// Step1: Count lines & chars
	using(var reader = File.OpenText(Path))
	{
		LineCount = 0;
		CharCount = 0;
		string line = reader.ReadLine();
		while (line != null)
		{
			LineCount++;
			CharCount += line.Length;
			line = reader.ReadLine();
		}
		reader.Close();
	}
	
	// Step2: Get Size & time modified
	var fileInfo = new FileInfo(Path);
	Size = fileInfo.Length;
	Time = fileInfo.LastWriteTime;
}

Getting all item files of a solution

The idea to get all files of a solution (includes files of external project, external content or external references) is:

  1. For each project in your solution.
  2. 				
    			foreach (Project project in applicationObject.Solution.Projects)
    			

  3. If this project is external

    				
    bool bIsExternal = false;
    bool bIsBackup = false;
    
    var strProjectPath = "";
    var strProjectFolder = "";
    
    if (project.FileName == project.UniqueName || project.FullName == project.UniqueName)
    {
    	bIsExternal = true;
    	...
    }
    else
    	...
    

    • We need to change the absolute path to relative path in solution file (.sln) and mark this as NeedToReplace. To do this, we make a copy of solution file ( .bk file) and modify the content by :
    • 					
      if (bIsExternal && !NeedToReplace)
      {
      	// Make a new copy of Solution file
      	if (!File.Exists(SolutionFileBackup))
      		File.Copy(SolutionFile, SolutionFileBackup);
      	
      	// Change to project path to new one
      	var rd = new StreamReader(SolutionFileBackup);
      	var slnContent = rd.ReadToEnd();
      	rd.Close();
      	rd.Dispose();
      
      	File.Delete(SolutionFileBackup);
      
      	slnContent = slnContent.Replace(strProjectPath.Substring(0,
      		Directory.GetParent(strProjectPath).Parent.FullName.Length + 1), "");
      
      	var writer = new StreamWriter(SolutionFileBackup);
      	writer.Write(slnContent);
      	writer.Close();
      	writer.Dispose();
      
      	// Mark this solution file need to be replaced
      	NeedToReplace = true;
      }
      

    • Example:

      GbackupSolution004.jpg

  4. Get all source code files using RegEx.
  5. // Read all content of project file. 
    var reader = new StreamReader(strProjectPath);
    var projContent = reader.ReadToEnd();
    
    // For source code files
    var r = new Regex("strProjectFolder;" item="File.Exists(ma.Groups[1].Value)" ms="r.Matches(projContent);" include="\"(.*?)\"",">
    

  6. Get all included contents/resources using RegEx.
  7. 	
    r = new Regex("strProjectFolder;" item="File.Exists(ma.Groups[1].Value)" ms="r.Matches(projContent);" include="\"(.*?)\""," />

  8. Get all external references using RegEx. Note that: We also need to remap the absolute path to relative path in project file, so we need to modify it like what we did with solution file:
  9. 	
    // We don't get details info of refs files.
    if (settings.BackupIncludeRef)
    {
    	r = new Regex("(.*?)", RegexOptions.Singleline);
    	ms = r.Matches(projContent);
    
    	if (ms.Count > 0)
    	{
    		var newContent = projContent;
    
    	foreach (Match ma in ms)
    	{
    		ItemFile item = File.Exists(ma.Groups[1].Value) ? new ItemFile(ma.Groups[1].Value) : new ItemFile(Path.Combine(Path.GetDirectoryName(project.FullName), ma.Groups[1].Value));
    
    			item.ProjectName = strProjectFolder;
    			item.HieraryPath = "References";
    			item.LineCount = item.CharCount = 0;
    
    			// Remap the file path of references files in project file
    			newContent = newContent.Replace(ma.Groups[1].Value, Path.Combine("References", Path.GetFileName(ma.Groups[1].Value)));
    			ItemList.Add(item);
    	}
    
    		if (!File.Exists(strProjectPath + ".bk"))
    			File.Delete(strProjectPath + ".bk");
    
    		var writer = new StreamWriter(strProjectPath + ".bk");
    		writer.Write(newContent);
    		writer.Close();
    		writer.Dispose();
    
    		bIsBackup = true;
    		DeleteList.Add(strProjectPath + ".bk");
    	}
    
    }
    

    GbackupSolution004.jpg

  10. Now, just add the project file:
  11. 	
    // Add *.csproj to Item list.
    var projectItem = new ItemFile(strProjectPath) { ProjectName = strProjectFolder, IsBackup = bIsBackup };
    if (BuildHierarchialStruture(projectItem, strProjectPath))
    	ItemList.Add(projectItem);
    

Then we've done in getting all files in a solution, supports external projects, resources or references. One more point here that why we need to call BuildHierarchialStruture function before add the item into list? Because we want the zip file have a perfect directory hierarchy after backup and we need to remap some external file to right place in the backup file so that we can run solution without any errors. The struture of zip file will be like this:

GbackupSolution013.jpg

Compress all to the backup file.

Once you have all all files in your solution, now we just simply add all of them into a zip file. After researching, I decided to use the DotNetZip library here because its power.

		
var zip = new ZipFile(Encoding.UTF8) { Password = settings.BackupPass, Encryption = EncryptionAlgorithm.PkzipWeak, CompressionLevel = (CompressionLevel)settings.BackupCompresionLvl };

foreach (var itemFile in ItemList)
{
	// TO: We need to build a hierarchial structure
	var path = Path.Combine(Path.Combine(SolutionName, itemFile.ProjectName), itemFile.HieraryPath);

	if (itemFile.IsBackup)
	{
		zip.AddFile(itemFile.Path + ".bk", path).FileName = Path.Combine(path, Path.GetFileName(itemFile.Path));
	}
	else
	zip.AddFile(itemFile.Path, path);

	TotalLine += itemFile.LineCount;
	TotalChar += itemFile.CharCount;
	UncompressSize += itemFile.Size;
}

if(settings.BackupIncludeUserStt)
{
	var suoFile = Path.Combine(SolutionPath, SolutionName + ".suo");
	if(Directory.Exists(suoFile))
		 zip.AddFile(suoFile, SolutionName); // *.suo file

	var userFile = Path.Combine(SolutionPath, SolutionName + ".user");
	if (Directory.Exists(userFile))
		zip.AddFile(userFile, SolutionName); // *.user file
}

Then, if we have external project (NeedToReplace is true) or external references, we need to delete these backup file.

		
if (NeedToReplace)
{
	File.Delete(SolutionFileBackup);
	NeedToReplace = false;
}

foreach (var list in DeleteList)
	File.Delete(list);
	

Hey guy, now if you just only need to backup your solution to file, you're done! If you need send this backup file to GMail, just simply call the SendToGmail function. This function is so simple to read so I'm not putting it here, please see the attached for detail

The end.

Once you've done in writting ClsBackup class, now we just call it if the command "Backup Now" is pressed, we add these code to connect.cs file:

		
if (commandName == "GBackupSolution.Connect.BackupNow")
{
	if (!_applicationObject.Solution.IsOpen)
	{
		MessageBox.Show("Please load your solution first!", "Solution is unloaded!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		handled = true;
		return;
	}
	var backUp = new ClsBackup(_applicationObject);

	...
  
	// Start to backup
	backUp.StartToBackup();                    

	handled = true;
	return;
}
	

Download and Installation

The add-in can be installed in following ways:

  • Build and install: Build the attached solution and copy GBackupSolution.dll in bin folder and GBackupSolution.AddIn in GBackupSolution folder to %USERPROFILE%/Visual Studio 2010/Addins/. The same for Visual Studio 2008 path.
  • Run the attached installer, this will setup the add-in for both Visual Studio 2008 and 2010.
  • Download from VS Gallery: Download the add-in from GBackupSolution URL and double click the msi file downloaded.

Summary

This is the first time I post an article to CodeProject as well as write an add-in for Visual Studio so can not avoid all my careless mistake so please let me know what I need to be improved and I'm sorry for any inconvenience.

Please provide your feedback and your suggestion to improve this add-in and also don't forget vote my add-in if if it helps to save your time. Thanks!.

History

  • Version 1.2: What need to do ? Any suggestion?
  • Version 1.1: Mar 17, 2010
    • + Rewrite GetAllItems() function, now more clearly, more readable.
    • + Add AddItemWithHierarchialStructure() to add an item file into list and build the Hierarchy Path also.
    • + Add Gmail icon for Opntions page.
    • ~ Fix a major bug :
      • + the version 1.0 doesn't take the *.resx files.
      • + Get all resources in a *.resx file.
    • ~ Fix a spelling error : "Promt" to "Prompt" in Options page dialog.
    • ~ Fix : use 24 hours format in $Date macro.
    • ~ Fix : fixed size for Options page dialog.
    • ~ Fix : blank password cause unziper ask for the password (empty).
    • - Remove BuildHierarchialStruture() function.
  • Initial Draft: Mar 14, 2010 - Version 1.0

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here