Skip to content

Commit

Permalink
Merge pull request #13 from webmd-health-services/feature/update-zip
Browse files Browse the repository at this point in the history
Feature/update zip
  • Loading branch information
decoyjoe committed Jan 25, 2019
2 parents 9b8bb24 + e9cea57 commit e8b7de7
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 51 deletions.
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# These owners will be the default owners for everything in
# the repo. The owners will be requested for review when
# someone opens a pull request.
* @webmd-health-services/devops
24 changes: 18 additions & 6 deletions ProGetAutomation/Functions/Add-ProGetUniversalPackageFile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function Add-ProGetUniversalPackageFile
The `Add-ProGetUniversalPackageFile` function adds files and directories to a upack file (use `New-ProGetUniversalPackage` to create a upack file). All items are added under the `package` directory in the package, per the upack specification.
Files are added to the package using their names. They are always added to the `package` directory in the package. For example, if you added `C:\Projects\Zip\Zip\Zip.psd1` to the package, it would get added at `package\Zip.psd1`.
Directories are added into a directory in the `package` directory. The directory in the package will use the name of the source directory. For example, if you add 'C:\Projects\Zip', all items will be added to the package at `package\Zip`.
You can change the name an item will have in the package with the `PackageItemName` parameter. Path separators are allowed, so you can put any item into any directory in the package.
Expand Down Expand Up @@ -52,7 +52,7 @@ function Add-ProGetUniversalPackageFile
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[Alias('FullName')]
[Alias('Path')]
[string]
[string[]]
# The files/directories to add to the upack file. Normally, you would pipe file/directory objects to `Add-ProGetUniversalPackageFile`. You may also pass any object that has a `FullName` or `Path property. You may also pass the path as a string.
#
# If you pass a directory object or path to a directory, that directory and all its sub-directories will be added to the upack file.
Expand Down Expand Up @@ -82,7 +82,11 @@ function Add-ProGetUniversalPackageFile

[Switch]
# By default, if a file already exists in the upack file, you'll get an error. Use this switch to replace any existing files.
$Force
$Force,

[switch]
# Suppress progress messages while adding files to the package.
$Quiet
)

begin
Expand All @@ -108,20 +112,28 @@ function Add-ProGetUniversalPackageFile
{
$params['EntryName'] = $PackageItemName
}

if( $Force )
{
$params['Force'] = $true
}

if( $Quiet )
{
$params['Quiet'] = $true
}

$items = New-Object 'Collections.Generic.List[string]'
}

process
{
$items.Add($InputObject)
foreach( $item in $InputObject )
{
$items.Add($item)
}
}

end
{
$items | Add-ZipArchiveEntry -ZipArchivePath $PackagePath -EntryParentPath $parentPath -CompressionLevel $CompressionLevel @params
Expand Down
15 changes: 6 additions & 9 deletions ProGetAutomation/ProGetAutomation.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'ProGetAutomation.psm1'

# Version number of this module.
ModuleVersion = '0.8.0'
ModuleVersion = '0.9.0'

# Supported PSEditions
CompatiblePSEditions = @( 'Desktop', 'Core' )
Expand Down Expand Up @@ -63,9 +63,9 @@
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = @(
'Formats\Inedo.ProGet.Native.Feed.ps1xml'
'Formats\Inedo.ProGet.PackageInfo.ps1xml'
FormatsToProcess = @(
'Formats\Inedo.ProGet.Native.Feed.ps1xml'
'Formats\Inedo.ProGet.PackageInfo.ps1xml'
)

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
Expand Down Expand Up @@ -129,11 +129,8 @@

# ReleaseNotes of this module
ReleaseNotes = @'
* `Add-ProGetUniversalPackageFile` is now an order of magnitude faster, thanks to performance improvements to the underlying Zip module used to add files to a universal package.
* `Add-ProGetUniversalPackageFile` now preserves file last write/modified date/times.
* Fixed: `Add-ProGetUniversalPackageFile` function behaves improperly when part of a pipeline, causing a major performance problem.
* Fix issue #7: the `Test-ProGetFeed` function ignores the feed's type, i.e. it always returns true if there is any feed with a given name, regardless of its type.
* Renamed the `New-ProGetFeed` and `Test-ProGetFeed` function's `FeedName` and `FeedType` parameters to `Name` and `Type`.
* Added `-Quiet` switch to `Add-ProGetUniversalPackageFile` to suppress progress messages while adding files to the package.
* Fixed: `Add-ProGetUniversalPackageFile` fails when passed multiple paths directly, in a non-pipeline manner.
'@

} # End of PSData hashtable
Expand Down
91 changes: 73 additions & 18 deletions ProGetAutomation/Zip/Functions/Add-ZipArchiveEntry.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ function Add-ZipArchiveEntry
Adds files and directories to a ZIP archive.
.DESCRIPTION
The `Add-ZipArchiveEntry` function adds files and directories to a ZIP archive. The archive must exist. Use the `New-ZipArchive` function to create a new ZIP file. Pipe file or directory objects that you want to add to the pipeline. You may also pass paths directly to the `InputObject` parameter. Relative paths are resolved from the current directory. If you pass a directory or path to a directory, the entire directory and all its sub-directories/files are added to the archive.
The `Add-ZipArchiveEntry` function adds files and directories to a ZIP archive. The archive must exist. Use the `New-ZipArchive` function to create a new ZIP file. Pipe file or directory objects that you want to add to the pipeline. You may also pass paths directly to the `InputObject` parameter (wildcards are **NOT** supported). Relative paths are resolved from the current directory. If you pass a directory or path to a directory, the entire directory and all its sub-directories/files are added to the archive.
Files are added to the ZIP archive using their names. They are always added to the root of the archive. For example, if you added `C:\Projects\Zip\Zip\Zip.psd1` to an archive, it would get added at `Zip.psd1`.
Directories are added into a directory in the root of the archive with the source directory's name. For example, if you add 'C:\Projects\Zip', all items will be added to the ZIP archive at `Zip`.
You can change the name an item will have in the archive with the `EntryName` parameter. Path separators are allowed, so you can put any item into any directory.
Expand Down Expand Up @@ -54,7 +54,7 @@ function Add-ZipArchiveEntry
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[Alias('FullName')]
[Alias('Path')]
[string]
[string[]]
# The files/directories to add to the archive. Normally, you would pipe file/directory objects to `Add-ZipArchiveEntry`. You may also pass any object that has a `FullName` or `Path property. You may also pass the path as a string.
#
# If you pass a directory object or path to a directory, all files in that directory and all its sub-directories will be added to the archive.
Expand Down Expand Up @@ -87,7 +87,11 @@ function Add-ZipArchiveEntry

[Switch]
# By default, if a file already exists in the ZIP file, you'll get an error. Use this switch to replace any existing entries.
$Force
$Force,

[switch]
# Suppress progress messages while adding files to the ZIP archive.
$Quiet
)

begin
Expand All @@ -110,7 +114,24 @@ function Add-ZipArchiveEntry

process
{
$filePaths = $InputObject | Resolve-Path | Select-Object -ExpandProperty 'ProviderPath'
$filePaths =
$InputObject |
ForEach-Object {
$path = Resolve-Path -LiteralPath $_ -ErrorAction Ignore
if( -not $path )
{
$errorSuffix = ''
if( $_ -match '\*|\?|\[.*\]' )
{
$errorSuffix = ' Wildcard expressions are not supported.'
}

Write-Error -Message ('Cannot find path "{0}" because it does not exist.{1}' -f $_, $errorSuffix) -ErrorAction $ErrorActionPreference
return
}
$path | Select-Object -ExpandProperty 'ProviderPath'
}

foreach( $filePath in $filePaths )
{
if( $BasePath )
Expand Down Expand Up @@ -139,15 +160,15 @@ function Add-ZipArchiveEntry
}

# Add the file.
if( (Test-Path -Path $filePath -PathType Leaf) )
if( (Test-Path -LiteralPath $filePath -PathType Leaf) )
{
$entries[$baseEntryName] = $filePath
continue
}

# Now, handle directories
$dirEntryBasePathRegex = '^{0}{1}' -f [regex]::Escape($filePath),$directorySeparatorsRegex
foreach( $filePath in (Get-ChildItem -Path $filePath -Recurse -File | Select-Object -ExpandProperty 'FullName') )
foreach( $filePath in (Get-ChildItem -LiteralPath $filePath -Recurse -File | Select-Object -ExpandProperty 'FullName') )
{
$fileEntryName = $filePath -replace $dirEntryBasePathRegex,''
if( $baseEntryName )
Expand All @@ -161,18 +182,47 @@ function Add-ZipArchiveEntry

end
{
$activity = 'Compressing files into ZIP archive {0}' -f $ZipArchivePath
$writeProgress = [Environment]::UserInteractive -and -not $Quiet
if( $writeProgress )
{
Write-Progress -Activity $activity
}

$bufferSize = 4kb
[byte[]]$buffer = New-Object 'byte[]' ($bufferSize)
$activity = 'Compressing files into ZIP archive {0}' -f $ZipArchivePath
Write-Progress -Activity $activity
[IO.Compression.ZipArchive]$zipFile = [IO.Compression.ZipFile]::Open($ZipArchivePath, [IO.Compression.ZipArchiveMode]::Update, $EntryNameEncoding)

$timer = New-Object 'Timers.Timer' 100
$timer |
Add-Member -Name 'ProcessedCount' -Value 0 -MemberType NoteProperty -PassThru |
Add-Member -MemberType NoteProperty -Name 'Activity' -Value $activity -PassThru |
Add-Member -MemberType NoteProperty -Name 'FilePath' -Value '' -PassThru|
Add-Member -MemberType NoteProperty -Name 'EntryName' -Value '' -PassThru |
Add-Member -MemberType NoteProperty -Name 'TotalCount' -Value $entries.Count

if( $writeProgress )
{
# Write-Progress is *expensive*. Only do it if the user is interactive and only every 1/10th of a second.
Register-ObjectEvent -InputObject $timer -EventName 'Elapsed' -Action {
param(
$Timer,
$EventArgs
)
Write-Progress -Activity $Timer.Activity -Status $Timer.FilePath -CurrentOperation $Timer.EntryName -PercentComplete (($Timer.ProcessedCount/$Timer.TotalCount) * 100)
} | Out-Null
$timer.Enabled = $true
$timer.Start()
}

try
{
$processedCount = 1
foreach( $entryName in $entries.Keys )
{
$filePath = $entries[$entryName]
Write-Progress -Activity $activity -Status $filePath -CurrentOperation $entryName -PercentComplete (($processedCount++/$entries.Count) * 100)
$timer.FilePath = $filePath = $entries[$entryName]
$timer.ProcessedCount += 1
$timer.EntryName = $entryName

Write-Debug -Message ('{0} -> {1}' -f $FilePath,$EntryName)
$entry = $zipFile.GetEntry($EntryName)
if( $entry )
Expand All @@ -183,13 +233,13 @@ function Add-ZipArchiveEntry
}
else
{
Write-Error -Message ('Unable to add file "{0}" to ZIP archive "{1}": the archive already has a file named "{2}". To overwrite existing entries, use the -Force switch.' -f $FilePath,$ZipArchivePath,$EntryName)
Write-Error -Message ('Unable to add file "{0}" to ZIP archive "{1}": the archive already has a file named "{2}".' -f $FilePath,$ZipArchivePath,$EntryName)
continue
}
}

$entry = $zipFile.CreateEntry($EntryName,$CompressionLevel)
$entry.LastWriteTime = (Get-Item -Path $filePath).LastWriteTime
$entry.LastWriteTime = (Get-Item -LiteralPath $filePath).LastWriteTime
$stream = $entry.Open()
try
{
Expand Down Expand Up @@ -231,9 +281,14 @@ function Add-ZipArchiveEntry
}
finally
{
Write-Progress -Activity $activity -Status 'Writing File' -PercentComplete 99
$timer.Stop()
$timer.Dispose()
$zipFile.Dispose()
Write-Progress -Activity $activity -Completed
if( $writeProgress )
{
Write-Progress -Activity $activity -Status 'Writing File' -PercentComplete 99
Write-Progress -Activity $activity -Completed
}
}
}
}
}
16 changes: 8 additions & 8 deletions ProGetAutomation/Zip/Functions/New-ZipArchive.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ function New-ZipArchive
.DESCRIPTION
The `New-ZipArchive` function createa a new, empty ZIP archive. Pass the path to the archive to the Path parameter. A new, empty ZIP archive is created at that path. The function returns a `IO.FileInfo` object representing the new ZIP archive.
If `Path` is relative, it is created relative to the current directory.
If `Path` is relative, it is created relative to the current directory.
If a file already exists, you'll get an error and nothing will be returned. To delete any existing file and create a new, empty ZIP archive, pass the `Force` switch.
You can control the compression level of the archive by passing an `IO.Compression.CompressionLevel` value to the `CompressionLevel` parameter. The default is `Optimal`. Other values are `Fastest` and `None`.
By default, entry names are encoded as UTF8 text. If your ZIP archive will be consumed by tools that don't support UTF8, pass the encoding they do support to the `EntryNameEncoding` parameter.
.EXAMPLE
Expand Down Expand Up @@ -58,21 +58,21 @@ function New-ZipArchive
}
$Path = [IO.Path]::GetFullPath($Path)

if( (Test-Path -Path $Path) )
if( (Test-Path -LiteralPath $Path) )
{
if( (Test-Path -Path $Path -PathType Container) )
if( (Test-Path -LiteralPath $Path -PathType Container) )
{
Write-Error -Message ('Path "{0}" is a directory. Unable to create a ZIP archive there.' -f $Path)
return
}

if( $Force )
{
Remove-Item -Path $Path
Remove-Item -LiteralPath $Path
}
else
{
Write-Error -Message ('The file "{0}" already exists. Unable to create a new ZIP archive at that path. Use the -Force switch to overwrite the file.' -f $Path)
Write-Error -Message ('The file "{0}" already exists. Unable to create a new ZIP archive at that path.' -f $Path)
return
}
}
Expand All @@ -82,10 +82,10 @@ function New-ZipArchive
try
{
[IO.Compression.ZipFile]::CreateFromDirectory($tempDir,$Path,$CompressionLevel,$false,$EntryNameEncoding)
Get-Item -Path $Path
Get-Item -LiteralPath $Path
}
finally
{
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction Ignore
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction Ignore
}
}
8 changes: 6 additions & 2 deletions ProGetAutomation/Zip/Zip.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'Zip.psm1'

# Version number of this module.
ModuleVersion = '0.2.0'
ModuleVersion = '0.3.0'

# Supported PSEditions
CompatiblePSEditions = @( 'Desktop', 'Core' )
Expand Down Expand Up @@ -111,7 +111,11 @@

# ReleaseNotes of this module
ReleaseNotes = '
* `Add-ZipArchiveEntry` now preserves a file''s last write/modified date/time.
* Fixed: `Add-ZipArchiveEntry` fails when passed multiple paths directly, in a non-pipeline manner.
* `Add-ZipArchiveEntry` no longer supports resolving wildcard expressions in paths.
* Fixed: `New-ZipArchive` fails if archive name contains `[]` characters.
* Improved the performance of `Add-ZipArchiveEntry` when zipping a large number of files by reducing the number of progress messages that are written during the operation. Progress messages are now only written every 100ms as opposed for every file added to the ZIP archive.
* Added a `-Quiet` switch to `Add-ZipArchiveEntry` that will suppress progress messages during the operation.
'

} # End of PSData hashtable
Expand Down
Loading

0 comments on commit e8b7de7

Please sign in to comment.