Repository: ansible/ansible

Issue:
Ansible 2.1.0 Windows Script broken
##### ISSUE TYPE
- Bug Report
##### ANSIBLE VERSION

```
ansible 2.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

N/A
##### OS / ENVIRONMENT

Running Ansible from RHEL 7.2 (Maipo)
Managing Windows Server 2012 R2
##### SUMMARY

The Ansible script module is one of the modules that works against Linux and Windows alike.  Copies a script from Ansible to remote managed host and runs it.  After upgrading from Ansible 1.9.x to 2.1.0, the script module no longer works against the same Windows machine it used to work on under 1.9.x.
##### STEPS TO REPRODUCE

Follow Windows instructions for establishing Ansible connectivity to Windows machines (e.g. winrm, inventory and group_vars config, etc.).

Create a simple playbook (e.g. test.yml) as follows:

```
- name: Test playbook
  hosts: [windows]
  tasks:
    - name: PS | run a Powershell script
      script: files/helloworld.ps1
      register: ps_output
      when: ansible_os_family == "Windows"

    - debug: msg="PS output is {{ ps_output }}"
      when: ps_output is defined
```

Ensure there is a PowerShell script as follows:
files/helloworld.ps1

```
Write-Host
Write-Host 'Hello World!'
Write-Host 'Good-bye World!'
```
##### EXPECTED RESULTS

Expected to see PowerShell output reflected on Ansible controller's console when playbook is run.
##### ACTUAL RESULTS

```
<172.16.62.63> ESTABLISH WINRM CONNECTION FOR USER: Jenkins on PORT 5986 TO 172.16.62.63
<172.16.62.63> EXEC Set-StrictMode -Version Latest
(New-Item -Type Directory -Path $env:temp -Name "ansible-tmp-1459970761.86-113151293514038").FullName | Write-Host -Separator '';
<172.16.62.63> PUT "/home/ec2-user/tmp/files/helloworld.ps1" TO "C:\Users\Jenkins\AppData\Local\Temp\ansible-tmp-1459970761.86-113151293514038\helloworld.ps1"
fatal: [172.16.62.63]: FAILED! => {"failed": true, "msg": "Failed to set file mode on remote files (rc: 254, err: )"}
```



Discussion:
I logged into the remote Windows machine and checked the path where the PUT command is placing the script (e.g. C:\Users\Jenkins\AppData\Local\Temp\ansible-tmp-1459970761.86-113151293514038\helloworld.ps1).

I can confirm that this directory exists and that the file (helloworld.ps1) is in fact there.  It appears that the script module is not able to actually execute the file after it has been PUT?  The error message indicates something about setting the file's mode, but I'm not sure what it is trying to set it to (e.g. executable?).

The Windows user that this is all running against has full control over the path in question, so I'm not sure what the problem is here.

@watsonb could you try and change "write-host" to "write-output" in your test file? 

Since you're running thru PywinRM you basically don't have a "host". For most intents and purposes write-output is a better choice since it doesn't dictate the type of host executing powershell, it simply outputs the string to stdout. Please give that a shot and report back.

@trondhindenes Just did, similar result

```
<172.16.62.63> ESTABLISH WINRM CONNECTION FOR USER: Jenkins on PORT 5986 TO 172.16.62.63
<172.16.62.63> WINRM CONNECT: transport=ssl endpoint=https://172.16.62.63:5986/wsman
<172.16.62.63> EXEC Set-StrictMode -Version Latest
(New-Item -Type Directory -Path $env:temp -Name "ansible-tmp-1459972511.34-60743870260149").FullName | Write-Host -Separator '';
<172.16.62.63> WINRM OPEN SHELL: A6E98298-DBAD-4A0B-83B9-25B49435AF1B
<172.16.62.63> WINRM EXEC u'PowerShell' [u'-NoProfile', u'-NonInteractive', u'-ExecutionPolicy', u'Unrestricted', u'-EncodedCommand', u'UwBlAHQALQBTAHQAcgBpAGMAdABNAG8AZABlACAALQBWAGUAcgBzAGkAbwBuACAATABhAHQAZQBzAHQACgAoAE4AZQB3AC0ASQB0AGUAbQAgAC0AVAB5AHAAZQAgAEQAaQByAGUAYwB0AG8AcgB5ACAALQBQAGEAdABoACAAJABlAG4AdgA6AHQAZQBtAHAAIAAtAE4AYQBtAGUAIAAiAGEAbgBzAGkAYgBsAGUALQB0AG0AcAAtADEANAA1ADkAOQA3ADIANQAxADEALgAzADQALQA2ADAANwA0ADMAOAA3ADAAMgA2ADAAMQA0ADkAIgApAC4ARgB1AGwAbABOAGEAbQBlACAAfAAgAFcAcgBpAHQAZQAtAEgAbwBzAHQAIAAtAFMAZQBwAGEAcgBhAHQAbwByACAAJwAnADsA']
<172.16.62.63> WINRM RESULT u'<Response code 0, out "C:\\Users\\Jenkins\\App", err "">'
<172.16.62.63> PUT "/home/ec2-user/tmp/files/helloworld.ps1" TO "C:\Users\Jenkins\AppData\Local\Temp\ansible-tmp-1459972511.34-60743870260149\helloworld.ps1"
<172.16.62.63> WINRM PUT "/home/ec2-user/tmp/files/helloworld.ps1" to "C:\Users\Jenkins\AppData\Local\Temp\ansible-tmp-1459972511.34-60743870260149\helloworld.ps1" (offset=117 size=117)
<172.16.62.63> WINRM CLOSE SHELL: A6E98298-DBAD-4A0B-83B9-25B49435AF1B
fatal: [172.16.62.63]: FAILED! => {"failed": true, "msg": "Failed to set file mode on remote files (rc: 254, err: )"}
```

Again, I logged into the remote Windows machine and confirmed that the file (helloworld.ps1) was delivered successfully and contains the following

```
Write-Output
Write-Output 'Hello World!'
Write-Output "Good-bye World! `n"
```

Is this possibly related to: https://github.com/ansible/ansible-modules-extras/issues/1406

Something with the Set-StrictMode?

I doubt it. There's nothing in your script that should misbehave under strictmode.
However, your empty write-output line might cause a snafu. Could you please remove the first line in your script and see if that clears it up? I kind of doubt it, but worth testing

Same behavior.  It seems to be failing before ever attempting to actually run the script.

okay, thanks for checking. Ping @nitzmahone 

I assume rc == return code which appears to be 254 in this case.  A little Google-fu reveals:

"The specified extended attribute name was invalid"

Not sure what the underlying script module is trying to do when creating the file on the remote machine.

This is due to some recent changes to shared action plugin code for safer unprivileged become (sudo) - obviously not applicable on Windows right now, but a couple of code paths are falling through.


Resolving patch:
diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py
--- a/lib/ansible/plugins/action/__init__.py
+++ b/lib/ansible/plugins/action/__init__.py
@@ -295,6 +295,11 @@ def _fixup_perms(self, remote_path, remote_user, execute=False, recursive=True):
         If the become_user is unprivileged and different from the
         remote_user then we need to make the files we've uploaded readable by them.
         """
+        if self._connection._shell.SHELL_FAMILY == 'powershell':
+            # This won't work on Powershell as-is, so we'll just completely skip until
+            # we have a need for it, at which point we'll have to do something different.
+            return remote_path
+
         if remote_path is None:
             # Sometimes code calls us naively -- it has a var which could
             # contain a path to a tmp dir but doesn't know if it needs to
diff --git a/lib/ansible/plugins/shell/powershell.py b/lib/ansible/plugins/shell/powershell.py
--- a/lib/ansible/plugins/shell/powershell.py
+++ b/lib/ansible/plugins/shell/powershell.py
@@ -67,7 +67,13 @@ def path_has_trailing_slash(self, path):
         return path.endswith('/') or path.endswith('\\')
 
     def chmod(self, mode, path, recursive=True):
-        return ''
+        raise NotImplementedError('chmod is not implemented for Powershell')
+
+    def chown(self, path, user, group=None, recursive=True):
+        raise NotImplementedError('chown is not implemented for Powershell')
+
+    def set_user_facl(self, path, user, mode, recursive=True):
+        raise NotImplementedError('set_user_facl is not implemented for Powershell')
 
     def remove(self, path, recurse=False):
         path = self._escape(self._unquote(path))


Test patch: