# encoding: ascii
# api: powershell
# title: ZipFile Module
# description: New-ZipFile and Expand-ZipFile — two functions for zipping and unzipping the way I like doing it…
# version: 0.1
# type: function
# author: Joel Bennett
# license: CC0
# function: New-ZipFile
# x-poshcode-id: 4845
# x-derived-from-id: 5968
# x-archived: 2017-05-22T04:07:07
# x-published: 2014-01-27T19:08:00
#
#
Add-Type -As System.IO.Compression.FileSystem

function New-ZipFile {
  #.Synopsis
  #  Create a new zip file, optionally appending to an existing zip...
  [CmdletBinding()]
  param(
    # The path of the zip to create
    [Parameter(Position=0, Mandatory=$true)]
    $ZipFilePath,

    # Items that we want to add to the ZipFile
    [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
    [Alias("PSPath","Item")]
    [string[]]$InputObject = $Pwd,

    # Append to an existing zip file, instead of overwriting it
    [Switch]$Append,

    # The compression level (defaults to Optimal):
    #   Optimal - The compression operation should be optimally compressed, even if the operation takes a longer time to complete.
    #   Fastest - The compression operation should complete as quickly as possible, even if the resulting file is not optimally compressed.
    #   NoCompression - No compression should be performed on the file.
    [System.IO.Compression.CompressionLevel]$Compression = "Optimal"
  )
  begin {
    # Make sure the folder already exists
    [string]$File = Split-Path $ZipFilePath -Leaf
    [string]$Folder = $(if($Folder = Split-Path $ZipFilePath) { Resolve-Path $Folder } else { $Pwd })
    $ZipFilePath = Join-Path $Folder $File
    # If they don't want to append, make sure the zip file doesn't already exist.
    if(!$Append) {
      if(Test-Path $ZipFilePath) { Remove-Item $ZipFilePath }
    }
    $Archive = [System.IO.Compression.ZipFile]::Open( $ZipFilePath, "Update" )
  }
  process {
    foreach($path in $InputObject) {
      foreach($item in Resolve-Path $path) {
        # Push-Location so we can use Resolve-Path -Relative 
        Push-Location (Split-Path $item)
        # This will get the file, or all the files in the folder (recursively)
        foreach($file in Get-ChildItem $item -Recurse -File -Force | % FullName) {
          # Calculate the relative file path
          $relative = (Resolve-Path $file -Relative).TrimStart(".\")
          # Add the file to the zip
          $null = [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($Archive, $file, $relative, $Compression)
        }
        Pop-Location
      }
    }
  }
  end {
    $Archive.Dispose()
    Get-Item $ZipFilePath
  }
}


function Expand-ZipFile {
  #.Synopsis
  #  Expand a zip file, ensuring it's contents go to a single folder ...
  [CmdletBinding()]
  param(
    # The path of the zip file that needs to be extracted
    [Parameter(ValueFromPipelineByPropertyName=$true, Position=0, Mandatory=$true)]
    [Alias("PSPath")]
    $FilePath,

    # The path where we want the output folder to end up
    [Parameter(Position=1)]
    $OutputPath = $Pwd,

    # Make sure the resulting folder is always named the same as the archive
    [Switch]$Force
  )
  process {
    $ZipFile = Get-Item $FilePath
    $Archive = [System.IO.Compression.ZipFile]::Open( $ZipFile, "Read" )

    # Figure out where we'd prefer to end up
    if(Test-Path $OutputPath) {
      # If they pass a path that exists, we want to create a new folder
      $Destination = Join-Path $OutputPath $ZipFile.BaseName
    } else {
      # Otherwise, since they passed a folder, they must want us to use it
      $Destination = $OutputPath
    }

    # The root folder of the first entry ...
    $ArchiveRoot = ($Archive.Entries[0].FullName -Split "/|\\")[0]

    Write-Verbose "Desired Destination: $Destination"
    Write-Verbose "Archive Root: $ArchiveRoot"

    # If any of the files are not in the same root folder ...
    if($Archive.Entries.FullName | Where-Object { @($_ -Split "/|\\")[0] -ne $ArchiveRoot }) { 
      # extract it into a new folder:
      New-Item $Destination -Type Directory -Force
      [System.IO.Compression.ZipFileExtensions]::ExtractToDirectory( $Archive, $Destination )
    } else {
      # otherwise, extract it to the OutputPath 
      [System.IO.Compression.ZipFileExtensions]::ExtractToDirectory( $Archive, $OutputPath )

      # If there was only a single file in the archive, then we'll just output that file...
      if($Archive.Entries.Count -eq 1) {
        # Except, if they asked for an OutputPath with an extension on it, we'll rename the file to that ... 
        if([System.IO.Path]::GetExtension($Destination)) {
          Move-Item (Join-Path $OutputPath $Archive.Entries[0].FullName) $Destination
        } else {
          Get-Item (Join-Path $OutputPath $Archive.Entries[0].FullName)
        }
      } elseif($Force) {
        # Otherwise let's make sure that we move it to where we expect it to go, in case the zip's been renamed
        if($ArchiveRoot -ne $ZipFile.BaseName) {
          Move-Item (join-path $OutputPath $ArchiveRoot) $Destination
          Get-Item $Destination
        }
      } else {
        Get-Item (Join-Path $OutputPath $ArchiveRoot)
      }
    }

    $Archive.Dispose()
  }
}
