Create Your Own Chocolatey Package: Basic Guide

While the official Chocolatey Community Repository boasts thousands of ready-to-use software packages, you’ll inevitably encounter situations where you need to manage applications not found there. This often includes internal tools, specific legacy versions of software, or applications requiring unique pre-configuration. This is where creating your own Chocolatey package becomes essential. Learning how to create Chocolatey package unlocks the ability to manage virtually any software on your Windows machines using Chocolatey’s powerful automation capabilities. Our goal in this basic tutorial is to guide you step-by-step through the process of building a simple package for a hypothetical piece of software, demonstrating the core concepts you’ll need.

Why Create Your Own Package?

There are several compelling reasons to invest time in learning how to build your own Chocolatey packages:

  • Manage Internal/Proprietary Software: Easily deploy and manage applications developed in-house or commercial software licensed specifically for your organization that isn’t publicly available.
  • Control Specific Versions: Pinpoint and deploy exact versions of software that might not be the latest on the Community Repository or are needed for compatibility reasons.
  • Apply Custom Configurations: Automate installations with specific settings, license keys, or feature selections tailored to your environment.
  • Standardize Deployment: Ensure reliable, repeatable, and automated deployment of your entire software catalog via a single tool.
  • Contribute to the Community: If you package public software that isn’t yet on the Community Repository or could be improved, you can share your work (after review).

Prerequisites

Before you begin creating your first package, ensure you have the following:

  • Chocolatey CLI Installed: You need Chocolatey working on your system. If not, follow the official installation guide.
  • Basic PowerShell Familiarity: The package installation logic is written in PowerShell. A basic understanding of scripting is helpful. You can find many PowerShell scripting guides online.
  • Administrator Privileges: While package creation doesn’t require admin rights, testing the installation and uninstallation of your package *does*.
  • The Software to Package: You need the installer file (.exe, .msi) or a portable archive (.zip, .7z) of the software you intend to package.

Understanding the Core Package Files

At its heart, a Chocolatey package is simply a NuGet package, which is essentially a standard .zip archive with a specific structure and a .nupkg file extension. This archive contains metadata about the software and scripts to automate its installation and management.

The two most critical files you’ll work with are:

  • The .nuspec file: This is an XML file containing the package’s metadata. It defines details like the package ID, version, title, description, authors, tags, dependencies, and crucially, which files from your package source directory should be included inside the final .nupkg archive. Think of this as the “what” of the package – what software, what version, what information.
  • The chocolateyInstall.ps1 script: This PowerShell script contains the logic that runs when someone executes choco install <your-package-id>. This is the “how” – how to find the installer, how to run it silently, how to handle different installation scenarios.

While there are other optional scripts like chocolateyUninstall.ps1 (for uninstall logic), chocolateyBeforeModify.ps1, etc., for a basic package, the .nuspec and chocolateyInstall.ps1 are the absolute core.

Step 1: Using choco new to Create a Template

Chocolatey provides a command to quickly generate the basic folder structure and template files needed for a new package: choco new.

The basic syntax is:

choco new <packageID> [options]

The <packageID> should be a unique identifier for your package, typically the name of the software. It should follow NuGet package ID conventions (lowercase, no spaces, dashes are okay). For this guide, let’s imagine we’re packaging a hypothetical internal tool called “MyInternalTool” with version 1.0.0.

Navigate to a directory where you want to create your package source files (e.g., C:\temp\MyPackages). Open a command prompt or PowerShell window in that location and run:

choco new MyInternalTool

Crucial Note: Running choco new does NOT require Administrator privileges.

Chocolatey will create a new folder named MyInternalTool in your current directory. Inside this folder, you’ll find a structure similar to this:

MyInternalTool\
├── MyInternalTool.nuspec
└── tools\
    ├── chocolateyInstall.ps1
    ├── chocolateyUninstall.ps1
    ├── ReadMe.md
    └── VERIFICATION.txt

This template includes boilerplate text and comments to guide you, along with useful helper functions within the PowerShell scripts to simplify common packaging tasks.

Step 2: Editing the .nuspec File

The next step is to customize the .nuspec file to accurately describe your package and specify which files to include. Open the MyInternalTool.nuspec file created in Step 1 using a text editor (like VS Code, Notepad++, or even Notepad).

You’ll see an XML structure. Here are the key elements you need to modify:

  • <id>: This should match the package ID you used with choco new (e.g., MyInternalTool).
  • <version>: Set this to the version of the software you are packaging (e.g., 1.0.0). Follow NuGet versioning standards.
  • <title>: A human-friendly title for the package (e.g., My Internal Tool Application).
  • <authors>: Your name or company name.
  • <description>: A brief explanation of what the package installs.
  • <tags>: Space-separated keywords to help categorize and find your package (e.g., internal tool company application).
  • <projectUrl> and <packageSourceUrl>: Links to the software’s homepage and the package’s source code/repository. While less critical for purely internal packages, it’s good practice to fill these if applicable.
  • <dependencies>: (Optional) If your software requires other software (like a specific .NET runtime or Visual C++ redistributable) that can also be installed via Chocolatey, you list those package IDs and versions here. This ensures they are installed automatically when your package is installed. For this basic guide, we’ll assume no dependencies, but you can learn more in the official documentation on dependencies.
  • <files>: This is crucial! This section tells Chocolatey which files from your package source directory (the MyInternalTool folder) should be included in the final .nupkg file. You need to include the software installer or portable archive here. The target="tools" attribute is standard practice for placing installer files or portable applications into the tools subdirectory within the package, which corresponds to the $ToolsDir variable in the install script.

Here’s a simplified example of what your MyInternalTool.nuspec might look like after editing. We’ll assume our installer file is named MyInternalToolSetup.exe and we’ve placed it inside the MyInternalTool\tools folder before editing the nuspec.

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/chocolatey.xsd">
  <metadata>
    <id>MyInternalTool</id>
    <version>1.0.0</version>
    <title>My Internal Tool Application</title>
    <authors>Your Name/Company</authors>
    <description>Installs the internal My Tool application version 1.0.0.</description>
    <tags>internal tool company</tags>
    <-- Optional: If MyInternalTool needs .NET 6 runtime -->
    <-- <dependencies>
       <dependency id="dotnetcore-runtime" version="6.0.0" />
    </dependencies> -->
  </metadata>
  <files>
    <-- Include the installer file located in the tools directory -->
    <file src="tools\MyInternalToolSetup.exe" target="tools" />
    <-- Or if it's a zip: -->
    <-- <file src="tools\MyInternalTool.zip" target="tools" /> -->
    <-- Include the install script itself (usually already there from choco new) -->
    <file src="tools\chocolateyInstall.ps1" target="tools" />
  </files>
</package>

Save the .nuspec file after making your changes. Ensure the XML syntax is valid.

Step 3: Writing the chocolateyInstall.ps1 Script

This is where you define the actual installation logic. Open the chocolateyInstall.ps1 file located in the MyInternalTool\tools folder. The template contains many comments and examples of Chocolatey helper functions designed to simplify common tasks like downloading files, installing MSIs, extracting archives, and adding executables to the PATH.

Your primary goal in this script is to find the installer file (which you included in the <files> section of the .nuspec and will be available locally during installation) and execute it with the correct silent arguments so it installs without user interaction.

The template script defines a variable $ToolsDir which points to the directory where the package’s tools content is extracted during installation (e.g., C:\ProgramData\chocolatey\lib\MyInternalTool\tools). You’ll use this variable to locate your installer file.

Scenario A: Packaging an Installer (.exe, .msi)

If you are packaging a traditional installer file (like a .exe or .msi), you need to execute it silently. Finding the correct silent arguments is often the trickiest part; it varies greatly between software installers. Common arguments include /S, /quiet, /qn (for MSI), /norestart, etc. You might need to consult the software’s documentation or use tools to discover these arguments. Chocolatey has resources on finding silent installation arguments.

You’ll typically use the Install-Process helper function for .exe installers or Install-MsiPackage for .msi installers. Both are designed to run processes and handle standard exit codes.

# This is a basic example for an EXE installer
$ErrorActionPreference = 'Stop' # Ensure script stops on errors

# Define the path to the installer file within the package's tools directory
$installer = "$($ToolsDir)\MyInternalToolSetup.exe"

# Define the silent arguments for the installer
# *** IMPORTANT: Replace "/S /ANYOTHERARGUMENTS" with the actual silent arguments for YOUR software ***
$silentArgs = "/S /D=C:\Program Files\MyInternalTool" # Example: /S for silent, /D for install directory
Write-Host "Installing MyInternalTool silently from $installer..."
# Use Install-Process helper function for .exe installers
# -FilePath: Path to the executable
# -SilentArgs: The silent arguments you found
# -WorkingDirectory: Set the working directory for the installer process (often the tools dir)
# -Passthru | Out-Null: Capture the exit code but suppress verbose output from the helper
Install-Process -FilePath $installer -SilentArgs $silentArgs -WorkingDirectory $ToolsDir -Passthru | Out-Null

# Check the exit code from the installation process
# Install-Process and Install-MsiPackage set $LASTEXITCODEif 
($LASTEXITCODE -ne 0)
{
     # If the exit code is not 0 (which usually indicates success), throw an error
     throw "MyInternalTool installation failed with exit code $LASTEXITCODE."
}
Write-Host "MyInternalTool installation successful."

# Optional: If the software installs an executable you want available in the PATH,
# use Install-BinFile to create a 'shim' in the Chocolatey bin directory.
# This makes the executable available from any command prompt without modifying the system PATH directly.
# Get-BinRoot 
# Get the location where choco puts executable shims (e.g., C:\ProgramData\chocolatey\bin)
# Assume MyInternalTool installs to C:\Program Files\MyInternalTool and the executable is mytool.exe
# Install-BinFile -Name "mytool" -Path "$($env:ProgramFiles)\MyInternalTool\mytool.exe" 
# Example shim creation

Key parts explained:

  • $ErrorActionPreference = 'Stop': A good practice to make the script fail immediately on non-terminating errors.
  • $installer = "$($ToolsDir)\MyInternalToolSetup.exe": Constructs the full path to your installer file using the $ToolsDir variable.
  • $silentArgs = "...": Variable holding the silent arguments. **You MUST replace this with the actual arguments for your specific software.**
  • Install-Process / Install-MsiPackage: Chocolatey helper functions that wrap the process execution and handle logging and exit codes.
  • -FilePath: Specifies the installer executable.
  • -SilentArgs: Passes the arguments to the installer.
  • -WorkingDirectory: Sets the directory where the installer runs from.
  • -Passthru | Out-Null: Used with Install-Process to get the exit code into $LASTEXITCODE without printing the helper’s output to the console during installation.
  • $LASTEXITCODE: A built-in PowerShell variable holding the exit code of the last process that ran. A non-zero value usually indicates an error.
  • throw "...": Halts the script and reports an error to Chocolatey, causing the installation to fail gracefully.
  • Install-BinFile: A helper to create executable shims, making your installed software accessible from the command line via a simple name (e.g., mytool).

Scenario B: Packaging a Portable Archive (.zip, .7z)

If you’re packaging a portable application distributed as a .zip or .7z file, your script needs to extract the contents to a suitable location on the user’s system (e.g., C:\Program Files\MyInternalTool or C:\tools\MyInternalTool). You can use the Install-ZipPackage or Install-7ZipPackage helper functions.

# This is a basic example for a ZIP archive
$ErrorActionPreference = 'Stop' 

# Ensure script stops on errors
# Define the path to the archive file within the package's tools directory
$archiveFile = "$($ToolsDir)\MyInternalTool.zip"

# Define the desired installation directory on the user's system# Choose a standard location like Program Files or a dedicated tools directory
$installDir = "$($env:ProgramFiles)\MyInternalTool" # Example install location
Write-Host "Extracting MyInternalTool to $installDir..."

# Use Install-ZipPackage helper function
# -Url: Specify the source archive file path (use the local path here)
# -Destination: Specify the directory where the contents should be extracted
Install-ZipPackage -Url $archiveFile -Destination $installDir

# Check if the extraction was successful by verifying the destination directory exists
# Install-ZipPackage does not reliably set $LASTEXITCODE for extraction success/failure
if (-not (Test-Path $installDir))
{
     throw "MyInternalTool extraction failed. Installation directory '$installDir' not found."
}
Write-Host "MyInternalTool extraction successful."

# Optional: Add executable to PATH using Shimgen
# Assume the main executable is MyInternalTool.exe inside the extracted folder
Install-BinFile -Name "mytool.exe" -Path "$($installDir)\MyInternalTool.exe"

Key parts explained:

  • $archiveFile = "$($ToolsDir)\MyInternalTool.zip": Path to the archive file within the package.
  • $installDir = "...": The target directory on the system where the software should reside. Use environment variables like $($env:ProgramFiles) or $($env:SystemDrive) for standard locations.
  • Install-ZipPackage / Install-7ZipPackage: Helpers to extract archives. Note that the -Url parameter can accept local file paths in this context.
  • -Destination: The folder where the archive contents will be placed.
  • Test-Path $installDir: A PowerShell cmdlet to check if a file or directory exists. Used here to verify extraction success.
  • Install-BinFile: Again, useful for creating shims for portable application executables.

Remember to save your chocolateyInstall.ps1 script after making your changes. Delete the commented-out template code you don’t need to keep the script clean.

Step 4: Packaging with choco pack

Once you’ve edited your .nuspec file and written your chocolateyInstall.ps1 script, you’re ready to package everything into the final .nupkg file.

Navigate in your command prompt or PowerShell to the root directory of your package source (the MyInternalTool folder, NOT the tools folder). This is the directory containing the .nuspec file.

Run the command:

choco pack

Crucial Note: Running choco pack does NOT require Administrator privileges.

Chocolatey will read the .nuspec file, collect the files specified in the <files> section, and create a .nupkg file in the current directory. The file name will be <packageID>.<version>.nupkg (e.g., MyInternalTool.1.0.0.nupkg).

Step 5: Testing Your Package

Before distributing your package, it is absolutely critical to test it thoroughly on a clean machine or virtual machine that mimics your target environment. You need to verify that it installs correctly and that the software works as expected.

Navigate in your command prompt or PowerShell to the directory containing the .nupkg file you just created.

Run the following command to install your local package:

choco install MyInternalTool -s . --version 1.0.0 -y

Crucial Note: Testing installation/uninstallation does require Administrator privileges.

  • MyInternalTool: The ID of the package to install.
  • -s .: Specifies the source for the package. The dot (.) means “the current directory”. This tells Chocolatey to look for the .nupkg file locally instead of on a remote feed.
  • --version 1.0.0: Explicitly requests a specific version. Good practice for testing.
  • -y: Automatically confirms any prompts. Essential for automated testing and deployment. Learn more about silent installation.

After running the command, check:

  • Did Chocolatey report a successful installation?
  • Is the software installed in the correct location?
  • Does the software launch and function as expected?
  • If you used Install-BinFile, can you run the software’s executable by name from a new command prompt?

Also, test the uninstallation process:

choco uninstall MyInternalTool -y

Verify that the software is correctly removed from the system and that installation directories are cleaned up (as much as the software’s uninstaller allows).

Repeat testing as you make changes to your .nuspec or chocolateyInstall.ps1.

Step 6: Sharing or Deploying Your Package

Once you have a tested and working .nupkg file, you need to make it available to the machines that will install it. How you do this depends on whether it’s a public package or an internal one.

  • Internal Deployment: For internal tools, you’ll typically add the package to an internal Chocolatey feed. This can be a simple file share, an HTTP feed, or a dedicated repository manager (like Nexus or Artifactory). You use the choco push command to upload your package to the feed.

Example pushing to an internal feed URL:

choco push MyInternalTool.1.0.0.nupkg -s https://your-internal-feed/nuget/

You might need to provide an API key or credentials depending on the feed’s configuration. Pushing to a feed often requires Administrator rights depending on the target location and permissions.

  • Community Repository: If you’ve packaged publicly available software, you can submit it to the Chocolatey Community Repository. Your package will go through a moderation process to ensure it follows community guidelines and is safe for users.

Sharing the .nupkg file directly is possible but not recommended for managing software across many machines, as it bypasses Chocolatey’s feed capabilities.

Conclusion

You’ve now learned the fundamental steps to create Chocolatey package: use choco new to get a template, edit the .nuspec file to define package metadata and included files, write the chocolateyInstall.ps1 script using helper functions to automate the installation, use choco pack to build the .nupkg file, and finally, test your package locally before sharing or deploying it.

This basic guide provides the foundation. Chocolatey package creation can involve more advanced concepts like uninstall scripts, package parameters, checksums, and more complex installation scenarios, but the core process remains the same. By mastering these basics, you gain the power to bring any software under Chocolatey’s automated management umbrella. We encourage you to pick a simple, freely redistributable portable application (like a Sysinternals tool) and try packaging it yourself as a hands-on exercise!