Search code examples
powershellzipsystem.io.compressioncompress-archivec#-ziparchive

Modify and ZIP file in one statement


PSVersion 5.1.18362.2212

I would like to know if it is possible to read in a set of text files, modify their content and their filenames and store the results directly into a ZIP file?

The following reads text files in and modifies them, storing the change into a new file:

$xSource = "sourcefile.json"
$xTarget = "targetfile.json"
$replacement = "abc"
(Get-Content $xSource) | Foreach-Object {
  $_.replace('[XX]', $replacement).`
} | Set-Content -path $xTarget

Is it possible to modify this to store the target file directly into a ZIP file?

I was hoping something like the following would work, but I am unsure of how I can pass the new filename through to the ZIP? or whether the following works at all?

$xSource = "sourcefile.json"
$xTarget = "targetfile.json"
$xTargetZip = "target.zip"
$replacement = "abc"
(Get-Content $xSource) | Foreach-Object {
  $_.replace('[XX]', $replacement).`
} | Compress-Archive -Update -DestinationPath $xTargetZip

I get the impression that I would need to store the target files into a temporary folder and then pack them from there ... is there any way of avoiding a temporary folder?

Thanks in advance for any and all help.


Solution

  • The solution to this is cumbersome but you asked for it, this is how you can write entries to a zip file without prior writing the updates of your Jsons to new files, in other words, having the contents of the files in memory and writing them to a zip entry.

    References for the .NET Docs used here:

    using namespace System.IO
    using namespace System.IO.Compression
    
    Add-Type -AssemblyName System.IO.Compression
    
    try {    
        # be aware, DO NOT use relative paths here!
        $DestinationPath = 'path\to\test.zip'
        $destfs = [File]::Open($DestinationPath, [FileMode]::CreateNew)
        $zip    = [ZipArchive]::new($destfs, [ZipArchiveMode]::Update)
    
        Get-ChildItem -Path path\to\jsonfolder -Filter *.json | ForEach-Object {
            # `OpenText` uses UTF8 encoding, normally there shouldn't be any issues here
            # but you can also use `Get-Content` instead to get the file content
            $reader    = $_.OpenText()
            $content   = $reader.ReadToEnd() -replace 'hello', 'world'
            # this is yours to define, this is how each entry should be named
            $entryName = $_.BaseName + '-ToBeDetermined' + $_.Extension
            $zipEntry  = $zip.CreateEntry($entryName)
            $zipStream = $zipEntry.Open()
            $writer    = [StreamWriter]::new($zipStream)
            $writer.Write($content)
            $writer, $reader, $zipStream | ForEach-Object 'Dispose'
        }
    }
    finally {
        $zip, $destfs | ForEach-Object 'Dispose'
    }
    

    If you're looking to simplify the process demonstrated above, reading a zip archive and replacing the content of zip archive entries, you might find it easier with the PSCompression Module (Disclaimer: I'm the author of this module).

    This is how the code would look using the module:

    $zip = New-Item 'path\to\test.zip' -ItemType File
    Get-ChildItem -Path path\to\jsonfolder -Filter *.json | ForEach-Object {
        $entryName = $_.BaseName + '-ToBeDetermined' + $_.Extension
        ($_ | Get-Content -Raw) -replace 'hello', 'world' |
            New-ZipEntry -Destination $zip.FullName -EntryPath $entryName
    }