Private/Compress-UnixZip.ps1

function Compress-UnixZip
{
<#
    .SYNOPSIS
        Creates a Unix-compatible zip file

    .DESCRIPTION
        The created zip is sufficiently compatible that AWS lambda will accept it.
        Unix attributes of rwxrwxrwx are set on all objects in the zip.

    .PARAMETER ZipFile
        Path to zip file to create

    .PARAMETER Path
        If this references a single file, it will be zipped.
        If this references a path, then the entire folder structure beneath the path will be zipped.

    .PARAMETER PassThru
        If set, the path passed to -ZipFile is returned

#>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$ZipFile,

        [Parameter(Mandatory=$true, Position=1)]
        [string]$Path,

        [switch]$PassThru
    )

    $isFolder = $false

    # Work out what to include in the zip file from the -Path argument
    $filesToZip = $(
        if (Test-Path -Path $Path -PathType Leaf)
        {
            Get-Item -Path $Path
        }
        elseif (Test-Path -Path $Path -PathType Container)
        {
            Get-ChildItem -Path $Path -Recurse
            $isFolder = $true
        }
        else
        {
            throw "Path not found: $Path"
        }
    )

    if (Test-Path -Path $ZipFile -PathType Leaf)
    {
        # Remove any pre-existing zip file
        Write-Verbose "Deleting existing package: $Zipfile"
        Remove-Item $ZipFile -Force
    }

    # Account for powershell and OS current directory not being the same
    # as .NET objects like ZipFile will use OS path
    $osZipPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ZipFile)

    try
    {
        Write-Verbose "Creating: $ZipFile"

        # Create the zip file
        $archive = [IO.Compression.ZipFile]::Open($osZipPath, [IO.Compression.ZipArchiveMode]::Create)

        # Go to location where we are zipping for easier path resolution when creating zip directory entries
        if ($isFolder)
        {
            # Change to directory we are zipping
            Push-Location $Path
        }
        else
        {
            # Change to directory containg the file we are zipping
            Push-Location (Split-Path -Parent (Resolve-Path $Path).Path)
        }

        # Add files to zip
        $filesToZip |
        Foreach-Object {
            $fullPath = Resolve-Path -Relative $_.FullName
            $isFile = Test-Path -Path $fullPath -PathType Leaf

            # Create zip directory entry name (getting rid of ./)
            $entryName = $fullPath.Substring(2).Replace('\', '/')

            # Set unix attributes: rwxrwxrwx
            if ($isFile)
            {
                # Create zip file entry
                $entry = $archive.CreateEntry($entryName)
                $entry.LastWriteTime = [System.DateTimeOffset]::Now

                try
                {
                    $entry.ExternalAttributes = 0x81ff -shl 16

                    # Add file
                    $fs = [IO.File]::OpenRead($_.FullName)
                    $es = $entry.Open()
                    $fs.CopyTo($es)
                    $es.Flush()
                    Write-Verbose "Added: $($entryName)"
                }
                finally
                {
                    # Close zip entry and file read into it
                    ($es, $fs) |
                    Where-Object {
                        $null -ne $_
                    } |
                    ForEach-Object {
                        $_.Dispose()
                    }
                }
            }
            else
            {
                # Create zip directory entry
                $entry = $archive.CreateEntry($entryName + '/')
                $entry.LastWriteTime = [System.DateTimeOffset]::Now
                $entry.ExternalAttributes = (0x41ff -shl 16) -bor [int]([System.IO.FileAttributes]::Directory)
            }
        }
    }
    finally
    {
        if ($null -ne $archive)
        {
            # Close zip file
            $archive.Dispose()
        }

        # Restore working directory
        Pop-Location
    }

    if ($PassThru)
    {
        # Return path to zip file
        $ZipFile
    }
}