Edit zip file content in subfolder with Powershell
Asked Answered
J

5

7

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?

Japan answered 27/8, 2014 at 23:26 Comment(3)
I've never been into ZIP manipulation with PS, but can you share the error you receive? This will make the question more completeParsec
When I add in something like this:Japan
When I add in something like this: foreach($item in $zip.items()) { (Get-Content $connstring) | Foreach-Object {$_ -replace "\{DatabaseServer\}", $sqlDatabase} | Set-Content $connstring } I get "You cannot call a method on a null-valued expression" error. I want to edit a file that's in a subfolder in the zip file.Japan
A
13

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

Arrangement answered 29/6, 2015 at 4:20 Comment(1)
System.IO.Compression.ZipFile also requires .Net 4.5+, in addition to PowerShell 3.0+.Lorileelorilyn
R
8

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:-)!

Roundworm answered 17/3, 2018 at 13:57 Comment(0)
P
5

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()
Piker answered 11/10, 2014 at 18:30 Comment(0)
G
0

This is what I did for multiple files inside zip: I basically updated all the env.js files inside the zip. Wherever there was an entry with local it was replaced by QA in this case.

$zipfileName = "D:\test-fe\frontend-main\C3.Web.Client.zip"

$environment = "QA"
$environment

Add-Type -assembly  System.IO.Compression.FileSystem
$zip =  [System.IO.Compression.ZipFile]::Open($zipfileName,"Update")

$envJSFiles = $zip.Entries.Where({$_.name -eq "env.js"})

foreach ($envJSFile in $envJSFiles)
{

    $envStream = $null
    $text = $null
    $result = $null

    $envStream = $envJSFile.Open()  
    $reader = New-Object IO.StreamReader($envStream)
    $text = $reader.ReadToEnd()
    $result = $text.Replace("local", $environment)
    write-host "File: $envJSFile is needed to be updated with content `n $result"
    $envStream.Flush()
    $envStream.Close()

    # Re-open the file this time with streamwriter
    $desiredFile = [System.IO.StreamWriter]($envJSFile).Open()
    $desiredFile.BaseStream.SetLength(0)
    # Insert the $text to the file and close
    $desiredFile.Write($result)
    $desiredFile.Flush()
    $desiredFile.Close()      
}

$zip.Dispose()
Grommet answered 4/11, 2022 at 6:16 Comment(0)
S
0

I am going to provide some input here has I had a similar issue and it seems that System.IO.Compression.ZipFile is not very reliable. In my specific case it generated a corrupt zip file in the output if I openned the zip in the "update" mode.

My solution is not elegant but it might help if you face this kind of issue.

# set an alias for 7zip
set-alias sz "C:\Program Files\7-Zip\7z.exe"
# unzip to temp folder from ziplocation
sz x -o"$tempFolder" $zip_location -r ;

# get xml content
$file_content = [xml] (Get-Content -Path "$tempFolder/file.xml")
    
# edit your xml $file_content.elementAbc.InnerText= "abc" 

# save xml to file
$file_content.Save($tempFolder/file.xml) | Out-Null

# remove the original zip
rm -fo $zip_location
# zip the folder with /* wilcard at the end to select only the contents     
sz a -tzip "$zip_location" "$tempFolderWild"
# remove the temp folder
rm -fo -r $tempFolder
Saenz answered 14/3, 2023 at 16:45 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.