Last active
June 2, 2022 09:54
-
-
Save rgilmutdinov/1286739c455744f27d3f69c8c435ce4e to your computer and use it in GitHub Desktop.
ZipFileProvider for PowerShell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import module | |
Import-Module ./PSZip.dll | |
// Create new drive referring to .zip arvhive | |
New-PSDrive -Name "ZIP" -PSProvider "ZipFile" -Root "D:\files\archive.zip" | |
// List zip archive contents | |
Get-ChildItem -Path ZIP: -Recurse | |
// List only *.txt files | |
Get-ChildItem -Path ZIP: -Recurse -Filter *.txt | |
// ...or | |
dir ZIP: -Recurse *.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.InteropServices; | |
namespace PSZip | |
{ | |
[Flags] | |
public enum MatchPatternFlags : uint | |
{ | |
Normal = 0x00000000, // PMSF_NORMAL | |
Multiple = 0x00000001, // PMSF_MULTIPLE | |
DontStripSpaces = 0x00010000 // PMSF_DONT_STRIP_SPACES | |
} | |
public class FileName | |
{ | |
[DllImport("Shlwapi.dll", SetLastError = false)] | |
static extern int PathMatchSpecExW( | |
[MarshalAs(UnmanagedType.LPWStr)] string file, | |
[MarshalAs(UnmanagedType.LPWStr)] string spec, | |
MatchPatternFlags flags); | |
public static bool MatchPattern(string file, string spec, MatchPatternFlags flags) | |
{ | |
if (string.IsNullOrEmpty(file)) | |
{ | |
return false; | |
} | |
if (string.IsNullOrEmpty(spec)) | |
{ | |
return true; | |
} | |
int result = PathMatchSpecExW(file, spec, flags); | |
return (result == 0); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO.Compression; | |
using System.Linq; | |
namespace PSZip | |
{ | |
public static class ZipArchiveExtensions | |
{ | |
public static IEnumerable<ZipArchiveEntry> GetFiles(this ZipArchive archive, string filter = null) | |
{ | |
return GetChildren(archive, "", false, filter); | |
} | |
public static IEnumerable<ZipArchiveEntry> GetDirectories(this ZipArchive archive) | |
{ | |
return GetChildren(archive, "", true); | |
} | |
public static IEnumerable<ZipArchiveEntry> GetFiles(this ZipArchiveEntry entry, string filter = null) | |
{ | |
return GetChildren(entry, false, filter); | |
} | |
public static IEnumerable<ZipArchiveEntry> GetDirectories(this ZipArchiveEntry entry) | |
{ | |
return GetChildren(entry, true); | |
} | |
public static string GetName(this ZipArchiveEntry entry) | |
{ | |
if (IsDirectory(entry)) | |
{ | |
string[] parts = entry.FullName.Split(ZipFileProvider.PathSeparators, StringSplitOptions.RemoveEmptyEntries); | |
return $"{parts.Last()}/"; | |
} | |
return entry.Name; | |
} | |
private static IEnumerable<ZipArchiveEntry> GetChildren(ZipArchiveEntry parentEntry, bool directory = false, string filter = null) | |
{ | |
if (parentEntry.Archive == null) | |
{ | |
return Enumerable.Empty<ZipArchiveEntry>(); | |
} | |
return GetChildren(parentEntry.Archive, parentEntry.FullName, directory, filter); | |
} | |
private static IEnumerable<ZipArchiveEntry> GetChildren(ZipArchive archive, string parentPath, bool directory = false, string filter = null) | |
{ | |
foreach (ZipArchiveEntry entry in archive.Entries) | |
{ | |
string entryPath = entry.FullName; | |
if (string.Equals(entryPath, parentPath, StringComparison.InvariantCultureIgnoreCase)) | |
{ | |
continue; | |
} | |
if (entryPath.StartsWith(parentPath)) | |
{ | |
string subpath = entryPath.Substring(parentPath.Length); | |
if (directory) | |
{ | |
if (subpath.Count(c => ZipFileProvider.PathSeparators.Any(s => s == c)) == 1 && | |
subpath.EndsWithAny(ZipFileProvider.StringPathSeparators)) | |
{ | |
yield return entry; | |
} | |
} | |
else | |
{ | |
if (subpath.Count(c => ZipFileProvider.PathSeparators.Any(s => s == c)) == 0) | |
{ | |
if (filter != null) | |
{ | |
if (FileName.MatchPattern(subpath, filter, MatchPatternFlags.Normal)) | |
{ | |
yield return entry; | |
} | |
} | |
else | |
{ | |
yield return entry; | |
} | |
} | |
} | |
} | |
} | |
} | |
public static bool IsDirectory(this ZipArchiveEntry entry) | |
{ | |
if (entry == null) | |
{ | |
return false; | |
} | |
return entry.FullName.EndsWithAny(ZipFileProvider.StringPathSeparators) && entry.Name == string.Empty; | |
} | |
public static bool EndsWithAny(this string value, string[] endings) | |
{ | |
return endings.Any(value.EndsWith); | |
} | |
public static string NormalizedPath(this string path) | |
{ | |
string result = path; | |
if (!string.IsNullOrEmpty(path)) | |
{ | |
result = path.Replace($"{ZipFileProvider.PathSeparator1}", $"{ZipFileProvider.PathSeparator2}"); | |
} | |
return result; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.IO.Compression; | |
using System.Management.Automation; | |
namespace PSZip | |
{ | |
internal class ZipFileDriveInfo : PSDriveInfo | |
{ | |
public ZipArchive Archive { get; set; } | |
public ZipFileDriveInfo(PSDriveInfo driveInfo) : base(driveInfo) { } | |
public string NormalizedRoot => Root.NormalizedPath(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.IO.Compression; | |
using System.Linq; | |
using System.Management.Automation; | |
using System.Management.Automation.Provider; | |
namespace PSZip | |
{ | |
[CmdletProvider("ZipFile", ProviderCapabilities.Filter)] | |
public class ZipFileProvider : NavigationCmdletProvider | |
{ | |
public const char PathSeparator1 = '/'; | |
public const char PathSeparator2 = '\\'; | |
public static readonly char[] PathSeparators = { | |
PathSeparator1, PathSeparator2 | |
}; | |
public static readonly string[] StringPathSeparators = { | |
$"{PathSeparator1}", $"{PathSeparator2}" | |
}; | |
internal ZipFileDriveInfo ZipDrive => PSDriveInfo as ZipFileDriveInfo; | |
protected override PSDriveInfo NewDrive(PSDriveInfo drive) | |
{ | |
try | |
{ | |
ZipFileDriveInfo zipDriveInfo = new ZipFileDriveInfo(drive) | |
{ | |
Archive = ZipFile.Open(drive.Root, ZipArchiveMode.Read) | |
}; | |
return zipDriveInfo; | |
} | |
catch (Exception ex) | |
{ | |
WriteError(new ErrorRecord(ex, "100", ErrorCategory.DeviceError, null)); | |
return null; | |
} | |
} | |
protected override PSDriveInfo RemoveDrive(PSDriveInfo drive) | |
{ | |
ZipFileDriveInfo driveInfo = drive as ZipFileDriveInfo; | |
driveInfo?.Archive.Dispose(); | |
return driveInfo; | |
} | |
protected override bool IsValidPath(string path) | |
{ | |
if (string.IsNullOrEmpty(path)) | |
{ | |
return false; | |
} | |
path = path.NormalizedPath(); | |
string[] pathChunks = path.Split(PathSeparator2); | |
foreach (string pathChunk in pathChunks) | |
{ | |
if (pathChunk.Length == 0) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
protected override bool ItemExists(string path) | |
{ | |
if (PathIsDrive(path)) | |
{ | |
return true; | |
} | |
path = path.NormalizedPath(); | |
string archivePath = GetArchivePath(path); | |
if (ZipDrive == null || ZipDrive.Archive == null) | |
{ | |
return false; | |
} | |
ZipArchiveEntry entry = ZipDrive.Archive.GetEntry(archivePath); | |
return entry != null; | |
} | |
protected override void GetChildItems(string path, bool recurse) | |
{ | |
if (ZipDrive != null && ZipDrive.Archive != null) | |
{ | |
WriteArchiveItems(path, recurse, 0); | |
} | |
} | |
private void WriteArchiveItems(string path, bool recurse, int depth) | |
{ | |
if (PathIsDrive(path)) | |
{ | |
WriteDirectoryItems(ZipDrive.Archive.GetDirectories(), recurse, depth); | |
WriteFileItems(ZipDrive.Archive.GetFiles(Filter), depth); | |
return; | |
} | |
path = path.NormalizedPath(); | |
string archivePath = GetArchivePath(path); | |
ZipArchiveEntry entry = ZipDrive.Archive.GetEntry(archivePath); | |
if (entry != null) | |
{ | |
WriteDirectoryItems(entry.GetDirectories(), recurse, depth); | |
WriteFileItems(entry.GetFiles(Filter), depth); | |
} | |
} | |
private void WriteDirectoryItems(IEnumerable<ZipArchiveEntry> directories, bool recurse, int depth) | |
{ | |
foreach (ZipArchiveEntry dirEntry in directories) | |
{ | |
string padding = "".PadLeft(depth * 2, ' '); | |
string dirPath = Path.Combine(ZipDrive.NormalizedRoot, dirEntry.FullName); | |
WriteItemObject($"{padding}{dirEntry.GetName()}", dirPath, true); | |
if (ItemExists(dirPath) && recurse) | |
{ | |
WriteArchiveItems(dirPath, true, depth + 1); | |
} | |
} | |
} | |
private void WriteFileItems(IEnumerable<ZipArchiveEntry> files, int depth) | |
{ | |
foreach (ZipArchiveEntry fileEntry in files) | |
{ | |
string padding = "".PadLeft(depth * 2, ' '); | |
string filePath = Path.Combine(ZipDrive.NormalizedRoot, fileEntry.FullName); | |
WriteItemObject($"{padding}{fileEntry.GetName()}", filePath, false); | |
} | |
} | |
protected override bool HasChildItems(string path) | |
{ | |
path = path.NormalizedPath(); | |
string archivePath = GetArchivePath(path); | |
if (ZipDrive == null || ZipDrive.Archive == null) | |
{ | |
return false; | |
} | |
ZipArchiveEntry entry = ZipDrive.Archive.GetEntry(archivePath); | |
if (entry == null) | |
{ | |
return false; | |
} | |
return entry.IsDirectory() && entry.GetDirectories().Any(); | |
} | |
protected override bool IsItemContainer(string path) | |
{ | |
if (PathIsDrive(path)) | |
{ | |
return true; | |
} | |
path = path.NormalizedPath(); | |
string archivePath = GetArchivePath(path); | |
if (ZipDrive == null || ZipDrive.Archive == null) | |
{ | |
return false; | |
} | |
ZipArchiveEntry entry = ZipDrive.Archive.GetEntry(archivePath); | |
if (entry == null) | |
{ | |
return false; | |
} | |
return entry.IsDirectory(); | |
} | |
private string GetArchivePath(string path) | |
{ | |
if (path.StartsWith(ZipDrive.NormalizedRoot)) | |
{ | |
string subPath = path.Substring(ZipDrive.NormalizedRoot.Length).TrimStart(PathSeparators); | |
return subPath.Replace($"{PathSeparator2}", $"{PathSeparator1}"); | |
} | |
return path; | |
} | |
private bool PathIsDrive(string path) | |
{ | |
if (string.IsNullOrEmpty(path.Replace(ZipDrive.NormalizedRoot, ""))) | |
{ | |
return true; | |
} | |
foreach (string separator in StringPathSeparators) | |
{ | |
if (string.IsNullOrEmpty(path.Replace(ZipDrive.NormalizedRoot + separator, ""))) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment