From e0813d7d47723a8a0c256dd5c6e446f2e331ef19 Mon Sep 17 00:00:00 2001
From: Jordan Borean <jborean93@gmail.com>
Date: Tue, 15 May 2018 09:33:36 +1000
Subject: [PATCH] become win: better error messages and docs update (#39936)

* become win: better error messages and docs update

* Fix syntax error and added changelog fragment
---
 .../window_become-better-errors.yaml          |  2 +
 docs/docsite/rst/user_guide/become.rst        | 24 ++++++++--
 lib/ansible/plugins/shell/powershell.py       | 12 +++--
 .../targets/win_become/tasks/main.yml         | 46 +++++++++++++++++--
 4 files changed, 72 insertions(+), 12 deletions(-)
 create mode 100644 changelogs/fragments/window_become-better-errors.yaml

diff --git a/changelogs/fragments/window_become-better-errors.yaml b/changelogs/fragments/window_become-better-errors.yaml
new file mode 100644
index 00000000000..f1a2d24903c
--- /dev/null
+++ b/changelogs/fragments/window_become-better-errors.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+- windows become - Show better error messages when the become process fails
diff --git a/docs/docsite/rst/user_guide/become.rst b/docs/docsite/rst/user_guide/become.rst
index baefb7fda06..ad47644fed3 100644
--- a/docs/docsite/rst/user_guide/become.rst
+++ b/docs/docsite/rst/user_guide/become.rst
@@ -342,7 +342,7 @@ module execution.
 To determine the type of token that Ansible was able to get, run the following
 task and check the output::
 
-    - win_shell: cmd.exe /c whoami && whoami /groups && whoami /priv
+    - win_whoami:
       become: yes
 
 Under the ``GROUP INFORMATION`` section, the ``Mandatory Label`` entry
@@ -453,7 +453,11 @@ or with this Ansible task:
 
 Become Flags
 ------------
-Ansible 2.5 adds the ``become_flags`` parameter to the ``runas`` become method. This parameter can be set using the ``become_flags`` task directive or set in Ansible's configuration using ``ansible_become_flags``. The two valid values that are initially supported for this parameter are ``logon_type`` and ``logon_flags``.
+Ansible 2.5 adds the ``become_flags`` parameter to the ``runas`` become method.
+This parameter can be set using the ``become_flags`` task directive or set in
+Ansible's configuration using ``ansible_become_flags``. The two valid values
+that are initially supported for this parameter are ``logon_type`` and
+``logon_flags``.
 
 
 .. Note:: These flags should only be set when becoming a normal user account, not a local service account like LocalSystem.
@@ -490,7 +494,7 @@ For more information, see
 `dwLogonType <https://msdn.microsoft.com/en-au/library/windows/desktop/aa378184.aspx>`_.
 
 The ``logon_flags`` key specifies how Windows will log the user on when creating
-the new process. The value can be set to one of the following:
+the new process. The value can be set to none or multiple of the following:
 
 * ``with_profile``: The default logon flag set. The process will load the
   user's profile in the ``HKEY_USERS`` registry key to ``HKEY_CURRENT_USER``.
@@ -500,6 +504,10 @@ the new process. The value can be set to one of the following:
   resource. This is useful in inter-domain scenarios where there is no trust
   relationship, and should be used with the ``new_credentials`` ``logon_type``.
 
+By default ``logon_flags=with_profile`` is set, if the profile should not be
+loaded set ``logon_flags=`` or if the profile should be loaded with
+``netcredentials_only``, set ``logon_flags=with_profile,netcredentials_only``.
+
 For more information, see `dwLogonFlags <https://msdn.microsoft.com/en-us/library/windows/desktop/ms682434.aspx>`_.
 
 Here are some examples of how to use ``become_flags`` with Windows tasks:
@@ -519,10 +527,15 @@ Here are some examples of how to use ``become_flags`` with Windows tasks:
       ansible_become_flags: logon_type=new_credentials logon_flags=netcredentials_only
 
   - name: run a command under a batch logon
-    win_command: whoami
+    win_whoami:
     become: yes
     become_flags: logon_type=batch
 
+  - name: run a command and not load the user profile
+    win_whomai:
+    become: yes
+    become_flags: logon_flags=
+
 
 Limitations
 -----------
@@ -535,7 +548,8 @@ Be aware of the following limitations with ``become`` on Windows:
 * By default, the become user logs on with an interactive session, so it must
   have the right to do so on the Windows host. If it does not inherit the
   ``SeAllowLogOnLocally`` privilege or inherits the ``SeDenyLogOnLocally``
-  privilege, the become process will fail.
+  privilege, the become process will fail. Either add the privilege or set the
+  ``logon_type`` flag to change the logon type used.
 
 * Prior to Ansible version 2.3, become only worked when
   ``ansible_winrm_transport`` was either ``basic`` or ``credssp``. This
diff --git a/lib/ansible/plugins/shell/powershell.py b/lib/ansible/plugins/shell/powershell.py
index 3fd6411e156..693d4127967 100644
--- a/lib/ansible/plugins/shell/powershell.py
+++ b/lib/ansible/plugins/shell/powershell.py
@@ -1161,10 +1161,14 @@ namespace Ansible
     Write-Output ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((Write-Output $output))))
 } # end exec_wrapper
 
-Function Dump-Error ($excep) {
+Function Dump-Error ($excep, $msg=$null) {
     $eo = @{failed=$true}
 
-    $eo.msg = $excep.Exception.Message
+    $exception_message = $excep.Exception.Message
+    if ($null -ne $msg) {
+        $exception_message = "$($msg): $exception_message"
+    }
+    $eo.msg = $exception_message
     $eo.exception = $excep | Out-String
     $host.SetShouldExit(1)
 
@@ -1243,7 +1247,7 @@ Function Run($payload) {
     try {
         $logon_type, $logon_flags = Parse-BecomeFlags -flags $payload.become_flags
     } catch {
-        Dump-Error -excep $_
+        Dump-Error -excep $_ -msg "Failed to parse become_flags '$($payload.become_flags)'"
         return $null
     }
 
@@ -1285,7 +1289,7 @@ Function Run($payload) {
         [Console]::Error.WriteLine($stderr.Trim())
     } Catch {
         $excep = $_
-        Dump-Error $excep
+        Dump-Error -excep $excep -msg "Failed to become user $username"
     } Finally {
         Remove-Item $temp -ErrorAction SilentlyContinue
     }
diff --git a/test/integration/targets/win_become/tasks/main.yml b/test/integration/targets/win_become/tasks/main.yml
index a71e2bb9c9a..e860ff78e44 100644
--- a/test/integration/targets/win_become/tasks/main.yml
+++ b/test/integration/targets/win_become/tasks/main.yml
@@ -125,6 +125,18 @@
   - name: test with module that will return non-zero exit code (https://github.com/ansible/ansible/issues/30468)
     vars: *become_vars
     setup:
+
+  - name: test become with invalid password
+    win_whoami:
+    vars:
+      ansible_become_pass: '{{ gen_pw }}abc'
+    become: yes
+    become_method: runas
+    become_user: '{{ become_test_username }}'
+    register: become_invalid_pass
+    failed_when:
+    - '"Failed to become user " + become_test_username not in become_invalid_pass.msg'
+    - '"LogonUser failed (The user name or password is incorrect, Win32ErrorCode 1326)" not in become_invalid_pass.msg'
     
   - name: test become with SYSTEM account
     win_whoami:
@@ -215,21 +227,21 @@
     become_flags: logon_type=batch invalid_flags=a
     become_method: runas
     register: failed_flags_invalid_key
-    failed_when: failed_flags_invalid_key.msg != "become_flags key 'invalid_flags' is not a valid runas flag, must be 'logon_type' or 'logon_flags'"
+    failed_when: "failed_flags_invalid_key.msg != \"Failed to parse become_flags 'logon_type=batch invalid_flags=a': become_flags key 'invalid_flags' is not a valid runas flag, must be 'logon_type' or 'logon_flags'\""
 
   - name: test failure with invalid logon_type
     vars: *become_vars
     win_whoami:
     become_flags: logon_type=invalid
     register: failed_flags_invalid_type
-    failed_when: "failed_flags_invalid_type.msg != \"become_flags logon_type value 'invalid' is not valid, valid values are: interactive, network, batch, service, unlock, network_cleartext, new_credentials\""
+    failed_when: "failed_flags_invalid_type.msg != \"Failed to parse become_flags 'logon_type=invalid': become_flags logon_type value 'invalid' is not valid, valid values are: interactive, network, batch, service, unlock, network_cleartext, new_credentials\""
 
   - name: test failure with invalid logon_flag
     vars: *become_vars
     win_whoami:
     become_flags: logon_flags=with_profile,invalid
     register: failed_flags_invalid_flag
-    failed_when: "failed_flags_invalid_flag.msg != \"become_flags logon_flags value 'invalid' is not valid, valid values are: with_profile, netcredentials_only\""
+    failed_when: "failed_flags_invalid_flag.msg != \"Failed to parse become_flags 'logon_flags=with_profile,invalid': become_flags logon_flags value 'invalid' is not valid, valid values are: with_profile, netcredentials_only\""
 
   # Server 2008 doesn't work with network and network_cleartext, there isn't really a reason why you would want this anyway
   - name: become different types
@@ -266,6 +278,34 @@
       - become_netcredentials.label.account_name == 'High Mandatory Level'
       - become_netcredentials.label.sid == 'S-1-16-12288'
 
+  - name: become logon_flags bitwise tests when loading the profile
+    # Error code of 2 means no file found == no profile loaded
+    win_shell: |
+      Add-Type -Name "Native" -Namespace "Ansible" -MemberDefinition '[DllImport("Userenv.dll", SetLastError=true)]public static extern bool GetProfileType(out UInt32 pdwFlags);'
+      $profile_type = $null
+      $res = [Ansible.Native]::GetProfileType([ref]$profile_type)
+      if (-not $res) {
+          $last_err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+          if ($last_err -eq 2) {
+              return $false
+          } else {
+              throw [System.ComponentModel.Win32Exception]$last_err
+          }
+      } else {
+          return $true
+      }
+    vars: *admin_become_vars
+    become_flags: logon_flags={{item.flags}}
+    register: become_logon_flags
+    failed_when: become_logon_flags.stdout_lines[0]|bool != item.actual
+    with_items:
+    - flags:
+      actual: False
+    - flags: netcredentials_only
+      actual: False
+    - flags: with_profile,netcredentials_only
+      actual: True
+
   - name: echo some non ascii characters
     win_command: cmd.exe /c echo über den Fußgängerübergang gehen
     vars: *become_vars