The Chocolatey Package Internalizer feature available in Chocolatey For Business (C4B) allows you to consume a package from the Chocolatey community repository, and rewrite the packaging to be completely offline.
This allows for the complete offline installation of that package. This is essential for business users where uptime matters, and reliance on a 3rd party can’t be trusted. For more information on why organizations should maintain their own repository of Chocolatey packages, see here.
This process works wonderfully, and is often unnoticed if your endpoints have a direct connection to the internet, or you are not leveraging Chocolatey GUI. This is because the icons are retrieved from the <iconUrl>
field inside of the nuspec file. There are many reasons why icon files could potentially not be loaded.
- Air gapped networks
- Restrictive content filter
- Proxy configuration
- Etc etc etc
This provides a less than ideal experience for users of Chocolatey GUI when using Tile View or when on the Details page of a particular package inside the application.
You may also wish to maintain the icons in your own infrastructure as it again removes a level of reliance on 3rd party resources for your Chocolatey Packages. The following blog post will walk you through setting up the necessary repository infrastructure, and provide code you can use to accomplish bringing icons for packages internal.
Configuring Your Repository Server
We’ll be using Sonatype Nexus in this blog post. They use the term "Raw"
for a repository that can hold any file type. Your repository server may use another term like "Generic"
, but it is the same thing. Our Nexus repository will have a hostname of nexus.fabrikam.com
, and connect over http on port 8081.
Steps:
- Login to Nexus
- Click the Gear Icon in the top nav bar
- Select
Repository
- Select
Raw (hosted)
for the repository type - Give the repository a name, select the appropriate blob store (default in our example), and apply any other policies you need.
- Click
Create Repository
Gif Overview
The Script
Here’s the code you’ll need to get this working in your environment. Once saved, you can execute it like this (I’ll use splatting to keep things readable):
You’ll need the following parameters:
InternalizeDownloadPath
name: InternalizerDownloadPath
type: string
mandatory: true
description: This is the path to the download folder created when you run something like 'choco download vlc --internalize'.
IconRepository
name: IconRepository
type: string
mandatory: true
description: The raw (Generic) repository you wish to push your icon files too.
PackageRepository
name: PackageRepository
type: string
mandatory: true
description: The Nuget repository where you store your chocolatey packages. The script will push the updated packages to this location.
Credential
name: Credential
type: System.Management.Automation.PSCredential
mandatory: true
description: This is a set of credentials with access to push items to both your Icon and Package repositories in your repository server.
ApiKey
name: ApiKey
type: string
mandatory: false
description: The api key used to push nuget packages to your repository. If you have used `choco apikey` to add your api key to your Chocolatey config, this is not necessary. Otherwise, please provide an api key with this parameter.
Complete Script
<#
.SYNOPSIS
Internalize package icons for internalized packages
.PARAMETER InternalizerDownloadPath
This is the path to the download folder created when you run something like 'choco download vlc --internalize'
.PARAMETER IconRepository
The raw (Generic) repository you wish to push your icon files too
.PARAMETER PackageRepository
The Nuget repository where you store your chocolatey packages. The script will push the updated packages to this location
.PARAMETER Credential
This is a set of credentials with access to push items to both your Icon and Package repositories in your repository server
.PARAMETER ApiKey
The apikey used to push nuget packages to your repository. If you have used `choco apikey` to add your api key to your Chocolatey config, this is not necessary. Otherwise, please provide an api key with this parameter.
.EXAMPLE
$params = @{
InternalizerDownloadPath = 'C:\internalized\download\'
IconRepository = 'http://nexus.fabrikam.com:8081/repository/icons/'
PackageRepository = 'http://nexus.fabrikam.com:8081/repository/choco/'
}
.\InternalizePackageIcons.ps1 @params
.NOTES
Run this script AFTER you have internalized your packages, and point it at the 'download' folder created during that process
#>
[cmdletBinding()]
param(
[Parameter(Mandatory)]
[String[]]
$InternalizerDownloadPath,
[Parameter(Mandatory)]
[String]
$IconRepository,
[Parameter(Mandatory)]
[String]
$PackageRepository,
[Parameter(Mandatory)]
[PSCredential]
$Credential,
[Parameter()]
[String]
$ApiKey
)
process {
$nuspecs = $(Get-ChildItem $InternalizerDownloadPath -Recurse -Include *.nuspec,chocolateyInstall.ps1)
Write-Verbose "Downloading icons and replacing values in nuspec files"
foreach ($nuspec in $nuspecs) {
[xml]$xml =$nuspec | Where-Object { $_.Extension -eq '.nuspec' } | Get-Content
$iconurl = $xml.package.metadata.iconUrl
$icon = ($iconurl -split ('/'))[-1]
$iconPath = Join-Path 'C:\icons' "$icon"
if ($iconurl) {
$null = Invoke-WebRequest -Uri $iconurl -OutFile "$($iconPath)" -ErrorAction SilentlyContinue
$user = $Credential.UserName
$password = $Credential.GetNetworkCredential().Password
$credPair = "{0}:{1}" -f $user, $password
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::Utf8.GetBytes($credPair))
$params = @{
Headers = @{
Authorization = "Basic $encodedCreds"
}
UseBasicParsing = $true
ContentType = 'text/plain'
}
if ($iconPath -eq 'C\icons\') {
$null
}
else {
$newUrl = "$($IconRepository)$icon"
Write-Verbose "Uploading: $iconPath"
$null = Invoke-WebRequest -Uri $newUrl -Method Put -infile $iconPath @params -ErrorAction SilentlyContinue
#Write new URL
$xml.package.metadata.iconUrl = $newUrl
$xml.Save($($nuspec.FullName))
$Script:RepackageDirectory = Split-Path -Parent -Path $InternalizerDownloadPath
$chocoPackArgs = @('pack',"$($nuspec.FullName)","--output-directory='$RepackageDirectory'")
& choco @chocoPackArgs
}
}
}
Write-Verbose "Uploading modified packages to repository"
Get-ChildItem $RepackageDirectory -Recurse -Filter *.nupkg | Foreach-Object {
$chocoPushArgs = @('push',"$($_.FullName)","--source='$PackageRepository'")
if($ApiKey){
$chocoPushArgs += "--api-key='$ApiKey'"
}
if($($PackageRepository.Split(':')[0]) -match 'http'){
$chocoPushArgs += '--force'
}
& choco @chocoPushArgs
}
Remove-Item C:\icons -Recurse -Force
}
Example
This is an example of internalizing icons using the information from this blog post as parameter values. Please update accordingly in your own environments.
$params = @{
InternalizerDownloadPath = 'C:\packages\download'
IconRepository = 'http://nexus.yourcompany.com:8081/repository/icons/'
PackageRepository = 'http://nexus.yourcompany.com:8081/repository/ChocolateyPackages/'
Credential = (Get-Credential)
}
. .\InternalizePackageIcons.ps1 @params
Conclusion
You made it! If you followed along, you should now have a repository where you can store your package icons, and all the code necessary to make your Chocolatey packages completely, totally, 100%, without question offline. Thanks for reading, and come back next time when the Mad Scientist strikes again!
Share On
Posted In
Popular Tags
- #news 70 Number of post with tag news
- #press release 57 Number of post with tag press release
- #chocolatey for business 44 Number of post with tag chocolatey for business
- #packaging 21 Number of post with tag packaging
- #open source 17 Number of post with tag open source
- #community 15 Number of post with tag community
- #tutorial 14 Number of post with tag tutorial
- #12 days of Chocolatey 2023 12 Number of post with tag 12 days of Chocolatey 2023
- #chocolatey community repository 12 Number of post with tag chocolatey community repository
- #podcast 11 Number of post with tag podcast