diff --git a/changelogs/fragments/win_find-performance.yaml b/changelogs/fragments/win_find-performance.yaml new file mode 100644 index 00000000000..fa42088151a --- /dev/null +++ b/changelogs/fragments/win_find-performance.yaml @@ -0,0 +1,2 @@ +minor_changes: +- win_find - Improve performance when scanning heavily nested directories and align behaviour to the ``find`` module. diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.10.rst b/docs/docsite/rst/porting_guides/porting_guide_2.10.rst index ffc3f706e68..56ef89b9915 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.10.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.10.rst @@ -98,6 +98,10 @@ Noteworthy module changes * :ref:`zabbix_action ` no longer requires ``esc_period`` and ``event_source`` arguments when ``state=absent``. * :ref:`gitlab_user ` no longer requires ``name``, ``email`` and ``password`` arguments when ``state=absent``. * :ref:`win_pester ` no longer runs all ``*.ps1`` file in the directory specified due to it executing potentially unknown scripts. It will follow the default behaviour of only running tests for files that are like ``*.tests.ps1`` which is built into Pester itself +* :ref:`win_find ` has been refactored to better match the behaviour of the ``find`` module. Here is what has changed: + * When the directory specified by ``paths`` does not exist or is a file, it will no longer fail and will just warn the user + * Junction points are no longer reported as ``islnk``, use ``isjunction`` to properly report these files. This behaviour matches the :ref:`win_stat ` + * Directories no longer return a ``size``, this matches the ``stat`` and ``find`` behaviour and has been removed due to the difficulties in correctly reporting the size of a directory Plugins ======= diff --git a/lib/ansible/modules/windows/win_find.ps1 b/lib/ansible/modules/windows/win_find.ps1 index 2dca0a26ddc..bc57c5ff5d2 100644 --- a/lib/ansible/modules/windows/win_find.ps1 +++ b/lib/ansible/modules/windows/win_find.ps1 @@ -3,357 +3,414 @@ # Copyright: (c) 2016, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -#Requires -Module Ansible.ModuleUtils.Legacy - -$ErrorActionPreference = "Stop" - -$params = Parse-Args -arguments $args -supports_check_mode $true -$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP - -$paths = Get-AnsibleParam -obj $params -name 'paths' -failifempty $true - -$age = Get-AnsibleParam -obj $params -name 'age' -$age_stamp = Get-AnsibleParam -obj $params -name 'age_stamp' -default 'mtime' -ValidateSet 'mtime','ctime','atime' -$file_type = Get-AnsibleParam -obj $params -name 'file_type' -default 'file' -ValidateSet 'file','directory' -$follow = Get-AnsibleParam -obj $params -name 'follow' -type "bool" -default $false -$hidden = Get-AnsibleParam -obj $params -name 'hidden' -type "bool" -default $false -$patterns = Get-AnsibleParam -obj $params -name 'patterns' -aliases "regex","regexp" -$recurse = Get-AnsibleParam -obj $params -name 'recurse' -type "bool" -default $false -$size = Get-AnsibleParam -obj $params -name 'size' -$use_regex = Get-AnsibleParam -obj $params -name 'use_regex' -type "bool" -default $false -$get_checksum = Get-AnsibleParam -obj $params -name 'get_checksum' -type "bool" -default $true -$checksum_algorithm = Get-AnsibleParam -obj $params -name 'checksum_algorithm' -default 'sha1' -ValidateSet 'md5', 'sha1', 'sha256', 'sha384', 'sha512' - -$result = @{ - files = @() - examined = 0 - matched = 0 - changed = $false +#AnsibleRequires -CSharpUtil Ansible.Basic +#Requires -Module Ansible.ModuleUtils.LinkUtil + +$spec = @{ + options = @{ + paths = @{ type = "list"; elements = "str"; required = $true } + age = @{ type = "str" } + age_stamp = @{ type = "str"; default = "mtime"; choices = "mtime", "ctime", "atime" } + file_type = @{ type = "str"; default = "file"; choices = "file", "directory" } + follow = @{ type = "bool"; default = $false } + hidden = @{ type = "bool"; default = $false } + patterns = @{ type = "list"; elements = "str"; aliases = "regex", "regexp" } + recurse = @{ type = "bool"; default = $false } + size = @{ type = "str" } + use_regex = @{ type = "bool"; default = $false } + get_checksum = @{ type = "bool"; default = $true } + checksum_algorithm = @{ type = "str"; default = "sha1"; choices = "md5", "sha1", "sha256", "sha384", "sha512" } + } + 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(); - } +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + +$paths = $module.Params.paths +$age = $module.Params.age +$age_stamp = $module.Params.age_stamp +$file_type = $module.Params.file_type +$follow = $module.Params.follow +$hidden = $module.Params.hidden +$patterns = $module.Params.patterns +$recurse = $module.Params.recurse +$size = $module.Params.size +$use_regex = $module.Params.use_regex +$get_checksum = $module.Params.get_checksum +$checksum_algorithm = $module.Params.checksum_algorithm + +$module.Result.examined = 0 +$module.Result.files = @() +$module.Result.matched = 0 + +Load-LinkUtils + +Function Assert-Age { + Param ( + [System.IO.FileSystemInfo]$File, + [System.Int64]$Age, + [System.String]$AgeStamp + ) + + $actual_age = switch ($AgeStamp) { + mtime { $File.LastWriteTime.Ticks } + ctime { $File.CreationTime.Ticks } + atime { $File.LastAccessTime.Ticks } + } + + if ($Age -ge 0) { + return $Age -ge $actual_age + } else { + return ($Age * -1) -le $actual_age } } -"@ -$original_tmp = $env:TMP -$env:TMP = $_remote_tmp -Add-Type -TypeDefinition $symlink_util -$env:TMP = $original_tmp - -Function Assert-Age($info) { - $valid_match = $true - - if ($null -ne $age) { - $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 ($null -eq $matches[2]) { - $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) - } +Function Assert-FileType { + Param ( + [System.IO.FileSystemInfo]$File, + [System.String]$FileType + ) - if ($specified_seconds -ge 0) { - $start_date = (Get-Date).AddSeconds($abs_seconds * -1) - if ($age_comparison -gt $start_date) { - $valid_match = $false - } - } else { - $start_date = (Get-Date).AddSeconds($abs_seconds) - if ($age_comparison -lt $start_date) { - $valid_match = $false - } + $is_dir = $File.Attributes.HasFlag([System.IO.FileAttributes]::Directory) + return ($FileType -eq 'directory' -and $is_dir) -or ($FileType -eq 'file' -and -not $is_dir) +} + +Function Assert-FileHidden { + Param ( + [System.IO.FileSystemInfo]$File, + [Switch]$IsHidden + ) + + $file_is_hidden = $File.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) + return $IsHidden.IsPresent -eq $file_is_hidden +} + + +Function Assert-FileNamePattern { + Param ( + [System.IO.FileSystemInfo]$File, + [System.String[]]$Patterns, + [Switch]$UseRegex + ) + + $valid_match = $false + foreach ($pattern in $Patterns) { + if ($UseRegex) { + if ($File.Name -match $pattern) { + $valid_match = $true + break } } else { - throw "failed to process age for file $($info.FullName)" + if ($File.Name -like $pattern) { + $valid_match = $true + break + } } } - - $valid_match + return $valid_match } -Function Assert-FileType($info) { - $valid_match = $true +Function Assert-FileSize { + Param ( + [System.IO.FileSystemInfo]$File, + [System.Int64]$Size + ) - 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 + if ($Size -ge 0) { + return $File.Length -ge $Size + } else { + return $File.Length -le ($Size * -1) } - - $valid_match } -Function Assert-Hidden($info) { - $valid_match = $true - - if ($hidden -eq $true -and $info.ishidden -eq $false) { - $valid_match = $false +Function Get-FileChecksum { + Param ( + [System.String]$Path, + [System.String]$Algorithm + ) + + $sp = switch ($algorithm) { + 'md5' { New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider } + 'sha1' { New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider } + 'sha256' { New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider } + 'sha384' { New-Object -TypeName System.Security.Cryptography.SHA384CryptoServiceProvider } + 'sha512' { New-Object -TypeName System.Security.Cryptography.SHA512CryptoServiceProvider } } - if ($hidden -eq $false -and $info.ishidden -eq $true) { - $valid_match = $false + + $fp = [System.IO.File]::Open($Path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) + try { + $hash = [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower() + } finally { + $fp.Dispose() } - $valid_match + return $hash } -Function Assert-Pattern($info) { - $valid_match = $false +Function Search-Path { + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [System.String] + $Path, - if ($null -ne $patterns) { - 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 + [Parameter(Mandatory=$true)] + [AllowEmptyCollection()] + [System.Collections.Generic.HashSet`1[System.String]] + $CheckedPaths, + + [Parameter(Mandatory=$true)] + [Object] + $Module, + + [System.Int64] + $Age, + + [System.String] + $AgeStamp, + + [System.String] + $FileType, + + [Switch] + $Follow, + + [Switch] + $GetChecksum, + + [Switch] + $IsHidden, + + [System.String[]] + $Patterns, + + [Switch] + $Recurse, + + [System.Int64] + $Size, + + [Switch] + $UseRegex + ) + + $dir_obj = New-Object -TypeName System.IO.DirectoryInfo -ArgumentList $Path + if ([Int32]$dir_obj.Attributes -eq -1) { + $Module.Warn("Argument path '$Path' does not exist, skipping") + return + } elseif (-not $dir_obj.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) { + $Module.Warn("Argument path '$Path' is a file not a directory, skipping") + return } - $valid_match -} + $dir_files = @() + try { + $dir_files = $dir_obj.EnumerateFileSystemInfos("*", [System.IO.SearchOption]::TopDirectoryOnly) + } catch [System.IO.DirectoryNotFoundException] { # Broken ReparsePoint/Symlink, cannot enumerate + } catch [System.UnauthorizedAccessException] {} # No ListDirectory permissions, Get-ChildItem ignored this -Function Assert-Size($info) { - $valid_match = $true - - if ($null -ne $size) { - $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) { - [int64]$specified_size = $matches[1] - if ($null -eq $matches[2]) { - $chosen_byte = 'b' - } else { - $chosen_byte = $matches[2] + foreach ($dir_child in $dir_files) { + if ($dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Directory) -and $Recurse) { + if ($Follow -or -not $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::ReparsePoint)) { + $PSBoundParameters.Remove('Path') > $null + Search-Path -Path $dir_child.FullName @PSBoundParameters } + } - $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 { - throw "failed to process size for file $($info.FullName)" + # Check to see if we've already encountered this path and skip if we have. + if (-not $CheckedPaths.Add($dir_child.FullName.ToLowerInvariant())) { + continue } - } - $valid_match -} + $Module.Result.examined++ -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 ($PSBoundParameters.ContainsKey('Age')) { + $age_match = Assert-Age -File $dir_child -Age $Age -AgeStamp $AgeStamp + } else { + $age_match = $true + } - if ($age_match -and $file_type_match -and $hidden_match -and $pattern_match -and $size_match) { - $info - } else { - $false - } -} + $file_type_match = Assert-FileType -File $dir_child -FileType $FileType + $hidden_match = Assert-FileHidden -File $dir_child -IsHidden:$IsHidden -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() - } + if ($PSBoundParameters.ContainsKey('Patterns')) { + $pattern_match = Assert-FileNamePattern -File $dir_child -Patterns $Patterns -UseRegex:$UseRegex.IsPresent + } else { + $pattern_match = $true + } - $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 - } + if ($PSBoundParameters.ContainsKey('Size')) { + $size_match = Assert-FileSize -File $dir_child -Size $Size + } else { + $size_match = $true + } - $islnk = $false - $isdir = $attributes -contains 'Directory' - $isshared = $false + if (-not ($age_match -and $file_type_match -and $hidden_match -and $pattern_match -and $size_match)) { + continue + } - if ($attributes -contains 'ReparsePoint') { - # TODO: Find a way to differenciate between soft and junction links - $islnk = $true + # It passed all our filters so add it + $module.Result.matched++ + + # TODO: Make this generic so it can be shared with win_find and win_stat. + $epoch = New-Object -Type System.DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0 + $file_info = @{ + attributes = $dir_child.Attributes.ToString() + checksum = $null + creationtime = (New-TimeSpan -Start $epoch -End $dir_child.CreationTime).TotalSeconds + exists = $true + extension = $null + filename = $dir_child.Name + isarchive = $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Archive) + isdir = $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Directory) + ishidden = $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) + isreadonly = $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::ReadOnly) + isreg = $false + isshared = $false + lastaccesstime = (New-TimeSpan -Start $epoch -End $dir_child.LastAccessTime).TotalSeconds + lastwritetime = (New-TimeSpan -Start $epoch -End $dir_child.LastWriteTime).TotalSeconds + owner = $null + path = $dir_child.FullName + sharename = $null + size = $null + } - # 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-CIMInstance -Class Win32_Share -Filter "Path='$($file.Fullname -replace '\\', '\\')'" - if ($null -ne $share_info) { - $isshared = $true - $file_stat.sharename = $share_info.Name + $file_info.owner = $dir_child.GetAccessControl().Owner + } catch {} # May not have rights to get the Owner, historical behaviour is to ignore. + + if ($dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) { + $share_info = Get-CimInstance -ClassName Win32_Share -Filter "Path='$($dir_child.FullName -replace '\\', '\\')'" + if ($null -ne $share_info) { + $file_info.isshared = $true + $file_info.sharename = $share_info.Name + } + } else { + $file_info.extension = $dir_child.Extension + $file_info.isreg = $true + $file_info.size = $dir_child.Length + + if ($GetChecksum) { + try { + $file_info.checksum = Get-FileChecksum -Path $dir_child.FullName -Algorithm $checksum_algorithm + } catch {} # Just keep the checksum as $null in the case of a failure. + } } - # only get the size of a directory if there are files (not directories) inside the folder - # Get-ChildItem -LiteralPath does not work properly on older OS', use .NET instead - $dir_files = @() - try { - $dir_files = $file.EnumerateFiles("*", [System.IO.SearchOption]::AllDirectories) - } catch [System.IO.DirectoryNotFoundException] { # Broken ReparsePoint/Symlink, cannot enumerate - } catch [System.UnauthorizedAccessException] {} # No ListDirectory permissions, Get-ChildItem ignored this - $size = 0 - foreach ($dir_file in $dir_files) { - $size += $dir_file.Length + # Append the link information if the path is a link + $link_info = @{ + isjunction = $false + islnk = $false + nlink = 1 + lnk_source = $null + lnk_target = $null + hlnk_targets = @() } - $file_stat.size = $size - } else { - $file_stat.size = $file.length - $file_stat.extension = $file.Extension - - if ($get_checksum) { - try { - $checksum = Get-FileChecksum -path $path -algorithm $checksum_algorithm - $file_stat.checksum = $checksum - } catch { - throw "failed to get checksum for file $($file.FullName)" + $link_stat = Get-Link -link_path $dir_child.FullName + if ($null -ne $link_stat) { + switch ($link_stat.Type) { + "SymbolicLink" { + $link_info.islnk = $true + $link_info.isreg = $false + $link_info.lnk_source = $link_stat.AbsolutePath + $link_info.lnk_target = $link_stat.TargetPath + break + } + "JunctionPoint" { + $link_info.isjunction = $true + $link_info.isreg = $false + $link_info.lnk_source = $link_stat.AbsolutePath + $link_info.lnk_target = $link_stat.TargetPath + break + } + "HardLink" { + $link_info.nlink = $link_stat.HardTargets.Count + + # remove current path from the targets + $hlnk_targets = $link_info.HardTargets | Where-Object { $_ -ne $dir_child.FullName } + $link_info.hlnk_targets = @($hlnk_targets) + break + } } } - } + foreach ($kv in $link_info.GetEnumerator()) { + $file_info.$($kv.Key) = $kv.Value + } - $file_stat.islnk = $islnk - $file_stat.isdir = $isdir - $file_stat.isshared = $isshared + # Output the file_info object + $file_info + } +} - Assert-FileStat -info $file_stat +$search_params = @{ + CheckedPaths = [System.Collections.Generic.HashSet`1[System.String]]@() + GetChecksum = $get_checksum + Module = $module + FileType = $file_type + Follow = $follow + IsHidden = $hidden + Recurse = $recurse } -Function Get-FilesInFolder($path) { - $items = @() +if ($null -ne $age) { + $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) { + $specified_seconds = [Int64]$Matches[1] + if ($null -eq $Matches[2]) { + $chosen_unit = 's' + } else { + $chosen_unit = $Matches[2] + } - # Get-ChildItem -LiteralPath can bomb out on older OS', use .NET instead - $dir = New-Object -TypeName System.IO.DirectoryInfo -ArgumentList $path - $dir_files = @() - try { - $dir_files = $dir.EnumerateFileSystemInfos("*", [System.IO.SearchOption]::TopDirectoryOnly) - } catch [System.IO.DirectoryNotFoundException] { # Broken ReparsePoint/Symlink, cannot enumerate - } catch [System.UnauthorizedAccessException] {} # No ListDirectory permissions, Get-ChildItem ignored this - foreach ($item in $dir_files) { - if ($item -is [System.IO.DirectoryInfo] -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 - } + $total_seconds = $specified_seconds * ($seconds_per_unit.$chosen_unit) + + if ($total_seconds -ge 0) { + $search_params.Age = (Get-Date).AddSeconds($total_seconds * -1).Ticks } else { - $items += $item.FullName + # Make sure we add the positive value of seconds to current time then make it negative for later comparisons. + $age = (Get-Date).AddSeconds($total_seconds).Ticks + $search_params.Age = $age * -1 } + $search_params.AgeStamp = $age_stamp + } else { + $module.FailJson("Invalid age pattern specified") } +} - $items +if ($null -ne $patterns) { + $search_params.Patterns = $patterns + $search_params.UseRegex = $use_regex } -$paths_to_check = @() -foreach ($path in $paths) { - if (Test-Path -LiteralPath $path) { - if ((Get-Item -LiteralPath $path -Force).PSIsContainer) { - $paths_to_check += Get-FilesInFolder -path $path +if ($null -ne $size) { + $bytes_per_unit = @{'b'=1; 'k'=1KB; 'm'=1MB; 'g'=1GB;'t'=1TB} + $size_pattern = '^(-?\d+)(b|k|m|g|t)?$' + $match = $size -match $size_pattern + if ($Match) { + $specified_size = [Int64]$Matches[1] + if ($null -eq $Matches[2]) { + $chosen_byte = 'b' } else { - Fail-Json $result "Argument path $path is a file not a directory" + $chosen_byte = $Matches[2] } + + $search_params.Size = $specified_size * ($bytes_per_unit.$chosen_byte) } else { - Fail-Json $result "Argument path $path does not exist cannot get information on" + $module.FailJson("Invalid size pattern specified") } } -$paths_to_check = $paths_to_check | Select-Object -Unique | Sort-Object -foreach ($path in $paths_to_check) { - try { - $file = Get-Item -LiteralPath $path -Force - $info = Get-FileStat -file $file - } catch { - Add-Warning -obj $result -message "win_find failed to check some files, these files were ignored and will not be part of the result output" - break - } - - $new_examined = $result.examined + 1 - $result.examined = $new_examined +$matched_files = foreach ($path in $paths) { + # Ensure we pass in an absolute path. We use the ExecutionContext as this is based on the PSProvider path not the + # process location which can be different. + $abs_path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) + Search-Path -Path $abs_path @search_params +} - if ($info -ne $false) { - $files = $result.Files - $files += $info +# Make sure we sort the files in alphabetical order. +$module.Result.files = @() + ($matched_files | Sort-Object -Property {$_.path}) - $new_matched = $result.matched + 1 - $result.matched = $new_matched - $result.files = $files - } -} +$module.ExitJson() -Exit-Json $result diff --git a/lib/ansible/modules/windows/win_find.py b/lib/ansible/modules/windows/win_find.py index ab02fafc089..f506f956f29 100644 --- a/lib/ansible/modules/windows/win_find.py +++ b/lib/ansible/modules/windows/win_find.py @@ -235,6 +235,11 @@ files: returned: success, path exists type: float sample: 1477984205.15 + exists: + description: Whether the file exists, will always be true for M(win_find). + returned: success, path exists + type: bool + sample: true extension: description: The extension of the file at path. returned: success, path exists, path is a file @@ -245,6 +250,13 @@ files: returned: success, path exists type: str sample: temp + hlnk_targets: + description: List of other files pointing to the same file (hard links), excludes the current file. + returned: success, path exists + type: list + sample: + - C:\temp\file.txt + - C:\Windows\update.log isarchive: description: If the path is ready for archiving or not. returned: success, path exists @@ -260,9 +272,14 @@ files: returned: success, path exists type: bool sample: true + isjunction: + description: If the path is a junction point. + returned: success, path exists + type: bool + sample: true islnk: - description: If the path is a symbolic link or junction or not. - returned: success, path exists or deduped files + description: If the path is a symbolic link. + returned: success, path exists type: bool sample: true isreadonly: @@ -270,6 +287,11 @@ files: returned: success, path exists type: bool sample: true + isreg: + description: If the path is a regular file or not. + returned: success, path exists + type: bool + sample: true isshared: description: If the path is shared or not. returned: success, path exists @@ -286,10 +308,20 @@ files: 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. - returned: success, path exists, path is a symbolic link + description: The target of the symlink normalized for the remote filesystem. + returned: success, path exists, path is a symbolic link or junction point type: str sample: C:\temp + lnk_target: + description: The target of the symlink. Note that relative paths remain relative, will return null if not a link. + returned: success, path exists, path is a symbolic link or junction point + type: str + sample: temp + nlink: + description: Number of links to the file (hard links) + returned: success, path exists + type: int + sample: 1 owner: description: The owner of the file. returned: success, path exists @@ -306,8 +338,8 @@ files: type: str sample: file-share size: - description: The size in bytes of a file or folder. - returned: success, path exists, path is not a link + description: The size in bytes of the file. + returned: success, path exists, path is a file type: int sample: 1024 ''' diff --git a/test/integration/targets/win_find/tasks/tests.yml b/test/integration/targets/win_find/tasks/tests.yml index 8c4a7ebacca..82fa879ced2 100644 --- a/test/integration/targets/win_find/tasks/tests.yml +++ b/test/integration/targets/win_find/tasks/tests.yml @@ -1,103 +1,139 @@ --- -- name: expect failure when not setting paths - win_find: - patterns: a - register: actual - failed_when: "actual.msg != 'Get-AnsibleParam: Missing required argument: paths'" - -- name: expect failure when setting paths to a file +- name: expected skip when paths to a file win_find: paths: "{{win_find_dir}}\\single\\large.ps1" register: actual - failed_when: actual.msg != 'Argument path ' + win_find_dir + '\\single\\large.ps1 is a file not a directory' -- name: expect failure when path is set to a non existent folder +- name: assert skip when path is set to a file + assert: + that: + - not actual is changed + - actual.examined == 0 + - actual.files == [] + - actual.matched == 0 + - actual.warnings == ["Argument path '" + win_find_dir + "\\single\\large.ps1' is a file not a directory, skipping"] + +- name: expect skip when path is set to a non existent folder win_find: paths: "{{win_find_dir}}\\thisisafakefolder" register: actual - failed_when: actual.msg != 'Argument path ' + win_find_dir + '\\thisisafakefolder does not exist cannot get information on' + +- name: assert skip when path is set to a non existent folder + assert: + that: + - not actual is changed + - actual.examined == 0 + - actual.files == [] + - actual.matched == 0 + - actual.warnings == ["Argument path '" + win_find_dir + "\\thisisafakefolder' does not exist, skipping"] - name: get files in single directory win_find: paths: "{{win_find_dir}}\\single" register: actual -- name: set expected value for files in a single directory - set_fact: - expected: - changed: False - examined: 5 - failed: False - files: - - { isarchive: True, - attributes: Archive, - checksum: f8d100cdcf0e6c1007db2f8dd0b7ee2884df89af, - creationtime: 1477984205, - extension: .ps1, - filename: large.ps1, - ishidden: False, - isdir: False, - islnk: 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, - islnk: 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, - islnk: 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, - islnk: 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 +- name: assert get files in single directory assert: - that: actual == expected + that: + - not actual is changed + - actual.examined == 5 + - actual.matched == 4 + - actual.files[0].attributes == 'Archive' + - actual.files[0].checksum == 'f8d100cdcf0e6c1007db2f8dd0b7ee2884df89af' + - actual.files[0].creationtime == 1477984205 + - actual.files[0].exists == True + - actual.files[0].extension == '.ps1' + - actual.files[0].filename == 'large.ps1' + - actual.files[0].hlnk_targets == [] + - actual.files[0].isarchive == True + - actual.files[0].isdir == False + - actual.files[0].ishidden == False + - actual.files[0].isjunction == False + - actual.files[0].islnk == False + - actual.files[0].isreadonly == False + - actual.files[0].isreg == True + - actual.files[0].isshared == False + - actual.files[0].lastaccesstime == 1477984205 + - actual.files[0].lastwritetime == 1477984205 + - actual.files[0].lnk_source == None + - actual.files[0].lnk_target == None + - actual.files[0].nlink == 1 + - actual.files[0].owner == 'BUILTIN\\Administrators' + - actual.files[0].path == win_find_dir + '\\single\\large.ps1' + - actual.files[0].sharename == None + - actual.files[0].size == 260002 + - actual.files[1].attributes == 'Archive' + - actual.files[1].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[1].creationtime == 1477984205 + - actual.files[1].exists == True + - actual.files[1].extension == '.log' + - actual.files[1].filename == 'out_20161101-091005.log' + - actual.files[1].hlnk_targets == [] + - actual.files[1].isarchive == True + - actual.files[1].isdir == False + - actual.files[1].ishidden == False + - actual.files[1].isjunction == False + - actual.files[1].islnk == False + - actual.files[1].isreadonly == False + - actual.files[1].isreg == True + - actual.files[1].isshared == False + - actual.files[1].lastaccesstime == 1477984205 + - actual.files[1].lastwritetime == 1477984205 + - actual.files[1].lnk_source == None + - actual.files[1].lnk_target == None + - actual.files[1].nlink == 1 + - actual.files[1].owner == 'BUILTIN\\Administrators' + - actual.files[1].path == win_find_dir + '\\single\\out_20161101-091005.log' + - actual.files[1].sharename == None + - actual.files[1].size == 14 + - actual.files[2].attributes == 'Archive' + - actual.files[2].checksum == '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8' + - actual.files[2].creationtime == 1477984205 + - actual.files[2].exists == True + - actual.files[2].extension == '.ps1' + - actual.files[2].filename == 'small.ps1' + - actual.files[2].hlnk_targets == [] + - actual.files[2].isarchive == True + - actual.files[2].isdir == False + - actual.files[2].ishidden == False + - actual.files[2].isjunction == False + - actual.files[2].islnk == False + - actual.files[2].isreadonly == False + - actual.files[2].isreg == True + - actual.files[2].isshared == False + - actual.files[2].lastaccesstime == 1477984205 + - actual.files[2].lastwritetime == 1477984205 + - actual.files[2].lnk_source == None + - actual.files[2].lnk_target == None + - actual.files[2].nlink == 1 + - actual.files[2].owner == 'BUILTIN\\Administrators' + - actual.files[2].path == win_find_dir + '\\single\\small.ps1' + - actual.files[2].sharename == None + - actual.files[2].size == 1 + - actual.files[3].attributes == 'Archive' + - actual.files[3].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[3].creationtime == 1477984205 + - actual.files[3].exists == True + - actual.files[3].extension == '.ps1' + - actual.files[3].filename == 'test.ps1' + - actual.files[3].hlnk_targets == [] + - actual.files[3].isarchive == True + - actual.files[3].isdir == False + - actual.files[3].ishidden == False + - actual.files[3].isjunction == False + - actual.files[3].islnk == False + - actual.files[3].isreadonly == False + - actual.files[3].isreg == True + - actual.files[3].isshared == False + - actual.files[3].lastaccesstime == 1477984205 + - actual.files[3].lastwritetime == 1477984205 + - actual.files[3].lnk_source == None + - actual.files[3].lnk_target == None + - actual.files[3].nlink == 1 + - actual.files[3].owner == 'BUILTIN\\Administrators' + - actual.files[3].path == win_find_dir + '\\single\\test.ps1' + - actual.files[3].sharename == None + - actual.files[3].size == 14 - name: find hidden files win_find: @@ -105,34 +141,36 @@ hidden: True register: actual -- name: set fact for hidden files - set_fact: - expected: - changed: False - examined: 11 - failed: False - files: - - { isarchive: True, - attributes: "Hidden, Archive", - checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3, - creationtime: 1477984205, - extension: .ps1, - filename: hidden.ps1, - ishidden: True, - isdir: False, - islnk: 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 +- name: assert get files in single directory assert: - that: actual == expected + that: + - not actual is changed + - actual.examined == 11 + - actual.matched == 1 + - actual.files[0].attributes == 'Hidden, Archive' + - actual.files[0].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[0].creationtime == 1477984205 + - actual.files[0].exists == True + - actual.files[0].extension == '.ps1' + - actual.files[0].filename == 'hidden.ps1' + - actual.files[0].hlnk_targets == [] + - actual.files[0].isarchive == True + - actual.files[0].isdir == False + - actual.files[0].ishidden == True + - actual.files[0].isjunction == False + - actual.files[0].islnk == False + - actual.files[0].isreadonly == False + - actual.files[0].isreg == True + - actual.files[0].isshared == False + - actual.files[0].lastaccesstime == 1477984205 + - actual.files[0].lastwritetime == 1477984205 + - actual.files[0].lnk_source == None + - actual.files[0].lnk_target == None + - actual.files[0].nlink == 1 + - actual.files[0].owner == 'BUILTIN\\Administrators' + - actual.files[0].path == win_find_dir + '\\single\\hidden.ps1' + - actual.files[0].sharename == None + - actual.files[0].size == 14 - name: find file based on pattern win_find: @@ -147,36 +185,37 @@ use_regex: True register: actual_regex -- name: set fact for pattern files - set_fact: - expected: - changed: False - examined: 5 - failed: False - files: - - { isarchive: True, - attributes: Archive, - checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3, - creationtime: 1477984205, - extension: .log, - filename: out_20161101-091005.log, - ishidden: False, - isdir: False, - islnk: 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 +- name: assert find file based on pattern assert: - that: - - actual_pattern == expected - - actual_regex == expected + that: + - not actual_pattern is changed + - actual_pattern.examined == 5 + - actual_pattern.matched == 1 + - actual_pattern.files[0].attributes == 'Archive' + - actual_pattern.files[0].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual_pattern.files[0].creationtime == 1477984205 + - actual_pattern.files[0].exists == True + - actual_pattern.files[0].extension == '.log' + - actual_pattern.files[0].filename == 'out_20161101-091005.log' + - actual_pattern.files[0].hlnk_targets == [] + - actual_pattern.files[0].isarchive == True + - actual_pattern.files[0].isdir == False + - actual_pattern.files[0].ishidden == False + - actual_pattern.files[0].isjunction == False + - actual_pattern.files[0].islnk == False + - actual_pattern.files[0].isreadonly == False + - actual_pattern.files[0].isreg == True + - actual_pattern.files[0].isshared == False + - actual_pattern.files[0].lastaccesstime == 1477984205 + - actual_pattern.files[0].lastwritetime == 1477984205 + - actual_pattern.files[0].lnk_source == None + - actual_pattern.files[0].lnk_target == None + - actual_pattern.files[0].nlink == 1 + - actual_pattern.files[0].owner == 'BUILTIN\\Administrators' + - actual_pattern.files[0].path == win_find_dir + '\\single\\out_20161101-091005.log' + - actual_pattern.files[0].sharename == None + - actual_pattern.files[0].size == 14 + - actual_pattern == actual_regex - name: find files with recurse set win_find: @@ -185,66 +224,84 @@ patterns: "*.ps1" register: actual -- name: set expected value for files in a nested directory - set_fact: - expected: - changed: False - examined: 8 - failed: False - files: - - { isarchive: True, - attributes: Archive, - checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3, - creationtime: 1477984205, - extension: .ps1, - filename: file.ps1, - ishidden: False, - isdir: False, - islnk: 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, - islnk: 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: test.ps1, - ishidden: False, - isdir: False, - islnk: 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 +- name: assert find files with recurse set assert: - that: actual == expected + that: + - not actual is changed + - actual.examined == 8 + - actual.matched == 3 + - actual.files[0].attributes == 'Archive' + - actual.files[0].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[0].creationtime == 1477984205 + - actual.files[0].exists == True + - actual.files[0].extension == '.ps1' + - actual.files[0].filename == 'file.ps1' + - actual.files[0].hlnk_targets == [] + - actual.files[0].isarchive == True + - actual.files[0].isdir == False + - actual.files[0].ishidden == False + - actual.files[0].isjunction == False + - actual.files[0].islnk == False + - actual.files[0].isreadonly == False + - actual.files[0].isreg == True + - actual.files[0].isshared == False + - actual.files[0].lastaccesstime == 1477984205 + - actual.files[0].lastwritetime == 1477984205 + - actual.files[0].lnk_source == None + - actual.files[0].lnk_target == None + - actual.files[0].nlink == 1 + - actual.files[0].owner == 'BUILTIN\\Administrators' + - actual.files[0].path == win_find_dir + '\\nested\\file.ps1' + - actual.files[0].sharename == None + - actual.files[0].size == 14 + - actual.files[1].attributes == 'Archive' + - actual.files[1].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[1].creationtime == 1477984205 + - actual.files[1].exists == True + - actual.files[1].extension == '.ps1' + - actual.files[1].filename == 'test.ps1' + - actual.files[1].hlnk_targets == [] + - actual.files[1].isarchive == True + - actual.files[1].isdir == False + - actual.files[1].ishidden == False + - actual.files[1].isjunction == False + - actual.files[1].islnk == False + - actual.files[1].isreadonly == False + - actual.files[1].isreg == True + - actual.files[1].isshared == False + - actual.files[1].lastaccesstime == 1477984205 + - actual.files[1].lastwritetime == 1477984205 + - actual.files[1].lnk_source == None + - actual.files[1].lnk_target == None + - actual.files[1].nlink == 1 + - actual.files[1].owner == 'BUILTIN\\Administrators' + - actual.files[1].path == win_find_dir + '\\nested\\sub-nest\\test.ps1' + - actual.files[1].sharename == None + - actual.files[1].size == 14 + - actual.files[2].attributes == 'Archive' + - actual.files[2].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[2].creationtime == 1477984205 + - actual.files[2].exists == True + - actual.files[2].extension == '.ps1' + - actual.files[2].filename == 'test.ps1' + - actual.files[2].hlnk_targets == [] + - actual.files[2].isarchive == True + - actual.files[2].isdir == False + - actual.files[2].ishidden == False + - actual.files[2].isjunction == False + - actual.files[2].islnk == False + - actual.files[2].isreadonly == False + - actual.files[2].isreg == True + - actual.files[2].isshared == False + - actual.files[2].lastaccesstime == 1477984205 + - actual.files[2].lastwritetime == 1477984205 + - actual.files[2].lnk_source == None + - actual.files[2].lnk_target == None + - actual.files[2].nlink == 1 + - actual.files[2].owner == 'BUILTIN\\Administrators' + - actual.files[2].path == win_find_dir + '\\nested\\test.ps1' + - actual.files[2].sharename == None + - actual.files[2].size == 14 - name: find files with recurse set and follow links win_find: @@ -254,82 +311,108 @@ patterns: "*.ps1" register: actual -- name: set expected value for files in a nested directory while following links - set_fact: - expected: - changed: False - examined: 10 - failed: False - files: - - { isarchive: True, - attributes: Archive, - checksum: 8df33cee3325596517df5bb5aa980cf9c5c1fda3, - creationtime: 1477984205, - extension: .ps1, - filename: file.ps1, - ishidden: False, - isdir: False, - islnk: 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: link.ps1, - ishidden: False, - isdir: False, - islnk: 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, - islnk: 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: test.ps1, - ishidden: False, - isdir: False, - islnk: 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 +- name: assert find files with recurse set and follow links assert: - that: actual == expected + that: + - not actual is changed + - actual.examined == 10 + - actual.matched == 4 + - actual.files[0].attributes == 'Archive' + - actual.files[0].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[0].creationtime == 1477984205 + - actual.files[0].exists == True + - actual.files[0].extension == '.ps1' + - actual.files[0].filename == 'file.ps1' + - actual.files[0].hlnk_targets == [] + - actual.files[0].isarchive == True + - actual.files[0].isdir == False + - actual.files[0].ishidden == False + - actual.files[0].isjunction == False + - actual.files[0].islnk == False + - actual.files[0].isreadonly == False + - actual.files[0].isreg == True + - actual.files[0].isshared == False + - actual.files[0].lastaccesstime == 1477984205 + - actual.files[0].lastwritetime == 1477984205 + - actual.files[0].lnk_source == None + - actual.files[0].lnk_target == None + - actual.files[0].nlink == 1 + - actual.files[0].owner == 'BUILTIN\\Administrators' + - actual.files[0].path == win_find_dir + '\\nested\\file.ps1' + - actual.files[0].sharename == None + - actual.files[0].size == 14 + - actual.files[1].attributes == 'Archive' + - actual.files[1].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[1].creationtime == 1477984205 + - actual.files[1].exists == True + - actual.files[1].extension == '.ps1' + - actual.files[1].filename == 'link.ps1' + - actual.files[1].hlnk_targets == [] + - actual.files[1].isarchive == True + - actual.files[1].isdir == False + - actual.files[1].ishidden == False + - actual.files[1].isjunction == False + - actual.files[1].islnk == False + - actual.files[1].isreadonly == False + - actual.files[1].isreg == True + - actual.files[1].isshared == False + - actual.files[1].lastaccesstime == 1477984205 + - actual.files[1].lastwritetime == 1477984205 + - actual.files[1].lnk_source == None + - actual.files[1].lnk_target == None + - actual.files[1].nlink == 1 + - actual.files[1].owner == 'BUILTIN\\Administrators' + - actual.files[1].path == win_find_dir + '\\nested\\link\\link.ps1' + - actual.files[1].sharename == None + - actual.files[1].size == 14 + - actual.files[2].attributes == 'Archive' + - actual.files[2].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[2].creationtime == 1477984205 + - actual.files[2].exists == True + - actual.files[2].extension == '.ps1' + - actual.files[2].filename == 'test.ps1' + - actual.files[2].hlnk_targets == [] + - actual.files[2].isarchive == True + - actual.files[2].isdir == False + - actual.files[2].ishidden == False + - actual.files[2].isjunction == False + - actual.files[2].islnk == False + - actual.files[2].isreadonly == False + - actual.files[2].isreg == True + - actual.files[2].isshared == False + - actual.files[2].lastaccesstime == 1477984205 + - actual.files[2].lastwritetime == 1477984205 + - actual.files[2].lnk_source == None + - actual.files[2].lnk_target == None + - actual.files[2].nlink == 1 + - actual.files[2].owner == 'BUILTIN\\Administrators' + - actual.files[2].path == win_find_dir + '\\nested\\sub-nest\\test.ps1' + - actual.files[2].sharename == None + - actual.files[2].size == 14 + - actual.files[3].attributes == 'Archive' + - actual.files[3].checksum == '8df33cee3325596517df5bb5aa980cf9c5c1fda3' + - actual.files[3].creationtime == 1477984205 + - actual.files[3].exists == True + - actual.files[3].extension == '.ps1' + - actual.files[3].filename == 'test.ps1' + - actual.files[3].hlnk_targets == [] + - actual.files[3].isarchive == True + - actual.files[3].isdir == False + - actual.files[3].ishidden == False + - actual.files[3].isjunction == False + - actual.files[3].islnk == False + - actual.files[3].isreadonly == False + - actual.files[3].isreg == True + - actual.files[3].isshared == False + - actual.files[3].lastaccesstime == 1477984205 + - actual.files[3].lastwritetime == 1477984205 + - actual.files[3].lnk_source == None + - actual.files[3].lnk_target == None + - actual.files[3].nlink == 1 + - actual.files[3].owner == 'BUILTIN\\Administrators' + - actual.files[3].path == win_find_dir + '\\nested\\test.ps1' + - actual.files[3].sharename == None + - actual.files[3].size == 14 - name: find directories win_find: @@ -337,32 +420,36 @@ file_type: directory register: actual -- name: set expected fact for directories with recurse and follow - set_fact: - expected: - changed: False - examined: 2 - failed: False - files: - - { isarchive: False, - attributes: Directory, - creationtime: 1477984205, - filename: sub-link, - ishidden: False, - isdir: True, - islnk: 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 +- name: assert find directories assert: - that: actual == expected + that: + - not actual is changed + - actual.examined == 2 + - actual.matched == 1 + - actual.files[0].attributes == 'Directory' + - actual.files[0].checksum == None + - actual.files[0].creationtime == 1477984205 + - actual.files[0].exists == True + - actual.files[0].extension == None + - actual.files[0].filename == 'sub-link' + - actual.files[0].hlnk_targets == [] + - actual.files[0].isarchive == False + - actual.files[0].isdir == True + - actual.files[0].ishidden == False + - actual.files[0].isjunction == False + - actual.files[0].islnk == False + - actual.files[0].isreadonly == False + - actual.files[0].isreg == False + - actual.files[0].isshared == False + - actual.files[0].lastaccesstime == 1477984205 + - actual.files[0].lastwritetime == 1477984205 + - actual.files[0].lnk_source == None + - actual.files[0].lnk_target == None + - actual.files[0].nlink == 1 + - actual.files[0].owner == 'BUILTIN\\Administrators' + - actual.files[0].path == win_find_dir + '\\link-dest\\sub-link' + - actual.files[0].sharename == None + - actual.files[0].size == None - name: find directories recurse and follow with a broken link win_find: @@ -375,12 +462,15 @@ - name: check directory count with recurse and follow is correct assert: that: + - not actual is changed - actual.examined == 37 - actual.matched == 17 - actual.files[0].filename == 'broken-link' + - actual.files[0].isjunction == False - actual.files[0].islnk == True - actual.files[6].filename == 'junction-link' - - actual.files[6].islnk == True + - actual.files[6].isjunction == True + - actual.files[6].islnk == False - actual.files[6].lnk_source == win_find_dir + '\\junction-link-dest' - actual.files[11].filename == 'link' - actual.files[11].islnk == True @@ -402,36 +492,37 @@ size: 253k register: actual_with_byte -- name: set expected fact for files by size - set_fact: - expected: - changed: False - examined: 5 - failed: False - files: - - { isarchive: True, - attributes: Archive, - checksum: f8d100cdcf0e6c1007db2f8dd0b7ee2884df89af, - creationtime: 1477984205, - extension: ".ps1", - filename: large.ps1, - ishidden: False, - isdir: False, - islnk: 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 +- name: assert filter files by size assert: - that: - - actual_without_byte == expected - - actual_with_byte == expected + that: + - not actual_without_byte is changed + - actual_without_byte.examined == 5 + - actual_without_byte.matched == 1 + - actual_without_byte.files[0].attributes == 'Archive' + - actual_without_byte.files[0].checksum == 'f8d100cdcf0e6c1007db2f8dd0b7ee2884df89af' + - actual_without_byte.files[0].creationtime == 1477984205 + - actual_without_byte.files[0].exists == True + - actual_without_byte.files[0].extension == '.ps1' + - actual_without_byte.files[0].filename == 'large.ps1' + - actual_without_byte.files[0].hlnk_targets == [] + - actual_without_byte.files[0].isarchive == True + - actual_without_byte.files[0].isdir == False + - actual_without_byte.files[0].ishidden == False + - actual_without_byte.files[0].isjunction == False + - actual_without_byte.files[0].islnk == False + - actual_without_byte.files[0].isreadonly == False + - actual_without_byte.files[0].isreg == True + - actual_without_byte.files[0].isshared == False + - actual_without_byte.files[0].lastaccesstime == 1477984205 + - actual_without_byte.files[0].lastwritetime == 1477984205 + - actual_without_byte.files[0].lnk_source == None + - actual_without_byte.files[0].lnk_target == None + - actual_without_byte.files[0].nlink == 1 + - actual_without_byte.files[0].owner == 'BUILTIN\\Administrators' + - actual_without_byte.files[0].path == win_find_dir + '\\single\\large.ps1' + - actual_without_byte.files[0].sharename == None + - actual_without_byte.files[0].size == 260002 + - actual_without_byte == actual_with_byte - name: filter files by size (less than) without byte specified win_find: @@ -445,36 +536,37 @@ size: -4b register: actual_with_byte -- name: set expected fact for files by size (less than) - set_fact: - expected: - changed: False - examined: 5 - failed: False - files: - - { isarchive: True, - attributes: Archive, - checksum: 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8, - creationtime: 1477984205, - extension: ".ps1", - filename: small.ps1, - ishidden: False, - isdir: False, - islnk: 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 +- name: assert filter files by size (less than) without byte specified assert: - that: - - actual_without_byte == expected - - actual_with_byte == expected + that: + - not actual_without_byte is changed + - actual_without_byte.examined == 5 + - actual_without_byte.matched == 1 + - actual_without_byte.files[0].attributes == 'Archive' + - actual_without_byte.files[0].checksum == '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8' + - actual_without_byte.files[0].creationtime == 1477984205 + - actual_without_byte.files[0].exists == True + - actual_without_byte.files[0].extension == '.ps1' + - actual_without_byte.files[0].filename == 'small.ps1' + - actual_without_byte.files[0].hlnk_targets == [] + - actual_without_byte.files[0].isarchive == True + - actual_without_byte.files[0].isdir == False + - actual_without_byte.files[0].ishidden == False + - actual_without_byte.files[0].isjunction == False + - actual_without_byte.files[0].islnk == False + - actual_without_byte.files[0].isreadonly == False + - actual_without_byte.files[0].isreg == True + - actual_without_byte.files[0].isshared == False + - actual_without_byte.files[0].lastaccesstime == 1477984205 + - actual_without_byte.files[0].lastwritetime == 1477984205 + - actual_without_byte.files[0].lnk_source == None + - actual_without_byte.files[0].lnk_target == None + - actual_without_byte.files[0].nlink == 1 + - actual_without_byte.files[0].owner == 'BUILTIN\\Administrators' + - actual_without_byte.files[0].path == win_find_dir + '\\single\\small.ps1' + - actual_without_byte.files[0].sharename == None + - actual_without_byte.files[0].size == 1 + - actual_without_byte == actual_with_byte # 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 @@ -528,7 +620,7 @@ assert: that: - actual_md5_checksum.files[0].checksum == 'd1713d0f1d2e8fae230328d8fd59de01' - + - name: get list of files with sha1 checksum win_find: paths: "{{win_find_dir}}\\single" @@ -587,7 +679,7 @@ - name: assert no checksum is returned assert: that: - - actual_no_checksum.files[0].checksum is undefined + - actual_no_checksum.files[0].checksum == None # https://github.com/ansible/ansible/issues/26158 - name: get list of files in an empty nested directory @@ -656,7 +748,8 @@ - name: assert win_find only examined 2 files with under-privileged account assert: that: - - secure_result.examined == 2 - - secure_result.matched == 2 + - secure_result.examined == 3 + - secure_result.matched == 3 - secure_result.files[0].path == win_find_dir + "\secure-tests\open" - secure_result.files[1].path == win_find_dir + "\secure-tests\open\internal-folder" + - secure_result.files[2].path == win_find_dir + "\secure-tests\secure"