diff --git a/Docs/Cmdlets/Add-NTFSAccess.md b/Docs/Cmdlets/Add-NTFSAccess.md index 3826c4e..7829a40 100644 --- a/Docs/Cmdlets/Add-NTFSAccess.md +++ b/Docs/Cmdlets/Add-NTFSAccess.md @@ -46,7 +46,7 @@ Adds an access control entry (ACE) to the discretionary access control list (DAC `-AccessRights` accepts the basic rights such as `Read`, `Modify`, and `FullControl` as well as the granular rights such as `CreateFiles` or `WriteAttributes`, and several values can be combined, for example `-AccessRights ReadData, WriteData, Delete`. For the mapping between the values of this module, the rights that Windows displays, and the entries of the advanced security dialog, see [Concepts](../Concepts.md). -The cmdlet has four parameter sets. The `Path` sets read the item from disk and write the changed DACL back immediately, while the `SD` sets change a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor` in memory until `Set-NTFSSecurityDescriptor` writes it back. The `Simple` sets take `-AppliesTo`, the `Complex` sets take `-InheritanceFlags` and `-PropagationFlags`; both describe the same ACE flags. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo`, `AccessType`, `InheritanceFlags`, or `PropagationFlags` that PowerShell can't convert to its type, such as the blank cell of a CSV file or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. +The cmdlet has four parameter sets. The `Path` sets read the item from disk and write the changed DACL back immediately, while the `SD` sets change a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor` in memory until `Set-NTFSSecurityDescriptor` writes it back. The `Simple` sets take `-AppliesTo`, the `Complex` sets take `-InheritanceFlags` and `-PropagationFlags`; both describe the same ACE flags. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both `Path` and `SecurityDescriptor` fails. A `$null`, an empty string, or `DBNull` value of a property counts as present for this choice, the same as a real value, so a CSV file, or any other source of piped objects, should have the columns of only one group: `AppliesTo`, or `InheritanceFlags` and `PropagationFlags`. A row or object that has properties of both groups, even if one of them is `$null` (for example a short CSV row, whose missing trailing columns `Import-Csv` fills with `$null`), an empty string, or `DBNull`, is an `AmbiguousParameterSet` error: terminating when named directly on the command line, and reported for just that object, unchanged, when it comes from the pipeline. A literal `$null`, named or piped, for `-AppliesTo` itself fails the same way but with its own, unrelated error, because `-AppliesTo` is mandatory. Within the group that PowerShell did choose, an `AppliesTo`, `AccessType`, `InheritanceFlags`, or `PropagationFlags` value that PowerShell can't convert to its type, such as a blank cell or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. A script block is a plain value for these parameters, not evaluated once for each piped object the way it would be for a strongly typed parameter, so a named script block, for example `-AppliesTo { 'FilesOnly' }`, fails with a terminating `InvalidParameterValue` error, the same as any other named value of the wrong type. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. When `-AccessType`, `-AppliesTo`, `-InheritanceFlags`, and `-PropagationFlags` are omitted, the cmdlet adds an `Allow` ACE that applies to this folder, subfolders, and files, which corresponds to the inheritance flags `ContainerInherit, ObjectInherit` and no propagation flags. An `Allow` ACE always receives the `Synchronize` right in addition to the requested rights, inheritance and propagation flags are ignored on files, and rights for an account that already has an ACE with the same access type and the same flags are merged into that ACE. The cmdlet writes no output unless `-PassThru` is used, and a failure on one item is reported as a non-terminating error while the remaining items are processed. diff --git a/Docs/Cmdlets/Add-NTFSAudit.md b/Docs/Cmdlets/Add-NTFSAudit.md index ec2410b..b59f886 100644 --- a/Docs/Cmdlets/Add-NTFSAudit.md +++ b/Docs/Cmdlets/Add-NTFSAudit.md @@ -46,7 +46,7 @@ The `Add-NTFSAudit` cmdlet adds an audit entry to the system access control list In the `PathSimple` and `PathComplex` parameter sets the cmdlet reads the security descriptor of every item in `-Path`, adds the entry, and writes the descriptor back right away. In the `SDSimple` and `SDComplex` parameter sets it adds the entry to an in-memory `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned; that change only reaches the file system when you pass the object to `Set-NTFSSecurityDescriptor`. The simple sets describe the scope of the entry with the single `-AppliesTo` parameter, the complex sets with `-InheritanceFlags` and `-PropagationFlags`. -A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo`, `AuditFlags`, `InheritanceFlags`, or `PropagationFlags` that PowerShell can't convert to its type, such as the blank cell of a CSV file or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. +A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both `Path` and `SecurityDescriptor` fails. A `$null`, an empty string, or `DBNull` value of a property counts as present for this choice, the same as a real value, so a CSV file, or any other source of piped objects, should have the columns of only one group: `AppliesTo`, or `InheritanceFlags` and `PropagationFlags`. A row or object that has properties of both groups, even if one of them is `$null` (for example a short CSV row, whose missing trailing columns `Import-Csv` fills with `$null`), an empty string, or `DBNull`, is an `AmbiguousParameterSet` error: terminating when named directly on the command line, and reported for just that object, unchanged, when it comes from the pipeline. A literal `$null`, named or piped, for `-AppliesTo` itself fails the same way but with its own, unrelated error, because `-AppliesTo` is mandatory. Within the group that PowerShell did choose, an `AppliesTo`, `AuditFlags`, `InheritanceFlags`, or `PropagationFlags` value that PowerShell can't convert to its type, such as a blank cell or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. A script block is a plain value for these parameters, not evaluated once for each piped object the way it would be for a strongly typed parameter, so a named script block, for example `-AppliesTo { 'FilesOnly' }`, fails with a terminating `InvalidParameterValue` error, the same as any other named value of the wrong type. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. When you omit them, `-AuditFlags` is `Success, Failure`, `-InheritanceFlags` is `ContainerInherit, ObjectInherit`, `-PropagationFlags` is `None`, and `-AppliesTo` is `ThisFolderSubfoldersAndFiles`, so both the simple and the complex set audit the item, its subfolders, and its files by default. Inheritance applies to folders only: when the item is a file, the cmdlet stores the entry without inheritance and propagation flags. diff --git a/Docs/Cmdlets/Remove-NTFSAccess.md b/Docs/Cmdlets/Remove-NTFSAccess.md index 195e0b9..20827da 100644 --- a/Docs/Cmdlets/Remove-NTFSAccess.md +++ b/Docs/Cmdlets/Remove-NTFSAccess.md @@ -48,7 +48,7 @@ Only the specified rights are taken away: when an entry grants more than `-Acces Inherited entries cannot be removed from the item that inherits them. Remove them from the folder named in the `InheritedFrom` property, or run `Disable-NTFSAccessInheritance` on the item first, which copies the inherited entries into it as explicit ones that this cmdlet can then remove. -The cmdlet has four parameter sets. The `Path` sets read the item from disk and write the changed DACL back immediately, while the `SD` sets change a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor` in memory until `Set-NTFSSecurityDescriptor` writes it back. The `Simple` sets take `-AppliesTo`, the `Complex` sets take `-InheritanceFlags` and `-PropagationFlags`. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo`, `AccessType`, `InheritanceFlags`, or `PropagationFlags` that PowerShell can't convert to its type, such as the blank cell of a CSV file or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. All relevant parameters bind by property name, so the output of `Get-NTFSAccess` and `Get-NTFSOrphanedAccess` can be piped directly into this cmdlet. The cmdlet writes no output unless `-PassThru` is used. +The cmdlet has four parameter sets. The `Path` sets read the item from disk and write the changed DACL back immediately, while the `SD` sets change a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor` in memory until `Set-NTFSSecurityDescriptor` writes it back. The `Simple` sets take `-AppliesTo`, the `Complex` sets take `-InheritanceFlags` and `-PropagationFlags`. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both `Path` and `SecurityDescriptor` fails. A `$null`, an empty string, or `DBNull` value of a property counts as present for this choice, the same as a real value, so a CSV file, or any other source of piped objects, should have the columns of only one group: `AppliesTo`, or `InheritanceFlags` and `PropagationFlags`. A row or object that has properties of both groups, even if one of them is `$null` (for example a short CSV row, whose missing trailing columns `Import-Csv` fills with `$null`), an empty string, or `DBNull`, is an `AmbiguousParameterSet` error: terminating when named directly on the command line, and reported for just that object, unchanged, when it comes from the pipeline. A literal `$null`, named or piped, for `-AppliesTo` itself fails the same way but with its own, unrelated error, because `-AppliesTo` is mandatory. Within the group that PowerShell did choose, an `AppliesTo`, `AccessType`, `InheritanceFlags`, or `PropagationFlags` value that PowerShell can't convert to its type, such as a blank cell or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. A script block is a plain value for these parameters, not evaluated once for each piped object the way it would be for a strongly typed parameter, so a named script block, for example `-AppliesTo { 'FilesOnly' }`, fails with a terminating `InvalidParameterValue` error, the same as any other named value of the wrong type. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. All relevant parameters bind by property name, so the output of `Get-NTFSAccess` and `Get-NTFSOrphanedAccess` can be piped directly into this cmdlet. The cmdlet writes no output unless `-PassThru` is used. ## EXAMPLES diff --git a/Docs/Cmdlets/Remove-NTFSAudit.md b/Docs/Cmdlets/Remove-NTFSAudit.md index 78e4516..2921ce9 100644 --- a/Docs/Cmdlets/Remove-NTFSAudit.md +++ b/Docs/Cmdlets/Remove-NTFSAudit.md @@ -46,7 +46,7 @@ The `Remove-NTFSAudit` cmdlet removes an audit entry from the system access cont Because the inheritance and propagation flags take part in the match, they must describe the entry you want to remove. `-AppliesTo ThisFolderOnly` removes an entry that is not inherited by child items, which is also the shape of every audit entry on a file, while the default of the complex parameter sets removes an entry that applies to the folder, its subfolders, and its files. An entry that an item inherits from a parent folder is stored on that parent, so remove it there, or use `Clear-NTFSAudit` with `-DisableInheritance` to drop the inherited entries on the item. -In the `PathSimple` and `PathComplex` parameter sets the cmdlet reads the security descriptor of every item in `-Path` and writes it back right away. In the `SDSimple` and `SDComplex` parameter sets it changes an in-memory `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned, and the change reaches the file system only when you pass the object to `Set-NTFSSecurityDescriptor`. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo`, `AuditFlags`, `InheritanceFlags`, or `PropagationFlags` that PowerShell can't convert to its type, such as the blank cell of a CSV file or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. +In the `PathSimple` and `PathComplex` parameter sets the cmdlet reads the security descriptor of every item in `-Path` and writes it back right away. In the `SDSimple` and `SDComplex` parameter sets it changes an in-memory `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned, and the change reaches the file system only when you pass the object to `Set-NTFSSecurityDescriptor`. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both `Path` and `SecurityDescriptor` fails. A `$null`, an empty string, or `DBNull` value of a property counts as present for this choice, the same as a real value, so a CSV file, or any other source of piped objects, should have the columns of only one group: `AppliesTo`, or `InheritanceFlags` and `PropagationFlags`. A row or object that has properties of both groups, even if one of them is `$null` (for example a short CSV row, whose missing trailing columns `Import-Csv` fills with `$null`), an empty string, or `DBNull`, is an `AmbiguousParameterSet` error: terminating when named directly on the command line, and reported for just that object, unchanged, when it comes from the pipeline. A literal `$null`, named or piped, for `-AppliesTo` itself fails the same way but with its own, unrelated error, because `-AppliesTo` is mandatory. Within the group that PowerShell did choose, an `AppliesTo`, `AuditFlags`, `InheritanceFlags`, or `PropagationFlags` value that PowerShell can't convert to its type, such as a blank cell or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. A script block is a plain value for these parameters, not evaluated once for each piped object the way it would be for a strongly typed parameter, so a named script block, for example `-AppliesTo { 'FilesOnly' }`, fails with a terminating `InvalidParameterValue` error, the same as any other named value of the wrong type. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. When you omit them, `-AuditFlags` is `Success, Failure`, `-InheritanceFlags` is `ContainerInherit, ObjectInherit`, `-PropagationFlags` is `None`. All parameters bind by property name, and `-Path` also binds by value and through its `FullName` alias, so you can pipe the output of `Get-NTFSAudit`, `Get-ChildItem`, `Get-ChildItem2`, and `Get-Item2` into the cmdlet. The cmdlet writes no object unless you use `-PassThru`. diff --git a/Tests/ParameterSets.Access.Tests.ps1 b/Tests/ParameterSets.Access.Tests.ps1 index 11189f1..f1b1cda 100644 --- a/Tests/ParameterSets.Access.Tests.ps1 +++ b/Tests/ParameterSets.Access.Tests.ps1 @@ -1431,6 +1431,44 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF @{ Parameter = 'PropagationFlags'; Word = 'Inh'; ExpectedName = 'InheritOnly' } @{ Parameter = 'AppliesTo'; Word = 'ThisFolderO'; ExpectedName = 'ThisFolderOnly' } ) + + # R1-1: because the five parameters are object, PowerShell counts a $null, an empty string, or DBNull value + # of a property as present when it chooses the parameter set, the same as a real value, so AppliesTo (the + # Simple group) together with a $null/''/DBNull InheritanceFlags or PropagationFlags (the Complex group) + # offers both groups and is AmbiguousParameterSet -- not InvalidParameterValue, which is what the docs + # wrongly claimed for every shape. The reverse (AppliesTo blank, a Complex property valid) is + # AmbiguousParameterSet too, except that AppliesTo is Mandatory, so a literal $null of it (unlike '' or + # DBNull) is rejected earlier by the binder's own Mandatory check, unrelated to and unchanged by this + # finding. Covers the four cmdlets' Path and SD sets; the Audit half of the four cmdlets is pinned in + # ParameterSets.Audit.Tests.ps1. + $accessAmbiguousOtherGroupCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') { + foreach ($targetKind in 'Path', 'SD') { + foreach ($shape in 'a $null', 'an empty string', 'DBNull') { + $blankValue = switch ($shape) { 'a $null' { $null }; 'an empty string' { '' }; 'DBNull' { [System.DBNull]::Value } } + @{ + Cmdlet = $cmdlet; TargetKind = $targetKind; Shape = $shape; BlankValue = $blankValue + Direction = 'AppliesTo valid, InheritanceFlags blank'; AppliesToIsBlank = $false + ExpectedErrorId = 'AmbiguousParameterSet' + } + @{ + Cmdlet = $cmdlet; TargetKind = $targetKind; Shape = $shape; BlankValue = $blankValue + Direction = 'AppliesTo blank, PropagationFlags valid'; AppliesToIsBlank = $true + ExpectedErrorId = $(if ($shape -eq 'a $null') { 'ParameterArgumentValidationErrorNullNotAllowed' } else { 'AmbiguousParameterSet' }) + } + } + } + } + + # R1-3: with the object type, a script block is a plain value (rc8's delay-bind evaluation, once per piped + # object, no longer applies), so it fails the same way any other value of the wrong shape does: a + # terminating InvalidParameterValue from ValidateNamedValue in BeginProcessing, before any piped object is + # processed. + $accessScriptBlockCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') { + @{ Cmdlet = $cmdlet; Parameter = 'AccessType' } + @{ Cmdlet = $cmdlet; Parameter = 'InheritanceFlags' } + @{ Cmdlet = $cmdlet; Parameter = 'PropagationFlags' } + @{ Cmdlet = $cmdlet; Parameter = 'AppliesTo' } + } } Context 'A bad value bound from the pipeline' { @@ -1751,6 +1789,66 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF } } + # R1-1: rc8 created the entry for every shape below; the object-typed parameters make a $null, an empty + # string, or DBNull value of a property of the other group count as present too, so PowerShell cannot resolve + # the parameter set and nothing is changed. + Context 'A blank value of the other groups property' { + It 'Should reject () for () with the error the binder gives, change nothing, and still process the next object' -ForEach $accessAmbiguousOtherGroupCases { + $bad = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousOtherGroupBad' -Directory + $next = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousOtherGroupNext' -Directory + $before = (Get-Acl -LiteralPath $bad).Sddl + + $badTarget = if ($TargetKind -eq 'Path') { @{ Path = $bad } } else { @{ SecurityDescriptor = (Get-NTFSSecurityDescriptor -Path $bad -ErrorAction Stop) } } + $nextTarget = if ($TargetKind -eq 'Path') { @{ Path = $next } } else { @{ SecurityDescriptor = (Get-NTFSSecurityDescriptor -Path $next -ErrorAction Stop) } } + + $badExtra = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' } + if ($AppliesToIsBlank) { + $badExtra['AppliesTo'] = $BlankValue + $badExtra['PropagationFlags'] = 'None' + } + else { + $badExtra['AppliesTo'] = 'ThisFolderOnly' + $badExtra['InheritanceFlags'] = $BlankValue + } + + $rows = @( + [pscustomobject] ($badTarget + $badExtra) + [pscustomobject] ($nextTarget + @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' }) + ) + + $result = @($rows | & $Cmdlet -PassThru -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue) + + $pipelineErrors | Should -HaveCount 1 + $pipelineErrors[0].FullyQualifiedErrorId | Should -BeLike "$ExpectedErrorId,*" + (Get-Acl -LiteralPath $bad).Sddl | Should -BeExactly $before + $result | Should -Not -BeNullOrEmpty + $result | ForEach-Object -Process { $_.FullName | Should -Be $next } + } + } + + # R1-3: with the object type, a script block is a plain value (rc8's delay-bind evaluation, once per piped + # object, no longer applies), so ValidateNamedValue in BeginProcessing rejects it the same as any other bad + # value, before any piped object is processed. + Context 'A named script block is a terminating error' { + It 'Should throw a terminating InvalidParameterValue error for a named script block value of -, and change nothing' -ForEach $accessScriptBlockCases { + $folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedScriptBlock' -Directory + $before = (Get-Acl -LiteralPath $folder).Sddl + $namedArguments = @{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData' } + $namedArguments[$Parameter] = { 'Deny' } + + $caughtId = $null + try { + & $Cmdlet @namedArguments -ErrorAction Stop + } + catch { + $caughtId = $_.FullyQualifiedErrorId + } + + $caughtId | Should -BeLike 'InvalidParameterValue,*' + (Get-Acl -LiteralPath $folder).Sddl | Should -BeExactly $before + } + } + Context 'Tab completion and the parameter type' { It ' - should complete exactly the names of its enumeration' -ForEach $accessCompleterCases { $inputScript = '{0} -{1} ' -f $Cmdlet, $Parameter diff --git a/Tests/ParameterSets.Audit.Tests.ps1 b/Tests/ParameterSets.Audit.Tests.ps1 index f3c6868..d43655e 100644 --- a/Tests/ParameterSets.Audit.Tests.ps1 +++ b/Tests/ParameterSets.Audit.Tests.ps1 @@ -1099,6 +1099,44 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF $auditCompletionFormCases = @( @{ Parameter = 'AuditFlags'; Word = 'F'; ExpectedName = 'Failure' } ) + + # R1-1: because the five parameters are object, PowerShell counts a $null, an empty string, or DBNull value + # of a property as present when it chooses the parameter set, the same as a real value, so AppliesTo (the + # Simple group) together with a $null/''/DBNull InheritanceFlags or PropagationFlags (the Complex group) + # offers both groups and is AmbiguousParameterSet -- not InvalidParameterValue, which is what the docs + # wrongly claimed for every shape. The reverse (AppliesTo blank, a Complex property valid) is + # AmbiguousParameterSet too, except that AppliesTo is Mandatory, so a literal $null of it (unlike '' or + # DBNull) is rejected earlier by the binder's own Mandatory check, unrelated to and unchanged by this + # finding. Covers the four cmdlets' Path and SD sets; the Access half is pinned in + # ParameterSets.Access.Tests.ps1. + $auditAmbiguousOtherGroupCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') { + foreach ($targetKind in 'Path', 'SD') { + foreach ($shape in 'a $null', 'an empty string', 'DBNull') { + $blankValue = switch ($shape) { 'a $null' { $null }; 'an empty string' { '' }; 'DBNull' { [System.DBNull]::Value } } + @{ + Cmdlet = $cmdlet; TargetKind = $targetKind; Shape = $shape; BlankValue = $blankValue + Direction = 'AppliesTo valid, InheritanceFlags blank'; AppliesToIsBlank = $false + ExpectedErrorId = 'AmbiguousParameterSet' + } + @{ + Cmdlet = $cmdlet; TargetKind = $targetKind; Shape = $shape; BlankValue = $blankValue + Direction = 'AppliesTo blank, PropagationFlags valid'; AppliesToIsBlank = $true + ExpectedErrorId = $(if ($shape -eq 'a $null') { 'ParameterArgumentValidationErrorNullNotAllowed' } else { 'AmbiguousParameterSet' }) + } + } + } + } + + # R1-3: with the object type, a script block is a plain value (rc8's delay-bind evaluation, once per piped + # object, no longer applies), so it fails the same way any other value of the wrong shape does: a + # terminating InvalidParameterValue from ValidateNamedValue in BeginProcessing, before any piped object is + # processed. + $auditScriptBlockCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') { + @{ Cmdlet = $cmdlet; Parameter = 'AuditFlags' } + @{ Cmdlet = $cmdlet; Parameter = 'InheritanceFlags' } + @{ Cmdlet = $cmdlet; Parameter = 'PropagationFlags' } + @{ Cmdlet = $cmdlet; Parameter = 'AppliesTo' } + } } Context 'A bad value bound from the pipeline' { @@ -1407,6 +1445,80 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF } } + # R1-1: rc8 created the entry for every shape below; the object-typed parameters make a $null, an empty + # string, or DBNull value of a property of the other group count as present too, so PowerShell cannot resolve + # the parameter set and nothing is changed. The conversion/parameter-set failure happens before the cmdlet + # ever touches the SACL, so the core assertion (the error id, and that the bad object's audit entries are + # unchanged) runs in every configuration; only the "good next object succeeded" part needs the Security + # privilege, the same way the rest of this file does. + Context 'A blank value of the other groups property' { + It 'Should reject () for () with the error the binder gives, change nothing, and still process the next object' -ForEach $auditAmbiguousOtherGroupCases { + $bad = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousOtherGroupBad' -Directory + $next = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousOtherGroupNext' -Directory + $before = if ($canReadAudit) { Get-TestAuditSddl -Path $bad } + + $badTarget = if ($TargetKind -eq 'Path') { @{ FullName = $bad } } else { @{ SecurityDescriptor = (Get-NTFSSecurityDescriptor -Path $bad -ErrorAction Stop) } } + $nextTarget = if ($TargetKind -eq 'Path') { @{ FullName = $next } } else { @{ SecurityDescriptor = (Get-NTFSSecurityDescriptor -Path $next -ErrorAction Stop) } } + + $badExtra = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' } + if ($AppliesToIsBlank) { + $badExtra['AppliesTo'] = $BlankValue + $badExtra['PropagationFlags'] = 'None' + } + else { + $badExtra['AppliesTo'] = 'ThisFolderOnly' + $badExtra['InheritanceFlags'] = $BlankValue + } + + $rows = @( + [pscustomobject] ($badTarget + $badExtra) + [pscustomobject] ($nextTarget + @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' }) + ) + + $result = @($rows | & $Cmdlet -PassThru -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue) + + $badErrors = @($pipelineErrors | Where-Object -FilterScript { + ($TargetKind -eq 'Path' -and $_.TargetObject.FullName -eq $bad) -or + ($TargetKind -eq 'SD' -and $_.TargetObject.SecurityDescriptor -eq $badTarget['SecurityDescriptor']) + }) + $badErrors | Should -HaveCount 1 + $badErrors[0].FullyQualifiedErrorId | Should -BeLike "$ExpectedErrorId,*" + if ($canReadAudit) { + Get-TestAuditSddl -Path $bad | Should -BeExactly $before + $pipelineErrors | Should -HaveCount 1 + $result | Should -Not -BeNullOrEmpty + $result | ForEach-Object -Process { $_.FullName | Should -Be $next } + } + } + } + + # R1-3: with the object type, a script block is a plain value (rc8's delay-bind evaluation, once per piped + # object, no longer applies), so ValidateNamedValue in BeginProcessing rejects it the same as any other bad + # value, before any piped object is processed and before the cmdlet ever touches the SACL. The terminating + # error itself needs no Security privilege; reading the SACL back to confirm nothing changed does, the same + # way the rest of this file does. + Context 'A named script block is a terminating error' { + It 'Should throw a terminating InvalidParameterValue error for a named script block value of -, and change nothing' -ForEach $auditScriptBlockCases { + $folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedScriptBlock' -Directory + $before = if ($canReadAudit) { Get-TestAuditSddl -Path $folder } + $namedArguments = @{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData' } + $namedArguments[$Parameter] = { 'Deny' } + + $caughtId = $null + try { + & $Cmdlet @namedArguments -ErrorAction Stop + } + catch { + $caughtId = $_.FullyQualifiedErrorId + } + + $caughtId | Should -BeLike 'InvalidParameterValue,*' + if ($canReadAudit) { + Get-TestAuditSddl -Path $folder | Should -BeExactly $before + } + } + } + Context 'Tab completion and the parameter type' { It ' - should complete exactly the names of its enumeration' -ForEach $auditCompleterCases { $inputScript = '{0} -{1} ' -f $Cmdlet, $Parameter