summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Davis <mdavis@ansible.com>2017-03-29 16:10:21 -0700
committerMatt Davis <mdavis@ansible.com>2017-07-03 17:43:50 -0700
commitf9f83c58264b357483d1138242d2d3060f4f0124 (patch)
treeb4d73f48956e89c4ef55b8d61c1dbbbc4efc5405
parente9473218317cf2b39c8660a985cbdd013920c527 (diff)
downloadansible-f9f83c58264b357483d1138242d2d3060f4f0124.tar.gz
converted become runas to explicit CreateProcessWithLogonW
* fixes become_method: runas for unprivileged users * sets permissions on tempdir appropriately * allows automatic system environment generation for new token (old Process.Start way prevents this) * add basic become runas tests (cherry picked from commit 6d99a0a93469448a2fd23169e31036263102e7e1)
-rw-r--r--lib/ansible/plugins/shell/powershell.py366
-rw-r--r--test/integration/targets/win_become/aliases1
-rw-r--r--test/integration/targets/win_become/tasks/main.yml87
3 files changed, 407 insertions, 47 deletions
diff --git a/lib/ansible/plugins/shell/powershell.py b/lib/ansible/plugins/shell/powershell.py
index 5ad6b86a4d..24119c9ad6 100644
--- a/lib/ansible/plugins/shell/powershell.py
+++ b/lib/ansible/plugins/shell/powershell.py
@@ -156,6 +156,7 @@ $helper_def = @"
using System;
using System.Diagnostics;
using System.IO;
+using System.Text;
using System.Threading;
using System.Security;
using System.Security.AccessControl;
@@ -164,7 +165,7 @@ using System.Runtime.InteropServices;
namespace Ansible.Shell
{
- public class ProcessUtil
+ public class NativeProcessUtil
{
public static void GetProcessOutput(StreamReader stdoutStream, StreamReader stderrStream, out string stdout, out string stderr)
{
@@ -201,6 +202,26 @@ namespace Ansible.Shell
GrantAccess(username, GetThreadDesktop(GetCurrentThreadId()), DesktopRightsAllAccess);
}
+ public static string SearchPath(string findThis)
+ {
+ StringBuilder sbOut = new StringBuilder(1024);
+ IntPtr filePartOut;
+
+ if(SearchPath(null, findThis, null, sbOut.Capacity, sbOut, out filePartOut) == 0)
+ throw new FileNotFoundException("Couldn't locate " + findThis + " on path");
+
+ return sbOut.ToString();
+ }
+
+ public static uint GetProcessExitCode(IntPtr processHandle) {
+ new NativeWaitHandle(processHandle).WaitOne();
+ uint exitCode;
+ if(!GetExitCodeProcess(processHandle, out exitCode)) {
+ throw new Exception("Error getting process exit code: " + Marshal.GetLastWin32Error());
+ }
+ return exitCode;
+ }
+
private static void GrantAccess(string username, IntPtr handle, int accessMask)
{
SafeHandle safeHandle = new NoopSafeHandle(handle);
@@ -212,6 +233,74 @@ namespace Ansible.Shell
security.Persist(safeHandle, AccessControlSections.Access);
}
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
+ public static extern uint SearchPath (
+ string lpPath,
+ string lpFileName,
+ string lpExtension,
+ int nBufferLength,
+ [MarshalAs (UnmanagedType.LPTStr)]
+ StringBuilder lpBuffer,
+ out IntPtr lpFilePart);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ private static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
+
+ [DllImport("kernel32.dll")]
+ public static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ public static extern IntPtr GetStdHandle(StandardHandleValues nStdHandle);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ public static extern bool SetHandleInformation(IntPtr hObject, HandleFlags dwMask, int dwFlags);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ public static extern bool CloseHandle(IntPtr hObject);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ public static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref int lpSize);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ public static extern bool UpdateProcThreadAttribute(
+ IntPtr lpAttributeList,
+ uint dwFlags,
+ IntPtr Attribute,
+ IntPtr lpValue,
+ IntPtr cbSize,
+ IntPtr lpPreviousValue,
+ IntPtr lpReturnSize);
+
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, BestFitMapping=false)]
+ public static extern bool CreateProcess(
+ [MarshalAs(UnmanagedType.LPTStr)]
+ string lpApplicationName,
+ StringBuilder lpCommandLine,
+ IntPtr lpProcessAttributes,
+ IntPtr lpThreadAttributes,
+ bool bInheritHandles,
+ uint dwCreationFlags,
+ IntPtr lpEnvironment,
+ [MarshalAs(UnmanagedType.LPTStr)]
+ string lpCurrentDirectory,
+ STARTUPINFO lpStartupInfo,
+ out PROCESS_INFORMATION lpProcessInformation);
+
+
+ [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
+ public static extern bool CreateProcessWithLogonW(
+ string userName,
+ string domain,
+ string password,
+ LOGON_FLAGS logonFlags,
+ string applicationName,
+ string commandLine,
+ uint creationFlags,
+ IntPtr environment,
+ string currentDirectory,
+ STARTUPINFOEX startupInfo,
+ out PROCESS_INFORMATION processInformation);
+
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetProcessWindowStation();
@@ -256,7 +345,117 @@ namespace Ansible.Shell
protected override bool ReleaseHandle() { return true; }
}
+
+ }
+
+ class NativeWaitHandle : WaitHandle {
+ public NativeWaitHandle(IntPtr handle) {
+ this.Handle = handle;
+ }
+ }
+
+ [Flags]
+ public enum LOGON_FLAGS
+ {
+ LOGON_WITH_PROFILE = 0x00000001,
+ LOGON_NETCREDENTIALS_ONLY = 0x00000002
+ }
+
+ [Flags]
+ public enum CREATION_FLAGS
+ {
+ CREATE_SUSPENDED = 0x00000004,
+ CREATE_NEW_CONSOLE = 0x00000010,
+ CREATE_UNICODE_ENVIRONMENT = 0x000000400,
+ CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
+ EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
+ }
+
+
+
+ [Flags]
+ public enum StartupInfoFlags : uint
+ {
+ USESTDHANDLES = 0x00000100
+ }
+
+ public enum StandardHandleValues : int
+ {
+ STD_INPUT_HANDLE = -10,
+ STD_OUTPUT_HANDLE = -11,
+ STD_ERROR_HANDLE = -12
+ }
+
+ [Flags]
+ public enum HandleFlags : uint
+ {
+ None = 0,
+ INHERIT = 1
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ public class SECURITY_ATTRIBUTES
+ {
+ public int nLength;
+ public IntPtr lpSecurityDescriptor;
+ public bool bInheritHandle = false;
+
+ public SECURITY_ATTRIBUTES() {
+ nLength = Marshal.SizeOf(this);
+ }
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ public class STARTUPINFO
+ {
+ public Int32 cb;
+ public IntPtr lpReserved;
+ public IntPtr lpDesktop;
+ public IntPtr lpTitle;
+ public Int32 dwX;
+ public Int32 dwY;
+ public Int32 dwXSize;
+ public Int32 dwYSize;
+ public Int32 dwXCountChars;
+ public Int32 dwYCountChars;
+ public Int32 dwFillAttribute;
+ public Int32 dwFlags;
+ public Int16 wShowWindow;
+ public Int16 cbReserved2;
+ public IntPtr lpReserved2;
+ public IntPtr hStdInput;
+ public IntPtr hStdOutput;
+ public IntPtr hStdError;
+
+ public STARTUPINFO() {
+ cb = Marshal.SizeOf(this);
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public class STARTUPINFOEX {
+ public STARTUPINFO startupInfo;
+ public IntPtr lpAttributeList;
+
+ public STARTUPINFOEX() {
+ startupInfo = new STARTUPINFO();
+ startupInfo.cb = Marshal.SizeOf(this);
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct PROCESS_INFORMATION
+ {
+ public IntPtr hProcess;
+ public IntPtr hThread;
+ public int dwProcessId;
+ public int dwThreadId;
}
+
+
+
}
"@
@@ -297,7 +496,6 @@ $actions = $payload.actions
$entrypoint = $payload.($actions[0])
$payload.actions = $payload.actions[1..99]
-
$entrypoint = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($entrypoint))
# load the current action entrypoint as a module custom object with a Run method
@@ -333,7 +531,8 @@ Function Run($payload) {
$username = $payload.become_user
$password = $payload.become_password
- Add-Type -TypeDefinition $helper_def -Debug:$false
+ # FUTURE: convert to SafeHandle so we can stop ignoring warnings?
+ Add-Type -TypeDefinition $helper_def -Debug:$false -IgnoreWarnings
$exec_args = $null
@@ -342,27 +541,105 @@ Function Run($payload) {
# NB: CreateProcessWithLogonW commandline maxes out at 1024 chars, must bootstrap via filesystem
$temp = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName() + ".ps1")
$exec_wrapper.ToString() | Set-Content -Path $temp
+ # allow (potentially unprivileged) target user access to the tempfile (NB: this likely won't work if traverse checking is enabled)
+ $acl = Get-Acl $temp
+ $acl.AddAccessRule($(New-Object System.Security.AccessControl.FileSystemAccessRule($username, "FullControl", "Allow")))
+ Set-Acl $temp $acl | Out-Null
# TODO: grant target user permissions on tempfile/tempdir
Try {
+ $exec_args = @("-noninteractive", $temp)
- # Base64 encode the command so we don't have to worry about the various levels of escaping
- # $encoded_command = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($exec_wrapper.ToString()))
+ # FUTURE: move these flags into C# enum?
+ # start process suspended + breakaway so we can record the watchdog pid without worrying about a completion race
+ Set-Variable CREATE_BREAKAWAY_FROM_JOB -Value ([uint32]0x01000000) -Option Constant
+ Set-Variable CREATE_SUSPENDED -Value ([uint32]0x00000004) -Option Constant
+ Set-Variable CREATE_UNICODE_ENVIRONMENT -Value ([uint32]0x000000400) -Option Constant
+ Set-Variable CREATE_NEW_CONSOLE -Value ([uint32]0x00000010) -Option Constant
+ Set-Variable EXTENDED_STARTUPINFO_PRESENT -Value ([uint32]0x00080000) -Option Constant
- # force the input encoding to preamble-free UTF8 before we create the new process
- [System.Console]::InputEncoding = $(New-Object System.Text.UTF8Encoding @($false))
+ $pstartup_flags = $CREATE_BREAKAWAY_FROM_JOB -bor $CREATE_UNICODE_ENVIRONMENT -bor $CREATE_NEW_CONSOLE # -bor $EXTENDED_STARTUPINFO_PRESENT
- $exec_args = @("-noninteractive", $temp)
+ $si = New-Object Ansible.Shell.STARTUPINFOEX
+
+ $pipesec = New-Object Ansible.Shell.SECURITY_ATTRIBUTES
+ $pipesec.bInheritHandle = $true
+ $stdout_read = $stdout_write = $stderr_read = $stderr_write = 0
+
+ If(-not [Ansible.Shell.NativeProcessUtil]::CreatePipe([ref]$stdout_read, [ref]$stdout_write, $pipesec, 0)) {
+ throw "Stdout pipe setup failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
+ }
+ If(-not [Ansible.Shell.NativeProcessUtil]::SetHandleInformation($stdout_read, [Ansible.Shell.HandleFlags]::INHERIT, 0)) {
+ throw "Stdout handle setup failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
+ }
+
+ If(-not [Ansible.Shell.NativeProcessUtil]::CreatePipe([ref]$stderr_read, [ref]$stderr_write, $pipesec, 0)) {
+ throw "Stderr pipe setup failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
+ }
+ If(-not [Ansible.Shell.NativeProcessUtil]::SetHandleInformation($stderr_read, [Ansible.Shell.HandleFlags]::INHERIT, 0)) {
+ throw "Stderr handle setup failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
+ }
+
+ # setup stdin redirection, we'll leave stdout/stderr as normal
+ $si.startupInfo.dwFlags = [Ansible.Shell.StartupInfoFlags]::USESTDHANDLES
+ $si.startupInfo.hStdOutput = $stdout_write #[Ansible.Shell.NativeProcessUtil]::GetStdHandle([Ansible.Shell.StandardHandleValues]::STD_OUTPUT_HANDLE)
+ $si.startupInfo.hStdError = $stderr_write #[Ansible.Shell.NativeProcessUtil]::GetStdHandle([Ansible.Shell.StandardHandleValues]::STD_ERROR_HANDLE)
+
+ $stdin_read = $stdin_write = 0
+
+ $pipesec = New-Object Ansible.Shell.SECURITY_ATTRIBUTES
+ $pipesec.bInheritHandle = $true
+
+ If(-not [Ansible.Shell.NativeProcessUtil]::CreatePipe([ref]$stdin_read, [ref]$stdin_write, $pipesec, 0)) {
+ throw "Stdin pipe setup failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
+ }
+ If(-not [Ansible.Shell.NativeProcessUtil]::SetHandleInformation($stdin_write, [Ansible.Shell.HandleFlags]::INHERIT, 0)) {
+ throw "Stdin handle setup failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
+ }
+ $si.startupInfo.hStdInput = $stdin_read
+
+
+ # create an attribute list with our explicit handle inheritance list to pass to CreateProcess
+ [int]$buf_sz = 0
+
+ # determine the buffer size necessary for our attribute list
+ If(-not [Ansible.Shell.NativeProcessUtil]::InitializeProcThreadAttributeList([IntPtr]::Zero, 1, 0, [ref]$buf_sz)) {
+ $last_err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ If($last_err -ne 122) { # ERROR_INSUFFICIENT_BUFFER
+ throw "Attribute list size query failed, Win32Error: $last_err"
+ }
+ }
- $proc = New-Object System.Diagnostics.Process
- $psi = $proc.StartInfo
- $psi.FileName = $exec_application
- $psi.Arguments = $exec_args
- $psi.RedirectStandardInput = $true
- $psi.RedirectStandardOutput = $true
- $psi.RedirectStandardError = $true
- $psi.UseShellExecute = $false
+ $si.lpAttributeList = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buf_sz)
+
+ # initialize the attribute list
+ If(-not [Ansible.Shell.NativeProcessUtil]::InitializeProcThreadAttributeList($si.lpAttributeList, 1, 0, [ref]$buf_sz)) {
+ throw "Attribute list init failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
+ }
+
+ $handles_to_inherit = [IntPtr[]]@($stdin_read,$stdout_write,$stderr_write)
+ $pinned_handles = [System.Runtime.InteropServices.GCHandle]::Alloc($handles_to_inherit, [System.Runtime.InteropServices.GCHandleType]::Pinned)
+
+ # update the attribute list with the handles we want to inherit
+ If(-not [Ansible.Shell.NativeProcessUtil]::UpdateProcThreadAttribute($si.lpAttributeList, 0, 0x20002, `
+ $pinned_handles.AddrOfPinnedObject(), [System.Runtime.InteropServices.Marshal]::SizeOf([type][IntPtr]) * $handles_to_inherit.Length, `
+ [System.IntPtr]::Zero, [System.IntPtr]::Zero)) {
+ throw "Attribute list update failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
+ }
+
+ # need to use a preamble-free version of UTF8Encoding
+ $utf8_encoding = New-Object System.Text.UTF8Encoding @($false)
+ $stdin_fs = New-Object System.IO.FileStream @($stdin_write, [System.IO.FileAccess]::Write, $true, 32768)
+ $stdin = New-Object System.IO.StreamWriter @($stdin_fs, $utf8_encoding, 32768)
+
+ $pi = New-Object Ansible.Shell.PROCESS_INFORMATION
+
+ # FUTURE: direct cmdline CreateProcess path lookup fails- this works but is sub-optimal
+ $exec_cmd = [Ansible.Shell.NativeProcessUtil]::SearchPath("powershell.exe")
+ $exec_args = New-Object System.Text.StringBuilder @("-NonInteractive -NoProfile -ExecutionPolicy Bypass $temp")
+
+ [Ansible.Shell.NativeProcessUtil]::GrantAccessToWindowStationAndDesktop($username)
If($username.Contains("\")) {
$sp = $username.Split(@([char]"\"), 2)
@@ -376,54 +653,49 @@ Function Run($payload) {
$domain = "."
}
- $psi.Domain = $domain
- $psi.Username = $username
- $psi.Password = $($password | ConvertTo-SecureString -AsPlainText -Force)
-
- Try {
- [Ansible.Shell.ProcessUtil]::GrantAccessToWindowStationAndDesktop($username)
- }
- Catch {
- $excep = $_
- throw "Error granting windowstation/desktop access to '$username' (is the username valid?): $excep"
+ # TODO: use proper Win32Exception + error
+ If(-not [Ansible.Shell.NativeProcessUtil]::CreateProcessWithLogonW($username, $domain, $password, [Ansible.Shell.LOGON_FLAGS]::LOGON_WITH_PROFILE,
+ $exec_cmd, $exec_args,
+ $pstartup_flags, [IntPtr]::Zero, $env:windir, $si, [ref]$pi)) {
+ #throw New-Object System.ComponentModel.Win32Exception
+ throw "Worker creation failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
}
- Try {
- $proc.Start() | Out-Null # will always return $true for non shell-exec cases
- }
- Catch {
- $excep = $_
- if ($excep.Exception.InnerException -and `
- $excep.Exception.InnerException -is [System.ComponentModel.Win32Exception] -and `
- $excep.Exception.InnerException.NativeErrorCode -eq 5) {
- throw "Become method 'runas' become is not currently supported with the NTLM or Kerberos auth types"
- }
- throw "Error launching under identity '$username': $excep"
- }
+ $stdout_fs = New-Object System.IO.FileStream @($stdout_read, [System.IO.FileAccess]::Read, $true, 4096)
+ $stdout = New-Object System.IO.StreamReader @($stdout_fs, $utf8_encoding, $true, 4096)
+ $stderr_fs = New-Object System.IO.FileStream @($stderr_read, [System.IO.FileAccess]::Read, $true, 4096)
+ $stderr = New-Object System.IO.StreamReader @($stderr_fs, $utf8_encoding, $true, 4096)
+
+ # close local write ends of stdout/stderr pipes so the open handles won't prevent EOF
+ [Ansible.Shell.NativeProcessUtil]::CloseHandle($stdout_write)
+ [Ansible.Shell.NativeProcessUtil]::CloseHandle($stderr_write)
$payload_string = $payload | ConvertTo-Json -Depth 99 -Compress
# push the execution payload over stdin
- $proc.StandardInput.WriteLine($payload_string)
- $proc.StandardInput.Close()
+ $stdin.WriteLine($payload_string)
+ $stdin.Close()
- $stdout = $stderr = [string] $null
+ $str_stdout = $str_stderr = ""
- [Ansible.Shell.ProcessUtil]::GetProcessOutput($proc.StandardOutput, $proc.StandardError, [ref] $stdout, [ref] $stderr) | Out-Null
+ [Ansible.Shell.NativeProcessUtil]::GetProcessOutput($stdout, $stderr, [ref] $str_stdout, [ref] $str_stderr)
- # TODO: decode CLIXML stderr output (and other streams?)
+ # FUTURE: decode CLIXML stderr output (and other streams?)
- $proc.WaitForExit() | Out-Null
+ #$proc.WaitForExit() | Out-Null
- $rc = $proc.ExitCode
+
+ # TODO: wait on process handle for exit, get process exit code
+ $rc = [Ansible.Shell.NativeProcessUtil]::GetProcessExitCode($pi.hProcess)
If ($rc -eq 0) {
- $stdout
- $stderr
+ $str_stdout
+ $str_stderr
}
Else {
Throw "failed, rc was $rc, stderr was $stderr, stdout was $stdout"
}
+
}
Catch {
$excep = $_
diff --git a/test/integration/targets/win_become/aliases b/test/integration/targets/win_become/aliases
new file mode 100644
index 0000000000..10e03fc2bf
--- /dev/null
+++ b/test/integration/targets/win_become/aliases
@@ -0,0 +1 @@
+windows/ci/group1
diff --git a/test/integration/targets/win_become/tasks/main.yml b/test/integration/targets/win_become/tasks/main.yml
new file mode 100644
index 0000000000..24f2eb0fbe
--- /dev/null
+++ b/test/integration/targets/win_become/tasks/main.yml
@@ -0,0 +1,87 @@
+- set_fact:
+ become_test_username: ansible_become_test
+ gen_pw: password123! + {{ lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}
+
+- name: create unprivileged user
+ win_user:
+ name: "{{ become_test_username }}"
+ password: "{{ gen_pw }}"
+ update_password: always
+ groups: Users
+
+- name: execute tests and ensure that test user is deleted regardless of success/failure
+ block:
+ - name: ensure current user is not the become user
+ win_shell: whoami
+ register: whoami_out
+
+ - name: verify output
+ assert:
+ that:
+ - not whoami_out.stdout_lines[0].endswith(become_test_username)
+
+ - name: get become user profile dir so we can clean it up later
+ vars: &become_vars
+ ansible_become_user: "{{ become_test_username }}"
+ ansible_become_password: "{{ gen_pw }}"
+ ansible_become_method: runas
+ ansible_become: yes
+ win_shell: $env:USERPROFILE
+ register: profile_dir_out
+
+ - name: ensure profile dir contains test username (eg, if become fails silently, prevent deletion of real user profile)
+ assert:
+ that:
+ - become_test_username in profile_dir_out.stdout_lines[0]
+
+ - name: test become runas via task vars
+ vars: *become_vars
+ win_shell: whoami
+ register: whoami_out
+
+ - name: verify output
+ assert:
+ that:
+ - whoami_out.stdout_lines[0].endswith(become_test_username)
+
+ - name: test become runas via task keywords
+ vars:
+ ansible_become_password: "{{ gen_pw }}"
+ become: yes
+ become_method: runas
+ become_user: "{{ become_test_username }}"
+ win_shell: whoami
+
+ register: whoami_out
+
+ - name: verify output
+ assert:
+ that:
+ - whoami_out.stdout_lines[0].endswith(become_test_username)
+
+ - name: test become via block vars
+ vars: *become_vars
+ block:
+ - name: ask who the current user is
+ win_shell: whoami
+ register: whoami_out
+
+ - name: verify output
+ assert:
+ that:
+ - whoami_out.stdout_lines[0].endswith(become_test_username)
+
+# FUTURE: test raw + script become behavior once they're running under the exec wrapper again
+# FUTURE: add standalone playbook tests to include password prompting and play become keywords
+
+ always:
+ - name: ensure test user is deleted
+ win_user:
+ name: "{{ become_test_username }}"
+ state: absent
+ - name: ensure test user profile is deleted
+ # NB: have to work around powershell limitation of long filenames until win_file fixes it
+ win_shell: rmdir /S /Q {{ profile_dir_out.stdout_lines[0] }}
+ args:
+ executable: cmd.exe
+ when: become_test_username in profile_dir_out.stdout_lines[0]