1"""
2Common functions for working with powershell
3
4.. note:: The PSModulePath environment variable should be set to the default
5    location for PowerShell modules. This applies to all OS'es that support
6    powershell. If not set, then Salt will attempt to use some default paths.
7    If Salt can't find your modules, ensure that the PSModulePath is set and
8    pointing to all locations of your Powershell modules.
9"""
10
11import logging
12import os
13
14import salt.utils.path
15
16log = logging.getLogger(__name__)
17
18
19def module_exists(name):
20    """
21    Check if a module exists on the system.
22
23    Use this utility instead of attempting to import the module with powershell.
24    Using powershell to try to import the module is expensive.
25
26    Args:
27
28        name (str):
29            The name of the module to check
30
31    Returns:
32        bool: True if present, otherwise returns False
33
34    Example:
35
36    .. code-block:: python
37
38        import salt.utils.powershell
39        exists = salt.utils.powershell.module_exists('ServerManager')
40    """
41    return name in get_modules()
42
43
44def get_modules():
45    """
46    Get a list of the PowerShell modules which are potentially available to be
47    imported. The intent is to mimic the functionality of ``Get-Module
48    -ListAvailable | Select-Object -Expand Name``, without the delay of loading
49    PowerShell to do so.
50
51    Returns:
52        list: A list of modules available to Powershell
53
54    Example:
55
56    .. code-block:: python
57
58        import salt.utils.powershell
59        modules = salt.utils.powershell.get_modules()
60    """
61    ret = list()
62    valid_extensions = (".psd1", ".psm1", ".cdxml", ".xaml", ".dll")
63    # need to create an info function to get PS information including version
64    # __salt__ is not available from salt.utils... need to create a salt.util
65    # for the registry to avoid loading powershell to get the version
66    # not sure how to get the powershell version in linux outside of powershell
67    # if running powershell to get version need to use subprocess.Popen
68    # That information will be loaded here
69    # ps_version = info()['version_raw']
70    root_paths = []
71
72    home_dir = os.environ.get("HOME", os.environ.get("HOMEPATH"))
73    system_dir = "{}\\System32".format(os.environ.get("WINDIR", "C:\\Windows"))
74    program_files = os.environ.get("ProgramFiles", "C:\\Program Files")
75    default_paths = [
76        "{}/.local/share/powershell/Modules".format(home_dir),
77        # Once version is available, these can be enabled
78        # '/opt/microsoft/powershell/{0}/Modules'.format(ps_version),
79        # '/usr/local/microsoft/powershell/{0}/Modules'.format(ps_version),
80        "/usr/local/share/powershell/Modules",
81        "{}\\WindowsPowerShell\\v1.0\\Modules\\".format(system_dir),
82        "{}\\WindowsPowerShell\\Modules".format(program_files),
83    ]
84    default_paths = ";".join(default_paths)
85
86    ps_module_path = os.environ.get("PSModulePath", default_paths)
87
88    # Check if defaults exist, add them if they do
89    ps_module_path = ps_module_path.split(";")
90    for item in ps_module_path:
91        if os.path.exists(item):
92            root_paths.append(item)
93
94    # Did we find any, if not log the error and return
95    if not root_paths:
96        log.error("Default paths not found")
97        return ret
98
99    for root_path in root_paths:
100
101        # only recurse directories
102        if not os.path.isdir(root_path):
103            continue
104
105        # get a list of all files in the root_path
106        for root_dir, sub_dirs, file_names in salt.utils.path.os_walk(root_path):
107            for file_name in file_names:
108                base_name, file_extension = os.path.splitext(file_name)
109
110                # If a module file or module manifest is present, check if
111                # the base name matches the directory name.
112
113                if file_extension.lower() in valid_extensions:
114                    dir_name = os.path.basename(os.path.normpath(root_dir))
115
116                    # Stop recursion once we find a match, and use
117                    # the capitalization from the directory name.
118                    if dir_name not in ret and base_name.lower() == dir_name.lower():
119                        del sub_dirs[:]
120                        ret.append(dir_name)
121
122    return ret
123