diff --git a/.memory-bank/activeContext.md b/.memory-bank/activeContext.md index b8c6f8b..b153a85 100644 --- a/.memory-bank/activeContext.md +++ b/.memory-bank/activeContext.md @@ -61,6 +61,10 @@ the PRs, and tags `5.0.0-rc2` after the merges. 395 passed, 26 skipped; PowerShell 7 366 passed, 55 skipped (421 tests). `Get-FileHash2` tests now run in PowerShell 7 as well. +- `ai/issue-fixes` fixes #3, #86, and #88 and adds `Docs/FAQ.md`: Windows + PowerShell 402 passed, 26 skipped; PowerShell 7 373 passed, 55 skipped + (428 tests). + ## Next step -The bugs from the issue triage (`ai/issue-fixes`), then 5.0.0-rc2. +5.0.0-rc2 on `ai/release-5.0.0-rc2`. diff --git a/.memory-bank/progress.md b/.memory-bank/progress.md index 5c15c67..97e21f9 100644 --- a/.memory-bank/progress.md +++ b/.memory-bank/progress.md @@ -179,3 +179,18 @@ Numbered as agreed with the maintainer; each is documented on its page. `MACTripleDES` uses a random key (verified), so it is deprecated. - Review of group E: the changelog now marks the `Set-NTFSInheritance` change as breaking and warns that it leaves broader access in place. + +#### Issue triage (fixes on `ai/issue-fixes`, not merged) + +- Fixed: #3 (braces in a path), #86 (`$PWD` shadowed, also for the default + location of nine cmdlets, found by the review), #88 (an object passed by + position), #17 (an entry with `GenericAll`); `Docs/FAQ.md` answers the + recurring questions. +- Review of #17: generic rights are removed the way .NET removes other + rights, an exact match as it is, otherwise without `Synchronize`. +- Maintainer decisions of 2026-10-05: #5 (`Get-ChildItem2 -Attributes` + matches any listed attribute) and #82 (no `Size` alias) ship in 5.0.0 as + breaking changes. Their review added that an empty `-Attributes` value is + an error, as in `Get-ChildItem`. +- Open bugs: #34 and #67 (writes owner and group, rc3 if a file server is + available), #41 (drive root) and #90 (trailing space), both after 5.0.0. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4c0b7..4e73cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,19 @@ The format is based on before. To remove the entries, use `Disable-NTFSAccessInheritance -RemoveInheritedAccessRules` or `Enable-NTFSAuditInheritance -RemoveExplicitAuditRules` +- **Breaking:** `Get-ChildItem2 -Attributes` returns the items that have any + of the listed attributes, like `Get-ChildItem`; it returned only the items + that had all of them. A call that lists several attributes now returns + more items, including hidden and system items when those are in the + list, so review calls whose result is deleted or whose permissions are + changed. To get the old result, filter with `Where-Object`, as the cmdlet + page shows. An empty value, such as `0`, is now an error; it returned + every item, also the hidden ones + ([#5](https://github.com/raandree/NTFSSecurity/issues/5)) +- **Breaking:** remove the alias `Size` of `LengthOnDisk` from the files of + `Get-ChildItem`, which made the import fail in Windows PowerShell when + another module had added a `Size` member; use `LengthOnDisk` + ([#82](https://github.com/raandree/NTFSSecurity/issues/82)) ### Deprecated @@ -169,5 +182,21 @@ The format is based on `RIPEMD160` and `MACTripleDES`, which .NET lacks there, now stop the cmdlet with an error that names the algorithm and points to Windows PowerShell 5.1 +- Fix a `FormatException` in the cmdlets for a path with braces, such as + `C:\Data\{Archive}`: their messages formatted the path a second time + ([#3](https://github.com/raandree/NTFSSecurity/issues/3)) +- Fix a `NullReferenceException` in every cmdlet when a variable named + `PWD` in the scope of the caller, such as a loop variable, hid the + automatic variable; the cmdlets now read the current location from the + session, and only for a relative path + ([#86](https://github.com/raandree/NTFSSecurity/issues/86)) +- Fix file and folder objects passed by position, such as + `Get-NTFSOwner $folder`, which Windows PowerShell bound as the name of the + item, so the cmdlets looked for it in the current location + ([#88](https://github.com/raandree/NTFSSecurity/issues/88)) +- Fix `Remove-NTFSAccess` for an entry with a generic right such as + `GenericAll`, which Windows keeps in the inherit-only entries of folders; + it failed with "The value '269484032' is not valid" + ([#17](https://github.com/raandree/NTFSSecurity/issues/17)) [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 0d78423..9e7b0b6 100644 --- a/Docs/Cmdlets/Copy-Item2.md +++ b/Docs/Cmdlets/Copy-Item2.md @@ -178,11 +178,11 @@ You can pipe an object that has a `Destination` property to supply the target of ### Alphaleonis.Win32.Filesystem.FileInfo -By default this cmdlet returns nothing. With `-PassThru $true` it returns a file object for each file that it copied. +By default this cmdlet returns nothing. With `-PassThru $true` it returns a file object for each file that it copied, pointing at the copy. ### Alphaleonis.Win32.Filesystem.DirectoryInfo -With `-PassThru $true` the cmdlet returns a folder object for each folder that it copied. +With `-PassThru $true` the cmdlet returns a folder object for each folder that it copied, pointing at the copy. ## NOTES diff --git a/Docs/Cmdlets/Get-ChildItem2.md b/Docs/Cmdlets/Get-ChildItem2.md index 5444ce2..af729af 100644 --- a/Docs/Cmdlets/Get-ChildItem2.md +++ b/Docs/Cmdlets/Get-ChildItem2.md @@ -57,19 +57,19 @@ PS C:\> Get-ChildItem2 -Path C:\Data -Recurse -Depth 1 -Filter '*.log' Returns the log files in `C:\Data` and in its immediate subfolders. Without `-Depth`, the command would descend through the entire tree. -### Example 4: List hidden system files +### Example 4: List hidden or system files ```PowerShell PS C:\> dir2 -Path C:\Data -Attributes Hidden, System ``` -Uses the `dir2` alias and returns the items of `C:\Data` that have both the hidden and the system attribute. +Uses the `dir2` alias and returns the items of `C:\Data` that have the hidden or the system attribute, like `Get-ChildItem -Attributes Hidden, System`. ## PARAMETERS ### -Attributes -Specifies a set of file attributes. The cmdlet returns only the items that have all the attributes you list; separate several values with commas, as in `-Attributes Hidden, System`. When you use this parameter, the cmdlet ignores `-Force`, `-Hidden`, `-System`, and `-ReadOnly`, and it returns matching hidden items without `-Force`. +Specifies a set of file attributes. Like `Get-ChildItem`, the cmdlet returns the items that have at least one of the attributes you list; separate several values with commas, as in `-Attributes Hidden, System`. Unlike `Get-ChildItem`, the parameter takes only such a list, not the `+` and `!` operators; to get only the items that have all the attributes, filter the result, for example with `Where-Object { ($_.Attributes -band [IO.FileAttributes]'Hidden, System') -eq [IO.FileAttributes]'Hidden, System' }`. An empty value, such as `0`, stops the cmdlet with the error `AttributesEmpty`. When you use this parameter, the cmdlet ignores `-Force`, `-Hidden`, `-System`, and `-ReadOnly`, and it returns matching hidden items without `-Force`. The parameter restricts the returned items only; `-Recurse` still descends into every subfolder, including hidden ones. ```yaml Type: FileAttributes @@ -307,7 +307,7 @@ The `PrivateData` section of the module manifest `NTFSSecurity.psd1` contains tw A folder that cannot be read produces a non-terminating error with the ID `DirUnauthorizedAccessError` for an access denial or `DirUnspecifiedError` for any other failure, and a path that does not exist produces the error `FileNotFound`. In each case the cmdlet continues with the next path. Failures that occur while `-Recurse` collects the subfolders of a folder are reported as verbose messages only, not as errors. -Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`. +Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`, `-Attributes` returned only the items that had all the listed attributes, and an empty `-Attributes` value returned every item, also the hidden ones. ## RELATED LINKS diff --git a/Docs/Cmdlets/Get-NTFSEffectiveAccess.md b/Docs/Cmdlets/Get-NTFSEffectiveAccess.md index c10b202..0385c01 100644 --- a/Docs/Cmdlets/Get-NTFSEffectiveAccess.md +++ b/Docs/Cmdlets/Get-NTFSEffectiveAccess.md @@ -164,7 +164,7 @@ One or more paths of files or folders, piped by value or by the property `FullNa ### Security2.FileSystemSecurity2[] -Security descriptors are accepted by the parameter binder but produce no result in this cmdlet. +One or more security descriptors that `Get-NTFSSecurityDescriptor` returned. The cmdlet calculates the effective access from the descriptor in memory instead of reading the item again. ### Security2.IdentityReference2 diff --git a/Docs/Cmdlets/Remove-NTFSAccess.md b/Docs/Cmdlets/Remove-NTFSAccess.md index e2b4c18..56090b6 100644 --- a/Docs/Cmdlets/Remove-NTFSAccess.md +++ b/Docs/Cmdlets/Remove-NTFSAccess.md @@ -304,6 +304,8 @@ If the ACL of an item cannot be written because access is denied, the cmdlet tri Removing rights from an entry that does not exist is not an error; the cmdlet leaves the ACL unchanged. +An entry with a generic right, such as `GenericAll`, can be removed, for example by piping it from `Get-NTFSAccess`. Windows keeps generic rights in the inherit-only entries of folders. Before 5.0.0, the cmdlet failed for such an entry with the error "The value '269484032' is not valid for this usage of the type FileSystemRights". + Before 5.0.0, the `-RemoveSpecific` switch was missing, although version 4.1 had introduced it. A path that does not exist produces the non-terminating error `ReadFileError`, and the cmdlet continues with the next path. Before 5.0.0, the cmdlet also wrote a misleading `RemoveAceError` for that path, and with `-PassThru` it stopped with a `NullReferenceException`. diff --git a/Docs/Concepts.md b/Docs/Concepts.md index 1852861..9ba01cf 100644 --- a/Docs/Concepts.md +++ b/Docs/Concepts.md @@ -234,7 +234,7 @@ the AlphaFS objects that the `*-Item2` cmdlets return. | --- | --- | --- | --- | | `Owner` | Property | Files and folders | The owner of the item. | | `IsInheritanceBlocked` | Property | Files and folders | `$true` if the item does not inherit access entries. | -| `LengthOnDisk` | Property | Files | The file size rounded up to whole clusters of the volume. `Size` is an alias. | +| `LengthOnDisk` | Property | Files | The file size rounded up to whole clusters of the volume. Before 5.0.0, `Size` was an alias. | | `EnableInheritance()` | Method | Files and folders | Turns on access inheritance. | | `DisableInheritance()` | Method | Files and folders | Turns off access inheritance. Pass `$false` to drop the inherited entries instead of copying them. | | `GetHash()` | Method | Files | Returns the SHA1 hash of the file as a hexadecimal string. | diff --git a/Docs/FAQ.md b/Docs/FAQ.md new file mode 100644 index 0000000..8f31dc8 --- /dev/null +++ b/Docs/FAQ.md @@ -0,0 +1,64 @@ +# Frequently asked questions + +Answers to questions that come up again and again in the issues. For the +background, read [Concepts](Concepts.md); for longer scripts, read +[Examples](Examples.md). + +## The module fails to load with HRESULT 0x80131515 + +Windows blocks the assemblies of a ZIP file that was downloaded from the +internet. Install the module from the PowerShell Gallery instead, as +described in [Installation](README.md#installation), or unblock the files of +a downloaded copy with `Get-ChildItem -Recurse | Unblock-File` before you +import it. + +## Access is denied, although I am an administrator + +Run PowerShell elevated. Only an elevated session holds the Backup, +Restore, Take Ownership, and Security privileges, which the cmdlets enable +to read and change items that your account has no rights on. Reading or +changing audit entries always needs the Security privilege. See +[Privileges](Concepts.md#privileges) and the module setting +`EnablePrivileges` in [Module settings](Concepts.md#module-settings). + +## Get-NTFSEffectiveAccess shows other rights than Explorer + +`Get-NTFSEffectiveAccess` calculates the rights that the NTFS permissions +of the item grant to one account. It doesn't include share permissions, +which Explorer adds for a path on a file share, and the account must be +resolvable on the computer that runs the cmdlet. See +[Get-NTFSEffectiveAccess](Cmdlets/Get-NTFSEffectiveAccess.md). + +## Get-ChildItem2 -Recurse runs in a loop through junctions + +A junction can point to a folder above it. Use `-SkipMountPoints` and +`-SkipSymbolicLinks` to leave out junctions and symbolic links when you +walk a tree, for example before you pipe the items to `Get-NTFSAccess`. +See [Get-ChildItem2](Cmdlets/Get-ChildItem2.md). + +## How do I restore permissions that I exported? + +`Get-NTFSAccess` returns the account, the rights, the type, and the +inheritance and propagation flags of each entry. `Add-NTFSAccess` binds +`-Account`, `-AccessRights`, `-AccessType`, `-InheritanceFlags`, and +`-PropagationFlags` by property name, so the objects, or the rows of a CSV +file with these columns and the path, can be piped back to it. See +[Add-NTFSAccess](Cmdlets/Add-NTFSAccess.md). + +## How do I apply the same audit entries to a whole folder tree? + +Add the entry to the top folder only. By default, `Add-NTFSAudit` applies +it to the folder, its subfolders, and its files, so the items below inherit +it. To remove other entries from the items below, use `Clear-NTFSAudit`, +and use `Enable-NTFSAuditInheritance` where inheritance is blocked. +Changing audit entries needs an elevated session. See +[Add-NTFSAudit](Cmdlets/Add-NTFSAudit.md). + +## Can I use a PowerShell drive in a path? + +No. The cmdlets read and write the file system directly through the AlphaFS +library, not through the PowerShell providers, so they don't know drives +that `New-PSDrive` created, or drives of other providers such as `HKLM:`. +Use the file system path instead, such as `C:\Data` or `\\server\share`. A +relative path is resolved against the current file system location. See +[Long paths](Concepts.md#long-paths). diff --git a/Docs/README.md b/Docs/README.md index 8d6bcff..956b1c0 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -72,7 +72,8 @@ Get-NTFSAccess -Path C:\Windows Read [Concepts](Concepts.md) for the background and [Examples](Examples.md) for common tasks. Every cmdlet has a reference page with all parameters and -examples; see the [cmdlet list](#cmdlets). +examples; see the [cmdlet list](#cmdlets). The [FAQ](FAQ.md) answers +questions that come up again and again. ## Cmdlets diff --git a/NTFSSecurity/AccessCmdlets/AddAccess.cs b/NTFSSecurity/AccessCmdlets/AddAccess.cs index 4770aa9..f503aaa 100644 --- a/NTFSSecurity/AccessCmdlets/AddAccess.cs +++ b/NTFSSecurity/AccessCmdlets/AddAccess.cs @@ -23,6 +23,7 @@ namespace NTFSSecurity [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/AccessCmdlets/ClearAccess.cs b/NTFSSecurity/AccessCmdlets/ClearAccess.cs index 9e96a39..1e4a1c6 100644 --- a/NTFSSecurity/AccessCmdlets/ClearAccess.cs +++ b/NTFSSecurity/AccessCmdlets/ClearAccess.cs @@ -13,6 +13,7 @@ namespace NTFSSecurity [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/AccessCmdlets/GetAccess.cs b/NTFSSecurity/AccessCmdlets/GetAccess.cs index f010e18..95af170 100644 --- a/NTFSSecurity/AccessCmdlets/GetAccess.cs +++ b/NTFSSecurity/AccessCmdlets/GetAccess.cs @@ -20,6 +20,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -73,7 +74,7 @@ namespace NTFSSecurity if (paths.Count == 0) { - paths = new List() { GetVariableValue("PWD").ToString() }; + paths = new List() { GetCurrentLocation() }; } } diff --git a/NTFSSecurity/AccessCmdlets/GetEffectiveAccess.cs b/NTFSSecurity/AccessCmdlets/GetEffectiveAccess.cs index bf1334c..1e63063 100644 --- a/NTFSSecurity/AccessCmdlets/GetEffectiveAccess.cs +++ b/NTFSSecurity/AccessCmdlets/GetEffectiveAccess.cs @@ -20,6 +20,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -108,7 +109,7 @@ namespace NTFSSecurity } // Like the other cmdlets, use the current location when -Path is omitted. - var targets = paths.Count > 0 ? paths : new List() { GetVariableValue("PWD").ToString() }; + var targets = paths.Count > 0 ? paths : new List() { GetCurrentLocation() }; foreach (var path in targets) { diff --git a/NTFSSecurity/AccessCmdlets/RemoveAccess.cs b/NTFSSecurity/AccessCmdlets/RemoveAccess.cs index bd4c04d..a1c566b 100644 --- a/NTFSSecurity/AccessCmdlets/RemoveAccess.cs +++ b/NTFSSecurity/AccessCmdlets/RemoveAccess.cs @@ -24,6 +24,7 @@ namespace NTFSSecurity [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/AuditCmdlets/AddAudit.cs b/NTFSSecurity/AuditCmdlets/AddAudit.cs index d5a11f2..f8b8584 100644 --- a/NTFSSecurity/AuditCmdlets/AddAudit.cs +++ b/NTFSSecurity/AuditCmdlets/AddAudit.cs @@ -23,6 +23,7 @@ namespace NTFSSecurity [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/AuditCmdlets/ClearAudit.cs b/NTFSSecurity/AuditCmdlets/ClearAudit.cs index 622e5d4..a6f7614 100644 --- a/NTFSSecurity/AuditCmdlets/ClearAudit.cs +++ b/NTFSSecurity/AuditCmdlets/ClearAudit.cs @@ -13,6 +13,7 @@ namespace NTFSSecurity [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/AuditCmdlets/GetAudit.cs b/NTFSSecurity/AuditCmdlets/GetAudit.cs index c569212..fabe976 100644 --- a/NTFSSecurity/AuditCmdlets/GetAudit.cs +++ b/NTFSSecurity/AuditCmdlets/GetAudit.cs @@ -20,6 +20,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -73,7 +74,7 @@ namespace NTFSSecurity if (paths.Count == 0) { - paths = new List() { GetVariableValue("PWD").ToString() }; + paths = new List() { GetCurrentLocation() }; } } diff --git a/NTFSSecurity/AuditCmdlets/RemoveAudit.cs b/NTFSSecurity/AuditCmdlets/RemoveAudit.cs index c9b618b..9d88c2b 100644 --- a/NTFSSecurity/AuditCmdlets/RemoveAudit.cs +++ b/NTFSSecurity/AuditCmdlets/RemoveAudit.cs @@ -24,6 +24,7 @@ namespace NTFSSecurity [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/BaseCmdlets.cs b/NTFSSecurity/BaseCmdlets.cs index 8cdb84d..0571c96 100644 --- a/NTFSSecurity/BaseCmdlets.cs +++ b/NTFSSecurity/BaseCmdlets.cs @@ -26,32 +26,7 @@ namespace NTFSSecurity #region GetFileSystemInfo protected System.IO.FileSystemInfo GetFileSystemInfo(string path) { - string currentLocation = GetVariableValue("PWD").ToString(); - - if (path == ".") - { - path = currentLocation; - } - if (path.StartsWith("..")) - { - path = System.IO.Path.Combine( - string.Join("\\", currentLocation.Split('\\').Take(currentLocation.Split('\\').Count() - path.Split('\\').Count(s => s == "..")).ToArray()), - string.Join("\\", path.Split('\\').Where(e => e != "..").ToArray())); - } - else if (path.StartsWith(".")) - { - //combine . and .\path\subpath - path = System.IO.Path.Combine(currentLocation, path.Substring(2)); - } - else if (path.StartsWith("\\")) - { - //do nothing - } - else - { - ////combine . and \path\subpath or path\subpath - path = System.IO.Path.Combine(currentLocation, path.Substring(0)); - } + path = GetRelativePath(path); if (System.IO.File.Exists(path)) { @@ -114,18 +89,17 @@ namespace NTFSSecurity #region GetRelativePath protected string GetRelativePath(string path) { - string currentLocation = GetVariableValue("PWD").ToString(); - if (string.IsNullOrEmpty(path)) { - path = currentLocation; + path = GetCurrentLocation(); } else if (path == ".") { - path = currentLocation; + path = GetCurrentLocation(); } else if (path.StartsWith("..")) { + var currentLocation = GetCurrentLocation(); path = System.IO.Path.Combine( string.Join("\\", currentLocation.Split('\\').Take(currentLocation.Split('\\').Count() - path.Split('\\').Count(s => s == "..")).ToArray()), string.Join("\\", path.Split('\\').Where(e => e != "..").ToArray())); @@ -133,20 +107,31 @@ namespace NTFSSecurity else if (path.StartsWith(".")) { //combine . and .\path\subpath - path = System.IO.Path.Combine(currentLocation, path.Substring(2)); + path = System.IO.Path.Combine(GetCurrentLocation(), path.Substring(2)); } - else if (path.StartsWith("\\")) + else if (path.StartsWith("\\") || System.IO.Path.IsPathRooted(path)) { - //do nothing + //an absolute path needs no location } else { - ////combine . and \path\subpath or path\subpath - path = System.IO.Path.Combine(currentLocation, path); + ////combine . and path\subpath + path = System.IO.Path.Combine(GetCurrentLocation(), path); } return path; } + + /// + /// Returns the current file system location of the session. It is read from the session state, not from + /// $PWD, which a variable named PWD in the scope of the caller can hide (#86). In a location of another + /// provider, such as the registry, this is the last file system location. + /// + /// The provider path of the current file system location. + protected string GetCurrentLocation() + { + return SessionState.Path.CurrentFileSystemLocation.ProviderPath; + } #endregion #region InvokeAsOwner @@ -337,18 +322,71 @@ namespace NTFSSecurity WriteDebug("The privilige 'Security' was disabled."); } + // These overloads hide the single-argument methods of Cmdlet, so a message without arguments must not be + // formatted: a path with braces in it would make string.Format throw (#3). protected void WriteWarning(string text, params string[] args) { - base.WriteWarning(string.Format(text, args)); + base.WriteWarning(args == null || args.Length == 0 ? text : string.Format(text, args)); } protected void WriteVerbose(string text, params string[] args) { - base.WriteVerbose(string.Format(text, args)); + base.WriteVerbose(args == null || args.Length == 0 ? text : string.Format(text, args)); } protected void WriteDebug(string text, params string[] args) { - base.WriteDebug(string.Format(text, args)); + base.WriteDebug(args == null || args.Length == 0 ? text : string.Format(text, args)); + } + } + + /// + /// Converts file and folder objects to their full path. Windows PowerShell binds an object that is passed by + /// position to a string parameter through ToString, which returns only the name of a child item, so the cmdlet + /// resolved it against the current location (#88). + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public sealed class FileSystemPathTransformationAttribute : ArgumentTransformationAttribute + { + /// + /// Returns the full path of a file or folder object, or of each one in a collection, and any other value + /// unchanged. + /// + /// The engine APIs of the session. + /// The argument to transform. + /// The transformed argument. + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + var input = inputData is PSObject ? ((PSObject)inputData).BaseObject : inputData; + + if (input is string || !(input is IEnumerable)) + { + return ToPath(inputData); + } + + var result = new List(); + foreach (var item in (IEnumerable)input) + { + result.Add(ToPath(item)); + } + + return result.ToArray(); + } + + private static object ToPath(object value) + { + var baseObject = value is PSObject ? ((PSObject)value).BaseObject : value; + + if (baseObject is System.IO.FileSystemInfo) + { + return ((System.IO.FileSystemInfo)baseObject).FullName; + } + + if (baseObject is Alphaleonis.Win32.Filesystem.FileSystemInfo) + { + return ((Alphaleonis.Win32.Filesystem.FileSystemInfo)baseObject).FullName; + } + + return value; } } } \ No newline at end of file diff --git a/NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs b/NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs index 8204ba6..bdb964d 100644 --- a/NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs @@ -15,6 +15,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs b/NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs index 610acb3..6505e1c 100644 --- a/NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs @@ -16,6 +16,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs b/NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs index 8db7bc6..16ea497 100644 --- a/NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs @@ -15,6 +15,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs b/NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs index a5f23cc..0c1a362 100644 --- a/NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs @@ -15,6 +15,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/InheritanceCmdlets/GetInheritance.cs b/NTFSSecurity/InheritanceCmdlets/GetInheritance.cs index 07d510c..9b02228 100644 --- a/NTFSSecurity/InheritanceCmdlets/GetInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/GetInheritance.cs @@ -13,6 +13,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -41,7 +42,7 @@ namespace NTFSSecurity if (paths.Count == 0) { - paths = new List() { GetVariableValue("PWD").ToString() }; + paths = new List() { GetCurrentLocation() }; } } diff --git a/NTFSSecurity/InheritanceCmdlets/SetInheritance.cs b/NTFSSecurity/InheritanceCmdlets/SetInheritance.cs index ecc4459..16ef6e6 100644 --- a/NTFSSecurity/InheritanceCmdlets/SetInheritance.cs +++ b/NTFSSecurity/InheritanceCmdlets/SetInheritance.cs @@ -16,6 +16,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/ItemCmdlets/CopyItem2.cs b/NTFSSecurity/ItemCmdlets/CopyItem2.cs index e0434c2..0775bc7 100644 --- a/NTFSSecurity/ItemCmdlets/CopyItem2.cs +++ b/NTFSSecurity/ItemCmdlets/CopyItem2.cs @@ -15,6 +15,7 @@ namespace NTFSSecurity [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -26,6 +27,7 @@ namespace NTFSSecurity } [Parameter(Position = 2, Mandatory = true, ValueFromPipelineByPropertyName = true)] + [FileSystemPathTransformation] public string Destination { get { return destination; } @@ -95,12 +97,13 @@ namespace NTFSSecurity try { var processed = false; + FileSystemInfo copy = null; if (item is FileInfo) { if (ShouldProcess(resolvedPath, "Copy File")) { - ((FileInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); + copy = ((FileInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); WriteVerbose(string.Format("File '{0}' copied to '{1}'", resolvedPath, actualDestination)); processed = true; } @@ -112,14 +115,15 @@ namespace NTFSSecurity // AlphaFS 2.2 copies into an existing folder only and otherwise fails with a // DirectoryNotFoundException for the first file. Directory.CreateDirectory(actualDestination); - ((DirectoryInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); + copy = ((DirectoryInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); WriteVerbose(string.Format("Directory '{0}' copied to '{1}'", resolvedPath, actualDestination)); processed = true; } } + // Write the object for the copy that CopyTo returns, not the source item. if (passThru && processed) - WriteObject(item); + WriteObject(copy); } catch (System.IO.IOException ex) { diff --git a/NTFSSecurity/ItemCmdlets/GetChildItem2.cs b/NTFSSecurity/ItemCmdlets/GetChildItem2.cs index 96f08f1..bb908de 100644 --- a/NTFSSecurity/ItemCmdlets/GetChildItem2.cs +++ b/NTFSSecurity/ItemCmdlets/GetChildItem2.cs @@ -33,6 +33,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -131,9 +132,17 @@ namespace NTFSSecurity { base.BeginProcessing(); + // An empty value would match every item, also the hidden ones; Get-ChildItem rejects it as well. + if (MyInvocation.BoundParameters.ContainsKey("Attributes") && attributes == 0) + { + ThrowTerminatingError(new ErrorRecord( + new ArgumentException("Specify at least one file attribute for the Attributes parameter."), + "AttributesEmpty", ErrorCategory.InvalidArgument, attributes)); + } + if (paths.Count == 0) { - paths = new List() { GetVariableValue("PWD").ToString() }; + paths = new List() { GetCurrentLocation() }; } wildcard = new WildcardPattern(filter, WildcardOptions.Compiled | WildcardOptions.IgnoreCase); @@ -274,7 +283,8 @@ namespace NTFSSecurity if (MyInvocation.BoundParameters.ContainsKey("Attributes")) { - if ((current.Attributes & attributes) != attributes) + // Like Get-ChildItem, an item matches when it has any of the listed attributes (#5). + if ((current.Attributes & attributes) == 0) continue; writeItem = true; diff --git a/NTFSSecurity/ItemCmdlets/GetItem2.cs b/NTFSSecurity/ItemCmdlets/GetItem2.cs index f45d82c..c2fe9e8 100644 --- a/NTFSSecurity/ItemCmdlets/GetItem2.cs +++ b/NTFSSecurity/ItemCmdlets/GetItem2.cs @@ -13,6 +13,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -29,7 +30,7 @@ namespace NTFSSecurity if (paths.Count == 0) { - paths = new List() { GetVariableValue("PWD").ToString() }; + paths = new List() { GetCurrentLocation() }; } modeMethodInfo = typeof(FileSystemCodeMembers).GetMethod("Mode"); diff --git a/NTFSSecurity/ItemCmdlets/MoveItem2.cs b/NTFSSecurity/ItemCmdlets/MoveItem2.cs index 4594d2f..c83c9e2 100644 --- a/NTFSSecurity/ItemCmdlets/MoveItem2.cs +++ b/NTFSSecurity/ItemCmdlets/MoveItem2.cs @@ -15,6 +15,7 @@ namespace NTFSSecurity [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -26,6 +27,7 @@ namespace NTFSSecurity } [Parameter(Position = 2, Mandatory = true, ValueFromPipelineByPropertyName = true)] + [FileSystemPathTransformation] public string Destination { get { return destination; } diff --git a/NTFSSecurity/ItemCmdlets/RemoveItem2.cs b/NTFSSecurity/ItemCmdlets/RemoveItem2.cs index b3caa91..59db095 100644 --- a/NTFSSecurity/ItemCmdlets/RemoveItem2.cs +++ b/NTFSSecurity/ItemCmdlets/RemoveItem2.cs @@ -15,6 +15,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/LinkCmdlets/GetHardLink.cs b/NTFSSecurity/LinkCmdlets/GetHardLink.cs index fab966e..7f96b1a 100644 --- a/NTFSSecurity/LinkCmdlets/GetHardLink.cs +++ b/NTFSSecurity/LinkCmdlets/GetHardLink.cs @@ -14,6 +14,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } @@ -30,7 +31,7 @@ namespace NTFSSecurity if (paths.Count == 0) { - paths = new List() { GetVariableValue("PWD").ToString() }; + paths = new List() { GetCurrentLocation() }; } modeMethodInfo = typeof(FileSystemCodeMembers).GetMethod("Mode"); diff --git a/NTFSSecurity/LinkCmdlets/NewHardLink.cs b/NTFSSecurity/LinkCmdlets/NewHardLink.cs index cec9d5b..3ce89c6 100644 --- a/NTFSSecurity/LinkCmdlets/NewHardLink.cs +++ b/NTFSSecurity/LinkCmdlets/NewHardLink.cs @@ -15,6 +15,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string Path { get { return paths[0]; } @@ -27,6 +28,7 @@ namespace NTFSSecurity [Parameter(Position = 2, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] + [FileSystemPathTransformation] public string Target { get { return target; } diff --git a/NTFSSecurity/LinkCmdlets/NewSymbolicLink.cs b/NTFSSecurity/LinkCmdlets/NewSymbolicLink.cs index 64ee2a1..091ae09 100644 --- a/NTFSSecurity/LinkCmdlets/NewSymbolicLink.cs +++ b/NTFSSecurity/LinkCmdlets/NewSymbolicLink.cs @@ -15,6 +15,7 @@ namespace NTFSSecurity [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string Path { get { return paths[0]; } @@ -27,6 +28,7 @@ namespace NTFSSecurity [Parameter(Position = 2, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] + [FileSystemPathTransformation] public string Target { get { return target; } diff --git a/NTFSSecurity/MiscCmdlets/GetFileHash2.cs b/NTFSSecurity/MiscCmdlets/GetFileHash2.cs index 113d8f0..9a9acb7 100644 --- a/NTFSSecurity/MiscCmdlets/GetFileHash2.cs +++ b/NTFSSecurity/MiscCmdlets/GetFileHash2.cs @@ -16,6 +16,7 @@ namespace NTFSSecurity [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("FullName")] + [FileSystemPathTransformation] public string[] Path { get { return paths.ToArray(); } diff --git a/NTFSSecurity/NTFSSecurity.types.ps1xml b/NTFSSecurity/NTFSSecurity.types.ps1xml index 4944423..681e8fc 100644 --- a/NTFSSecurity/NTFSSecurity.types.ps1xml +++ b/NTFSSecurity/NTFSSecurity.types.ps1xml @@ -23,10 +23,6 @@ [Math]::Ceiling($this.Length / ($driveInfo.BytesPerSector * $driveInfo.SectorsPerCluster)) * ($driveInfo.BytesPerSector * $driveInfo.SectorsPerCluster) - - Size - LengthOnDisk - EnableInheritance