diff --git a/Tests/ItemCmdlets.Tests.ps1 b/Tests/ItemCmdlets.Tests.ps1 index e610a82..721a027 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/Tests/ItemCmdlets.Tests.ps1 @@ -350,9 +350,9 @@ Describe 'Get-ChildItem2' { # last dot before the dot token and the question mark account for the final segment. The 8.3-compliant # names of the truth table above have at most one dot, where the first dot and the last dot are the same # position, so they cannot tell the two rules apart; this fixture uses names with two or three dots - # instead, including the realistic ones from the WI. Expected values are pinned with the real name of each - # item, confirmed with `fsutil file setshortname` so that a short name the volume might otherwise assign - # cannot also produce a match. + # instead, including realistic ones such as archive.tar.gz and jquery.min.js. Expected values are pinned + # with the real name of each item, confirmed with `fsutil file setshortname` so that a short name the volume + # might otherwise assign cannot also produce a match. It 'Should match -Filter "" on a name with two or more dots the way Windows PowerShell 5.1 does' -ForEach @( @{ Filter = '*.?'; Expected = @('a.b.c', 'v1.2.3', 'x.y.z.w') } @{ Filter = '*.??'; Expected = @('a.b.c', 'a.b.cd', 'archive.tar.gz', 'index.d.ts', 'jquery.min.js', 'my.file.tx', 'v1.2.3', 'x.y.z.w') } diff --git a/Tests/ObjectApis.Tests.ps1 b/Tests/ObjectApis.Tests.ps1 index 2312e85..5b39cc9 100644 --- a/Tests/ObjectApis.Tests.ps1 +++ b/Tests/ObjectApis.Tests.ps1 @@ -1026,9 +1026,10 @@ Describe 'The FileSystemSecurity2 conversions from FileSecurity and DirectorySec } } -# Review finding: PowerShell's binder uses the fixed conversion of 3.1 for every -SecurityDescriptor parameter, so a -# descriptor that Get-Acl returns, never meant for these cmdlets, now binds where it used to be a binding error. These -# pin the four cmdlets the review named; the one-line probes of the others are in the final report, not here. +# PowerShell's binder uses the conversion from FileSecurity and DirectorySecurity for every -SecurityDescriptor +# parameter, so a descriptor that Get-Acl returns, never meant for these cmdlets, binds where it used to be a binding +# error. The descriptor has no item and no audit section unless Get-Acl read one. These pin a reader of the access +# entries, a reader of the audit entries, the inheritance reader, and the writer. Describe 'Cmdlets with a security descriptor that Get-Acl returns' { BeforeAll { $plainFile = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAclPlain' @@ -1082,6 +1083,7 @@ Describe 'Cmdlets with a security descriptor that Get-Acl returns' { (Get-Acl -LiteralPath $plainFile).Sddl | Should -BeExactly $before } } + Describe 'RemoveFileSystemAccessRuleAll and RemoveFileSystemAuditRuleAll honor the account list' { BeforeAll { $allow = [System.Security.AccessControl.AccessControlType]::Allow diff --git a/Tests/ParameterSets.Access.Tests.ps1 b/Tests/ParameterSets.Access.Tests.ps1 index f1b1cda..75481c0 100644 --- a/Tests/ParameterSets.Access.Tests.ps1 +++ b/Tests/ParameterSets.Access.Tests.ps1 @@ -1384,7 +1384,7 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF @{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; Value = 'Typoo' } } - # R1-2: a named $null (a literal $null, a variable, an alias, splatting, or $PSDefaultParameterValues) must + # A named $null (a literal $null, a variable, an alias, splatting, or $PSDefaultParameterValues) must # stay a terminating error, the same as a bad named value; only a $null that a piped object's property # carries keeps the parameter's own default. $accessNamedNullCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') { @@ -1421,10 +1421,10 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF @{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; EnumTypeName = 'Security2.ApplyTo' } } - # R1-4: a quoted or a wildcard word completed in rc8 (a typed parameter of an enumeration); the first - # EnumArgumentCompleters.cs returned nothing for either shape. Each ExpectedName has no sibling name that - # also starts with it (ThisFolderOnly has no ThisFolderOnlyOneLevel), so the unquoted and quoted forms match - # it alone; the wildcard only needs to contain it among the matches. + # A quoted or a wildcard word completed while these parameters were typed as enumerations, so the completers + # must offer the names for either shape too. Each ExpectedName has no sibling name that also starts with it + # (ThisFolderOnly has no ThisFolderOnlyOneLevel), so the unquoted and quoted forms match it alone; the + # wildcard only needs to contain it among the matches. $accessCompletionFormCases = @( @{ Parameter = 'AccessType'; Word = 'D'; ExpectedName = 'Deny' } @{ Parameter = 'InheritanceFlags'; Word = 'Container'; ExpectedName = 'ContainerInherit' } @@ -1432,15 +1432,13 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF @{ 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. + # 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. 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. Covers the + # Path and SD sets of the access cmdlets; the audit cmdlets are 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') { @@ -1459,10 +1457,9 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF } } - # 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. + # With the object type, a script block is a plain value (a typed parameter evaluates it once per piped + # object), 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' } @@ -1671,9 +1668,9 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF } } - # R1-2: rc8 ended a named $null (for example a variable that misspells a property, such as -AccessType - # $rule.AccessTpye) with a terminating ParameterBindingFailed; the object-typed parameter accepted it and - # silently kept the default instead. A named $null is now reported the same way as a bad named value. + # A typed parameter ends a named $null (for example a variable that misspells a property, such as -AccessType + # $rule.AccessTpye) with a terminating ParameterBindingFailed; the object-typed parameter accepts it, so the + # cmdlet must not keep the default silently. A named $null is reported the same way as a bad named value. Context 'A named $null is a terminating error' { It 'Should throw a terminating error for a named $null of -, and change nothing' -ForEach $accessNamedNullCases { $folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedNull' -Directory @@ -1789,10 +1786,10 @@ 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' { + # A typed parameter skips a $null, an empty string, or DBNull value of a property, and the entry was created for + # every shape below; the object-typed parameters count such a value of a property of the other group as present + # too, so PowerShell cannot resolve the parameter set and nothing is changed. + Context 'A blank value of a property of the other group' { 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 @@ -1826,9 +1823,9 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF } } - # 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. + # With the object type, a script block is a plain value (a typed parameter evaluates it once per piped object), + # 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 diff --git a/Tests/ParameterSets.Audit.Tests.ps1 b/Tests/ParameterSets.Audit.Tests.ps1 index d43655e..d892875 100644 --- a/Tests/ParameterSets.Audit.Tests.ps1 +++ b/Tests/ParameterSets.Audit.Tests.ps1 @@ -1054,7 +1054,7 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF @{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; Value = 'Typoo' } } - # R1-2: a named $null (a literal $null, a variable, splatting, or $PSDefaultParameterValues) must stay a + # A named $null (a literal $null, a variable, splatting, or $PSDefaultParameterValues) must stay a # terminating error, the same as a bad named value; only a $null that a piped object's property carries # keeps the parameter's own default. $auditNamedNullCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') { @@ -1093,22 +1093,20 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF @{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; EnumTypeName = 'Security2.ApplyTo' } } - # R1-4: AuditFlags is the one completer that ParameterSets.Access.Tests.ps1 does not already cover with the + # AuditFlags is the one completer that ParameterSets.Access.Tests.ps1 does not already cover with the # quoted and wildcard forms (it shares the same completer code for InheritanceFlags, PropagationFlags, and # AppliesTo, so those are pinned there). $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. + # 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. 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. Covers the + # Path and SD sets of the audit cmdlets; the access cmdlets are 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') { @@ -1127,10 +1125,9 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF } } - # 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. + # With the object type, a script block is a plain value (a typed parameter evaluates it once per piped + # object), 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' } @@ -1349,8 +1346,9 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF } } - # R1-2: rc8 ended a named $null with a terminating ParameterBindingFailed; the object-typed parameter accepted - # it and silently kept the default instead. A named $null is now reported the same way as a bad named value. + # A typed parameter ends a named $null with a terminating ParameterBindingFailed; the object-typed parameter + # accepts it, so the cmdlet must not keep the default silently. A named $null is reported the same way as a bad + # named value. Context 'A named $null is a terminating error' { It 'Should throw a terminating error for a named $null of -, and change nothing' -ForEach $auditNamedNullCases { $folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedNull' -Directory @@ -1445,13 +1443,13 @@ 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' { + # A typed parameter skips a $null, an empty string, or DBNull value of a property, and the entry was created for + # every shape below; the object-typed parameters count such a value of a property of the other group 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 a property of the other group' { 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 @@ -1492,11 +1490,10 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF } } - # 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. + # With the object type, a script block is a plain value (a typed parameter evaluates it once per piped object), + # 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 diff --git a/Tests/PipelineControl.Tests.ps1 b/Tests/PipelineControl.Tests.ps1 index 01b117f..68c4c7b 100644 --- a/Tests/PipelineControl.Tests.ps1 +++ b/Tests/PipelineControl.Tests.ps1 @@ -637,33 +637,28 @@ namespace NtfsSecurityTests { public class StandInForBreak : System.Management.A } } -# InvokeAsOwner (and Set-NTFSSecurityDescriptor's WriteChangesAsOwner) take ownership, run the action, and in a +# InvokeAsOwner (and WriteChangesAsOwner of Set-NTFSSecurityDescriptor) take ownership, run the action, and in a # finally restore the previous owner, writing a RestoreOwnerError through WriteError when that restore fails. # WriteError runs the later commands of the pipeline and notes what they raise (IsFromLaterCommand, above); the catch -# around the InvokeAsOwner/WriteChangesAsOwner call must pass that on instead of reporting it as the item's own error -# and going on, or the caller never sees it. Each row denies the current user the right its own action needs (a -# change denial for the writers, a read denial on Everyone for Get-FileHash2, which needs file data, not -# permissions) and gives the item an owner that only the Restore privilege can set back, which the tests turn off; -# InvokeAsOwner/WriteChangesAsOwner then takes ownership, runs the action, and fails to restore the owner. Eight of -# the seventeen handlers of item 7 can be pinned this way. The scenario and the "without a later command" assertion -# already exist for six of them: ParameterSets.Inheritance.Tests.ps1 (Disable/Enable-NTFSAccessInheritance, -# Set-NTFSInheritance), PathErrors.Tests.ps1 (Clear-NTFSAccess -DisableInheritance), SecurityDescriptor.Tests.ps1 -# (Set-NTFSSecurityDescriptor), FileHash.Tests.ps1 (Get-FileHash2); Add-NTFSAccess and Remove-NTFSAccess use the same -# scenario as the inheritance cmdlets. So this table only adds the later-command assertion, not a duplicate of the -# existing one. +# around the call must pass that on instead of reporting it as the item's own error and going on, or the caller never +# sees it. Each row denies the current user the right its own action needs (a change denial for the writers, a read +# denial on Everyone for Get-FileHash2, which needs file data, not permissions) and gives the item an owner that only +# the Restore privilege can set back, which the tests turn off; the cmdlet then takes ownership, runs the action, and +# fails to restore the owner. Eight of the seventeen handlers can be pinned this way. The scenario and the "without a +# later command" assertion already exist for most of them in the test files of their cmdlets +# (ParameterSets.Inheritance.Tests.ps1, PathErrors.Tests.ps1, SecurityDescriptor.Tests.ps1, FileHash.Tests.ps1), and +# Add-NTFSAccess and Remove-NTFSAccess use the scenario of the inheritance cmdlets, so this table only adds the +# later-command assertion. # -# NOT DONE for the other nine (the reason is the same Windows rule in both groups: taking ownership only bypasses a -# denial of ReadPermissions/ChangePermissions, never of a different right): Get-NTFSAccess, Get-NTFSOrphanedAccess, -# Get-NTFSEffectiveAccess, and Get-NTFSInheritance read the security descriptor; InvokeAsOwner's own first statement, -# FileSystemOwner.GetOwner, reads it too and needs the same ReadPermissions right the cmdlet's read needs, so a -# denial that fails one fails the other and InvokeAsOwner never reaches the owner-restore step (verified: denying -# ReadPermissions directly to the user reports ReadSecurityError with the plain UnauthorizedAccessException, not -# RestoreOwnerError, and the owner is never taken; probes-wi2\p-restoreowner-read-vs-write.ps1, -# p-read-cmdlets-2.ps1). Clear-NTFSAudit, Disable-NTFSAuditInheritance, Enable-NTFSAuditInheritance, Add-NTFSAudit, and -# Remove-NTFSAudit write the SACL, which Windows grants to this elevated session regardless of a DACL deny and -# regardless of the module's EnablePrivileges setting, so the action does not fail to begin with (verified with both -# a ChangePermissions deny and EnablePrivileges off: no error, the SACL is written, and the owner is left unchanged; -# probes-wi2\p-audit-restoreowner.ps1, p-audit-restoreowner-2.ps1). The handlers of all seventeen carry the check. +# The other nine have no row, for one Windows rule: taking ownership only bypasses a denial of +# ReadPermissions/ChangePermissions, never of a different right. Get-NTFSAccess, Get-NTFSOrphanedAccess, +# Get-NTFSEffectiveAccess, and Get-NTFSInheritance read the security descriptor, and so does the first statement of +# InvokeAsOwner (FileSystemOwner.GetOwner), which needs the same ReadPermissions right: a denial that fails one fails +# the other, so the owner-restore step is never reached (denying ReadPermissions reports a ReadSecurityError with the +# plain UnauthorizedAccessException, and the owner is never taken). Clear-NTFSAudit, Disable-NTFSAuditInheritance, +# Enable-NTFSAuditInheritance, Add-NTFSAudit, and Remove-NTFSAudit write the SACL, which Windows grants to this +# elevated session whatever the DACL says and whatever the EnablePrivileges setting is, so the action does not fail +# to begin with (the SACL is written and the owner is left unchanged). The handlers of all seventeen carry the check. Describe 'A later command that throws at the RestoreOwnerError record' { BeforeDiscovery { $holdsRestorePrivilege = Test-PrivilegeHeld -Name 'SeRestorePrivilege'