diff --git a/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths-Results.csv b/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths-Results.csv new file mode 100644 index 0000000..d95cd71 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths-Results.csv @@ -0,0 +1,157 @@ +"Edition","Role","Test","Baseline","Candidate","BaselineFailure" +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Admin","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Core","Admin","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of" +"Core","Admin","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Admin","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Admin","InheritedFrom of audit entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","Failed","Passed","Expected $null or empty, but got 'TakeOwnership'." +"Core","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should hold the four privileges that the cmdlets enable","Passed","Passed", +"Core","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should pass on what a later command throws at the message after the enabling and disable the privileges","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should leave the loop for a break of a later command that takes the error of a nested folder","Failed","Passed","Expected $false, but got $true." +"Core","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should pass on what a later command throws when it takes the error of a nested folder","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should start with folders that the account cannot list","Passed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Delegate","An item that the account owns and whose owner may not change its permissions on a share.Clear-NTFSAccess -DisableInheritance should take ownership, clear and protect the DACL, and report no RestoreOwnerError","Failed","Passed","Expected $null or empty, but got 'RestoreOwnerError,NTFSSecurity.ClearAccess: (5) Access is denied: [\\F1AFile2.a.forest1.net\NTFSSecurityLive\local-Core-20261009213136\Case10\Delegate\Owner\Clear.txt]'." +"Core","Delegate","An item that the account owns and whose owner may not change its permissions on a share.Set-NTFSSecurityDescriptor should write the cleared DACL and report no RestoreOwnerError","Failed","Passed","Expected $null or empty, but got 'RestoreOwnerError,NTFSSecurity.SetSecurityDescriptor: (5) Access is denied: [\\F1AFile2.a.forest1.net\NTFSSecurityLive\local-Core-20261009213136\Case10\Delegate\Owner\Descriptor.txt]'." +"Core","Delegate","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Core","Delegate","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of" +"Core","Delegate","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Delegate","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Delegate","InheritedFrom of an item below a folder on a share whose permissions the account cannot read.Get-NTFSAccess should name an unknown parent for an inherited entry and no source for an explicit entry","Failed","Passed","Expected strings to be the same, but they were different." +"Core","Delegate","InheritedFrom of an item below a folder on a share whose permissions the account cannot read.Should start with a folder whose permissions the account cannot read, and an item that it can","Passed","Passed", +"Core","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","Failed","Passed","Expected $null or empty, but got 'TakeOwnership'." +"Core","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should hold the four privileges that the cmdlets enable","Passed","Passed", +"Core","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should pass on what a later command throws at the message after the enabling and disable the privileges","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of Admin for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Core","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of Delegate for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Core","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of ServerAdmin for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Core","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Core","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Core","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of" +"Core","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Core","ServerAdmin","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Core","ServerAdmin","InheritedFrom of audit entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Admin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Admin","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Desktop","Admin","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of" +"Desktop","Admin","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Admin","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Admin","InheritedFrom of audit entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","Failed","Passed","Expected $null or empty, but got 'TakeOwnership'." +"Desktop","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should hold the four privileges that the cmdlets enable","Passed","Passed", +"Desktop","Admin","Privileges when a later command takes the debug messages of the cmdlet on a share.Should pass on what a later command throws at the message after the enabling and disable the privileges","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should leave the loop for a break of a later command that takes the error of a nested folder","Failed","Passed","Expected $false, but got $true." +"Desktop","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should pass on what a later command throws when it takes the error of a nested folder","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command and the error of a folder that Get-ChildItem2 cannot read on a share.Should start with folders that the account cannot list","Passed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","Delegate","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Delegate","An item that the account owns and whose owner may not change its permissions on a share.Clear-NTFSAccess -DisableInheritance should take ownership, clear and protect the DACL, and report no RestoreOwnerError","Failed","Passed","Expected $null or empty, but got 'RestoreOwnerError,NTFSSecurity.ClearAccess: (5) Access is denied: [\\F1AFile2.a.forest1.net\NTFSSecurityLive\local-Desktop-20261009212312\Case10\Delegate\Owner\Clear.txt]'." +"Desktop","Delegate","An item that the account owns and whose owner may not change its permissions on a share.Set-NTFSSecurityDescriptor should write the cleared DACL and report no RestoreOwnerError","Failed","Passed","Expected $null or empty, but got 'RestoreOwnerError,NTFSSecurity.SetSecurityDescriptor: (5) Access is denied: [\\F1AFile2.a.forest1.net\NTFSSecurityLive\local-Desktop-20261009212312\Case10\Delegate\Owner\Descriptor.txt]'" +"Desktop","Delegate","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Desktop","Delegate","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of" +"Desktop","Delegate","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Delegate","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Delegate","InheritedFrom of an item below a folder on a share whose permissions the account cannot read.Get-NTFSAccess should name an unknown parent for an inherited entry and no source for an explicit entry","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","Delegate","InheritedFrom of an item below a folder on a share whose permissions the account cannot read.Should start with a folder whose permissions the account cannot read, and an item that it can","Passed","Passed", +"Desktop","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should disable the privilege when Select-Object -First ends the pipeline at the message after its enabling","Failed","Passed","Expected $null or empty, but got 'TakeOwnership'." +"Desktop","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should hold the four privileges that the cmdlets enable","Passed","Passed", +"Desktop","Delegate","Privileges when a later command takes the debug messages of the cmdlet on a share.Should pass on what a later command throws at the message after the enabling and disable the privileges","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of Admin for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Desktop","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of Delegate for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Desktop","Server","Security descriptors on the file server after the runs on the client.Should have changed only the first item of ServerAdmin for each cmdlet that a later command stopped","Failed","Passed","Expected $true, because Remove-Item2 left the second item (Select), but got $false." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Copy-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Move-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Remove-Item2 should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSOwner should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.Set-NTFSSecurityDescriptor should stop after the first object for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed","Expected 0, but got 1." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Get-FileHash2 should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected 1, but got 2." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSOwner should stop at the debug message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for Select-Object -First 1 and change nothing else","Failed","Passed", +"Desktop","ServerAdmin","A later command that ends the pipeline or throws, for the item cmdlets on a share.With the messages of the verbose and debug streams.Set-NTFSSecurityDescriptor should stop at the verbose message for throw and change nothing else","Failed","Passed","Expected like wildcard '*Downstream failure*' to match $null, but it did not match." +"Desktop","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should find a file whose name contains brackets by that name with -Filter","Failed","Passed","Expected a collection with size 1, but got an empty collection." +"Desktop","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should reject a null -Filter","Failed","Passed","Expected an exception with message like '*'Filter'*' and FullyQualifiedErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' to be thrown, but the message was 'Cannot process argument because the value of" +"Desktop","ServerAdmin","Get-ChildItem2 -Filter on a share folder.Should return every item for -Filter *.*, also the ones without a dot in their names","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","ServerAdmin","InheritedFrom of access entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." +"Desktop","ServerAdmin","InheritedFrom of audit entries that Windows cannot resolve on a share.Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone","Failed","Passed","Expected strings to be the same, but they were different." diff --git a/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths.md b/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths.md new file mode 100644 index 0000000..aa15f61 --- /dev/null +++ b/Tests/Lab/Acceptance-2026-10-09-quality-gate-paths.md @@ -0,0 +1,176 @@ +# Quality-gate paths follow-up acceptance, 2026-10-09 + +Live acceptance, in the lab, of the behavior that the fixes of +`ai/quality-gate-paths` change, as the handoff table of the +[path report](../Coverage/Quality-Gate-Paths-2026-10-09.md) asks. The branch +(28 commits on `f11ff41`, the head of #117; head `83149ee`, draft #118) is a +local candidate, tested from its extracted package with `-ModulePath`. It is +not a published package, and this record is not a claim that the quality gate +is complete. Architecture and cmdlet-design choices remain with the +maintainer (Decisions 16, 21, and 22). + +## Method + +New live tests, case 10 and one test of the Server role, check what each fix +changed. The same tests, controller, and lab ran against two builds, in new +processes for each edition: the candidate (`83149ee`) and the baseline +(`f11ff41`, the base of the branch). A test is evidence of a fix when it +passes on the candidate and fails on the baseline; a test that passes on both +is a control. + +## Candidate and artifact identity + +| | Candidate | Baseline | +| --- | --- | --- | +| Commit | `83149eedee0684bd0a0522865abd0bc6127f6bf5` | `f11ff412947b35d682878ac4a8121c949868fcb2` | +| `NTFSSecurity.dll` SHA-256 | `40D0C8A6B819F15AE69A21D4D510B3B3CFCE2D93294368046C707BD558E67C1F` | `96F087E2AA39D521018346CC9F0A23C8AE2EE2D8CB39AE0E9B7A9325CF47AB73` | +| `NTFSSecurity.5.0.0-rc7.nupkg` SHA-256 | `2AAE3403A2D1C3AEE5156F05441E46B85B855AF95B46A7B73D2F80435513D71D` | `06244B161F76F3A9DCCCFDE3D7D6C5D0D5FEB625127FBF1B298D935BCBD8A2E2` | +| `NTFSSecurity.zip` SHA-256 | `A5AFA241DCA5DF87080A9801BB336282BD424D70DA395F6456F2D74B7FC8076A` | `3DF287C9AC4A311E4093DE519DED046B94109F51B513ACBC1653EF483DB3A2C0` | + +- Each build is a Release build (.NET Framework 4.5.2) in an isolated worktree + of its commit, packaged by `.github/scripts/New-ModulePackage.ps1`. Both + carry the label `5.0.0-rc7` and one assembly version, so every run used a + new process. All 11 files of each tested module folder equal the extracted + `NTFSSecurity.zip` byte for byte (SHA-256). +- Test source, identical in both runs (last written 20:56 and 20:54 UTC, + before the first run started): `NTFSSecurity.Live.Tests.ps1` (Git blob + `67b85efeb45af67070538f241c203c4afa38b6f4`) and + `Invoke-NTFSSecurityLabTest.ps1` (blob + `0b46427bc32b0b15449e283a2a6cf67879937541`). Both are in the commit that + adds this record. + +## Tests added + +Case 10 adds 78 tests per edition to the 166 of the acceptance at `3442194` +(244 in all): 75 in the roles on the client and 3 for the state that the file +server finds. The fixture adds the folder `Case10` with delegated Full +Control, the folder `Locked` that Administrators own, and files that +Administrators own for the cases of `Set-NTFSOwner`. + +| Describe (roles) | Tests | Fail on baseline | Fail on candidate | Fix | +| --- | ---: | ---: | ---: | --- | +| An item that the account owns and whose owner may not change its permissions (Delegate) | 2 | 2 | 0 | `c7a0383` owner restore | +| InheritedFrom of access entries that Windows cannot resolve (3 roles) | 3 | 3 | 0 | `2909a1c` | +| InheritedFrom of audit entries that Windows cannot resolve (ServerAdmin, Admin) | 2 | 2 | 0 | `2909a1c` | +| InheritedFrom of an item below a folder whose permissions the account cannot read (Delegate) | 2 | 1 | 0 | `2909a1c` | +| A later command that ends the pipeline or throws, for the item cmdlets (3 roles; 16 each) | 48 | 48 | 0 | `c77ecbf`, `40bf6a8` | +| A later command and the error of a folder that Get-ChildItem2 cannot read (Delegate) | 3 | 2 | 0 | `d44a200` | +| Get-ChildItem2 -Filter (3 roles; brackets, `*.*`, null) | 9 | 9 | 0 | `ee7c105`, `40bf6a8`, `ae3078f` | +| Privileges when a later command takes the debug messages (Delegate, Admin) | 6 | 4 | 0 | `d44a200` | +| State of the file server: only the first item changed (Server; one per role) | 3 | 3 | 0 | `c77ecbf`, `40bf6a8` | + +The tests that pass on the baseline are controls (a precondition, or the +privileges the cmdlets hold). `b14c90b` (public object APIs) has no lab +scenario; the package smoke below runs its unit tests. The fix of the leaked +native buffer has no observable guard. + +A first run of the new tests on the candidate in Windows PowerShell failed +five tests. All five were errors of the tests, not of the module: a native +`icacls` call that Pester's `Stop` turned into a terminating error, and +assertions that expected a descriptor to be written at a verbose stop, which +that stop prevents. The tests were corrected, and the runs below are complete +runs of the final test files. + +## Package smoke + +Before the lab run, the eight unit-test files that guard the fixes ran +against the extracted candidate package in a scratch tree, in the four +configurations of the report (650 cases each): elevated Desktop 643 passed, +elevated Core 642, basic Desktop 528, basic Core 527; none failed; 7, 8, 122, +and 123 were skipped by their own conditions, which the report's eligibility +check covers. + +## Lab and rollback evidence + +`WindowsAccessControlLab`: F1ADC1, F1BDC1, F2DC1, F3DC1, F1AFile1 (client), +and F1AFile2 (file server), all Windows Server 2025 (10.0.26100). At +20:41 UTC, authenticated WinRM, LDAP RootDSE, Kerberos tickets, member secure +channels, and clocks (skew at most 7 s) passed on all six machines. No +`NTFSSecurityLive` OU or `NtfsLive*` account existed before the run. No VM, +operating system, or network changed, and no other session or controller +process used the lab. + +Six checkpoints named `ntfs-qg-paths-83149ee-before-acceptance` were taken +at 20:45 to 20:46 UTC, one per machine. The policy of each machine is +Production, but Hyper-V reports the type Standard. As before, Production +classification is unverified, and no checkpoint was restored. + +## Live results + +Candidate run 21:02 to 21:19 UTC, baseline run 21:22 to 21:39 UTC, each in +Windows PowerShell 5.1 and PowerShell 7 against the extracted package. Both +editions gave the same counts in each build. + +| Build | Role | Passed | Failed | Skipped | +| --- | --- | ---: | ---: | ---: | +| Candidate | Delegate | 69 | 0 | 0 | +| Candidate | ServerAdmin | 34 | 0 | 0 | +| Candidate | Admin | 64 | 0 | 0 | +| Candidate | Server | 76 | 0 | 1 | +| Baseline | Delegate | 42 | 27 | 0 | +| Baseline | ServerAdmin | 13 | 21 | 0 | +| Baseline | Admin | 41 | 23 | 0 | +| Baseline | Server | 73 | 3 | 1 | + +Candidate, both editions: 486 passed, zero failed, two skipped; the skip is +the test that needs the module in the Server role, which doesn't import it. +Baseline, both editions: 338 passed, 148 failed, two skipped. Every role +exited 0 on the candidate. A joined verification of the result files (not of +the counts) found the same 488 tests in both builds, no duplicate, and every +one of the 148 baseline failures passed on the candidate. All 148 failures are +among the 156 tests of case 10 and the state test, which +[the results file](Acceptance-2026-10-09-quality-gate-paths-Results.csv) +lists with both results and the first line of the baseline message. + +What the baseline shows, from its messages: + +- Owner: `RestoreOwnerError ... (5) Access is denied` for the unchanged owner. +- `InheritedFrom`: a text of 13 characters instead of the 14 of + `unknown parent`. +- Later command: the second item changed after `Select-Object -First 1`; the + `Downstream failure` of a `throw` never reached the caller; and a `break` + of a later command didn't leave the caller's loop. +- `-Filter`: no result for a name with brackets; `*.*` returned only the + three names with a dot and dropped `NoExtension` and `NoExtensionFolder`; + `$null` gave `ArgumentNull` instead of the parameter validation error. +- Privileges: `TakeOwnership` still enabled after the pipeline stopped. + +The 21 `Select-Object -First 1` tests per edition carry no message on the +baseline and no line in the Pester log. The State test shows independently +that the baseline changed the second item for each role. + +## Cleanup and review + +Before the removal, the SIDs of the fixture were saved from the four domains +(10: seven in `a.forest1.net`, one each in `b.forest1.net`, `forest2.net`, +and `forest3.net`). The fixture was removed at 21:40 to 21:41 UTC with +`Invoke-NTFSSecurityLabTest.ps1 -RemoveFixture`. A separate read-only check +at 21:41 UTC, not the wrapper's marker, found in all four domains no +`NTFSSecurityLive` OU and no `NtfsLive*` account, and on F1AFile1 and +F1AFile2 no share, no `C:\NTFSSecurityLive` or `C:\NTFSSecurityLab`, no +`NtfsLiveLocal` group, no fixture member of Administrators, Access Control +Assistance Operators, or Remote Management Users, and no profile of the ten +SIDs. No checkpoint was restored. + +## Limits + +- `Get-NTFSAudit` below an unreadable parent folder can't be built here: an + account that may read the audit entries (it holds the Security privilege) + also reads the DACL of an Administrators-owned folder. The audit scenario + uses a file that was deleted after it was read, which reaches the same + `unknown parent` text. +- The run covers the candidate package from disk (`-ModulePath`), not the + published package, one lab, and Windows Server 2025 only. The other + operating systems of Decision 21, the acceptance of the published + prerelease, and the answer of a non-Windows file server (#34) stay with the + other gates. The stable version remains 4.2.6. +- The candidate and the baseline differ only by the 28 commits; the test and + controller files are the same. + +## Evidence + +The result files, logs, hashes, readiness, checkpoint, snapshot, and cleanup +logs of both runs are in the session artifact +`4b12e2f4-d4c7-4a5d-883a-ddb7421c4848\files\lab-qg-paths` (local, not in Git). +The per-test results of case 10 are in +[the results file](Acceptance-2026-10-09-quality-gate-paths-Results.csv). diff --git a/Tests/Lab/Invoke-NTFSSecurityLabTest.ps1 b/Tests/Lab/Invoke-NTFSSecurityLabTest.ps1 index 5ba6514..0b46427 100644 --- a/Tests/Lab/Invoke-NTFSSecurityLabTest.ps1 +++ b/Tests/Lab/Invoke-NTFSSecurityLabTest.ps1 @@ -723,6 +723,27 @@ $fixtureScript = { } ) + # Case 10: the behavior that the fixes of the quality gate before 5.0.0 changed. The tests create their items below the + # folder of their role, which the delegated group fully controls. Administrators own the folder Locked, whose + # permissions the delegated account denies itself, and the files that Set-NTFSOwner changes: a file that the account + # created would be owned by the account already. + $null = New-FixtureFolder -RelativePath 'Case10' -AccessRule $delegatesFullControl + $null = New-FixtureFolder -RelativePath 'Case10\Locked' + foreach ($role in 'Admin', 'ServerAdmin', 'Delegate') { + foreach ($style in 'Select', 'Throw') { + foreach ($ownerFolder in "SetOwner-$style", "SetOwner-Debug$style") { + $ownerPath = New-FixtureFolder -RelativePath "Case10\$role\LaterCommand\$ownerFolder" + foreach ($name in 'First', 'Second') { + $file = Join-Path -Path $ownerPath -ChildPath "$name.txt" + Set-Content -LiteralPath $file -Value $name -NoNewline + if ((Get-LabSecurityDescriptor -Path $file).Owner.Value -ne 'S-1-5-32-544') { + throw "Administrators don't own '$file'." + } + } + } + } + } + # The rights that the file server's own token of each foreign account gets on the folder, like case 3. A token # that the file server can't create is reported as -1, which fails only the effective-access test of the account. $foreignDescriptor = Get-LabSecurityDescriptor -Path $foreignPath diff --git a/Tests/Lab/NTFSSecurity.Live.Tests.ps1 b/Tests/Lab/NTFSSecurity.Live.Tests.ps1 index 8ba8939..67b85ef 100644 --- a/Tests/Lab/NTFSSecurity.Live.Tests.ps1 +++ b/Tests/Lab/NTFSSecurity.Live.Tests.ps1 @@ -104,6 +104,31 @@ BeforeDiscovery { } } ) + + # Case 10: the cmdlets that a later command in the pipeline stops, and the roles whose items the file server checks. + $laterCommandCases = @( + foreach ($name in 'Remove-Item2', 'Copy-Item2', 'Move-Item2', 'Set-NTFSOwner', 'Set-NTFSSecurityDescriptor') { + foreach ($style in 'Select-Object -First 1', 'throw') { + @{ Name = $name; Style = $style } + } + } + ) + $laterCommandStreamCases = @( + foreach ($case in @( + @{ Name = 'Set-NTFSSecurityDescriptor'; Stream = 'verbose' } + @{ Name = 'Get-FileHash2'; Stream = 'verbose' } + @{ Name = 'Set-NTFSOwner'; Stream = 'debug' } + )) { + foreach ($style in 'Select-Object -First 1', 'throw') { + @{ Name = $case.Name; Stream = $case.Stream; Style = $style } + } + } + ) + $laterCommandStates = @( + foreach ($stateRole in 'Admin', 'ServerAdmin', 'Delegate') { + @{ StateRole = $stateRole } + } + ) } BeforeAll { @@ -846,6 +871,538 @@ Describe 'Accounts of another domain and of other forests on share folders' -Tag } } +# Case 10: the behavior that the fixes of the quality gate before 5.0.0 changed. Each test works in a folder of its role below +# Case10, which the delegated group fully controls, and fails on a build before the fix that its comment names. +Describe 'An item that the account owns and whose owner may not change its permissions on a share' -Tag 'Delegate' -Skip:(-not $configured) { + # The delegated account owns what it creates on the share. A deny entry for OWNER RIGHTS replaces the right of the owner to + # change the DACL, so the cmdlets take ownership for the write, which the file server answers by removing that entry. Once + # the DACL is cleared and protected, nobody holds the right to set an owner. Before 5.0.0, the cmdlets set the previous + # owner back also when it was the account itself: the file server refused it, and they reported a RestoreOwnerError for an + # owner that had not changed. + BeforeAll { + $folder = Get-LabPath -RelativePath "Case10\$Role\Owner" + $null = New-Item -ItemType Directory -Path $folder -Force + $ownerRights = 'S-1-3-4' + $protectedFlag = [System.Security.AccessControl.ControlFlags]::DiscretionaryAclProtected + + function New-LabUnchangeableFile { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only writes to the folder of the run.' + )] + param ([string] $Name) + + $file = Join-Path -Path $folder -ChildPath $Name + Set-Content -LiteralPath $file -Value $Name -NoNewline + Get-LabOwner -Path $file | Should -Be $configuration.Accounts.$Role.Sid -Because 'the account owns what it creates' + Add-NTFSAccess -Path $file -Account $ownerRights -AccessType Deny -AccessRights ChangePermissions -ErrorAction Stop + # icacls reports the refusal on its error stream, which a terminating error action would turn into an exception + $savedPreference = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + try { + $null = & icacls.exe $file /grant '*S-1-1-0:(R)' 2>&1 + $exitCode = $LASTEXITCODE + } + finally { + $ErrorActionPreference = $savedPreference + } + + $exitCode | Should -Not -Be 0 -Because 'a plain write of the DACL fails for the owner now' + $file + } + + function Assert-LabClearedDescriptor { + param ([string] $File) + + $descriptor = Get-LabSecurityDescriptor -Path $File + $descriptor.Owner.Value | Should -Be $configuration.Accounts.$Role.Sid + ($descriptor.ControlFlags -band $protectedFlag) | Should -Be $protectedFlag + $null -ne $descriptor.DiscretionaryAcl | Should -BeTrue -Because 'the DACL is empty, not NULL' + $descriptor.DiscretionaryAcl.Count | Should -Be 0 + } + } + + It 'Clear-NTFSAccess -DisableInheritance should take ownership, clear and protect the DACL, and report no RestoreOwnerError' { + $file = New-LabUnchangeableFile -Name 'Clear.txt' + + Clear-NTFSAccess -Path $file -DisableInheritance -ErrorVariable operationErrors -ErrorAction SilentlyContinue + + Format-LabError -ErrorRecord $operationErrors | Should -BeNullOrEmpty + Assert-LabClearedDescriptor -File $file + } + + It 'Set-NTFSSecurityDescriptor should write the cleared DACL and report no RestoreOwnerError' { + $file = New-LabUnchangeableFile -Name 'Descriptor.txt' + $descriptor = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop + Clear-NTFSAccess -SecurityDescriptor $descriptor -DisableInheritance -ErrorAction Stop + + Set-NTFSSecurityDescriptor -SecurityDescriptor $descriptor -ErrorVariable operationErrors -ErrorAction SilentlyContinue + + Format-LabError -ErrorRecord $operationErrors | Should -BeNullOrEmpty + Assert-LabClearedDescriptor -File $file + } +} + +# Windows can't name the folders that the inherited entries of an item come from when the item is gone, for example deleted by +# another process after its security descriptor was read, or when a folder above it can't be read. The entries still come +# back. Before 5.0.0, the text lost its last character, and an explicit entry, which has no source, got it as well. +Describe 'InheritedFrom of access entries that Windows cannot resolve on a share' -Tag 'Delegate', 'ServerAdmin', 'Admin' -Skip:(-not $configured) { + BeforeAll { + $folder = Get-LabPath -RelativePath "Case10\$Role\InheritedFrom" + $null = New-Item -ItemType Directory -Path $folder -Force + } + + It 'Should name an unknown parent for an inherited entry and no source for an explicit entry when the file is gone' { + $file = Join-Path -Path $folder -ChildPath 'Gone.txt' + Set-Content -LiteralPath $file -Value 'Gone' -NoNewline + Add-NTFSAccess -Path $file -Account $everyone -AccessRights ReadData -ErrorAction Stop + $descriptor = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop + Remove-Item -LiteralPath $file -Force + + $entries = @([Security2.FileSystemAccessRule2]::GetFileSystemAccessRules($descriptor, $true, $true, $true)) + + $inherited = @($entries | Where-Object -FilterScript { $_.IsInherited }) + $inherited | Should -Not -BeNullOrEmpty + foreach ($entry in $inherited) { + $entry.InheritedFrom | Should -BeExactly 'unknown parent' + } + + $explicit = @($entries | Where-Object -FilterScript { -not $_.IsInherited }) + $explicit | Should -HaveCount 1 + $explicit[0].InheritedFrom | Should -BeNullOrEmpty + } +} + +Describe 'InheritedFrom of audit entries that Windows cannot resolve on a share' -Tag 'ServerAdmin', 'Admin' -Skip:(-not $configured) { + # The administrators of the file server read and change the audit entries over SMB (case 2). + BeforeAll { + $folder = Get-LabPath -RelativePath "Case10\$Role\InheritedFromAudit" + $null = New-Item -ItemType Directory -Path $folder -Force + Add-NTFSAudit -Path $folder -Account $everyone -AccessRights ReadData -InheritanceFlags 'ContainerInherit, ObjectInherit' -PropagationFlags None -ErrorAction Stop + } + + It 'Should name an unknown parent for an inherited audit entry and no source for an explicit entry when the file is gone' { + $file = Join-Path -Path $folder -ChildPath 'Gone.txt' + Set-Content -LiteralPath $file -Value 'Gone' -NoNewline + Add-NTFSAudit -Path $file -Account 'S-1-5-32-546' -AccessRights Delete -InheritanceFlags None -PropagationFlags None -ErrorAction Stop + $descriptor = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop + Remove-Item -LiteralPath $file -Force + + $entries = @([Security2.FileSystemAuditRule2]::GetFileSystemAuditRules($descriptor, $true, $true, $true)) + + $inherited = @($entries | Where-Object -FilterScript { $_.IsInherited }) + $inherited | Should -HaveCount 1 + $inherited[0].InheritedFrom | Should -BeExactly 'unknown parent' + $explicit = @($entries | Where-Object -FilterScript { -not $_.IsInherited }) + $explicit | Should -HaveCount 1 + $explicit[0].InheritedFrom | Should -BeNullOrEmpty + } +} + +Describe 'InheritedFrom of an item below a folder on a share whose permissions the account cannot read' -Tag 'Delegate' -Skip:(-not $configured) { + # Administrators own the folder, so a deny entry for the delegated account takes effect for it. The entry applies to the + # folder only: the item below it keeps its entries and stays readable. + BeforeAll { + $locked = Get-LabPath -RelativePath 'Case10\Locked' + $child = Join-Path -Path $locked -ChildPath 'Child.txt' + Set-Content -LiteralPath $child -Value 'Child' -NoNewline + Add-NTFSAccess -Path $child -Account $everyone -AccessRights ReadData -ErrorAction Stop + Add-NTFSAccess -Path $locked -Account $configuration.Accounts.Delegate.Sid -AccessType Deny -AccessRights ReadPermissions -AppliesTo ThisFolderOnly -ErrorAction Stop + } + + It 'Should start with a folder whose permissions the account cannot read, and an item that it can' { + { Get-Acl -LiteralPath $locked -ErrorAction Stop } | Should -Throw + { Get-Acl -LiteralPath $child -ErrorAction Stop } | Should -Not -Throw + } + + It 'Get-NTFSAccess should name an unknown parent for an inherited entry and no source for an explicit entry' { + $entries = @(Get-NTFSAccess -Path $child -ErrorVariable operationErrors -ErrorAction SilentlyContinue) + + Format-LabError -ErrorRecord $operationErrors | Should -BeNullOrEmpty + $inherited = @($entries | Where-Object -FilterScript { $_.IsInherited }) + $inherited | Should -Not -BeNullOrEmpty + foreach ($entry in $inherited) { + $entry.InheritedFrom | Should -BeExactly 'unknown parent' + } + + $explicit = @($entries | Where-Object -FilterScript { -not $_.IsInherited -and $_.Account.Sid -eq $everyone }) + $explicit | Should -HaveCount 1 + $explicit[0].InheritedFrom | Should -BeNullOrEmpty + } +} + +# Before 5.0.0, a cmdlet took what a later command ended the pipeline with (Select-Object -First, a break) or threw for a failure +# of the item and went on with the next item: Remove-Item2 removed every item after Select-Object -First 1, and the caller +# never saw a throw. Each case runs one command over two items and the file server checks the items afterwards (role Server). +Describe 'A later command that ends the pipeline or throws, for the item cmdlets on a share' -Tag 'Delegate', 'ServerAdmin', 'Admin' -Skip:(-not $configured) { + BeforeAll { + $account = $configuration.Accounts.$Role.Sid + $caseRoot = Get-LabPath -RelativePath "Case10\$Role\LaterCommand" + $privateData = (Get-Module -Name NTFSSecurity).PrivateData + $savedEnablePrivileges = $privateData['EnablePrivileges'] + + function Get-LabSlug { + param ([string] $Style) + + if ($Style -eq 'throw') { 'Throw' } else { 'Select' } + } + + function New-LabCaseFolder { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only writes to the folder of the run.' + )] + param ([string] $Name) + + $path = Join-Path -Path $caseRoot -ChildPath $Name + $null = New-Item -ItemType Directory -Path $path -Force + $path + } + + function New-LabPair { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only writes to the folder of the run.' + )] + param ([string] $Name) + + $directory = New-LabCaseFolder -Name $Name + foreach ($item in 'First', 'Second') { + Set-Content -LiteralPath (Join-Path -Path $directory -ChildPath "$item.txt") -Value $item -NoNewline + } + + @{ + Directory = $directory + First = (Join-Path -Path $directory -ChildPath 'First.txt') + Second = (Join-Path -Path $directory -ChildPath 'Second.txt') + } + } + + # Each case runs one command over the two items of its context. Untouched tells whether the second item is as it was, + # which it is only when the command stopped after the first one. + $cases = @{ + 'Remove-Item2' = @{ + Prepare = { param ($Slug) New-LabPair -Name "RemoveItem2-$Slug" } + Run = { param ($Context) Remove-Item2 -Path $Context.First, $Context.Second -PassThru -ErrorAction SilentlyContinue } + Untouched = { param ($Context) Test-Path -LiteralPath $Context.Second } + } + 'Copy-Item2' = @{ + Prepare = { + param ($Slug) + $context = New-LabPair -Name "CopyItem2-$Slug" + $context.Destination = New-LabCaseFolder -Name "CopyItem2-$Slug-To" + $context + } + Run = { param ($Context) Copy-Item2 -Path $Context.First, $Context.Second -Destination $Context.Destination -PassThru $true -ErrorAction SilentlyContinue } + Untouched = { param ($Context) -not (Test-Path -LiteralPath (Join-Path -Path $Context.Destination -ChildPath 'Second.txt')) } + } + 'Move-Item2' = @{ + Prepare = { + param ($Slug) + $context = New-LabPair -Name "MoveItem2-$Slug" + $context.Destination = New-LabCaseFolder -Name "MoveItem2-$Slug-To" + $context + } + Run = { param ($Context) Move-Item2 -Path $Context.First, $Context.Second -Destination $Context.Destination -PassThru $true -ErrorAction SilentlyContinue } + Untouched = { param ($Context) Test-Path -LiteralPath $Context.Second } + } + # The files of the fixture are owned by Administrators, so that the first one changes its owner. + 'Set-NTFSOwner' = @{ + Prepare = { + param ($Slug) + $directory = Join-Path -Path $caseRoot -ChildPath "SetOwner-$Slug" + @{ First = (Join-Path -Path $directory -ChildPath 'First.txt'); Second = (Join-Path -Path $directory -ChildPath 'Second.txt') } + } + Run = { param ($Context) Set-NTFSOwner -Path $Context.First, $Context.Second -Account $account -PassThru -ErrorAction SilentlyContinue } + Untouched = { param ($Context) (Get-LabOwner -Path $Context.Second) -eq $administrators } + } + 'Set-NTFSSecurityDescriptor' = @{ + Prepare = { + param ($Slug) + $context = New-LabPair -Name "SetDescriptor-$Slug" + $context.Descriptors = @(Get-NTFSSecurityDescriptor -Path $context.First, $context.Second -ErrorAction Stop) + Add-NTFSAccess -SecurityDescriptor $context.Descriptors -Account $everyone -AccessRights ReadData -ErrorAction Stop + $context + } + Run = { param ($Context) Set-NTFSSecurityDescriptor -SecurityDescriptor $Context.Descriptors -PassThru -ErrorAction SilentlyContinue } + Untouched = { param ($Context) -not (Get-LabExplicitAccessRule -Path $Context.Second -Sid $everyone) } + } + } + + # The command writes a verbose or a debug message inside the try of its loop, which the later command takes. With the + # privileges enabled, the cmdlet writes a message before that, outside the try, so the module setting is off for these + # cases. Get-FileHash2 skips the folder that comes first with a verbose message. + $streamCases = @{ + 'Set-NTFSSecurityDescriptor/verbose' = @{ + Prepare = $cases['Set-NTFSSecurityDescriptor'].Prepare + Run = { param ($Context) Set-NTFSSecurityDescriptor -SecurityDescriptor $Context.Descriptors -Verbose -ErrorAction SilentlyContinue 4>&1 } + Untouched = $cases['Set-NTFSSecurityDescriptor'].Untouched + RecordType = [System.Management.Automation.VerboseRecord] + } + 'Get-FileHash2/verbose' = @{ + Prepare = { + param ($Slug) + @{ + First = (New-LabCaseFolder -Name "FileHash2-$Slug-Folder") + File = (New-LabPair -Name "FileHash2-$Slug").First + } + } + Run = { param ($Context) Get-FileHash2 -Path $Context.First, $Context.File -Verbose -ErrorAction SilentlyContinue 4>&1 } + RecordType = [System.Management.Automation.VerboseRecord] + } + 'Set-NTFSOwner/debug' = @{ + Prepare = $cases['Set-NTFSOwner'].Prepare + Run = { param ($Context) Set-NTFSOwner -Path $Context.First, $Context.Second -Account $account -ErrorAction SilentlyContinue 5>&1 } + Untouched = $cases['Set-NTFSOwner'].Untouched + RecordType = [System.Management.Automation.DebugRecord] + } + } + + function Assert-LabPipelineStop { + param ([hashtable] $Case, [string] $Slug, [string] $Stream) + + if ($Stream -eq 'debug') { $DebugPreference = 'Continue' } + $context = & $Case.Prepare $Slug + $Error.Clear() + + $result = @(& $Case.Run $context | Select-Object -First 1) + + $result | Should -HaveCount 1 + if ($Case.RecordType) { + $result[0] | Should -BeOfType $Case.RecordType + } + + $Error.Count | Should -Be 0 + if ($Case.Untouched) { + (& $Case.Untouched $context) | Should -BeTrue + } + } + + # A later command that throws ends the pipeline for the commands before it. The error is the caller's: the cmdlet must + # neither report it as an error of an item nor go on with the next item. + function Assert-LabDownstreamFailure { + param ([hashtable] $Case, [string] $Slug, [string] $Stream) + + if ($Stream -eq 'debug') { $DebugPreference = 'Continue' } + $context = & $Case.Prepare $Slug + $emitted = 0 + $caught = $null + $Error.Clear() + try { + & $Case.Run $context | ForEach-Object -Process { + $emitted++ + throw 'Downstream failure' + } + } + catch { + $caught = $_ + } + + $caught.Exception.Message | Should -BeLike '*Downstream failure*' + $emitted | Should -Be 1 + @($Error | Where-Object -FilterScript { $_.Exception.Message -notlike '*Downstream failure*' }) | Should -BeNullOrEmpty + if ($Case.Untouched) { + (& $Case.Untouched $context) | Should -BeTrue + } + } + } + + It ' should stop after the first object for