diff --git a/.memory-bank/activeContext.md b/.memory-bank/activeContext.md index 915ec7e..609b3fd 100644 --- a/.memory-bank/activeContext.md +++ b/.memory-bank/activeContext.md @@ -42,16 +42,22 @@ the PRs, and tags `5.0.0-rc2` after the merges. Review: Dependabot PRs ran unreviewed actions in a job with `contents: write`; the wiki preview is now read-only (`publish-wiki`). - `ai/defects-a` fixes defects 1 to 13 and the same repeat bug in - `Get-NTFSAccess` (found with 4): Windows PowerShell 310 passed, 17 - skipped; PowerShell 7 280 passed, 47 skipped (327 tests). 10 tests need - privileges and run only in CI. + `Get-NTFSAccess` (found with 4); its review fixes are in the last + commit: Windows PowerShell 312 passed, 17 skipped; PowerShell 7 282 + passed, 47 skipped (329 tests). - `ai/defects-b` fixes defects 14 to 17 (`-AppliesTo` is mandatory in the - `Simple` sets; `-RemoveSpecific` is back): Windows PowerShell 331 passed, - 19 skipped; PowerShell 7 301 passed, 49 skipped (350 tests). + `Simple` sets; `-RemoveSpecific` is back): Windows PowerShell 333 passed, + 19 skipped; PowerShell 7 303 passed, 49 skipped (352 tests). - `ai/defects-c` fixes defects 18 to 21, the same missing `continue` in - `Remove-NTFSAudit`, and a stale hash in `Get-FileHash2`. + `Remove-NTFSAudit`, and a stale hash in `Get-FileHash2`. Its review + found that a failed retry after taking ownership left the owner changed; + `BaseCmdlet.InvokeAsOwner` now restores it: Windows PowerShell 356 + passed, 20 skipped; PowerShell 7 325 passed, 51 skipped (376 tests). +- `ai/defects-d` fixes defects 22 to 24 and `-PassThru` under `-WhatIf` in + the `*-Item2` cmdlets: Windows PowerShell 379 passed, 23 skipped; + PowerShell 7 348 passed, 54 skipped (402 tests). ## Next step -Group D (22 to 24) on `ai/defects-d`, then the E decisions and the bugs -from the issue triage (`ai/issue-fixes`). +The E decisions on `ai/decisions-e` (Decision 13 for `Set-NTFSInheritance`), +then the bugs from the issue triage (`ai/issue-fixes`) and 5.0.0-rc2. diff --git a/.memory-bank/progress.md b/.memory-bank/progress.md index bba0bb4..c3a08e7 100644 --- a/.memory-bank/progress.md +++ b/.memory-bank/progress.md @@ -154,7 +154,7 @@ Numbered as agreed with the maintainer; each is documented on its page. after a failed change, and a failed retry after taking ownership left the owner changed; `BaseCmdlet.InvokeAsOwner` now restores it on every path. -#### D: Metadata and cosmetics +#### D: Metadata and cosmetics (fixed on `ai/defects-d`, not merged) - (22) Wrong or missing `[OutputType]` (`Test-Path2`, `Get-FileHash2`, `Add-NTFSAudit`, `*-Item2`, inheritance cmdlets); @@ -163,6 +163,8 @@ Numbered as agreed with the maintainer; each is documented on its page. - (23) Typos: "Privliege" in the `Get-NTFSEffectiveAccess` warning; "are now enabled" in the `Disable-Privileges` verbose message. - (24) Dead code in `RemoveItem2.cs` and `OtherCmdlets.cs`. +- Found with group D: `-PassThru` of the `*-Item2` cmdlets wrote the item + also when `-WhatIf` skipped the operation. #### E: Maintainer decisions before changing behavior diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c73265..3d38308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,5 +134,20 @@ The format is based on - Fix the cmdlets that take ownership of an item to repeat an operation that was denied: when the second attempt failed as well, the account that ran the cmdlet stayed the owner of the item; now the previous owner is restored +- Fix `-PassThru` of `Enable-Privileges` and `Disable-Privileges`, which + wrote the privileges as one collection instead of one object per + privilege, and of `New-NTFSSymbolicLink`, which returned a file object for + a link to a folder +- Declare the output types of `Test-Path2`, `Get-FileHash2`, + `Add-NTFSAudit`, `Remove-NTFSAudit`, `Copy-Item2`, `Move-Item2`, + `Remove-Item2`, and the inheritance cmdlets correctly, so that + `Get-Command` and tab completion report the objects they write +- Fix the verbose messages of `Copy-Item2` and `Move-Item2`, which named the + source path as the destination, and of `Disable-Privileges`, which said + that the privileges were enabled, and the spelling of the privilege in + the warning of `Get-NTFSEffectiveAccess` +- Fix `-PassThru` of `Copy-Item2`, `Move-Item2`, and `Remove-Item2`, which + wrote the item also when `-WhatIf` or a declined confirmation skipped the + operation [Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD diff --git a/Docs/Cmdlets/Copy-Item2.md b/Docs/Cmdlets/Copy-Item2.md index 9bd30c6..0d78423 100644 --- a/Docs/Cmdlets/Copy-Item2.md +++ b/Docs/Cmdlets/Copy-Item2.md @@ -176,14 +176,20 @@ You can pipe an object that has a `Destination` property to supply the target of ## OUTPUTS -### System.Object +### Alphaleonis.Win32.Filesystem.FileInfo -By default this cmdlet returns nothing. With `-PassThru $true` it returns an `Alphaleonis.Win32.Filesystem.FileInfo` or `Alphaleonis.Win32.Filesystem.DirectoryInfo` object for each item that it copied. +By default this cmdlet returns nothing. With `-PassThru $true` it returns a file object for each file that it copied. + +### Alphaleonis.Win32.Filesystem.DirectoryInfo + +With `-PassThru $true` the cmdlet returns a folder object for each folder that it copied. ## NOTES `Copy-Item2` copies through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it handles source and destination paths that exceed the 260-character `MAX_PATH` limit of the built-in `Copy-Item` cmdlet. +Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. + Before 5.0.0, copying a folder that contained files failed with a `CopyError` that reported a `DirectoryNotFoundException` for the first file in the folder. If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call. diff --git a/Docs/Cmdlets/Disable-NTFSAccessInheritance.md b/Docs/Cmdlets/Disable-NTFSAccessInheritance.md index fc19225..0a8ca64 100644 --- a/Docs/Cmdlets/Disable-NTFSAccessInheritance.md +++ b/Docs/Cmdlets/Disable-NTFSAccessInheritance.md @@ -156,7 +156,7 @@ You can pipe the security descriptors that `Get-NTFSSecurityDescriptor` returns ## OUTPUTS -### System.Object +### Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. diff --git a/Docs/Cmdlets/Disable-NTFSAuditInheritance.md b/Docs/Cmdlets/Disable-NTFSAuditInheritance.md index 18ad002..4fca4bc 100644 --- a/Docs/Cmdlets/Disable-NTFSAuditInheritance.md +++ b/Docs/Cmdlets/Disable-NTFSAuditInheritance.md @@ -156,7 +156,7 @@ You can pipe the security descriptors that `Get-NTFSSecurityDescriptor` returns ## OUTPUTS -### System.Object +### Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. diff --git a/Docs/Cmdlets/Disable-Privileges.md b/Docs/Cmdlets/Disable-Privileges.md index 0d48b8f..223adff 100644 --- a/Docs/Cmdlets/Disable-Privileges.md +++ b/Docs/Cmdlets/Disable-Privileges.md @@ -93,7 +93,7 @@ This cmdlet does not accept pipeline input. ### ProcessPrivileges.PrivilegeAndAttributes -With `-PassThru`, the cmdlet writes the privilege collection of the current process. The pipeline enumerates it into one `ProcessPrivileges.PrivilegeAndAttributes` object per privilege, each with a `Privilege`, a `PrivilegeAttributes`, and a `PrivilegeState` property. Without `-PassThru`, the cmdlet writes nothing. +With `-PassThru`, the cmdlet writes one `ProcessPrivileges.PrivilegeAndAttributes` object per privilege of the current process, each with a `Privilege`, a `PrivilegeAttributes`, and a `PrivilegeState` property. Without `-PassThru`, the cmdlet writes nothing. Before 5.0.0, it wrote the privileges as one collection. ## NOTES diff --git a/Docs/Cmdlets/Enable-NTFSAccessInheritance.md b/Docs/Cmdlets/Enable-NTFSAccessInheritance.md index 0a8b6da..6f38c39 100644 --- a/Docs/Cmdlets/Enable-NTFSAccessInheritance.md +++ b/Docs/Cmdlets/Enable-NTFSAccessInheritance.md @@ -155,7 +155,7 @@ You can pipe the security descriptors that `Get-NTFSSecurityDescriptor` returns ## OUTPUTS -### System.Object +### Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. diff --git a/Docs/Cmdlets/Enable-NTFSAuditInheritance.md b/Docs/Cmdlets/Enable-NTFSAuditInheritance.md index 4765a1b..36fd685 100644 --- a/Docs/Cmdlets/Enable-NTFSAuditInheritance.md +++ b/Docs/Cmdlets/Enable-NTFSAuditInheritance.md @@ -155,7 +155,7 @@ You can pipe the security descriptors that `Get-NTFSSecurityDescriptor` returns ## OUTPUTS -### System.Object +### Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. diff --git a/Docs/Cmdlets/Enable-Privileges.md b/Docs/Cmdlets/Enable-Privileges.md index a8c7fa3..cc0c479 100644 --- a/Docs/Cmdlets/Enable-Privileges.md +++ b/Docs/Cmdlets/Enable-Privileges.md @@ -95,7 +95,7 @@ This cmdlet does not accept pipeline input. ### ProcessPrivileges.PrivilegeAndAttributes -With `-PassThru`, the cmdlet writes the privilege collection of the current process. The pipeline enumerates it into one `ProcessPrivileges.PrivilegeAndAttributes` object per privilege, each with a `Privilege`, a `PrivilegeAttributes`, and a `PrivilegeState` property. Without `-PassThru`, the cmdlet writes nothing. +With `-PassThru`, the cmdlet writes one `ProcessPrivileges.PrivilegeAndAttributes` object per privilege of the current process, each with a `Privilege`, a `PrivilegeAttributes`, and a `PrivilegeState` property. Without `-PassThru`, the cmdlet writes nothing. Before 5.0.0, it wrote the privileges as one collection. ## NOTES diff --git a/Docs/Cmdlets/Get-FileHash2.md b/Docs/Cmdlets/Get-FileHash2.md index 6dd281e..783aa4d 100644 --- a/Docs/Cmdlets/Get-FileHash2.md +++ b/Docs/Cmdlets/Get-FileHash2.md @@ -111,9 +111,9 @@ You can supply the `-Algorithm` value through a pipeline object that has an `Alg ## OUTPUTS -### Security2.FileSystemAccessRule2 +### Alphaleonis.Win32.Filesystem.FileInfo -The cmdlet does not return access rules. For every hashed file it writes the file object of that file, decorated with the type name `Alphaleonis.Win32.Filesystem.FileInfo+Hash` and extended with the `Hash` and `Algorithm` note properties, so all regular file properties such as `FullName`, `Name`, and `Length` remain available. +For every hashed file, the cmdlet writes the file object of that file, decorated with the type name `Alphaleonis.Win32.Filesystem.FileInfo+Hash` and extended with the `Hash` and `Algorithm` note properties, so all regular file properties such as `FullName`, `Name`, and `Length` remain available. ## NOTES diff --git a/Docs/Cmdlets/Move-Item2.md b/Docs/Cmdlets/Move-Item2.md index b4f3eff..06d409f 100644 --- a/Docs/Cmdlets/Move-Item2.md +++ b/Docs/Cmdlets/Move-Item2.md @@ -176,14 +176,20 @@ You can pipe an object that has a `Destination` property to supply the target of ## OUTPUTS -### System.Object +### Alphaleonis.Win32.Filesystem.FileInfo -By default this cmdlet returns nothing. With `-PassThru $true` it returns an `Alphaleonis.Win32.Filesystem.FileInfo` or `Alphaleonis.Win32.Filesystem.DirectoryInfo` object for each item that it moved, pointing at the new location. +By default this cmdlet returns nothing. With `-PassThru $true` it returns a file object for each file that it moved, pointing at the new location. + +### Alphaleonis.Win32.Filesystem.DirectoryInfo + +With `-PassThru $true` the cmdlet returns a folder object for each folder that it moved, pointing at the new location. ## NOTES `Move-Item2` moves through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it handles source and destination paths that exceed the 260-character `MAX_PATH` limit of the built-in `Move-Item` cmdlet. +Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. + The cmdlet chooses between two mutually exclusive move options. Without `-Force` it moves with `CopyAllowed`, which permits a file to cross volume boundaries because Windows then copies and deletes it. With `-Force` it moves with `ReplaceExisting`, which overwrites the destination but does not request `CopyAllowed`, so a move across volumes can fail when `-Force` is specified. If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call. diff --git a/Docs/Cmdlets/New-NTFSSymbolicLink.md b/Docs/Cmdlets/New-NTFSSymbolicLink.md index 2be53db..b1757ff 100644 --- a/Docs/Cmdlets/New-NTFSSymbolicLink.md +++ b/Docs/Cmdlets/New-NTFSSymbolicLink.md @@ -124,11 +124,11 @@ You can pass the path of the new link and the path of the target as strings. ### Alphaleonis.Win32.Filesystem.FileInfo -With `-PassThru`, the cmdlet writes a file object for the new link. The cmdlet writes that object type for a link to a folder as well. Without `-PassThru`, the cmdlet writes nothing. +With `-PassThru`, the cmdlet writes a file object for a new link to a file. Without `-PassThru`, the cmdlet writes nothing. ### Alphaleonis.Win32.Filesystem.DirectoryInfo -The cmdlet does not write folder objects. A directory symbolic link is also returned as a file object. +With `-PassThru`, the cmdlet writes a folder object for a new link to a folder. Before 5.0.0, it wrote a file object for those links as well. ## NOTES diff --git a/Docs/Cmdlets/Remove-Item2.md b/Docs/Cmdlets/Remove-Item2.md index 0669f84..cda6435 100644 --- a/Docs/Cmdlets/Remove-Item2.md +++ b/Docs/Cmdlets/Remove-Item2.md @@ -169,14 +169,20 @@ You can pipe one or more paths to this cmdlet, either as strings or as objects t ## OUTPUTS -### System.Object +### Alphaleonis.Win32.Filesystem.FileInfo -By default this cmdlet returns nothing. With `-PassThru` it returns an `Alphaleonis.Win32.Filesystem.FileInfo` or `Alphaleonis.Win32.Filesystem.DirectoryInfo` object for each item that it deleted. +By default this cmdlet returns nothing. With `-PassThru` it returns a file object for each file that it deleted. + +### Alphaleonis.Win32.Filesystem.DirectoryInfo + +With `-PassThru` the cmdlet returns a folder object for each folder that it deleted. ## NOTES `Remove-Item2` deletes through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it reaches items whose path exceeds the 260-character `MAX_PATH` limit of the built-in `Remove-Item` cmdlet. Deletion is permanent; the cmdlet does not use the Recycle Bin. +Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. + The module defines the aliases `rm2` and `del2` for this cmdlet. A path that does not exist causes the error `FileNotFound`, and a deletion that the file system rejects causes a `DeleteError`. In both cases the cmdlet continues with the next path. Before 5.0.0, a path that did not exist made the cmdlet skip the remaining paths that were passed in the same call. diff --git a/Docs/Cmdlets/Set-NTFSInheritance.md b/Docs/Cmdlets/Set-NTFSInheritance.md index 34c141f..be5a2d4 100644 --- a/Docs/Cmdlets/Set-NTFSInheritance.md +++ b/Docs/Cmdlets/Set-NTFSInheritance.md @@ -178,7 +178,7 @@ You can supply `-AccessInheritanceEnabled` and `-AuditInheritanceEnabled` throug ## OUTPUTS -### System.Object +### Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. diff --git a/Docs/Cmdlets/Test-Path2.md b/Docs/Cmdlets/Test-Path2.md index ee925f0..41e14d6 100644 --- a/Docs/Cmdlets/Test-Path2.md +++ b/Docs/Cmdlets/Test-Path2.md @@ -109,13 +109,9 @@ You can supply the `-PathType` value through a pipeline object that has a `PathT ## OUTPUTS -### Alphaleonis.Win32.Filesystem.FileInfo +### System.Boolean -`Test-Path2` does not write file objects. For each path it writes a single `System.Boolean` value that is `$true` when the item exists and matches `-PathType`, and `$false` otherwise. - -### Alphaleonis.Win32.Filesystem.DirectoryInfo - -`Test-Path2` does not write folder objects either. A folder is reported through the same `System.Boolean` result as a file. +For each path, the cmdlet writes `$true` when the item exists and matches `-PathType`, and `$false` otherwise. ## NOTES diff --git a/NTFSSecurity/AccessCmdlets/GetEffectiveAccess.cs b/NTFSSecurity/AccessCmdlets/GetEffectiveAccess.cs index d929bbf..bf1334c 100644 --- a/NTFSSecurity/AccessCmdlets/GetEffectiveAccess.cs +++ b/NTFSSecurity/AccessCmdlets/GetEffectiveAccess.cs @@ -72,7 +72,7 @@ namespace NTFSSecurity securityPrivilege = privControl.GetPrivileges().Where(priv => priv.Privilege == ProcessPrivileges.Privilege.Security).ToList(); if (securityPrivilege.Count() == 0) { - this.WriteWarning("The user does not hold the Security Privliege and might not be able to read the effective permissions"); + this.WriteWarning("The user does not hold the Security privilege and might not be able to read the effective permissions."); } else { diff --git a/NTFSSecurity/AuditCmdlets/AddAudit.cs b/NTFSSecurity/AuditCmdlets/AddAudit.cs index 4ce3edb..d5a11f2 100644 --- a/NTFSSecurity/AuditCmdlets/AddAudit.cs +++ b/NTFSSecurity/AuditCmdlets/AddAudit.cs @@ -8,7 +8,7 @@ using System.Security.AccessControl; namespace NTFSSecurity { [Cmdlet(VerbsCommon.Add, "NTFSAudit", DefaultParameterSetName = "PathComplex")] - [OutputType(typeof(FileSystemAccessRule2))] + [OutputType(typeof(FileSystemAuditRule2))] public class AddAudit : BaseCmdletWithPrivControl { private IdentityReference2[] account; diff --git a/NTFSSecurity/AuditCmdlets/RemoveAudit.cs b/NTFSSecurity/AuditCmdlets/RemoveAudit.cs index f075b30..c9b618b 100644 --- a/NTFSSecurity/AuditCmdlets/RemoveAudit.cs +++ b/NTFSSecurity/AuditCmdlets/RemoveAudit.cs @@ -8,7 +8,7 @@ using System.Security.AccessControl; namespace NTFSSecurity { [Cmdlet(VerbsCommon.Remove, "NTFSAudit", DefaultParameterSetName = "PathComplex")] - [OutputType(typeof(FileSystemAccessRule2))] + [OutputType(typeof(FileSystemAuditRule2))] public class RemoveAudit : BaseCmdletWithPrivControl { private IdentityReference2[] account; diff --git a/NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs b/NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs index f459e61..8204ba6 100644 --- a/NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs @@ -6,6 +6,7 @@ using System.Management.Automation; namespace NTFSSecurity { [Cmdlet(VerbsLifecycle.Disable, "NTFSAccessInheritance", DefaultParameterSetName = "Path")] + [OutputType(typeof(FileSystemInheritanceInfo))] public class DisableAccessInheritance : BaseCmdletWithPrivControl { private bool removeInheritedAccessRules; diff --git a/NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs b/NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs index bf51018..c64168e 100644 --- a/NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs @@ -7,6 +7,7 @@ namespace NTFSSecurity { [Cmdlet(VerbsLifecycle.Disable, "NTFSAuditInheritance", DefaultParameterSetName = "Path")] + [OutputType(typeof(FileSystemInheritanceInfo))] public class DisableAuditInheritance : BaseCmdletWithPrivControl { private bool removeInheritedAccessRules; diff --git a/NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs b/NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs index 1821e49..8db7bc6 100644 --- a/NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs @@ -6,6 +6,7 @@ using System.Management.Automation; namespace NTFSSecurity { [Cmdlet(VerbsLifecycle.Enable, "NTFSAccessInheritance", DefaultParameterSetName = "Path")] + [OutputType(typeof(FileSystemInheritanceInfo))] public class EnableAccessInheritance : BaseCmdletWithPrivControl { private bool removeExplicitAccessRules; diff --git a/NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs b/NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs index d3c753c..5a19158 100644 --- a/NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs @@ -6,6 +6,7 @@ using System.Management.Automation; namespace NTFSSecurity { [Cmdlet(VerbsLifecycle.Enable, "NTFSAuditInheritance", DefaultParameterSetName = "Path")] + [OutputType(typeof(FileSystemInheritanceInfo))] public class EnableAuditInheritance : BaseCmdletWithPrivControl { private bool removeExplicitAccessRules; diff --git a/NTFSSecurity/InheritanceCmdlets/SetInheritance.cs b/NTFSSecurity/InheritanceCmdlets/SetInheritance.cs index 3dcc8b3..ec3333b 100644 --- a/NTFSSecurity/InheritanceCmdlets/SetInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/SetInheritance.cs @@ -6,6 +6,7 @@ using System.Management.Automation; namespace NTFSSecurity { [Cmdlet(VerbsCommon.Set, "NTFSInheritance", DefaultParameterSetName = "Path")] + [OutputType(typeof(FileSystemInheritanceInfo))] public class SetInheritance : BaseCmdletWithPrivControl { private bool? accessInheritanceEnabled; diff --git a/NTFSSecurity/ItemCmdlets/CopyItem2.cs b/NTFSSecurity/ItemCmdlets/CopyItem2.cs index 3e391d9..e0434c2 100644 --- a/NTFSSecurity/ItemCmdlets/CopyItem2.cs +++ b/NTFSSecurity/ItemCmdlets/CopyItem2.cs @@ -5,6 +5,7 @@ using System.Management.Automation; namespace NTFSSecurity { [Cmdlet(VerbsCommon.Copy, "Item2", SupportsShouldProcess = true)] + [OutputType(typeof(FileInfo), typeof(DirectoryInfo))] public class CopyItem2 : BaseCmdlet { private string destination; @@ -93,12 +94,15 @@ namespace NTFSSecurity try { + var processed = false; + if (item is FileInfo) { if (ShouldProcess(resolvedPath, "Copy File")) { ((FileInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); - WriteVerbose(string.Format("File '{0}' copied to '{0}'", resolvedPath, destination)); + WriteVerbose(string.Format("File '{0}' copied to '{1}'", resolvedPath, actualDestination)); + processed = true; } } else @@ -109,11 +113,12 @@ namespace NTFSSecurity // DirectoryNotFoundException for the first file. Directory.CreateDirectory(actualDestination); ((DirectoryInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); - WriteVerbose(string.Format("Directory '{0}' copied to '{0}'", resolvedPath, destination)); + WriteVerbose(string.Format("Directory '{0}' copied to '{1}'", resolvedPath, actualDestination)); + processed = true; } } - if (passThru) + if (passThru && processed) WriteObject(item); } catch (System.IO.IOException ex) diff --git a/NTFSSecurity/ItemCmdlets/MoveItem2.cs b/NTFSSecurity/ItemCmdlets/MoveItem2.cs index 2a360e1..4594d2f 100644 --- a/NTFSSecurity/ItemCmdlets/MoveItem2.cs +++ b/NTFSSecurity/ItemCmdlets/MoveItem2.cs @@ -5,6 +5,7 @@ using System.Management.Automation; namespace NTFSSecurity { [Cmdlet(VerbsCommon.Move, "Item2", SupportsShouldProcess = true)] + [OutputType(typeof(FileInfo), typeof(DirectoryInfo))] public class MoveItem2 : BaseCmdlet { private string destination; @@ -93,12 +94,15 @@ namespace NTFSSecurity try { + var processed = false; + if (item is FileInfo) { if (ShouldProcess(resolvedPath, "Move File")) { ((FileInfo)item).MoveTo(actualDestination, force ? MoveOptions.ReplaceExisting : MoveOptions.CopyAllowed, PathFormat.RelativePath); - WriteVerbose(string.Format("File '{0}' moved to '{0}'", resolvedPath, destination)); + WriteVerbose(string.Format("File '{0}' moved to '{1}'", resolvedPath, actualDestination)); + processed = true; } } else @@ -106,11 +110,12 @@ namespace NTFSSecurity if (ShouldProcess(resolvedPath, "Move Directory")) { ((DirectoryInfo)item).MoveTo(actualDestination, force ? MoveOptions.ReplaceExisting : MoveOptions.CopyAllowed, PathFormat.RelativePath); - WriteVerbose(string.Format("Directory '{0}' moved to '{0}'", resolvedPath, destination)); + WriteVerbose(string.Format("Directory '{0}' moved to '{1}'", resolvedPath, actualDestination)); + processed = true; } } - if (passThru) + if (passThru && processed) WriteObject(item); } catch (System.IO.IOException ex) diff --git a/NTFSSecurity/ItemCmdlets/RemoveItem2.cs b/NTFSSecurity/ItemCmdlets/RemoveItem2.cs index de34ca0..b3caa91 100644 --- a/NTFSSecurity/ItemCmdlets/RemoveItem2.cs +++ b/NTFSSecurity/ItemCmdlets/RemoveItem2.cs @@ -5,11 +5,11 @@ using System.Management.Automation; namespace NTFSSecurity { [Cmdlet(VerbsCommon.Remove, "Item2", SupportsShouldProcess = true)] + [OutputType(typeof(FileInfo), typeof(DirectoryInfo))] public class RemoveItem2 : BaseCmdlet { private SwitchParameter force; private SwitchParameter recurse; - private string filter; private bool passThru; [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -71,12 +71,15 @@ namespace NTFSSecurity try { + var processed = false; + if (item is FileInfo) { if (ShouldProcess(item.ToString(), "Remove File")) { ((FileInfo)item).Delete(force); WriteVerbose(string.Format("File '{0}' was removed", item.ToString())); + processed = true; } } else @@ -85,10 +88,11 @@ namespace NTFSSecurity { ((DirectoryInfo)item).Delete(recurse, force); WriteVerbose(string.Format("Directory '{0}' was removed", item.ToString())); + processed = true; } } - if (passThru) + if (passThru && processed) WriteObject(item); } catch (System.IO.IOException ex) diff --git a/NTFSSecurity/LinkCmdlets/NewSymbolicLink.cs b/NTFSSecurity/LinkCmdlets/NewSymbolicLink.cs index 81d85ca..64ee2a1 100644 --- a/NTFSSecurity/LinkCmdlets/NewSymbolicLink.cs +++ b/NTFSSecurity/LinkCmdlets/NewSymbolicLink.cs @@ -70,7 +70,10 @@ namespace NTFSSecurity if (passThru) { - WriteObject(new FileInfo(path)); + if (targetItem is FileInfo) + WriteObject(new FileInfo(path)); + else + WriteObject(new DirectoryInfo(path)); } } catch (System.IO.FileNotFoundException ex) diff --git a/NTFSSecurity/MiscCmdlets/GetFileHash2.cs b/NTFSSecurity/MiscCmdlets/GetFileHash2.cs index 2e46f78..91ef656 100644 --- a/NTFSSecurity/MiscCmdlets/GetFileHash2.cs +++ b/NTFSSecurity/MiscCmdlets/GetFileHash2.cs @@ -7,7 +7,7 @@ using Security2.FileSystem.FileInfo; namespace NTFSSecurity { [Cmdlet(VerbsCommon.Get, "FileHash2")] - [OutputType(typeof(FileSystemAccessRule2))] + [OutputType(typeof(FileInfo))] public class GetFileHash2 : BaseCmdlet { private HashAlgorithms algorithm = HashAlgorithms.SHA256; diff --git a/NTFSSecurity/OtherCmdlets.cs b/NTFSSecurity/OtherCmdlets.cs index 83edb5d..6cd18d3 100644 --- a/NTFSSecurity/OtherCmdlets.cs +++ b/NTFSSecurity/OtherCmdlets.cs @@ -14,7 +14,6 @@ namespace NTFSSecurity { private bool enablePrivileges = false; private SwitchParameter passThru; - public string[] Path { get; set; } [Parameter] public SwitchParameter PassThru @@ -54,7 +53,7 @@ namespace NTFSSecurity if (passThru) { - this.WriteObject(this.privControl.GetPrivileges()); + this.WriteObject(this.privControl.GetPrivileges(), true); } } @@ -71,7 +70,6 @@ namespace NTFSSecurity public class DisablePrivileges : BaseCmdletWithPrivControl { private SwitchParameter passThru; - public string[] Path { get; set; } [Parameter] public SwitchParameter PassThru @@ -100,11 +98,11 @@ namespace NTFSSecurity } this.DisableFileSystemPrivileges(); - this.WriteVerbose("The privileges 'TakeOwnership', 'Restore' and 'Backup' are now enabled."); + this.WriteVerbose("The privileges 'TakeOwnership', 'Restore' and 'Backup' are now disabled."); if (passThru) { - this.WriteObject(this.privControl.GetPrivileges()); + this.WriteObject(this.privControl.GetPrivileges(), true); } } @@ -120,7 +118,6 @@ namespace NTFSSecurity [OutputType(typeof(ProcessPrivileges.PrivilegeAndAttributes))] public class GetPrivileges : BaseCmdlet { - public string[] Path { get; set; } protected override void BeginProcessing() { diff --git a/NTFSSecurity/PathCmdlets/TestPath2.cs b/NTFSSecurity/PathCmdlets/TestPath2.cs index 220f7da..8bb4dfd 100644 --- a/NTFSSecurity/PathCmdlets/TestPath2.cs +++ b/NTFSSecurity/PathCmdlets/TestPath2.cs @@ -5,7 +5,7 @@ using System.Management.Automation; namespace NTFSSecurity { [Cmdlet(VerbsDiagnostic.Test, "Path2")] - [OutputType(typeof(FileInfo), typeof(DirectoryInfo))] + [OutputType(typeof(bool))] public class TestPath2 : BaseCmdlet { private TestPathType pathType = TestPathType.Any; diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml index 9425ccf..1646c8a 100644 --- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml +++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml @@ -2163,16 +2163,25 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd - System.Object + Alphaleonis.Win32.Filesystem.FileInfo + + + By default this cmdlet returns nothing. With `-PassThru $true` it returns a file object for each file that it copied. + + + + + Alphaleonis.Win32.Filesystem.DirectoryInfo - By default this cmdlet returns nothing. With `-PassThru $true` it returns an `Alphaleonis.Win32.Filesystem.FileInfo` or `Alphaleonis.Win32.Filesystem.DirectoryInfo` object for each item that it copied. + With `-PassThru $true` the cmdlet returns a folder object for each folder that it copied. `Copy-Item2` copies through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it handles source and destination paths that exceed the 260-character `MAX_PATH` limit of the built-in `Copy-Item` cmdlet. + Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. Before 5.0.0, copying a folder that contained files failed with a `CopyError` that reported a `DirectoryNotFoundException` for the first file in the folder. If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call. @@ -2400,7 +2409,7 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd - System.Object + Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. @@ -2645,7 +2654,7 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd - System.Object + Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. @@ -2785,7 +2794,7 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd ProcessPrivileges.PrivilegeAndAttributes - With `-PassThru`, the cmdlet writes the privilege collection of the current process. The pipeline enumerates it into one `ProcessPrivileges.PrivilegeAndAttributes` object per privilege, each with a `Privilege`, a `PrivilegeAttributes`, and a `PrivilegeState` property. Without `-PassThru`, the cmdlet writes nothing. + With `-PassThru`, the cmdlet writes one `ProcessPrivileges.PrivilegeAndAttributes` object per privilege of the current process, each with a `Privilege`, a `PrivilegeAttributes`, and a `PrivilegeState` property. Without `-PassThru`, the cmdlet writes nothing. Before 5.0.0, it wrote the privileges as one collection. @@ -3017,7 +3026,7 @@ PS C:\> Get-Privileges | Where-Object { $_.Privilege -in 'Backup', 'Restore', - System.Object + Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. @@ -3262,7 +3271,7 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd - System.Object + Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. @@ -3403,7 +3412,7 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd ProcessPrivileges.PrivilegeAndAttributes - With `-PassThru`, the cmdlet writes the privilege collection of the current process. The pipeline enumerates it into one `ProcessPrivileges.PrivilegeAndAttributes` object per privilege, each with a `Privilege`, a `PrivilegeAttributes`, and a `PrivilegeState` property. Without `-PassThru`, the cmdlet writes nothing. + With `-PassThru`, the cmdlet writes one `ProcessPrivileges.PrivilegeAndAttributes` object per privilege of the current process, each with a `Privilege`, a `PrivilegeAttributes`, and a `PrivilegeState` property. Without `-PassThru`, the cmdlet writes nothing. Before 5.0.0, it wrote the privileges as one collection. @@ -4140,10 +4149,10 @@ PS C:\> Disable-Privileges - Security2.FileSystemAccessRule2 + Alphaleonis.Win32.Filesystem.FileInfo - The cmdlet does not return access rules. For every hashed file it writes the file object of that file, decorated with the type name `Alphaleonis.Win32.Filesystem.FileInfo+Hash` and extended with the `Hash` and `Algorithm` note properties, so all regular file properties such as `FullName`, `Name`, and `Length` remain available. + For every hashed file, the cmdlet writes the file object of that file, decorated with the type name `Alphaleonis.Win32.Filesystem.FileInfo+Hash` and extended with the `Hash` and `Algorithm` note properties, so all regular file properties such as `FullName`, `Name`, and `Length` remain available. @@ -6938,16 +6947,25 @@ PS C:\Data> Get-NTFSSecurityDescriptor - System.Object + Alphaleonis.Win32.Filesystem.FileInfo - By default this cmdlet returns nothing. With `-PassThru $true` it returns an `Alphaleonis.Win32.Filesystem.FileInfo` or `Alphaleonis.Win32.Filesystem.DirectoryInfo` object for each item that it moved, pointing at the new location. + By default this cmdlet returns nothing. With `-PassThru $true` it returns a file object for each file that it moved, pointing at the new location. + + + + + Alphaleonis.Win32.Filesystem.DirectoryInfo + + + With `-PassThru $true` the cmdlet returns a folder object for each folder that it moved, pointing at the new location. `Move-Item2` moves through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it handles source and destination paths that exceed the 260-character `MAX_PATH` limit of the built-in `Move-Item` cmdlet. + Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. The cmdlet chooses between two mutually exclusive move options. Without `-Force` it moves with `CopyAllowed`, which permits a file to cross volume boundaries because Windows then copies and deletes it. With `-Force` it moves with `ReplaceExisting`, which overwrites the destination but does not request `CopyAllowed`, so a move across volumes can fail when `-Force` is specified. If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call. @@ -7300,7 +7318,7 @@ PS C:\Data> Get-NTFSSecurityDescriptor Alphaleonis.Win32.Filesystem.FileInfo - With `-PassThru`, the cmdlet writes a file object for the new link. The cmdlet writes that object type for a link to a folder as well. Without `-PassThru`, the cmdlet writes nothing. + With `-PassThru`, the cmdlet writes a file object for a new link to a file. Without `-PassThru`, the cmdlet writes nothing. @@ -7308,7 +7326,7 @@ PS C:\Data> Get-NTFSSecurityDescriptor Alphaleonis.Win32.Filesystem.DirectoryInfo - The cmdlet does not write folder objects. A directory symbolic link is also returned as a file object. + With `-PassThru`, the cmdlet writes a folder object for a new link to a folder. Before 5.0.0, it wrote a file object for those links as well. @@ -7545,16 +7563,25 @@ PS C:\Data> Get-NTFSSecurityDescriptor - System.Object + Alphaleonis.Win32.Filesystem.FileInfo - By default this cmdlet returns nothing. With `-PassThru` it returns an `Alphaleonis.Win32.Filesystem.FileInfo` or `Alphaleonis.Win32.Filesystem.DirectoryInfo` object for each item that it deleted. + By default this cmdlet returns nothing. With `-PassThru` it returns a file object for each file that it deleted. + + + + + Alphaleonis.Win32.Filesystem.DirectoryInfo + + + With `-PassThru` the cmdlet returns a folder object for each folder that it deleted. `Remove-Item2` deletes through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it reaches items whose path exceeds the 260-character `MAX_PATH` limit of the built-in `Remove-Item` cmdlet. Deletion is permanent; the cmdlet does not use the Recycle Bin. + Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. The module defines the aliases `rm2` and `del2` for this cmdlet. A path that does not exist causes the error `FileNotFound`, and a deletion that the file system rejects causes a `DeleteError`. In both cases the cmdlet continues with the next path. Before 5.0.0, a path that did not exist made the cmdlet skip the remaining paths that were passed in the same call. @@ -9508,7 +9535,7 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd - System.Object + Security2.FileSystemInheritanceInfo By default this cmdlet returns no output. With `-PassThru` it writes one `Security2.FileSystemInheritanceInfo` object per item, which reports the `AccessInheritanceEnabled` and `AuditInheritanceEnabled` state after the change. @@ -10085,18 +10112,10 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd - Alphaleonis.Win32.Filesystem.FileInfo - - - `Test-Path2` does not write file objects. For each path it writes a single `System.Boolean` value that is `$true` when the item exists and matches `-PathType`, and `$false` otherwise. - - - - - Alphaleonis.Win32.Filesystem.DirectoryInfo + System.Boolean - `Test-Path2` does not write folder objects either. A folder is reported through the same `System.Boolean` result as a file. + For each path, the cmdlet writes `$true` when the item exists and matches `-PathType`, and `$false` otherwise. diff --git a/Tests/Access.Tests.ps1 b/Tests/Access.Tests.ps1 index bede5be..49a67bc 100644 --- a/Tests/Access.Tests.ps1 +++ b/Tests/Access.Tests.ps1 @@ -10,6 +10,7 @@ BeforeDiscovery { Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force # With the Restore privilege, Windows may grant writing the DACL despite a deny entry. $canBypassWriteDeny = Test-PrivilegeHeld -Name 'SeRestorePrivilege' + $holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege' } BeforeAll { @@ -55,6 +56,14 @@ Describe 'Get-NTFSEffectiveAccess' { Set-Acl -LiteralPath $effectiveFile -AclObject $acl } + # Before 5.0.0, the warning misspelled the privilege as "Privliege". + It 'Should warn once that the Security privilege is missing' -Skip:$holdsSecurityPrivilege { + Get-NTFSEffectiveAccess -Path $effectiveFile -WarningVariable accessWarnings -WarningAction SilentlyContinue | Out-Null + + $accessWarnings | Should -HaveCount 1 + $accessWarnings[0].Message | Should -BeExactly 'The user does not hold the Security privilege and might not be able to read the effective permissions.' + } + It 'Should leave out an account without access when -ExcludeNoneAccessEntries is used' { $result = @(Get-NTFSEffectiveAccess -Path $effectiveFile -Account 'S-1-5-32-546' -ExcludeNoneAccessEntries -WarningAction SilentlyContinue -ErrorAction Stop) diff --git a/Tests/ItemCmdlets.Tests.ps1 b/Tests/ItemCmdlets.Tests.ps1 index 31a5f48..c629536 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/Tests/ItemCmdlets.Tests.ps1 @@ -116,6 +116,31 @@ Describe 'Copy-Item2, Move-Item2, and Remove-Item2 with several paths' { $itemErrors | Should -HaveCount 1 Join-Path -Path $destination -ChildPath 'Second.txt' | Should -Exist } + + # Before 5.0.0, the verbose message named the source path as the destination. + It ' should name the destination in the verbose message' -ForEach @( + @{ Command = 'Copy-Item2'; Verb = 'copied' } + @{ Command = 'Move-Item2'; Verb = 'moved' } + ) { + $target = Join-Path -Path $destination -ChildPath 'First.txt' + + $messages = & $Command -Path $first -Destination $destination -Verbose 4>&1 + + $messages.Message | Should -Contain ("File '{0}' {1} to '{2}'" -f $first, $Verb, $target) + } + + # Before 5.0.0, -PassThru wrote the item also when -WhatIf skipped the operation. + It '<_> should write nothing with -PassThru and -WhatIf' -ForEach @('Copy-Item2', 'Move-Item2', 'Remove-Item2') { + $parameters = @{ Path = $first; PassThru = $true; WhatIf = $true } + if ($_ -ne 'Remove-Item2') { + $parameters.Destination = $destination + } + + $result = @(& $_ @parameters) + + $result | Should -BeNullOrEmpty + $first | Should -Exist + } } Describe 'Copy-Item2' { diff --git a/Tests/OutputTypes.Tests.ps1 b/Tests/OutputTypes.Tests.ps1 new file mode 100644 index 0000000..c767c41 --- /dev/null +++ b/Tests/OutputTypes.Tests.ps1 @@ -0,0 +1,102 @@ +<# + Tests the output types of the cmdlets of the module built in NTFSSecurity\bin\Release: the [OutputType] that + Get-Command reports, and the objects that -PassThru writes. Tests that need a privilege skip without it and run + in CI, whose runners are elevated. +#> +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.' +)] +param () + +BeforeDiscovery { + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force + $holdsBackupPrivilege = Test-PrivilegeHeld -Name 'SeBackupPrivilege' + $canCreateSymbolicLinks = Test-PrivilegeHeld -Name 'SeCreateSymbolicLinkPrivilege' + + $itemTypes = @('Alphaleonis.Win32.Filesystem.FileInfo', 'Alphaleonis.Win32.Filesystem.DirectoryInfo') + $declaredTypes = @( + @{ Name = 'Test-Path2'; Types = @('System.Boolean') } + @{ Name = 'Get-FileHash2'; Types = @('Alphaleonis.Win32.Filesystem.FileInfo') } + @{ Name = 'Add-NTFSAudit'; Types = @('Security2.FileSystemAuditRule2') } + @{ Name = 'Remove-NTFSAudit'; Types = @('Security2.FileSystemAuditRule2') } + @{ Name = 'Copy-Item2'; Types = $itemTypes } + @{ Name = 'Move-Item2'; Types = $itemTypes } + @{ Name = 'Remove-Item2'; Types = $itemTypes } + @{ Name = 'Enable-NTFSAccessInheritance'; Types = @('Security2.FileSystemInheritanceInfo') } + @{ Name = 'Disable-NTFSAccessInheritance'; Types = @('Security2.FileSystemInheritanceInfo') } + @{ Name = 'Enable-NTFSAuditInheritance'; Types = @('Security2.FileSystemInheritanceInfo') } + @{ Name = 'Disable-NTFSAuditInheritance'; Types = @('Security2.FileSystemInheritanceInfo') } + @{ Name = 'Set-NTFSInheritance'; Types = @('Security2.FileSystemInheritanceInfo') } + ) +} + +BeforeAll { + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force + $modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1' + Import-Module -Name $modulePath -Force -ErrorAction Stop + $sandbox = New-TestSandbox -Name 'OutputTypes' + Push-Location -LiteralPath $sandbox +} + +AfterAll { + Pop-Location + Remove-TestSandbox -Sandbox $sandbox + Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue +} + +Describe 'Declared output types' { + It ' should declare the type of the objects it writes' -ForEach $declaredTypes { + @((Get-Command -Name $Name).OutputType.Name) | Should -Be $Types + } +} + +Describe 'Privilege cmdlets with -PassThru' { + AfterEach { + Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + + # Before 5.0.0, -PassThru wrote the privileges as one collection. + It 'Enable-Privileges should write one object per privilege' { + $result = @(Enable-Privileges -PassThru -ErrorAction SilentlyContinue) + + $result.Count | Should -BeGreaterThan 1 + $result | ForEach-Object -Process { $_ | Should -BeOfType [ProcessPrivileges.PrivilegeAndAttributes] } + } + + It 'Disable-Privileges should write one object per privilege' -Skip:(-not $holdsBackupPrivilege) { + Enable-Privileges -ErrorAction SilentlyContinue + + $result = @(Disable-Privileges -PassThru -WarningAction SilentlyContinue) + + $result.Count | Should -BeGreaterThan 1 + $result | ForEach-Object -Process { $_ | Should -BeOfType [ProcessPrivileges.PrivilegeAndAttributes] } + } +} + +Describe 'New-NTFSSymbolicLink with -PassThru' { + # Before 5.0.0, the cmdlet returned a file object for a link to a folder as well. + It 'Should return a folder object for a link to a folder' -Skip:(-not $canCreateSymbolicLinks) { + $folder = New-TestSandboxItem -Sandbox $sandbox -Name 'Target' -Directory + $link = Join-Path -Path $sandbox -ChildPath 'FolderLink' + Assert-TestSandboxPath -Sandbox $sandbox -Path $link + + $result = New-NTFSSymbolicLink -Path $link -Target $folder -PassThru + + $result | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo] + } +} + +Describe 'Cmdlet classes' { + # Before 5.0.0, these classes declared members that nothing used. + It '<_> should declare no Path property, which was never a parameter' -ForEach @( + 'Enable-Privileges', 'Disable-Privileges', 'Get-Privileges' + ) { + (Get-Command -Name $_).ImplementingType.GetProperty('Path') | Should -BeNullOrEmpty + } + + It 'Remove-Item2 should declare no filter field' { + $flags = [System.Reflection.BindingFlags]'NonPublic, Instance' + + (Get-Command -Name 'Remove-Item2').ImplementingType.GetField('filter', $flags) | Should -BeNullOrEmpty + } +} \ No newline at end of file diff --git a/Tests/Privileges.Tests.ps1 b/Tests/Privileges.Tests.ps1 index 1ebfa5d..70a5852 100644 --- a/Tests/Privileges.Tests.ps1 +++ b/Tests/Privileges.Tests.ps1 @@ -59,6 +59,15 @@ Describe 'Disable-Privileges' { $privilegeWarnings | Should -BeNullOrEmpty Get-BackupPrivilegeState | Should -Be 'Disabled' } + + # Before 5.0.0, the verbose message said that the privileges were now enabled. + It 'Should say in the verbose message that the privileges are disabled' -Skip:(-not $holdsPrivileges) { + Enable-Privileges + + $messages = Disable-Privileges -Verbose -WarningAction SilentlyContinue 4>&1 + + $messages.Message | Should -Contain "The privileges 'TakeOwnership', 'Restore' and 'Backup' are now disabled." + } } }