![]() |
General Programming »
Macros and Add-ins »
VS.NET Addins
Beginner
License: The Code Project Open License (CPOL)
SolutionZipper: VS 2005 Add-in Cleans and Zips a Solution in One StepBy Robert NadlerA convenient tool for quick solution back-ups. |
C# 2.0, Windows, .NET 2.0VS2005, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Updated to v1.3 on 31-July-2007: See updated source, notes in article and Revision history for details. There are already a couple of good Visual Studio solution and project zipping tools available:
The code described in this article provides a simplified version of these add-ins. Specifically, SolutionZipper has a single-click-does-everything interface, which is what automation is all about: saving time. There are no dialogs that present file lists, file names to be chosen or user options to select. SolutionZipper does the following in a single step:
When these operations are completed, a confirmation dialog is displayed:
The ZIP file can be used to share projects and is in the proper form for CodeProject submissions (of course). If you are not using a source code control system, SolutionZipper is useful for quickly backing up a snapshot of a solution at major development milestones.
The standard VS Add-in Wizard was used to create the project. See How to: Create an Add-in for details. In order to get a custom icon on the menu item,
follow the instructions in How to: Display a Custom Icon on the Add-in Button carefully. Also note that the icon must be exactly 16x16 pixels to be displayed. ICSharpCode.SharpZipLib.dll is used for the ZIP implementation and can be downloaded here.
The automation actions are implemented in the IDTExtensibility2.Exec function.
// Updated: v1.3
public void Exec(string commandName,
vsCommandExecOption executeOption,
ref object varIn, ref object varOut, ref bool handled)
{
handled = false;
if(executeOption ==
vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if(commandName ==
"SolutionZipper.Connect.SolutionZipper")
{
const string ADDIN_NAME = "SolutionZipper";
_Solution sol = _applicationObject.Solution;
handled = true;
if (string.IsNullOrEmpty(sol.FullName)) return;
// no solution loaded
string solpath = Path.GetDirectoryName(sol.FullName);
// solution path
// If there are external projects, they will be copied here to
// be included in the zip file and then deleted.
string extDir = null;
// Status bar
EnvDTE.StatusBar statusbar = _applicationObject.StatusBar;
// Do a Save All
//
statusbar.Text = ADDIN_NAME + ": Save All Files...";
_applicationObject.ExecuteCommand("File.SaveAll", "");
// Clean all configurations
//
statusbar.Text = ADDIN_NAME + ": Cleaning all configurations...";
SolutionBuild2 sb = (SolutionBuild2)sol.SolutionBuild;
// Remember current configuration
SolutionConfiguration curr_conf = sb.ActiveConfiguration;
foreach (SolutionConfiguration s in sb.SolutionConfigurations)
{
s.Activate();
sb.Clean(true); // Clean the solution.
}
// Restore previously active config
curr_conf.Activate();
// Clean deployment projects.
//
statusbar.Text = ADDIN_NAME + ": Handle deployment projects...";
foreach (Project p in sol.Projects)
{
try
{
if (p.UniqueName.IndexOf(".vdproj",
0, StringComparison.CurrentCultureIgnoreCase) != -1)
{
string vddir = Path.GetDirectoryName(p.FullName);
// Remove contents of the Release and
// Debug directories
DeleteDirContents(Path.Combine(vddir, "Release"));
DeleteDirContents(Path.Combine(vddir, "Debug"));
}
}
catch (NullReferenceException)
{
// A Web Deployment project will throw this
// exception when UniqueName is accessed.
// Name is the only valid property: See if the
// project is in the solution dir.
// Delete product if it is.
string wdprojfile =
Path.Combine(Path.Combine(solpath, p.Name),
p.Name + ".wdproj");
if (File.Exists(wdprojfile))
{
string wddir = Path.Combine(solpath, p.Name);
// Remove contents of the Release and
// Debug directories
DeleteDirContents(Path.Combine(wddir, "Release"));
DeleteDirContents(Path.Combine(wddir, "Debug"));
}
}
catch (Exception ex)
{
// Just in case some other type of exception occurs!!
MessageBox.Show(string.Format(
"An Error has occurred:\n{0}\n\nZip " +
"file not created.",
ex.Message), ADDIN_NAME,
MessageBoxButtons.OK, MessageBoxIcon.Error);
statusbar.Clear();
return;
}
}
// Copy external projects to solution dir
//
ExtProjectMap fmap = null;
statusbar.Text = ADDIN_NAME + ": Handle external projects...";
foreach (Project p in sol.Projects)
{
// Ignore Web deployment projects!
try
{
string uname = p.UniqueName;
}
catch (NullReferenceException)
{
continue;
}
if (!string.IsNullOrEmpty(p.FileName)
&& Path.IsPathRooted(p.FileName) &&
p.FileName.IndexOf(solpath) == -1)
{
// This project is not in the solution directory
if (extDir == null)
{
// Make the external projects dir
extDir = Path.Combine(solpath, "szExternalProjects");
if (!Directory.Exists(extDir))
{
try
{
Directory.CreateDirectory(extDir);
}
catch (Exception ex)
{
MessageBox.Show(string.Format(
"Failed to create temporary " +
"directory:\n{0}\nError: " +
"{1}\n\nZip file not created.",
extDir, ex.Message), ADDIN_NAME,
MessageBoxButtons.OK,
MessageBoxIcon.Error);
statusbar.Clear();
return;
}
}
fmap = new ExtProjectMap(extDir);
}
// Copy this project to solution temporary directory
string srcPath = Path.GetDirectoryName(p.FullName);
string dstPath =
Path.Combine(extDir, Path.GetFileName(srcPath));
try
{
xDirectory.Copy(srcPath, dstPath);
}
catch (Exception ex)
{
MessageBox.Show(string.Format(
"Failed to copy:\n\nSource: " +
"{0}\nDestination: {1}\n\nError: {2}\n\nZip " +
"file not created.",
srcPath, dstPath, ex.Message),
ADDIN_NAME, MessageBoxButtons.OK,
MessageBoxIcon.Error);
DeleteDirContents(extDir);
statusbar.Clear();
return;
}
fmap.Add(Path.GetFileName(srcPath) +
"\\" + Path.GetFileName(p.UniqueName),srcPath);
}
}
// Create zip file paths and name
//
statusbar.Text = ADDIN_NAME + ": Creating Zip file...";
DateTime dt = DateTime.Now;
string sname = Path.GetFileName(sol.FileName);
// SolutionName_YYYYMMDD-HHMMSS.zip
string zipname =
string.Format("{0}_{1:D2}{2:D2}{3:D2}-{4:D2}{5:D2}{6:D2}.zip",
sname.Substring(0, sname.Length - 4),
dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
// Save zip file in the parent directory, not
// in the solution directory
string zippath =
Path.Combine(Path.GetDirectoryName(solpath),zipname);
ZipDirectoryHierarchy zs = new ZipDirectoryHierarchy();
zs.ZipIt(solpath, zippath);
// Clean up for projects that were copied to the temporary dir
//
if (extDir != null)
{
statusbar.Text = ADDIN_NAME + ": Project clean-up...";
DeleteDirContents(extDir);
}
statusbar.Clear();
MessageBox.Show(string.Format(
"Successfully created:\n\n{0}\n\n{1}",
zippath,zs.StatusString),
ADDIN_NAME, MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
}
}
This code is pretty self-explanatory.
Text property, EnvDTE.StatusBar also has a Progress method that allows control of the VS progress bar _DTE.ExecuteCommand method allows you to execute any VS command or macro. The command name can be found in the Tools>Options>Environment>Keyboard dialog, which has a nice search feature:
".vdproj" in the UniqueName property. The contents of the Release and Debug directories are then deleted. szExternalProjects. After the ZIP file is created, the temporary directory is removed. See Revision history for more details. DeleteDirContents method now removes the entire directory tree recursively, including the directory itself. Project instance they create. ExtProjectMap class creates a ExternalProjectsMap.txt file and adds an entry for each external project added to the temporary directory. This documentation allows for easier reconstruction of the solution from the extracted ZIP. Here is an example:
SolutionZipper: External projects in this directory.
Project : Original Path
---------------------- : -------------
extProject\extProject.csproj: C:\Projects\SolutionZipper\
extProject\extProject
extSetup\extSetup.vdproj : C:\Projects\SolutionZipper\
extProject\extSetup
IDTExtensibility2.QueryStatus function was modified so that the SolutionZipper Tools menu item would only be available when a saved solution is loaded into the IDE.
public void QueryStatus(string commandName,
vsCommandStatusTextWanted neededText,
ref vsCommandStatus status, ref object commandText)
{
if(neededText ==
vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
{
if(commandName == "SolutionZipper.Connect.SolutionZipper")
{
status = (vsCommandStatus
)vsCommandStatus.vsCommandStatusSupported;
if (!string.IsNullOrEmpty(
_applicationObject.Solution.FullName))
status |= vsCommandStatus.vsCommandStatusEnabled;
return;
}
}
}
Since there are usually other non-project files somewhere in the solution, it is preferable to simply zip up the entire solution directory hierarchy. The ZipDirectoryHierarchy class uses the SharpZipLib to do this. The path entries in the ZIP file will be relative, i.e. not begin with \, and will start at the solution directory.
internal class ZipDirectoryHierarchy
{
// Keep track of dir/file/byte counts
private int DirCount = 0;
private int FileCount = 0;
private long ByteCount = 0;
// Add a single file to the zip output
private void ZipOne(ZipOutputStream s,
Crc32 crc, string basedir, string file)
{
if (Path.GetExtension(file) == ".ncb")
return; // ignore VC++ Intellisense database files
FileInfo fi = new FileInfo(file);
if ((fi.Attributes & FileAttributes.Hidden) != 0)
return; // ignore hidden files
FileStream fs = File.OpenRead(file);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
String relpath = file.Substring(basedir.Length + 1);
// make path relative
ZipEntry entry = new ZipEntry(relpath);
entry.DateTime = DateTime.Now;
entry.Size = fs.Length;
this.FileCount++;
this.ByteCount += entry.Size;
fs.Close();
crc.Reset();
crc.Update(buffer);
entry.Crc = crc.Value;
s.PutNextEntry(entry);
s.Write(buffer, 0, buffer.Length);
}
// Recursive directory zipping
private void ZipDirectory(ZipOutputStream s,
Crc32 crc, string basedir, string dirpath)
{
// All of the files
string[] filenames = Directory.GetFiles(dirpath);
if (filenames.Length > 0)
{
this.DirCount++;
// only count directories with files in them
foreach (string file in filenames)
{
ZipOne(s, crc, basedir, file);
}
}
// All of the directories
string[] dirs = Directory.GetDirectories(dirpath);
foreach (string dir in dirs)
{
DirectoryInfo di = new DirectoryInfo(dir);
if ((di.Attributes & FileAttributes.Hidden) == 0)
// ignore hidden directories
ZipDirectory(s, crc, basedir, dir);
}
}
//------------------------------------------------------------
// Public methods
// Zip method
public void ZipIt(string dirpath, string zipname)
{
Crc32 crc = new Crc32();
ZipOutputStream s = new ZipOutputStream(File.Create(zipname));
s.SetLevel(6); // 0 - store only to 9 - means best compression
// Get the base path, which is the parent of the directory being
// zipped (dirpath).
// All zip file paths will be relative to this.
string basepath = Path.GetDirectoryName(dirpath);
ZipDirectory(s, crc, basepath , dirpath);
s.Finish();
s.Close();
}
// Get the status string
public string StatusString
{
get
{
return string.Format(
"{0,-6}\tKB " +
"(uncompressed)\n{1,-6}\tFiles\n{2,-6}\tDirectories",
ByteCount / 1024, FileCount, DirCount);
}
}
}
I have only been able to test SolutionZipper on a limited number of environments and project types.
Please let me know if you have problems with SolutionZipper. I will provide bug fix updates as necessary and will consider including suggested enhancements in the future. Enjoy!
DeleteDirContents that was causing SolutionZipper to fail if the directory did not exist. Name property in the Project instance, so it is impossible to know the actual project directory. What's interesting is that other Project properties, like UniqueName, contain instances of an Exception. I've used this to keep SolutionZipper from failing when this project type is encountered. DeleteDirContents method now removes the entire directory tree recursively, including the directory itself.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 3 Aug 2007 Editor: Genevieve Sovereign |
Copyright 2006 by Robert Nadler Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |