1"""
2Win System Utils
3
4Functions shared with salt.modules.win_system and salt.grains.pending_reboot
5
6.. versionadded:: 3001
7"""
8# NOTE: DO NOT USE RAW STRINGS IN THIS MODULE! UNICODE_LITERALS DOES NOT PLAY
9# NICELY WITH RAW STRINGS CONTAINING \u or \U.
10
11import logging
12
13import salt.utils.win_reg
14import salt.utils.win_update
15
16try:
17    import win32api
18    import win32con
19
20    HAS_WIN32_MODS = True
21except ImportError:
22    HAS_WIN32_MODS = False
23
24
25log = logging.getLogger(__name__)
26
27# Define the module's virtual name
28__virtualname__ = "win_system"
29MINION_VOLATILE_KEY = "SYSTEM\\CurrentControlSet\\Services\\salt-minion\\Volatile-Data"
30REBOOT_REQUIRED_NAME = "Reboot required"
31
32
33def __virtual__():
34    """
35    Only works on Windows systems
36    """
37    if not salt.utils.platform.is_windows():
38        return (
39            False,
40            "win_system salt util failed to load: "
41            "The util will only run on Windows systems",
42        )
43    if not HAS_WIN32_MODS:
44        return (
45            False,
46            "win_system salt util failed to load: "
47            "The util will only run on Windows systems",
48        )
49    return __virtualname__
50
51
52def get_computer_name():
53    """
54    Get the Windows computer name. Uses the win32api to get the current computer
55    name.
56
57    .. versionadded:: 3001
58
59    Returns:
60        str: Returns the computer name if found. Otherwise returns ``False``.
61
62    Example:
63
64    .. code-block:: python
65
66        import salt.utils.win_system
67        salt.utils.win_system.get_computer_name()
68    """
69    name = win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
70    return name if name else False
71
72
73def get_pending_computer_name():
74    """
75    Get a pending computer name. If the computer name has been changed, and the
76    change is pending a system reboot, this function will return the pending
77    computer name. Otherwise, ``None`` will be returned. If there was an error
78    retrieving the pending computer name, ``False`` will be returned, and an
79    error message will be logged to the minion log.
80
81    .. versionadded:: 3001
82
83    Returns:
84        str:
85            Returns the pending name if pending restart. Returns ``None`` if not
86            pending restart.
87
88    Example:
89
90    .. code-block:: python
91
92        import salt.utils.win_system
93        salt.utils.win_system.get_pending_computer_name()
94    """
95    current = get_computer_name()
96    try:
97        pending = salt.utils.win_reg.read_value(
98            hive="HKLM",
99            key="SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters",
100            vname="NV Hostname",
101        )["vdata"]
102    except TypeError:
103        # This should never happen as the above key and vname are system names
104        # and should always be present
105        return None
106    if pending:
107        return pending if pending.lower() != current.lower() else None
108
109
110def get_pending_component_servicing():
111    """
112    Determine whether there are pending Component Based Servicing tasks that
113    require a reboot.
114
115    If any the following registry keys exist then a reboot is pending:
116
117    ``HKLM:\\\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending``
118    ``HKLM:\\\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootInProgress``
119    ``HKLM:\\\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\PackagesPending``
120
121    .. versionadded:: 3001
122
123    Returns:
124        bool: ``True`` if there are pending Component Based Servicing tasks,
125        otherwise ``False``
126
127    CLI Example:
128
129    .. code-block:: bash
130
131        salt '*' system.get_pending_component_servicing
132    """
133    # So long as one of the registry keys exists, a reboot is pending
134    base_key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing"
135    sub_keys = ("RebootPending", "RebootInProgress", "PackagesPending")
136    for sub_key in sub_keys:
137        key = "\\".join((base_key, sub_key))
138        if salt.utils.win_reg.key_exists(hive="HKLM", key=key):
139            return True
140
141    return False
142
143
144def get_pending_domain_join():
145    """
146    Determine whether there is a pending domain join action that requires a
147    reboot.
148
149    If any the following registry keys exist then a reboot is pending:
150
151    ``HKLM:\\\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\AvoidSpnSet``
152    ``HKLM:\\\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\JoinDomain``
153
154    .. versionadded:: 3001
155
156    Returns:
157        bool: ``True`` if there is a pending domain join action, otherwise
158        ``False``
159
160    Example:
161
162    .. code-block:: python
163
164        import salt.utils.win_system
165        salt.utils.win_system.get_pending_domain_join()
166    """
167    base_key = "SYSTEM\\CurrentControlSet\\Services\\Netlogon"
168    sub_keys = ("AvoidSpnSet", "JoinDomain")
169
170    # If any keys are present then there is a reboot pending.
171    for sub_key in sub_keys:
172        key = "\\".join((base_key, sub_key))
173        if salt.utils.win_reg.key_exists(hive="HKLM", key=key):
174            return True
175
176    return False
177
178
179def get_pending_file_rename():
180    """
181    Determine whether there are pending file rename operations that require a
182    reboot.
183
184    A reboot is pending if any of the following value names exist and have value
185    data set:
186
187    - ``PendingFileRenameOperations``
188    - ``PendingFileRenameOperations2``
189
190    in the following registry key:
191
192    ``HKLM:\\\\SYSTEM\\CurrentControlSet\\Control\\Session Manager``
193
194    .. versionadded:: 3001
195
196    Returns:
197        bool: ``True`` if there are pending file rename operations, otherwise
198        ``False``
199
200    Example:
201
202    .. code-block:: python
203
204        import salt.utils.win_system
205        salt.utils.win_system.get_pending_file_rename()
206    """
207    vnames = ("PendingFileRenameOperations", "PendingFileRenameOperations2")
208    key = "SYSTEM\\CurrentControlSet\\Control\\Session Manager"
209    for vname in vnames:
210        reg_ret = salt.utils.win_reg.read_value(hive="HKLM", key=key, vname=vname)
211        if reg_ret["success"]:
212            if reg_ret["vdata"] and (reg_ret["vdata"] != "(value not set)"):
213                return True
214    return False
215
216
217def get_pending_servermanager():
218    """
219    Determine whether there are pending Server Manager tasks that require a
220    reboot.
221
222    A reboot is pending if the ``CurrentRebootAttempts`` value name exists and
223    has an integer value. The value name resides in the following registry key:
224
225    ``HKLM:\\\\SOFTWARE\\Microsoft\\ServerManager``
226
227    .. versionadded:: 3001
228
229    Returns:
230        bool: ``True`` if there are pending Server Manager tasks, otherwise
231        ``False``
232
233    Example:
234
235    .. code-block:: python
236
237        import salt.utils.win_system
238        salt.utils.win_system.get_pending_servermanager()
239    """
240    vname = "CurrentRebootAttempts"
241    key = "SOFTWARE\\Microsoft\\ServerManager"
242
243    # There are situations where it's possible to have '(value not set)' as
244    # the value data, and since an actual reboot won't be pending in that
245    # instance, just catch instances where we try unsuccessfully to cast as int.
246
247    reg_ret = salt.utils.win_reg.read_value(hive="HKLM", key=key, vname=vname)
248    if reg_ret["success"]:
249        try:
250            if int(reg_ret["vdata"]) > 0:
251                return True
252        except ValueError:
253            pass
254    return False
255
256
257def get_pending_dvd_reboot():
258    """
259    Determine whether the DVD Reboot flag is set.
260
261    The system requires a reboot if the ``DVDRebootSignal`` value name exists
262    at the following registry location:
263
264    ``HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce``
265
266    .. versionadded:: 3001
267
268    Returns:
269        bool: ``True`` if the above condition is met, otherwise ``False``
270
271    Example:
272
273    .. code-block:: python
274
275        import salt.utils.win_system
276        salt.utils.win_system.get_pending_dvd_reboot()
277    """
278    # So long as the registry key exists, a reboot is pending.
279    return salt.utils.win_reg.value_exists(
280        hive="HKLM",
281        key="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
282        vname="DVDRebootSignal",
283    )
284
285
286def get_pending_update():
287    """
288    Determine whether there are pending updates that require a reboot.
289
290    If either of the following registry keys exists, a reboot is pending:
291
292    ``HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired``
293    ``HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\PostRebootReporting``
294
295    .. versionadded:: 3001
296
297    Returns:
298        bool: ``True`` if any of the above conditions are met, otherwise
299        ``False``
300
301    Example:
302
303    .. code-block:: python
304
305        import salt.utils.win_system
306        salt.utils.win_system.get_pending_update()
307    """
308    # So long as any of the registry keys exists, a reboot is pending.
309    base_key = (
310        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update"
311    )
312    sub_keys = ("RebootRequired", "PostRebootReporting")
313    for sub_key in sub_keys:
314        key = "\\".join((base_key, sub_key))
315        if salt.utils.win_reg.key_exists(hive="HKLM", key=key):
316            return True
317
318    return False
319
320
321def get_reboot_required_witnessed():
322    """
323    Determine if at any time during the current boot session the salt minion
324    witnessed an event indicating that a reboot is required.
325
326    This function will return ``True`` if an install completed with exit
327    code 3010 during the current boot session and can be extended where
328    appropriate in the future.
329
330    If the ``Reboot required`` value name exists in the following location and
331    has a value of ``1`` then the system is pending reboot:
332
333    ``HKLM:\\\\SYSTEM\\CurrentControlSet\\Services\\salt-minion\\Volatile-Data``
334
335    .. versionadded:: 3001
336
337    Returns:
338        bool: ``True`` if the ``Requires reboot`` registry flag is set to ``1``,
339        otherwise ``False``
340
341    Example:
342
343    .. code-block:: python
344
345        import salt.utils.win_system
346        salt.utils.win_system.get_reboot_required_witnessed()
347
348    """
349    value_dict = salt.utils.win_reg.read_value(
350        hive="HKLM", key=MINION_VOLATILE_KEY, vname=REBOOT_REQUIRED_NAME
351    )
352    return value_dict["vdata"] == 1
353
354
355def set_reboot_required_witnessed():
356    """
357    This function is used to remember that an event indicating that a reboot is
358    required was witnessed. This function relies on the salt-minion's ability to
359    create the following volatile registry key in the *HKLM* hive:
360
361       *SYSTEM\\CurrentControlSet\\Services\\salt-minion\\Volatile-Data*
362
363    Because this registry key is volatile, it will not persist beyond the
364    current boot session. Also, in the scope of this key, the name *'Reboot
365    required'* will be assigned the value of *1*.
366
367    For the time being, this function is being used whenever an install
368    completes with exit code 3010 and can be extended where appropriate in the
369    future.
370
371    .. versionadded:: 3001
372
373    Returns:
374        bool: ``True`` if successful, otherwise ``False``
375
376    Example:
377
378    .. code-block:: python
379
380        import salt.utils.win_system
381        salt.utils.win_system.set_reboot_required_witnessed()
382    """
383    return salt.utils.win_reg.set_value(
384        hive="HKLM",
385        key=MINION_VOLATILE_KEY,
386        volatile=True,
387        vname=REBOOT_REQUIRED_NAME,
388        vdata=1,
389        vtype="REG_DWORD",
390    )
391
392
393def get_pending_update_exe_volatile():
394    """
395    Determine whether there is a volatile update exe that requires a reboot.
396
397    Checks ``HKLM:\\Microsoft\\Updates``. If the ``UpdateExeVolatile`` value
398    name is anything other than 0 there is a reboot pending
399
400    .. versionadded:: 3001
401
402    Returns:
403        bool: ``True`` if there is a volatile exe, otherwise ``False``
404
405    Example:
406
407    .. code-block:: python
408
409        import salt.utils.win_system
410        salt.utils.win_system.get_pending_update_exe_volatile()
411    """
412    key = "SOFTWARE\\Microsoft\\Updates"
413    reg_ret = salt.utils.win_reg.read_value(
414        hive="HKLM", key=key, vname="UpdateExeVolatile"
415    )
416    if reg_ret["success"]:
417        try:
418            if int(reg_ret["vdata"]) != 0:
419                return True
420        except ValueError:
421            pass
422    return False
423
424
425def get_pending_windows_update():
426    """
427    Check the Windows Update system for a pending reboot state.
428
429    This leverages the Windows Update System to determine if the system is
430    pending a reboot.
431
432    .. versionadded:: 3001
433
434    Returns:
435        bool: ``True`` if the Windows Update system reports a pending update,
436        otherwise ``False``
437
438    Example:
439
440    .. code-block:: python
441
442        import salt.utils.win_system
443        salt.utils.win_system.get_pending_windows_update()
444    """
445    return salt.utils.win_update.needs_reboot()
446
447
448def get_pending_reboot():
449    """
450    Determine whether there is a reboot pending.
451
452    .. versionadded:: 3001
453
454    Returns:
455        bool: ``True`` if the system is pending reboot, otherwise ``False``
456
457    Example:
458
459    .. code-block:: python
460
461        import salt.utils.win_system
462        salt.utils.win_system.get_pending_reboot()
463    """
464    # Order the checks for reboot pending in most to least likely.
465    checks = (
466        get_pending_update,
467        get_pending_windows_update,
468        get_pending_update_exe_volatile,
469        get_pending_file_rename,
470        get_pending_servermanager,
471        get_pending_component_servicing,
472        get_pending_dvd_reboot,
473        get_reboot_required_witnessed,
474        get_pending_computer_name,
475        get_pending_domain_join,
476    )
477
478    for check in checks:
479        if check():
480            return True
481
482    return False
483
484
485def get_pending_reboot_details():
486    """
487    Determine which check is signalling that the system is pending a reboot.
488    Useful in determining why your system is signalling that it needs a reboot.
489
490    .. versionadded:: 3001
491
492    Returns:
493        dict: A dictionary of the results of each function that checks for a
494        pending reboot
495
496    Example:
497
498    .. code-block:: python
499
500        import salt.utils.win_system
501        salt.utils.win_system.get_pending_reboot_details()
502    """
503    return {
504        "Pending Component Servicing": get_pending_component_servicing(),
505        "Pending Computer Rename": get_pending_computer_name() is not None,
506        "Pending DVD Reboot": get_pending_dvd_reboot(),
507        "Pending File Rename": get_pending_file_rename(),
508        "Pending Join Domain": get_pending_domain_join(),
509        "Pending ServerManager": get_pending_servermanager(),
510        "Pending Update": get_pending_update(),
511        "Pending Windows Update": get_pending_windows_update(),
512        "Reboot Required Witnessed": get_reboot_required_witnessed(),
513        "Volatile Update Exe": get_pending_update_exe_volatile(),
514    }
515