# Windows Package Manager

## Introduction

Salt provides a Windows package management tool for installing, updating, removing, and managing software packages on remote Windows systems. This tool provides a software repository and a package manager similar to what is provided by `yum` and `apt` on Linux. The repository contains a collection of package definition files.

### What are package definition files?

A package definition file is a YAML/JINJA2 file with a `.sls` file extension that contains all the information needed to install software using Salt. It defines:

- Full name of the software package

- The version of the software package

- Download location of the software package

- Command-line switches for silent install and uninstall

- Whether or not to use the Windows task scheduler to install the package

Package definition files can be hosted in one or more Git repositories. The `.sls` files used to install Windows packages are not distributed by default with Salt. You have to initialize and clone the default repository salt-winrepo-ng which is hosted on GitHub by SaltStack. The repository contains package definition files for many common Windows packages and is maintained by SaltStack and the Salt community. Anyone can submit a pull request to this repo to add new package definitions.

You can manage the package definition file through either Salt or Git. You can download software packages from either a git repository or from HTTP(S) or FTP URLs. You can store the installer defined in the package definition file anywhere as long as it is accessible from the host running Salt.

You can use the Salt Windows package manager like `yum` on Linux. You do not have to know the underlying command to install the software.

- Use `pkg.install` to install a package using a package manager based on the OS the system runs on.

- Use `pkg.installed` to check if a particular package is installed in the minion.

Note

The Salt Windows package manager does not automatically resolve dependencies while installing, updating, or removing packages. You have to manage the dependencies between packages manually.

### Quickstart

This quickstart guides you through using the Windows Salt package manager (winrepo) to install software packages in four steps:

1. (Optional) Install libraries

2. Populate the local Git repository

3. Update minion database

4. Install software packages

## Install libraries

(Optional) If you are using the Salt Windows package manager with package definition files hosted on a Salt Git repo, install the libraries `GitPython` or `pygit2`.

## Populate the local Git repository

The SLS files used to install Windows packages are not distributed by default with Salt. Assuming no changes to the default configuration (`file_roots`), initialize and clone salt-winrepo-ng repository.

    salt-run winrepo.update_git_repos

On successful execution of `winrepo.update_git_repos`, the winrepo repository is cloned on the master in the location specified in `winrepo_dir_ng` and all package definition files are pulled down from the Git repository.

On a masterless minion, use `salt-call` to initialize and clone the salt-winrepo-ng

    salt-call --local winrepo.update_git_repos

On successful execution of the runner, the winrepo repository is cloned on the minion in the location specified in `winrepo_dir_ng` and all package definition files are pulled down from the Git repository.

## Update minion database

Run `pkg.refresh_db` on all Windows minions to create a database entry for every package definition file and build the package database.

    # From the master
    salt -G 'os:windows' pkg.refresh_db

    # From the minion in masterless mode
    salt-call --local pkg.refresh_db

The `pkg.refresh_db` command parses the YAML/JINJA package definition files and generates the database. The above command returns the following summary denoting the number of packages that succeeded or failed to compile:

    local:
        ----------
        failed:
            0
        success:
            301
        total:
            301

Note

This command can take a few minutes to complete as all the package definition files are copied to the minion and the database is generated.

Note

You can use `pkg.refresh_db` when writing new Windows package definitions to check for errors in the definitions against one or more Windows minions.

## Install software package

You can now install a software package using `pkg.install`:

    # From the master
    salt * pkg.install 'firefox_x64'

    # From the minion in masterless mode
    salt-call --local pkg.install "firefox_x64"

The above command installs the latest version of Firefox on the minions.

## Configuration

The Github repository (winrepo) is synced to the `file_roots` in a location specified by the `winrepo_dir_ng` setting in the config. The default value of `winrepo_dir_ng` is as follows:

- Linux master: `/srv/salt/win/repo-ng` (`salt://win/repo-ng`)

- Masterless minion: `C:\salt\srv\salt\win\repo-ng` (`salt://win/repo-ng`)

### Master Configuration

The following settings are available for configuring the winrepo on the master:

- `winrepo_dir`

- `winrepo_dir_ng`

- `winrepo_remotes`

- `winrepo_remotes_ng`

- `winrepo_branch`

- `winrepo_provider`

- `winrepo_ssl_verify`

See here for detailed information on all master config options for winrepo.

#### winrepo_dir

`winrepo_dir` (str)

This setting is maintained for backwards compatibility with legacy minions. It points to the location in the `file_roots` where the winrepo files are kept. The default is: `/srv/salt/win/repo`

#### winrepo_dir_ng

`winrepo_dir_ng` (str)

The location in the `file_roots` where the winrepo files are kept. The default is `/srv/salt/win/repo-ng`.

Warning

You can change the location of the winrepo directory. However, it must always be set to a path that is inside the `file_roots`. Otherwise, the software definition files will be unreachable by the minion.

Important

A common mistake is to change the `file_roots` setting and fail to update the `winrepo_dir_ng` and `winrepo_dir` settings so that they are inside the `file_roots`

#### winrepo_remotes

`winrepo_remotes` (list)

This setting is maintained for backwards compatibility with legacy minions. It points to the legacy git repo. The default is a list containing a single URL:

https://github.com/saltstack/salt-winrepo

The legacy repo can be disabled by setting it to an empty list in the master config.

    winrepo_remotes: []

#### winrepo_remotes_ng

`winrepo_remotes_ng` (list)

This setting tells the `winrepo.update_git_repos` command where the next generation winrepo is hosted. This a list of URLs to multiple git repos. The default is a list containing a single URL:

https://github.com/saltstack/salt-winrepo-ng

#### winrepo_refspecs

`winrepo_refspecs` (list)

Specify what references to fetch from remote repositories. The default is `['+refs/heads/*:refs/remotes/origin/*', '+refs/tags/*:refs/tags/*']`

#### winrepo_branch

`winrepo_branch` (str)

The branch of the git repo to checkout. The default is `master`

#### winrepo_provider

`winrepo_provider` (str)

The provider to be used for winrepo. Default is `pygit2`. Falls back to `gitpython` when `pygit2` is not available

#### winrepo_ssl_verify

`winrepo_ssl_verify` (bool)

Ignore SSL certificate errors when contacting remote repository. Default is `False`

### Master Configuration (pygit2)

The following configuration options only apply when the `winrepo_provider` option is set to `pygit2`.

- `winrepo_insecure_auth`

- `winrepo_passphrase`

- `winrepo_password`

- `winrepo_privkey`

- `winrepo_pubkey`

- `winrepo_user`

#### winrepo_insecure_auth

`winrepo_insecure_auth` (bool)

Used only with `pygit2` provider. Whether or not to allow insecure auth. Default is `False`

#### winrepo_passphrase

`winrepo_passphrase` (str)

Used only with `pygit2` provider. Used when the SSH key being used to authenticate is protected by a passphrase. Default is `''`

#### winrepo_privkey

`winrepo_privkey` (str)

Used only with `pygit2` provider. Used with `winrepo_pubkey` to authenticate to SSH remotes. Default is `''`

#### winrepo_pubkey

`winrepo_pubkey` (str)

Used only with `pygit2` provider. Used with `winrepo_privkey` to authenticate to SSH remotes. Default is `''`

#### winrepo_user

`winrepo_user` (str)

Used only with `pygit2` provider. Used with `winrepo_password` to authenticate to HTTPS remotes. Default is `''`

#### winrepo_password

`winrepo_password` (str)

Used only with `pygit2` provider. Used with `winrepo_user` to authenticate to HTTPS remotes. Default is `''`

### Minion Configuration

Refreshing the package definitions can take some time, these options were introduced to allow more control of when it occurs. These settings apply to all minions whether in masterless mode or not.

- `winrepo_cache_expire_max`

- `winrepo_cache_expire_min`

- `winrepo_cachefile`

- `winrepo_source_dir`

#### winrepo_cache_expire_max

`winrepo_cache_expire_max` (int)

Sets the maximum age in seconds of the winrepo metadata file to avoid it becoming stale. If the metadata file is older than this setting, it will trigger a `pkg.refresh_db` on the next run of any `pkg` module function that requires the metadata file. Default is 604800 (1 week).

Software package definitions are automatically refreshed if stale after `winrepo_cache_expire_max`. Running a highstate forces the refresh of the package definitions and regenerates the metadata, unless the metadata is younger than `winrepo_cache_expire_max`.

#### winrepo_cache_expire_min

`winrepo_cache_expire_min` (int)

Sets the minimum age in seconds of the winrepo metadata file to avoid refreshing too often. If the metadata file is older than this setting, the metadata will be refreshed unless you pass `refresh: False` in the state. Default is 1800 (30 min).

#### winrepo_cachefile

`winrepo_cachefile` (str)

The file name of the winrepo cache file. The file is placed at the root of `winrepo_dir_ng`. Default is `winrepo.p`.

#### winrepo_source_dir

`winrepo_source_dir` (str)

The location of the .sls files on the Salt file server. This allows for using different environments. Default is `salt://win/repo-ng/`.

Warning

If the default for `winrepo_dir_ng` is changed, this setting may need to be changed on each minion. The default setting for `winrepo_dir_ng` is `/srv/salt/win/repo-ng`. If that were changed to `/srv/salt/new/repo-ng`, then the `winrepo_source_dir` would need to be changed to `salt://new/repo-ng`

### Masterless Minion Configuration

The following settings are available for configuring the winrepo on a masterless minion:

- `winrepo_dir`

- `winrepo_dir_ng`

- `winrepo_remotes`

- `winrepo_remotes_ng`

See here for detailed information on all minion config options for winrepo.

#### winrepo_dir

`winrepo_dir` (str)

This setting is maintained for backwards compatibility with legacy minions. It points to the location in the `file_roots` where the winrepo files are kept. The default is: `C:\ProgramData\Salt Project\Salt\srv\salt\win\repo`

#### winrepo_dir_ng

`winrepo_dir_ng` (str)

The location in the `file_roots` where the winrepo files are kept. The default is `C:\salt\srv\salt\win\repo-ng`.

Warning

You can change the location of the winrepo directory. However, it must always be set to a path that is inside the `file_roots`. Otherwise, the software definition files will be unreachable by the minion.

Important

A common mistake is to change the `file_roots` setting and fail to update the `winrepo_dir_ng` and `winrepo_dir` settings so that they are inside the `file_roots`. You might also want to verify `winrepo_source_dir` on the minion as well.

#### winrepo_remotes

`winrepo_remotes` (list)

This setting is maintained for backwards compatibility with legacy minions. It points to the legacy git repo. The default is a list containing a single URL:

https://github.com/saltstack/salt-winrepo

The legacy repo can be disabled by setting it to an empty list in the minion config.

    winrepo_remotes: []

#### winrepo_remotes_ng

`winrepo_remotes_ng` (list)

This setting tells the `winrepo.update_git_repos` command where the next generation winrepo is hosted. This a list of URLs to multiple git repos. The default is a list containing a single URL:

https://github.com/saltstack/salt-winrepo-ng

## Sample Configurations

### Masterless

The configs in this section are for working with winrepo on a Windows minion using `salt-call --local`.

#### Default Configuration

This is the default configuration if nothing is configured in the minion config. The config is shown here for clarity. These are the defaults:

    file_roots:
      base:
        - C:\ProgramData\Salt Project\Salt\srv\salt
    winrepo_source_dir: 'salt://win/repo-ng'
    winrepo_dir_ng: C:\ProgramData\Salt Project\Salt\srv\salt\win\repo-ng

The `winrepo.update_git_repos` command will clone the repository to `win\repo-ng` on the file_roots.

#### Multiple Salt Environments

This starts to get a little tricky. The winrepo repository doesn't get cloned to each environment when you run `winrepo.update_git_repos`, so to make this work, all environments share the same winrepo. Applying states using the `saltenv` option will find the state files in the appropriate environment, but the package definition files will always be pulled from the same location. Therefore, you have to put the same winrepo location in each saltenv. Here's how this would look:

    file_roots:
      base:
        - C:\ProgramData\Salt Project\Salt\srv\salt\base
        - C:\ProgramData\Salt Project\Salt\srv\salt\winrepo
      test:
        - C:\ProgramData\Salt Project\Salt\srv\salt\test
        - C:\ProgramData\Salt Project\Salt\srv\salt\winrepo
    winrepo_source_dir: 'salt://salt-winrepo-ng'
    winrepo_dir_ng: C:\ProgramData\Salt Project\Salt\srv\salt\winrepo
    winrepo_dir: C:\ProgramData\Salt Project\Salt\srv\salt\winrepo

When you run `winrepo.update_git_repos` the Git repository will be cloned to the location specified in the `winrepo_dir_ng` setting. I specified the `winrepo_dir` setting just so everything gets cloned to the same place. The directory that gets cloned is named `salt-winrepo-ng` so you specify that in the `winrepo_source_dir` setting.

The `winrepo` directory should only contain the package definition files. You wouldn't want to place any states in the `winrepo` directory as they will be available to both environments.

### Master

When working in a Master/Minion environment you have to split up some of the config settings between the master and the minion. Here are some sample configs for winrepo in a Master/Minion environment.

#### Default Configuration

This is the default configuration if nothing is configured. The config is shown here for clarity. These are the defaults on the master:

    file_roots:
      base:
        - /srv/salt
    winrepo_dir_ng: /srv/salt/win/repo-ng

This is the default in the minion config:

    winrepo_source_dir: 'salt://win/repo-ng'

The `winrepo.update_git_repos` command will clone the repository to `win\repo-ng` on the file_roots.

#### Multiple Salt Environments

To set up multiple saltenvs using a Master/Minion configuration set the following in the master config:

    file_roots:
      base:
        - /srv/salt/base
        - /srv/salt/winrepo
      test:
        - /srv/salt/test
        - /srv/salt/winrepo
    winrepo_dir_ng: /srv/salt/winrepo
    winrepo_dir: /srv/salt/winrepo

Use the winrepo runner to set up the winrepo repository on the master.

    salt-run winrepo.update_git_repos

The winrepo will be cloned to `/srv/salt/winrepo` under a directory named `salt-winrepo-ng`.

Set the following on the minion config so the minion knows where to find the package definition files in the file_roots:

    winrepo_source_dir: 'salt://salt-winrepo-ng'

The same stipulations apply in a Master/Minion configuration as they do in a Masterless configuration

## Usage

After completing the configuration and initialization, you can use the Salt package manager commands to manage software on Windows minions.

Note

The following example commands can be run from the master using `salt` or on a masterless minion using `salt-call`

| Command | Description
--- | --- | ---
1 | pkg.list_pkgs | Displays a list of all packages installed in the system.
2 | pkg.list_available | Displays the versions available of a particular package to be installed.
3 | pkg.install | Installs a given package.
4 | pkg.remove | Uninstalls a given package.

### List installed packages

Use `pkg.list_pkgs` to display a list of packages installed on the system.

    # From the master
    salt -G 'os:windows' pkg.list_pkgs

    # From the minion in masterless mode
    salt-call --local pkg.list_pkgs

The command displays the software name and the version for every package installed on the system irrespective of whether it was installed by the Salt package manager.

    local:
        ----------
        Frhed 1.6.0:
            1.6.0
        GNU Privacy Guard:
            2.2.16
        Gpg4win (3.1.9):
            3.1.9
        git:
            2.17.1.2
        nsis:
            3.03
        python3_x64:
            3.7.4150.0
        salt-minion-py3:
            2019.2.3

The software name indicates whether the software is managed by Salt or not.

If Salt finds a match in the winrepo database, then the software name is the short name as defined in the package definition file. It is usually a single-word, lower-case name.

All other software names are displayed as the full name as shown in Add/Remove Programs. In the above example, Git (git), Nullsoft Installer (nsis), Python 3.7 (python3_x64), and Salt (salt-minion-py3) have corresponding package definition files and are managed by Salt, while Frhed 1.6.0, GNU Privacy guard, and GPG4win are not.

### List available versions

Use `pkg.list_available` to display a list of versions of a package available for installation. You can pass the name of the software in the command. You can refer to the software by its `name` or its `full_name` surrounded by quotes.

    # From the master
    salt winminion pkg.list_available firefox_x64

    # From the minion in masterless mode
    salt-call --local pkg.list_available firefox_x64

The command lists all versions of Firefox available for installation.

    winminion:
        - 69.0
        - 69.0.1
        - 69.0.2
        - 69.0.3
        - 70.0
        - 70.0.1
        - 71.0
        - 72.0
        - 72.0.1
        - 72.0.2
        - 73.0
        - 73.0.1
        - 74.0

Note

For a Linux master, you can surround the file name with single quotes. However, for the `cmd` shell on Windows, use double quotes when wrapping strings that may contain spaces. Powershell accepts either single quotes or double quotes.

### Install a package

Use `pkg.install`: to install a package.

    # From the master
    salt winminion pkg.install 'firefox_x64'

    # From the minion in masterless mode
    salt-call --local pkg.install "firefox_x64"

The command installs the latest version of Firefox.

    # From the master
    salt winminion pkg.install 'firefox_x64' version=74.0

    # From the minion in masterless mode
    salt-call --local pkg.install "firefox_x64" version=74.0

The command installs version 74.0 of Firefox.

If a different version of the package is already installed, then the old version is replaced with the version in the winrepo (only if the package supports live updating).

You can also specify the full name of the software while installing:

    # From the master
    salt winminion pkg.install 'Mozilla Firefox 17.0.1 (x86 en-US)'

    # From the minion in masterless mode
    salt-call --local pkg.install "Mozilla Firefox 17.0.1 (x86 en-US)"

### Remove a package

Use `pkg.remove` to remove a package.

    # From the master
    salt winminion pkg.remove firefox_x64

    # From the minion in masterless mode
    salt-call --local pkg.remove firefox_x64

### Package definition file directory structure and naming

All package definition files are stored in the location configured in the `winrepo_dir_ng` setting. All files in this directory with a `.sls` file extension are considered package definition files. These files are evaluated to create the metadata file on the minion.

You can maintain standalone package definition files that point to software on other servers or on the internet. In this case the file name is the short name of the software with the `.sls` extension, for example,``firefox.sls``.

You can also store the binaries for your software together with their software definition files in their own directory. In this scenario, the directory name is the short name for the software and the package definition file stored that directory is named `init.sls`.

Look at the following example directory structure on a Linux master assuming default config settings:

    srv/
    |---salt/
    |   |---win/
    |   |   |---repo-ng/
    |   |   |   |---custom_defs/
    |   |   |   |   |---ms_office_2013_x64/
    |   |   |   |   |   |---access.en-us/
    |   |   |   |   |   |---excel.en-us/
    |   |   |   |   |   |---outlook.en-us/
    |   |   |   |   |   |---powerpoint.en-us/
    |   |   |   |   |   |---word.en-us/
    |   |   |   |   |   |---init.sls
    |   |   |   |   |   |---setup.dll
    |   |   |   |   |   |---setup.exe
    |   |   |   |   |---openssl.sls
    |   |   |   |   |---zoom.sls
    |   |   |   |---salt-winrepo-ng/
    |   |   |   |   |---auditbeat/
    |   |   |   |   |   |---init.sls
    |   |   |   |   |   |---install.cmd
    |   |   |   |   |   |---install.ps1
    |   |   |   |   |   |---remove.cmd
    |   |   |   |   |---gpg4win/
    |   |   |   |   |   |---init.sls
    |   |   |   |   |   |---silent.ini
    |   |   |   |   |---7zip.sls
    |   |   |   |   |---adobereader.sls
    |   |   |   |   |---audacity.sls
    |   |   |   |   |---ccleaner.sls
    |   |   |   |   |---chrome.sls
    |   |   |   |   |---firefox.sls

In the above directory structure:

- The `custom_defs` directory contains the following custom package definition files.

  - A folder for MS Office 2013 that contains the installer files for all the MS Office software and a package definition file named `init.sls`.

  - Two additional standalone package definition files `openssl.sls` and `zoom.sls` to install OpenSSl and Zoom.

- The `salt-winrepo-ng` directory contains the clone of the git repo specified by the `winrepo_remotes_ng` config setting.

Warning

Do not modify the files in the `salt-winrepo-ng` directory as it breaks future runs of `winrepo.update_git_repos`.

Warning

Do not place any custom software definition files in the `salt-winrepo-ng` directory as the `winrepo.update_git_repos` command wipes out the contents of the `salt-winrepo-ng` directory each time it is run and any extra files stored in the Salt winrepo are lost.

### Writing package definition files

You can write your own software definition file if you know:

- The full name of the software as shown in Add/Remove Programs

- The exact version number as shown in Add/Remove Programs

- How to install your software silently from the command line

Here is a YAML software definition file for Firefox:

    firefox_x64:
      '74.0':
        full_name: Mozilla Firefox 74.0 (x64 en-US)
        installer: 'https://download-installer.cdn.mozilla.net/pub/firefox/releases/74.0/win64/en-US/Firefox%20Setup%2074.0.exe'
        install_flags: '/S'
        uninstaller: '%ProgramFiles(x86)%/Mozilla Firefox/uninstall/helper.exe'
        uninstall_flags: '/S'
      '73.0.1':
        full_name: Mozilla Firefox 73.0.1 (x64 en-US)
        installer: 'https://download-installer.cdn.mozilla.net/pub/firefox/releases/73.0.1/win64/en-US/Firefox%20Setup%2073.0.1.exe'
        install_flags: '/S'
        uninstaller: '%ProgramFiles(x86)%/Mozilla Firefox/uninstall/helper.exe'
        uninstall_flags: '/S'

The package definition file itself is a data structure written in YAML with three indentation levels:

- The first level item is a short name that Salt uses to reference the software. This short name is used to install and remove the software and it must be unique across all package definition files in the repo. Also, there must be only one short name in the file.

- The second level item is the version number. There can be multiple version numbers for a package but they must be unique within the file.

Note

When running `pkg.list_pkgs`, the short name and version number are displayed when Salt finds a match in the repo. Otherwise, the full package name is displayed.

- The third indentation level contains all parameters that Salt needs to install the software. The parameters are:

  - `full_name` : The full name as displayed in Add/Remove Programs

  - `installer` : The location of the installer binary

  - `install_flags` : The flags required to install silently

  - `uninstaller` : The location of the uninstaller binary

  - `uninstall_flags` : The flags required to uninstall silently

  - `msiexec` : Use msiexec to install this package

  - `allusers` : If this is an MSI, install to all users

  - `cache_dir` : Cache the entire directory in the installer URL if it starts with `salt://`

  - `cache_file` : Cache a single file in the installer URL if it starts with `salt://`

  - `use_scheduler` : Launch the installer using the task scheduler

  - `source_hash` : The hash sum for the installer

### Example package definition files

This section provides some examples of package definition files for different use cases such as:

- Writing a simple package definition file

- Writing a JINJA templated package definition file

- Writing a package definition file to install the latest version of the software

- Writing a package definition file to install an MSI patch

These examples enable you to gain a better understanding of the usage of different file parameters. To understand the examples, you need a basic Understanding Jinja. For an exhaustive dive into Jinja, refer to the official Jinja Template Designer documentation.

### Example: Simple

Here is a pure YAML example of a simple package definition file for Firefox:

    firefox_x64:
      '74.0':
        full_name: Mozilla Firefox 74.0 (x64 en-US)
        installer: 'https://download-installer.cdn.mozilla.net/pub/firefox/releases/74.0/win64/en-US/Firefox%20Setup%2074.0.exe'
        install_flags: '/S'
        uninstaller: '%ProgramFiles(x86)%/Mozilla Firefox/uninstall/helper.exe'
        uninstall_flags: '/S'
      '73.0.1':
        full_name: Mozilla Firefox 73.0.1 (x64 en-US)
        installer: 'https://download-installer.cdn.mozilla.net/pub/firefox/releases/73.0.1/win64/en-US/Firefox%20Setup%2073.0.1.exe'
        install_flags: '/S'
        uninstaller: '%ProgramFiles(x86)%/Mozilla Firefox/uninstall/helper.exe'
        uninstall_flags: '/S'

The first line is the short name of the software which is `firefox_x64`.

Important

The short name must be unique across all other short names in the software repository. The `full_name` combined with the version must also be unique.

The second line is the `software version` and is indented two spaces.

Important

The version number must be enclosed in quotes or the YAML parser removes the trailing zeros. For example, if the version number `74.0` is not enclosed within quotes, then the version number is rendered as `74`.

The lines following the `version` are indented two more spaces and contain all the information needed to install the Firefox package.

Important

You can specify multiple versions of software by specifying multiple version numbers at the same indentation level as the first with its software definition below it.

Important

The `full_name` must match exactly what is shown in Add/Remove Programs (`appwiz.cpl`)

### Example: JINJA templated package definition file

JINJA is the default templating language used in package definition files. You can use JINJA to add variables and expressions to package definition files that get replaced with values when the `.sls` go through the Salt renderer.

When there are tens or hundreds of versions available for a piece of software, the definition file can become large and cumbersome to maintain. In this scenario, JINJA can be used to add logic, variables, and expressions to automatically create the package definition file for software with multiple versions.

Here is a an example of a package definition file for Firefox that uses JINJA:

    {%- set lang = salt['config.get']('firefox:pkg:lang', 'en-US') %}

    firefox_x64:
      {% for version in ['74.0',
                         '73.0.1', '73.0',
                         '72.0.2', '72.0.1', '72.0',
                         '71.0', '70.0.1', '70.0',
                         '69.0.3', '69.0.2', '69.0.1'] %}
      '{{ version }}':
        full_name: 'Mozilla Firefox {{ version }} (x64 {{ lang }})'
        installer: 'https://download-installer.cdn.mozilla.net/pub/firefox/releases/{{ version }}/win64/{{ lang }}/Firefox%20Setup%20{{ version }}.exe'
        install_flags: '/S'
        uninstaller: '%ProgramFiles%\Mozilla Firefox\uninstall\helper.exe'
        uninstall_flags: '/S'
      {% endfor %}

In this example, JINJA is used to generate a package definition file that defines how to install 12 versions of Firefox. Jinja is used to create a list of available versions. The list is iterated through a `for loop` where each version is placed in the `version` variable. The version is inserted everywhere there is a `{{ version }}` marker inside the `for loop`.

The single variable (`lang`) defined at the top of the package definition identifies the language of the package. You can access the Salt modules using the `salt` keyword. In this case, the `config.get` function is invoked to retrieve the language setting. If the `lang` variable is not defined then the default value is `en-US`.

### Example: Package definition file to install the latest version

Some software vendors do not provide access to all versions of their software. Instead, they provide a single URL to what is always the latest version. In some cases, the software keeps itself up to date. One example of this is the Google Chrome web browser.

To handle situations such as these, set the version to latest. Here's an example of a package definition file to install the latest version of Chrome.

    chrome:
      latest:
        full_name: 'Google Chrome'
        installer: 'https://dl.google.com/edgedl/chrome/install/GoogleChromeStandaloneEnterprise.msi'
        install_flags: '/qn /norestart'
        uninstaller: 'https://dl.google.com/edgedl/chrome/install/GoogleChromeStandaloneEnterprise.msi'
        uninstall_flags: '/qn /norestart'
        msiexec: True

In the above example:

- `Version` is set to `latest`. Salt then installs the latest version of Chrome at the URL and displays that version.

- `msiexec` is set to `True`, hence the software is installed using an MSI.

### Example: Package definition file to install an MSI patch

For MSI installers, when the `msiexec` parameter is set to true, the `/i` option is used for installation, and the `/x` option is used for uninstallation. However, when installing an MSI patch, the `/i` and `/x` options cannot be combined.

Here is an example of a package definition file to install an MSI patch:

    MyApp:
      '1.0':
        full_name: MyApp
        installer: 'salt://win/repo-ng/MyApp/MyApp.1.0.msi'
        install_flags: '/qn /norestart'
        uninstaller: '{B5B5868F-23BA-297A-917D-0DF345TF5764}'
        uninstall_flags: '/qn /norestart'
        msiexec: True
      '1.1':
        full_name: MyApp
        installer: 'salt://win/repo-ng/MyApp/MyApp.1.0.msi'
        install_flags: '/qn /norestart /update "%cd%\\MyApp.1.1.msp" '
        uninstaller: '{B5B5868F-23BA-297A-917D-0DF345TF5764}'
        uninstall_flags: '/qn /norestart'
        msiexec: True
        cache_file: salt://win/repo-ng/MyApp/MyApp.1.1.msp

In the above example:

- Version `1.0` of the software installs the application using the `1.0` MSI defined in the `installer` parameter.

- There is no file to be cached and the `install_flags` parameter does not include any special values.

Version `1.1` of the software uses the same installer file as Version `1.0`. Now, to apply a patch to Version 1.0, make the following changes in the package definition file:

- Place the patch file (MSP file) in the same directory as the installer file (MSI file) on the `file_roots`

- In the `cache_file` parameter, specify the path to the single patch file.

- In the `install_flags` parameter, add the `/update` flag and include the path to the MSP file using the `%cd%` environment variable. `%cd%` resolves to the current working directory, which is the location in the minion cache where the installer file is cached.

For more information, see issue #32780.

The same approach could be used for applying MST files for MSIs and answer files for other types of .exe-based installers.

### Parameters

This section describes the parameters placed under the `version` in the package definition file. Examples can be found on the Salt winrepo repository.

#### full_name (str)

The full name of the software as shown in "Add/Remove Programs". You can also retrieve the full name of the package by installing the package manually and then running `pkg.list_pkgs`. Here's an example of the output from `pkg.list_pkgs`:

    salt 'test-2008' pkg.list_pkgs
    test-2008
        ----------
        7-Zip 9.20 (x64 edition):
            9.20.00.0
        Mozilla Firefox 74.0 (x64 en-US)
            74.0
        Mozilla Maintenance Service:
            74.0
        salt-minion-py3:
            3001

Notice the full Name for Firefox: `Mozilla Firefox 74.0 (x64 en-US)`. The `full_name` parameter in the package definition file must match this name.

The example below shows the `pkg.list_pkgs` for a machine that has Mozilla Firefox 74.0 installed with a package definition file for that version of Firefox.

    test-2008:
        ----------
        7zip:
            9.20.00.0
        Mozilla Maintenance Service:
            74.0
        firefox_x64:
            74.0
        salt-minion-py3:
            3001

On running `pkg.list_pkgs`, if any of the software installed on the machine matches the full name defined in any one of the software definition files in the repository, then the package name is displayed in the output.

Important

The version number and `full_name` must match the output of `pkg.list_pkgs` so that the installation status can be verified by the state system.

Note

You can successfully install packages using `pkg.install`, even if the `full_name` or the version number doesn't match. The module will complete successfully, but continue to display the full name in `pkg.list_pkgs`. If this is happening, verify that the `full_name` and the `version` match exactly what is displayed in Add/Remove Programs.

Tip

To force Salt to display the full name when there's already an existing package definition file on the system, you can pass a bogus `saltenv` parameter to the command like so: `pkg.list_pkgs saltenv=NotARealEnv`

Tip

It's important use `pkg.refresh_db` to check for errors and ensure the latest package definition is on any minion you're testing new definitions on.

#### installer (str)

The path to the binary (`.exe`, `.msi`) that installs the package.

This can be a local path or a URL. If it is a URL or a Salt path (`salt://`), then the package is cached locally and then executed. If it is a path to a file on disk or a file share, then it is executed directly.

Note

When storing software in the same location as the winrepo:

- Create a sub folder named after the package.

- Store the package definition file named `init.sls` and the binary installer in the same sub folder if you are hosting those files on the `file_roots`.

Note

The `pkg.refresh_db` command processes all `.sls` files in all sub directories in the `winrepo_dir_ng` directory.

#### install_flags (str)

The flags passed to the installer for silent installation.

You may be able to find these flags by adding `/?` or `/h` when running the installer from the command line. See WPKG project wiki for information on silent install flags.

Warning

Always ensure that the installer has the ability to install silently, otherwise Salt appears to hang while the installer waits for user input.

#### uninstaller (str)

The path to the program to uninstall the software.

This can be the path to the same `.exe` or `.msi` used to install the software. If you use a `.msi` to install the software, then you can either use the GUID of the software or the same `.msi` to uninstall the software.

You can find the uninstall information in the registry:

- Software\Microsoft\Windows\CurrentVersion\Uninstall

- Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall

Here's an example that uses the GUID to uninstall software:

    7zip:
      '9.20.00.0':
        full_name: 7-Zip 9.20 (x64 edition)
        installer: salt://win/repo-ng/7zip/7z920-x64.msi
        install_flags: '/qn /norestart'
        uninstaller: '{23170F69-40C1-2702-0920-000001000000}'
        uninstall_flags: '/qn /norestart'
        msiexec: True

Here's an example that uses the MSI installer to uninstall software:

    7zip:
      '9.20.00.0':
        full_name: 7-Zip 9.20 (x64 edition)
        installer: salt://win/repo-ng/7zip/7z920-x64.msi
        install_flags: '/qn /norestart'
        uninstaller: salt://win/repo-ng/7zip/7z920-x64.msi
        uninstall_flags: '/qn /norestart'
        msiexec: True

#### uninstall_flags (str)

The flags passed to the uninstaller for silent uninstallation.

You may be able to find these flags by adding `/?` or `/h` when running the uninstaller from the command-line. See WPKG project wiki for information on silent uninstall flags.

Warning

Always ensure that the installer has the ability to uninstall silently, otherwise Salt appears to hang while the uninstaller waits for user input.

#### msiexec (bool, str)

This setting informs Salt to use `msiexec /i` to install the package and `msiexec /x` to uninstall. This setting only applies to `.msi` installations.

Possible options are:

- True

- False (default)

- the path to `msiexec.exe` on your system

    7zip:
      '9.20.00.0':
        full_name: 7-Zip 9.20 (x64 edition)
        installer: salt://win/repo/7zip/7z920-x64.msi
        install_flags: '/qn /norestart'
        uninstaller: salt://win/repo/7zip/7z920-x64.msi
        uninstall_flags: '/qn /norestart'
        msiexec: 'C:\Windows\System32\msiexec.exe'

#### allusers (bool)

This parameter is specific to `.msi` installations. It tells `msiexec` to install the software for all users. The default is `True`.

#### cache_dir (bool)

This setting requires the software to be stored on the `file_roots` and only <<<<<<< HEAD applies to URLs that begin with `salt://`. If set to `True`, then the ======= applies to URLs that begin with `salt://`. If set to `True`, then the entire directory where the installer resides is recursively cached. This is useful for installers that depend on other files in the same directory for installation.

Warning

If set to `True`, then all files and directories in the same location as the installer file are copied down to the minion. For example, if you place your package definition file with `cache_dir: True` in the root of winrepo (`/srv/salt/win/repo-ng`) then the entire contents of winrepo is cached to the minion. Therefore, it is best practice to place your package definition file along with its installer files in a subdirectory if they are stored in winrepo.

Here's an example using cache_dir:

    sqlexpress:
      '12.0.2000.8':
        full_name: Microsoft SQL Server 2014 Setup (English)
        installer: 'salt://win/repo/sqlexpress/setup.exe'
        install_flags: '/ACTION=install /IACCEPTSQLSERVERLICENSETERMS /Q'
        cache_dir: True

#### cache_file (str)

This setting requires the file to be stored on the `file_roots` and only applies to URLs that begin with `salt://`. It indicates that the single file specified is copied down for use with the installer. It is copied to the same location as the installer. Use this setting instead of `cache_dir` when you only need to cache a single file.

#### use_scheduler (bool)

If set to `True`, Windows uses the task scheduler to run the installation. A one-time task is created in the task scheduler and launched. The return to the minion is that the task was launched successfully, not that the software was installed successfully.

Note

This is used in the package definition for Salt itself. The first thing the Salt installer does is kill the Salt service, which then kills all child processes. If the Salt installer is launched via Salt, then the installer is killed with the salt-minion service, leaving Salt on the machine but not running. Using the task scheduler allows an external process to launch the Salt installer so its processes aren't killed when the Salt service is stopped.

#### source_hash (str)

This setting informs Salt to compare a hash sum of the installer to the provided hash sum before execution. The value can be formatted as `<hash_algorithm>=<hash_sum>`, or it can be a URI to a file containing the hash sum.

For a list of supported algorithms, see the hashlib documentation.

Here's an example using `source_hash`:

    messageanalyzer:
      '4.0.7551.0':
        full_name: 'Microsoft Message Analyzer'
        installer: 'salt://win/repo/messageanalyzer/MessageAnalyzer64.msi'
        install_flags: '/quiet /norestart'
        uninstaller: '{1CC02C23-8FCD-487E-860C-311EC0A0C933}'
        uninstall_flags: '/quiet /norestart'
        msiexec: True
        source_hash: 'sha1=62875ff451f13b10a8ff988f2943e76a4735d3d4'

#### Not Implemented

The following parameters are often seen in the software definition files hosted on the Git repo. However, they are not implemented and do not affect the installation process.

param bool reboot:

  Not implemented

param str locale:

  Not implemented

## Managing Windows Software on a Standalone Windows Minion

The Windows Software Repository functions similarly in a standalone environment, with a few differences in the configuration.

To replace the winrepo runner used on the Salt master, an `execution module` exists to provide the same functionality to standalone minions. The functions for the module share the same names with functions in the runner and are used in the same way; the only difference is that `salt-call` is used instead of `salt-run` to run those functions:

    salt-call winrepo.update_git_repos
    salt-call pkg.refresh_db

After executing the previous commands, the repository on the standalone system is ready for use.

## Troubleshooting

### My software installs correctly but `pkg.installed` says it failed

If you have a package that seems to install properly but Salt reports a failure then it is likely you have a `version` or `full_name` mismatch.

- Check the `full_name` and `version` of the package as shown in Add/Remove Programs (`appwiz.cpl`).

- Use `pkg.list_pkgs` to check that the `full_name` and `version` exactly match what is installed.

- Verify that the `full_name` and `version` in the package definition file match the full name and version in Add/Remove programs.

- Ensure that the `version` is wrapped in single quotes in the package definition file.

### Changes to package definition files not being picked up

Make sure you refresh the database on the minion (`pkg.refresh_db`) after updating package definition files in the repo.

    salt winminion pkg.refresh_db

### Winrepo upgrade issues

To minimize potential issues, it is a good idea to remove any winrepo git repositories that were checked out by the legacy (pre-2015.8.0) winrepo code when upgrading the master to 2015.8.0 or later. Run `winrepo.update_git_repos` to clone them anew after the master is started.

## pygit2 / GitPython Support for Maintaining Git Repos

pygit2 and GitPython are the supported python interfaces to Git. The runner `winrepo.update_git_repos` uses the same underlying code as Git Fileserver Backend and `Git External Pillar` to maintain and update its local clones of git repositories.

Note

If compatible versions of both pygit2 and GitPython are installed, then Salt will prefer pygit2. To override this behavior use the `winrepo_provider` configuration parameter, ie: `winrepo_provider: gitpython`

## Accessing authenticated Git repos (pygit2)

pygit2 enables you to access authenticated git repositories and set per-remote config settings. An example of this is:

    winrepo_remotes:
      - https://github.com/saltstack/salt-winrepo.git
      - git@github.com:myuser/myrepo.git:
        - pubkey: /path/to/key.pub
        - privkey: /path/to/key
        - passphrase: myaw3s0m3pa$$phr4$3
      - https://github.com/myuser/privaterepo.git:
        - user: mygithubuser
        - password: CorrectHorseBatteryStaple

Note

The per-remote configuration settings work in the same manner as they do in gitfs, with global parameters being overridden by their per-remote counterparts. For instance, setting `winrepo_passphrase` sets a global passphrase for winrepo that applies to all SSH-based remotes, unless overridden by a `passphrase` per-remote parameter.

See here for a detailed explanation of how per-remote configuration works in gitfs. The same principles apply to winrepo.

## Maintaining Git repos

A `clean` argument is added to the `winrepo.update_git_repos` runner to maintain the Git repos. When `clean=True` the runner removes directories under the `winrepo_dir_ng`/`winrepo_dir_ng` that are not explicitly configured. This eliminates the need to manually remove these directories when a repo is removed from the config file.

    salt-run winrepo.update_git_repos clean=True

If a mix of git and non-git Windows Repo definition files are used, then do not pass `clean=True`, as it removes the directories containing non-git definitions.

## Name collisions between repos

Salt detects collisions between repository names. The `winrepo.update_git_repos` runner does not execute successfully if any collisions between repository names are detected. Consider the following configuration:

    winrepo_remotes:
      - https://foo.com/bar/baz.git
      - https://mydomain.tld/baz.git
      - https://github.com/foobar/baz.git

With the above configuration, the `winrepo.update_git_repos` runner fails to execute as all three repos would be checked out to the same directory. To resolve this conflict, use the per-remote parameter called `name`.

    winrepo_remotes:
      - https://foo.com/bar/baz.git
      - https://mydomain.tld/baz.git:
        - name: baz_junior
      - https://github.com/foobar/baz.git:
        - name: baz_the_third

Now on running the `winrepo.update_git_repos`:

- https://foo.com/bar/baz.git repo is initialized and cloned under the `win_repo_dir_ng` directory.

- https://mydomain.tld/baz.git repo is initialized and cloned under the `win_repo_dir_ng\baz_junior` directory.

- https://github.com/foobar/baz.git repo is initialized and cloned under the `win_repo_dir_ng\baz_the_third` directory.