From 629efb6eaa93416669b912bf47ecbbd25e32b182 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 24 Apr 2018 12:15:15 +1000 Subject: [PATCH] psexec: new module to run commands on a remote Windows host without WinRM (#36723) * psexec: new module to run commands on a remote Windows host without WinRM * fix up sanity issue, create test firewall rule for SMB traffic * Fixed up yaml linting issues, trying to fix on the fly firewall rule * Added SMB exception to catch when cleaning up PAExec exe * Don't load profile for Azure hosts when becoming another user * Fixed up example to use correct option * Reworded notes section of module docs * Simplified module options around process integrity levels and the system account --- lib/ansible/modules/commands/psexec.py | 494 ++++++++++++++++++ test/integration/targets/psexec/aliases | 2 + .../integration/targets/psexec/tasks/main.yml | 49 ++ .../targets/psexec/tasks/tests.yml | 231 ++++++++ 4 files changed, 776 insertions(+) create mode 100644 lib/ansible/modules/commands/psexec.py create mode 100644 test/integration/targets/psexec/aliases create mode 100644 test/integration/targets/psexec/tasks/main.yml create mode 100644 test/integration/targets/psexec/tasks/tests.yml diff --git a/lib/ansible/modules/commands/psexec.py b/lib/ansible/modules/commands/psexec.py new file mode 100644 index 00000000000..b03b44d584c --- /dev/null +++ b/lib/ansible/modules/commands/psexec.py @@ -0,0 +1,494 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, Jordan Borean +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + + +DOCUMENTATION = r''' +--- +module: psexec +short_description: Runs commands on a remote Windows host based on the PsExec + model +version_added: "2.6" +description: +- Runs a remote command from a Linux host to a Windows host without WinRM being + set up. +- Can be run on the Ansible controller to bootstrap Windows hosts to get them + ready for WinRM. +options: + hostname: + description: + - The remote Windows host to connect to, can be either an IP address or a + hostname. + required: yes + connection_username: + description: + - The username to use when connecting to the remote Windows host. + - This user must be a member of the C(Administrators) group of the Windows + host. + - Required if the Kerberos requirements are not installed or the username + is a local account to the Windows host. + - Can be omitted to use the default Kerberos principal ticket in the + local credential cache if the Kerberos library is installed. + - If I(process_username) is not specified, then the remote process will run + under a Network Logon under this account. + connection_password: + description: + - The password for I(connection_user). + - Required if the Kerberos requirements are not installed or the username + is a local account to the Windows host. + - Can be omitted to use a Kerberos principal ticket for the principal set + by I(connection_user) if the Kerberos library is installed and the + ticket has already been retrieved with the C(kinit) command before. + port: + description: + - The port that the remote SMB service is listening on. + default: 445 + encrypt: + description: + - Will use SMB encryption to encrypt the SMB messages sent to and from the + host. + - This requires the SMB 3 protocol which is only supported from Windows + Server 2012 or Windows 8, older versions like Windows 7 or Windows Server + 2008 (R2) must set this to C(no) and use no encryption. + - When setting to C(no), the packets are in plaintext and can be seen by + anyone sniffing the network, any process options are included in this. + type: bool + default: 'yes' + connection_timeout: + description: + - The timeout in seconds to wait when receiving the initial SMB negotiate + response from the server. + default: 60 + executable: + description: + - The executable to run on the Windows host. + required: yes + arguments: + description: + - Any arguments as a single string to use when running the executable. + working_directory: + description: + - Changes the working directory set when starting the process. + default: C:\Windows\System32 + asynchronous: + description: + - Will run the command as a detached process and the module returns + immediately after starting the processs while the process continues to + run in the background. + - The I(stdout) and I(stderr) return values will be null when this is set + to C(yes). + - The I(stdin) option does not work with this type of process. + - The I(rc) return value is not set when this is C(yes) + type: bool + default: 'no' + load_profile: + description: + - Runs the remote command with the user's profile loaded. + type: bool + default: 'yes' + process_username: + description: + - The user to run the process as. + - This can be set to run the process under an Interactive logon of the + specified account which bypasses limitations of a Network logon used when + this isn't specified. + - If omitted then the process is run under the same account as + I(connection_username) with a Network logon. + - Set to C(System) to run as the builtin SYSTEM account, no password is + required with this account. + - If I(encrypt) is C(no), the username and password are sent as a simple + XOR scrambled byte string that is not encrypted. No special tools are + required to get the username and password just knowledge of the protocol. + process_password: + description: + - The password for I(process_username). + - Required if I(process_username) is defined and not C(System). + integrity_level: + description: + - The integrity level of the process when I(process_username) is defined + and is not equal to C(System). + - When C(default), the default integrity level based on the system setup. + - When C(elevated), the command will be run with Administrative rights. + - When C(limited), the command will be forced to run with + non-Administrative rights. + choices: + - limited + - default + - elevated + default: default + interactive: + description: + - Will run the process as an interactive process that shows a process + Window of the Windows session specified by I(interactive_session). + - The I(stdout) and I(stderr) return values will be null when this is set + to C(yes). + - The I(stdin) option does not work with this type of process. + type: bool + default: 'no' + interactive_session: + description: + - The Windows session ID to use when displaying the interactive process on + the remote Windows host. + - This is only valid when I(interactive) is C(yes). + - The default is C(0) which is the console session of the Windows host. + default: 0 + priority: + description: + - Set the command's priority on the Windows host. + - See U(https://msdn.microsoft.com/en-us/library/windows/desktop/ms683211.aspx) + for more details. + choices: + - above_normal + - below_normal + - high + - idle + - normal + - realtime + default: normal + show_ui_on_logon_screen: + description: + - Shows the process UI on the Winlogon secure desktop when + I(process_username) is C(System). + type: bool + default: 'no' + process_timeout: + description: + - The timeout in seconds that is placed upon the running process. + - A value of C(0) means no timeout. + default: 0 + stdin: + description: + - Data to send on the stdin pipe once the process has started. + - This option has no effect when I(interactive) or I(asynchronous) is + C(yes). +requirements: +- pypsexec +- smbprotocol[kerberos] for optional Kerberos authentication +notes: +- This module requires the Windows host to have SMB configured and enabled, + and port 445 opened on the firewall. +- This module will wait until the process is finished unless I(asynchronous) + is C(yes), ensure the process is run as a non-interactive command to avoid + infinite hangs waiting for input. +- The I(connection_username) must be a member of the local Administrator group + of the Windows host. For non-domain joined hosts, the + C(LocalAccountTokenFilterPolicy) should be set to C(1) to ensure this works, + see U(https://support.microsoft.com/en-us/help/951016/description-of-user-account-control-and-remote-restrictions-in-windows). +- For more information on this module and the various host requirements, see + U(https://github.com/jborean93/pypsexec). +author: +- Jordan Borean (@jborean93) +''' + +EXAMPLES = r''' +- name: run a cmd.exe command + psexec: + hostname: server + connection_username: username + connection_password: password + executable: cmd.exe + arguments: /c echo Hello World + +- name: run a PowerShell command + psexec: + hostname: server.domain.local + connection_username: username@DOMAIN.LOCAL + connection_password: password + executable: powershell.exe + arguments: Write-Host Hello World + +- name: send data through stdin + psexec: + hostname: 192.168.1.2 + connection_username: username + connection_password: password + executable: powershell.exe + arguments: '-' + stdin: | + Write-Host Hello World + Write-Error Error Message + exit 0 + +- name: Run the process as a different user + psexec: + hostname: server + connection_user: username + connection_password: password + executable: whoami.exe + arguments: /all + process_username: anotheruser + process_password: anotherpassword + +- name: Run the process asynchronously + psexec: + hostname: server + connection_username: username + connection_password: password + executable: cmd.exe + arguments: /c rmdir C:\temp + asynchronous: yes + +- name: Use Kerberos authentication for the connection (requires smbprotocol[kerberos]) + psexec: + hostname: host.domain.local + connection_username: user@DOMAIN.LOCAL + executable: C:\some\path\to\executable.exe + arguments: /s + +- name: Disable encryption to work with WIndows 7/Server 2008 (R2) + psexec: + hostanme: windows-pc + connection_username: Administrator + connection_password: Password01 + encrypt: no + integrity_level: elevated + process_username: Administrator + process_password: Password01 + executable: powershell.exe + arguments: (New-Object -ComObject Microsoft.Update.Session).CreateUpdateInstaller().IsBusy + +- name: Download and run ConfigureRemotingForAnsible.ps1 to setup WinRM + psexec: + hostname: windows-pc + connection_username: Administrator + connection_password: Password01 + encrypt: yes + executable: powershell.exe + arguments: '-' + stdin: | + $ErrorActionPreference = "Stop" + $sec_protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault + $sec_protocols = $sec_protocols -bor [Net.SecurityProtocolType]::Tls12 + [Net.ServicePointManager]::SecurityProtocol = $sec_protocols + $url = "https://github.com/ansible/ansible/raw/devel/examples/scripts/ConfigureRemotingForAnsible.ps1" + Invoke-Expression ((New-Object Net.WebClient).DownloadString($url)) + exit +''' + +RETURN = r''' +msg: + description: Any exception details when trying to run the process + returned: module failed + type: str + sample: 'Received exception from remote PAExec service: Failed to start "invalid.exe". The system cannot find the file specified. [Err=0x2, 2]' +stdout: + description: The stdout from the remote process + returned: success and interactive or asynchronous is 'no' + type: str + sample: Hello World +stderr: + description: The stderr from the remote process + returned: success and interactive or asynchronous is 'no' + type: str + sample: Error [10] running process +pid: + description: The process ID of the asynchronous process that was created + returned: success and asynchronous is 'yes' + type: int + sample: 719 +rc: + description: The return code of the remote process + returned: success and asynchronous is 'no' + type: int + sample: 0 +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_bytes, to_text + +PYPSEXEC_IMP_ERR = None +try: + from pypsexec import client + from pypsexec.exceptions import PypsexecException, PAExecException, \ + PDUException, SCMRException + from pypsexec.paexec import ProcessPriority + from smbprotocol.exceptions import SMBException, SMBAuthenticationError, \ + SMBResponseException + HAS_PYPSEXEC = True +except ImportError as exc: + PYPSEXEC_IMP_ERR = exc + HAS_PYPSEXEC = False + +KERBEROS_IMP_ERR = None +try: + import gssapi + # GSSAPI extension required for Kerberos Auth in SMB + from gssapi.raw import inquire_sec_context_by_oid + HAS_KERBEROS = True +except ImportError as exc: + KERBEROS_IMP_ERR = exc + HAS_KERBEROS = False + + +def remove_artifacts(module, client): + try: + client.remove_service() + except (SMBException, PypsexecException) as exc: + module.warn("Failed to cleanup PAExec service and executable: %s" + % to_text(exc)) + + +def main(): + module_args = dict( + hostname=dict(type='str', required=True), + connection_username=dict(type='str'), + connection_password=dict(type='str', no_log=True), + port=dict(type='int', required=False, default=445), + encrypt=dict(type='bool', default=True), + connection_timeout=dict(type='int', default=60), + executable=dict(type='str', required=True), + arguments=dict(type='str'), + working_directory=dict(type='str', default=r'C:\Windows\System32'), + asynchronous=dict(type='bool', default=False), + load_profile=dict(type='bool', default=True), + process_username=dict(type='str'), + process_password=dict(type='str', no_log=True), + integrity_level=dict(type='str', default='default', + choices=['default', 'elevated', 'limited']), + interactive=dict(type='bool', default=False), + interactive_session=dict(type='int', default=0), + priority=dict(type='str', default='normal', + choices=['above_normal', 'below_normal', 'high', + 'idle', 'normal', 'realtime']), + show_ui_on_logon_screen=dict(type='bool', default=False), + process_timeout=dict(type='int', default=0), + stdin=dict(type='str') + ) + result = dict( + changed=False, + ) + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=False, + ) + + process_username = module.params['process_username'] + process_password = module.params['process_password'] + use_system = False + if process_username is not None and process_username.lower() == "system": + use_system = True + process_username = None + process_password = None + + if process_username is not None and process_password is None: + module.fail_json(msg='parameters are required together when not ' + 'running as System: process_username, ' + 'process_password') + if not HAS_PYPSEXEC: + module.fail_json(msg='The pypsexec python module is required', + exception=PYPSEXEC_IMP_ERR) + + hostname = module.params['hostname'] + connection_username = module.params['connection_username'] + connection_password = module.params['connection_password'] + port = module.params['port'] + encrypt = module.params['encrypt'] + connection_timeout = module.params['connection_timeout'] + executable = module.params['executable'] + arguments = module.params['arguments'] + working_directory = module.params['working_directory'] + asynchronous = module.params['asynchronous'] + load_profile = module.params['load_profile'] + elevated = module.params['integrity_level'] == "elevated" + limited = module.params['integrity_level'] == "limited" + interactive = module.params['interactive'] + interactive_session = module.params['interactive_session'] + + priority = { + "above_normal": ProcessPriority.ABOVE_NORMAL_PRIORITY_CLASS, + "below_normal": ProcessPriority.BELOW_NORMAL_PRIORITY_CLASS, + "high": ProcessPriority.HIGH_PRIORITY_CLASS, + "idle": ProcessPriority.IDLE_PRIORITY_CLASS, + "normal": ProcessPriority.NORMAL_PRIORITY_CLASS, + "realtime": ProcessPriority.REALTIME_PRIORITY_CLASS + }[module.params['priority']] + show_ui_on_logon_screen = module.params['show_ui_on_logon_screen'] + + process_timeout = module.params['process_timeout'] + stdin = module.params['stdin'] + + if connection_username is None or connection_password is None and \ + not HAS_KERBEROS: + module.fail_json(msg='The gssapi python module with the GGF extension ' + 'is required for Kerberos authentication', + exception=KERBEROS_IMP_ERR) + + win_client = client.Client(server=hostname, username=connection_username, + password=connection_password, port=port, + encrypt=encrypt) + + try: + win_client.connect(timeout=connection_timeout) + except SMBAuthenticationError as exc: + module.fail_json(msg='Failed to authenticate over SMB: %s' + % to_text(exc)) + except SMBResponseException as exc: + module.fail_json(msg='Received unexpected SMB response when opening ' + 'the connection: %s' % to_text(exc)) + except PDUException as exc: + module.fail_json(msg='Received an exception with RPC PDU message: %s' + % to_text(exc)) + except SCMRException as exc: + module.fail_json(msg='Received an exception when dealing with SCMR on ' + 'the Windows host: %s' % to_text(exc)) + except (SMBException, PypsexecException) as exc: + module.fail_json(msg=to_text(exc)) + + # create PAExec service and run the process + result['changed'] = True + b_stdin = to_bytes(stdin, encoding='utf-8') if stdin else None + run_args = dict( + executable=executable, arguments=arguments, asynchronous=asynchronous, + load_profile=load_profile, interactive_session=interactive_session, + run_elevated=elevated, run_limited=limited, + username=process_username, password=process_password, + use_system_account=use_system, working_dir=working_directory, + priority=priority, show_ui_on_win_logon=show_ui_on_logon_screen, + timeout_seconds=process_timeout, stdin=b_stdin + ) + try: + win_client.create_service() + except (SMBException, PypsexecException) as exc: + module.fail_json(msg='Failed to create PAExec service: %s' + % to_text(exc)) + + try: + proc_result = win_client.run_executable(**run_args) + except (SMBException, PypsexecException) as exc: + module.fail_json(msg='Received error when running remote process: %s' + % to_text(exc)) + finally: + remove_artifacts(module, win_client) + + if asynchronous: + result['pid'] = proc_result[2] + elif interactive: + result['rc'] = proc_result[2] + else: + result['stdout'] = proc_result[0] + result['stderr'] = proc_result[1] + result['rc'] = proc_result[2] + + # close the SMB connection + try: + win_client.disconnect() + except (SMBException, PypsexecException) as exc: + module.warn("Failed to close the SMB connection: %s" % to_text(exc)) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/psexec/aliases b/test/integration/targets/psexec/aliases new file mode 100644 index 00000000000..ab8829535ca --- /dev/null +++ b/test/integration/targets/psexec/aliases @@ -0,0 +1,2 @@ +windows/ci/group1 + diff --git a/test/integration/targets/psexec/tasks/main.yml b/test/integration/targets/psexec/tasks/main.yml new file mode 100644 index 00000000000..c785d87175f --- /dev/null +++ b/test/integration/targets/psexec/tasks/main.yml @@ -0,0 +1,49 @@ +--- +- name: check whether the host supports encryption + win_shell: | + if ([System.Environment]::OSVersion.Version -lt [Version]"6.2") { + "false" + } else { + "true" + } + register: encryption_supported_raw + +- name: install pypsexec Python library for tests + pip: + name: pypsexec + state: latest + delegate_to: localhost + +- name: define psexec variables + set_fact: + psexec_hostname: '{{ansible_host}}' + psexec_username: '{{ansible_user}}' + psexec_password: '{{ansible_password}}' + psexec_encrypt: '{{encryption_supported_raw.stdout_lines[0]|bool}}' + +- name: create test rule to allow SMB traffic inbound + win_firewall_rule: + name: File and Printer Sharing (SMB-In) Test + direction: in + action: allow + localport: 445 + enabled: yes + protocol: tcp + program: System + profiles: + - domain + - private + - public + state: present + +- name: run tests + block: + - include_tasks: tests.yml + + always: + - name: remove test rule that allows SMB traffic inbound + win_firewall_rule: + name: File and Printer Sharing (SMB-In) Test + direction: in + action: allow + state: absent diff --git a/test/integration/targets/psexec/tasks/tests.yml b/test/integration/targets/psexec/tasks/tests.yml new file mode 100644 index 00000000000..e87c79a1e09 --- /dev/null +++ b/test/integration/targets/psexec/tasks/tests.yml @@ -0,0 +1,231 @@ +--- +- name: fail when process_password is not set with process_username + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: hostname.exe + process_username: '{{psexec_username}}' + delegate_to: localhost + register: fail_no_process_pass + failed_when: 'fail_no_process_pass.msg != "parameters are required together when not running as System: process_username, process_password"' + +- name: get current host + win_command: hostname.exe + register: actual_hostname + +- name: run basic psexec command + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: hostname.exe + delegate_to: localhost + register: psexec_hostname_actual + +- name: assert basic psexec command matches expected output + assert: + that: + - psexec_hostname_actual is changed + - psexec_hostname_actual.rc == 0 + - psexec_hostname_actual.stderr == '' + - psexec_hostname_actual.stdout == actual_hostname.stdout + +- name: get output for executable with arguments + win_command: hostname.exe /? + register: actual_hostname_help + failed_when: actual_hostname_help.rc != 1 + +- name: run psexec command with arguments + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: hostname.exe + arguments: /? + delegate_to: localhost + register: psexec_hostname_help + failed_when: psexec_hostname_help.rc != 1 + +- name: assert basic pesexec command with arguments matches expected output + assert: + that: + - psexec_hostname_help is changed + - psexec_hostname_help.rc == 1 + - psexec_hostname_help.stderr == actual_hostname_help.stderr + - psexec_hostname_help.stdout == actual_hostname_help.stdout + +- name: run psexec command and send data through stdin + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: powershell.exe + arguments: '-' + stdin: | + Write-Host hello world + Write-Host this is another message + exit 0 + delegate_to: localhost + register: psexec_stdin + +- name: assert psexec ommand and send data through stdin + assert: + that: + - psexec_stdin is changed + - psexec_stdin.rc == 0 + - psexec_stdin.stderr == '' + - psexec_stdin.stdout == 'hello world\nthis is another message\n' + +- name: run psexec command with specific process username + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + load_profile: no # on Azure, the profile does not exist yet so we don't load it for this task + executable: powershell.exe + arguments: '-' + stdin: | + ((Get-CimInstance Win32_Process -filter "processid = $pid") | Get-CimAssociatedInstance -Association Win32_SessionProcess).LogonType + exit 0 + process_username: '{{psexec_username}}' + process_password: '{{psexec_password}}' + delegate_to: localhost + register: psexec_process_username + +- name: assert psexec command with specific process username + assert: + that: + - psexec_process_username is changed + - psexec_process_username.rc == 0 + - psexec_process_username.stderr == '' + - psexec_process_username.stdout_lines[0] != '3' # 3 is Network Logon Type, we assert we are not a network logon with process credentials + +- name: run psexec command with both stderr and stdout + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: cmd.exe + arguments: /c echo first && echo second 1>&2 && echo third + delegate_to: localhost + register: psexec_process_stderr + +- name: assert psexec command with both stderr and stdout + assert: + that: + - psexec_process_stderr is changed + - psexec_process_stderr.rc == 0 + - psexec_process_stderr.stderr == 'second \r\n' + - psexec_process_stderr.stdout == 'first \r\nthird\r\n' + +- name: run process asynchronously + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: powershell.exe + arguments: Start-Sleep -Seconds 30 + asynchronous: yes + delegate_to: localhost + register: psexec_process_async + +- name: check if process is still running + win_shell: (Get-Process -ID {{psexec_process_async.pid}}).ProcessName + register: psexec_process_async_actual + +- name: assert run process asynchronously + assert: + that: + - psexec_process_async is changed + - psexec_process_async.rc is not defined + - psexec_process_async.pid is defined + - psexec_process_async.stdout is not defined + - psexec_process_async.stderr is not defined + - psexec_process_async_actual.stdout_lines[0] == 'powershell' + +- name: run process interactively + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: powershell.exe + arguments: Write-Host hi + interactive: yes + delegate_to: localhost + register: psexec_process_interactive + +- name: assert run process interactively + assert: + that: + - psexec_process_interactive is changed + - psexec_process_interactive.rc == 0 + - psexec_process_interactive.stdout is not defined + - psexec_process_interactive.stderr is not defined + +- name: run process with timeout + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: powershell.exe + arguments: Start-Sleep -Seconds 30 + process_timeout: 5 + delegate_to: localhost + register: psexec_process_timeout + failed_when: psexec_process_timeout.rc == 0 + +- name: assert psexec process with timeout + assert: + that: + - psexec_process_timeout.rc != 0 + - psexec_process_timeout.stdout == '' + - psexec_process_timeout.stderr == '' + +- name: run process as system + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: whoami.exe + process_username: System + delegate_to: localhost + register: psexec_process_system + +- name: assert run process as system + assert: + that: + - psexec_process_system is changed + - psexec_process_system.rc == 0 + - psexec_process_system.stderr == '' + - psexec_process_system.stdout == 'nt authority\system\r\n' + +- name: run process with different chdir + psexec: + hostname: '{{psexec_hostname}}' + connection_username: '{{psexec_username}}' + connection_password: '{{psexec_password}}' + encrypt: '{{psexec_encrypt}}' + executable: powershell.exe + arguments: (pwd).Path + working_directory: C:\Windows + delegate_to: localhost + register: psexec_process_working_dir + +- name: assert run process with different chdir + assert: + that: + - psexec_process_working_dir is changed + - psexec_process_working_dir.rc == 0 + - psexec_process_working_dir.stderr == '' + - psexec_process_working_dir.stdout == 'C:\Windows\r\n'