diff --git a/.memory-bank/progress.md b/.memory-bank/progress.md index 4997c55..97e21f9 100644 --- a/.memory-bank/progress.md +++ b/.memory-bank/progress.md @@ -190,6 +190,7 @@ Numbered as agreed with the maintainer; each is documented on its page. rights, an exact match as it is, otherwise without `Synchronize`. - Maintainer decisions of 2026-10-05: #5 (`Get-ChildItem2 -Attributes` matches any listed attribute) and #82 (no `Size` alias) ship in 5.0.0 as - breaking changes. + breaking changes. Their review added that an empty `-Attributes` value is + an error, as in `Get-ChildItem`. - Open bugs: #34 and #67 (writes owner and group, rc3 if a file server is available), #41 (drive root) and #90 (trailing space), both after 5.0.0. diff --git a/CHANGELOG.md b/CHANGELOG.md index 718cb6a..4e73cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,8 +51,12 @@ The format is based on `Enable-NTFSAuditInheritance -RemoveExplicitAuditRules` - **Breaking:** `Get-ChildItem2 -Attributes` returns the items that have any of the listed attributes, like `Get-ChildItem`; it returned only the items - that had all of them. To get the old result, filter with `Where-Object`, - as the cmdlet page shows + that had all of them. A call that lists several attributes now returns + more items, including hidden and system items when those are in the + list, so review calls whose result is deleted or whose permissions are + changed. To get the old result, filter with `Where-Object`, as the cmdlet + page shows. An empty value, such as `0`, is now an error; it returned + every item, also the hidden ones ([#5](https://github.com/raandree/NTFSSecurity/issues/5)) - **Breaking:** remove the alias `Size` of `LengthOnDisk` from the files of `Get-ChildItem`, which made the import fail in Windows PowerShell when diff --git a/Docs/Cmdlets/Get-ChildItem2.md b/Docs/Cmdlets/Get-ChildItem2.md index 0c43b87..af729af 100644 --- a/Docs/Cmdlets/Get-ChildItem2.md +++ b/Docs/Cmdlets/Get-ChildItem2.md @@ -69,7 +69,7 @@ Uses the `dir2` alias and returns the items of `C:\Data` that have the hidden or ### -Attributes -Specifies a set of file attributes. Like `Get-ChildItem`, the cmdlet returns the items that have at least one of the attributes you list; separate several values with commas, as in `-Attributes Hidden, System`. To get only the items that have all of them, filter the result, for example with `Where-Object { ($_.Attributes -band [IO.FileAttributes]'Hidden, System') -eq [IO.FileAttributes]'Hidden, System' }`. When you use this parameter, the cmdlet ignores `-Force`, `-Hidden`, `-System`, and `-ReadOnly`, and it returns matching hidden items without `-Force`. +Specifies a set of file attributes. Like `Get-ChildItem`, the cmdlet returns the items that have at least one of the attributes you list; separate several values with commas, as in `-Attributes Hidden, System`. Unlike `Get-ChildItem`, the parameter takes only such a list, not the `+` and `!` operators; to get only the items that have all the attributes, filter the result, for example with `Where-Object { ($_.Attributes -band [IO.FileAttributes]'Hidden, System') -eq [IO.FileAttributes]'Hidden, System' }`. An empty value, such as `0`, stops the cmdlet with the error `AttributesEmpty`. When you use this parameter, the cmdlet ignores `-Force`, `-Hidden`, `-System`, and `-ReadOnly`, and it returns matching hidden items without `-Force`. The parameter restricts the returned items only; `-Recurse` still descends into every subfolder, including hidden ones. ```yaml Type: FileAttributes @@ -307,7 +307,7 @@ The `PrivateData` section of the module manifest `NTFSSecurity.psd1` contains tw A folder that cannot be read produces a non-terminating error with the ID `DirUnauthorizedAccessError` for an access denial or `DirUnspecifiedError` for any other failure, and a path that does not exist produces the error `FileNotFound`. In each case the cmdlet continues with the next path. Failures that occur while `-Recurse` collects the subfolders of a folder are reported as verbose messages only, not as errors. -Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`, and `-Attributes` returned only the items that had all the listed attributes. +Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`, `-Attributes` returned only the items that had all the listed attributes, and an empty `-Attributes` value returned every item, also the hidden ones. ## RELATED LINKS diff --git a/NTFSSecurity/ItemCmdlets/GetChildItem2.cs b/NTFSSecurity/ItemCmdlets/GetChildItem2.cs index 246df25..bb908de 100644 --- a/NTFSSecurity/ItemCmdlets/GetChildItem2.cs +++ b/NTFSSecurity/ItemCmdlets/GetChildItem2.cs @@ -132,6 +132,14 @@ namespace NTFSSecurity { base.BeginProcessing(); + // An empty value would match every item, also the hidden ones; Get-ChildItem rejects it as well. + if (MyInvocation.BoundParameters.ContainsKey("Attributes") && attributes == 0) + { + ThrowTerminatingError(new ErrorRecord( + new ArgumentException("Specify at least one file attribute for the Attributes parameter."), + "AttributesEmpty", ErrorCategory.InvalidArgument, attributes)); + } + if (paths.Count == 0) { paths = new List() { GetCurrentLocation() }; @@ -276,7 +284,7 @@ namespace NTFSSecurity if (MyInvocation.BoundParameters.ContainsKey("Attributes")) { // Like Get-ChildItem, an item matches when it has any of the listed attributes (#5). - if (attributes != 0 && (current.Attributes & attributes) == 0) + if ((current.Attributes & attributes) == 0) continue; writeItem = true; diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml index e8d1d7b..5cd0dd8 100644 --- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml +++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml @@ -3524,7 +3524,7 @@ PS C:\> Disable-Privileges Attributes - Specifies a set of file attributes. Like `Get-ChildItem`, the cmdlet returns the items that have at least one of the attributes you list; separate several values with commas, as in `-Attributes Hidden, System`. To get only the items that have all of them, filter the result, for example with `Where-Object { ($_.Attributes -band [IO.FileAttributes]'Hidden, System') -eq [IO.FileAttributes]'Hidden, System' }`. When you use this parameter, the cmdlet ignores `-Force`, `-Hidden`, `-System`, and `-ReadOnly`, and it returns matching hidden items without `-Force`. + Specifies a set of file attributes. Like `Get-ChildItem`, the cmdlet returns the items that have at least one of the attributes you list; separate several values with commas, as in `-Attributes Hidden, System`. Unlike `Get-ChildItem`, the parameter takes only such a list, not the `+` and `!` operators; to get only the items that have all the attributes, filter the result, for example with `Where-Object { ($_.Attributes -band [IO.FileAttributes]'Hidden, System') -eq [IO.FileAttributes]'Hidden, System' }`. An empty value, such as `0`, stops the cmdlet with the error `AttributesEmpty`. When you use this parameter, the cmdlet ignores `-Force`, `-Hidden`, `-System`, and `-ReadOnly`, and it returns matching hidden items without `-Force`. The parameter restricts the returned items only; `-Recurse` still descends into every subfolder, including hidden ones. ReadOnly @@ -3668,7 +3668,7 @@ PS C:\> Disable-Privileges Attributes - Specifies a set of file attributes. Like `Get-ChildItem`, the cmdlet returns the items that have at least one of the attributes you list; separate several values with commas, as in `-Attributes Hidden, System`. To get only the items that have all of them, filter the result, for example with `Where-Object { ($_.Attributes -band [IO.FileAttributes]'Hidden, System') -eq [IO.FileAttributes]'Hidden, System' }`. When you use this parameter, the cmdlet ignores `-Force`, `-Hidden`, `-System`, and `-ReadOnly`, and it returns matching hidden items without `-Force`. + Specifies a set of file attributes. Like `Get-ChildItem`, the cmdlet returns the items that have at least one of the attributes you list; separate several values with commas, as in `-Attributes Hidden, System`. Unlike `Get-ChildItem`, the parameter takes only such a list, not the `+` and `!` operators; to get only the items that have all the attributes, filter the result, for example with `Where-Object { ($_.Attributes -band [IO.FileAttributes]'Hidden, System') -eq [IO.FileAttributes]'Hidden, System' }`. An empty value, such as `0`, stops the cmdlet with the error `AttributesEmpty`. When you use this parameter, the cmdlet ignores `-Force`, `-Hidden`, `-System`, and `-ReadOnly`, and it returns matching hidden items without `-Force`. The parameter restricts the returned items only; `-Recurse` still descends into every subfolder, including hidden ones. FileAttributes @@ -3857,7 +3857,7 @@ PS C:\> Disable-Privileges The default table view shows the `Mode`, `Inherits`, `LastWriteTime`, `Size(M)`, and `Name` columns. `Inherits` is `False` for an item whose access inheritance is disabled. Reading that value costs one access to the ACL of each displayed item, which slows down the display of large listings; to avoid it, select the properties you need, for example with `Format-Table -Property Mode, LastWriteTime, Length, Name`. Objects that you pipe to another command are not affected. Before 5.0.0, the column showed `True` for every item. The `PrivateData` section of the module manifest `NTFSSecurity.psd1` contains two settings that this cmdlet reads when it starts. `GetFileSystemModeProperty` adds the calculated `Mode` property to every item. `IdentifyHardLinks` adds the `HardLinkCount` property to every file, which requires an extra call into the file system for each file and therefore slows down large listings noticeably. Set either value to `$false` in the manifest and import the module again if you prefer the faster enumeration over the additional properties. A folder that cannot be read produces a non-terminating error with the ID `DirUnauthorizedAccessError` for an access denial or `DirUnspecifiedError` for any other failure, and a path that does not exist produces the error `FileNotFound`. In each case the cmdlet continues with the next path. Failures that occur while `-Recurse` collects the subfolders of a folder are reported as verbose messages only, not as errors. - Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`, and `-Attributes` returned only the items that had all the listed attributes. + Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`, `-Attributes` returned only the items that had all the listed attributes, and an empty `-Attributes` value returned every item, also the hidden ones. diff --git a/Tests/ItemCmdlets.Tests.ps1 b/Tests/ItemCmdlets.Tests.ps1 index 1308cd8..4d181d6 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/Tests/ItemCmdlets.Tests.ps1 @@ -98,6 +98,11 @@ Describe 'Get-ChildItem2' { @($result.Name | Sort-Object) | Should -Be @('Hidden.txt', 'ReadOnly.txt') } + # Before 5.0.0, an empty value applied no filter and returned hidden items as well. + It 'Should reject an empty value' { + { Get-ChildItem2 -Path $attributeFolder -Attributes 0 -ErrorAction Stop } | Should -Throw -ErrorId 'AttributesEmpty,NTFSSecurity.GetChildItem2' + } + It 'Should return only the items with the attribute when one is listed' { $result = @(Get-ChildItem2 -Path $attributeFolder -Attributes ReadOnly) diff --git a/Tests/Manifest.Tests.ps1 b/Tests/Manifest.Tests.ps1 index 1aaf613..491287e 100644 --- a/Tests/Manifest.Tests.ps1 +++ b/Tests/Manifest.Tests.ps1 @@ -67,17 +67,29 @@ Describe 'Module manifest of NTFSSecurity' { } Describe 'Type data of NTFSSecurity' { + BeforeDiscovery { + # Only Windows PowerShell fails to import type data that conflicts with an existing member, so both CI legs + # start it. + $windowsPowerShell = Join-Path -Path $env:SystemRoot -ChildPath 'System32\WindowsPowerShell\v1.0\powershell.exe' + } + + BeforeAll { + $windowsPowerShell = Join-Path -Path $env:SystemRoot -ChildPath 'System32\WindowsPowerShell\v1.0\powershell.exe' + } + # Before 5.0.0, the types file added the alias Size to System.IO.FileInfo, so the import failed when another module # had added a member with that name (#82). The module is imported in a child process, because type data stays in a # session. - It 'Should import after another module added a Size member to System.IO.FileInfo' { + It 'Should import in Windows PowerShell after another module added a Size member to System.IO.FileInfo' -Skip:(-not (Test-Path -LiteralPath $windowsPowerShell)) { $manifestPath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1' - $executable = (Get-Process -Id $PID).Path + $manifestPath | Should -Exist + $quotedPath = $manifestPath.Replace("'", "''") $command = 'Update-TypeData -TypeName System.IO.FileInfo -MemberType AliasProperty -MemberName Size -Value Length -Force; ' + - ("Import-Module -Name '{0}' -ErrorAction Stop; 'IMPORTED'" -f $manifestPath.Replace("'", "''")) + ("Import-Module -Name '{0}' -ErrorAction Stop; " -f $quotedPath) + + ("if ((Get-Item -LiteralPath '{0}').PSObject.Properties['LengthOnDisk']) {{ 'IMPORTED' }}" -f $quotedPath) - $output = & $executable -NoProfile -NonInteractive -Command $command 2>&1 + $output = & $windowsPowerShell -NoProfile -NonInteractive -Command $command 2>&1 $output | Select-Object -Last 1 | Should -Be 'IMPORTED' } -} \ No newline at end of file +}