03 · Deep dive · ~15 min
winrepo — the Windows package manager.
Like apt on Linux. Like yum on RHEL. Salt for Windows software, fleet-wide. Install, upgrade, remove with one command — no logging into individual boxes.
The mental model
winrepo isn't magic. It's a Git repo of YAML files. Each file describes how to install a piece of Windows software silently. Salt reads the files, builds a database, and uses that database when you say pkg.install firefox.
Three pieces: a Git repo of package definitions (the community one is salt-winrepo-ng), a local cache on each minion (built from the repo), and the installer URLs the package definitions point at (vendor download URLs, your own SMB share, or anywhere reachable from the minion).
No dependency resolution. Unlike apt, winrepo won't auto-install dependencies. If X requires Y, you install Y first. Manage the order yourself with require: in your states.
Quickstart — four steps
Run all four on the master. Steps 03 and 04 also work in masterless mode — swap salt for salt-call --local on the minion.
01
Install the Git library (optional)
If your master doesn't have it: GitPython or pygit2. Salt uses one of these to clone winrepo on the master.
sudo salt-pip install GitPython
sudo salt-pip install pygit2
02
Clone winrepo to the master
Pulls salt-winrepo-ng into /srv/salt/win/repo-ng/ by default.
sudo salt-run winrepo.update_git_repos
After this, you have hundreds of package definitions ready to use.
03
Build package database on minions
Each Windows minion parses the YAML and builds a local database. Slow first time, fast after.
sudo salt -G 'os:windows' pkg.refresh_db
Output: success: 301, failed: 0 or similar. If anything fails, you've got malformed YAML.
04
Install something
Latest version of Firefox on every Windows minion, in one command:
sudo salt -G 'os:windows' pkg.install firefox_x64
Pin a version: pkg.install firefox_x64 version=74.0.
Day-2 operations
Once winrepo is set up, these are the four commands you'll actually run.
List installed
What's on the box?
Returns short names for software Salt manages, full names for everything else.
salt 'win01' pkg.list_pkgs
List versions
What can I install?
All versions of a package available in winrepo.
salt 'win01' pkg.list_available firefox_x64
Install
Install or upgrade
Latest version, or pin to a specific one with version=.
salt 'win01' pkg.install firefox_x64
salt 'win01' pkg.install firefox_x64 version=74.0
Remove
Uninstall
Runs the uninstaller defined in the package definition.
salt 'win01' pkg.remove firefox_x64
Write your own package definition
The community winrepo covers hundreds of packages. For internal software (or anything missing), you write the YAML yourself. Three things you need: the full name as Add/Remove Programs shows it, the exact version, and the silent-install command-line switches.
Here's the canonical Firefox example, annotated:
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'
Three rules
- Short name must be unique across your whole winrepo.
firefox_x64 is what you'll type in pkg.install.
- Versions must be in quotes. Without quotes, YAML strips trailing zeros —
74.0 becomes 74 and your install breaks.
full_name must match Add/Remove Programs exactly. That's how Salt detects whether the package is already installed.
Common parameters
installer — URL or salt:// path to the installer binary
install_flags — silent install switches (/S, /quiet /norestart, /qn, etc.)
uninstaller + uninstall_flags — same idea for removal
msiexec: True — set when installing an MSI (Salt wraps with msiexec /i)
cache_file — cache the installer on the minion (use when installer is on salt://)
source_hash — SHA-256 of the installer; verifies download integrity
After writing your .sls file, run pkg.refresh_db again to pick it up.
Field-tested patterns
From production
Host the installers yourself
Pointing installer at vendor URLs (download.mozilla.org, etc.) works for testing. In production it's fragile — vendors move URLs, change naming conventions, and rate-limit. Mirror installers to your own SMB share or salt:// file root. Cuts download time, removes a third-party dependency, gives you version control.
installer: 'salt://win/installers/firefox/Firefox Setup 74.0.exe'
cache_file: 'salt://win/installers/firefox/Firefox Setup 74.0.exe'
source_hash: 'sha256=<your hash>'
From production
Multi-master rsync for installers
If you run multiple Salt masters (the 3-server stack), don't store installers in Git — they bloat the repo. Store them on master 1's filesystem and rsync to the others nightly. Salt's file_roots will serve them via salt:// the same way regardless.
From production
Version pinning isn't optional
"Latest" sounds great until a vendor pushes a breaking release on a Tuesday morning. Always pin versions in production. Pin via state files (pkg.installed: name: firefox_x64, version: 74.0). Bump in pillar when you've tested. Roll back by changing pillar.