1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2018, Jordan Borean <jborean93@gmail.com> 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10DOCUMENTATION = r''' 11--- 12module: psexec 13short_description: Runs commands on a remote Windows host based on the PsExec 14 model 15description: 16- Runs a remote command from a Linux host to a Windows host without WinRM being 17 set up. 18- Can be run on the Ansible controller to bootstrap Windows hosts to get them 19 ready for WinRM. 20options: 21 hostname: 22 description: 23 - The remote Windows host to connect to, can be either an IP address or a 24 hostname. 25 type: str 26 required: yes 27 connection_username: 28 description: 29 - The username to use when connecting to the remote Windows host. 30 - This user must be a member of the C(Administrators) group of the Windows 31 host. 32 - Required if the Kerberos requirements are not installed or the username 33 is a local account to the Windows host. 34 - Can be omitted to use the default Kerberos principal ticket in the 35 local credential cache if the Kerberos library is installed. 36 - If I(process_username) is not specified, then the remote process will run 37 under a Network Logon under this account. 38 type: str 39 connection_password: 40 description: 41 - The password for I(connection_user). 42 - Required if the Kerberos requirements are not installed or the username 43 is a local account to the Windows host. 44 - Can be omitted to use a Kerberos principal ticket for the principal set 45 by I(connection_user) if the Kerberos library is installed and the 46 ticket has already been retrieved with the C(kinit) command before. 47 type: str 48 port: 49 description: 50 - The port that the remote SMB service is listening on. 51 type: int 52 default: 445 53 encrypt: 54 description: 55 - Will use SMB encryption to encrypt the SMB messages sent to and from the 56 host. 57 - This requires the SMB 3 protocol which is only supported from Windows 58 Server 2012 or Windows 8, older versions like Windows 7 or Windows Server 59 2008 (R2) must set this to C(no) and use no encryption. 60 - When setting to C(no), the packets are in plaintext and can be seen by 61 anyone sniffing the network, any process options are included in this. 62 type: bool 63 default: yes 64 connection_timeout: 65 description: 66 - The timeout in seconds to wait when receiving the initial SMB negotiate 67 response from the server. 68 type: int 69 default: 60 70 executable: 71 description: 72 - The executable to run on the Windows host. 73 type: str 74 required: yes 75 arguments: 76 description: 77 - Any arguments as a single string to use when running the executable. 78 type: str 79 working_directory: 80 description: 81 - Changes the working directory set when starting the process. 82 type: str 83 default: C:\Windows\System32 84 asynchronous: 85 description: 86 - Will run the command as a detached process and the module returns 87 immediately after starting the process while the process continues to 88 run in the background. 89 - The I(stdout) and I(stderr) return values will be null when this is set 90 to C(yes). 91 - The I(stdin) option does not work with this type of process. 92 - The I(rc) return value is not set when this is C(yes) 93 type: bool 94 default: no 95 load_profile: 96 description: 97 - Runs the remote command with the user's profile loaded. 98 type: bool 99 default: yes 100 process_username: 101 description: 102 - The user to run the process as. 103 - This can be set to run the process under an Interactive logon of the 104 specified account which bypasses limitations of a Network logon used when 105 this isn't specified. 106 - If omitted then the process is run under the same account as 107 I(connection_username) with a Network logon. 108 - Set to C(System) to run as the builtin SYSTEM account, no password is 109 required with this account. 110 - If I(encrypt) is C(no), the username and password are sent as a simple 111 XOR scrambled byte string that is not encrypted. No special tools are 112 required to get the username and password just knowledge of the protocol. 113 type: str 114 process_password: 115 description: 116 - The password for I(process_username). 117 - Required if I(process_username) is defined and not C(System). 118 type: str 119 integrity_level: 120 description: 121 - The integrity level of the process when I(process_username) is defined 122 and is not equal to C(System). 123 - When C(default), the default integrity level based on the system setup. 124 - When C(elevated), the command will be run with Administrative rights. 125 - When C(limited), the command will be forced to run with 126 non-Administrative rights. 127 type: str 128 choices: 129 - limited 130 - default 131 - elevated 132 default: default 133 interactive: 134 description: 135 - Will run the process as an interactive process that shows a process 136 Window of the Windows session specified by I(interactive_session). 137 - The I(stdout) and I(stderr) return values will be null when this is set 138 to C(yes). 139 - The I(stdin) option does not work with this type of process. 140 type: bool 141 default: no 142 interactive_session: 143 description: 144 - The Windows session ID to use when displaying the interactive process on 145 the remote Windows host. 146 - This is only valid when I(interactive) is C(yes). 147 - The default is C(0) which is the console session of the Windows host. 148 type: int 149 default: 0 150 priority: 151 description: 152 - Set the command's priority on the Windows host. 153 - See U(https://msdn.microsoft.com/en-us/library/windows/desktop/ms683211.aspx) 154 for more details. 155 type: str 156 choices: 157 - above_normal 158 - below_normal 159 - high 160 - idle 161 - normal 162 - realtime 163 default: normal 164 show_ui_on_logon_screen: 165 description: 166 - Shows the process UI on the Winlogon secure desktop when 167 I(process_username) is C(System). 168 type: bool 169 default: no 170 process_timeout: 171 description: 172 - The timeout in seconds that is placed upon the running process. 173 - A value of C(0) means no timeout. 174 type: int 175 default: 0 176 stdin: 177 description: 178 - Data to send on the stdin pipe once the process has started. 179 - This option has no effect when I(interactive) or I(asynchronous) is 180 C(yes). 181 type: str 182requirements: 183- pypsexec 184- smbprotocol[kerberos] for optional Kerberos authentication 185notes: 186- This module requires the Windows host to have SMB configured and enabled, 187 and port 445 opened on the firewall. 188- This module will wait until the process is finished unless I(asynchronous) 189 is C(yes), ensure the process is run as a non-interactive command to avoid 190 infinite hangs waiting for input. 191- The I(connection_username) must be a member of the local Administrator group 192 of the Windows host. For non-domain joined hosts, the 193 C(LocalAccountTokenFilterPolicy) should be set to C(1) to ensure this works, 194 see U(https://support.microsoft.com/en-us/help/951016/description-of-user-account-control-and-remote-restrictions-in-windows). 195- For more information on this module and the various host requirements, see 196 U(https://github.com/jborean93/pypsexec). 197seealso: 198- module: ansible.builtin.raw 199- module: ansible.windows.win_command 200- module: community.windows.win_psexec 201- module: ansible.windows.win_shell 202author: 203- Jordan Borean (@jborean93) 204''' 205 206EXAMPLES = r''' 207- name: Run a cmd.exe command 208 community.windows.psexec: 209 hostname: server 210 connection_username: username 211 connection_password: password 212 executable: cmd.exe 213 arguments: /c echo Hello World 214 215- name: Run a PowerShell command 216 community.windows.psexec: 217 hostname: server.domain.local 218 connection_username: username@DOMAIN.LOCAL 219 connection_password: password 220 executable: powershell.exe 221 arguments: Write-Host Hello World 222 223- name: Send data through stdin 224 community.windows.psexec: 225 hostname: 192.168.1.2 226 connection_username: username 227 connection_password: password 228 executable: powershell.exe 229 arguments: '-' 230 stdin: | 231 Write-Host Hello World 232 Write-Error Error Message 233 exit 0 234 235- name: Run the process as a different user 236 community.windows.psexec: 237 hostname: server 238 connection_user: username 239 connection_password: password 240 executable: whoami.exe 241 arguments: /all 242 process_username: anotheruser 243 process_password: anotherpassword 244 245- name: Run the process asynchronously 246 community.windows.psexec: 247 hostname: server 248 connection_username: username 249 connection_password: password 250 executable: cmd.exe 251 arguments: /c rmdir C:\temp 252 asynchronous: yes 253 254- name: Use Kerberos authentication for the connection (requires smbprotocol[kerberos]) 255 community.windows.psexec: 256 hostname: host.domain.local 257 connection_username: user@DOMAIN.LOCAL 258 executable: C:\some\path\to\executable.exe 259 arguments: /s 260 261- name: Disable encryption to work with WIndows 7/Server 2008 (R2) 262 community.windows.psexec: 263 hostanme: windows-pc 264 connection_username: Administrator 265 connection_password: Password01 266 encrypt: no 267 integrity_level: elevated 268 process_username: Administrator 269 process_password: Password01 270 executable: powershell.exe 271 arguments: (New-Object -ComObject Microsoft.Update.Session).CreateUpdateInstaller().IsBusy 272 273- name: Download and run ConfigureRemotingForAnsible.ps1 to setup WinRM 274 community.windows.psexec: 275 hostname: '{{ hostvars[inventory_hostname]["ansible_host"] | default(inventory_hostname) }}' 276 connection_username: '{{ ansible_user }}' 277 connection_password: '{{ ansible_password }}' 278 encrypt: yes 279 executable: powershell.exe 280 arguments: '-' 281 stdin: | 282 $ErrorActionPreference = "Stop" 283 $sec_protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault 284 $sec_protocols = $sec_protocols -bor [Net.SecurityProtocolType]::Tls12 285 [Net.ServicePointManager]::SecurityProtocol = $sec_protocols 286 $url = "https://github.com/ansible/ansible/raw/devel/examples/scripts/ConfigureRemotingForAnsible.ps1" 287 Invoke-Expression ((New-Object Net.WebClient).DownloadString($url)) 288 exit 289 delegate_to: localhost 290''' 291 292RETURN = r''' 293msg: 294 description: Any exception details when trying to run the process 295 returned: module failed 296 type: str 297 sample: 'Received exception from remote PAExec service: Failed to start "invalid.exe". The system cannot find the file specified. [Err=0x2, 2]' 298stdout: 299 description: The stdout from the remote process 300 returned: success and interactive or asynchronous is 'no' 301 type: str 302 sample: Hello World 303stderr: 304 description: The stderr from the remote process 305 returned: success and interactive or asynchronous is 'no' 306 type: str 307 sample: Error [10] running process 308pid: 309 description: The process ID of the asynchronous process that was created 310 returned: success and asynchronous is 'yes' 311 type: int 312 sample: 719 313rc: 314 description: The return code of the remote process 315 returned: success and asynchronous is 'no' 316 type: int 317 sample: 0 318''' 319 320import traceback 321 322from ansible.module_utils.basic import AnsibleModule, missing_required_lib 323from ansible.module_utils._text import to_bytes, to_text 324 325PYPSEXEC_IMP_ERR = None 326try: 327 from pypsexec import client 328 from pypsexec.exceptions import PypsexecException, PAExecException, \ 329 PDUException, SCMRException 330 from pypsexec.paexec import ProcessPriority 331 from smbprotocol.exceptions import SMBException, SMBAuthenticationError, \ 332 SMBResponseException 333 import socket 334 HAS_PYPSEXEC = True 335except ImportError: 336 PYPSEXEC_IMP_ERR = traceback.format_exc() 337 HAS_PYPSEXEC = False 338 339KERBEROS_IMP_ERR = None 340try: 341 import gssapi 342 # GSSAPI extension required for Kerberos Auth in SMB 343 from gssapi.raw import inquire_sec_context_by_oid 344 HAS_KERBEROS = True 345except ImportError: 346 KERBEROS_IMP_ERR = traceback.format_exc() 347 HAS_KERBEROS = False 348 349 350def remove_artifacts(module, client): 351 try: 352 client.remove_service() 353 except (SMBException, PypsexecException) as exc: 354 module.warn("Failed to cleanup PAExec service and executable: %s" 355 % to_text(exc)) 356 357 358def main(): 359 module_args = dict( 360 hostname=dict(type='str', required=True), 361 connection_username=dict(type='str'), 362 connection_password=dict(type='str', no_log=True), 363 port=dict(type='int', required=False, default=445), 364 encrypt=dict(type='bool', default=True), 365 connection_timeout=dict(type='int', default=60), 366 executable=dict(type='str', required=True), 367 arguments=dict(type='str'), 368 working_directory=dict(type='str', default=r'C:\Windows\System32'), 369 asynchronous=dict(type='bool', default=False), 370 load_profile=dict(type='bool', default=True), 371 process_username=dict(type='str'), 372 process_password=dict(type='str', no_log=True), 373 integrity_level=dict(type='str', default='default', 374 choices=['default', 'elevated', 'limited']), 375 interactive=dict(type='bool', default=False), 376 interactive_session=dict(type='int', default=0), 377 priority=dict(type='str', default='normal', 378 choices=['above_normal', 'below_normal', 'high', 379 'idle', 'normal', 'realtime']), 380 show_ui_on_logon_screen=dict(type='bool', default=False), 381 process_timeout=dict(type='int', default=0), 382 stdin=dict(type='str') 383 ) 384 result = dict( 385 changed=False, 386 ) 387 module = AnsibleModule( 388 argument_spec=module_args, 389 supports_check_mode=False, 390 ) 391 392 process_username = module.params['process_username'] 393 process_password = module.params['process_password'] 394 use_system = False 395 if process_username is not None and process_username.lower() == "system": 396 use_system = True 397 process_username = None 398 process_password = None 399 400 if process_username is not None and process_password is None: 401 module.fail_json(msg='parameters are required together when not ' 402 'running as System: process_username, ' 403 'process_password') 404 if not HAS_PYPSEXEC: 405 module.fail_json(msg=missing_required_lib("pypsexec"), 406 exception=PYPSEXEC_IMP_ERR) 407 408 hostname = module.params['hostname'] 409 connection_username = module.params['connection_username'] 410 connection_password = module.params['connection_password'] 411 port = module.params['port'] 412 encrypt = module.params['encrypt'] 413 connection_timeout = module.params['connection_timeout'] 414 executable = module.params['executable'] 415 arguments = module.params['arguments'] 416 working_directory = module.params['working_directory'] 417 asynchronous = module.params['asynchronous'] 418 load_profile = module.params['load_profile'] 419 elevated = module.params['integrity_level'] == "elevated" 420 limited = module.params['integrity_level'] == "limited" 421 interactive = module.params['interactive'] 422 interactive_session = module.params['interactive_session'] 423 424 priority = { 425 "above_normal": ProcessPriority.ABOVE_NORMAL_PRIORITY_CLASS, 426 "below_normal": ProcessPriority.BELOW_NORMAL_PRIORITY_CLASS, 427 "high": ProcessPriority.HIGH_PRIORITY_CLASS, 428 "idle": ProcessPriority.IDLE_PRIORITY_CLASS, 429 "normal": ProcessPriority.NORMAL_PRIORITY_CLASS, 430 "realtime": ProcessPriority.REALTIME_PRIORITY_CLASS 431 }[module.params['priority']] 432 show_ui_on_logon_screen = module.params['show_ui_on_logon_screen'] 433 434 process_timeout = module.params['process_timeout'] 435 stdin = module.params['stdin'] 436 437 if (connection_username is None or connection_password is None) and \ 438 not HAS_KERBEROS: 439 module.fail_json(msg=missing_required_lib("gssapi"), 440 execption=KERBEROS_IMP_ERR) 441 442 win_client = client.Client(server=hostname, username=connection_username, 443 password=connection_password, port=port, 444 encrypt=encrypt) 445 446 try: 447 win_client.connect(timeout=connection_timeout) 448 except SMBAuthenticationError as exc: 449 module.fail_json(msg='Failed to authenticate over SMB: %s' 450 % to_text(exc)) 451 except SMBResponseException as exc: 452 module.fail_json(msg='Received unexpected SMB response when opening ' 453 'the connection: %s' % to_text(exc)) 454 except PDUException as exc: 455 module.fail_json(msg='Received an exception with RPC PDU message: %s' 456 % to_text(exc)) 457 except SCMRException as exc: 458 module.fail_json(msg='Received an exception when dealing with SCMR on ' 459 'the Windows host: %s' % to_text(exc)) 460 except (SMBException, PypsexecException) as exc: 461 module.fail_json(msg=to_text(exc)) 462 except socket.error as exc: 463 module.fail_json(msg=to_text(exc)) 464 465 # create PAExec service and run the process 466 result['changed'] = True 467 b_stdin = to_bytes(stdin, encoding='utf-8') if stdin else None 468 run_args = dict( 469 executable=executable, arguments=arguments, asynchronous=asynchronous, 470 load_profile=load_profile, interactive=interactive, 471 interactive_session=interactive_session, 472 run_elevated=elevated, run_limited=limited, 473 username=process_username, password=process_password, 474 use_system_account=use_system, working_dir=working_directory, 475 priority=priority, show_ui_on_win_logon=show_ui_on_logon_screen, 476 timeout_seconds=process_timeout, stdin=b_stdin 477 ) 478 try: 479 win_client.create_service() 480 except (SMBException, PypsexecException) as exc: 481 module.fail_json(msg='Failed to create PAExec service: %s' 482 % to_text(exc)) 483 484 try: 485 proc_result = win_client.run_executable(**run_args) 486 except (SMBException, PypsexecException) as exc: 487 module.fail_json(msg='Received error when running remote process: %s' 488 % to_text(exc)) 489 finally: 490 remove_artifacts(module, win_client) 491 492 if asynchronous: 493 result['pid'] = proc_result[2] 494 elif interactive: 495 result['rc'] = proc_result[2] 496 else: 497 result['stdout'] = proc_result[0] 498 result['stderr'] = proc_result[1] 499 result['rc'] = proc_result[2] 500 501 # close the SMB connection 502 try: 503 win_client.disconnect() 504 except (SMBException, PypsexecException) as exc: 505 module.warn("Failed to close the SMB connection: %s" % to_text(exc)) 506 507 module.exit_json(**result) 508 509 510if __name__ == '__main__': 511 main() 512