Functions/New-ProGetUniversalPackage.ps1


function New-ProGetUniversalPackage
{
    <#
    .SYNOPSIS
    Creates a ProGet universal package file.
 
    .DESCRIPTION
    The `New-ProGetUniversalPackage` function creates a ProGet universal package file. The file will only contain a upack.json file. Pass the path to the file to create to the `OutFile` parameter (the file must not exist or you'll get an error). You must supply a name (with the `Name` parameter) and a version (with the `Version` parameter). Names can only contain letters, numbers, periods, underscores, and hyphens. Version must be a valid semantic version. Pass
 
    `New-ProGetUniversalPackage` has the following parameters that add the appropriate metadata to the package's upack.json manifest:
 
    * GroupName
    * Title
    * ProjectUri
    * IconUri
    * Description
    * Tag (creates the `tags` property)
    * Dependency (creates the `dependencies` property)
    * Reason (creates the `createdReason` property)
    * Author (creates the `createdBy` property)
 
    You can pass additional custom metadata to the `AdditionalMetadata` property. It is recommended that all custom metadata be prefixed with an underscore to prevent collision with future standard metadata.
 
    The `New-ProGetUniversalPackage` function always adds two additional pieces of metadata:
     
    * `createdDate`, the UTC date/time this function gets called
    * `createdUsing`, a string that identifies the ProGetAutomation module as the tool used; it includes the module's version, the PowerShell version, and, if available, the PowerShell edition.
 
    A `IO.FileInfo` object is returned for the just-created package.
 
    The `Zip` PowerShell module is used to create the ZIP archive and add the upack.json file to it.
 
    By default, optimal compression is used. You can customize your compression level with the `CompressionLevel` parameter.
 
    See the [upack.json Manifest Specification page](https://inedo.com/support/documentation/upack/universal-packages/metacontent-guidance/manifest-specification) for more information about the format and contents of the upack.json file.
 
    Once you've created the package, you can then add additional files to it with the `Add-ProGetUniversalPackage` function.
 
    .EXAMPLE
    New-ProGetUniversalPackage -OutFile 'package.upack' -Version '0.0.0' -Name 'ProGetAutomation'
 
    Demonstrates how to create a minimal upack package.
 
    .EXAMPLE
    New-ProGetUniversalPackage -OutFile 'package.upack' -Version '0.0.0' -Name 'ProGetAutomation' -GroupName 'WHS/PowerShell' -Title 'ProGet Automation' -ProjectUri 'https://github.com/webmd-health-services/ProGetAutomation' -IconUri 'https://github.com/webmd-health-services/ProGetAutomation/icon.png' -Description 'A PowerShell module for automationg ProGet.' -Tag @( 'powershell', 'module', 'inedo', 'proget' ) -Dependency @( 'zip' ) -Reason 'Because the world needs more PowerShell!' -Author 'WebMD Health Services'
 
    Demonstrates how to create a upack package with all required and optional metadata. (The ProGetAutomation package doesn't have any dependencies. The example shows one for illustrative purposes only.)
 
    .EXAMPLE
    New-ProGetUniversalPackage -OutFile 'package.upack' -Version '0.0.0' -Name 'ProGetAutomation' -AdditionalMetadata @{ '_whs' = @{ 'fubar' = 'snafu' } }
 
    Demonstrates how to add custom metadata to your package.
 
    .EXAMPLE
    New-ProGetUniversalPackage -OutFile 'package.upack' -Version '0.0.0' -Name 'ProGetAutomation' -CompressionLevel Fastest
 
    Demonstrates how to change the compression level of the package.
    #>

    [CmdletBinding()]
    [OutputType([IO.FileInfo])]
    param(
        [Parameter(Mandatory)]
        [string]
        # Path to the package. The package is created here. The filename should have a .upack extension.
        $OutFile,

        [Parameter(Mandatory)]
        [string]
        # The version of the package. Semantic Version 2 supported.
        $Version,

        [Parameter(Mandatory)]
        [ValidatePattern('^[A-Za-z0-9._-]+$')]
        [string]
        # The name of the package. Must only contain letters, numbers, periods, underscores or hyphens.
        $Name,

        [ValidatePattern('(^[A-Za-z0-9._/-]+$)|(^$)')]
        [ValidatePattern('(^[^/])|(^$)')]
        [ValidatePattern('([^/]$)|(^$)')]
        [string]
        # The group name of the package. Must only contain letters, numbers, periods, underscores, forward slashes, or hyphens. Must not begin or end with forward slashes.
        $GroupName,

        [ValidateLength(1,50)]
        [string]
        # The package's title/display name. Any characters are allowed. Can't be longer than 50 characters.
        $Title,

        [uri]
        # The URI to the project.
        $ProjectUri,

        [uri]
        # The URI to the projet/package's icon. The icon may be in the package itself. If it is, pass `package://path/to/icon`.
        $IconUri,

        [string]
        # A full description of the package. Formatted as Markdown in the ProGet UI.
        $Description,

        [string[]]
        [ValidatePattern('^[A-Za-z0-9._-]+$')]
        # An array of tags. Each tag must only contain letters, numbers, periods, underscores, and hyphens.
        $Tag,

        [string[]]
        # A list of dependencies as package names. Must be formatted like:
        #
        # * �group�/�package-name�
        # * �group�/�package-name�:�version�
        # * �group�/�package-name�:�version�:�sha-hash�
        $Dependency,

        [string]
        # The reason the package is getting created.
        $Reason,

        [string]
        # The author of the package.
        $Author,

        [hashtable]
        # Any additional metadata for the package. It is recommended that you prefix custom metadata with an underscore to prevent possible collisions with future system metadata.
        #
        # If you provide a parameter and duplicate that parameter's metadata in this hashtable, the parameter value takes precedence.
        #
        #
        $AdditionalMetadata = @{ },

        [IO.Compression.CompressionLevel]
        # The compression level to use. The default is `Optimal`. Other values are `Fastest` (larger file, created faster) or `None` (nothing is compressed).
        $CompressionLevel = [IO.Compression.CompressionLevel]::Optimal
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $tempDir = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath ('{0}.{1}' -f ($OutFile | Split-Path -Leaf),([IO.Path]::GetRandomFileName()))
    New-Item -Path $tempDir -ItemType 'Directory' | Out-Null

    try
    {
        $upackJsonPath = Join-Path -Path $tempDir -ChildPath 'upack.json'

        [hashtable]$upackJson = $AdditionalMetadata.Clone()

        $upackJson['name'] =  $Name
        $upackJson['version'] = $Version

        if( -not $upackJson.ContainsKey('createdUsing') )
        {
            $psEdition = ''
            if( $PSVersionTable.ContainsKey('PSEdition') )
            {
                $psEdition = '; {0}' -f $PSVersionTable['PSEdition']
            }
            $upackJson['createdUsing'] = 'ProGetAutomation/{0} (PowerShell {1}{2})' -f (Get-Module -Name 'ProGetAutomation').Version,$PSVersionTable['PSVersion'],$psEdition
        }

        if( -not $upackJson.ContainsKey('createdDate') )
        {
            $upackJson['createdDate'] = (Get-Date).ToUniversalTime().ToString('O')
        }

        $parameterToMetadataMap = @{
                                        'GroupName' = 'group';
                                        'Title' = 'title';
                                        'ProjectUri' = 'projectUri';
                                        'IconUri' = 'iconUri';
                                        'Description' = 'description';
                                        'Tag' = 'tags';
                                        'Dependency' = 'dependencies';
                                        'Reason' = 'createdReason';
                                        'Author' = 'createdBy';
                                    }
        foreach( $parameterName in $parameterToMetadataMap.Keys )
        {
            if( -not $PSBoundParameters[$parameterName] )
            {
                continue
            }
        
            $metadataName = $parameterToMetadataMap[$parameterName]
            $upackJson[$metadataName] = $PSBoundParameters[$parameterName]
        }

        $upackJson | 
            ForEach-Object { [pscustomobject]$_ } |
            ConvertTo-Json -Depth 50 | 
            Set-Content -Path $upackJsonPath

        $archive = New-ZipArchive -Path $OutFile -CompressionLevel $CompressionLevel
        $upackJsonPath | Add-ZipArchiveEntry -ZipArchivePath $archive.FullName -CompressionLevel $CompressionLevel
        $archive
    }
    finally
    {
        Remove-Item -Path $tempDir -Recurse -Force -ErrorAction Ignore
    }
}