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."
+ }
}
}