mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Add some documentation, add functions and states in dism modules
This commit is contained in:
parent
e7785cb886
commit
bc1217c99d
3 changed files with 266 additions and 28 deletions
|
@ -1,3 +1,45 @@
|
|||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
they be upgraded.
|
||||
|
||||
An app removed with this module can only be re-provisioned on the machine, but
|
||||
it can't be re-installed for all users. Also, once a package has been
|
||||
deprovisioned, the only way to reinstall it is to download the package. This is
|
||||
difficult. I've outlined the steps below:
|
||||
|
||||
1. Obtain the Microsoft Store URL for the app:
|
||||
- Open the page for the app in the Microsoft Store
|
||||
- Click the share button and copy the URL
|
||||
|
||||
2. Look up the packages on https://store.rg-adguard.net/:
|
||||
- Ensure ``URL (link)`` is selected in the first dropdown
|
||||
- Paste the URL in the search field
|
||||
- Ensure Retail is selected in the 2nd dropdown
|
||||
- Click the checkmark button
|
||||
|
||||
This should give you a list of URLs for the package and all dependencies for all
|
||||
architectures. Download the package and all dependencies for your system
|
||||
architecture. These will usually have one of the following file extensions:
|
||||
|
||||
- ``.appx``
|
||||
- ``.appxbundle``
|
||||
- ``.msix``
|
||||
- ``.msixbundle``
|
||||
|
||||
Dependencies will need to be installed first.
|
||||
|
||||
Not all packages can be found this way, but it seems like most of them can.
|
||||
|
||||
Use the ``appx.install`` function to provision the new app.
|
||||
"""
|
||||
import fnmatch
|
||||
import logging
|
||||
|
||||
|
@ -6,9 +48,11 @@ import salt.utils.win_reg
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
CURRENTVERSION_KEY = r"SOFTWARE\Microsoft\Windows\CurrentVersion"
|
||||
DEPROVISIONED_KEY = fr"{CURRENTVERSION_KEY}\Appx\AppxAllUserStore\Deprovisioned"
|
||||
CURRENT_VERSION_KEY = r"SOFTWARE\Microsoft\Windows\CurrentVersion"
|
||||
DEPROVISIONED_KEY = fr"{CURRENT_VERSION_KEY}\Appx\AppxAllUserStore\Deprovisioned"
|
||||
|
||||
__virtualname__ = "appx"
|
||||
__func_alias__ = {"list_": "list"}
|
||||
|
||||
|
||||
def __virtual__():
|
||||
|
@ -21,7 +65,7 @@ def __virtual__():
|
|||
return __virtualname__
|
||||
|
||||
|
||||
def _pkg_list(raw, field="PackageFullName"):
|
||||
def _pkg_list(raw, field="Name"):
|
||||
result = []
|
||||
if raw:
|
||||
if isinstance(raw, list):
|
||||
|
@ -34,7 +78,7 @@ def _pkg_list(raw, field="PackageFullName"):
|
|||
return result
|
||||
|
||||
|
||||
def get(query=None, field="Name", include_store=False, frameworks=False, bundles=True):
|
||||
def list_(query=None, field="Name", include_store=False, frameworks=False, bundles=True):
|
||||
"""
|
||||
Get a list of Microsoft Store packages installed on the system.
|
||||
|
||||
|
@ -82,6 +126,24 @@ def get(query=None, field="Name", include_store=False, frameworks=False, bundles
|
|||
Raises:
|
||||
CommandExecutionError: If an error is encountered retrieving packages
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# List installed apps that contain the word "candy"
|
||||
salt '*' appx.list *candy*
|
||||
|
||||
# Return more information about the package
|
||||
salt '*' appx.list *candy* field=None
|
||||
|
||||
# List all installed apps, including the Microsoft Store
|
||||
salt '*' appx.list include_store=True
|
||||
|
||||
# List all installed apps, including frameworks
|
||||
salt '*' appx.list frameworks=True
|
||||
|
||||
# List all installed apps that are bundles
|
||||
salt '*' appx.list bundles=True
|
||||
"""
|
||||
cmd = []
|
||||
|
||||
|
@ -113,9 +175,9 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
|
|||
Removes Microsoft Store packages from the system. If the package is part of
|
||||
a bundle, the entire bundle will be removed.
|
||||
|
||||
By default, this function will remove the package for all users on the
|
||||
system. It will also deprovision the packages so that it isn't re-installed
|
||||
by later system updates. To only deprovision a package, set
|
||||
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
|
||||
``deprovision_only=True``.
|
||||
|
||||
Args:
|
||||
|
@ -134,7 +196,8 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
|
|||
|
||||
.. note::
|
||||
Use the ``appx.get`` function to make sure your query is
|
||||
returning what you expect
|
||||
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
|
||||
|
@ -160,8 +223,13 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
|
|||
Raises:
|
||||
CommandExecutionError: On errors encountered removing the package
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt
|
||||
"""
|
||||
packages = get(
|
||||
packages = list_(
|
||||
query=query,
|
||||
field=None,
|
||||
include_store=include_store,
|
||||
|
@ -175,7 +243,7 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
|
|||
# fail. Let's make sure it's a bundle
|
||||
if not package["IsBundle"]:
|
||||
# If it's not a bundle, let's see if we can find the bundle
|
||||
bundle = get(
|
||||
bundle = list_(
|
||||
query=f'{package["Name"]}*',
|
||||
field=None,
|
||||
include_store=include_store,
|
||||
|
@ -210,8 +278,9 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
|
|||
|
||||
def get_deprovisioned(query=None):
|
||||
"""
|
||||
Get a list of applications that have been deprovisioned. A deprovisioned
|
||||
package will not be reinstalled during a Major system update.
|
||||
When an app is deprovisioned, a registry key is created that will keep it
|
||||
from being reinstalled during a major system update. This function returns a
|
||||
list of keys for apps that have been deprovisioned.
|
||||
|
||||
Args:
|
||||
|
||||
|
@ -236,20 +305,30 @@ def get_deprovisioned(query=None):
|
|||
return fnmatch.filter(ret, query)
|
||||
|
||||
|
||||
def install(package):
|
||||
"""
|
||||
This function uses ``dism`` to provision a package. This means that it will
|
||||
be made a part of the online image and added to new users on the system. If
|
||||
a package has dependencies, those must be installed first.
|
||||
|
||||
def reprovision(query=None):
|
||||
pkgs = salt.utils.win_reg.list_keys(hive="HKLM", key=f"{DEPROVISIONED_KEY}")
|
||||
failed = []
|
||||
for item in fnmatch.filter(pkgs, query):
|
||||
key = f"{DEPROVISIONED_KEY}\\{item}"
|
||||
log.debug(f"Deprovisioning app: {item}")
|
||||
ret = salt.utils.win_reg.delete_key_recursive(hive="HKLM", key=key)
|
||||
if ret["Failed"]:
|
||||
log.debug(f"Failed to deprovision: {item}")
|
||||
failed.append(item)
|
||||
if failed:
|
||||
return {"Failed to deprovision": failed}
|
||||
return True
|
||||
If a package installed using this function has been deprovisioned
|
||||
previously, the registry entry marking it as deprovisioned will be removed.
|
||||
|
||||
Args:
|
||||
|
||||
package (str):
|
||||
The full path to the package to install. Can be one of the
|
||||
following:
|
||||
|
||||
- ``.appx`` or ``.appxbundle``
|
||||
- ``.msix`` or ``.msixbundle``
|
||||
- ``.ppkg``
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if successful, otherwise ``False``
|
||||
"""
|
||||
# I don't see a way to make the app installed for existing users on
|
||||
# the system. The best we can do is provision the package for new
|
||||
# users
|
||||
ret = __salt__["dism.add_provisioned_package"](package)
|
||||
return ret["retcode"] == 0
|
||||
|
|
|
@ -56,7 +56,10 @@ def _get_components(type_regex, plural_type, install_value, image=None):
|
|||
"/Get-{}".format(plural_type),
|
||||
]
|
||||
out = __salt__["cmd.run"](cmd)
|
||||
pattern = r"{} : (.*)\r\n.*State : {}\r\n".format(type_regex, install_value)
|
||||
if install_value:
|
||||
pattern = r"{} : (.*)\r\n.*State : {}\r\n".format(type_regex, install_value)
|
||||
else:
|
||||
pattern = r"{} : (.*)\r\n.*".format(type_regex, install_value)
|
||||
capabilities = re.findall(pattern, out, re.MULTILINE)
|
||||
capabilities.sort()
|
||||
return capabilities
|
||||
|
@ -511,6 +514,58 @@ def add_package(
|
|||
return __salt__["cmd.run_all"](cmd)
|
||||
|
||||
|
||||
def add_provisioned_package(package, image=None, restart=False):
|
||||
"""
|
||||
Provision a package using DISM. A provisioned package will install for new
|
||||
users on the system. It will also be reinstalled on each user if the system
|
||||
if updated.
|
||||
|
||||
Args:
|
||||
|
||||
package (str):
|
||||
The package to install. Can be one of the following:
|
||||
|
||||
- ``.appx`` or ``.appxbundle``
|
||||
- ``.msix`` or ``.msixbundle``
|
||||
- ``.ppkg``
|
||||
|
||||
image (Optional[str]):
|
||||
The path to the root directory of an offline Windows image. If
|
||||
``None`` is passed, the running operating system is targeted.
|
||||
Default is ``None``.
|
||||
|
||||
restart (Optional[bool]):
|
||||
Reboot the machine if required by the installation. Default is
|
||||
``False``
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of the command
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism.add_provisioned_package C:\\Packages\\package.appx
|
||||
salt '*' dism.add_provisioned_package C:\\Packages\\package.appxbundle
|
||||
salt '*' dism.add_provisioned_package C:\\Packages\\package.msix
|
||||
salt '*' dism.add_provisioned_package C:\\Packages\\package.msixbundle
|
||||
salt '*' dism.add_provisioned_package C:\\Packages\\package.ppkg
|
||||
"""
|
||||
cmd = [
|
||||
bin_dism,
|
||||
"/Quiet",
|
||||
"/Image:{}".format(image) if image else "/Online",
|
||||
"/Add-ProvisionedAppxPackage",
|
||||
"/PackagePath:{}".format(package),
|
||||
"/SkipLicense",
|
||||
]
|
||||
|
||||
if not restart:
|
||||
cmd.append("/NoRestart")
|
||||
|
||||
return __salt__["cmd.run_all"](cmd)
|
||||
|
||||
|
||||
def remove_package(package, image=None, restart=False):
|
||||
"""
|
||||
Uninstall a package
|
||||
|
@ -654,6 +709,32 @@ def installed_packages(image=None):
|
|||
)
|
||||
|
||||
|
||||
def provisioned_packages(image=None):
|
||||
"""
|
||||
List the packages installed on the system
|
||||
|
||||
Args:
|
||||
image (Optional[str]): The path to the root directory of an offline
|
||||
Windows image. If `None` is passed, the running operating system is
|
||||
targeted. Default is None.
|
||||
|
||||
Returns:
|
||||
list: A list of installed packages
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism.installed_packages
|
||||
"""
|
||||
return _get_components(
|
||||
type_regex="PackageName",
|
||||
plural_type="ProvisionedAppxPackages",
|
||||
install_value="",
|
||||
image=image,
|
||||
)
|
||||
|
||||
|
||||
def package_info(package, image=None):
|
||||
"""
|
||||
Display information about a package
|
||||
|
@ -674,7 +755,7 @@ def package_info(package, image=None):
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism. package_info C:\\packages\\package.cab
|
||||
salt '*' dism.package_info C:\\packages\\package.cab
|
||||
"""
|
||||
cmd = [
|
||||
bin_dism,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Installing of Windows features using DISM
|
||||
=========================================
|
||||
|
||||
Install windows features/capabilties with DISM
|
||||
Install Windows features/capabilities/packages with DISM
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
@ -342,6 +342,84 @@ def package_installed(
|
|||
return ret
|
||||
|
||||
|
||||
def provisioned_package_installed(name, image=None, restart=False):
|
||||
"""
|
||||
Install a package.
|
||||
|
||||
Args:
|
||||
|
||||
name (str):
|
||||
The package to install. Can be one of the following:
|
||||
|
||||
- ``.appx`` or ``.appxbundle``
|
||||
- ``.msix`` or ``.msixbundle``
|
||||
- ``.ppkg``
|
||||
|
||||
The name of the file before the file extension must match the name
|
||||
of the package after it is installed. This name can be found by
|
||||
running ``dism.provisioned_packages".
|
||||
|
||||
image (Optional[str]):
|
||||
The path to the root directory of an offline Windows image. If
|
||||
``None`` is passed, the running operating system is targeted.
|
||||
Default is ``None``.
|
||||
|
||||
restart (Optional[bool]):
|
||||
Reboot the machine if required by the installation. Default is
|
||||
``False``
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
install_windows_media_player:
|
||||
dism.provisioned_package_installed:
|
||||
- name: C:\\Packages\\Microsoft.ZuneVideo_2019.22091.10036.0_neutral_~_8wekyb3d8bbwe.Msixbundle
|
||||
|
||||
"""
|
||||
ret = {"name": name, "result": True, "comment": "", "changes": {}}
|
||||
|
||||
# Fail if using a non-existent package path
|
||||
if not os.path.exists(name):
|
||||
if __opts__["test"]:
|
||||
ret["result"] = None
|
||||
else:
|
||||
ret["result"] = False
|
||||
ret["comment"] = f"Package path {name} does not exist"
|
||||
return ret
|
||||
|
||||
old = __salt__["dism.provisioned_packages"]()
|
||||
|
||||
# Get package name so we can see if it's already installed
|
||||
package_name = os.path.splitext(os.path.basename(name))
|
||||
|
||||
if package_name in old:
|
||||
ret["comment"] = f"The package {name} is already installed: {package_name}"
|
||||
return ret
|
||||
|
||||
if __opts__["test"]:
|
||||
ret["changes"]["package"] = f"{name} will be installed"
|
||||
ret["result"] = None
|
||||
return ret
|
||||
|
||||
# Install the package
|
||||
status = __salt__["dism.add_provisioned_package"](name, image, restart)
|
||||
|
||||
if status["retcode"] not in [0, 1641, 3010]:
|
||||
ret["comment"] = f'Failed to install {name}: {status["stdout"]}'
|
||||
ret["result"] = False
|
||||
|
||||
new = __salt__["dism.provisioned_packages"]()
|
||||
changes = salt.utils.data.compare_lists(old, new)
|
||||
|
||||
if changes:
|
||||
ret["comment"] = f"Installed {name}"
|
||||
ret["changes"] = status
|
||||
ret["changes"]["package"] = changes
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def package_removed(name, image=None, restart=False):
|
||||
"""
|
||||
Uninstall a package
|
||||
|
|
Loading…
Add table
Reference in a new issue