From 4c917d47fc1a0ed4199e0f82efc7e59c387f09d1 Mon Sep 17 00:00:00 2001
From: Jon Hawkesworth <jhawkesworth@users.noreply.github.com>
Date: Fri, 29 May 2015 02:57:13 +0100
Subject: [PATCH] Fix win_copy problems described here:
 https://github.com/ansible/ansible-modules-core/issues/1404 and update
 documentation.

---
 windows/win_copy.ps1    | 86 +++++++++++++++++++++++++----------------
 windows/win_copy.py     | 49 +++++++++++++++++++++--
 windows/win_file.ps1    | 53 +++++++++++++++----------
 windows/win_file.py     |  4 +-
 windows/win_template.py | 22 ++++++-----
 5 files changed, 144 insertions(+), 70 deletions(-)

diff --git a/windows/win_copy.ps1 b/windows/win_copy.ps1
index 9ffdab85f03..4a83e091c56 100644
--- a/windows/win_copy.ps1
+++ b/windows/win_copy.ps1
@@ -17,68 +17,88 @@
 # WANT_JSON
 # POWERSHELL_COMMON
 
-$params = Parse-Args $args;
+$params = Parse-Args $args
 
-$src= Get-Attr $params "src" $FALSE;
+$src= Get-Attr $params "src" $FALSE
 If ($src -eq $FALSE)
 {
-    Fail-Json (New-Object psobject) "missing required argument: src";
+    Fail-Json (New-Object psobject) "missing required argument: src"
 }
 
-$dest= Get-Attr $params "dest" $FALSE;
+$dest= Get-Attr $params "dest" $FALSE
 If ($dest -eq $FALSE)
 {
-    Fail-Json (New-Object psobject) "missing required argument: dest";
+    Fail-Json (New-Object psobject) "missing required argument: dest"
 }
 
-# seems to be supplied by the calling environment, but
-# probably shouldn't be a test for it existing in the params.
-# TODO investigate.
-$original_basename = Get-Attr $params "original_basename" $FALSE;
+$original_basename = Get-Attr $params "original_basename" $FALSE
 If ($original_basename -eq $FALSE)
 {
-    Fail-Json (New-Object psobject) "missing required argument: original_basename ";
+    Fail-Json (New-Object psobject) "missing required argument: original_basename "
 }
 
 $result = New-Object psobject @{
     changed = $FALSE
-};
+    original_basename = $original_basename
+}
+
+# original_basename gets set if src and dest are dirs
+# but includes subdir if the source folder contains sub folders
+# e.g. you could get subdir/foo.txt 
+
+# detect if doing recursive folder copy and create any non-existent destination sub folder
+$parent = Split-Path -Path $original_basename -Parent
+if ($parent.length -gt 0) 
+{
+    $dest_folder = Join-Path $dest $parent
+    New-Item -Force $dest_folder -Type directory
+}
 
 # if $dest is a dir, append $original_basename so the file gets copied with its intended name.
 if (Test-Path $dest -PathType Container)
 {
-    $dest = Join-Path $dest $original_basename;
+    $dest = Join-Path $dest $original_basename
 }
 
-If (Test-Path $dest)
-{
-    $dest_checksum = Get-FileChecksum ($dest);
-    $src_checksum = Get-FileChecksum ($src);
+$dest_checksum = Get-FileChecksum ($dest)
+$src_checksum = Get-FileChecksum ($src)
 
-    If (! $src_checksum.CompareTo($dest_checksum))
+If ($src_checksum.Equals($dest_checksum))
+{
+    # if both are "3" then both are folders, ok to copy 
+    If ($src_checksum.Equals("3")) 
     {
-        # New-Item -Force creates subdirs for recursive copies
-        New-Item -Force $dest -Type file;
-        Copy-Item -Path $src -Destination $dest -Force;
+       # New-Item -Force creates subdirs for recursive copies
+       New-Item -Force $dest -Type file
+       Copy-Item -Path $src -Destination $dest -Force
+       $result.operation = "folder_copy"
     }
-    $dest_checksum = Get-FileChecksum ($dest);
-    If ( $src_checksum.CompareTo($dest_checksum))
+
+}
+ElseIf (! $src_checksum.Equals($dest_checksum))
+{
+    If ($src_checksum.Equals("3")) 
     {
-        $result.changed = $TRUE;
-    }
-    Else
-    {
-        Fail-Json (New-Object psobject) "Failed to place file";
+       Fail-Json (New-Object psobject) "If src is a folder, dest must also be a folder"
     }
+    # The checksums don't match, there's something to do
+    Copy-Item -Path $src -Destination $dest -Force
+    $result.operation = "file_copy"
+}
+
+# verify before we return that the file has changed
+$dest_checksum = Get-FileChecksum ($dest)
+If ( $src_checksum.Equals($dest_checksum))
+{
+    $result.changed = $TRUE
 }
 Else
 {
-    New-Item -Force $dest -Type file;
-    Copy-Item -Path $src -Destination $dest;
-    $result.changed = $TRUE;
+    Fail-Json (New-Object psobject) "src checksum $src_checksum did not match dest_checksum $dest_checksum  Failed to place file $original_basename in $dest"
 }
+# generate return values
 
-$dest_checksum = Get-FileChecksum($dest);
-$result.checksum = $dest_checksum;
+$info = Get-Item $dest
+$result.size = $info.Length
 
-Exit-Json $result;
+Exit-Json $result
diff --git a/windows/win_copy.py b/windows/win_copy.py
index 16b6859488f..d77f37f64d0 100644
--- a/windows/win_copy.py
+++ b/windows/win_copy.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 
-# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2015, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
 #
 # This file is part of Ansible
 #
@@ -45,16 +45,57 @@ options:
         this must be a directory too. Use \\ for path separators.
     required: true
     default: null
-author: Michael DeHaan
+author: "Jon Hawkesworth (@jhawkesworth)"
 notes:
    - The "win_copy" module recursively copy facility does not scale to lots (>hundreds) of files.
      Instead, you may find it better to create files locally, perhaps using win_template, and 
-     then use win_get_url to put them in the correct location.
+     then use win_get_url to fetch them from your managed hosts into the correct location.
 '''
 
 EXAMPLES = '''
-# Example from Ansible Playbooks
+# Copy a single file
 - win_copy: src=/srv/myfiles/foo.conf dest=c:\\TEMP\\foo.conf
 
+# Copy the contents of files/temp_files dir into c:\temp\.  Includes any sub dirs under files/temp_files
+# Note the use of unix style path in the dest.  
+# This is necessary because \ is yaml escape sequence
+- win_copy: src=files/temp_files/ dest=c:/temp/
+
+# Copy the files/temp_files dir and any files or sub dirs into c:\temp
+# Copies the folder because there is no trailing / on 'files/temp_files'
+- win_copy: src=files/temp_files dest=c:/temp/
+
+'''
+RETURN = '''
+dest:
+    description: destination file/path
+    returned: changed
+    type: string
+    sample: "c:/temp/"
+src:
+    description: source file used for the copy on the target machine
+    returned: changed
+    type: string
+    sample: "/home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source"
+checksum:
+    description: checksum of the file after running copy
+    returned: success
+    type: string
+    sample: "6e642bb8dd5c2e027bf21dd923337cbb4214f827"
+size:
+    description: size of the target, after execution
+    returned: changed (single files only)
+    type: int
+    sample: 1220
+operation:
+    description: whether a single file copy took place or a folder copy
+    returned: changed (single files only)
+    type: string
+    sample: "file_copy"
+original_basename:
+    description: basename of the copied file
+    returned: changed (single files only)
+    type: string
+    sample: "foo.txt"
 '''
 
diff --git a/windows/win_file.ps1 b/windows/win_file.ps1
index 62ac81fc1ee..0f3c20ec8e3 100644
--- a/windows/win_file.ps1
+++ b/windows/win_file.ps1
@@ -17,19 +17,19 @@
 # WANT_JSON
 # POWERSHELL_COMMON
 
-$params = Parse-Args $args;
+$params = Parse-Args $args
 
 # path
-$path = Get-Attr $params "path" $FALSE;
+$path = Get-Attr $params "path" $FALSE
 If ($path -eq $FALSE)
 {
-    $path = Get-Attr $params "dest" $FALSE;
+    $path = Get-Attr $params "dest" $FALSE
     If ($path -eq $FALSE)
     {
-        $path = Get-Attr $params "name" $FALSE;
+        $path = Get-Attr $params "name" $FALSE
         If ($path -eq $FALSE)
         {
-            Fail-Json (New-Object psobject) "missing required argument: path";
+            Fail-Json (New-Object psobject) "missing required argument: path"
         }
     }
 }
@@ -39,17 +39,14 @@ If ($path -eq $FALSE)
 # state - file, directory, touch, absent
 # (originally was: state - file, link, directory, hard, touch, absent)
 
-$state = Get-Attr $params "state" "file";
-
-#$recurse = Get-Attr $params "recurse" "no";
-
-# force - yes, no
-# $force = Get-Attr $params "force" "no";
+$state = Get-Attr $params "state" "unspecified"
+# if state is not supplied, test the $path to see if it looks like 
+# a file or a folder and set state to file or folder
 
 # result
 $result = New-Object psobject @{
     changed = $FALSE
-};
+}
 
 If ( $state -eq "touch" )
 {
@@ -61,45 +58,59 @@ If ( $state -eq "touch" )
     {
         echo $null > $file
     }
-    $result.changed = $TRUE;
+    $result.changed = $TRUE
 }
 
 If (Test-Path $path)
 {
-    $fileinfo = Get-Item $path;
+    $fileinfo = Get-Item $path
     If ( $state -eq "absent" )
     {   
-        Remove-Item -Recurse -Force $fileinfo;
-        $result.changed = $TRUE;
+        Remove-Item -Recurse -Force $fileinfo
+        $result.changed = $TRUE
     }
     Else
     {
         # Only files have the .Directory attribute.
         If ( $state -eq "directory" -and $fileinfo.Directory )
         {
-            Fail-Json (New-Object psobject) "path is not a directory";
+            Fail-Json (New-Object psobject) "path is not a directory"
         }
 
         # Only files have the .Directory attribute.
         If ( $state -eq "file" -and -not $fileinfo.Directory )
         {
-            Fail-Json (New-Object psobject) "path is not a file";
+            Fail-Json (New-Object psobject) "path is not a file"
         }
 
     }
 }
 Else
+# doesn't yet exist
 {
+    If ( $state -eq "unspecified" )
+    {
+        $basename = Split-Path -Path $path -Leaf
+        If ($basename.length -gt 0) 
+        {
+           $state = "file"
+        }
+        Else
+        {
+           $state = "directory"
+        }
+    }
+
     If ( $state -eq "directory" )
     {
         New-Item -ItemType directory -Path $path
-        $result.changed = $TRUE;
+        $result.changed = $TRUE
     }
 
     If ( $state -eq "file" )
     {
-        Fail-Json (New-Object psobject) "path will not be created";
+        Fail-Json (New-Object psobject) "path will not be created"
     }
 }
 
-Exit-Json $result;
+Exit-Json $result
diff --git a/windows/win_file.py b/windows/win_file.py
index 6a218216617..4953dd9363b 100644
--- a/windows/win_file.py
+++ b/windows/win_file.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 
-# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2015, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
 #
 # This file is part of Ansible
 #
@@ -32,7 +32,7 @@ description:
 notes:
     - See also M(win_copy), M(win_template), M(copy), M(template), M(assemble)
 requirements: [ ]
-author: Michael DeHaan
+author: "Jon Hawkesworth (@jhawkesworth)"
 options:
   path:
     description:
diff --git a/windows/win_template.py b/windows/win_template.py
index d95d1125fcc..7f981c33daf 100644
--- a/windows/win_template.py
+++ b/windows/win_template.py
@@ -31,22 +31,24 @@ options:
       - Location to render the template to on the remote machine.
     required: true
     default: null
-  backup:
-    description:
-      - Create a backup file including the timestamp information so you can get
-        the original file back if you somehow clobbered it incorrectly.
-    required: false
-    choices: [ "yes", "no" ]
-    default: "no"
 notes:
   - "templates are loaded with C(trim_blocks=True)."
+  - By default, windows line endings are not created in the generated file.
+  - In order to ensure windows line endings are in the generated file,
+    add the following header as the first line of your template:
+    "#jinja2: newline_sequence:'\r\n'"
+    and ensure each line of the template ends with \r\n
+  - Beware fetching files from windows machines when creating templates
+    because certain tools, such as Powershell ISE,  and regedit's export facility
+    add a Byte Order Mark as the first character of the file, which can cause tracebacks.  
+  - Use "od -cx" to examine your templates for Byte Order Marks.
 requirements: []
-author: Michael DeHaan
+author: "Jon Hawkesworth (@jhawkesworth)"
 '''
 
 EXAMPLES = '''
-# Example 
-- win_template: src=/mytemplates/foo.j2 dest=C:\\temp\\file.conf 
+# Playbook Example  (win_template can only be run inside a playbook)
+- win_template: src=/mytemplates/file.conf.j2 dest=C:\\temp\\file.conf 
 
 
 '''