GBackupSolution - Visual Studio Extension VSX 2008/2010
Visual Studio 2008/2010 Extension for backing-up your solution to Gmail.
- Download GBackupSolution_VS2008_src.zip - 436.99 KB
- Download GBackupSolution_VS2010_src.zip - 461.64 KB
- Download GBackupSolution-2008and2010_Setup.zip - 914 KB
Contents
Introduction
When searching a way to backup my source codes, I found a great application at CodeProject to do this kind of job:
- Backup2Gmail: Backup Project Files to Gmail
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:
- A menu tool has 2 sub-menus with shortcut.
- Save all working file prior to backup.
- Just backup only (locally) or send to mail.
- Prompt for backup file name or result.
- 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.
- 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.
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 :
- If you wanna use Visual Studio 2010 Beta 2:
- If you wanna use Visual Studio 2010 RC (RC means Release Candidate):
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:
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 ):
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:
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:
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:
- For each project in your solution.
- 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:
- Get all source code files using RegEx.
- Get all included contents/resources using RegEx.
- 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:
- Now, just add the project file:
foreach (Project project in applicationObject.Solution.Projects)
// Read all content of project file.
var reader = new StreamReader(strProjectPath);
var projContent = reader.ReadToEnd();
// For source code files
var r = new Regex("
r = new Regex("
// 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");
}
}
// 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:
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.
- + Rewrite
- Initial Draft: Mar 14, 2010 - Version 1.0