ZipIt.psm1
Add-Type -TypeDefinition @" using System; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; public class ZipItFileHelper { // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT // https://games.greggman.com/game/zip-rant/ public const uint ZipLocalFileHeaderSignature = 0x04034b50; public const uint ZipLocalFileHeaderSize = 30; [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 30)] public struct ZipLocalFileHeader { public uint signature; public ushort version; public ushort bitflags; public ushort compressionMethod; public ushort lastModFileTime; public ushort lastModFileDate; public uint crc32; public uint compressedSize; public uint uncompressedSize; public ushort fileNameLength; public ushort extraFieldLength; } public const uint ZipCentralFileHeaderSignature = 0x02014b50; public const uint ZipCentralFileHeaderSize = 46; [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 46)] public struct ZipCentralFileHeader { public uint signature; public ushort versionUsed; public ushort versionRequired; public ushort bitflags; public ushort compressionMethod; public ushort lastModeFileTime; public ushort lastModFileDate; public uint crc32; public uint compressedSize; public uint uncompressedSize; public ushort fileNameLength; public ushort extraFieldLength; public ushort fileCommentLength; public ushort diskNumberStart; public ushort internalFileAttributes; public uint externalFileAttributes; public uint relativeOffsetOfLocalHeader; } public const uint ZipEndOfCentralDirHeaderSignature = 0x06054b50; public const uint ZipEndOfCentralDirHeaderSize = 22; [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 22)] public struct ZipEndOfCentralDirHeader { public uint signature; public ushort diskNumberCurrent; public ushort diskNumberCentral; public ushort diskEntryCountCurrent; public ushort diskEntryCountCentral; public uint centralDirSize; public uint centralDirOffset; public ushort fileCommentLength; } // https://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute public const int S_IFIFO = 0x1000; // named pipe (fifo) public const int S_IFCHR = 0x2000; // character special public const int S_IFDIR = 0x4000; // directory public const int S_IFBLK = 0x6000; // block special public const int S_IFREG = 0x8000; // regular public const int S_IFLNK = 0xA000; // symbolic link public const int S_IFSOCK = 0xC000; // socket public const int S_ISUID = 0x800; // set user id on execution public const int S_ISGID = 0x400; // set group id on execution public const int S_ISTXT = 0x200; // sticky bit public const int S_IRWXU = 0x1C0; // RWX mask for owner public const int S_IRUSR = 0x100; // R for owner public const int S_IWUSR = 0x80; // W for owner public const int S_IXUSR = 0x40; // X for owner public const int S_IRWXG = 0x38; // RWX mask for group public const int S_IRGRP = 0x20; // R for group public const int S_IWGRP = 0x10; // W for group public const int S_IXGRP = 0x8; // X for group public const int S_IRWXO = 0x7; // RWX mask for other public const int S_IROTH = 0x4; // R for other public const int S_IWOTH = 0x2; // W for other public const int S_IXOTH = 0x1; // X for other public const int S_ISVTX = 0x200; // save swapped text even after use static uint ConvertSymbolicPermissionsToOctal(string permissions) { if (permissions.Length == 9) { Console.WriteLine("updating permissions length"); permissions = "-" + permissions; } if (permissions.Length != 10) throw new ArgumentException("Invalid permission length. Permission string should be 10 characters long."); uint octalPermissions = 0; // Calculate the permission for user octalPermissions += (permissions[1] == 'r' ? 4u : 0u); octalPermissions += (permissions[2] == 'w' ? 2u : 0u); octalPermissions += (permissions[3] == 'x' ? 1u : 0u); octalPermissions <<= 3; // Shift left for the next group // Calculate the permission for group octalPermissions += (permissions[4] == 'r' ? 4u : 0u); octalPermissions += (permissions[5] == 'w' ? 2u : 0u); octalPermissions += (permissions[6] == 'x' ? 1u : 0u); octalPermissions <<= 3; // Shift left for others // Calculate the permission for others octalPermissions += (permissions[7] == 'r' ? 4u : 0u); octalPermissions += (permissions[8] == 'w' ? 2u : 0u); octalPermissions += (permissions[9] == 'x' ? 1u : 0u); return octalPermissions; } public static void SetUnixFilePermissions(string filePath, string pattern, string permissions) { using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite)) using (BinaryReader br = new BinaryReader(fs)) { long fileLength = fs.Length; while (fs.Position < fileLength) { long headerStartPosition = fs.Position; uint signature = br.ReadUInt32(); fs.Seek(-4, SeekOrigin.Current); // Move back to the start of the signature if (signature == ZipLocalFileHeaderSignature) { var header = ReadStruct<ZipLocalFileHeader>(br); string fileName = Encoding.UTF8.GetString(br.ReadBytes(header.fileNameLength)); fs.Seek(header.compressedSize + header.extraFieldLength, SeekOrigin.Current); } else if (signature == ZipCentralFileHeaderSignature) { var header = ReadStruct<ZipCentralFileHeader>(br); byte[] fileNameBytes = br.ReadBytes(header.fileNameLength); string fileName = Encoding.UTF8.GetString(fileNameBytes); Console.WriteLine("File: {0}", fileName); if (Regex.IsMatch(fileName, pattern)) { uint fileAttributes = 0x0001; // DOS attributes (lower byte) uint unixFileType = S_IFREG; // regular file uint unixPermissions = ConvertSymbolicPermissionsToOctal(permissions); uint unixAttributes = unixFileType | unixPermissions; fileAttributes |= unixAttributes << 16; Console.WriteLine($"Updated file external attributes: {fileAttributes:X8}"); header.versionUsed = (ushort)((header.versionUsed & 0x00FF) | (0x03 << 8)); // Unix header.externalFileAttributes = fileAttributes; fs.Seek(headerStartPosition, SeekOrigin.Begin); WriteStruct(fs, header); fs.Seek(header.fileNameLength, SeekOrigin.Current); } fs.Seek(header.extraFieldLength + header.fileCommentLength, SeekOrigin.Current); } else if (signature == ZipEndOfCentralDirHeaderSignature) { break; // No need to continue reading after the end of central directory record } else { Console.WriteLine($"Unknown Header: 0x{signature:X8}"); break; } } } } private static T ReadStruct<T>(BinaryReader reader) { byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); } finally { handle.Free(); } } private static void WriteStruct<T>(FileStream fs, T theStruct) where T : struct { byte[] bytes = new byte[Marshal.SizeOf(typeof(T))]; GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); Marshal.StructureToPtr(theStruct, handle.AddrOfPinnedObject(), true); fs.Write(bytes, 0, bytes.Length); handle.Free(); } } "@ -Language CSharp function Set-ZipItUnixFilePermissions { [CmdletBinding()] param( [Parameter(Position = 0, Mandatory = $true)] [string] $ZipFilePath, [Parameter(Position = 1, Mandatory = $true)] [string] $FilePattern, [Parameter(Position = 2, Mandatory = $true)] [string] $FilePermissions ) [ZipItFileHelper]::SetUnixFilePermissions($ZipFilePath, $FilePattern, $FilePermissions) } |