Add appx.absent state, remove duplicated code

This commit is contained in:
Twangboy 2023-05-23 16:18:40 -06:00 committed by Megan Wilhite
parent d9b5ee5429
commit 608f7acfa2
4 changed files with 148 additions and 84 deletions

View file

@ -4,7 +4,7 @@ Manage provisioned apps
Provisioned apps are part of the image and are installed for every user the
first time the user logs on. Provisioned apps are also updated and sometimes
reinstalled when the system is updated.
reinstalled when the system is updated.
Apps removed with this module will remove the app for all users and deprovision
the app. Deprovisioned apps will neither be installed for new users nor will
@ -44,6 +44,7 @@ import fnmatch
import logging
import salt.utils.platform
import salt.utils.win_pwsh
import salt.utils.win_reg
log = logging.getLogger(__name__)
@ -60,7 +61,11 @@ def __virtual__():
Load only on Windows
"""
if not salt.utils.platform.is_windows():
return False, "Module appx: module only works on Windows systems."
return False, "Appx module: Only available on Windows systems"
pwsh_info = __salt__["cmd.shell_info"](shell="powershell", list_modules=False)
if not pwsh_info["installed"]:
return False, "Appx module: PowerShell not available"
return __virtualname__
@ -164,10 +169,10 @@ def list_(query=None, field="Name", include_store=False, frameworks=False, bundl
if not field:
cmd.append("Sort-Object Name")
cmd.append("Select Name, Version, PackageFullName, PackageFamilyName, IsBundle, IsFramework")
return __utils__["win_pwsh.run_dict"](" | ".join(cmd))
return salt.utils.win_pwsh.run_dict(" | ".join(cmd))
else:
cmd.append(f"Sort-Object {field}")
return _pkg_list(__utils__["win_pwsh.run_dict"](" | ".join(cmd)), field)
return _pkg_list(salt.utils.win_pwsh.run_dict(" | ".join(cmd)), field)
def remove(query=None, include_store=False, frameworks=False, deprovision_only=False):
@ -176,8 +181,8 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
a bundle, the entire bundle will be removed.
This function removes the package for all users on the system. It also
deprovisions the packages so that it isn't re-installed by later system
updates. To only deprovision a package and not remove for all users, set
deprovisions the package so that it isn't re-installed by later system
updates. To only deprovision a package and not remove it for all users, set
``deprovision_only=True``.
Args:
@ -195,13 +200,13 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
``include_store=True``
.. note::
Use the ``appx.get`` function to make sure your query is
Use the ``appx.list`` function to make sure your query is
returning what you expect. Then use the same query to remove
those packages
include_store (bool):
Include the Microsoft Store in the results of the query to be
removed. Use this with caution. It difficult to reinstall the
removed. Use this with caution. It is difficult to reinstall the
Microsoft Store once it has been removed with this function. Default
is ``False``
@ -210,7 +215,7 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
Default is ``False``
deprovision_only (bool):
Deprovision the package. The package will be removed from the
Only deprovision the package. The package will be removed from the
current user and added to the list of deprovisioned packages. The
package will not be re-installed in future system updates. New users
of the system will not have the package installed. However, the
@ -260,7 +265,7 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
else:
log.debug("Removing package: %s", remove_name)
remove_cmd = f"Remove-AppxPackage -AllUsers -Package {remove_name}"
__utils__["win_pwsh.run_dict"](remove_cmd)
salt.utils.win_pwsh.run_dict(remove_cmd)
if isinstance(packages, list):
log.debug("Removing %s packages", len(packages))
@ -314,6 +319,10 @@ def install(package):
If a package installed using this function has been deprovisioned
previously, the registry entry marking it as deprovisioned will be removed.
.. NOTE::
There is no ``appx.present`` state. Instead, use the
``dism.provisioned_package_installed`` state.
Args:
package (str):

View file

@ -15,6 +15,7 @@ import salt.utils.json
import salt.utils.platform
import salt.utils.powershell
import salt.utils.versions
import salt.utils.win_pwsh
from salt.exceptions import CommandExecutionError
log = logging.getLogger(__name__)
@ -51,41 +52,6 @@ def __virtual__():
return __virtualname__
def _pshell_json(cmd, cwd=None):
"""
Execute the desired powershell command and ensure that it returns data
in JSON format and load that into python
"""
cmd = "Import-Module ServerManager; {}".format(cmd)
if "convertto-json" not in cmd.lower():
cmd = "{} | ConvertTo-Json".format(cmd)
log.debug("PowerShell: %s", cmd)
ret = __salt__["cmd.run_all"](cmd, shell="powershell", cwd=cwd)
if "pid" in ret:
del ret["pid"]
if ret.get("stderr", ""):
error = ret["stderr"].splitlines()[0]
raise CommandExecutionError(error, info=ret)
if "retcode" not in ret or ret["retcode"] != 0:
# run_all logs an error to log.error, fail hard back to the user
raise CommandExecutionError(
"Issue executing PowerShell {}".format(cmd), info=ret
)
# Sometimes Powershell returns an empty string, which isn't valid JSON
if ret["stdout"] == "":
ret["stdout"] = "{}"
try:
ret = salt.utils.json.loads(ret["stdout"], strict=False)
except ValueError:
raise CommandExecutionError("No JSON results from PowerShell", info=ret)
return ret
def list_available():
"""
List available features to install
@ -129,7 +95,7 @@ def list_installed():
"-WarningAction SilentlyContinue "
"| Select DisplayName,Name,Installed"
)
features = _pshell_json(cmd)
features = salt.utils.win_pwsh.run_dict(cmd)
ret = {}
for entry in features:
@ -230,7 +196,7 @@ def install(feature, recurse=False, restart=False, source=None, exclude=None):
"-IncludeAllSubFeature" if recurse else "",
"" if source is None else "-Source {}".format(source),
)
out = _pshell_json(cmd)
out = salt.utils.win_pwsh.run_dict(cmd)
# Uninstall items in the exclude list
# The Install-WindowsFeature command doesn't have the concept of an exclude
@ -375,7 +341,7 @@ def remove(feature, remove_payload=False, restart=False):
"-Restart" if restart else "",
)
try:
out = _pshell_json(cmd)
out = salt.utils.win_pwsh.run_dict(cmd)
except CommandExecutionError as exc:
if "ArgumentNotValid" in exc.message:
raise CommandExecutionError("Invalid Feature Name", info=exc.info)

View file

@ -13,6 +13,7 @@ import logging
import os
import salt.utils.platform
import salt.utils.win_pwsh
from salt.exceptions import CommandExecutionError
log = logging.getLogger(__name__)
@ -35,41 +36,6 @@ def __virtual__():
return __virtualname__
def _pshell_json(cmd, cwd=None):
"""
Execute the desired powershell command and ensure that it returns data
in JSON format and load that into python
"""
if "convertto-json" not in cmd.lower():
cmd = "{} | ConvertTo-Json".format(cmd)
log.debug("PowerShell: %s", cmd)
ret = __salt__["cmd.run_all"](cmd, shell="powershell", cwd=cwd)
if "pid" in ret:
del ret["pid"]
if ret.get("stderr", ""):
error = ret["stderr"].splitlines()[0]
raise CommandExecutionError(error, info=ret)
if "retcode" not in ret or ret["retcode"] != 0:
# run_all logs an error to log.error, fail hard back to the user
raise CommandExecutionError(
"Issue executing PowerShell {}".format(cmd), info=ret
)
# Sometimes Powershell returns an empty string, which isn't valid JSON
if ret["stdout"] == "":
ret["stdout"] = "{}"
try:
ret = salt.utils.json.loads(ret["stdout"], strict=False)
except ValueError:
raise CommandExecutionError("No JSON results from PowerShell", info=ret)
return ret
def is_installed(name):
"""
Check if a specific KB is installed.
@ -229,7 +195,7 @@ def list():
salt '*' wusa.list
"""
kbs = []
ret = _pshell_json("Get-HotFix | Select HotFixID")
ret = salt.utils.win_pwsh.run_dict("Get-HotFix | Select HotFixID")
for item in ret:
kbs.append(item["HotFixID"])
return kbs

123
salt/states/win_appx.py Normal file
View file

@ -0,0 +1,123 @@
"""
Manage Microsoft Store apps on Windows. Removing an app with this modules will
deprovision the app from the online Windows image.
"""
import fnmatch
import logging
import salt.utils.data
import salt.utils.platform
log = logging.getLogger(__name__)
__virtualname__ = "appx"
def __virtual__():
"""
Only work on Windows where the DISM module is available
"""
if not salt.utils.platform.is_windows():
return False, "Appx state: Only available on Windows"
pwsh_info = __salt__["cmd.shell_info"](shell="powershell", list_modules=False)
if not pwsh_info["installed"]:
return False, "Appx state: PowerShell not available"
return __virtualname__
def absent(
name, query, include_store=False, frameworks=False, deprovision_only=False
):
"""
Removes Microsoft Store packages from the system. If the package is part of
a bundle, the entire bundle will be removed.
This function removes the package for all users on the system. It also
deprovisions the package so that it isn't re-installed by later system
updates. To only deprovision a package and not remove it for all users, set
``deprovision_only=True``.
Args:
query (str):
The query string to use to select the packages to be removed. If the
string matches multiple packages, they will all be removed. Here are
some example strings:
- ``*teams*`` - Remove Microsoft Teams
- ``*zune*`` - Remove Windows Media Player and ZuneVideo
- ``*zuneMusic*`` - Only remove Windows Media Player
- ``*xbox*`` - Remove all xbox packages, there are 5 by default
- ``*`` - Remove everything but the Microsoft Store, unless
``include_store=True``
.. note::
Use the ``appx.list`` function to make sure your query is
returning what you expect. Then use the same query to remove
those packages
include_store (bool):
Include the Microsoft Store in the results of the query to be
removed. Use this with caution. It is difficult to reinstall the
Microsoft Store once it has been removed with this function. Default
is ``False``
frameworks (bool):
Include frameworks in the results of the query to be removed.
Default is ``False``
deprovision_only (bool):
Only deprovision the package. The package will be removed from the
current user and added to the list of deprovisioned packages. The
package will not be re-installed in future system updates. New users
of the system will not have the package installed. However, the
package will still be installed for existing users. Default is
``False``
Returns:
bool: ``True`` if successful, ``None`` if no packages found
Raises:
CommandExecutionError: On errors encountered removing the package
CLI Example:
.. code-block:: bash
salt
"""
ret = {"name": name, "result": True, "comment": "", "changes": {}}
old = __salt__["appx.list"](include_store=include_store, frameworks=frameworks)
matches = fnmatch.filter(old, query)
if not matches:
ret["comment"] = f"No apps found matching query: {query}"
return ret
if __opts__["test"]:
ret["changes"]["removed"] = matches
ret["comment"] = "Matching apps will be removed"
ret["result"] = None
return ret
# Install the capability
status = __salt__["appx.remove"](
query,
include_store=include_store,
frameworks=frameworks,
deprovision_only=deprovision_only
)
if status is None:
ret["comment"] = f"No apps found matching query: {query}"
ret["result"] = False
new = __salt__["appx.list"](include_store=include_store, frameworks=frameworks)
changes = salt.utils.data.compare_lists(old, new)
if changes:
ret["comment"] = f"Removed apps matching query: {query}"
ret["changes"] = changes
return ret