D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
proc
/
self
/
root
/
opt
/
saltstack
/
salt
/
lib
/
python3.10
/
site-packages
/
salt
/
modules
/
Filename :
win_servermanager.py
back
Copy
""" Manage Windows features via the ServerManager powershell module. Can list available and installed roles/features. Can install and remove roles/features. :maintainer: Shane Lee <slee@saltstack.com> :platform: Windows Server 2008R2 or greater :depends: PowerShell module ``ServerManager`` """ import logging import shlex import salt.utils.json import salt.utils.platform import salt.utils.powershell import salt.utils.versions from salt.exceptions import CommandExecutionError log = logging.getLogger(__name__) __virtualname__ = "win_servermanager" def __virtual__(): """ Load only on windows with servermanager module """ if not salt.utils.platform.is_windows(): return ( False, "Module win_servermanager: module only works on Windows systems.", ) if salt.utils.versions.version_cmp(__grains__["osversion"], "6.1.7600") == -1: return ( False, "Failed to load win_servermanager module: " "Requires Remote Server Administration Tools which " "is only available on Windows 2008 R2 and later.", ) if not salt.utils.powershell.module_exists("ServerManager"): return ( False, "Failed to load win_servermanager module: " "ServerManager module not available. " "May need to install Remote Server Administration Tools.", ) 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 = f"Import-Module ServerManager; {cmd}" if "convertto-json" not in cmd.lower(): cmd = f"{cmd} | ConvertTo-Json" 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(f"Issue executing PowerShell {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 Returns: str: A list of available features as returned by the ``Get-WindowsFeature`` PowerShell command CLI Example: .. code-block:: bash salt '*' win_servermanager.list_available """ cmd = ( "Import-Module ServerManager; " "Get-WindowsFeature " "-ErrorAction SilentlyContinue " "-WarningAction SilentlyContinue" ) return __salt__["cmd.shell"](cmd, shell="powershell") def list_installed(): """ List installed features. Supported on Windows Server 2008 and Windows 8 and newer. Returns: dict: A dictionary of installed features CLI Example: .. code-block:: bash salt '*' win_servermanager.list_installed """ cmd = ( "Get-WindowsFeature " "-ErrorAction SilentlyContinue " "-WarningAction SilentlyContinue " "| Select DisplayName,Name,Installed" ) features = _pshell_json(cmd) ret = {} for entry in features: if entry["Installed"]: ret[entry["Name"]] = entry["DisplayName"] return ret def install(feature, recurse=False, restart=False, source=None, exclude=None): r""" Install a feature .. note:: Some features require reboot after un/installation, if so until the server is restarted other features can not be installed! .. note:: Some features take a long time to complete un/installation, set -t with a long timeout Args: feature (str, list): The name of the feature(s) to install. This can be a single feature, a string of features in a comma-delimited list (no spaces), or a list of features. .. versionadded:: 2018.3.0 Added the ability to pass a list of features to be installed. recurse (:obj:`bool`, optional): Install all sub-features. Default is ``False``. restart (:obj:`bool`, optional): Restarts the computer when installation is complete, if required by the role/feature installed. Will also trigger a reboot if an item in ``exclude`` requires a reboot to be properly removed. Default is ``False``. source (:obj:`str`, optional): Path to the source files if missing from the target system. None means that the system will use windows update services to find the required files. Default is ``None``. exclude (:obj:`str`, optional): The name of the feature to exclude when installing the named feature. This can be a single feature, a string of features in a comma-delimited list (no spaces), or a list of features. .. warning:: As there is no exclude option for the ``Add-WindowsFeature`` or ``Install-WindowsFeature`` PowerShell commands the features named in ``exclude`` will be installed with other sub-features and will then be removed. **If the feature named in ``exclude`` is not a sub-feature of one of the installed items it will still be removed.** Default is ``None``. Returns: dict: A dictionary containing the results of the install CLI Example: .. code-block:: bash # Install the Telnet Client passing a single string salt '*' win_servermanager.install Telnet-Client # Install the TFTP Client and the SNMP Service passing a comma-delimited # string. Install all sub-features salt '*' win_servermanager.install TFTP-Client,SNMP-Service recurse=True # Install the TFTP Client from d:\side-by-side salt '*' win_servermanager.install TFTP-Client source=d:\\side-by-side # Install the XPS Viewer, SNMP Service, and Remote Access passing a # list. Install all sub-features, but exclude the Web Server salt '*' win_servermanager.install "['XPS-Viewer', 'SNMP-Service', 'RemoteAccess']" True recurse=True exclude="Web-Server" """ # If it is a list of features, make it a comma delimited string if isinstance(feature, list): feature = ",".join(feature) # Use Install-WindowsFeature on Windows 2012 (osversion 6.2) and later # minions. Default to Add-WindowsFeature for earlier releases of Windows. # The newer command makes management tools optional so add them for parity # with old behavior. command = "Add-WindowsFeature" management_tools = "" if salt.utils.versions.version_cmp(__grains__["osversion"], "6.2") >= 0: command = "Install-WindowsFeature" management_tools = "-IncludeManagementTools" cmd = "{} -Name {} {} {} {} -WarningAction SilentlyContinue".format( command, shlex.quote(feature), management_tools, "-IncludeAllSubFeature" if recurse else "", "" if source is None else f"-Source {source}", ) out = _pshell_json(cmd) # Uninstall items in the exclude list # The Install-WindowsFeature command doesn't have the concept of an exclude # list. So you install first, then remove if exclude is not None: removed = remove(exclude) # Results are stored in a list of dictionaries in `FeatureResult` if out["FeatureResult"]: ret = { "ExitCode": out["ExitCode"], "RestartNeeded": False, "Restarted": False, "Features": {}, "Success": out["Success"], } # FeatureResult is a list of dicts, so each item is a dict for item in out["FeatureResult"]: ret["Features"][item["Name"]] = { "DisplayName": item["DisplayName"], "Message": item["Message"], "RestartNeeded": item["RestartNeeded"], "SkipReason": item["SkipReason"], "Success": item["Success"], } if item["RestartNeeded"]: ret["RestartNeeded"] = True # Only items that installed are in the list of dictionaries # Add 'Already installed' for features that aren't in the list of dicts for item in feature.split(","): if item not in ret["Features"]: ret["Features"][item] = {"Message": "Already installed"} # Some items in the exclude list were removed after installation # Show what was done, update the dict if exclude is not None: # Features is a dict, so it only iterates over the keys for item in removed["Features"]: if item in ret["Features"]: ret["Features"][item] = { "Message": "Removed after installation (exclude)", "DisplayName": removed["Features"][item]["DisplayName"], "RestartNeeded": removed["Features"][item]["RestartNeeded"], "SkipReason": removed["Features"][item]["SkipReason"], "Success": removed["Features"][item]["Success"], } # Exclude items might need a restart if removed["Features"][item]["RestartNeeded"]: ret["RestartNeeded"] = True # Restart here if needed if restart: if ret["RestartNeeded"]: if __salt__["system.reboot"](in_seconds=True): ret["Restarted"] = True return ret else: # If we get here then all features were already installed ret = { "ExitCode": out["ExitCode"], "Features": {}, "RestartNeeded": False, "Restarted": False, "Success": out["Success"], } for item in feature.split(","): ret["Features"][item] = {"Message": "Already installed"} return ret def remove(feature, remove_payload=False, restart=False): r""" Remove an installed feature .. note:: Some features require a reboot after installation/uninstallation. If one of these features are modified, then other features cannot be installed until the server is restarted. Additionally, some features take a while to complete installation/uninstallation, so it is a good idea to use the ``-t`` option to set a longer timeout. Args: feature (str, list): The name of the feature(s) to remove. This can be a single feature, a string of features in a comma-delimited list (no spaces), or a list of features. .. versionadded:: 2018.3.0 Added the ability to pass a list of features to be removed. remove_payload (:obj:`bool`, optional): True will cause the feature to be removed from the side-by-side store (``%SystemDrive%:\Windows\WinSxS``). Default is ``False``. restart (:obj:`bool`, optional): Restarts the computer when uninstall is complete, if required by the role/feature removed. Default is ``False``. Returns: dict: A dictionary containing the results of the uninstall CLI Example: .. code-block:: bash salt -t 600 '*' win_servermanager.remove Telnet-Client """ # If it is a list of features, make it a comma delimited string if isinstance(feature, list): feature = ",".join(feature) # Use Uninstall-WindowsFeature on Windows 2012 (osversion 6.2) and later # minions. Default to Remove-WindowsFeature for earlier releases of Windows. # The newer command makes management tools optional so add them for parity # with old behavior. command = "Remove-WindowsFeature" management_tools = "" _remove_payload = "" if salt.utils.versions.version_cmp(__grains__["osversion"], "6.2") >= 0: command = "Uninstall-WindowsFeature" management_tools = "-IncludeManagementTools" # Only available with the `Uninstall-WindowsFeature` command if remove_payload: _remove_payload = "-Remove" cmd = "{} -Name {} {} {} {} -WarningAction SilentlyContinue".format( command, shlex.quote(feature), management_tools, _remove_payload, "-Restart" if restart else "", ) try: out = _pshell_json(cmd) except CommandExecutionError as exc: if "ArgumentNotValid" in exc.message: raise CommandExecutionError("Invalid Feature Name", info=exc.info) raise # Results are stored in a list of dictionaries in `FeatureResult` if out["FeatureResult"]: ret = { "ExitCode": out["ExitCode"], "RestartNeeded": False, "Restarted": False, "Features": {}, "Success": out["Success"], } for item in out["FeatureResult"]: ret["Features"][item["Name"]] = { "DisplayName": item["DisplayName"], "Message": item["Message"], "RestartNeeded": item["RestartNeeded"], "SkipReason": item["SkipReason"], "Success": item["Success"], } # Only items that installed are in the list of dictionaries # Add 'Not installed' for features that aren't in the list of dicts for item in feature.split(","): if item not in ret["Features"]: ret["Features"][item] = {"Message": "Not installed"} return ret else: # If we get here then none of the features were installed ret = { "ExitCode": out["ExitCode"], "Features": {}, "RestartNeeded": False, "Restarted": False, "Success": out["Success"], } for item in feature.split(","): ret["Features"][item] = {"Message": "Not installed"} return ret