From 8e44cd5d101ec7f66c0792c2dee6d97809e90752 Mon Sep 17 00:00:00 2001
From: Jordan Borean <jborean93@gmail.com>
Date: Mon, 23 Oct 2017 09:50:19 +1000
Subject: [PATCH] win_command win_shell: add stdin option (#31619)

---
 lib/ansible/modules/windows/win_command.ps1   | 13 ++++++++++++-
 lib/ansible/modules/windows/win_command.py    | 11 ++++++++++-
 lib/ansible/modules/windows/win_shell.ps1     | 19 +++++++++++++++++--
 lib/ansible/modules/windows/win_shell.py      | 17 +++++++++++++++++
 .../targets/win_command/tasks/main.yml        | 15 +++++++++++++++
 .../targets/win_shell/tasks/main.yml          | 14 ++++++++++++++
 6 files changed, 85 insertions(+), 4 deletions(-)

diff --git a/lib/ansible/modules/windows/win_command.ps1 b/lib/ansible/modules/windows/win_command.ps1
index 4570ffc31f5..da6b4f62b97 100644
--- a/lib/ansible/modules/windows/win_command.ps1
+++ b/lib/ansible/modules/windows/win_command.ps1
@@ -18,6 +18,7 @@ $raw_command_line = Get-AnsibleParam -obj $params -name "_raw_params" -type "str
 $chdir = Get-AnsibleParam -obj $params -name "chdir" -type "path"
 $creates = Get-AnsibleParam -obj $params -name "creates" -type "path"
 $removes = Get-AnsibleParam -obj $params -name "removes" -type "path"
+$stdin = Get-AnsibleParam -obj $params -name "stdin" -type 'str"'
 
 $raw_command_line = $raw_command_line.Trim()
 
@@ -34,9 +35,19 @@ If($removes -and -not $(Test-Path -Path $removes)) {
     Exit-Json @{msg="skipped, since $removes does not exist";cmd=$raw_command_line;changed=$false;skipped=$true;rc=0}
 }
 
+$command_args = @{
+    command = $raw_command_line
+}
+if ($chdir) {
+    $command_args['working_directory'] = $chdir
+}
+if ($stdin) {
+    $command_args['stdin'] = $stdin
+}
+
 $start_datetime = [DateTime]::UtcNow
 try {
-    $command_result = Run-Command -command $raw_command_line -working_directory $chdir
+    $command_result = Run-Command @command_args
 } catch {
     $result.changed = $false
     try {
diff --git a/lib/ansible/modules/windows/win_command.py b/lib/ansible/modules/windows/win_command.py
index 793f5a0c237..8699015d032 100644
--- a/lib/ansible/modules/windows/win_command.py
+++ b/lib/ansible/modules/windows/win_command.py
@@ -50,7 +50,11 @@ options:
       - a path or path filter pattern; when the referenced path B(does not) exist on the target host, the task will be skipped.
   chdir:
     description:
-      - set the specified path as the current working directory before executing a command
+      - set the specified path as the current working directory before executing a command.
+  stdin:
+    description:
+    - Set the stdin of the command directly to the specified value.
+    version_added: '2.5'
 notes:
     - If you want to run a command through a shell (say you are using C(<),
       C(>), C(|), etc), you actually want the M(win_shell) module instead. The
@@ -73,6 +77,11 @@ EXAMPLES = r'''
   args:
     chdir: C:\somedir\
     creates: C:\backup\
+
+- name: Run an executable and send data to the stdin for the executable
+  win_command: powershell.exe -
+  args:
+    stdin: Write-Host test
 '''
 
 RETURN = r'''
diff --git a/lib/ansible/modules/windows/win_shell.ps1 b/lib/ansible/modules/windows/win_shell.ps1
index e25678d2dcb..ee7172c8267 100644
--- a/lib/ansible/modules/windows/win_shell.ps1
+++ b/lib/ansible/modules/windows/win_shell.ps1
@@ -46,6 +46,7 @@ $chdir = Get-AnsibleParam -obj $params -name "chdir" -type "path"
 $executable = Get-AnsibleParam -obj $params -name "executable" -type "path"
 $creates = Get-AnsibleParam -obj $params -name "creates" -type "path"
 $removes = Get-AnsibleParam -obj $params -name "removes" -type "path"
+$stdin = Get-AnsibleParam -obj $params -name "stdin" -type "str"
 
 $raw_command_line = $raw_command_line.Trim()
 
@@ -72,7 +73,11 @@ If(-not $executable -or $executable -eq "powershell") {
     # 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($raw_command_line))
 
-    $exec_args = "-noninteractive -encodedcommand $encoded_command"
+    if ($stdin) {
+        $exec_args = "-encodedcommand $encoded_command"
+    } else {
+        $exec_args = "-noninteractive -encodedcommand $encoded_command"
+    }
 }
 Else {
     # FUTURE: support arg translation from executable (or executable_args?) to process arguments for arbitrary interpreter?
@@ -84,9 +89,19 @@ Else {
 }
 
 $command = "$exec_application $exec_args"
+$run_command_arg = @{
+    command = $command
+}
+if ($chdir) {
+    $run_command_arg['working_directory'] = $chdir
+}
+if ($stdin) {
+    $run_command_arg['stdin'] = $stdin
+}
+
 $start_datetime = [DateTime]::UtcNow
 try {
-    $command_result = Run-Command -command $command -working_directory $chdir
+    $command_result = Run-Command @run_command_arg
 } catch {
     $result.changed = $false
     try {
diff --git a/lib/ansible/modules/windows/win_shell.py b/lib/ansible/modules/windows/win_shell.py
index f42fa754b41..4bf5bf818d5 100644
--- a/lib/ansible/modules/windows/win_shell.py
+++ b/lib/ansible/modules/windows/win_shell.py
@@ -53,6 +53,10 @@ options:
     description:
       - change the shell used to execute the command (eg, C(cmd)). The target shell must accept a C(/c) parameter followed by the raw command line to be
         executed.
+  stdin:
+    description:
+    - Set the stdin of the command directly to the specified value.
+    version_added: '2.5'
 notes:
    -  If you want to run an executable securely and predictably, it may be
       better to use the M(win_command) module instead. Best practices when writing
@@ -88,6 +92,19 @@ EXAMPLES = r'''
   args:
     executable: cmd
   register: homedir_out
+
+- name: run multi-lined shell commands
+  win_shell: |
+    $value = Test-Path -Path C:\temp
+    if ($value) {
+        Remove-Item -Path C:\temp -Force
+    }
+    New-Item -Path C:\temp -ItemType Directory
+
+- name: retrieve the input based on stdin
+  win_shell: '$string = [Console]::In.ReadToEnd(); Write-Output $string.Trim()'
+  args:
+    stdin: Input message
 '''
 
 RETURN = r'''
diff --git a/test/integration/targets/win_command/tasks/main.yml b/test/integration/targets/win_command/tasks/main.yml
index b49d19301d6..aaa8ea3fb94 100644
--- a/test/integration/targets/win_command/tasks/main.yml
+++ b/test/integration/targets/win_command/tasks/main.yml
@@ -178,3 +178,18 @@
   win_file:
     path: C:\ansible testing
     state: absent
+
+- name: run stdin test
+  win_command: powershell.exe -
+  args:
+    stdin: Write-Host "some input"
+  register: cmdout
+
+- name: assert run stdin test
+  assert:
+    that:
+    - cmdout|changed
+    - cmdout.rc == 0
+    - cmdout.stdout_lines|count == 1
+    - cmdout.stdout_lines[0] == "some input"
+    - cmdout.stderr == ""
diff --git a/test/integration/targets/win_shell/tasks/main.yml b/test/integration/targets/win_shell/tasks/main.yml
index 89d011d8bd7..2716b948e3f 100644
--- a/test/integration/targets/win_shell/tasks/main.yml
+++ b/test/integration/targets/win_shell/tasks/main.yml
@@ -188,3 +188,17 @@
     - shellout.stdout is search("doneout")
     - shellout.stderr is search("starterror")
     - shellout.stderr is search("doneerror")
+
+- name: run stdin test
+  win_shell: '$string = [Console]::In.ReadToEnd(); Write-Output $string.Trim()'
+  args:
+    stdin: some input
+  register: shellout
+
+- name: assert run stdin test
+  assert:
+    that:
+    - shellout|changed
+    - shellout.rc == 0
+    - shellout.stderr == ""
+    - shellout.stdout == "some input\r\n"