The Issue
I was hitting issues when trying to use the Salt Win-Repo to install software. Below is a copy of my state file.
ensure_malwarebytes_installed: pkg.installed: - pkgs: - malwarebytes
It would fail with the below helpful error messages. But most importantly, I’d check the minion, to find the software was actually installed.
{ "return": { "pkg_|-ensure_malwarebytes_installed_|-ensure_malwarebytes_installed_|-installed": { "name": "ensure_malwarebytes_installed", "__id__": "ensure_malwarebytes_installed", "result": false, "__sls__": "Windows.software-install.malwarebytes", "changes": { "malwarebytes": "Unable to locate package malwarebytes" }, "comment": "The following packages failed to install/update: malwarebytes", "duration": 343.731, "start_time": "13:07:43.183808", "__run_num__": 0 }
If I instead ran the command from my salt master, it would be successful with no error outputs:
salt {minion_name} pkg.install malwarebytes -l debug
The Debugging Effort
Because the software is installed on the minion, I run the “pkg.list_pkgs” command, so I can detail exactly what the system returns.
C:\Users\Administrator>salt-call pkg.list_pkgs local: ---------- ... Malwarebytes version 4.5.12.204: 4.5.12.204 ...
Next, I want to remove the package, before I continue to debug, however I hit another issue.
C:\Users\Administrator>salt-call pkg.remove malwarebytes -l info local: ---------- malwarebytes: ---------- current: not installed C:\Users\Administrator>salt-call pkg.remove malwarebytes -l info [INFO ] Executing command '"C:\Windows\system32\cmd.exe"' in directory 'C:\Users\Administrator' [ERROR ] Command '"C:\Windows\system32\cmd.exe"' failed with return code: 1 [ERROR ] stdout: The system cannot find the path specified. [ERROR ] retcode: 1 [ERROR ] Failed to remove malwarebytes; retcode: 1; uninstaller output: The system cannot find the path specified. [WARNING ] Expected changes for package removal may not have occurred local: ---------- malwarebytes: ---------- uninstall status: failed
The Cause
Now to the debugging effort. I render the SLS file on my minion, so I can understand exactly what values are computed and used on the system to install and remove the package.
C:\Users\Administrator>salt-call slsutil.renderer salt://win/repo-ng/salt-winrepo-ng/malwarebytes.sls default_template=jinja local: ---------- malwarebytes: ---------- 4.3.0.210: ---------- full_name: Malwarebytes Anti-Malware version 4.3.0.210 installer: https://data-cdn.mbamupdates.com/web/mb4-setup-consumer/MBSetup.exe install_flags: /SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART uninstaller: %ProgramFiles(x86)%\Malwarebytes Anti-Malware\unins000.exe uninstall_flags: /SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART msiexec: False locale: en_US reboot: False
First, the name provided in the “pkg.list_pkgs” command, doesn’t match the name provided in the win-repo malware bytes file, after the software is installed (despite the error when running the state file).
This is the probable cause of the inital error in my state file, it cannot return the expected software is installed, due to the name mismatch.
However, we also have the uninstall issue to dive into as well. The second step is to validate the path used for the uninstall file and its arguments.
The first obvious thing, is that the new Malware bytes version is installed into the “Program Files” folder on 64bit Operating Systems now, and not the x86 folder. The other issue is that there is no longer a unins000.exe file.
This explains the error message when running “pkg.remove malwarebytes” returning “system cannot find the path specified”.
The Fix
Ok, so let’s check the file in question that Salt is using.
- You can also see it at this git commit in the win-repo git repo.
# cat /srv/salt/win/repo-ng/salt-winrepo-ng/malwarebytes.sls # just 32-bit x86 installer available {% if grains['cpuarch'] == 'AMD64' %} {% set PROGRAM_FILES = "%ProgramFiles(x86)%" %} {% else %} {% set PROGRAM_FILES = "%ProgramFiles%" %} {% endif %} # Source: http://www.malwarebytes.org malwarebytes: # {% for version in ['4.3.0.210'] %} # '{{ version }}': # full_name: 'Malwarebytes Anti-Malware version {{ version }}' # installer: 'https://data-cdn.mbamupdates.com/web/mb4-setup-consumer/MBSetup.exe' # install_flags: '/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' # uninstaller: '{{ PROGRAM_FILES }}\Malwarebytes Anti-Malware\unins000.exe' # uninstall_flags: '/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' # msiexec: False # locale: en_US # reboot: False # {% endfor %} # '4.3.0.210': full_name: 'Malwarebytes Anti-Malware version 4.3.0.210' installer: 'https://data-cdn.mbamupdates.com/web/mb4-setup-consumer/MBSetup.exe' install_flags: '/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' uninstaller: '{{ PROGRAM_FILES }}\Malwarebytes Anti-Malware\unins000.exe' uninstall_flags: '/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' msiexec: False locale: en_US reboot: False #
Now to break down the items in the file that are problematic.
- If Statement
- This sets the Program Files location to be used for computing the uninstall location based on the Operating Systems CPU Architecture, defined by looking up the grain information for the minion.
- The issue here is that it sets a 64Bit system to use the “Program Files x86” folder, which as we uncovered in the above analysis, is the wrong location.
- Uninstaller
- This uses the wrong program folder, as the structure has now changed not only into the “Program Files” folder, but also from “\Malwarebytes Anti-Malware\” to “\Malwarebytes\Anti-Malware\
- The uninstall file itself has now changed.
- After a bit of digging, I found it in the program folder now called “mbuns.exe”
- Installer
- MalwareBytes unfortunately only provides a public generic link which has no version attached. This is fantastic, as you always get the latest version. But terrible, as you don’t know the version number upfront.
- MalwareBytes doesn’t provide any links to older versions.
- This causes issues in how best to define the win-repo file going forward to differentiate between versions and ensuring salt acts in the correct manor when it encounters an installed version of MalwareBytes already.
- The commented-out code
- Just makes the file look a bit messier, I assume it was left there for testing by the previous creator.
Ok, so fixing this is a few small changes, but also a bit of a pain of the fact you cannot 100% resolve this. Thanks to the lack of previous versions from the vendor in their software download.
First let’s ditch the commented-out code. And replicate the version block of code.
- Create a version for the existing code (4.3.0.210)
- Create a version for the latest file install available (4.5.12.204)
For both versions, add at the start and end of each version block a “for/endfor” statement, which references the version number as the string.
Change the version numbering at the start of the block to the variable {{ version}}. This will now be populated by the “for” statement that precedes it.
For the “full_name” property, remove the static version number and add in the variable.
- Update the newer version code block to match that as seen when we ran pkg.list_pkgs
Focusing on just the changes needed in the new version block
- Remove the “If” statement that sets the “Program Files” variable
- Add in a “set” statement to configure the variable to the correct location.
- Update the “uninstaller” location to the correct location and file
- Update the “uninstall_flags” to use the correct ones
- There’s no documentation from MalwareBytes on this, so it was some googling and trial and error running the commands manually to find the right ones.
And that’s it and done. Giving you the code below.
- I submitted this as a PR to the Git Repo for win-repo. You can diff the two files on the file changes tab.
# Source: http://www.malwarebytes.org malwarebytes: {% for version in ['4.3.0.210'] %} # just 32-bit x86 installer available {% if grains['cpuarch'] == 'AMD64' %} {% set PROGRAM_FILES = "%ProgramFiles(x86)%" %} {% else %} {% set PROGRAM_FILES = "%ProgramFiles%" %} {% endif %} '{{ version }}': full_name: 'Malwarebytes Anti-Malware version {{ version }}' installer: 'https://data-cdn.mbamupdates.com/web/mb4-setup-consumer/MBSetup.exe' install_flags: '/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' uninstaller: '{{ PROGRAM_FILES }}\Malwarebytes Anti-Malware\unins000.exe' uninstall_flags: '/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' msiexec: False locale: en_US reboot: False {% endfor %} {% for version in ['4.5.12.204'] %} {% set PROGRAM_FILES = "%ProgramFiles%" %} '{{ version }}': full_name: 'Malwarebytes version {{ version }}' installer: 'https://data-cdn.mbamupdates.com/web/mb4-setup-consumer/MBSetup.exe' install_flags: '/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' uninstaller: '{{ PROGRAM_FILES }}\Malwarebytes\Anti-Malware\mbuns.exe' uninstall_flags: '/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' msiexec: False locale: en_US reboot: False {% endfor %}
Now that’s all that’s left to do is update our minions with the new file on our system. And apply the state file again.
First we can run the “slsutil.renderer” on my test machine to make sure my new file renders correctly.
And finally applying the state file.
"return": { "pkg_|-ensure_malwarebytes_installed_|-ensure_malwarebytes_installed_|-installed": { "name": "ensure_malwarebytes_installed", "__id__": "ensure_malwarebytes_installed", "result": true, "__sls__": "Windows.software-install.malwarebytes", "changes": { "malwarebytes": { "new": "4.5.12.204", "old": "" } }, "comment": "The following packages were installed/updated: malwarebytes", "duration": 127472.689, "start_time": "17:45:28.779065", "__run_num__": 0
Regards