With Chocolatey and Windows PowerShell, we can build and install internal Chocolatey packages. Chocolatey has thousands of community-maintained packages anyone can use, but as I mentioned in my last post, sometimes you may need preconfigured or customized installers you want to host internally. Building these internal NuGet packages from our nuspec files is easy and standardizes the way you deploy applications in your organization.

In my previous post, we talked about the structure of our internal Chocolatey packages. This includes understanding the basics of the nuspec specification and the binary package manager Chocolatey.

As a reminder, here is our internal folder structure of the packages we need to generate. We have a top-level Packages folder followed by our package name. Under each package name, we have a nuspec XML file and a subfolder called tools that contains our chocolateyInstall.ps1 file.

|__Packages
	    |__MyLocalChocolateyPackage
	        |__MyLocalChocolateyPackage.nuspec
	        |__tools
	            |__chocolateyInstall.ps1
	    |__AnotherPackage
	        |__AnotherPackage.nuspec
	        |__tools
	            |__chocolateyInstall.ps1

Now that we understand the basics of our nuspec packages, we now need to call our New-LocalChocoPackage.ps1 script that will loop through a directory of packages and generate new NuGet package (nupkg) files.

<#
.SYNOPSIS
    Creates new internal Chocolatey package(s)
.DESCRIPTION
    Creates new internal Chocolatey package(s) from nuspec definition files
.EXAMPLE
    C:\choco_packages\source_packages\ contains the following package directories:
        OfficeProfessionalPlus2013x64
        OfficeProfessionalPlus2013x86
    
    And running the New-LocalChocoPackage command below...
    New-LocalChocoPackage -SourcePath C:\choco_packages\source_packages\  OutputPath C:\choco_packages\new_packages\


    ...will generate two nupkg files in C:\choco_packages\new_packages\:
        OfficeProfessionalPlus2013x64.2013.64.20180717.nupkg  
        OfficeProfessionalPlus2013x86.2013.64.20180717.nupkg  
#>
function New-LocalChocoPackage {
    [CmdletBinding(DefaultParameterSetName = 'Parameter Set 1',
        PositionalBinding = $false,
        HelpUri = 'http://www.microsoft.com/',
        ConfirmImpact = 'Medium')]
    Param (
        # Param1 help description
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Parameter Set 1')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [string]$SourcePath,


        # Param2 help description
        [Parameter(Mandatory = $true,
            Position = 1,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Parameter Set 1')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [string]$OutputPath
    )
    begin {
        Write-Verbose -Message 'Building local Chocolatey packages'


        if (-not (Test-Path $OutputPath)) {
            New-Item $OutputPath -Type Directory
            Write-Verbose -Message "Creating $OutputPath"
        }


        $pkgs = Get-ChildItem -Directory $srcPath
    }
    process {
        foreach ($pkg in $pkgs) {
            $pkgPath = "$SourcePath\$pkg"


            Push-Location $pkgPath
            Write-Verbose -Message "Trying to build NuGet package for $pkg"
            choco pack --outputdirectory "$OutputPath" --use-system-powershell
            if ($LASTEXITCODE -ne 0) {
                Write-Warning -Message "Failed to build Chocolatey package for $pkg"
                Write-Error -ErrorRecord $Error[0] 
                Pop-Location


                Exit -1
            }
            Pop-Location
        }
    }
    end {
        Write-Verbose -Message 'Successfully built Chocolatey packages'
    }
}

This PowerShell function takes two parameters: SourcePath and OutputPath. The SourcePath parameter is the root path of our package(s). The OutputPath is the location where we want to output our NuGet packages to.

The function first makes sure our OutputPath is available, and if not, it creates it. Next, it loops through our SourcePath packages (package folders) and uses the choco command-line interface (CLI) to build our NuGet packages.

We use the choco CLI utility to "pack" our nuspec definition file into a new NuGet package that ends in a .nupkg extension. This package is essentially a zipped container with a .nupkg extension. This package contains our definition file metadata as well as our chocolateyInstall.ps1 PowerShell script.

To easily inspect our .nupkg, we can use the NuGet Package Explorer, which we can install using Chocolatey:

choco install nugetpackageexplorer

After installing this, you can open the generated nupkg files using the Package Explorer to see their contents.

Compiled nupkg contents include chocolateyInstall

Compiled nupkg contents include chocolateyInstall

After we have generated our internal NuGet Packages, we can use the following PowerShell function to install them.

<#
.SYNOPSIS
    Installs internal Chocolatey package(s)
.DESCRIPTION
    Installs internal .nupkg Chocolatey package(s)
.EXAMPLE
    Install-LocalChocoPackage -PackageSource package-build-output-directory
#>
function Install-LocalChocoPackage {
    [CmdletBinding(DefaultParameterSetName = 'Parameter Set 1',
        PositionalBinding = $false,
        HelpUri = 'http://www.microsoft.com/',
        ConfirmImpact = 'Medium')]
    Param (
        # Param3 help description
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Parameter Set 1')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [string]$PackageSource
    )


    Write-Verbose -Message "Current package: $PackageName"


    $Packages = Get-ChildItem -Path $PackageSource


    foreach ($package in $Packages) {
        Write-Verbose -Message "choco install -y $($package.FullName)"
        choco install -y $($package.FullName)


        if ($LASTEXITCODE -eq 3010 -or $LASTEXITCODE -eq -2147205120) {
            Write-Information -Message 'Reboot pending...'
            Write-Information -Message 'Please reboot the machine and then rerun this script'


            Write-Information -Message 'ACTION REQUIRED -- reboot pending'


            Exit -1
        }
        elseif ($LASTEXITCODE -ne 0) {
            Write-Warning -Message "FAILED TO INSTALL $PackageName"
            Write-Error -ErrorRecord $Error[0]
            Exit -1
        }
    }
    Write-Verbose -Message 'Successfully built Chocolatey packages'

Install-LocalChocoPackage has a single mandatory parameter: PackageSource.

PackageSource is the location of the nupkgs you generated earlier. Typically, this parameter's value would be the same as the OutputPath you passed to the New-LocalChocoPackage function.

That's it! You've successfully built and installed your own internal Chocolatey packages. You are one step closer toward automating your third-party software installations on your systems.

Subscribe to 4sysops newsletter!

Chocolatey is extremely powerful on its own, but having the ability to generate your own internal packages puts Chocolatey on a whole new level.

0 Comments

Leave a reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2021

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account