Skip to content
Ana María Martínez Gómez edited this page Feb 21, 2022 · 10 revisions

Welcome to the VM-Packages wiki! 😃

Contributing

We would love that you add new VM packages and/or help us maintaining the existing one. Here's a great way to get started.

We use Chocolatey: https://chocolatey.org

nuspec

Template

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
  <metadata>
    <id>application-name.vm</id>
    <version>0.0.0</version>
    <description>short description</description>
    <authors>Author1, Author2</authors>
    <dependencies>
      <dependency id="common.vm" />
    </dependencies>
  </metadata>
</package>

Fields

Our packages are expected to be installed automatically by a script, so simplicity and maintainability is more important than providing detailed information in the .nuspec. Because of that, the .nuspec file should only contain the following fields.

id

The id should have the following format: toolName.vm

version

Non-metapackages

For packages that install tools via URLs and use three or fewer segments, use the tool's version string. For tools that use four segments, use the tool version for the segments one to three and the current date in the format 0.0.0.YYYYMMDDfor the forth one.

For example:

2.1 -> 2.1
2.1.3 -> 2.1.3
2.1.3.4 -> 2.1.3.20220221

What if there is no clear version?

  • If the tool/code does NOT specify a version, use the scheme 0.0.0.YYYYMMDD until a version is specified/made obvious by the tool's creator/maintainer.

What if I modify a package without changing the tool version?

The version needs to be increased every time you modify an existent package. If you modify a package without changing the tool version (for example to fix a bug in the package), you need to update the version by adding the current date to the 4th segment using the format YYYMMDD. For example, 1.6.2 to 1.6.2.20220113 and 3.02 to 3.02.0.20220113.

Metapackages

A metapackage is a package that installs and configures other packages via dependencies. For metapackages, use the version scheme 0.0.0.YYYMMDD (e.g., 0.0.0.20210521) and only include metadata relevant to the metapackage in the .nuspec file -- nothing specific to the packages it installs (since there can be many).

Version Exception!!

  • When a metapackage is named after and installs/configures a specific tool (for instance cygwin.vm that installs/configures cygwin) lock the dependency version (e.g., version="[3.2.0]") and use the locked version as the metapackage's version instead of the general scheme above.

description

The description should be short and not include version specific details or other information that is likely to change in a future version.

authors

Authors of software comma separated.

dependencies

common.vm should be included in the dependencies for every package.


Coding Conventions

General

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.


Standard Template

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 $_
}

ZIP Packages Template

For software that is downloaded as a ZIP file from a URL and does not require any special installation steps, use the VM-Install-From-Zip function. 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

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 -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 the 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 $zipUrl_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 $_
}

Standard Uninstaller Template

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

Help Packages Fail when Installing

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')

Uninstalling is Best Effort

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'

ZIP Files

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.


Best Practices

Below are common variables used throughout most of our chocolateyInstall.ps1 files. Please reuse these names so each installer file feels and reads similar.

$toolName

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).

Example
$toolName = 'FakeNet-NG'

$toolDir

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}.

Example
$toolDir = Join-Path ${Env:RAW_TOOLS_DIR} $toolName

$shortcutDir

Path to a subdirectory inside the tools shortcuts directory (${Env:TOOL_LIST_DIR}). The subdirectory represents the tool category. Possible values: Android, Debuggers, Decompilers, Delphi, Developer Tools, Disassemblers, dotNet, Flash, Forensic, Hex Editors, Java, Javascript, Net, Office, PDF, Pentest, PowerShell, Python, Text Editors, Utilities, VB, and Web Application.

Example
$shortcutDir = Join-Path ${Env:TOOL_LIST_DIR} 'Utilities'

$shortcut

The .LNK shortcut file that opens the tool. This file is located in the $shortcutDir.

$shortcut = Join-Path $shortcutDir "$toolName.lnk"

$packageArgs

Arguments used to call Install-ChocolateyZipPackage or Install-ChocolateyInstallPackage.

Example
$packageArgs = @{
  packageName   = ${Env:ChocolateyPackageName}
  unzipLocation = $toolDir
  url           = $url
  checksum      = $checksum
  checksumType  = 'sha256'
}
Install-ChocolateyZipPackage @packageArgs

Proxy Execution of a Tool

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.

$executablePath

Path to the tool's executable file. Generally used as the target of a shortcut and tool's file path added to the PATH.

$executableIcon

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).

$executableCmd

Proxy to execute the tool's executable -- typically cmd.exe.

$executableDir

Working directory to execute the tool from.

$executableArgs

Additional command-line arguments to pass to the tool.

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

Path Passing

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 instance:

$executableArgs = "/K `"cd `"$executableDir`" && `"$executablePath`" --help`""

See full example below:

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

Testing

We use GitHub actions to run several checks and tests in every PR. Check the ci.yml workflow. Only PRs were all test succeed can be merged.

The scripts in the test folder can be useful for local testing.


FAQ

When to use Out-Null?

From MSDN:

The Out-Null cmdlet sends its output to NULL, in effect, removing it from the pipeline and preventing the output to be displayed at the screen. If it data would be displayed in the console, pipe to Out-Null to prevent users from seeing the output in the console

Guidance

If data would be displayed in the console, pipe to Out-Null to prevent users from seeing the output in the console. However, to simply suppress errors and continue executing, use -ea 0.

What to use instead of ${Env:TEMP}?

Use ${Env:USERPROFILE}\AppData\Local\Temp. If Chocolatey's cache location is updated, the value ${Env:TEMP} is remapped in choco.exe to Chocolatey's cache location. This can cause unwanted side-effects in your scripts.

Chocolatey links