diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 55aaab18472..3c58b210d82 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -225,14 +225,28 @@ Function Parse-Args($arguments, $supports_check_mode = $false) # Helper function to calculate a hash of a file in a way which powershell 3 # and above can handle: -Function Get-FileChecksum($path) +Function Get-FileChecksum($path, $algorithm = 'sha1') { If (Test-Path -PathType Leaf $path) { - $sp = new-object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider; - $fp = [System.IO.File]::Open($path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite); - $hash = [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower(); - $fp.Dispose(); + switch ($algorithm) + { + 'md5' { $sp = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider } + 'sha1' { $sp = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider } + 'sha256' { $sp = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider } + 'sha384' { $sp = New-Object -TypeName System.Security.Cryptography.SHA384CryptoServiceProvider } + 'sha512' { $sp = New-Object -TypeName System.Security.Cryptography.SHA512CryptoServiceProvider } + default { Fail-Json (New-Object PSObject) "Unsupported hash algorithm supplied '$algorithm'" } + } + + If ($PSVersionTable.PSVersion.Major -ge 4) { + $raw_hash = Get-FileHash $path -Algorithm $algorithm + $hash = $raw_hash.Hash.ToLower() + } Else { + $fp = [System.IO.File]::Open($path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite); + $hash = [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower(); + $fp.Dispose(); + } } ElseIf (Test-Path -PathType Container $path) { diff --git a/lib/ansible/modules/windows/win_stat.ps1 b/lib/ansible/modules/windows/win_stat.ps1 index 366652249b6..300cf531f25 100644 --- a/lib/ansible/modules/windows/win_stat.ps1 +++ b/lib/ansible/modules/windows/win_stat.ps1 @@ -17,7 +17,51 @@ # WANT_JSON # POWERSHELL_COMMON -$params = Parse-Args $args $true; +$params = Parse-Args -arguments $args -supports_check_mode $true; + +# C# code to determine link target, copied from http://chrisbensen.blogspot.com.au/2010/06/getfinalpathnamebyhandle.html +$symlink_util = @" +using System; +using System.Text; +using Microsoft.Win32.SafeHandles; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace Ansible.Command +{ + public class SymLinkHelper + { + private const int FILE_SHARE_WRITE = 2; + private const int CREATION_DISPOSITION_OPEN_EXISTING = 3; + private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + + [DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int GetFinalPathNameByHandle(IntPtr handle, [In, Out] StringBuilder path, int bufLen, int flags); + + [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, + int dwShareMode, IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); + + public static string GetSymbolicLinkTarget(System.IO.DirectoryInfo symlink) + { + SafeFileHandle directoryHandle = CreateFile(symlink.FullName, 0, 2, System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero); + if(directoryHandle.IsInvalid) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + StringBuilder path = new StringBuilder(512); + int size = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), path, path.Capacity, 0); + + if (size<0) + throw new Win32Exception(Marshal.GetLastWin32Error()); // The remarks section of GetFinalPathNameByHandle mentions the return being prefixed with "\\?\" // More information about "\\?\" here -> http://msdn.microsoft.com/en-us/library/aa365247(v=VS.85).aspx + if (path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\') + return path.ToString().Substring(4); + else + return path.ToString(); + } + } +} +"@ +Add-Type -TypeDefinition $symlink_util function Date_To_Timestamp($start_date, $end_date) { @@ -27,15 +71,26 @@ function Date_To_Timestamp($start_date, $end_date) } } -$path = Get-Attr $params "path" $FALSE; -If ($path -eq $FALSE) -{ - Fail-Json (New-Object psobject) "missing required argument: path"; +function Get-Hash($path, $algorithm) { + # Using PowerShell V4 and above we can use some powershell cmdlets instead of .net + If ($PSVersionTable.PSVersion.Major -ge 4) + { + $hash = (Get-FileHash $path -Algorithm $algorithm).Hash + } + Else + { + $net_algorithm = [Security.Cryptography.HashAlgorithm]::Create($algorithm) + $raw_hash = [System.BitConverter]::ToString($net_algorithm.ComputeHash([System.IO.File]::ReadAllBytes($path))) + $hash = $raw_hash -replace '-' + } + + $hash.ToLower() } -$get_md5 = Get-Attr $params "get_md5" $TRUE | ConvertTo-Bool; -# until we support real aliasing, get the default value from get_md5 -$get_checksum = Get-Attr $params "get_checksum" $get_md5 | ConvertTo-Bool; +$path = Get-AnsibleParam -obj $params -name 'path' -failifempty $true; +$get_md5 = Get-AnsibleParam -obj $params -name 'get_md5' -failifempty $false -default $true | ConvertTo-Bool; +$get_checksum = Get-AnsibleParam -obj $params -name 'get_checksum' -failifempty $false -default $true | ConvertTo-Bool; +$checksum_algorithm = Get-AnsibleParam -obj $params -name 'checksum_algorithm' -failifempty $false -default 'sha1' -ValidateSet 'sha1','sha256','sha384','sha512' $result = New-Object psobject @{ stat = New-Object psobject @@ -46,11 +101,20 @@ If (Test-Path $path) { Set-Attr $result.stat "exists" $TRUE; - $info = Get-Item $path; - $iscontainer = Get-Attr $info "PSIsContainer" $null; - $length = Get-Attr $info "Length" $null; - $extension = Get-Attr $info "Extension" $null; - $attributes = Get-Attr $info "Attributes" ""; + # Need to use -Force so it picks up hidden files + $info = Get-Item -Force $path; + $iscontainer = $info.PSIsContainer; + $filename = $info.Name; + $filepath = $info.FullName; + $attributes = @() + foreach ($attribute in ($info.Attributes -split ',')) { + $attributes += $attribute.Trim(); + } + $attributes_string = $info.Attributes.ToString(); + $isreadonly = $attributes -contains 'ReadOnly'; + $ishidden = $attributes -contains 'Hidden'; + $isarchive = $attributes -contains 'Archive'; + If ($info) { $accesscontrol = $info.GetAccessControl(); @@ -59,26 +123,72 @@ If (Test-Path $path) { $accesscontrol = $null; } - $owner = Get-Attr $accesscontrol "Owner" $null; - $creationtime = Get-Attr $info "CreationTime" $null; - $lastaccesstime = Get-Attr $info "LastAccessTime" $null; - $lastwritetime = Get-Attr $info "LastWriteTime" $null; - + $owner = $accessControl.Owner; + $creationtime = $info.CreationTime; + $lastaccesstime = $info.LastAccessTime; + $lastwritetime = $info.LastAccessTime; $epoch_date = Get-Date -Date "01/01/1970" - If ($iscontainer) + $islink = $false + $isdir = $false + $isshared = $false + + If ($attributes -contains 'ReparsePoint') { - Set-Attr $result.stat "isdir" $TRUE; + # TODO: Find a way to differenciate between soft and junction links + $islink = $true + $isdir = $true + # Try and get the symlink source, can result in failure if link is broken + try { + $lnk_source = [Ansible.Command.SymLinkHelper]::GetSymbolicLinkTarget($path) + Set-Attr $result.stat "lnk_source" $lnk_source + } catch {} + } + ElseIf ($iscontainer) + { + $isdir = $true + + $share_info = Get-WmiObject -Class Win32_Share -Filter "Path='$($info.Fullname -replace '\\', '\\')'"; + If ($share_info -ne $null) + { + $isshared = $true + Set-Attr $result.stat "sharename" $share_info.Name; + } + + $dir_files_sum = Get-ChildItem $info.FullName -Recurse | Measure-Object -property length -sum; + If ($dir_files_sum -eq $null) + { + Set-Attr $result.stat "size" 0; + } + Else{ + Set-Attr $result.stat "size" $dir_files_sum.Sum; + } } Else { - Set-Attr $result.stat "isdir" $FALSE; - Set-Attr $result.stat "size" $length; + Set-Attr $result.stat "size" $info.Length; + Set-Attr $result.stat "extension" $info.extension; + + If ($get_md5) { + $md5 = Get-Hash -path $path -algorithm 'md5' + Set-Attr $result.stat "md5" $md5 + } + + If ($get_checksum) { + $checksum = Get-Hash -path $path -algorithm $checksum_algorithm + Set-Attr $result.stat "checksum" $checksum + } } - Set-Attr $result.stat "extension" $extension; - Set-Attr $result.stat "attributes" $attributes.ToString(); - # Set-Attr $result.stat "owner" $getaccesscontrol.Owner; - # Set-Attr $result.stat "owner" $info.GetAccessControl().Owner; + + Set-Attr $result.stat "islink" $islink; + Set-Attr $result.stat "isdir" $isdir; + Set-Attr $result.stat "isshared" $isshared; + Set-Attr $result.stat "isreadonly" $isreadonly; + Set-Attr $result.stat "ishidden" $ishidden; + Set-Attr $result.stat "isarchive" $isarchive; + Set-Attr $result.stat "filename" $filename; + Set-Attr $result.stat "path" $filepath; + Set-Attr $result.stat "attributes" $attributes_string; Set-Attr $result.stat "owner" $owner; Set-Attr $result.stat "creationtime" (Date_To_Timestamp $epoch_date $creationtime); Set-Attr $result.stat "lastaccesstime" (Date_To_Timestamp $epoch_date $lastaccesstime); @@ -89,12 +199,4 @@ Else Set-Attr $result.stat "exists" $FALSE; } -# only check get_checksum- it either got its value from get_md5 or was set directly. -If (($get_checksum) -and $result.stat.exists -and -not $result.stat.isdir) -{ - $hash = Get-FileChecksum($path); - Set-Attr $result.stat "md5" $hash; - Set-Attr $result.stat "checksum" $hash; -} - Exit-Json $result; diff --git a/lib/ansible/modules/windows/win_stat.py b/lib/ansible/modules/windows/win_stat.py index ce874871d35..73c8e436839 100644 --- a/lib/ansible/modules/windows/win_stat.py +++ b/lib/ansible/modules/windows/win_stat.py @@ -29,27 +29,33 @@ short_description: returns information about a Windows file description: - Returns information about a Windows file options: - path: - description: - - The full path of the file/object to get the facts of; both forward and - back slashes are accepted. - required: true - default: null - aliases: [] - get_md5: - description: - - Whether to return the checksum sum of the file. As of Ansible 1.9 this - is no longer a MD5, but a SHA1 instead. - required: false - default: yes - aliases: [] - get_checksum: - description: - - Whether to return a checksum of the file - (only sha1 currently supported) - required: false - default: yes - version_added: "2.1" + path: + description: + - The full path of the file/object to get the facts of; both forward and + back slashes are accepted. + required: yes + get_md5: + description: + - Whether to return the checksum sum of the file. Between Ansible 1.9 + and 2.2 this is no longer an MD5, but a SHA1 isntead. As of Ansible + 2.3 this is back to an MD5. Will return None if host is unable to + use specified algorithm + required: no + default: True + get_checksum: + description: + - Whether to return a checksum of the file (default sha1) + required: no + default: True + version_added: "2.1" + checksum_algorithm: + description: + - Algorithm to determine checksum of file. Will throw an error if + the host is unable to use specified algorithm. + required: no + default: sha1 + choices: ['sha1', 'sha256', 'sha384', 'sha512'] + version_added: "2.3" author: "Chris Church (@cchurch)" ''' @@ -59,7 +65,140 @@ EXAMPLES = r''' path: C:\foo.ini register: file_info +# Obtain information about a folder +- win_stat: + path: C:\\bar + register: folder_info + +# Get MD5 checksum of a file +- win_stat: + path: C:\\foo.ini + get_md5: True + register: md5_checksum + +- debug: + var: md5_checksum.stat.md5 + +# Get SHA1 checksum of file +- win_stat: + path: C:\\foo.ini + get_checksum: True + register: sha1_checksum + - debug: - var: file_info + var: sha1_checksum.stat.checksum + +# Get SHA256 checksum of file +- win_stat: + path: C:\\foo.ini + get_checksum: True + checksum_algorithm: sha256 + register: sha256_checksum + +- debug: + var: sha256_checksum.stat.checksum ''' +RETURN = ''' +changed: + description: Whether anything was changed + returned: always + type: boolean + sample: True +stat: + description: dictionary containing all the stat data + returned: success + type: dictionary + contains: + attributes: + description: attributes of the file at path in raw form + returned: success, path exists + type: string + sample: "Archive, Hidden" + checksum: + description: The checksum of a file based on checksum_algorithm specified + returned: success, path exist, path is a file, get_checksum == True + checksum_algorithm specified is supported + type: string + sample: 09cb79e8fc7453c84a07f644e441fd81623b7f98 + creationtime: + description: the create time of the file represented in seconds since epoch + returned: success, path exists + type: float + sample: 1477984205.15 + extension: + description: the extension of the file at path + returned: success, path exists, path is a file + type: string + sample: ".ps1" + isarchive: + description: if the path is ready for archiving or not + returned: success, path exists + type: boolean + sample: True + isdir: + description: if the path is a directory or not + returned: success, path exists + type: boolean + sample: True + ishidden: + description: if the path is hidden or not + returned: success, path exists + type: boolean + sample: True + islink: + description: if the path is a symbolic link or junction or not + returned: success, path exists + type: boolean + sample: True + isreadonly: + description: if the path is read only or not + returned: success, path exists + type: boolean + sample: True + isshared: + description: if the path is shared or not + returned: success, path exists + type: boolean + sample: True + lastaccesstime: + description: the last access time of the file represented in seconds since epoch + returned: success, path exists + type: float + sample: 1477984205.15 + lastwritetime: + description: the last modification time of the file represented in seconds since epoch + returned: success, path exists + type: float + sample: 1477984205.15 + lnk_source: + description: the target of the symbolic link, will return null if not a link or the link is broken + return: success, path exists, file is a symbolic link + type: string + sample: C:\\temp + md5: + description: The MD5 checksum of a file (Between Ansible 1.9 and 2.2 this was returned as a SHA1 hash) + returned: success, path exist, path is a file, get_md5 == True, md5 is supported + type: string + sample: 09cb79e8fc7453c84a07f644e441fd81623b7f98 + owner: + description: the owner of the file + returned: success, path exists + type: string + sample: BUILTIN\\Administrators + path: + description: the full absolute path to the file + returned: success, path exists + type: string + sample: BUILTIN\\Administrators + sharename: + description: the name of share if folder is shared + returned: success, path exists, file is a directory and isshared == True + type: string + sample: file-share + size: + description: the size in bytes of a file or folder + returned: success, path exists, file is not a link + type: int + sample: 1024 +''' diff --git a/test/integration/targets/win_stat/defaults/main.yml b/test/integration/targets/win_stat/defaults/main.yml new file mode 100644 index 00000000000..121d9468b58 --- /dev/null +++ b/test/integration/targets/win_stat/defaults/main.yml @@ -0,0 +1 @@ +win_stat_dir: "{{win_output_dir}}\\win_stat" \ No newline at end of file diff --git a/test/integration/targets/win_stat/files/set_attributes.ps1 b/test/integration/targets/win_stat/files/set_attributes.ps1 new file mode 100644 index 00000000000..b57a368c438 --- /dev/null +++ b/test/integration/targets/win_stat/files/set_attributes.ps1 @@ -0,0 +1,9 @@ +$path = $args[0] +$attr = $args[1] +$item = Get-Item "$path" + +$attributes = $item.Attributes -split ',' +If ($attributes -notcontains $attr) { + $attributes += $attr +} +$item.Attributes = $attributes -join ',' diff --git a/test/integration/targets/win_stat/files/set_filedate.ps1 b/test/integration/targets/win_stat/files/set_filedate.ps1 new file mode 100644 index 00000000000..3bc525a4ff0 --- /dev/null +++ b/test/integration/targets/win_stat/files/set_filedate.ps1 @@ -0,0 +1,6 @@ +$date = Get-Date -Year 2016 -Month 11 -Day 1 -Hour 7 -Minute 10 -Second 5 -Millisecond 0 + +$item = Get-Item -Path "$($args[0])" +$item.CreationTime = $date +$item.LastAccessTime = $date +$item.LastWriteTime = $date diff --git a/test/integration/targets/win_stat/files/setup_share.ps1 b/test/integration/targets/win_stat/files/setup_share.ps1 new file mode 100644 index 00000000000..a024c2855f5 --- /dev/null +++ b/test/integration/targets/win_stat/files/setup_share.ps1 @@ -0,0 +1,6 @@ +$share_stat = Get-WmiObject -Class Win32_Share -Filter "name='folder-share'" +If ($share_stat) { + $share_stat.Delete() +} +$wmi = [wmiClass] 'Win32_Share' +$wmi.Create($args[0], 'folder-share', 0) diff --git a/test/integration/targets/win_stat/tasks/main.yml b/test/integration/targets/win_stat/tasks/main.yml index 56553d4d755..d80a01785f9 100644 --- a/test/integration/targets/win_stat/tasks/main.yml +++ b/test/integration/targets/win_stat/tasks/main.yml @@ -16,70 +16,587 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +- name: remove links if they exist as win_file struggles + win_command: cmd.exe /c rmdir "{{item}}" + ignore_errors: True + with_items: + - "{{win_stat_dir}}\\link" + - "{{win_stat_dir}}\\nested\\hard-link.ps1" + - "{{win_stat_dir}}\\junction-link" + +- name: ensure the testing directory is cleared before the run + win_file: + path: "{{win_stat_dir}}" + state: absent + +- name: ensure win testing directories exist + win_file: + path: "{{item}}" + state: directory + with_items: + - "{{win_stat_dir}}\\folder" + - "{{win_stat_dir}}\\folder space" + - "{{win_stat_dir}}\\nested" + - "{{win_stat_dir}}\\nested\\nested" + - "{{win_stat_dir}}\\shared" + - "{{win_stat_dir}}\\hidden" + - "{{win_stat_dir}}\\link-dest" + - "{{win_stat_dir}}\\junction-dest" + +- name: create empty test files + win_file: + path: "{{item}}" + state: touch + with_items: + - "{{win_stat_dir}}\\nested\\file.ps1" + - "{{win_stat_dir}}\\nested\\read-only.ps1" + - "{{win_stat_dir}}\\nested\\archive.ps1" + - "{{win_stat_dir}}\\nested\\hidden.ps1" + - "{{win_stat_dir}}\\nested\\nested\\file.ps1" + - "{{win_stat_dir}}\\folder space\\file.ps1" + +- name: populate files with a test string + win_lineinfile: + dest: "{{item}}" + line: abc + with_items: + - "{{win_stat_dir}}\\nested\\file.ps1" + - "{{win_stat_dir}}\\nested\\read-only.ps1" + - "{{win_stat_dir}}\\nested\\archive.ps1" + - "{{win_stat_dir}}\\nested\\hidden.ps1" + - "{{win_stat_dir}}\\nested\\nested\\file.ps1" + - "{{win_stat_dir}}\\folder space\\file.ps1" + +- name: create share + script: setup_share.ps1 {{win_stat_dir}}\shared + +- name: create links + win_command: cmd.exe /c mklink /{{item.type}} {{item.source}} {{item.target}} + with_items: + - { type: 'D', source: "{{win_stat_dir}}\\link", target: "{{win_stat_dir}}\\link-dest" } + - { type: 'H', source: "{{win_stat_dir}}\\nested\\hard-link.ps1", target: "{{win_stat_dir}}\\nested\\file.ps1" } + - { type: 'J', source: "{{win_stat_dir}}\\junction-link", target: "{{win_stat_dir}}\\junction-dest" } + +- name: set modification date on files/folders + script: set_filedate.ps1 "{{item}}" + with_items: + - "{{win_stat_dir}}\\nested\\file.ps1" + - "{{win_stat_dir}}\\nested\\read-only.ps1" + - "{{win_stat_dir}}\\nested\\archive.ps1" + - "{{win_stat_dir}}\\nested\\hidden.ps1" + - "{{win_stat_dir}}\\nested\\nested\\file.ps1" + - "{{win_stat_dir}}\\folder space\\file.ps1" + - "{{win_stat_dir}}\\folder" + - "{{win_stat_dir}}\\folder space" + - "{{win_stat_dir}}\\nested" + - "{{win_stat_dir}}\\nested\\nested" + - "{{win_stat_dir}}\\shared" + - "{{win_stat_dir}}\\hidden" + - "{{win_stat_dir}}\\link-dest" + - "{{win_stat_dir}}\\junction-dest" + +- name: set file attributes for test + script: set_attributes.ps1 {{item.path}} {{item.attr}} + with_items: + - { path: "{{win_stat_dir}}\\hidden", attr: "Hidden" } + - { path: "{{win_stat_dir}}\\nested\\read-only.ps1", attr: "ReadOnly" } + - { path: "{{win_stat_dir}}\\nested\\archive.ps1", attr: "Archive" } + - { path: "{{win_stat_dir}}\\nested\\hidden.ps1", attr: "Hidden" } +# End setup of test files + - name: test win_stat module on file - win_stat: path="C:/Windows/win.ini" - register: win_stat_file - -- name: check win_stat file result - assert: - that: - - "win_stat_file.stat.exists" - - "not win_stat_file.stat.isdir" - - "win_stat_file.stat.size > 0" - - "win_stat_file.stat.md5" - - "win_stat_file.stat.extension" - - "win_stat_file.stat.attributes" - - "win_stat_file.stat.owner" - - "win_stat_file.stat.creationtime" - - "win_stat_file.stat.lastaccesstime" - - "win_stat_file.stat.lastwritetime" - - "not win_stat_file|failed" - - "not win_stat_file|changed" - -- name: test win_stat module on file without md5 and backslashes - win_stat: path="C:\Windows\win.ini" get_md5=no - register: win_stat_file_no_md5 - -- name: check win_stat file result without md5 - assert: - that: - - "win_stat_file_no_md5.stat.exists" - - "not win_stat_file_no_md5.stat.isdir" - - "win_stat_file_no_md5.stat.size > 0" - - "not win_stat_file_no_md5.stat.md5|default('')" - - "win_stat_file_no_md5.stat.extension" - - "win_stat_file_no_md5.stat.attributes" - - "win_stat_file_no_md5.stat.owner" - - "win_stat_file_no_md5.stat.creationtime" - - "win_stat_file_no_md5.stat.lastaccesstime" - - "win_stat_file_no_md5.stat.lastwritetime" - - "not win_stat_file_no_md5|failed" - - "not win_stat_file_no_md5|changed" - -- name: test win_stat module on directory - win_stat: path="C:\\Windows" - register: win_stat_dir - -- name: check win_stat dir result - assert: - that: - - "win_stat_dir.stat.exists" - - "win_stat_dir.stat.isdir" - - "win_stat_dir.stat.extension == ''" - - "win_stat_dir.stat.attributes" - - "win_stat_dir.stat.owner" - - "win_stat_dir.stat.creationtime" - - "win_stat_dir.stat.lastaccesstime" - - "win_stat_dir.stat.lastwritetime" - - "not win_stat_dir|failed" - - "not win_stat_dir|changed" + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + register: actual + +- name: set expected fact for file + set_fact: + expected: + changed: False + stat: + attributes: Archive + checksum: 5943831380243c1269c8b68bb92de6e2f1844590 + creationtime: 1477984205 + exists: True + extension: .ps1 + filename: file.ps1 + isarchive: True + isdir: False + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + md5: c627105fad747457b7e88ed1016e065f + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + size: 8 + +- name: check actual for file + assert: + that: + - "actual == expected" + +- name: test win_stat module on file without md5 + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + get_md5: False + register: actual + +- name: set expected fact for file without md5 + set_fact: + expected: + changed: False + stat: + attributes: Archive + checksum: 5943831380243c1269c8b68bb92de6e2f1844590 + creationtime: 1477984205 + exists: True + extension: .ps1 + filename: file.ps1 + isarchive: True + isdir: False + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + size: 8 + +- name: check actual for file without md5 + assert: + that: + - "actual == expected" + +- name: test win_stat module on file with sha256 + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + checksum_algorithm: sha256 + register: actual + +- name: set expected fact for file with sha256 + set_fact: + expected: + changed: False + stat: + attributes: Archive + checksum: aec4cd5ed4c05ed5cf9dea5a8b30f5e655ad87b937b36e3c059840ce3cf3642f + creationtime: 1477984205 + exists: True + extension: .ps1 + filename: file.ps1 + isarchive: True + isdir: False + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + md5: c627105fad747457b7e88ed1016e065f + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + size: 8 + +- name: check actual for file with sha256 + assert: + that: + - "actual == expected" + +- name: test win_stat module on file with sha384 + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + checksum_algorithm: sha384 + register: actual + +- name: set expected fact for file with sha384 + set_fact: + expected: + changed: False + stat: + attributes: Archive + checksum: 862076ed4ef117cb61395daa839ccfe20a880e8cd048e76b3b1503d39d72f4c09d58ab3c44cdd5a3272ca8da9d8d8666 + creationtime: 1477984205 + exists: True + extension: .ps1 + filename: file.ps1 + isarchive: True + isdir: False + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + md5: c627105fad747457b7e88ed1016e065f + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + size: 8 + +- name: check actual for file with sha384 + assert: + that: + - "actual == expected" + +- name: test win_stat module on file with sha512 + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + checksum_algorithm: sha512 + register: actual + +- name: set expected fact for file with sha512 + set_fact: + expected: + changed: False + stat: + attributes: Archive + checksum: 6d821d068cd8f09582de9ef14550666547eb6d4ae1d34cfc5aea921e595dbb9c462bf7f4c88a90190a69e52d69ac157e8146b555ed679a3e997061ca69632d0f + creationtime: 1477984205 + exists: True + extension: .ps1 + filename: file.ps1 + isarchive: True + isdir: False + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + md5: c627105fad747457b7e88ed1016e065f + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested\\file.ps1" + size: 8 + +- name: check actual for file with sha512 + assert: + that: + - "actual == expected" + +- name: test win_stat on hidden file + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested\\hidden.ps1" + register: actual + +- name: set expected fact for hidden file + set_fact: + expected: + changed: False + stat: + attributes: "Hidden, Archive" + checksum: 5943831380243c1269c8b68bb92de6e2f1844590 + creationtime: 1477984205 + exists: True + extension: .ps1 + filename: hidden.ps1 + isarchive: True + isdir: False + ishidden: True + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + md5: c627105fad747457b7e88ed1016e065f + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested\\hidden.ps1" + size: 8 + +- name: check actual for hidden file + assert: + that: + - "actual == expected" + +- name: test win_stat on readonly file + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested\\read-only.ps1" + register: actual + +- name: set expected fact for readonly file + set_fact: + expected: + changed: False + stat: + attributes: "ReadOnly, Archive" + checksum: 5943831380243c1269c8b68bb92de6e2f1844590 + creationtime: 1477984205 + exists: True + extension: .ps1 + filename: read-only.ps1 + isarchive: True + isdir: False + ishidden: False + islink: False + isreadonly: True + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + md5: c627105fad747457b7e88ed1016e065f + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested\\read-only.ps1" + size: 8 + +- name: check actual for readonly file + assert: + that: + - "actual == expected" + +- name: test win_stat on hard link file + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested\\hard-link.ps1" + register: actual + +- name: set expected fact for hard link file + set_fact: + expected: + changed: False + stat: + attributes: Archive + checksum: 5943831380243c1269c8b68bb92de6e2f1844590 + creationtime: 1477984205 + exists: True + extension: .ps1 + filename: hard-link.ps1 + isarchive: True + isdir: False + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + md5: c627105fad747457b7e88ed1016e065f + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested\\hard-link.ps1" + size: 8 + +- name: check actual for hard link file + assert: + that: + - "actual == expected" + +- name: test win_stat on directory + win_stat: + path: "{{win_output_dir}}\\win_stat\\nested" + register: actual + +- name: set expected fact for directory + set_fact: + expected: + changed: False + stat: + attributes: Directory + creationtime: 1477984205 + exists: True + filename: nested + isarchive: False + isdir: True + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\nested" + size: 40 + +- name: check actual for directory + assert: + that: + - "actual == expected" + +- name: test win_stat on empty directory + win_stat: + path: "{{win_output_dir}}\\win_stat\\folder" + register: actual + +- name: set expected fact for empty directory + set_fact: + expected: + changed: False + stat: + attributes: Directory + creationtime: 1477984205 + exists: True + filename: folder + isarchive: False + isdir: True + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\folder" + size: 0 + +- name: check actual for empty directory + assert: + that: + - "actual == expected" + +- name: test win_stat on directory with space in name + win_stat: + path: "{{win_output_dir}}\\win_stat\\folder space" + register: actual + +- name: set expected fact for directory with space in name + set_fact: + expected: + changed: False + stat: + attributes: Directory + creationtime: 1477984205 + exists: True + filename: folder space + isarchive: False + isdir: True + ishidden: False + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\folder space" + size: 8 + +- name: check actual for directory with space in name + assert: + that: + - "actual == expected" + +- name: test win_stat on hidden directory + win_stat: + path: "{{win_output_dir}}\\win_stat\\hidden" + register: actual + +- name: set expected fact for hidden directory + set_fact: + expected: + changed: False + stat: + attributes: "Hidden, Directory" + creationtime: 1477984205 + exists: True + filename: hidden + isarchive: False + isdir: True + ishidden: True + islink: False + isreadonly: False + isshared: False + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\hidden" + size: 0 + +- name: check actual for hidden directory + assert: + that: + - "actual == expected" + +- name: test win_stat on shared directory + win_stat: + path: "{{win_output_dir}}\\win_stat\\shared" + register: actual + +- name: set expected fact for shared directory + set_fact: + expected: + changed: False + stat: + attributes: Directory + creationtime: 1477984205 + exists: True + filename: shared + isarchive: False + isdir: True + ishidden: False + islink: False + isreadonly: False + isshared: True + lastaccesstime: 1477984205 + lastwritetime: 1477984205 + owner: BUILTIN\Administrators + path: "{{win_output_dir}}\\win_stat\\shared" + sharename: folder-share + size: 0 + +- name: check actual for shared directory + assert: + that: + - "actual == expected" + +- name: test win_stat on symlink + win_stat: + path: "{{win_output_dir}}\\win_stat\\link" + register: actual + +# I cannot change modification time on links so we are resorting to checking the dict against known valuea +- name: assert symlink actual + assert: + that: + - "actual.stat.attributes == 'Directory, ReparsePoint'" + - "actual.stat.creationtime is defined" + - "actual.stat.exists == True" + - "actual.stat.filename == 'link'" + - "actual.stat.isarchive == False" + - "actual.stat.isdir == True" + - "actual.stat.ishidden == False" + - "actual.stat.islink == True" + - "actual.stat.isreadonly == False" + - "actual.stat.isshared == False" + - "actual.stat.lastaccesstime is defined" + - "actual.stat.lastwritetime is defined" + - "actual.stat.lnk_source == '{{win_output_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\win_stat\\\\link-dest'" + - "actual.stat.owner == 'BUILTIN\\Administrators'" + - "actual.stat.path == '{{win_output_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\win_stat\\\\link'" + - "actual.stat.size is not defined" + - "actual.stat.checksum is not defined" + - "actual.stat.md5 is not defined" + +- name: test win_stat on junction + win_stat: + path: "{{win_output_dir}}\\win_stat\\junction-link" + register: actual + +- name: assert symlink actual + assert: + that: + - "actual.stat.attributes == 'Directory, ReparsePoint'" + - "actual.stat.creationtime is defined" + - "actual.stat.exists == True" + - "actual.stat.filename == 'junction-link'" + - "actual.stat.isarchive == False" + - "actual.stat.isdir == True" + - "actual.stat.ishidden == False" + - "actual.stat.islink == True" + - "actual.stat.isreadonly == False" + - "actual.stat.isshared == False" + - "actual.stat.lastaccesstime is defined" + - "actual.stat.lastwritetime is defined" + - "actual.stat.lnk_source == '{{win_output_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\win_stat\\\\junction-dest'" + - "actual.stat.owner == 'BUILTIN\\Administrators'" + - "actual.stat.path == '{{win_output_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\win_stat\\\\junction-link'" + - "actual.stat.size is not defined" + - "actual.stat.checksum is not defined" + - "actual.stat.md5 is not defined" - name: test win_stat module non-existent path - win_stat: path="C:/this_file_should_not_exist.txt" + win_stat: + path: "{{win_output_dir}}\\win_stat\\this_file_should_not_exist" register: win_stat_missing - name: check win_stat missing result - assert: + assert: that: - "not win_stat_missing.stat.exists" - "not win_stat_missing|failed" @@ -96,3 +613,26 @@ - "win_stat_no_args|failed" - "win_stat_no_args.msg" - "not win_stat_no_args|changed" + +- name: check if junction symbolic link exists + win_stat: + path: "{{win_output_dir}}\\win_stat\\junction-link" + register: junction_link_exists + +- name: delete junction symbolic link if it exists + win_command: cmd.exe /c rmdir {{win_output_dir}}\win_stat\junction-link + when: junction_link_exists.stat.exists + +- name: check if symbolic link exists + win_stat: + path: "{{win_output_dir}}\\win_stat\\link" + register: nested_link_exists + +- name: delete nested symbolic link if it exists + win_shell: cmd.exe /c rmdir {{win_output_dir}}\win_stat\link + when: nested_link_exists.stat.exists + +- name: remove testing folder + win_file: + path: "{{win_output_dir}}\\win_stat" + state: absent