Skip to content

Instantly share code, notes, and snippets.

@JPRuskin
Last active May 13, 2022 09:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JPRuskin/91eeef066642f1d3f1000f38bea059f5 to your computer and use it in GitHub Desktop.
Save JPRuskin/91eeef066642f1d3f1000f38bea059f5 to your computer and use it in GitHub Desktop.
Some unfinished functions to handle CRUD in zip (/nupkg) archives.
function Find-FileInArchive {
<#
.Synopsis
Finds files matching a pattern in an archive.
.Example
Find-FileInArchive -Path "C:\Archive.zip" -like "tools/files/*-x86.exe"
.Example
Find-FileInArchive -Path $Nupkg -match "tools/files/dotnetcore-sdk-(?<Version>\d+\.\d+\.\d+)-win-x86\.exe(\.ignore)?"
.Notes
Please be aware that this matches against the full name of the file, not just the file name.
Though given that, you can easily write something to match the file name.
#>
[CmdletBinding(DefaultParameterSetName="match")]
param(
# Path to the archive
[Parameter(Mandatory)]
[string]$Path,
# Pattern to match with basic globbing
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName="match")]
[string]$match,
# Pattern to match with regex
[Parameter(Mandatory, ParameterSetName="like")]
[string]$like
)
begin {
while (-not $Zip -and $AccessRetries++ -lt 3) {
try {
$Stream = [IO.FileStream]::new($Path, [IO.FileMode]::Open)
$Zip = [IO.Compression.ZipArchive]::new($Stream, [IO.Compression.ZipArchiveMode]::Read)
} catch [System.IO.IOException] {
if ($AccessRetries -ge 3) {
Write-Error -Message "Accessing '$Path' failed after $AccessRetries attempts." -TargetObject $Path
} else {
Write-Information "Could not access '$Path', retrying..."
Start-Sleep -Milliseconds 500
}
}
}
}
process {
if ($Zip) {
$WhereBlock = [ScriptBlock]::Create("`$_.FullName -$($PSCmdlet.ParameterSetName) '$(Get-Variable -Name $PSCmdlet.ParameterSetName -ValueOnly)'")
$Zip.Entries | Where-Object -FilterScript $WhereBlock
}
}
end {
if ($Zip) {
$Zip.Dispose()
}
if ($Stream) {
$Stream.Close()
$Stream.Dispose()
}
}
}
function Remove-FileInArchive {
<#
.Synopsis
Removes files from a zip formatted archive
.Description
In order to reduce the size of an archive, you may want to remove files from it.
This function will remove the files you specify from the archive you specify.
.Example
Remove-FileInArchive -Path $Nupkg -Name dotnet-sdk-3.1.410-win-x86.exe dotnet-sdk-3.1.410-win-x86.exe.ignore
.Example
Remove-FileInArchive -Zip $Zip -FullName "tools/chocolateyInstall.ps1"
#>
[CmdletBinding(SupportsShouldProcess)]
param(
# Path to the archive
[Parameter(Mandatory, ParameterSetName = "PathFullName")]
[Parameter(Mandatory, ParameterSetName = "PathName")]
[string]$Path,
# Zip object for the archive
[Parameter(Mandatory, ParameterSetName = "ZipFullName")]
[Parameter(Mandatory, ParameterSetName = "ZipName")]
[IO.Compression.ZipArchive]$Zip,
# Name of the file(s) to remove in the archive
[Parameter(Mandatory, ValueFromPipeline, ValueFromRemainingArguments, ParameterSetName="PathFullName")]
[Parameter(Mandatory, ValueFromPipeline, ValueFromRemainingArguments, ParameterSetName="ZipFullName")]
[string[]]$FullName,
# Name of the file(s) to remove in the archive
[Parameter(Mandatory, ValueFromPipeline, ValueFromRemainingArguments, ParameterSetName="PathName")]
[Parameter(Mandatory, ValueFromPipeline, ValueFromRemainingArguments, ParameterSetName="ZipName")]
[string[]]$Name
)
begin {
if (-not $PSCmdlet.ParameterSetName.StartsWith("Zip")) {
$Stream = [IO.FileStream]::new($Path, [IO.FileMode]::Open)
$Zip = [IO.Compression.ZipArchive]::new($Stream, [IO.Compression.ZipArchiveMode]::Update)
}
}
process {
foreach ($File in Get-Variable -Name $PSCmdlet.ParameterSetName.TrimStart('PathZip') -ValueOnly) {
($Zip.Entries | Where-Object {$_."$($PSCmdlet.ParameterSetName.TrimStart('PathZip'))" -eq $File}) | ForEach-Object {
if ($PSCmdlet.ShouldProcess($_.FullName, "Remove")) {
$_.Delete()
}
}
}
}
end {
if (-not $PSCmdlet.ParameterSetName.StartsWith("Zip")) {
$Zip.Dispose()
$Stream.Close()
$Stream.Dispose()
}
}
}
function Get-FileContentInArchive {
<#
.Synopsis
Returns the content of a file from within an archive
.Example
Get-FileContentInArchive -Path $ZipPath -Name "chocolateyInstall.ps1"
.Example
Get-FileContentInArchive -Zip $Zip -FullName "tools\chocolateyInstall.ps1"
#>
[CmdletBinding(DefaultParameterSetName="PathFullName")]
[OutputType([string])]
param(
# Path to the archive
[Parameter(Mandatory, ParameterSetName = "PathFullName")]
[Parameter(Mandatory, ParameterSetName = "PathName")]
[string]$Path,
# Zip object for the archive
[Parameter(Mandatory, ParameterSetName = "ZipFullName")]
[Parameter(Mandatory, ParameterSetName = "ZipName")]
[IO.Compression.ZipArchive]$Zip,
# Name of the file(s) to remove from the archive
[Parameter(Mandatory, ParameterSetName = "PathFullName")]
[Parameter(Mandatory, ParameterSetName = "ZipFullName")]
[string]$FullName,
# Name of the file(s) to remove from the archive
[Parameter(Mandatory, ParameterSetName = "PathName")]
[Parameter(Mandatory, ParameterSetName = "ZipName")]
[string]$Name
)
begin {
if (-not $PSCmdlet.ParameterSetName.StartsWith("Zip")) {
$Stream = [IO.FileStream]::new($Path, [IO.FileMode]::Open)
$Zip = [IO.Compression.ZipArchive]::new($Stream, [IO.Compression.ZipArchiveMode]::Read)
}
}
process {
if (-not $FullName) {
$MatchingEntries = $Zip.Entries | Where-Object {$_.Name -eq $Name}
if ($MatchingEntries.Count -ne 1) {
Write-Error "File '$Name' not found in archive" -ErrorAction Stop
}
$FullName = $MatchingEntries[0].FullName
}
[System.IO.StreamReader]::new(
$Zip.GetEntry($FullName).Open()
).ReadToEnd()
}
end {
if (-not $PSCmdlet.ParameterSetName.StartsWith("Zip")) {
$Zip.Dispose()
$Stream.Close()
$Stream.Dispose()
}
}
}
function Update-FileInArchive {
<#
.Synopsis
Updates a text file in an archive
.Description
In order to remove references to given files from install scripts, you may want to update a file within an archive (without repacking it).
This function will update the content of files in the archive you specify.
.Example
Update-FileInArchive -Path $Nupkg -FullName "tools/chocolateyInstall.ps1" -Value $UpdatedScript
.Example
Update-FileInArchive -Zip $Zip -Name "chocolateyInstall.ps1" -Value $UpdatedScript
#>
[CmdletBinding(DefaultParameterSetName="PathFullName", SupportsShouldProcess)]
param(
# Path to the archive
[Parameter(Mandatory, ParameterSetName = "PathFullName")]
[Parameter(Mandatory, ParameterSetName = "PathName")]
[string]$Path,
# Zip object for the archive
[Parameter(Mandatory, ParameterSetName = "ZipFullName")]
[Parameter(Mandatory, ParameterSetName = "ZipName")]
[IO.Compression.ZipArchive]$Zip,
# Name of the file to update in the archive
[Parameter(Mandatory, ParameterSetName = "PathFullName")]
[Parameter(Mandatory, ParameterSetName = "ZipFullName")]
[string]$FullName,
# Name of the file to update in the archive
[Parameter(Mandatory, ParameterSetName = "PathName")]
[Parameter(Mandatory, ParameterSetName = "ZipName")]
[string]$Name,
# Value to set the file to
[Parameter(Mandatory)]
[string]$Value
)
begin {
if (-not $PSCmdlet.ParameterSetName.StartsWith("Zip")) {
$Stream = [IO.FileStream]::new($Path, [IO.FileMode]::Open)
$Zip = [IO.Compression.ZipArchive]::new($Stream, [IO.Compression.ZipArchiveMode]::Update)
}
}
process {
if (-not $FullName) {
$MatchingEntries = $Zip.Entries | Where-Object {$_.Name -eq $Name}
if ($MatchingEntries.Count -ne 1) {
Write-Error "File '$Name' not found in archive" -ErrorAction Stop
}
$FullName = $MatchingEntries[0].FullName
}
if ($PSCmdlet.ShouldProcess("Update", $_.FullName)) {
# Either remove file, create entry
Remove-FileInArchive -Zip $Zip -FullName $FullName
$Zip.CreateEntry()
# Or update existing entry and length
}
}
end {
if (-not $PSCmdlet.ParameterSetName.StartsWith("Zip")) {
$Zip.Dispose()
$Stream.Close()
$Stream.Dispose()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment