Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Edit zip file content in subfolder with Powershell

I'm trying to update the contents of a zip file created from an Excel document. I want to replace some of the content of \zipfile\xl\connections.xml.

This partial script will list the contents of the zip file:

$shell_app = new-object -com shell.application
$zip = "$destination\exceltemplates\Templates\Template1.xlsx.zip"
$zip_file=$shell_app.NameSpace($zip)
$zip_file.Items() | Select Path

But every update method I've tried has generated an error. What's the next step needed to access and update a file in the zip file?

like image 406
Judy Way Avatar asked Aug 27 '14 23:08

Judy Way


3 Answers

This is not very complicated. Using PowerShell v3.0 or higher (and the standard .NET System.IO libraries) is easier.

# Parameters
$zipfileName = "E:\temp\WebsitePackage.zip"
$fileToEdit = "robots.txt"
$contents = "User-agent: *
Disallow: /"

# Open zip and find the particular file (assumes only one inside the Zip file)
Add-Type -assembly  System.IO.Compression.FileSystem
$zip =  [System.IO.Compression.ZipFile]::Open($zipfileName,"Update")
$robotsFile = $zip.Entries.Where({$_.name -eq $fileToEdit})

# Update the contents of the file
$desiredFile = [System.IO.StreamWriter]($robotsFile).Open()
$desiredFile.BaseStream.SetLength(0)
$desiredFile.Write($contents)
$desiredFile.Flush()
$desiredFile.Close()

# Write the changes and close the zip file
$zip.Dispose()
Write-Host "zip file updated"

The next problem is how to quickly check that your changes were successful? A simple adaptation of the script allows you to read the contents of file inside a Zip file:

# Parameters
$zipfileName = "E:\temp\WebsitePackage.zip"
$fileToRead = "robots.txt"

# Open zip and find the particular file (assumes only one inside the Zip file)
Add-Type -assembly  System.IO.Compression.FileSystem
$zip =  [System.IO.Compression.ZipFile]::Open($zipfileName,"Update")
$robotsFile = $zip.Entries.Where({$_.name -eq $fileToRead})

# Read the contents of the file
$desiredFile = [System.IO.StreamReader]($robotsFile).Open()
$text = $desiredFile.ReadToEnd()

# Output the contents
$text

$desiredFile.Close()
$desiredFile.Dispose()

# Close the zip file
$zip.Dispose()

There is useful background material at this article: https://mcpmag.com/articles/2014/09/29/file-frontier-part-6.aspx

like image 52
David White Avatar answered Nov 14 '22 04:11

David White


I had to replace the version xml file (NuGetApp1.nuspec) in a zipped nuget package, like so:

Original file content in zip:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
  <metadata>
  <id>NuGetApp1</id>
  <version>1.1.3-dev22222</version>
  <title>NuGetApp1</title>
.....

Required :

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
  <metadata>
  <id>NuGetApp1</id>
  <version>1.0.0.0</version>
  <title>NuGetApp1</title>
.....

Powershell Script:

$replaceWithVersion="1.0.0.0" 

$path = "c:\temp\testnuget"
$files = Get-ChildItem *.nupkg -Path $path 
foreach($fileNuget in $files)
{ 
    $target = $fileNuget.FullName -replace "[0-9]+(\.([0-9]+|\*)){1,4}", $replaceWithVersion
    Copy-Item $fileNuget.FullName -Destination $target
    $zipfileName = $target
    $fileToEdit = "*.nuspec"


    # Open zip and find the particular file (assumes only one inside the Zip file)
    $zip =  [System.IO.Compression.ZipFile]::Open($zipfileName,"Update")

    $nuspecFile = $zip.Entries.Where({$_.name -like $fileToEdit})

    # Read the contents of the file
    $desiredFile = [System.IO.StreamReader]($nuspecFile).Open()
    $text = $desiredFile.ReadToEnd()
    $desiredFile.Close()
    $desiredFile.Dispose()
    $text = $text -replace  '<version>[\s\S]*?<\/version>',"<version>$replaceWithVersion</version>"
    #update file with new content
    $desiredFile = [System.IO.StreamWriter]($nuspecFile).Open()
    $desiredFile.BaseStream.SetLength(0)

    # Insert the $text to the file and close
    $desiredFile.Write($text)
    $desiredFile.Flush()
    $desiredFile.Close()


   # Write the changes and close the zip file
   $zip.Dispose()
  Write-Host "zip file updated"
}

Happy hunting:-)!

like image 23
Roland Roos Avatar answered Nov 14 '22 04:11

Roland Roos


When searching, this is a question I found, so I thought I would venture to answer. In this generic script, the invocation of VB is not used, instead the dotnet import of System.IO.Compression.FileSystem. Hope this helps someone. Who knows, might even be me in the future! LOL

# The zip file to be updated
$file = Get-ChildItem ~\file.zip

# Load ZipFile (Compression.FileSystem) if necessary
try { $null = [IO.Compression.ZipFile] }
catch { [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') }

# Open zip file with update mode (Update, Read, Create -- are the options)
try { $fileZip = [System.IO.Compression.ZipFile]::Open( $file, 'Update' ) }
catch { throw "Another process has locked the '$file' file." }

# Finding the specific file within the zip file
<#
NOTE: These entries have the directories separated with a forward slash (/) instead of MS convention
of a backward slash (\).  Even though this is regex it seems the forward slash does not need to be escaped (\/).
NOTE2: Because this is regex '.*' must be used instead of a simple '*'
#>
$fileZip.Entries | Where-Object { $_.FullName -match '/subdir/.*/desiredfile.xml' }

# If needed, read the contents of specific file to $text and release the file so to use streamwriter later
$desiredFile = [System.IO.StreamReader]($fileZip.Entries | Where-Object { $_.FullName -match '/subdir/.*/desiredfile.xml' }).Open()
$text = $desiredFile.ReadToEnd()
$desiredFile.Close()
$desiredFile.Dispose()

# If needed, manipulate $text however for the update
$text = $text -replace '\n', [char]30

# Re-open the file this time with streamwriter
$desiredFile = [System.IO.StreamWriter]($fileZip.Entries | Where-Object { $_.FullName -match '/subdir/.*/desiredfile.xml' }).Open()

# If needed, zero out the file -- in case the new file is shorter than the old one
$desiredFile.BaseStream.SetLength(0)

# Insert the $text to the file and close
$desiredFile.Write($text -join "`r`n")
$desiredFile.Flush()
$desiredFile.Close()

# Write the changes and close the zip file
$fileZip.Dispose()
like image 23
J-o-h-n- Avatar answered Nov 14 '22 05:11

J-o-h-n-