From 1ce5fcf0616b525daacfab712fb34d0be8a52329 Mon Sep 17 00:00:00 2001
From: Jordan Borean <jborean93@gmail.com>
Date: Thu, 9 Feb 2017 08:19:08 +1000
Subject: [PATCH] Added win_find module (#19144)

---
 CHANGELOG.md                                  |   1 +
 lib/ansible/modules/windows/win_find.ps1      | 347 ++++++++
 lib/ansible/modules/windows/win_find.py       | 323 ++++++++
 test/integration/targets/win_find/aliases     |   1 +
 .../targets/win_find/defaults/main.yml        |   1 +
 .../targets/win_find/files/set_attributes.ps1 |   9 +
 .../targets/win_find/files/set_filedate.ps1   |   6 +
 .../targets/win_find/files/set_share.ps1      |   7 +
 .../targets/win_find/meta/main.yml            |   2 +
 .../targets/win_find/tasks/main.yml           | 754 ++++++++++++++++++
 test/integration/test_win_group2.yml          |   1 +
 11 files changed, 1452 insertions(+)
 create mode 100644 lib/ansible/modules/windows/win_find.ps1
 create mode 100644 lib/ansible/modules/windows/win_find.py
 create mode 100644 test/integration/targets/win_find/aliases
 create mode 100644 test/integration/targets/win_find/defaults/main.yml
 create mode 100644 test/integration/targets/win_find/files/set_attributes.ps1
 create mode 100644 test/integration/targets/win_find/files/set_filedate.ps1
 create mode 100644 test/integration/targets/win_find/files/set_share.ps1
 create mode 100644 test/integration/targets/win_find/meta/main.yml
 create mode 100644 test/integration/targets/win_find/tasks/main.yml

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 874aed39af6..70f45181373 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -233,6 +233,7 @@ Ansible Changes By Release
 - web_infrastructure
   * jenkins_script
 - windows:
+  * win_find
   * win_path
   * win_psexec
   * win_say
diff --git a/lib/ansible/modules/windows/win_find.ps1 b/lib/ansible/modules/windows/win_find.ps1
new file mode 100644
index 00000000000..2e7bdb40eff
--- /dev/null
+++ b/lib/ansible/modules/windows/win_find.ps1
@@ -0,0 +1,347 @@
+#!powershell
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+# WANT_JSON
+# POWERSHELL_COMMON
+
+$ErrorActionPreference = "Stop"
+
+$params = Parse-Args -arguments $args -supports_check_mode $true
+
+$paths = Get-AnsibleParam -obj $params -name 'paths' -failifempty $true
+
+$age = Get-AnsibleParam -obj $params -name 'age' -failifempty $false -default $null
+$age_stamp = Get-AnsibleParam -obj $params -name 'age_stamp' -failifempty $false -default 'mtime' -ValidateSet 'mtime','ctime','atime'
+$file_type = Get-AnsibleParam -obj $params -name 'file_type' -failifempty $false -default 'file' -ValidateSet 'file','directory'
+$follow = Get-AnsibleParam -obj $params -name 'follow' -type "bool" -failifempty $false -default $false
+$hidden = Get-AnsibleParam -obj $params -name 'hidden' -type "bool" -failifempty $false -default $false
+$patterns = Get-AnsibleParam -obj $params -name 'patterns' -failifempty $false -default $null
+$recurse = Get-AnsibleParam -obj $params -name 'recurse' -type "bool" -failifempty $false -default $false
+$size = Get-AnsibleParam -obj $params -name 'size' -failifempty $false -default $null
+$use_regex = Get-AnsibleParam -obj $params -name 'use_regex' -type "bool" -failifempty $false -default $false
+$get_checksum = Get-AnsibleParam -obj $params -name 'get_checksum' -type "bool" -failifempty $false -default $true
+$checksum_algorithm = Get-AnsibleParam -obj $params -name 'checksum_algorithm' -failifempty $false -default 'sha1' -ValidateSet 'md5', 'sha1', 'sha256', 'sha384', 'sha512'
+
+$result = @{
+    files = @()
+    warnings = @()
+    examined = 0
+    matched = 0
+    changed = $false
+}
+
+# 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 Assert-Age($info) {
+    $valid_match = $true
+
+    if ($age -ne $null) {
+        $seconds_per_unit = @{'s'=1; 'm'=60; 'h'=3600; 'd'=86400; 'w'=604800}
+        $seconds_pattern = '^(-?\d+)(s|m|h|d|w)?$'
+        $match = $age -match $seconds_pattern
+        if ($match) {
+            [int]$specified_seconds = $matches[1]
+            if ($matches[2] -eq $null) {
+                $chosen_unit = 's'
+            } else {
+                $chosen_unit = $matches[2]
+            }
+
+            $abs_seconds = $specified_seconds * ($seconds_per_unit.$chosen_unit)
+            $epoch = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
+            if ($age_stamp -eq 'mtime') {
+                $age_comparison = $epoch.AddSeconds($info.lastwritetime)
+            } elseif ($age_stamp -eq 'ctime') {
+                $age_comparison = $epoch.AddSeconds($info.creationtime)
+            } elseif ($age_stamp -eq 'atime') {
+                $age_comparison = $epoch.AddSeconds($info.lastaccesstime)
+            }
+
+            if ($specified_seconds -ge 0) {
+                $start_date = (Get-Date).AddSeconds($abs_seconds * -1)
+                if ($age_comparison -lt $start_date) {
+                    $valid_match = $false
+                }
+            } else {
+                $start_date = (Get-Date).AddSeconds($abs_seconds)
+                if ($age_comparison -gt $start_date) {
+                    $valid_match = $false
+                }
+            }
+        } else {
+            Fail-Json $result "failed to process age"
+        }
+    }
+
+    $valid_match
+}
+
+Function Assert-FileType($info) {
+    $valid_match = $true
+
+    if ($file_type -eq 'directory' -and $info.isdir -eq $false) {
+        $valid_match = $false
+    }
+    if ($file_type -eq 'file' -and $info.isdir -eq $true) {
+        $valid_match = $false
+    }
+
+    $valid_match
+}
+
+Function Assert-Hidden($info) {
+    $valid_match = $true
+
+    if ($hidden -eq $true -and $info.ishidden -eq $false) {
+        $valid_match = $false
+    }
+    if ($hidden -eq $false -and $info.ishidden -eq $true) {
+        $valid_match = $false
+    }
+
+    $valid_match
+}
+
+Function Assert-Pattern($info) {
+    $valid_match = $false
+
+    if ($patterns -ne $null) {
+        foreach ($pattern in $patterns) {
+            if ($use_regex -eq $true) {
+                # Use -match for regex matching
+                if ($info.filename -match $pattern) {
+                    $valid_match = $true
+                }
+            } else {
+                # Use -like for wildcard matching
+                if ($info.filename -like $pattern) {
+                    $valid_match = $true
+                }
+            }
+        }
+    } else {
+        $valid_match = $true
+    }
+
+    $valid_match
+}
+
+Function Assert-Size($info) {
+    $valid_match = $true
+
+    if ($size -ne $null) {
+        $bytes_per_unit = @{'b'=1; 'k'=1024; 'm'=1024*1024; 'g'=1024*1024*1024; 't'=1024*1024*1024*1024}
+        $size_pattern = '^(-?\d+)(b|k|m|g|t)?$'
+        $match = $size -match $size_pattern
+        if ($match) {
+            [int]$specified_size = $matches[1] 
+            if ($matches[2] -eq $null) {
+                $chosen_byte = 'b'
+            } else {
+                $chosen_byte = $matches[2]
+            }
+
+            $abs_size = $specified_size * ($bytes_per_unit.$chosen_byte)
+            if ($specified_size -ge 0) {
+                if ($info.size -lt $abs_size) {
+                    $valid_match = $false
+                }
+            } else {
+                if ($info.size -gt $abs_size * -1) {
+                    $valid_match = $false
+                }
+            }
+        } else {
+            Fail-Json $result "failed to process size"
+        }
+    }
+
+    $valid_match
+}
+
+Function Assert-FileStat($info) {
+    $age_match = Assert-Age -info $info
+    $file_type_match = Assert-FileType -info $info
+    $hidden_match = Assert-Hidden -info $info
+    $pattern_match = Assert-Pattern -info $info
+    $size_match = Assert-Size -info $info
+
+    if ($age_match -and $file_type_match -and $hidden_match -and $pattern_match -and $size_match) {
+        $info
+    } else {
+        $false
+    }
+}
+
+Function Get-FileStat($file) {
+    $epoch = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
+    $access_control = $file.GetAccessControl()
+    $attributes = @()
+    foreach ($attribute in ($file.Attributes -split ',')) {
+        $attributes += $attribute.Trim()
+    }
+
+    $file_stat = @{
+        isreadonly = $attributes -contains 'ReadOnly'
+        ishidden = $attributes -contains 'Hidden'
+        isarchive = $attributes -contains 'Archive'
+        attributes = $file.Attributes.ToString()
+        owner = $access_control.Owner
+        lastwritetime = (New-TimeSpan -Start $epoch -End $file.LastWriteTime).TotalSeconds
+        creationtime = (New-TimeSpan -Start $epoch -End $file.CreationTime).TotalSeconds
+        lastaccesstime = (New-TimeSpan -Start $epoch -End $file.LastAccessTime).TotalSeconds
+        path = $file.FullName
+        filename = $file.Name
+    }
+
+    $islink = $false
+    $isdir = $false
+    $isshared = $false
+
+    if ($attributes -contains 'ReparsePoint') {
+        # 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($file)
+            $file_stat.lnk_source = $lnk_source
+        } catch {}
+    } elseif ($file.PSIsContainer) {
+        $isdir = $true
+
+        $share_info = Get-WmiObject -Class Win32_Share -Filter "Path='$($file.Fullname -replace '\\', '\\')'"
+        if ($share_info -ne $null) {
+            $isshared = $true
+            $file_stat.sharename = $share_info.Name
+        }
+        
+        #$dir_files_sum = Get-ChildItem $file.FullName -Recurse | Measure-Object -property length -sum
+        $dir_files_sum = Get-ChildItem $file.FullName -Recurse
+
+        if ($dir_files_sum -eq $null -or ($dir_files_sum.PSObject.Properties.name -contains 'length' -eq $false)) {
+            $file_stat.size = 0
+        } else {
+            $file_stat.size = ($dir_files_sum | Measure-Object -property length -sum).Sum
+        }
+    } else {
+        $file_stat.size = $file.length
+        $file_stat.extension = $file.Extension
+
+        if ($get_checksum) {
+            $checksum = Get-FileChecksum -path $path -algorithm $checksum_algorithm
+            $file_stat.checksum = $checksum
+        }
+    }
+
+    $file_stat.islink = $islink
+    $file_stat.isdir = $isdir
+    $file_stat.isshared = $isshared
+
+    Assert-FileStat -info $file_stat
+}
+
+Function Get-FilesInFolder($path) {
+    $items = @()
+    foreach ($item in (Get-ChildItem -Force -Path $path -ErrorAction SilentlyContinue)) {
+        if ($item.PSIsContainer -and $recurse) {
+            if (($item.Attributes -like '*ReparsePoint*' -and $follow) -or ($item.Attributes -notlike '*ReparsePoint*')) {
+                # File is a link and we want to follow a link OR file is not a link
+                $items += $item.FullName
+                $items += Get-FilesInFolder -path $item.FullName
+            } else {
+                # File is a link but we don't want to follow a link
+                $items += $item.FullName
+            }
+        } else {
+            $items += $item.FullName
+        }
+    }
+
+    $items
+}
+
+$paths_to_check = @()
+foreach ($path in $paths) {
+    if (Test-Path $path) {
+        if ((Get-Item -Force $path).PSIsContainer) {
+            $paths_to_check += Get-FilesInFolder -path $path
+        } else {
+            Fail-Json $result "Argument path $path is a file not a directory"
+        }
+    } else {
+        Fail-Json $result "Argument path $path does not exist cannot get information on"
+    }
+}
+$paths_to_check = $paths_to_check | Select -Unique
+
+foreach ($path in $paths_to_check) {
+    $file = Get-Item -Force -Path $path
+    $info = Get-FileStat -file $file
+    $new_examined = $result.examined + 1
+    $result.examined = $new_examined
+
+    if ($info -ne $false) {
+        $files = $result.Files        
+        $files += $info
+
+        $new_matched = $result.matched + 1
+        $result.matched = $new_matched
+        $result.files = $files
+    }
+}
+
+Exit-Json $result
diff --git a/lib/ansible/modules/windows/win_find.py b/lib/ansible/modules/windows/win_find.py
new file mode 100644
index 00000000000..194621c7301
--- /dev/null
+++ b/lib/ansible/modules/windows/win_find.py
@@ -0,0 +1,323 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2016, Ansible, inc
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+ANSIBLE_METADATA = {'status': ['preview'],
+                    'supported_by': 'community',
+                    'version': '1.0'}
+
+DOCUMENTATION = r'''
+---
+module: win_find
+version_added: "2.3"
+short_description: return a list of files based on specific criteria
+description:
+    - Return a list of files based on specified criteria.
+    - Multiple criteria are AND'd together.
+options:
+    age:
+        description:
+            - Select files or folders whose age is equal to or greater than
+              the specified tim. Use a negative age to find files equal to or
+              less than the specified time. You can choose seconds, minues,
+              hours, days or weeks by specifying the first letter of an of
+              those words (e.g., "1w").
+        required: false
+    age_stamp:
+        description:
+            - Choose the file property against which we compare C(age). The
+              default attribute we compare with is last modification time.
+        required: false
+        default: mtime
+        choices: ['atime', 'mtime', 'ctime']
+    checksum_algorithm:
+        description:
+            - Algorithm to determine the checksum of a file. Will throw an error
+              if the host is unable to use specified algorithm.
+        required: false
+        default: sha1
+        choices: ['md5', 'sha1', 'sha256', 'sha384', 'sha512']
+    file_type:
+        description: Type of file to search for
+        required: false
+        default: file
+        choices: ['file', 'directory']
+    follow:
+        description:
+            - Set this to true to follow symlinks in the path. This needs to
+              be used in conjunction with C(recurse).
+        required: false
+        default: false
+        choices: ['true', 'false']
+    get_checksum:
+        description:
+            - Whether to return a checksum of the file in the return info (default sha1),
+              use C(checksum_algorithm) to change from the default.
+        required: false
+        default: true
+        choices: ['true', 'false']
+    hidden:
+        description: Set this to include hidden files or folders
+        required: false
+        default: false
+        choices: ['true', 'false']
+    paths:
+        description:
+            - List of paths of directories to search for files or folders in.
+              This can be supplied as a single path or a list of paths.
+        required: true
+    patterns:
+        description:
+            - One or more (powershell or regex) patterns to compare filenames
+              with. The type of pattern matching is controlled by C(use_regex)
+              option. The patterns retrict the list of files or folders to be
+              returned based on the filenames. For a file to be matched it
+              only has to match with one pattern in a list provided.
+        required: false
+    recurse:
+        description:
+            - Will recursively descend into the directory looking for files
+              or folders
+        required: false
+        default: false
+        choices: ['true', 'false']
+    size:
+        description:
+            - Select files or folders whose size is equal to or greater than
+              the specified size. Use a negative value to find files equal to
+              or less than the specified size. You can specify the size with
+              a suffix of the byte type i.e. kilo = k, mega = m... Size is not
+              evaluated for symbolic links.
+        required: false
+        default: false
+    use_regex:
+        description:
+            - Will set patterns to run as a regex check if true
+        required: false
+        default: false
+        choices: ['true', 'false']
+author: "Jordan Borean (@jborean93)"
+'''
+
+EXAMPLES = r'''
+# Find files in path
+- win_find:
+    paths: D:\temp
+
+# Find hidden files in path
+- win_find:
+    paths: D:\temp
+    hidden: True
+
+# Find files in multiple paths
+- win_find:
+    paths: ['C:\temp', 'D:\temp']
+
+# Find files in directory while searching recursively
+- win_find:
+    paths: D:\temp
+    recurse: True
+
+# Find files in directory while following symlinks
+- win_find:
+    paths: D:\temp
+    recurse: True
+    follow: True
+
+# Find files with .log and .out extension using powershell wildcards
+- win_find:
+    paths: D:\temp
+    patterns: ['*.log', '*.out']
+
+# Find files in path based on regex pattern
+- win_find:
+    paths: D:\temp
+    patterns: "out_\d{8}-\d{6}.log"
+
+# Find files older than 1 day
+- win_find:
+    paths: D:\temp
+    age: 86400
+
+# Find files older than 1 day based on create time
+- win_find:
+    paths: D:\temp
+    age: 86400
+    age_stamp: ctime
+
+# Find files older than 1 day with unit syntax
+- win_find:
+    paths: D:\temp
+    age: 1d
+
+# Find files newer than 1 hour
+- win_find:
+    paths: D:\temp
+    age: -3600
+
+# Find files newer than 1 hour with unit syntax
+- win_find:
+    paths: D:\temp
+    age: -1h
+
+# Find files larger than 1MB
+- win_find:
+    paths: D:\temp
+    size: 1048576
+
+# Find files larger than 1GB with unit syntax
+- win_find:
+    paths: D:\temp
+    size: 1g
+
+# Find files smaller than 1MB
+- win_find:
+    paths: D:\temp
+    size: -1048576
+
+# Find files smaller than 1GB with unit syntax
+- win_find:
+    paths: D:\temp
+    size: -1g
+
+# Find folders/symlinks in multiple paths
+- win_find:
+    paths: ['C:\temp', 'D:\temp']
+    file_type: directory
+
+# Find files and return SHA256 checksum of files found
+- win_find:
+    paths: C:\temp
+    get_checksum: True
+    checksum_algorithm: sha256
+
+# Find files and do not return the checksum
+- win_find:
+    path: C:\temp
+    get_checksum: False
+'''
+
+RETURN = r'''
+changed:
+    description: Whether anything was chagned
+    returned: always
+    type: boolean
+    sample: True
+examined:
+    description: The number of files/folders that was checked
+    returned: always
+    type: int
+    sample: 10
+matched:
+    description: The number of files/folders that match the criteria
+    returns: always
+    type: int
+    sample: 2
+files:
+    description: Information on the files/folders that match the criteria returned as a list of dictionary elements for each file matched
+    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 exists, path is a file, get_checksum == True
+            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, path is a symbolic link
+            type: string
+            sample: C:\temp
+        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, path 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, path is not a link
+            type: int
+            sample: 1024
+'''
diff --git a/test/integration/targets/win_find/aliases b/test/integration/targets/win_find/aliases
new file mode 100644
index 00000000000..4a25390297e
--- /dev/null
+++ b/test/integration/targets/win_find/aliases
@@ -0,0 +1 @@
+windows/ci/group/2
diff --git a/test/integration/targets/win_find/defaults/main.yml b/test/integration/targets/win_find/defaults/main.yml
new file mode 100644
index 00000000000..d3d03f767fe
--- /dev/null
+++ b/test/integration/targets/win_find/defaults/main.yml
@@ -0,0 +1 @@
+win_find_dir: "{{win_output_dir}}\\win_find"
diff --git a/test/integration/targets/win_find/files/set_attributes.ps1 b/test/integration/targets/win_find/files/set_attributes.ps1
new file mode 100644
index 00000000000..b57a368c438
--- /dev/null
+++ b/test/integration/targets/win_find/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_find/files/set_filedate.ps1 b/test/integration/targets/win_find/files/set_filedate.ps1
new file mode 100644
index 00000000000..3bc525a4ff0
--- /dev/null
+++ b/test/integration/targets/win_find/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_find/files/set_share.ps1 b/test/integration/targets/win_find/files/set_share.ps1
new file mode 100644
index 00000000000..c56242cf40f
--- /dev/null
+++ b/test/integration/targets/win_find/files/set_share.ps1
@@ -0,0 +1,7 @@
+$share_name = $args[1]
+$share_stat = Get-WmiObject -Class Win32_Share -Filter "name='$share_name'"
+If ($share_stat) {
+    $share_stat.Delete()
+}
+$wmi = [wmiClass] 'Win32_Share'
+$wmi.Create($args[0], $share_name, 0)
diff --git a/test/integration/targets/win_find/meta/main.yml b/test/integration/targets/win_find/meta/main.yml
new file mode 100644
index 00000000000..d328716dfa4
--- /dev/null
+++ b/test/integration/targets/win_find/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+  - prepare_win_tests
diff --git a/test/integration/targets/win_find/tasks/main.yml b/test/integration/targets/win_find/tasks/main.yml
new file mode 100644
index 00000000000..af6b2b78287
--- /dev/null
+++ b/test/integration/targets/win_find/tasks/main.yml
@@ -0,0 +1,754 @@
+---
+- name: remove links if they exist as win_file struggles
+  win_command: cmd.exe /c rmdir "{{item}}"
+  ignore_errors: True
+  with_items:
+  - "{{win_find_dir}}\\nested\\link"
+  - "{{win_find_dir}}\\broken-link"
+  - "{{win_find_dir}}\\hard-link-dest\\hard-link.log"
+  - "{{win_find_dir}}\\junction-link"
+
+- name: ensure the testing directory is cleared before setting up test
+  win_file:
+    path: "{{win_find_dir}}"
+    state: absent
+
+- name: ensure testing directories exist
+  win_file:
+    path: "{{item}}"
+    state: directory
+  with_items:
+  - "{{win_find_dir}}\\nested"
+  - "{{win_find_dir}}\\single"
+  - "{{win_find_dir}}\\link-dest"
+  - "{{win_find_dir}}\\link-dest\\sub-link"
+  - "{{win_find_dir}}\\hard-link-dest"
+  - "{{win_find_dir}}\\junction-link-dest"
+  - "{{win_find_dir}}\\broken-link-dest"
+  - "{{win_find_dir}}\\nested\\sub-nest"
+  - "{{win_find_dir}}\\shared"
+  - "{{win_find_dir}}\\shared\\folder"
+  - "{{win_find_dir}}\\hidden"
+  - "{{win_find_dir}}\\date"
+
+- name: create empty test files
+  win_file:
+    path: "{{item}}"
+    state: touch
+  with_items:
+  - "{{win_find_dir}}\\nested\\file.ps1"
+  - "{{win_find_dir}}\\nested\\test.ps1"
+  - "{{win_find_dir}}\\nested\\out.log"
+  - "{{win_find_dir}}\\nested\\archive.log"
+  - "{{win_find_dir}}\\nested\\sub-nest\\test.ps1"
+  - "{{win_find_dir}}\\nested\\sub-nest\\readonly.txt"
+  - "{{win_find_dir}}\\link-dest\\link.ps1"
+  - "{{win_find_dir}}\\single\\large.ps1"
+  - "{{win_find_dir}}\\single\\small.ps1"
+  - "{{win_find_dir}}\\single\\test.ps1"
+  - "{{win_find_dir}}\\single\\hidden.ps1"
+  - "{{win_find_dir}}\\date\\new.ps1"
+  - "{{win_find_dir}}\\date\\old.ps1"
+  - "{{win_find_dir}}\\single\\out_20161101-091005.log"
+  - "{{win_find_dir}}\\hidden\\out_20161101-091005.log"
+  - "{{win_find_dir}}\\hard-link-dest\\file-abc.log"
+
+- name: populate files with a test string
+  win_lineinfile:
+    dest: "{{item.path}}"
+    line: "{{item.text}}"
+  with_items:
+  - { 'path': "{{win_find_dir}}\\nested\\file.ps1", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\nested\\test.ps1", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\nested\\out.log", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\nested\\archive.log", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\nested\\sub-nest\\test.ps1", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\nested\\sub-nest\\readonly.txt", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\link-dest\\link.ps1", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\single\\test.ps1", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\single\\hidden.ps1", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\date\\new.ps1", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\date\\old.ps1", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\single\\out_20161101-091005.log", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\hidden\\out_20161101-091005.log", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\hard-link-dest\\file-abc.log", 'text': 'abcdefg1234567' }
+  - { 'path': "{{win_find_dir}}\\single\\small.ps1", 'text': "a" }
+  - { 'path': "{{win_find_dir}}\\date\\new.ps1", 'text': "random text for new date" }
+  - { 'path': "{{win_find_dir}}\\date\\old.ps1", 'text': "random text for old date" }
+
+- name: populate large text file
+  win_command: powershell "Set-Content {{win_find_dir}}\\single\\large.ps1 ('abcdefghijklmnopqrstuvwxyz' * 10000)"
+
+- name: create share
+  script: set_share.ps1 "{{win_find_dir}}\shared\folder" "folder-share"
+
+- name: create links
+  win_command: cmd.exe /c mklink /{{item.type}} "{{item.source}}" "{{item.target}}"
+  with_items:
+  - { type: 'D', source: "{{win_find_dir}}\\nested\\link", target: "{{win_find_dir}}\\link-dest" }
+  - { type: 'D', source: "{{win_find_dir}}\\broken-link", target: "{{win_find_dir}}\\broken-link-dest" }
+  - { type: 'H', source: "{{win_find_dir}}\\hard-link-dest\\hard-link.log", target: "{{win_find_dir}}\\hard-link-dest\\file-abc.log" }
+  - { type: 'J', source: "{{win_find_dir}}\\junction-link", target: "{{win_find_dir}}\\junction-link-dest" }
+
+- name: set modification date on files/folders
+  script: set_filedate.ps1 "{{item}}"
+  with_items:
+  - "{{win_find_dir}}\\nested\\file.ps1"
+  - "{{win_find_dir}}\\nested\\test.ps1"
+  - "{{win_find_dir}}\\nested\\out.log"
+  - "{{win_find_dir}}\\nested\\archive.log"
+  - "{{win_find_dir}}\\nested\\sub-nest\\test.ps1"
+  - "{{win_find_dir}}\\nested\\sub-nest\\readonly.txt"
+  - "{{win_find_dir}}\\link-dest\\link.ps1"
+  - "{{win_find_dir}}\\single\\large.ps1"
+  - "{{win_find_dir}}\\single\\small.ps1"
+  - "{{win_find_dir}}\\single\\test.ps1"
+  - "{{win_find_dir}}\\single\\hidden.ps1"
+  - "{{win_find_dir}}\\date\\old.ps1"
+  - "{{win_find_dir}}\\single\\out_20161101-091005.log"
+  - "{{win_find_dir}}\\hidden\\out_20161101-091005.log"
+  - "{{win_find_dir}}\\hard-link-dest\\file-abc.log"
+  - "{{win_find_dir}}\\nested"
+  - "{{win_find_dir}}\\single"
+  - "{{win_find_dir}}\\link-dest"
+  - "{{win_find_dir}}\\link-dest\\sub-link"
+  - "{{win_find_dir}}\\hard-link-dest"
+  - "{{win_find_dir}}\\junction-link-dest"
+  - "{{win_find_dir}}\\broken-link-dest"
+  - "{{win_find_dir}}\\nested\\sub-nest"
+  - "{{win_find_dir}}\\shared"
+  - "{{win_find_dir}}\\shared\\folder"
+  - "{{win_find_dir}}\\hidden"
+  - "{{win_find_dir}}\\date"
+
+- name: set file attributes for test
+  script: set_attributes.ps1 "{{item.path}}" {{item.attr}}
+  with_items:
+  - { 'path': "{{win_find_dir}}\\hidden", 'attr': "Hidden" }
+  - { 'path': "{{win_find_dir}}\\date", 'attr': "Hidden" }
+  - { 'path': "{{win_find_dir}}\\nested\\archive.log", 'attr': "Archive" }
+  - { 'path': "{{win_find_dir}}\\nested\\sub-nest\\readonly.txt", 'attr': "ReadOnly" }
+  - { 'path': "{{win_find_dir}}\\single\\hidden.ps1", 'attr': "Hidden" }
+
+- name: break the broken link target
+  win_file:
+    path: "{{win_find_dir}}\\broken-link-dest"
+    state: absent
+# end test setup
+
+- name: expect failure when not setting paths
+  win_find:
+    patterns: a
+  register: actual
+  failed_when: "actual.msg != 'Missing required argument: paths'"
+
+- name: expect failure when setting paths to a file
+  win_find:
+    paths: "{{win_output_dir}}\\win_find\\single\\large.ps1"
+  register: actual
+  failed_when: "actual.msg != 'Argument path {{win_output_dir|regex_replace('\\\\', '\\\\\\\\')}}\\win_find\\single\\large.ps1 is a file not a directory'"
+
+- name: expect failure whe path is set to a non existant folder
+  win_find:
+    paths: "{{win_output_dir}}\\win_find\\thisisafakefolder"
+  register: actual
+  failed_when: "actual.msg != 'Argument path {{win_output_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\win_find\\\\thisisafakefolder does not exist cannot get information on'"
+
+- name: get files in single directory
+  win_find:
+    paths: "{{win_output_dir}}\\win_find\\single"
+  register: actual
+
+- name: set expected value for files in a single directory
+  set_fact:
+    expected:
+      changed: False
+      examined: 5
+      files:
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: f8d100cdcf0e6c1007db2f8dd0b7ee2884df89af,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: large.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\single\\large.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 260002 }
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .log,
+          filename: out_20161101-091005.log,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\single\\out_20161101-091005.log",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: small.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\single\\small.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 1 }
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: test.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\single\\test.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      matched: 4
+
+- name: assert actual == expected
+  assert:
+    that: "actual == expected"
+
+- name: find hidden files
+  win_find:
+    paths: ['{{win_find_dir}}\\single', '{{win_find_dir}}\\nested']
+    hidden: True
+  register: actual
+
+- name: set fact for hidden files
+  set_fact:
+    expected:
+      changed: False
+      examined: 11
+      files:
+      - { isarchive: True,
+          attributes: "Hidden, Archive",
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: hidden.ps1,
+          ishidden: True,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\single\\hidden.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      matched: 1
+
+- name: assert actual == expected
+  assert:
+    that: "actual == expected"
+
+- name: find file based on pattern
+  win_find:
+    paths: '{{win_find_dir}}\\single'
+    patterns: ['*.log', 'out_*']
+  register: actual_pattern
+
+- name: find file based on pattern regex
+  win_find:
+    paths: '{{win_find_dir}}\\single'
+    patterns: "out_\\d{8}-\\d{6}.log"
+    use_regex: True
+  register: actual_regex
+
+- name: set fact for pattern files
+  set_fact:
+    expected:
+      changed: False
+      examined: 5
+      files:
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .log,
+          filename: out_20161101-091005.log,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\single\\out_20161101-091005.log",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      matched: 1
+
+- name: assert actual == expected
+  assert:
+    that: 
+    - "actual_pattern == expected"
+    - "actual_regex == expected"
+
+- name: find files with recurse set
+  win_find:
+    paths: "{{win_find_dir}}\\nested"
+    recurse: True
+    patterns: "*.ps1"
+  register: actual
+
+- name: set expected value for files in a nested directory
+  set_fact:
+    expected:
+      changed: False
+      examined: 8
+      files:
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: test.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\nested\\sub-nest\\test.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: file.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\nested\\file.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: test.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\nested\\test.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      matched: 3
+
+- name: assert actual == expected
+  assert:
+    that: "actual == expected"
+
+- name: find files with recurse set and follow links
+  win_find:
+    paths: "{{win_find_dir}}\\nested"
+    recurse: True
+    follow: True
+    patterns: "*.ps1"
+  register: actual
+
+- name: set expected value for files in a nested directory while following links
+  set_fact:
+    expected:
+      changed: False
+      examined: 10
+      files:
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: link.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\nested\\link\\link.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: test.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\nested\\sub-nest\\test.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: file.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\nested\\file.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3,
+          creationtime: 1477984205,
+          extension: .ps1,
+          filename: test.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\nested\\test.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 14 }
+      matched: 4
+
+- name: assert actual == expected
+  assert:
+    that: "actual == expected"
+
+- name: find directories
+  win_find:
+    paths: "{{win_find_dir}}\\link-dest"
+    file_type: directory
+  register: actual
+
+- name: set expected fact for directories with recurse and follow
+  set_fact:
+    expected:
+      changed: False
+      examined: 2
+      files:
+      - { isarchive: False,
+          attributes: Directory,
+          creationtime: 1477984205,
+          filename: sub-link,
+          ishidden: False,
+          isdir: True,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\link-dest\\sub-link",
+          isreadonly: False,
+          isshared: False,
+          size: 0 }
+      matched: 1
+
+- name: assert actual == expected
+  assert:
+    that: "actual == expected"
+
+- name: find directories recurse and follow with a broken link
+  win_find:
+    paths: "{{win_find_dir}}"
+    file_type: directory
+    recurse: True
+    follow: True
+  register: actual
+
+- name: check directory count with recurse and follow is correct
+  assert:
+    that:
+    - "actual.examined == 33"
+    - "actual.matched == 13"
+    - "actual.files[0].filename == 'broken-link'"
+    - "actual.files[0].islink == True"
+    - "actual.files[2].filename == 'junction-link'"
+    - "actual.files[2].islink == True"
+    - "actual.files[2].lnk_source == '{{win_find_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\junction-link-dest'"
+    - "actual.files[7].filename == 'link'"
+    - "actual.files[7].islink == True"
+    - "actual.files[7].lnk_source == '{{win_find_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\link-dest'"
+    - "actual.files[11].filename == 'folder'"
+    - "actual.files[11].islink == False"
+    - "actual.files[11].isshared == True"
+    - "actual.files[11].sharename == 'folder-share'"
+
+- name: filter files by size without byte specified
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    size: 260002
+  register: actual_without_byte
+
+- name: filter files by size with byte specified
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    size: 253k
+  register: actual_with_byte
+
+- name: set expected fact for files by size
+  set_fact:
+    expected:
+      changed: False
+      examined: 5
+      files:
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: f8d100cdcf0e6c1007db2f8dd0b7ee2884df89af,
+          creationtime: 1477984205,
+          extension: ".ps1",
+          filename: large.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\single\\large.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 260002 }
+      matched: 1
+
+- name: assert actual == expected
+  assert:
+    that: 
+    - "actual_without_byte == expected"
+    - "actual_with_byte == expected"
+
+- name: filter files by size (less than) without byte specified
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    size: -4
+  register: actual_without_byte
+
+- name: filter files by size (less than) with byte specified
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    size: -4b
+  register: actual_with_byte
+
+- name: set expected fact for files by size (less than)
+  set_fact:
+    expected:
+      changed: False
+      examined: 5
+      files:
+      - { isarchive: True,
+          attributes: Archive,
+          checksum: 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8,
+          creationtime: 1477984205,
+          extension: ".ps1",
+          filename: small.ps1,
+          ishidden: False,
+          isdir: False,
+          islink: False,
+          lastaccesstime: 1477984205,
+          lastwritetime: 1477984205,
+          owner: BUILTIN\Administrators,
+          path: "{{win_find_dir}}\\single\\small.ps1",
+          isreadonly: False,
+          isshared: False,
+          size: 1 }
+      matched: 1
+
+- name: assert actual == expected
+  assert:
+    that: 
+    - "actual_without_byte == expected"
+    - "actual_with_byte == expected"
+
+# For dates we cannot assert against expected as the times change, this is a poor mans attempt at testing
+- name: filter files by age without unit specified
+  win_find:
+    paths: "{{win_find_dir}}\\date"
+    age: 3600
+  register: actual_without_unit
+
+- name: filter files by age with unit specified
+  win_find:
+    paths: "{{win_find_dir}}\\date"
+    age: 1h
+  register: actual_with_unit
+
+- name: assert dates match each other
+  assert:
+    that:
+    - "actual_without_unit == actual_with_unit"
+    - "actual_without_unit.matched == 1"
+    - "actual_without_unit.files[0].checksum == '7454f04e3ac587f711a416f4edf26507255e0a2e'"
+    - "actual_without_unit.files[0].path == '{{win_find_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\date\\\\new.ps1'"
+
+- name: filter files by age (older than) without unit specified
+  win_find:
+    paths: "{{win_find_dir}}\\date"
+    age: -1
+  register: actual_without_unit
+
+- name: filter files by age (older than) without unit specified
+  win_find:
+    paths: "{{win_find_dir}}\\date"
+    age: -1s
+  register: actual_with_unit
+
+- name: assert dates match each other
+  assert:
+    that:
+    - "actual_without_unit == actual_with_unit"
+    - "actual_without_unit.matched == 2"
+    - "actual_without_unit.files[0].checksum == '7454f04e3ac587f711a416f4edf26507255e0a2e'"
+    - "actual_without_unit.files[0].path == '{{win_find_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\date\\\\new.ps1'"
+    - "actual_without_unit.files[1].checksum == '031a04ecc76f794d7842651de732075dec6fef04'"
+    - "actual_without_unit.files[1].path == '{{win_find_dir|regex_replace('\\\\', '\\\\\\\\')}}\\\\date\\\\old.ps1'"
+
+- name: get list of files with md5 checksum
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    patterns: test.ps1
+    checksum_algorithm: md5
+  register: actual_md5_checksum
+
+- name: assert md5 checksum value
+  assert:
+    that:
+    - "actual_md5_checksum.files[0].checksum == 'd1713d0f1d2e8fae230328d8fd59de01'"
+  
+- name: get list of files with sha1 checksum
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    patterns: test.ps1
+    checksum_algorithm: sha1
+  register: actual_sha1_checksum
+
+- name: assert sha1 checksum value
+  assert:
+    that:
+    - "actual_sha1_checksum.files[0].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3'"
+
+- name: get list of files with sha256 checksum
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    patterns: test.ps1
+    checksum_algorithm: sha256
+  register: actual_sha256_checksum
+
+- name: assert sha256 checksum value
+  assert:
+    that:
+    - "actual_sha256_checksum.files[0].checksum == 'c20d2eba7ffda0079812721b6f4e4e109e2f0c5e8cc3d1273a060df6f7d9f339'"
+
+- name: get list of files with sha384 checksum
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    patterns: test.ps1
+    checksum_algorithm: sha384
+  register: actual_sha384_checksum
+
+- name: assert sha384 checksum value
+  assert:
+    that:
+    - "actual_sha384_checksum.files[0].checksum == 'aed515eb216b9c7009ae8c4680f46c1e22004528b231aa0482a8587543bca47d3504e9f77e884eb2d11b2f9f5dc01651'"
+
+- name: get list of files with sha512 checksum
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    patterns: test.ps1
+    checksum_algorithm: sha512
+  register: actual_sha512_checksum
+
+- name: assert sha512 checksum value
+  assert:
+    that:
+    - "actual_sha512_checksum.files[0].checksum == '05abf64a68c4731699c23b4fc6894a36646fce525f3c96f9cf743b5d0c3bfd933dad0e95e449e3afe1f74d534d69a53b8f46cf835763dd42915813c897b02b87'"
+
+- name: get list of files without checksum
+  win_find:
+    paths: "{{win_find_dir}}\\single"
+    patterns: test.ps1
+    get_checksum: False
+  register: actual_no_checksum
+
+- name: assert no checksum is returned
+  assert:
+    that:
+    - "actual_no_checksum.files[0].checksum is undefined"
+
+- name: check if broken symbolic link exists
+  win_stat:
+    path: "{{win_find_dir}}\\broken-link"
+  register: broken_link_exists
+
+- name: delete broken symbolic link if it exists
+  win_command: cmd.exe /c rmdir {{win_find_dir}}\broken-link
+
+  when: broken_link_exists.stat.exists
+
+- name: check if junction symbolic link exists
+  win_stat:
+    path: "{{win_find_dir}}\\junction-link"
+  register: junction_link_exists
+
+- name: delete junction symbolic link if it exists
+  win_command: cmd.exe /c rmdir {{win_find_dir}}\junction-link
+  when: junction_link_exists.stat.exists
+
+- name: check if nested symbolic link exists
+  win_stat:
+    path: "{{win_find_dir}}\\nested\\link"
+  register: nested_link_exists
+
+- name: delete nested symbolic link if it exists
+  win_command: cmd.exe /c rmdir {{win_find_dir}}\nested\link
+  when: nested_link_exists.stat.exists
+
+- name: remove testing folder
+  win_file:
+    path: "{{win_find_dir}}"
+    state: absent
diff --git a/test/integration/test_win_group2.yml b/test/integration/test_win_group2.yml
index 27765ea5995..5eae464ec35 100644
--- a/test/integration/test_win_group2.yml
+++ b/test/integration/test_win_group2.yml
@@ -7,6 +7,7 @@
     - { role: win_template, tags: test_win_template }
     - { role: win_lineinfile, tags: test_win_lineinfile }
     - { role: win_stat, tags: test_win_stat }
+    - { role: win_find, tags: test_win_find }
     - { role: win_get_url, tags: test_win_get_url }
     - { role: win_msi, tags: test_win_msi }
     - { role: win_package, tags: test_win_package }