How to create zip archive contains files with certain extensions only - c#

I need to create zip archive which should contain files with certain extensions only, but I need to save the structure of the original directory.
For example, I have a directory with the following structure:
dir\
sub_dir1\
1.exe
sub_dir_2\
1.txt
1.exe
1.txt
1.bat
and I need to get an archive with the following structure (only .exe and .bat files):
dir\
sub_dir1\
1.exe
sub_dir_2\
1.exe
1.bat
I know how to find these files via Directory.GetFiles method:
var ext = new List<string> {".exe", ".bat"};
var myFiles = Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories)
.Where(s => ext.Any(e => s.EndsWith(e));
but I don't know how to save the archive's structure then.
How can I achieve such behavior?

You can get all the files with extension .exe and .bat from all the sub directories like:
IList<FileInfo> info = null;
DirectoryInfo dirInfo = new DirectoryInfo(path);
info = dirInfo
.GetFiles("*.*", SearchOption.AllDirectories)
.Where( f => f.Extension
.Equals(".exe", StringComparison.CurrentCultureIgnoreCase)
|| f.Extension
.Equals(".bat", StringComparison.CurrentCultureIgnoreCase)
)
.ToList()
;
Then based on this FileInfo list you can create you zip and folder structure.You can find the fileinfo details Here

System.IO.Directory.GetFiles("C:\\temp", "*.exe", SearchOption.AllDirectories); will return an array with the full file paths like:
[C:\temp\dir1\app1.exe]
[C:\temp\dir2\subdir1\app2.exe]
[C:\temp\dir3\subdir2\subdir3\app3.exe]
So you won't have any trouble to put these files in a zip container with ZipArchive.CreateEntry because this method will create the same directory structure in the zip. However, you should remove the C:\ at the beginning.

I believe this very nice tutorial will help you to do that.
If you want to keep empty folder in the target zip, maybe you have to use ZipArchive.CreateEntry method to do. In this demo, the author only use ZipArchive. CreateEntryFromFile method to archive a file from a file path.

Also you can use DotNetZip library for solving your problem. For example the following code snippet can help you. Use CreateZip() method. In brief you should find files for writing to archive GetFileNames() and create zip file using CreateZipFromFileNames():
/// <summary>
/// Create zip archive from root directory with search patterns
/// </summary>
/// <param name="rootDirectory">Root directory</param>
/// <param name="searchPatterns">Search patterns</param>
/// <param name="zipFileName">Zip archive file name</param>
public static void CreateZip(string rootDirectory, List<string> searchPatterns, string zipFileName)
{
var fileNames = GetFileNames(rootDirectory, searchPatterns, true);
CreateZipFromFileNames(rootDirectory, zipFileName, fileNames);
}
/// <summary>
/// Get file names filtered by search patterns
/// </summary>
/// <param name="rootDirectory">Root diirectory</param>
/// <param name="searchPatterns">Search patterns</param>
/// <param name="includeSubdirectories">True if it is included files from subdirectories</param>
/// <returns>List of file names</returns>
private static IEnumerable<string> GetFileNames(string rootDirectory, List<string> searchPatterns, bool includeSubdirectories)
{
var foundFiles = new List<string>();
var directoriesToSearch = new Queue<string>();
directoriesToSearch.Enqueue(rootDirectory);
// Breadth-First Search
while (directoriesToSearch.Count > 0)
{
var path = directoriesToSearch.Dequeue();
foreach (var searchPattern in searchPatterns)
{
foundFiles.AddRange(Directory.EnumerateFiles(path, searchPattern));
}
if (includeSubdirectories)
{
foreach (var subDirectory in Directory.EnumerateDirectories(path))
{
directoriesToSearch.Enqueue(subDirectory);
}
}
}
return foundFiles;
}
/// <summary>
/// Create zip archive from list of file names
/// </summary>
/// <param name="rootDirectroy">Root directory (for saving required structure of directories)</param>
/// <param name="zipFileName">File name of zip archive</param>
/// <param name="fileNames">List of file names</param>
private static void CreateZipFromFileNames(string rootDirectroy, string zipFileName, IEnumerable<string> fileNames)
{
var rootZipPath = Directory.GetParent(rootDirectroy).FullName;
using (var zip = new ZipFile(zipFileName))
{
foreach (var filePath in fileNames)
{
var directoryPathInArchive = Path.GetFullPath(Path.GetDirectoryName(filePath)).Substring(rootZipPath.Length);
zip.AddFile(filePath, directoryPathInArchive);
}
zip.Save();
}
}
Example of use:
CreateZip("dir", new List<string> { "*.exe", "*.bat" }, "myFiles.zip");

What #Didgeridoo said: DotNetZip. But DotNetZip lets you be even more expressive. For instance:
string cwd = Environment.CurrentDirectory ;
try
{
Environment.CurrentDirectory = #"c:\root\of\directory\tree\to\be\zipped" ;
using ( ZipFile zipfile = new ZipFile() )
{
zipfile.AddSelectedFiles( "name = *.bat OR name = *.exe" , true ) ;
zipfile.Save( #"c:\foo\bar\my-archive.zip") ;
}
}
finally
{
Environment.CurrentDirectory = cwd ;
}
Edited To Note: DotNetZip used to live at Codeplex. Codeplex has been shut down. The old archive is still available at Codeplex. It looks like the code has migrated to Github:
https://github.com/DinoChiesa/DotNetZip. Looks to be the original author's repo.
https://github.com/haf/DotNetZip.Semverd. This looks to be the currently maintained version. It's also packaged up an available via Nuget at https://www.nuget.org/packages/DotNetZip/

Related

Extract ZIP file to memory (not disk)

I need to extract a zip file to memory (not to the disk). I cannot save it to a directory, even temporarily.
Is there a way to extract a zip file just to memory, and perform "File" functions there?
I can't open the file as a file stream because this doesn't allow me to read the metadata (last write time, attributes, etc). Some but not all the file attributes can be read from zip entry itself but this is insufficient for my purposes.
I've been using:
using (ZipArchive archive = ZipFile.OpenRead(openFileDialog.FileName)) // Read files from the zip file
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if(entry.Name.EndsWith(".txt", StringComparison.InvariantCultureIgnoreCase)) // get .txt file
{
FileStream fs = entry.Open() as FileStream;
}
}
}
Thanks.
The code below presents a way to get the file into memory as an array of strings, but it is unclear as to what file functions you are asking for. Other commenters have mentioned ExternalAttributes, which is OS dependent therefore it is relevant to have more information as to the problem space.
using System;
using System.IO;
using System.IO.Compression;
namespace StackOverflowSampleCode
{
class Program
{
/// <summary>
/// Validate the extension is correct
/// </summary>
/// <param name="entry"></param>
/// <param name="ext"></param>
/// <returns></returns>
static bool validateExtension(ZipArchiveEntry entry, string ext)
{
return entry.Name.EndsWith(
ext,
StringComparison.InvariantCultureIgnoreCase);
}
/// <summary>
/// Convert the entry into an array of strings
/// </summary>
/// <param name="entry"></param>
/// <returns></returns>
static string[] extractFileStrings(ZipArchiveEntry entry)
{
string[] file;
// Store into MemoryStream
using (var ms = entry.Open() as MemoryStream)
{
// Verify we are at the start of the stream
ms.Seek(0, SeekOrigin.Begin);
// Handle the bytes of the memory stream
// by converting to array of strings
file = ms.ToString().Split(
Environment.NewLine, // OS agnostic
StringSplitOptions.RemoveEmptyEntries);
}
return file;
}
static void Main(string[] args)
{
string fileName = "";
using (ZipArchive archive = ZipFile.OpenRead(fileName))
{
foreach (var entry in archive.Entries)
{
// Limit results to files with ".txt" extension
if (validateExtension(entry, ".txt"))
{
var file = extractFileStrings(entry);
foreach (var line in file)
{
Console.WriteLine(line);
}
Console.WriteLine($"Last Write Time: {entry.LastWriteTime}");
Console.WriteLine($"External Attributes: {entry.ExternalAttributes}");
}
}
}
}
}
}

Copying files and subdirectories to another directory with existing files

I'm quite stuck on this problem for a while now. I need to copy (update) everything from Folder1\directory1 to Updated\directory1 overwriting same files but not deleting files that already exist on Updated\directory1 but does not exist on Folder1\directory1. To make my question clearer, this is my expected results:
C:\Folder1\directory1
subfolder1
subtext1.txt (2KB)
subfolder2
name.txt (2KB)
C:\Updated\directory1
subfolder1
subtext1.txt (1KB)
subtext2.txt (2KB)
Expected Result:
C:\Updated\directory1
subfolder1
subtext1.txt (2KB) <--- updated
subtext2.txt (2KB)
subfolder2 <--- added
name.txt (2KB) <--- added
I'm currently using Directory.Move(source, destination) but I'm having trouble about the destination part since some of it's destination folder is non-existent. My only idea is to use String.Trim to determine if there's additional folders but I can't really use it since the directories are supposed to be dynamic (there can be more subdirectories or more folders). I'm really stuck. Can you recommend some hints or some codes to get my stuff moving? Thanks!
I got this example from msdn http://msdn.microsoft.com/en-us/library/cc148994.aspx I think this is what you looking for
// To copy all the files in one directory to another directory.
// Get the files in the source folder. (To recursively iterate through
// all subfolders under the current directory, see
// "How to: Iterate Through a Directory Tree.")
// Note: Check for target path was performed previously
// in this code example.
if (System.IO.Directory.Exists(sourcePath))
{
string[] files = System.IO.Directory.GetFiles(sourcePath);
// Copy the files and overwrite destination files if they already exist.
foreach (string s in files)
{
// Use static Path methods to extract only the file name from the path.
fileName = System.IO.Path.GetFileName(s);
destFile = System.IO.Path.Combine(targetPath, fileName);
System.IO.File.Copy(s, destFile, true);
}
}
else
{
Console.WriteLine("Source path does not exist!");
}
If you need to deal with non existing folder path you should create a new folder
if (System.IO.Directory.Exists(targetPath){
System.IO.Directory.CreateDirectory(targetPath);
}
Parallel fast copying of all files from a folder to a folder with any level of nesting
Tested on copying 100,000 files
using System.IO;
using System.Linq;
namespace Utilities
{
public static class DirectoryUtilities
{
public static void Copy(string fromFolder, string toFolder, bool overwrite = false)
{
Directory
.EnumerateFiles(fromFolder, "*.*", SearchOption.AllDirectories)
.AsParallel()
.ForAll(from =>
{
var to = from.Replace(fromFolder, toFolder);
// Create directories if required
var toSubFolder = Path.GetDirectoryName(to);
if (!string.IsNullOrWhiteSpace(toSubFolder))
{
Directory.CreateDirectory(toSubFolder);
}
File.Copy(from, to, overwrite);
});
}
}
}
// This can be handled any way you want, I prefer constants
const string STABLE_FOLDER = #"C:\temp\stable\";
const string UPDATE_FOLDER = #"C:\temp\updated\";
// Get our files (recursive and any of them, based on the 2nd param of the Directory.GetFiles() method
string[] originalFiles = Directory.GetFiles(STABLE_FOLDER,"*", SearchOption.AllDirectories);
// Dealing with a string array, so let's use the actionable Array.ForEach() with a anonymous method
Array.ForEach(originalFiles, (originalFileLocation) =>
{
// Get the FileInfo for both of our files
FileInfo originalFile = new FileInfo(originalFileLocation);
FileInfo destFile = new FileInfo(originalFileLocation.Replace(STABLE_FOLDER, UPDATE_FOLDER));
// ^^ We can fill the FileInfo() constructor with files that don't exist...
// ... because we check it here
if (destFile.Exists)
{
// Logic for files that exist applied here; if the original is larger, replace the updated files...
if (originalFile.Length > destFile.Length)
{
originalFile.CopyTo(destFile.FullName, true);
}
}
else // ... otherwise create any missing directories and copy the folder over
{
Directory.CreateDirectory(destFile.DirectoryName); // Does nothing on directories that already exist
originalFile.CopyTo(destFile.FullName,false); // Copy but don't over-write
}
});
This was a quick one-off... no error handling was implemented here.
This will help you it is a generic recursive function so always merged subfolders as well.
/// <summary>
/// Directories the copy.
/// </summary>
/// <param name="sourceDirPath">The source dir path.</param>
/// <param name="destDirName">Name of the destination dir.</param>
/// <param name="isCopySubDirs">if set to <c>true</c> [is copy sub directories].</param>
/// <returns></returns>
public static void DirectoryCopy(string sourceDirPath, string destDirName, bool isCopySubDirs)
{
// Get the subdirectories for the specified directory.
DirectoryInfo directoryInfo = new DirectoryInfo(sourceDirPath);
DirectoryInfo[] directories = directoryInfo.GetDirectories();
if (!directoryInfo.Exists)
{
throw new DirectoryNotFoundException("Source directory does not exist or could not be found: "
+ sourceDirPath);
}
DirectoryInfo parentDirectory = Directory.GetParent(directoryInfo.FullName);
destDirName = System.IO.Path.Combine(parentDirectory.FullName, destDirName);
// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}
// Get the files in the directory and copy them to the new location.
FileInfo[] files = directoryInfo.GetFiles();
foreach (FileInfo file in files)
{
string tempPath = System.IO.Path.Combine(destDirName, file.Name);
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
file.CopyTo(tempPath, false);
}
// If copying subdirectories, copy them and their contents to new location using recursive function.
if (isCopySubDirs)
{
foreach (DirectoryInfo item in directories)
{
string tempPath = System.IO.Path.Combine(destDirName, item.Name);
DirectoryCopy(item.FullName, tempPath, isCopySubDirs);
}
}
}

How to export all the source code from Visual Studio into a text file?

I have a relatively large Visual Studio solution.
I need to export all the source code into a text file. I would also want to include a file name somewhere. How can I do so?
For example if I have a type
namespace MyProject.Core
{
/// <summary>
/// Enumerates possible record status
/// </summary>
public enum RecordType
{
NonWearTime = 0,
WearTime = 1,
NotClassified = 2
}
}
I want this to go to the output.txt file (or any other text format) and appear like so
//***********************************
//Filename: RecordType.cs
//***********************************
namespace MyProject.Core
{
/// <summary>
/// Enumerates possible record status
/// </summary>
public enum RecordType
{
NonWearTime = 0,
WearTime = 1,
NotClassified = 2
}
}
All the other types shall just be appended to the end of the file. I tried Resharper, but its header file options can only contain static text (I tried Filename: $FILENAME$) and the template option only applies to the newly created classes.
Folks, this is a study project, where I have to provide the source code along with a thesis.
This should do the trick
string rootPath = #"path you your root folder";
var header = "***********************************" + Environment.NewLine;
var files = Directory.GetFiles(rootPath, "*.cs", SearchOption.AllDirectories);
var result = files.Select(path => new { Name = Path.GetFileName(path), Contents = File.ReadAllText(path)})
.Select(info =>
header
+ "Filename: " + info.Name + Environment.NewLine
+ header
+ info.Contents);
var singleStr = string.Join(Environment.NewLine, result);
Console.WriteLine ( singleStr );
File.WriteAllText(#"C:\output.txt", singleStr, Encoding.UTF8);
Remarks: if you experience performance or memory inefficiencies, try to use StringBuilder instead and set it's Capacity at the start to the sum of all files contents. This will eliminate lots of redundant strings, created in last Select method.
I would go for a homemade solution.
This helps you get into a String the content of each file.
using System.IO;
...
foreach (string file in Directory.EnumerateFiles(folderPath, "*.cs"))
{
string contents = File.ReadAllText(file);
}
You have the filename, so you can append it at the beginning.
All you still need to do is to parse through all directories.
public void DirSearch(string root)
{
foreach (string file in Directory.EnumerateFiles(root, "*.cs"))
{
string contents = File.ReadAllText(file);
// Write to your outputfile once you've happened what you want in your header.
}
foreach (string d in Directory.GetDirectories(root))
{
DirSearch(d);
}
}
As a none code soloution how about trying a windows command line to merge all files into one.
e.g. copy /b *.cs newfile.txt
https://superuser.com/questions/111825/any-command-line-or-batch-cmd-to-concatenate-multiple-files
Admittedly its quick and dirty, but it might produce what you require with some tailoring
I would write a simple console app to do that. It would search for all files with a *.cs extension, make the necessary modification and then save the file to the desired location. You can loop through directories iteratively using Directory.EnumerateDirectories().

DotNetZip: Add Files to Dynamically Created Archive Directory

I can't imagine this is hard to do, but I haven't been able to get it to work. I have a files class that just stores the location, directory, and name of the files I want to zip. The files I'm zipping exist on disk so the FileLocation is the full path. ZipFileDirectory doesn't exist on disk. If I have two items in my files list,
{ FileLocation = "path/file1.doc", ZipFileDirectory = #"\", FileName = "CustomName1.doc" },
{ FileLocation = "path/file2.doc", ZipFileDirectory = #"\NewDirectory", FileName = "CustomName2.doc" }
I would expect to see MyCustomName1.doc in the root, and a folder named NewDirectory containing MyCustomName2.doc, but what happens is they both end up in the root using this code:
using (var zip = new Ionic.Zip.ZipFile())
{
foreach (var file in files)
{
zip.AddFile(file.FileLocation, file.ZipFileDirectory).FileName = file.FileName;
}
zip.Save(HttpContext.Current.Response.OutputStream);
}
If I use this:
zip.AddFiles(files.Select(o => o.FileLocation), false, "NewDirectory");
Then it creates the new directory and puts all of the files inside, as expected, but then I lose the ability to use the custom naming with this method, and it also introduces more complexities that the first method would handle perfectly.
Is there a way I can get the first method (AddFile()) to work as I expect?
On further inspection, since posting a comment a few minutes ago, I suspect that setting FileName is erasing the archive path.
Testing confirms this.
Setting the name to #"NewDirectory\CustomName2.doc" will fix the problem.
You can also use #"\NewDirectory\CustomName2.doc"
Not sure if this exactly suites your needs but thought I would share. It is a method that is part of a helper class that I created to make working with DotNetZip a bit easier for my dev team. The IOHelper class is another simple helper class that you can ignore.
/// <summary>
/// Create a zip file adding all of the specified files.
/// The files are added at the specified directory path in the zip file.
/// </summary>
/// <remarks>
/// If the zip file exists then the file will be added to it.
/// If the file already exists in the zip file an exception will be thrown.
/// </remarks>
/// <param name="filePaths">A collection of paths to files to be added to the zip.</param>
/// <param name="zipFilePath">The fully-qualified path of the zip file to be created.</param>
/// <param name="directoryPathInZip">The directory within the zip file where the file will be placed.
/// Ex. specifying "files\\docs" will add the file(s) to the files\docs directory in the zip file.</param>
/// <param name="deleteExisting">Delete the zip file if it already exists.</param>
public void CreateZipFile(ICollection<FileInfo> filePaths, string zipFilePath, string directoryPathInZip, bool deleteExisting)
{
if (deleteExisting)
{
IOHelper ioHelper = new IOHelper();
ioHelper.DeleteFile(zipFilePath);
}
using (ZipFile zip = new ZipFile(zipFilePath))
{
foreach (FileInfo filePath in filePaths)
{
zip.AddFile(filePath.FullName, directoryPathInZip);
}
zip.Save();
}
}

Embedding an external executable inside a C# program

How do I embed an external executable inside my C# Windows Forms application?
Edit: I need to embed it because it's an external free console application (made in C++) from which I read the output values to use in my program. It would be nice and more professional to have it embedded.
Second reason is a requirement to embed a Flash projector file inside a .NET application.
Simplest way, leading on from what Will said:
Add the .exe using Resources.resx
Code this:
string path = Path.Combine(Path.GetTempPath(), "tempfile.exe");
File.WriteAllBytes(path, MyNamespace.Properties.Resources.MyExecutable);
Process.Start(path);
Here is some sample code that would roughly accomplish this, minus error checking of any sort. Also, please make sure that the license of the program to be embedded allows this sort of use.
// extracts [resource] into the the file specified by [path]
void ExtractResource( string resource, string path )
{
Stream stream = GetType().Assembly.GetManifestResourceStream( resource );
byte[] bytes = new byte[(int)stream.Length];
stream.Read( bytes, 0, bytes.Length );
File.WriteAllBytes( path, bytes );
}
string exePath = "c:\temp\embedded.exe";
ExtractResource( "myProj.embedded.exe", exePath );
// run the exe...
File.Delete( exePath );
The only tricky part is getting the right value for the first argument to ExtractResource. It should have the form "namespace.name", where namespace is the default namespace for your project (find this under Project | Properties | Application | Default namespace). The second part is the name of the file, which you'll need to include in your project (make sure to set the build option to "Embedded Resource"). If you put the file under a directory, e.g. Resources, then that name becomes part of the resource name (e.g. "myProj.Resources.Embedded.exe"). If you're having trouble, try opening your compiled binary in Reflector and look in the Resources folder. The names listed here are the names that you would pass to GetManifestResourceStream.
Just add it to your project and set the build option to "Embedded Resource"
This is probably the simplest:
byte[] exeBytes = Properties.Resources.myApp;
string exeToRun = Path.Combine(Path.GetTempPath(), "myApp.exe");
using (FileStream exeFile = new FileStream(exeToRun, FileMode.CreateNew))
exeFile.Write(exeBytes, 0, exeBytes.Length);
Process.Start(exeToRun);
Is the executable a managed assembly? If so you can use ILMerge to merge that assembly with yours.
Here's my version:
Add the file to the project as an existing item, change the properties on the file to "Embedded resource"
To dynamically extract the file to a given location: (this example doesn't test location for write permissions etc)
/// <summary>
/// Extract Embedded resource files to a given path
/// </summary>
/// <param name="embeddedFileName">Name of the embedded resource file</param>
/// <param name="destinationPath">Path and file to export resource to</param>
public static void extractResource(String embeddedFileName, String destinationPath)
{
Assembly currentAssembly = Assembly.GetExecutingAssembly();
string[] arrResources = currentAssembly.GetManifestResourceNames();
foreach (string resourceName in arrResources)
if (resourceName.ToUpper().EndsWith(embeddedFileName.ToUpper()))
{
Stream resourceToSave = currentAssembly.GetManifestResourceStream(resourceName);
var output = File.OpenWrite(destinationPath);
resourceToSave.CopyTo(output);
resourceToSave.Close();
}
}
Add File to VS Project
Mark as "Embedded Resource" -> File properties
Use name to resolve: [Assembly Name].[Name of embedded resource] like "MyFunkyNTServcice.SelfDelete.bat"
Your code has resource bug (file handle not freed!), please correct to:
public static void extractResource(String embeddedFileName, String destinationPath)
{
var currentAssembly = Assembly.GetExecutingAssembly();
var arrResources = currentAssembly.GetManifestResourceNames();
foreach (var resourceName in arrResources)
{
if (resourceName.ToUpper().EndsWith(embeddedFileName.ToUpper()))
{
using (var resourceToSave = currentAssembly.GetManifestResourceStream(resourceName))
{
using (var output = File.OpenWrite(destinationPath))
resourceToSave.CopyTo(output);
resourceToSave.Close();
}
}
}
}
Extract something as string, if needed:
public static string ExtractResourceAsString(String embeddedFileName)
{
var currentAssembly = Assembly.GetExecutingAssembly();
var arrResources = currentAssembly.GetManifestResourceNames();
foreach (var resourceName in arrResources)
{
if (resourceName.ToUpper().EndsWith(embeddedFileName.ToUpper()))
{
using (var resourceToSave = currentAssembly.GetManifestResourceStream(resourceName))
{
using (var output = new MemoryStream())
{
resourceToSave.CopyTo(output);
return Encoding.ASCII.GetString(output.ToArray());
}
}
}
}
return string.Empty;
}

Categories

Resources