diff --git a/changelogs/fragments/win_find-paths.yaml b/changelogs/fragments/win_find-paths.yaml new file mode 100644 index 0000000000..a113a72ed1 --- /dev/null +++ b/changelogs/fragments/win_find-paths.yaml @@ -0,0 +1,3 @@ +bugfixes: +- win_find - Fix issues when using paths with glob like characters, e.g. ``[``, ``]`` +- win_find - Ensure found files are sorted alphabetically by the path instead of it being random diff --git a/lib/ansible/modules/windows/win_find.ps1 b/lib/ansible/modules/windows/win_find.ps1 index f2095e09bf..da8c62842d 100644 --- a/lib/ansible/modules/windows/win_find.ps1 +++ b/lib/ansible/modules/windows/win_find.ps1 @@ -78,13 +78,13 @@ $env:TMP = $original_tmp Function Assert-Age($info) { $valid_match = $true - if ($age -ne $null) { + 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 ($matches[2] -eq $null) { + if ($null -eq $matches[2]) { $chosen_unit = 's' } else { $chosen_unit = $matches[2] @@ -148,7 +148,7 @@ Function Assert-Hidden($info) { Function Assert-Pattern($info) { $valid_match = $false - if ($patterns -ne $null) { + if ($null -ne $patterns) { foreach ($pattern in $patterns) { if ($use_regex -eq $true) { # Use -match for regex matching @@ -172,13 +172,13 @@ Function Assert-Pattern($info) { Function Assert-Size($info) { $valid_match = $true - if ($size -ne $null) { + 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) { [int]$specified_size = $matches[1] - if ($matches[2] -eq $null) { + if ($null -eq $matches[2]) { $chosen_byte = 'b' } else { $chosen_byte = $matches[2] @@ -255,19 +255,24 @@ Function Get-FileStat($file) { $isdir = $true $share_info = Get-WmiObject -Class Win32_Share -Filter "Path='$($file.Fullname -replace '\\', '\\')'" - if ($share_info -ne $null) { + if ($null -ne $share_info) { $isshared = $true $file_stat.sharename = $share_info.Name } # only get the size of a directory if there are files (not directories) inside the folder - $dir_files_sum = Get-ChildItem $file.FullName -Recurse | Where-Object { -not $_.PSIsContainer } + # 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 - 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 + $size = 0 + foreach ($dir_file in $dir_files) { + $size += $dir_file.Length } + $file_stat.size = $size } else { $file_stat.size = $file.length $file_stat.extension = $file.Extension @@ -291,8 +296,17 @@ Function Get-FileStat($file) { Function Get-FilesInFolder($path) { $items = @() - foreach ($item in (Get-ChildItem -Force -Path $path -ErrorAction SilentlyContinue)) { - if ($item.PSIsContainer -and $recurse) { + + # 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 @@ -311,8 +325,8 @@ Function Get-FilesInFolder($path) { $paths_to_check = @() foreach ($path in $paths) { - if (Test-Path $path) { - if ((Get-Item -Force $path).PSIsContainer) { + if (Test-Path -LiteralPath $path) { + if ((Get-Item -LiteralPath $path -Force).PSIsContainer) { $paths_to_check += Get-FilesInFolder -path $path } else { Fail-Json $result "Argument path $path is a file not a directory" @@ -321,11 +335,11 @@ foreach ($path in $paths) { Fail-Json $result "Argument path $path does not exist cannot get information on" } } -$paths_to_check = $paths_to_check | Select-Object -Unique +$paths_to_check = $paths_to_check | Select-Object -Unique | Sort-Object foreach ($path in $paths_to_check) { try { - $file = Get-Item -Force -Path $path + $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" diff --git a/lib/ansible/modules/windows/win_find.py b/lib/ansible/modules/windows/win_find.py index 5954891ff4..31ffb799b6 100644 --- a/lib/ansible/modules/windows/win_find.py +++ b/lib/ansible/modules/windows/win_find.py @@ -214,7 +214,8 @@ matched: 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. + description: Information on the files/folders that match the criteria returned as a list of dictionary elements + for each file matched. The entries are sorted by the path value alphabetically. returned: success type: complex contains: diff --git a/test/integration/targets/win_find/defaults/main.yml b/test/integration/targets/win_find/defaults/main.yml index d04ec5f422..fc2e2e1179 100644 --- a/test/integration/targets/win_find/defaults/main.yml +++ b/test/integration/targets/win_find/defaults/main.yml @@ -1,2 +1,2 @@ -win_find_dir: "{{win_output_dir}}\\win_find" +win_find_dir: '{{ win_output_dir }}\win_find .ÅÑŚÌβŁÈ [$!@^&test(;)]' test_win_find_username: testuser diff --git a/test/integration/targets/win_find/tasks/main.yml b/test/integration/targets/win_find/tasks/main.yml index a44d5217c3..c2f40a8fde 100644 --- a/test/integration/targets/win_find/tasks/main.yml +++ b/test/integration/targets/win_find/tasks/main.yml @@ -22,8 +22,10 @@ "emptynested\nest\dir1", "emptynested\nest\dir2" ) + + $tmp_dir = '{{ win_find_dir }}' foreach ($directory in $directories) { - New-Item -Path "{{win_find_dir}}\$directory" -ItemType Directory + New-Item -Path "$tmp_dir\$directory" -ItemType Directory } $normal_content = "abcdefg1234567" @@ -42,40 +44,42 @@ "hard-link-dest\file-abc.log" ) foreach ($file in $normal_files) { - New-Item -Path "{{win_find_dir}}\$file" -ItemType File - [System.IO.File]::WriteAllText("{{win_find_dir}}\$file", $normal_content) + New-Item -Path "$tmp_dir\$file" -ItemType File + [System.IO.File]::WriteAllText("$tmp_dir\$file", $normal_content) } - New-Item -Path "{{win_find_dir}}\single\small.ps1" -ItemType File - [System.IO.File]::WriteAllText("{{win_find_dir}}\single\small.ps1", "a") + New-Item -Path "$tmp_dir\single\small.ps1" -ItemType File + [System.IO.File]::WriteAllText("$tmp_dir\single\small.ps1", "a") - New-Item -Path "{{win_find_dir}}\date\new.ps1" -ItemType File - [System.IO.File]::WriteAllText("{{win_find_dir}}\date\new.ps1", "random text for new date") + New-Item -Path "$tmp_dir\date\new.ps1" -ItemType File + [System.IO.File]::WriteAllText("$tmp_dir\date\new.ps1", "random text for new date") - New-Item -Path "{{win_find_dir}}\date\old.ps1" -ItemType File - [System.IO.File]::WriteAllText("{{win_find_dir}}\date\old.ps1", "random text for old date") + New-Item -Path "$tmp_dir\date\old.ps1" -ItemType File + [System.IO.File]::WriteAllText("$tmp_dir\date\old.ps1", "random text for old date") - New-Item -Path "{{win_find_dir}}\single\large.ps1" -ItemType File - Set-Content -Path "{{win_find_dir}}\single\large.ps1" -Value ('abcdefghijklmnopqrstuvwxyz' * 10000) + New-Item -Path "$tmp_dir\single\large.ps1" -ItemType File + Set-Content -LiteralPath "$tmp_dir\single\large.ps1" -Value ('abcdefghijklmnopqrstuvwxyz' * 10000) $share_stat = Get-WmiObject -Class Win32_Share -Filter "name='folder-share'" if ($share_stat) { $share_stat.Delete() } $wmi = [wmiClass] 'Win32_Share' - $wmi.Create("{{win_find_dir}}\shared\folder", "folder-share", 0) + $wmi.Create("$tmp_dir\shared\folder", "folder-share", 0) + + cmd.exe /c mklink /D "$tmp_dir\nested\link" "$tmp_dir\link-dest" + cmd.exe /c mklink /D "$tmp_dir\broken-link" "$tmp_dir\broken-link-dest" + cmd.exe /c mklink /H "$tmp_dir\hard-link-dest\hard-link.log" "$tmp_dir\hard-link-dest\file-abc.log" + cmd.exe /c mklink /J "$tmp_dir\junction-link" "$tmp_dir\junction-link-dest" - cmd.exe /c mklink /D "{{win_find_dir}}\nested\link" "{{win_find_dir}}\link-dest" - cmd.exe /c mklink /D "{{win_find_dir}}\broken-link" "{{win_find_dir}}\broken-link-dest" - cmd.exe /c mklink /H "{{win_find_dir}}\hard-link-dest\hard-link.log" "{{win_find_dir}}\hard-link-dest\file-abc.log" - cmd.exe /c mklink /J "{{win_find_dir}}\junction-link" "{{win_find_dir}}\junction-link-dest" - $date = Get-Date -Year 2016 -Month 11 -Day 1 -Hour 7 -Minute 10 -Second 5 -Millisecond 0 - Get-ChildItem -Path "{{win_find_dir}}" -Recurse | Where-Object { $_.Name -ne "new.ps1" } | ForEach-Object { + Set-Location -LiteralPath $tmp_dir + Get-ChildItem -Recurse | Where-Object { $_.Name -ne "new.ps1" } | ForEach-Object { $_.CreationTime = $date $_.LastAccessTime = $date $_.LastWriteTime = $date } + Pop-Location $attributes = @{ "hidden" = "Hidden" @@ -85,7 +89,7 @@ "single\hidden.ps1" = "Hidden" } foreach ($attribute in $attributes.GetEnumerator()) { - $item = Get-Item -Path "{{win_find_dir}}\$($attribute.Name)" + $item = Get-Item -LiteralPath "$tmp_dir\$($attribute.Name)" $file_attributes = $item.Attributes -split ',' if ($file_attributes -notcontains $attribute.Value) { $file_attributes += $attribute.Value @@ -93,7 +97,7 @@ $item.Attributes = $file_attributes -join ',' } - Remove-Item -Path "{{win_find_dir}}\broken-link-dest" -Force + Remove-Item -LiteralPath "$tmp_dir\broken-link-dest" -Force - block: - include_tasks: tests.yml diff --git a/test/integration/targets/win_find/tasks/tests.yml b/test/integration/targets/win_find/tasks/tests.yml index 0afe771946..8c4a7ebacc 100644 --- a/test/integration/targets/win_find/tasks/tests.yml +++ b/test/integration/targets/win_find/tasks/tests.yml @@ -7,19 +7,19 @@ - name: expect failure when setting paths to a file win_find: - paths: "{{win_output_dir}}\\win_find\\single\\large.ps1" + paths: "{{win_find_dir}}\\single\\large.ps1" register: actual - failed_when: actual.msg != 'Argument path ' + win_output_dir + '\\win_find\\single\\large.ps1 is a file not a directory' + 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 win_find: - paths: "{{win_output_dir}}\\win_find\\thisisafakefolder" + paths: "{{win_find_dir}}\\thisisafakefolder" register: actual - failed_when: actual.msg != 'Argument path ' + win_output_dir + '\\win_find\\thisisafakefolder does not exist cannot get information on' + failed_when: actual.msg != 'Argument path ' + win_find_dir + '\\thisisafakefolder does not exist cannot get information on' - name: get files in single directory win_find: - paths: "{{win_output_dir}}\\win_find\\single" + paths: "{{win_find_dir}}\\single" register: actual - name: set expected value for files in a single directory @@ -192,22 +192,6 @@ examined: 8 failed: False files: - - { 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, @@ -224,6 +208,22 @@ 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, @@ -261,6 +261,22 @@ 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, @@ -293,22 +309,6 @@ isreadonly: False, isshared: False, size: 14 } - - { 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,