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.

8 Comments
  1. Josef 6 months ago

    Hi

    First at all, you are missing a ‘}’ at the end. Secondly, how about an example on how to use this? I’m a newbie in PowerShell. I tried saving your script into a filed called: “install-local.ps1”.

    Inside that file I put this at the end:
    Install-LocalChocoPackage “C:\Users\my_user\Desktop\local_choco\paint.net.portable”

    I previously downloaded paint.net.portable from here:
    https://github.com/SebastianK90/chocolateyautomaticpackages/tree/master/paint.net.portable

    Then I ran it as follows:
    Set-ExecutionPolicy AllSigned
    Set-ExecutionPolicy Bypass -Scope Process -Force; .\install-local.ps1

    But now, I’m getting the error at the bottom of this message.

    How can I solve it?

    Best regards
    Josef

    Chocolatey v1.3.1
    Installing the following packages:
    C:\Users\my_user\Desktop\local_choco\paint.net.portable\legal
    By installing, you accept licenses for the packages.

    Chocolatey installed 0/0 packages.
    See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).
    Second path fragment must not be a drive or UNC name.
    Parameter name: path2
    WARNING: FAILED TO INSTALL
    Write-Error : Cannot bind parameter ‘ErrorRecord’. Cannot convert the “System.Management.Automation.ParseException: At line:5 char:2
    + + .\install-local.ps1
    + ~
    Missing expression after unary operator ‘+’.
    At line:5 char:3
    + + .\install-local.ps1
    + ~~~~~~~~~~~~~~~~~~~
    Unexpected token ‘.\install-local.ps1’ in expression or statement.
    At line:6 char:2
    + + ~~~~~~~~~~~~~~~~~~~
    + ~
    Missing expression after unary operator ‘+’.
    At line:6 char:3
    + + ~~~~~~~~~~~~~~~~~~~
    + ~~~~~~~~~~~~~~~~~~~
    Unexpected token ‘~~~~~~~~~~~~~~~~~~~’ in expression or statement.
    At line:7 char:6
    + + CategoryInfo : SecurityError: (:) [], PSSecurityExcept …
    + ~
    Missing expression after unary operator ‘+’.
    At line:7 char:7
    + + CategoryInfo : SecurityError: (:) [], PSSecurityExcept …
    + ~~~~~~~~~~~~
    Unexpected token ‘CategoryInfo’ in expression or statement.
    At line:8 char:6
    + + FullyQualifiedErrorId : UnauthorizedAccess
    + ~
    Missing expression after unary operator ‘+’.
    At line:8 char:7
    + + FullyQualifiedErrorId : UnauthorizedAccess
    + ~~~~~~~~~~~~~~~~~~~~~
    Unexpected token ‘FullyQualifiedErrorId’ in expression or statement.
    at System.Management.Automation.AutomationEngine.ParseScriptBlock(String script, String fileName, Boolean interactiveCommand)
    at System.Management.Automation.Runspaces.Command.CreateCommandProcessor(ExecutionContext executionContext, CommandFactory commandFactory, Boolean addToHistory, CommandOrigin origin)
    at System.Management.Automation.Runspaces.LocalPipeline.CreatePipelineProcessor()
    at System.Management.Automation.Runspaces.LocalPipeline.InvokeHelper()
    at System.Management.Automation.Runspaces.LocalPipeline.InvokeThreadProc()” value of type “System.Management.Automation.ParseException” to type “System.Management.Automation.ErrorRecord”.
    At C:\Users\my_user\Desktop\local_choco\install-local.ps1:49 char:38
    + Write-Error -ErrorRecord $Error[0]
    + ~~~~~~~~~
    + CategoryInfo : InvalidArgument: (:) [Write-Error], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.WriteErrorCommand

    • Josef 6 months ago

      Sorry, I just saw that there is an example on the source comments. I will try it that way

    • I added the missing }. Thanks!

      I wonder why your error occurred in line 5 because in the script of the article line 5 only contains a comment.

      • Josef 6 months ago

        I guess you have to count after the comments

      • Josef 6 months ago

        But anyway, I ran this differently:
        Install-LocalChocoPackage -PackageSource “C:\Users\my_user\Desktop\local_choco\paint.net.portable”

        Then I’m getting this:

        Chocolatey v1.3.1
        Installing the following packages:
        C:\Users\my_user\Desktop\local_choco\paint.net.portable\legal
        By installing, you accept licenses for the packages.

        Chocolatey installed 0/0 packages.
        See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).
        Second path fragment must not be a drive or UNC name.
        Parameter name: path2
        WARNING: FAILED TO INSTALL
        Install-LocalChocoPackage : Cannot bind parameter ‘ErrorRecord’. Cannot convert the “System.Management.Automation.ParseException: At line:5 char:2
        + + .\install-local.ps1
        + ~
        Missing expression after unary operator ‘+’.
        At line:5 char:3
        + + .\install-local.ps1
        + ~~~~~~~~~~~~~~~~~~~
        Unexpected token ‘.\install-local.ps1’ in expression or statement.
        At line:6 char:2
        + + ~~~~~~~~~~~~~~~~~~~
        + ~
        Missing expression after unary operator ‘+’.
        At line:6 char:3
        + + ~~~~~~~~~~~~~~~~~~~
        + ~~~~~~~~~~~~~~~~~~~
        Unexpected token ‘~~~~~~~~~~~~~~~~~~~’ in expression or statement.
        At line:7 char:6
        + + CategoryInfo : SecurityError: (:) [], PSSecurityExcept …
        + ~
        Missing expression after unary operator ‘+’.
        At line:7 char:7
        + + CategoryInfo : SecurityError: (:) [], PSSecurityExcept …
        + ~~~~~~~~~~~~
        Unexpected token ‘CategoryInfo’ in expression or statement.
        At line:8 char:6
        + + FullyQualifiedErrorId : UnauthorizedAccess
        + ~
        Missing expression after unary operator ‘+’.
        At line:8 char:7
        + + FullyQualifiedErrorId : UnauthorizedAccess
        + ~~~~~~~~~~~~~~~~~~~~~
        Unexpected token ‘FullyQualifiedErrorId’ in expression or statement.
        at System.Management.Automation.AutomationEngine.ParseScriptBlock(String script, String fileName, Boolean interactiveCommand)
        at System.Management.Automation.Runspaces.Command.CreateCommandProcessor(ExecutionContext executionContext, CommandFactory commandFactory, Boolean addToHistory, CommandOrigin origin)
        at System.Management.Automation.Runspaces.LocalPipeline.CreatePipelineProcessor()
        at System.Management.Automation.Runspaces.LocalPipeline.InvokeHelper()
        at System.Management.Automation.Runspaces.LocalPipeline.InvokeThreadProc()” value of type “System.Management.Automation.ParseException” to type “System.Management.Automation.ErrorRecord”.
        At C:\Users\my_user\Desktop\local_choco\install-local.ps1:56 char:1
        + Install-LocalChocoPackage -PackageSource “C:\Users\my_user\Desktop\loc …
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : InvalidArgument: (:) [Write-Error], ParameterBindingException
        + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Install-LocalChocoPackage

      • Josef 6 months ago

        Ok, I found the source of your problem. You can’t use a path on the “install” argument. You need to specify either a path to a .nupkg or .nuspec file, which is now deprecated. Or you use the –source option

        On my example, to install paint.net.portable from a local source, I just did:
        choco install paint.net.portable –source “C:\Users\my_user\Desktop\local_choco\packages\paint.net.portable”

        So, I guess you will have to modify your script in order to make it work.

        Since I only want to install a package, this is enough for me.

        Best regards
        Josef

Leave a reply

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

*

© 4sysops 2006 - 2023

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