-
Notifications
You must be signed in to change notification settings - Fork 71
Coding Conventions
We use GitHub Actions to run several checks and tests in every pull request (PR). Check the ci.yml workflow. Only PRs where all test succeed can be merged.
The linter scripts in the test folder can be useful for local testing.
Packages should depend on the package common.vm
and import the module vm.common
near the top of the code via:
Import-Module vm.common -Force -DisableNameChecking
This module, packages/common.vm/tools/vm.common/vm.common.psm1, defines functions that start with VM-
to reuse code amongst packages and make package creation easier.
The package template generation script create_package_template.py can automate most of the initial file setup. In most cases this should be your starting point.
Use this standard template to ensure all errors are properly logged.
$ErrorActionPreference = 'Stop'
Import-Module vm.common -Force -DisableNameChecking
try {
...YOUR_CODE_HERE...
} catch {
VM-Write-Log-Exception $_
}
For executables that are downloaded as a ZIP file from a URL and does not require any special installation steps, use the VM-Install-From-Zip
function from vm.common.psm1. For example, check the capa installer.
NOTE: This assumes there is an executable after unzipping named
$toolName.exe
.
$ErrorActionPreference = 'Stop'
Import-Module vm.common -Force -DisableNameChecking
$toolName = 'TOOL-NAME'
$category = 'CATEGORY-NAME'
$zipUrl = "ZIP-URL"
$zipSha256 = "ZIP-SHA265-HASH"
VM-Install-From-Zip $toolName $category $zipUrl -zipSha256 $zipSha256 -consoleApp $true
If your ZIP file unzips to a single folder and this folder contains all the tools (this is commonly seen with ZIPs from GitHub), use the flag -innerFolder $true
as follows:
$ErrorActionPreference = 'Stop'
Import-Module vm.common -Force -DisableNameChecking
$toolName = 'TOOL-NAME'
$category = 'CATEGORY-NAME'
$zipUrl = "ZIP-URL"
$zipSha256 = "ZIP-SHA265-HASH"
VM-Install-From-Zip $toolName $category $zipUrl -zipSha256 $zipSha256 -innerFolder $true
If you perform additional instructions before or after VM-Install-From-Zip
(you've deviated from the ZIP template), surround all the code after Import-Module vm.common -Force -DisableNameChecking
in a try/catch
as seen in the standard template. For instance:
$ErrorActionPreference = 'Stop'
Import-Module vm.common -Force -DisableNameChecking
try {
$toolName = 'die'
$category = 'Utilities'
$zipUrl = 'https://github.com/horsicq/DIE-engine/releases/download/3.02/die_win32_portable_3.02.zip'
$zipSha256 = '00e01b28e50d32673d59d09f1e33457639a722aabec9f12c832e738e104fdf5b'
$zipUrl_64 = 'https://github.com/horsicq/DIE-engine/releases/download/3.02/die_win64_portable_3.02.zip'
$zipSha256_64 = '1ffd192e0f8120691e5c2c018c05245c6761d8aa01695807257044c82a676f27'
$executablePath = (VM-Install-From-Zip $toolName $category $zipUrl -zipSha256 $zipSha256 -zipUrl_64 $zipUrl_64 -zipSha256_64 $zipSha256_64 -innerFolder $true)[-1]
VM-Add-To-Right-Click-Menu $toolName "detect it easy (DIE)" "`"$executablePath`" `"%1`"" "file"
} catch {
VM-Write-Log-Exception $_
}
For software that does not require any special uninstall steps, use the VM-Uninstall
function. For example, check the capa uninstaller.
$ErrorActionPreference = 'Continue'
Import-Module vm.common -Force -DisableNameChecking
$toolName = 'TOOL-NAME'
$category = 'CATEGORY-NAME'
VM-Uninstall $toolName $category
Package installers should set their error preferences to Stop
when possible to ensure Chocolatey is notified of errors and that the packaged failed to install. This should be the first line of an installer (e.g., chocolateyinstall.ps1
):
$ErrorActionPreference = 'Stop'
Where possible, leverage the function VM-Assert-Path
to assert specific paths exist in order to fail the package as quickly as possible. For instance, after downloading a tool check that the file path to the tool exists:
# Download and unzip
$url = "https://github.com/mandiant/capa/releases/download/v1.6.3/capa-v1.6.3-windows.zip"
$checksum = "00e8d32941b3a1a58a164efc38826099fd70856156762647c4bbd9e946e41606"
$packageArgs = @{
packageName = ${Env:ChocolateyPackageName}
unzipLocation = $toolDir
url = $url
checksum = $checksum
checksumType = 'sha256'
}
Install-ChocolateyZipPackage @packageArgs
VM-Assert-Path (Join-Path $toolDir "capa.exe")
If joining a path via Join-Path
that you expect to immediately exist, suffix it with -Resolve
to have PowerShell check to see if the path exists and throw an error if it doesn't. For example:
$toolDir = Join-Path ${Env:RAW_TOOLS_DIR} 'x64dbg\release' -Resolve
Additionally, check for a few expected files to ensure installation succeeded. For example:
# Check for a few expected files to ensure installation succeeded
Join-Path $toolDir 'x64dbgpy.h' -Resolve | Out-Null
# -OR-
VM-Assert-Path (Join-Path $toolDir 'x64dbgpy.h')
When uninstalling a package we do our best effort to remove everything related to the package. But this is not always possible (at least not in an easy way). Because of that, the uninstall shouldn't fail if any of the steps fail. This should be the first line of an uninstaller (e.g., chocolateyuninstall.ps1
):
$ErrorActionPreference = 'Continue'
Changing a tool of category may cause problems with updates. We discuss the package category when adding a package and we do our best effort to not change categories often and only when it really makes sense. For example:
- We made a mistake and the category needs to be corrected
- We have man tools that would qualify for a new category and help making existent categories easier to navigate.
Note also that we are not testing updates, only new installations. Please if updating fails, make a fresh install.
If installing the contents of a ZIP file (i.e., using Install-ChocolateyZipPackage
) preface the installation with the line of code below to assist potential upgrades:
VM-Remove-PreviousZipPackage ${Env:chocolateyPackageFolder}
The helper function above reads a .txt
file within the Chocolatey package folder that lists line by line the location of each file copied to disk from the installed ZIP file and deletes each file.
Below are common variables used throughout most of our chocolateyinstall.ps1
files. Please reuse these names so each installer file feels and reads similar.
The name of the tool being installed, normally different from the package name. For example fakenet-ng.vm
(package name) vs FakeNet-NG
(tool name).
For example:
$toolName = 'FakeNet-NG'
Represents the directory where the tool is actually installed. Typically, a subdirectory inside the tools directory (i.e., ${Env:RAW_TOOLS_DIR}
); however, this can also be somewhere like ${Env:ProgramFiles}
.
For example:
$toolDir = Join-Path ${Env:RAW_TOOLS_DIR} $toolName
The tool category. It needs to be set to a category in categories.txt. For example:
$category = 'Utilities'
If the package doesn 't create a shortcut in the tools directory (for example if it only installs libraries), then the package needs to be excluded in the UsesInvalidCategory
linter.
Path to a subdirectory inside the tools shortcuts directory (i.e., ${Env:TOOL_LIST_DIR}
). The subdirectory includes the tool category.
For example:
$shortcutDir = Join-Path ${Env:TOOL_LIST_DIR} $category
The .LNK
shortcut file that opens the tool. This file is located in the $shortcutDir
.
For example:
$shortcut = Join-Path $shortcutDir "$toolName.lnk"
Arguments used to call Install-ChocolateyZipPackage
or Install-ChocolateyInstallPackage
.
For example:
$packageArgs = @{
packageName = ${Env:ChocolateyPackageName}
unzipLocation = $toolDir
url = $url
checksum = $checksum
checksumType = 'sha256'
}
Install-ChocolateyZipPackage @packageArgs
Some tools are command-line tools. The current method to execute command-line tools is to use a proxy such as cmd.exe
. Below are the variables and an example showing how to execute a tool via a proxy.
Path to the tool's executable file. Generally used as the target of a shortcut and tool's file path added to the PATH.
- If your tool is a Python script, you should use the path to Python like such:
$executablePath = (Get-Command python).Source
Path to the tool's icon. If the tool executable has an icon within its resource section, you can reuse the tool's path (i.e., $executablePath
).
Proxy to execute the tool's executable -- typically cmd.exe
.
Working directory to execute the tool from.
Additional command-line arguments to pass to the tool. For example:
$executablePath = Join-Path $toolsDir 'capa.exe' -Resolve
$executableIcon = $executablePath
$executableCmd = Join-Path ${Env:WinDir} "system32\cmd.exe"
$executableDir = Join-Path ${Env:UserProfile} "Desktop"
$executableArgs = "/K `"cd `"$executableDir`" && `"$executablePath`" --help`""
$shortcut = Join-Path $shortcutDir 'capa.lnk'
Install-ChocolateyShortcut -shortcutFilePath $shortcut -targetPath $executableCmd -Arguments $executableArgs -WorkingDirectory $executableDir -IconLocation $executableIcon
VM-Assert-Path $shortcut
When using a file path variable as an argument to an external program, properly quote the path so that it will work correctly even if it has spaces in the path. For example:
$executableArgs = "/K `"cd `"$executableDir`" && `"$executablePath`" --help`""
And a full example:
$executablePath = Join-Path $toolsDir 'capa.exe' -Resolve
$executableIcon = $executablePath
$executableCmd = Join-Path ${Env:WinDir} "system32\cmd.exe"
$executableDir = Join-Path ${Env:UserProfile} "Desktop"
$executableArgs = "/K `"cd `"$executableDir`" && `"$executablePath`" --help`""
$shortcut = Join-Path $shortcutDir 'capa.lnk'
Install-ChocolateyShortcut -shortcutFilePath $shortcut -targetPath $executableCmd -Arguments $executableArgs -WorkingDirectory $executableDir -IconLocation $executableIcon
VM-Assert-Path $shortcut