|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionUpdated 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. Add-in implementationThe 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. AutomationThe automation actions are implemented in the // 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. Notes
ZipDirectoryHierarchySince there are usually other non-project files somewhere in the solution, it is preferable to simply zip up the entire solution directory hierarchy. The 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);
}
}
}
CompatibilityI have only been able to test SolutionZipper on a limited number of environments and project types.
ConclusionPlease 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! Revision history
| ||||||||||||||||||||