Browse Source

Merge pull request #100 from raandree/ai/defects-a

fix: crashes and wrong results (defect group A)
pull/112/head
Raimund Andrée 6 days ago
committed by GitHub
parent
commit
3f62404367
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      .memory-bank/activeContext.md
  2. 4
      .memory-bank/progress.md
  3. 51
      .memory-bank/systemPatterns.md
  4. 40
      CHANGELOG.md
  5. 8
      Docs/Cmdlets/Add-NTFSAudit.md
  6. 2
      Docs/Cmdlets/Copy-Item2.md
  7. 2
      Docs/Cmdlets/Disable-NTFSAccessInheritance.md
  8. 2
      Docs/Cmdlets/Disable-NTFSAuditInheritance.md
  9. 2
      Docs/Cmdlets/Disable-Privileges.md
  10. 2
      Docs/Cmdlets/Enable-NTFSAccessInheritance.md
  11. 2
      Docs/Cmdlets/Enable-NTFSAuditInheritance.md
  12. 6
      Docs/Cmdlets/Get-ChildItem2.md
  13. 4
      Docs/Cmdlets/Get-FileHash2.md
  14. 2
      Docs/Cmdlets/Get-NTFSAccess.md
  15. 8
      Docs/Cmdlets/Get-NTFSAudit.md
  16. 6
      Docs/Cmdlets/Get-NTFSInheritance.md
  17. 4
      Docs/Cmdlets/Get-NTFSOwner.md
  18. 4
      Docs/Cmdlets/Remove-NTFSAudit.md
  19. 10
      Docs/Cmdlets/Set-NTFSInheritance.md
  20. 6
      Docs/Concepts.md
  21. 39
      NTFSSecurity/AccessCmdlets/GetAccess.cs
  22. 4
      NTFSSecurity/AuditCmdlets/AddAudit.cs
  23. 69
      NTFSSecurity/AuditCmdlets/GetAudit.cs
  24. 2
      NTFSSecurity/AuditCmdlets/RemoveAudit.cs
  25. 3
      NTFSSecurity/BaseCmdlets.cs
  26. 1
      NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs
  27. 1
      NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs
  28. 1
      NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs
  29. 1
      NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs
  30. 1
      NTFSSecurity/InheritanceCmdlets/GetInheritance.cs
  31. 194
      NTFSSecurity/InheritanceCmdlets/SetInheritance.cs
  32. 3
      NTFSSecurity/ItemCmdlets/CopyItem2.cs
  33. 16
      NTFSSecurity/ItemCmdlets/GetChildItem2.cs
  34. 6
      NTFSSecurity/MiscCmdlets/GetFileHash2.cs
  35. 2
      NTFSSecurity/NTFSSecurity.format.ps1xml
  36. 31
      NTFSSecurity/OwnerCmdlets/GetOwner.cs
  37. 162
      NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
  38. 2
      Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.GetFileSystemAuditRules.cs
  39. 9
      Security2/FileSystem/FileSystemInheritanceInfo.cs
  40. 12
      Security2/FileSystem/FileSystemSecurity2.cs
  41. 3
      Security2/Properties/AssemblyInfo.cs
  42. 37
      Tests/Access.Tests.ps1
  43. 140
      Tests/Audit.Tests.ps1
  44. 45
      Tests/FileHash.Tests.ps1
  45. 97
      Tests/Inheritance.Tests.ps1
  46. 106
      Tests/ItemCmdlets.Tests.ps1
  47. 54
      Tests/Owner.Tests.ps1
  48. 88
      Tests/Privileges.Tests.ps1
  49. 146
      Tests/TestHelpers.Tests.ps1
  50. 250
      Tests/TestHelpers.psm1

9
.memory-bank/activeContext.md

@ -39,7 +39,14 @@ the PRs, and tags `5.0.0-rc2` after the merges.
261 passed, 7 skipped; PowerShell 7 232 passed, 36 skipped (268 tests).
- `ai/maintenance` adds `Tests\Repository.Tests.ps1` (8 tests): Windows
PowerShell 269 passed, 7 skipped; PowerShell 7 240 passed, 36 skipped.
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.
## Next step
Group A on `ai/defects-a`, starting with the shared test helpers.
Group B (14 to 17) on `ai/defects-b`, then C, D, the E decisions, and the
bugs from the issue triage (`ai/issue-fixes`).

4
.memory-bank/progress.md

@ -93,7 +93,7 @@ authorization, restrict wiki editing to collaborators, and ask
Numbered as agreed with the maintainer; each is documented on its page.
#### A: Crashes and wrong results
#### A: Crashes and wrong results (fixed on `ai/defects-a`, not merged)
- (1) `Set-NTFSInheritance` reads an unset `Nullable<bool>` when
`-AccessInheritanceEnabled` is omitted; omitted should mean unchanged.
@ -122,6 +122,8 @@ Numbered as agreed with the maintainer; each is documented on its page.
`BeginProcessing` and leave them enabled.
- (13) Format view `Children2`: the `Inherits` column uses
`IsInheritanceBlocked`, so it always shows `True` for `Get-ChildItem2`.
- Review of group A: five Major findings fixed in the last commit; the
Minor ones are listed in the PR description.
#### B: Ignored parameters and parameter sets

51
.memory-bank/systemPatterns.md

@ -66,32 +66,33 @@ Each Decision record is a file in `decisions/`; read only the relevant ones.
- Run platyPS in Windows PowerShell 5.1 against a module build; a copy of
`Docs/Cmdlets` must round-trip through `Update-MarkdownHelp` unchanged.
platyPS rewrites non-ASCII punctuation, so keep cmdlet pages ASCII-only.
- GitHub renders the docs (Decision 9); CI publishes them to the wiki
(Decision 11). MarkdownLinkCheck: relative `Docs` links, no anchors;
`Tests\Wiki.Tests.ps1`: every wiki link and anchor (GitHub slug rules).
Neither covers the links in `README.md` and `CHANGELOG.md`.
- The wiki is generated from `Docs`; never edit the wiki. Pages are named
after their files, `Docs/README.md` becomes Home, and its cmdlet groups
form the sidebar; a cmdlet missing there fails `Wiki.Tests.ps1`.
- In cmdlet pages, end a sentence with a link: platyPS renders a link as
`text (url)` in the help file and drops the space after it.
- Verify examples in a `$env:TEMP` sandbox, never on real data; parse every
example and check its parameters against `Get-Command` metadata.
It takes a parameter's `Position` from `Get-Help`, that is from the shipped
help file: after a position change, edit the page YAML, run
`New-ExternalHelp`, rebuild, and check the round trip.
- Links: MarkdownLinkCheck checks relative `Docs` links (no anchors),
`Tests\Wiki.Tests.ps1` the wiki links and anchors; neither covers
`README.md` and `CHANGELOG.md`. The wiki is generated from `Docs` (never
edit it); `Docs/README.md` becomes Home, its cmdlet groups the sidebar.
- In cmdlet pages, end a sentence with a link (platyPS drops the space after
it). Verify examples in a `$env:TEMP` sandbox, never on real data.
### Testing the module
- Pester 5 tests in `Tests/*.Tests.ps1` import
`NTFSSecurity\bin\Release\NTFSSecurity.psd1`; CI runs them in Windows
PowerShell 5.1 and in PowerShell 7 (Decision 11).
- `Get-Help -Online` tests use the internal hook `BypassOnlineHelpRetrieval`
(URI instead of a browser); it skips the help file in PowerShell 7, so
those 36 tests run only in Windows PowerShell.
- `.github/scripts/Invoke-Tests.ps1` runs Pester in CI: counts and failures
to the job summary, NUnit to `test-results`; failed test files fail too.
- `Tests\Manifest.Tests.ps1`: `Test-ModuleManifest` without errors or
warnings, exactly 36 cmdlets, one version in manifest and assemblies
(Decision 10). A new cmdlet updates `CmdletsToExport` and that count.
- `Tests\Release.Tests.ps1` checks that `CHANGELOG.md` has release notes
for the manifest version (dated section, or `[Unreleased]` for a
prerelease) and the packages: only `FileList` files, version with label,
command tags, and `NTFSSecurity.zip` with the module folder.
`NTFSSecurity\bin\Release\NTFSSecurity.psd1`; CI runs every file of
`Tests` in Windows PowerShell 5.1 and in PowerShell 7 (Decision 11) with
`.github/scripts/Invoke-Tests.ps1` (job summary, NUnit `test-results`).
- A test that changes files, links, or security descriptors uses
`Tests\TestHelpers.psm1`: its own sandbox below
`$env:TEMP\NTFSSecurity.Tests`, `Assert-TestSandboxPath` before each
change, `Remove-TestSandbox` (links first, then ACL reset). Cases that need
a privilege skip with `Test-PrivilegeHeld` and run in CI (elevated);
`Block-TestReadPermission` (OWNER RIGHTS deny) makes a read fail without
elevation.
- `Get-Help -Online` tests use the internal hook `BypassOnlineHelpRetrieval`,
which PowerShell 7 ignores for the help file: 36 tests run only in Windows
PowerShell.
- `Manifest.Tests.ps1`: `Test-ModuleManifest` clean, exactly 36 cmdlets, one
version in manifest and assemblies (Decision 10). `Release.Tests.ps1`:
release notes for the manifest version, and the packages (`FileList`
files, version with label, command tags, zip layout).

40
CHANGELOG.md

@ -52,5 +52,45 @@ The format is based on
- Remove `Show-NTFSSimpleAccess`, which no longer exists, and duplicate
entries from the cmdlets that the module manifest exports and the
PowerShell Gallery lists
- Fix `Set-NTFSInheritance`, which failed with "Nullable object must have a
value" when `-AccessInheritanceEnabled` or `-AuditInheritanceEnabled` was
omitted; an omitted parameter now leaves its section unchanged
- Fix `Get-ChildItem2`, which stopped with an `InvalidCastException` when
`-Path` pointed to a file; it now returns the file, like `Get-ChildItem`
- Fix `Get-FileHash2`, which stopped at a folder in `-Path` and didn't hash
the files that followed it; folders are now skipped
- Fix `Get-NTFSAudit`, which returned nothing without the Security privilege
instead of an error, and which returned the entries of the previous item
again after a path whose security descriptor it couldn't read; it also no
longer takes ownership of an item whose audit entries it can't read, which
didn't help and could leave the owner changed
- Fix `Get-NTFSAccess`, which returned the entries of the previous item again
after a path whose ACL it couldn't read
- Fix `Add-NTFSAudit`, whose `-Account` and `-AccessRights` parameters were
both at position 2, so that positional calls failed; `-AccessRights` is
now at position 3, like in `Remove-NTFSAudit`
([#4](https://github.com/raandree/NTFSSecurity/issues/4))
- Fix `-PassThru` of `Add-NTFSAudit` with `-SecurityDescriptor` and of
`Remove-NTFSAudit` with `-Path`, which returned access entries; both now
return the audit entries
- Fix the `InheritanceEnabled` property of audit entries, which reported the
inheritance of the access entries; it now reports whether the audit
entries are inherited
- Fix `Get-NTFSInheritance -SecurityDescriptor`, which reported
`AuditInheritanceEnabled` as `$true` for a security descriptor that was
read without its audit section; it now reports `$null`, like `-Path`
- Fix `Get-NTFSOwner`, which wrote a "The pipeline has been stopped" error
for every path when a command such as `Select-Object -First 1` stopped the
pipeline, and which repeated a failed read instead of reporting the
denied access
- Fix `Copy-Item2`, which failed with a `DirectoryNotFoundException` when it
copied a folder that contained files
- Fix `Disable-Privileges`, which couldn't disable the privileges when the
module setting `EnablePrivileges` was `$false`
- Fix the inheritance cmdlets, which enabled the Backup, Restore, Take
Ownership, and Security privileges even when the module setting
`EnablePrivileges` was `$false`, and left them enabled
- Fix the `Inherits` column of the `Get-ChildItem2` output, which showed
`True` for every item, also for items whose inheritance is disabled
[Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD

8
Docs/Cmdlets/Add-NTFSAudit.md

@ -101,7 +101,7 @@ Aliases: FileSystemRights
Accepted values: None, ReadData, ListDirectory, WriteData, CreateFiles, AppendData, CreateDirectories, ReadExtendedAttributes, WriteExtendedAttributes, ExecuteFile, Traverse, DeleteSubdirectoriesAndFiles, ReadAttributes, WriteAttributes, Write, Delete, ReadPermissions, Read, ReadAndExecute, Modify, ChangePermissions, TakeOwnership, Synchronize, FullControl, GenericAll, GenericExecute, GenericWrite, GenericRead
Required: True
Position: 2
Position: 3
Default value: None
Accept pipeline input: True (ByPropertyName)
Accept wildcard characters: False
@ -278,9 +278,9 @@ The value passed to `-AppliesTo` is converted to this type and binds by property
## OUTPUTS
### Security2.FileSystemAccessRule2
### Security2.FileSystemAuditRule2
Without `-PassThru` the cmdlet writes nothing. With `-PassThru` the type depends on the parameter set: in the `Path` sets the cmdlet writes all audit entries of the item, explicit and inherited ones, as `Security2.FileSystemAuditRule2` objects, while in the `SecurityDescriptor` sets it writes the access entries of the descriptor as `Security2.FileSystemAccessRule2` objects. Use `Get-NTFSAudit` when you need the audit entries of a security descriptor.
Without `-PassThru` the cmdlet writes nothing. With `-PassThru` the cmdlet writes all audit entries of the item or the security descriptor, explicit and inherited ones, as `Security2.FileSystemAuditRule2` objects. Before 5.0.0, the `SecurityDescriptor` sets wrote the access entries of the descriptor instead.
## NOTES
@ -290,7 +290,7 @@ Writing the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage
If the security descriptor cannot be read or written because access is denied, the cmdlet takes ownership of the item, repeats the operation, and restores the previous owner. If the second attempt fails as well, the cmdlet writes an error, and the ownership change is not rolled back.
The syntax shows `-Path`, `-Account`, and `-AccessRights` as positional parameters, but `-Account` and `-AccessRights` are both declared at position 2. A command that passes them positionally therefore fails with the error that positional parameters cannot be bound because no names were given, and `Get-Command Add-NTFSAudit -Syntax` leaves `-Account` out for the same reason. Pass `-Account` and `-AccessRights` by name, as the examples above do.
`-Path` or `-SecurityDescriptor`, `-Account`, and `-AccessRights` are positional parameters at positions 1, 2, and 3, like in `Remove-NTFSAudit`. Before 5.0.0, `-Account` and `-AccessRights` were both declared at position 2, so a command that passed them by position failed.
An audit entry alone does not create events. Windows writes the events to the security log only while the "Audit object access" policy, or the corresponding "Audit File System" advanced audit policy, is enabled for success, failure, or both. That policy is a Windows setting and is not managed by this module.

2
Docs/Cmdlets/Copy-Item2.md

@ -184,7 +184,7 @@ By default this cmdlet returns nothing. With `-PassThru $true` it returns an `Al
`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.
Copying a folder that contains files currently fails with a `CopyError` that reports a `DirectoryNotFoundException` for the first file in the folder. Copy files individually, for example by piping `Get-ChildItem2 -Recurse -File` into this cmdlet, and create the target folders beforehand.
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 skips the remaining paths that were passed in the same call. Items that arrive one by one through the pipeline are not affected, because each of them is processed separately.

2
Docs/Cmdlets/Disable-NTFSAccessInheritance.md

@ -168,6 +168,8 @@ Blocking access inheritance requires permission to change the DACL of the item,
A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.
Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.
## RELATED LINKS
[Enable-NTFSAccessInheritance](Enable-NTFSAccessInheritance.md)

2
Docs/Cmdlets/Disable-NTFSAuditInheritance.md

@ -170,6 +170,8 @@ If the descriptor cannot be opened because the account has no permission to the
A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.
Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.
## RELATED LINKS
[Enable-NTFSAuditInheritance](Enable-NTFSAuditInheritance.md)

2
Docs/Cmdlets/Disable-Privileges.md

@ -99,6 +99,8 @@ With `-PassThru`, the cmdlet writes the privilege collection of the current proc
When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), the file system cmdlets of the module try to enable the Backup, Restore, Take Ownership, and Security privileges while they run and disable the privileges they enabled when they finish. You therefore need `Disable-Privileges` only after an explicit `Enable-Privileges`. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group.
Before 5.0.0, when `EnablePrivileges` was `$false`, the cmdlet wrote warnings that it could not disable the privileges and left them enabled.
## RELATED LINKS
[Enable-Privileges](Enable-Privileges.md)

2
Docs/Cmdlets/Enable-NTFSAccessInheritance.md

@ -167,6 +167,8 @@ Restoring access inheritance requires permission to change the DACL of the item,
A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.
Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.
## RELATED LINKS
[Disable-NTFSAccessInheritance](Disable-NTFSAccessInheritance.md)

2
Docs/Cmdlets/Enable-NTFSAuditInheritance.md

@ -169,6 +169,8 @@ If the descriptor cannot be opened because the account has no permission to the
A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.
Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.
## RELATED LINKS
[Disable-NTFSAuditInheritance](Disable-NTFSAuditInheritance.md)

6
Docs/Cmdlets/Get-ChildItem2.md

@ -182,7 +182,7 @@ Accept wildcard characters: False
### -Path
Specifies the folders whose content you want to list. Relative paths are resolved against the current location, and wildcard characters are not supported. If you omit this parameter, the cmdlet lists the current location. A value that points to a file instead of a folder produces an error.
Specifies the folders whose content you want to list. Relative paths are resolved against the current location, and wildcard characters are not supported. If you omit this parameter, the cmdlet lists the current location. A value that points to a file returns that file, like `Get-ChildItem`, unless you use `-Directory`.
```yaml
Type: String[]
@ -301,10 +301,14 @@ The cmdlet returns this object for every folder it finds. Depending on the modul
The module defines the alias `dir2` for this cmdlet.
The default table view shows the `Mode`, `Inherits`, `LastWriteTime`, `Size(M)`, and `Name` columns. `Inherits` is `False` for an item whose access inheritance is disabled. Reading that value costs one access to the ACL of each displayed item, which slows down the display of large listings; to avoid it, select the properties you need, for example with `Format-Table -Property Mode, LastWriteTime, Length, Name`. Objects that you pipe to another command are not affected. Before 5.0.0, the column showed `True` for every item.
The `PrivateData` section of the module manifest `NTFSSecurity.psd1` contains two settings that this cmdlet reads when it starts. `GetFileSystemModeProperty` adds the calculated `Mode` property to every item. `IdentifyHardLinks` adds the `HardLinkCount` property to every file, which requires an extra call into the file system for each file and therefore slows down large listings noticeably. Set either value to `$false` in the manifest and import the module again if you prefer the faster enumeration over the additional properties.
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`.
## RELATED LINKS
[Get-Item2](Get-Item2.md)

4
Docs/Cmdlets/Get-FileHash2.md

@ -23,7 +23,7 @@ The `Get-FileHash2` cmdlet calculates the hash value of each file that `-Path` p
`-Algorithm` selects the hash algorithm and accepts `SHA1`, `SHA256`, `SHA384`, `SHA512`, `MACTripleDES`, `MD5`, and `RIPEMD160`. The default is `SHA256`.
The cmdlet hashes files only. A path that points to a folder is skipped, and because the cmdlet stops processing the current input when it meets one, a folder in the middle of a `-Path` array suppresses the results of the paths that follow it in the same array. Pass only file paths, or filter folders out before you pipe items into the cmdlet. A path that does not exist produces a non-terminating `ReadFileError`.
The cmdlet hashes files only and skips paths that point to folders. A path that does not exist produces a non-terminating `ReadFileError`.
`-Path` accepts pipeline input by value and by property name through its `FullName` alias, so you can pipe the output of `Get-ChildItem2`, `Get-Item2`, or `Get-ChildItem` into the cmdlet; folders that arrive through the pipeline are skipped individually. Because the cmdlet reads files through the AlphaFS library, it also hashes files whose path exceeds the 260-character `MAX_PATH` limit. Relative paths are resolved against the current location.
@ -125,6 +125,8 @@ The hash is returned as an uppercase hexadecimal string without separators, whic
`MACTripleDES` is a keyed message authentication code that is created with a key that is generated for each call, so its result is not reproducible across invocations and is not suitable for comparing files.
Before 5.0.0, a folder in a `-Path` array stopped the processing of that array, so the files that followed the folder were not hashed.
## RELATED LINKS
[Get-ChildItem2](Get-ChildItem2.md)

2
Docs/Cmdlets/Get-NTFSAccess.md

@ -184,6 +184,8 @@ If the ACL of an item cannot be read because access is denied, the cmdlet tries
Entries whose account cannot be translated into a name are returned with their SID. Use `Get-NTFSOrphanedAccess` to list only those entries.
Before 5.0.0, after a path whose ACL could not be read, the cmdlet returned the entries of the previous item again.
## RELATED LINKS
[Add-NTFSAccess](Add-NTFSAccess.md)

8
Docs/Cmdlets/Get-NTFSAudit.md

@ -173,15 +173,17 @@ You can pass an account name or a SID string to `-Account`, which the cmdlet con
### Security2.FileSystemAuditRule2
The cmdlet returns one object per audit entry, with the audited account, the audited access rights, the audit flags, the inheritance and propagation flags, the `IsInherited` flag, and the `InheritedFrom` path. When an item has no audit entries, or when the SACL cannot be read, the cmdlet returns nothing for that item.
The cmdlet returns one object per audit entry, with the audited account, the audited access rights, the audit flags, the inheritance and propagation flags, the `IsInherited` flag, and the `InheritedFrom` path. When an item has no audit entries, the cmdlet returns nothing for that item; when its SACL cannot be read, the cmdlet writes an error.
## NOTES
When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.
Reading the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it the cmdlet falls back to reading the security descriptor without its SACL; it then returns no audit entries and reports no error, which looks the same as an item that is not audited at all.
Reading the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it, the cmdlet writes the non-terminating error `ReadSecurityError` for each item, which reports "A required privilege is not held by the client". `Get-NTFSSecurityDescriptor` reads a security descriptor without its SACL when the privilege is missing; for such a descriptor, the cmdlet writes a `ReadSecurityError` as well.
If the security descriptor cannot be read because access is denied, the cmdlet takes ownership of the item, reads the descriptor again, and restores the previous owner. If the second attempt fails as well, the cmdlet writes an error, and the ownership change is not rolled back.
If reading the audit entries is denied, the cmdlet writes a `ReadSecurityError` with the category `PermissionDenied`. It doesn't take ownership of the item, because ownership grants no access to the SACL.
Before 5.0.0, the cmdlet returned no entries and no error without the Security privilege, and after a path whose security descriptor could not be read, it returned the entries of the previous item again. The `InheritanceEnabled` property of the entries also reported whether the access entries were inherited instead of the audit entries.
## RELATED LINKS

6
Docs/Cmdlets/Get-NTFSInheritance.md

@ -29,7 +29,7 @@ The `Get-NTFSInheritance` cmdlet reports whether a file or folder inherits acces
`AccessInheritanceEnabled` is `$false` when the discretionary access control list (DACL) of the item is protected, which is the state that `Disable-NTFSAccessInheritance` produces. `AuditInheritanceEnabled` reports the same for the system access control list (SACL), which holds the audit rules. When the audit section cannot be read because the session does not hold the Security privilege, `AuditInheritanceEnabled` is `$null` and its column stays empty; the access value is still reported and no error is written.
In the `Path` parameter set the cmdlet reads the security descriptor of each item from disk. In the `SecurityDescriptor` parameter set it reads the state from the `Security2.FileSystemSecurity2` objects that `Get-NTFSSecurityDescriptor` returns, without touching the file system. Note that a descriptor that was retrieved without its audit section reports `AuditInheritanceEnabled` as `$true`, because the protection flag of a section that was never read is not set.
In the `Path` parameter set the cmdlet reads the security descriptor of each item from disk. In the `SecurityDescriptor` parameter set it reads the state from the `Security2.FileSystemSecurity2` objects that `Get-NTFSSecurityDescriptor` returns, without touching the file system. A descriptor that was read without its audit section, because the session doesn't hold the Security privilege, reports `AuditInheritanceEnabled` as `$null`, like the `Path` parameter set.
`-Path` accepts pipeline input by value and by property name through its `FullName` alias, so the output of `Get-ChildItem`, `Get-ChildItem2`, and `Get-Item2` binds to it. Relative paths are resolved against the current location, and when no path is supplied at all, the cmdlet reports the current location.
@ -130,10 +130,14 @@ When the module setting `EnablePrivileges` is `$true` (the default in the `Priva
Reading the audit section (SACL) of an item requires the Security privilege (`SeSecurityPrivilege`), which an account can only use in an elevated session. Without it, the cmdlet still reports the access state and sets `AuditInheritanceEnabled` to `$null` instead of writing an error.
Before 5.0.0, a security descriptor that was read without its audit section reported `AuditInheritanceEnabled` as `$true`.
If the security descriptor of an item cannot be opened because the account has no permission to it, the cmdlet takes ownership of the item, reads the state, and sets the previous owner back. That fallback only succeeds when the account can take ownership of the item and restore the original owner; otherwise the cmdlet writes an error and continues with the next item.
A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.
Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.
## RELATED LINKS
[Set-NTFSInheritance](Set-NTFSInheritance.md)

4
Docs/Cmdlets/Get-NTFSOwner.md

@ -127,6 +127,10 @@ When the module setting `EnablePrivileges` is `$true` (the default in the `Priva
The module also adds an `Owner` script property to `System.IO.FileInfo` and `System.IO.DirectoryInfo`, so `(Get-Item C:\Data).Owner` returns the owning account as a `Security2.IdentityReference2` object as well.
If the owner of an item cannot be read because access is denied, the cmdlet writes the non-terminating error `ReadSecurityError` with the category `PermissionDenied` and continues with the next path. It does not take ownership of the item, which would replace the owner that it reports.
Before 5.0.0, a command that stopped the pipeline early, such as `Select-Object -First 1`, made the cmdlet write a `ReadSecurityError` with the message "The pipeline has been stopped" for every path.
## RELATED LINKS
[Set-NTFSOwner](Set-NTFSOwner.md)

4
Docs/Cmdlets/Remove-NTFSAudit.md

@ -276,9 +276,9 @@ The value passed to `-AppliesTo` is converted to this type and binds by property
## OUTPUTS
### Security2.FileSystemAccessRule2
### Security2.FileSystemAuditRule2
Without `-PassThru` the cmdlet writes nothing. With `-PassThru` the type depends on the parameter set: in the `Path` sets the cmdlet writes all access entries of the item, explicit and inherited ones, as `Security2.FileSystemAccessRule2` objects, while in the `SecurityDescriptor` sets it writes all audit entries of the descriptor as `Security2.FileSystemAuditRule2` objects. Use `Get-NTFSAudit` to check the audit entries of an item after the removal.
Without `-PassThru` the cmdlet writes nothing. With `-PassThru` the cmdlet writes all audit entries of the item or the security descriptor, explicit and inherited ones, as `Security2.FileSystemAuditRule2` objects. Before 5.0.0, the `Path` sets wrote the access entries of the item instead.
## NOTES

10
Docs/Cmdlets/Set-NTFSInheritance.md

@ -31,7 +31,7 @@ The `Set-NTFSInheritance` cmdlet turns the inheritance of access rules and audit
The cmdlet performs the same operations as `Enable-NTFSAccessInheritance`, `Disable-NTFSAccessInheritance`, `Enable-NTFSAuditInheritance`, and `Disable-NTFSAuditInheritance`, but it does not expose their switches and it does not use their defaults. `-AccessInheritanceEnabled $false` discards the inherited access rules instead of copying them into the item's own DACL, `-AccessInheritanceEnabled $true` keeps the explicit access rules, `-AuditInheritanceEnabled $false` copies the inherited audit rules into the item's own SACL, and `-AuditInheritanceEnabled $true` removes the explicit audit rules. Use the individual Enable and Disable cmdlets when you need the opposite behavior.
Specify both `-AccessInheritanceEnabled` and `-AuditInheritanceEnabled`. The cmdlet compares the current state against the parameter value even when the parameter was not supplied, and when such a comparison reports a difference it fails with the non-terminating error "Nullable object must have a value". Omitting `-AccessInheritanceEnabled` always triggers that error, and omitting `-AuditInheritanceEnabled` triggers it in a session that can read the audit section. Changing the audit section requires the Security privilege and therefore an elevated session.
Omit `-AccessInheritanceEnabled` or `-AuditInheritanceEnabled` to leave that section unchanged. Changing the audit section requires the Security privilege and therefore an elevated session.
In the `Path` parameter set the cmdlet writes each changed section back to disk immediately. In the `SecurityDescriptor` parameter set it changes the `Security2.FileSystemSecurity2` object in memory only; nothing reaches the file system until you pass that object to `Set-NTFSSecurityDescriptor`. `-Path`, `-AccessInheritanceEnabled`, and `-AuditInheritanceEnabled` all accept pipeline input by property name, so a `Security2.FileSystemInheritanceInfo` object from `Get-NTFSInheritance` binds to all three at once.
@ -77,7 +77,7 @@ The first two commands read the security descriptor and change its inheritance i
### -AccessInheritanceEnabled
Specifies whether the item inherits access rules from its parent folder. `$true` removes the protection from the DACL and keeps the access rules that are stored directly on the item; `$false` protects the DACL and discards the rules the item currently inherits, which leaves only its explicit rules. The section is left untouched when the requested value already matches the current state. Always supply this parameter, because the cmdlet fails with "Nullable object must have a value" when it has to compare against a value that was not provided.
Specifies whether the item inherits access rules from its parent folder. `$true` removes the protection from the DACL and keeps the access rules that are stored directly on the item; `$false` protects the DACL and discards the rules the item currently inherits, which leaves only its explicit rules. The section is left untouched when the requested value already matches the current state. When you omit the parameter, the access section is left unchanged.
```yaml
Type: Boolean
@ -93,7 +93,7 @@ Accept wildcard characters: False
### -AuditInheritanceEnabled
Specifies whether the item inherits audit rules from its parent folder. `$true` removes the protection from the SACL and removes the audit rules that are stored directly on the item; `$false` protects the SACL and copies the inherited audit rules into it. The section is left untouched when the requested value already matches the current state. Reading and writing the audit section requires the Security privilege and therefore an elevated session.
Specifies whether the item inherits audit rules from its parent folder. `$true` removes the protection from the SACL and removes the audit rules that are stored directly on the item; `$false` protects the SACL and copies the inherited audit rules into it. The section is left untouched when the requested value already matches the current state. When you omit the parameter, the audit section is left unchanged. Reading and writing the audit section requires the Security privilege and therefore an elevated session.
```yaml
Type: Boolean
@ -192,6 +192,10 @@ If the descriptor cannot be opened because the account has no permission to the
A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.
Before 5.0.0, omitting `-AccessInheritanceEnabled` or `-AuditInheritanceEnabled` could fail with the error "Nullable object must have a value".
Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.
## RELATED LINKS
[Get-NTFSInheritance](Get-NTFSInheritance.md)

6
Docs/Concepts.md

@ -188,9 +188,9 @@ The access, audit, inheritance, owner, and security descriptor cmdlets enable
these privileges automatically while they run and disable the ones they
enabled when they finish. If a privilege cannot be enabled, the cmdlet
continues without it. You can turn this behavior off with the
`EnablePrivileges` module setting. The inheritance cmdlets are an exception:
they always try to enable the privileges, and when `EnablePrivileges` is
`$false`, they leave them enabled.
`EnablePrivileges` module setting. Before 5.0.0, the inheritance cmdlets were
an exception: they always tried to enable the privileges, and when
`EnablePrivileges` was `$false`, they left them enabled.
`Enable-Privileges` enables the four privileges for the current PowerShell
process until you run `Disable-Privileges` or close the session.

39
NTFSSecurity/AccessCmdlets/GetAccess.cs

@ -79,13 +79,13 @@ namespace NTFSSecurity
protected override void ProcessRecord()
{
IEnumerable<FileSystemAccessRule2> acl = null;
FileSystemInfo item = null;
if (ParameterSetName == "Path")
{
foreach (var path in paths)
{
FileSystemInfo item = null;
IEnumerable<FileSystemAccessRule2> acl = null;
try
{
item = GetFileSystemInfo2(path);
@ -122,34 +122,27 @@ namespace NTFSSecurity
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.OpenError, path));
continue;
}
finally
{
if (acl != null)
{
if (account != null)
{
acl = acl.Where(ace => ace.Account == account);
}
acl.ForEach(ace => WriteObject(ace));
}
}
WriteAccessRules(acl);
}
}
else
{
foreach (var sd in securityDescriptors)
{
acl = FileSystemAccessRule2.GetFileSystemAccessRules(sd, !excludeExplicit, !excludeInherited, getInheritedFrom);
if (account != null)
{
acl = acl.Where(ace => ace.Account == account);
}
acl.ForEach(ace => WriteObject(ace));
WriteAccessRules(FileSystemAccessRule2.GetFileSystemAccessRules(sd, !excludeExplicit, !excludeInherited, getInheritedFrom));
}
}
}
private void WriteAccessRules(IEnumerable<FileSystemAccessRule2> acl)
{
if (account != null)
{
acl = acl.Where(ace => ace.Account == account);
}
acl.ForEach(ace => WriteObject(ace));
}
}
}
}

4
NTFSSecurity/AuditCmdlets/AddAudit.cs

@ -54,7 +54,7 @@ namespace NTFSSecurity
set { account = value; }
}
[Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true)]
[Parameter(Mandatory = true, Position = 3, ValueFromPipelineByPropertyName = true)]
[Alias("FileSystemRights")]
public FileSystemRights2 AccessRights
{
@ -169,7 +169,7 @@ namespace NTFSSecurity
if (passThru == true)
{
FileSystemAccessRule2.GetFileSystemAccessRules(sd, true, true).ForEach(ace => WriteObject(ace));
FileSystemAuditRule2.GetFileSystemAuditRules(sd, true, true).ForEach(ace => WriteObject(ace));
}
}
}

69
NTFSSecurity/AuditCmdlets/GetAudit.cs

@ -79,13 +79,13 @@ namespace NTFSSecurity
protected override void ProcessRecord()
{
IEnumerable<FileSystemAuditRule2> acl = null;
FileSystemInfo item = null;
if (ParameterSetName == "Path")
{
foreach (var path in paths)
{
FileSystemInfo item = null;
IEnumerable<FileSystemAuditRule2> acl = null;
try
{
item = GetFileSystemInfo2(path);
@ -98,58 +98,55 @@ namespace NTFSSecurity
try
{
acl = FileSystemAuditRule2.GetFileSystemAuditRules(item, !excludeExplicit, !excludeInherited, getInheritedFrom);
acl = GetAuditRules(item);
}
catch (UnauthorizedAccessException)
catch (UnauthorizedAccessException ex)
{
try
{
var ownerInfo = FileSystemOwner.GetOwner(item);
var previousOwner = ownerInfo.Owner;
FileSystemOwner.SetOwner(item, System.Security.Principal.WindowsIdentity.GetCurrent().User);
acl = FileSystemAuditRule2.GetFileSystemAuditRules(item, !excludeExplicit, !excludeInherited, getInheritedFrom);
FileSystemOwner.SetOwner(item, previousOwner);
}
catch (Exception ex2)
{
WriteError(new ErrorRecord(ex2, "ReadSecurityError", ErrorCategory.WriteError, path));
continue;
}
// Taking ownership grants no access to the SACL, so it wouldn't help, and it would change the owner.
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.PermissionDenied, path));
continue;
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.OpenError, path));
continue;
}
finally
{
if (acl != null)
{
if (account != null)
{
acl = acl.Where(ace => ace.Account == account);
}
acl.ForEach(ace => WriteObject(ace));
}
}
WriteAuditRules(acl);
}
}
else
{
foreach (var sd in securityDescriptors)
{
acl = FileSystemAuditRule2.GetFileSystemAuditRules(sd, !excludeExplicit, !excludeInherited, getInheritedFrom);
if (account != null)
if (!sd.HasAuditSection)
{
acl = acl.Where(ace => ace.Account == account);
var ex = new InvalidOperationException(string.Format(
"The security descriptor of '{0}' doesn't contain the audit entries, because it was read without the Security privilege.", sd.FullName));
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.InvalidData, sd));
continue;
}
acl.ForEach(ace => WriteObject(ace));
WriteAuditRules(FileSystemAuditRule2.GetFileSystemAuditRules(sd, !excludeExplicit, !excludeInherited, getInheritedFrom));
}
}
}
private IEnumerable<FileSystemAuditRule2> GetAuditRules(FileSystemInfo item)
{
// Reading only the SACL fails without the Security privilege, instead of returning no entries.
var sd = new FileSystemSecurity2(item, System.Security.AccessControl.AccessControlSections.Audit);
return FileSystemAuditRule2.GetFileSystemAuditRules(sd, !excludeExplicit, !excludeInherited, getInheritedFrom);
}
private void WriteAuditRules(IEnumerable<FileSystemAuditRule2> acl)
{
if (account != null)
{
acl = acl.Where(ace => ace.Account == account);
}
acl.ForEach(ace => WriteObject(ace));
}
}
}

2
NTFSSecurity/AuditCmdlets/RemoveAudit.cs

@ -162,7 +162,7 @@ namespace NTFSSecurity
if (passThru == true)
{
FileSystemAccessRule2.GetFileSystemAccessRules(item, true, true).ForEach(ace => WriteObject(ace));
FileSystemAuditRule2.GetFileSystemAuditRules(item, true, true).ForEach(ace => WriteObject(ace));
}
}
}

3
NTFSSecurity/BaseCmdlets.cs

@ -278,7 +278,8 @@ namespace NTFSSecurity
protected void DisableFileSystemPrivileges()
{
var privileges = privControl.GetPrivileges();
// Refreshes the field that DisablePrivilege reads; it is null when BeginProcessing enabled nothing.
privileges = privControl.GetPrivileges();
if (privileges.Where(p => p.Privilege == Privilege.TakeOwnership) != null)
if (!TryDisablePrivilege(Privilege.TakeOwnership))

1
NTFSSecurity/InheritanceCmdlets/DisableAccessInheritance.cs

@ -53,7 +53,6 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
base.BeginProcessing();
EnableFileSystemPrivileges(true);
}
protected override void ProcessRecord()

1
NTFSSecurity/InheritanceCmdlets/DisableAuditInheritance.cs

@ -54,7 +54,6 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
base.BeginProcessing();
EnableFileSystemPrivileges(true);
}
protected override void ProcessRecord()

1
NTFSSecurity/InheritanceCmdlets/EnableAccessInheritance.cs

@ -53,7 +53,6 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
base.BeginProcessing();
EnableFileSystemPrivileges(true);
}
protected override void ProcessRecord()

1
NTFSSecurity/InheritanceCmdlets/EnableAuditInheritance.cs

@ -53,7 +53,6 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
base.BeginProcessing();
EnableFileSystemPrivileges(true);
}
protected override void ProcessRecord()

1
NTFSSecurity/InheritanceCmdlets/GetInheritance.cs

@ -38,7 +38,6 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
base.BeginProcessing();
EnableFileSystemPrivileges(true);
if (paths.Count == 0)
{

194
NTFSSecurity/InheritanceCmdlets/SetInheritance.cs

@ -61,7 +61,6 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
base.BeginProcessing();
EnableFileSystemPrivileges(true);
}
protected override void ProcessRecord()
@ -84,41 +83,7 @@ namespace NTFSSecurity
try
{
var currentState = FileSystemInheritanceInfo.GetFileSystemInheritanceInfo(item);
if (currentState.AccessInheritanceEnabled != accessInheritanceEnabled)
{
WriteVerbose("AccessInheritanceEnabled not equal");
if (accessInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAccessInheritance");
FileSystemInheritanceInfo.EnableAccessInheritance(item, false);
}
else
{
WriteVerbose("Calling DisableAccessInheritance");
FileSystemInheritanceInfo.DisableAccessInheritance(item, true);
}
}
else
WriteVerbose("AccessInheritanceEnabled is equal - no change was done");
if (currentState.AuditInheritanceEnabled != auditInheritanceEnabled)
{
WriteVerbose("AuditInheritanceEnabled not equal");
if (auditInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAuditInheritance");
FileSystemInheritanceInfo.EnableAuditInheritance(item, true);
}
else
{
WriteVerbose("Calling DisableAuditInheritance");
FileSystemInheritanceInfo.DisableAuditInheritance(item, false);
}
}
else
WriteVerbose("AuditInheritanceEnabled is equal - no change was done");
SetInheritanceState(item);
}
catch (UnauthorizedAccessException)
{
@ -129,41 +94,7 @@ namespace NTFSSecurity
FileSystemOwner.SetOwner(item, System.Security.Principal.WindowsIdentity.GetCurrent().User);
var currentState = FileSystemInheritanceInfo.GetFileSystemInheritanceInfo(item);
if (currentState.AccessInheritanceEnabled != accessInheritanceEnabled)
{
WriteVerbose("AccessInheritanceEnabled not equal");
if (accessInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAccessInheritance");
FileSystemInheritanceInfo.EnableAccessInheritance(item, false);
}
else
{
WriteVerbose("Calling DisableAccessInheritance");
FileSystemInheritanceInfo.DisableAccessInheritance(item, true);
}
}
else
WriteVerbose("AccessInheritanceEnabled is equal - no change was done");
if (currentState.AuditInheritanceEnabled != auditInheritanceEnabled)
{
WriteVerbose("AuditInheritanceEnabled not equal");
if (auditInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAuditInheritance");
FileSystemInheritanceInfo.EnableAuditInheritance(item, true);
}
else
{
WriteVerbose("Calling DisableAuditInheritance");
FileSystemInheritanceInfo.DisableAuditInheritance(item, false);
}
}
else
WriteVerbose("AuditInheritanceEnabled is equal - no change was done");
SetInheritanceState(item);
FileSystemOwner.SetOwner(item, previousOwner);
}
@ -191,41 +122,7 @@ namespace NTFSSecurity
{
foreach (var sd in securityDescriptors)
{
var currentState = FileSystemInheritanceInfo.GetFileSystemInheritanceInfo(sd);
if (currentState.AccessInheritanceEnabled != accessInheritanceEnabled)
{
WriteVerbose("AccessInheritanceEnabled not equal");
if (accessInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAccessInheritance");
FileSystemInheritanceInfo.EnableAccessInheritance(sd, false);
}
else
{
WriteVerbose("Calling DisableAccessInheritance");
FileSystemInheritanceInfo.DisableAccessInheritance(sd, true);
}
}
else
WriteVerbose("AccessInheritanceEnabled is equal - no change was done");
if (currentState.AuditInheritanceEnabled != auditInheritanceEnabled)
{
WriteVerbose("AuditInheritanceEnabled not equal");
if (auditInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAuditInheritance");
FileSystemInheritanceInfo.EnableAuditInheritance(sd, true);
}
else
{
WriteVerbose("Calling DisableAuditInheritance");
FileSystemInheritanceInfo.DisableAuditInheritance(sd, false);
}
}
else
WriteVerbose("AuditInheritanceEnabled is equal - no change was done");
SetInheritanceState(sd);
if (passThru)
{
@ -234,5 +131,90 @@ namespace NTFSSecurity
}
}
}
private void SetInheritanceState(FileSystemInfo item)
{
var currentState = FileSystemInheritanceInfo.GetFileSystemInheritanceInfo(item);
if (IsChangeRequested("AccessInheritanceEnabled", accessInheritanceEnabled, currentState.AccessInheritanceEnabled))
{
if (accessInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAccessInheritance");
FileSystemInheritanceInfo.EnableAccessInheritance(item, false);
}
else
{
WriteVerbose("Calling DisableAccessInheritance");
FileSystemInheritanceInfo.DisableAccessInheritance(item, true);
}
}
if (IsChangeRequested("AuditInheritanceEnabled", auditInheritanceEnabled, currentState.AuditInheritanceEnabled))
{
if (auditInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAuditInheritance");
FileSystemInheritanceInfo.EnableAuditInheritance(item, true);
}
else
{
WriteVerbose("Calling DisableAuditInheritance");
FileSystemInheritanceInfo.DisableAuditInheritance(item, false);
}
}
}
private void SetInheritanceState(FileSystemSecurity2 sd)
{
var currentState = FileSystemInheritanceInfo.GetFileSystemInheritanceInfo(sd);
if (IsChangeRequested("AccessInheritanceEnabled", accessInheritanceEnabled, currentState.AccessInheritanceEnabled))
{
if (accessInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAccessInheritance");
FileSystemInheritanceInfo.EnableAccessInheritance(sd, false);
}
else
{
WriteVerbose("Calling DisableAccessInheritance");
FileSystemInheritanceInfo.DisableAccessInheritance(sd, true);
}
}
if (IsChangeRequested("AuditInheritanceEnabled", auditInheritanceEnabled, currentState.AuditInheritanceEnabled))
{
if (auditInheritanceEnabled.Value)
{
WriteVerbose("Calling EnableAuditInheritance");
FileSystemInheritanceInfo.EnableAuditInheritance(sd, true);
}
else
{
WriteVerbose("Calling DisableAuditInheritance");
FileSystemInheritanceInfo.DisableAuditInheritance(sd, false);
}
}
}
// An omitted parameter leaves its section unchanged.
private bool IsChangeRequested(string parameterName, bool? requestedState, bool? currentState)
{
if (!requestedState.HasValue)
{
WriteVerbose(string.Format("{0} not specified - no change was done", parameterName));
return false;
}
if (currentState == requestedState)
{
WriteVerbose(string.Format("{0} is equal - no change was done", parameterName));
return false;
}
WriteVerbose(string.Format("{0} not equal", parameterName));
return true;
}
}
}

3
NTFSSecurity/ItemCmdlets/CopyItem2.cs

@ -105,6 +105,9 @@ namespace NTFSSecurity
{
if (ShouldProcess(resolvedPath, "Copy Directory"))
{
// 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);
WriteVerbose(string.Format("Directory '{0}' copied to '{0}'", resolvedPath, destination));
}

16
NTFSSecurity/ItemCmdlets/GetChildItem2.cs

@ -148,12 +148,11 @@ namespace NTFSSecurity
{
foreach (var path in paths)
{
DirectoryInfo di = null;
FileSystemInfo item = null;
try
{
di = (DirectoryInfo)GetFileSystemInfo2(path);
item = GetFileSystemInfo2(path);
}
catch (System.IO.FileNotFoundException ex)
{
@ -161,6 +160,17 @@ namespace NTFSSecurity
continue;
}
var di = item as DirectoryInfo;
if (di == null)
{
// Like Get-ChildItem, a file path returns the file itself.
if (!directory)
{
WriteFileSystemInfoCollection(new FileSystemInfo[] { item }.GetEnumerator());
}
continue;
}
try
{
WriteFileSystem(di, 0);

6
NTFSSecurity/MiscCmdlets/GetFileHash2.cs

@ -48,7 +48,11 @@ namespace NTFSSecurity
{
item = GetFileSystemInfo2(path) as FileInfo;
if (item == null)
return;
{
// Like Get-FileHash, skip folders and continue with the next path.
WriteVerbose(string.Format("Skipping '{0}', which is a folder", path));
continue;
}
}
catch (Exception ex)
{

2
NTFSSecurity/NTFSSecurity.format.ps1xml

@ -430,7 +430,7 @@
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>
!$_.IsInheritanceBlocked
-not $_.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access).AreAccessRulesProtected
</ScriptBlock>
</TableColumnItem>
<TableColumnItem>

31
NTFSSecurity/OwnerCmdlets/GetOwner.cs

@ -43,10 +43,11 @@ namespace NTFSSecurity.OwnerCmdlets
{
if (ParameterSetName == "Path")
{
FileSystemInfo item = null;
foreach (var path in paths)
{
FileSystemInfo item = null;
FileSystemOwner owner = null;
try
{
item = GetFileSystemInfo2(path);
@ -59,32 +60,22 @@ namespace NTFSSecurity.OwnerCmdlets
try
{
WriteObject(FileSystemOwner.GetOwner(item));
owner = FileSystemOwner.GetOwner(item);
}
catch (UnauthorizedAccessException)
catch (UnauthorizedAccessException ex)
{
try
{
var ownerInfo = FileSystemOwner.GetOwner(item);
var previousOwner = ownerInfo.Owner;
FileSystemOwner.SetOwner(item, System.Security.Principal.WindowsIdentity.GetCurrent().User);
WriteObject(FileSystemOwner.GetOwner(item));
FileSystemOwner.SetOwner(item, previousOwner);
}
catch (Exception ex2)
{
WriteError(new ErrorRecord(ex2, "ReadSecurityError", ErrorCategory.WriteError, path));
continue;
}
// Taking ownership to read the owner would replace the owner that the cmdlet reports.
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.PermissionDenied, path));
continue;
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.OpenError, path));
continue;
}
// Outside the try block, so that a stopped pipeline isn't reported as a read error.
WriteObject(owner);
}
}
else

162
NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml

@ -814,7 +814,19 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="FileSystemRights">
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="IdentityReference, ID">
<maml:name>Account</maml:name>
<maml:description>
<maml:para>Specifies the accounts whose access to the item is audited. The value is an account name such as `CONTOSO\JohnDoe`, `CONTOSO\Domain Users`, `BUILTIN\Users`, or `Everyone`, or a SID string such as `S-1-5-32-545`. When you pass several accounts, the cmdlet adds one audit entry per account.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">IdentityReference2[]</command:parameterValue>
<dev:type>
<maml:name>IdentityReference2[]</maml:name>
<maml:uri />
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="3" aliases="FileSystemRights">
<maml:name>AccessRights</maml:name>
<maml:description>
<maml:para>Specifies the access rights to audit. The value accepts the basic rights such as `Read`, `Write`, `Modify`, and `FullControl` as well as the individual rights such as `Delete` or `WriteAttributes`, and it accepts a comma-separated list that combines them. For the meaning of each right, see Concepts (../Concepts.md).</maml:para>
@ -856,18 +868,6 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="IdentityReference, ID">
<maml:name>Account</maml:name>
<maml:description>
<maml:para>Specifies the accounts whose access to the item is audited. The value is an account name such as `CONTOSO\JohnDoe`, `CONTOSO\Domain Users`, `BUILTIN\Users`, or `Everyone`, or a SID string such as `S-1-5-32-545`. When you pass several accounts, the cmdlet adds one audit entry per account.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">IdentityReference2[]</command:parameterValue>
<dev:type>
<maml:name>IdentityReference2[]</maml:name>
<maml:uri />
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AppliesTo</maml:name>
<maml:description>
@ -938,7 +938,19 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="FileSystemRights">
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="IdentityReference, ID">
<maml:name>Account</maml:name>
<maml:description>
<maml:para>Specifies the accounts whose access to the item is audited. The value is an account name such as `CONTOSO\JohnDoe`, `CONTOSO\Domain Users`, `BUILTIN\Users`, or `Everyone`, or a SID string such as `S-1-5-32-545`. When you pass several accounts, the cmdlet adds one audit entry per account.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">IdentityReference2[]</command:parameterValue>
<dev:type>
<maml:name>IdentityReference2[]</maml:name>
<maml:uri />
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="3" aliases="FileSystemRights">
<maml:name>AccessRights</maml:name>
<maml:description>
<maml:para>Specifies the access rights to audit. The value accepts the basic rights such as `Read`, `Write`, `Modify`, and `FullControl` as well as the individual rights such as `Delete` or `WriteAttributes`, and it accepts a comma-separated list that combines them. For the meaning of each right, see Concepts (../Concepts.md).</maml:para>
@ -980,18 +992,6 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="IdentityReference, ID">
<maml:name>Account</maml:name>
<maml:description>
<maml:para>Specifies the accounts whose access to the item is audited. The value is an account name such as `CONTOSO\JohnDoe`, `CONTOSO\Domain Users`, `BUILTIN\Users`, or `Everyone`, or a SID string such as `S-1-5-32-545`. When you pass several accounts, the cmdlet adds one audit entry per account.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">IdentityReference2[]</command:parameterValue>
<dev:type>
<maml:name>IdentityReference2[]</maml:name>
<maml:uri />
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AppliesTo</maml:name>
<maml:description>
@ -1062,7 +1062,19 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="FileSystemRights">
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="IdentityReference, ID">
<maml:name>Account</maml:name>
<maml:description>
<maml:para>Specifies the accounts whose access to the item is audited. The value is an account name such as `CONTOSO\JohnDoe`, `CONTOSO\Domain Users`, `BUILTIN\Users`, or `Everyone`, or a SID string such as `S-1-5-32-545`. When you pass several accounts, the cmdlet adds one audit entry per account.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">IdentityReference2[]</command:parameterValue>
<dev:type>
<maml:name>IdentityReference2[]</maml:name>
<maml:uri />
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="3" aliases="FileSystemRights">
<maml:name>AccessRights</maml:name>
<maml:description>
<maml:para>Specifies the access rights to audit. The value accepts the basic rights such as `Read`, `Write`, `Modify`, and `FullControl` as well as the individual rights such as `Delete` or `WriteAttributes`, and it accepts a comma-separated list that combines them. For the meaning of each right, see Concepts (../Concepts.md).</maml:para>
@ -1104,18 +1116,6 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="IdentityReference, ID">
<maml:name>Account</maml:name>
<maml:description>
<maml:para>Specifies the accounts whose access to the item is audited. The value is an account name such as `CONTOSO\JohnDoe`, `CONTOSO\Domain Users`, `BUILTIN\Users`, or `Everyone`, or a SID string such as `S-1-5-32-545`. When you pass several accounts, the cmdlet adds one audit entry per account.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">IdentityReference2[]</command:parameterValue>
<dev:type>
<maml:name>IdentityReference2[]</maml:name>
<maml:uri />
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AuditFlags</maml:name>
<maml:description>
@ -1193,7 +1193,19 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="FileSystemRights">
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="IdentityReference, ID">
<maml:name>Account</maml:name>
<maml:description>
<maml:para>Specifies the accounts whose access to the item is audited. The value is an account name such as `CONTOSO\JohnDoe`, `CONTOSO\Domain Users`, `BUILTIN\Users`, or `Everyone`, or a SID string such as `S-1-5-32-545`. When you pass several accounts, the cmdlet adds one audit entry per account.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">IdentityReference2[]</command:parameterValue>
<dev:type>
<maml:name>IdentityReference2[]</maml:name>
<maml:uri />
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="3" aliases="FileSystemRights">
<maml:name>AccessRights</maml:name>
<maml:description>
<maml:para>Specifies the access rights to audit. The value accepts the basic rights such as `Read`, `Write`, `Modify`, and `FullControl` as well as the individual rights such as `Delete` or `WriteAttributes`, and it accepts a comma-separated list that combines them. For the meaning of each right, see Concepts (../Concepts.md).</maml:para>
@ -1235,18 +1247,6 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="IdentityReference, ID">
<maml:name>Account</maml:name>
<maml:description>
<maml:para>Specifies the accounts whose access to the item is audited. The value is an account name such as `CONTOSO\JohnDoe`, `CONTOSO\Domain Users`, `BUILTIN\Users`, or `Everyone`, or a SID string such as `S-1-5-32-545`. When you pass several accounts, the cmdlet adds one audit entry per account.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">IdentityReference2[]</command:parameterValue>
<dev:type>
<maml:name>IdentityReference2[]</maml:name>
<maml:uri />
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AuditFlags</maml:name>
<maml:description>
@ -1312,7 +1312,7 @@
</command:syntaxItem>
</command:syntax>
<command:parameters>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="2" aliases="FileSystemRights">
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="3" aliases="FileSystemRights">
<maml:name>AccessRights</maml:name>
<maml:description>
<maml:para>Specifies the access rights to audit. The value accepts the basic rights such as `Read`, `Write`, `Modify`, and `FullControl` as well as the individual rights such as `Delete` or `WriteAttributes`, and it accepts a comma-separated list that combines them. For the meaning of each right, see Concepts (../Concepts.md).</maml:para>
@ -1490,10 +1490,10 @@
<command:returnValues>
<command:returnValue>
<dev:type>
<maml:name>Security2.FileSystemAccessRule2</maml:name>
<maml:name>Security2.FileSystemAuditRule2</maml:name>
</dev:type>
<maml:description>
<maml:para>Without `-PassThru` the cmdlet writes nothing. With `-PassThru` the type depends on the parameter set: in the `Path` sets the cmdlet writes all audit entries of the item, explicit and inherited ones, as `Security2.FileSystemAuditRule2` objects, while in the `SecurityDescriptor` sets it writes the access entries of the descriptor as `Security2.FileSystemAccessRule2` objects. Use `Get-NTFSAudit` when you need the audit entries of a security descriptor.</maml:para>
<maml:para>Without `-PassThru` the cmdlet writes nothing. With `-PassThru` the cmdlet writes all audit entries of the item or the security descriptor, explicit and inherited ones, as `Security2.FileSystemAuditRule2` objects. Before 5.0.0, the `SecurityDescriptor` sets wrote the access entries of the descriptor instead.</maml:para>
</maml:description>
</command:returnValue>
</command:returnValues>
@ -1502,7 +1502,7 @@
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>Writing the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it, the cmdlet writes a non-terminating `AddAceError` whose message states that a required privilege is not held by the client, and the item is left unchanged.</maml:para>
<maml:para>If the security descriptor cannot be read or written because access is denied, the cmdlet takes ownership of the item, repeats the operation, and restores the previous owner. If the second attempt fails as well, the cmdlet writes an error, and the ownership change is not rolled back.</maml:para>
<maml:para>The syntax shows `-Path`, `-Account`, and `-AccessRights` as positional parameters, but `-Account` and `-AccessRights` are both declared at position 2. A command that passes them positionally therefore fails with the error that positional parameters cannot be bound because no names were given, and `Get-Command Add-NTFSAudit -Syntax` leaves `-Account` out for the same reason. Pass `-Account` and `-AccessRights` by name, as the examples above do.</maml:para>
<maml:para>`-Path` or `-SecurityDescriptor`, `-Account`, and `-AccessRights` are positional parameters at positions 1, 2, and 3, like in `Remove-NTFSAudit`. Before 5.0.0, `-Account` and `-AccessRights` were both declared at position 2, so a command that passed them by position failed.</maml:para>
<maml:para>An audit entry alone does not create events. Windows writes the events to the security log only while the "Audit object access" policy, or the corresponding "Audit File System" advanced audit policy, is enabled for success, failure, or both. That policy is a Windows setting and is not managed by this module.</maml:para>
</maml:alert>
</maml:alertSet>
@ -2173,7 +2173,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:alertSet>
<maml:alert>
<maml:para>`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.</maml:para>
<maml:para>Copying a folder that contains files currently fails with a `CopyError` that reports a `DirectoryNotFoundException` for the first file in the folder. Copy files individually, for example by piping `Get-ChildItem2 -Recurse -File` into this cmdlet, and create the target folders beforehand.</maml:para>
<maml:para>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.</maml:para>
<maml:para>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 skips the remaining paths that were passed in the same call. Items that arrive one by one through the pipeline are not affected, because each of them is processed separately.</maml:para>
</maml:alert>
</maml:alertSet>
@ -2412,6 +2412,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>Blocking access inheritance requires permission to change the DACL of the item, which the owner of an item always has. If the descriptor cannot be opened, the cmdlet takes ownership of the item, applies the change, and sets the previous owner back. That fallback only succeeds when the account can take ownership of the item and restore the original owner; otherwise the cmdlet writes an error and continues with the next item.</maml:para>
<maml:para>A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.</maml:para>
<maml:para>Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -2656,6 +2657,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:para>The audit section of a security descriptor can only be read and written with the Security privilege (`SeSecurityPrivilege`), which an account can only use in an elevated session. Without it, the cmdlet writes a non-terminating error that reports Windows error 1314, "A required privilege is not held by the client", and the audit rules of the item stay unchanged.</maml:para>
<maml:para>If the descriptor cannot be opened because the account has no permission to the item, the cmdlet takes ownership of the item, applies the change, and sets the previous owner back. That fallback only succeeds when the account can take ownership of the item and restore the original owner; a missing Security privilege is not an access problem and is not repaired by it.</maml:para>
<maml:para>A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.</maml:para>
<maml:para>Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -2788,6 +2790,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:alertSet>
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), the file system cmdlets of the module try to enable the Backup, Restore, Take Ownership, and Security privileges while they run and disable the privileges they enabled when they finish. You therefore need `Disable-Privileges` only after an explicit `Enable-Privileges`. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group.</maml:para>
<maml:para>Before 5.0.0, when `EnablePrivileges` was `$false`, the cmdlet wrote warnings that it could not disable the privileges and left them enabled.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -3024,6 +3027,7 @@ PS C:\&gt; Get-Privileges | Where-Object { $_.Privilege -in 'Backup', 'Restore',
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>Restoring access inheritance requires permission to change the DACL of the item, which the owner of an item always has. If the descriptor cannot be opened, the cmdlet takes ownership of the item, applies the change, and sets the previous owner back. That fallback only succeeds when the account can take ownership of the item and restore the original owner; otherwise the cmdlet writes an error and continues with the next item.</maml:para>
<maml:para>A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.</maml:para>
<maml:para>Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -3268,6 +3272,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:para>The audit section of a security descriptor can only be read and written with the Security privilege (`SeSecurityPrivilege`), which an account can only use in an elevated session. Without it, the cmdlet writes a non-terminating error that reports Windows error 1314, "A required privilege is not held by the client", and the audit rules of the item stay unchanged.</maml:para>
<maml:para>If the descriptor cannot be opened because the account has no permission to the item, the cmdlet takes ownership of the item, applies the change, and sets the previous owner back. That fallback only succeeds when the account can take ownership of the item and restore the original owner; a missing Security privilege is not an access problem and is not repaired by it.</maml:para>
<maml:para>A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.</maml:para>
<maml:para>Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -3482,7 +3487,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="1" aliases="FullName">
<maml:name>Path</maml:name>
<maml:description>
<maml:para>Specifies the folders whose content you want to list. Relative paths are resolved against the current location, and wildcard characters are not supported. If you omit this parameter, the cmdlet lists the current location. A value that points to a file instead of a folder produces an error.</maml:para>
<maml:para>Specifies the folders whose content you want to list. Relative paths are resolved against the current location, and wildcard characters are not supported. If you omit this parameter, the cmdlet lists the current location. A value that points to a file returns that file, like `Get-ChildItem`, unless you use `-Directory`.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String[]</command:parameterValue>
<dev:type>
@ -3734,7 +3739,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="1" aliases="FullName">
<maml:name>Path</maml:name>
<maml:description>
<maml:para>Specifies the folders whose content you want to list. Relative paths are resolved against the current location, and wildcard characters are not supported. If you omit this parameter, the cmdlet lists the current location. A value that points to a file instead of a folder produces an error.</maml:para>
<maml:para>Specifies the folders whose content you want to list. Relative paths are resolved against the current location, and wildcard characters are not supported. If you omit this parameter, the cmdlet lists the current location. A value that points to a file returns that file, like `Get-ChildItem`, unless you use `-Directory`.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String[]</command:parameterValue>
<dev:type>
@ -3836,8 +3841,10 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:alert>
<maml:para>`Get-ChildItem2` enumerates the file system through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it returns items whose path exceeds the 260-character `MAX_PATH` limit that the built-in `Get-ChildItem` cmdlet is bound to. The objects are AlphaFS objects, not `System.IO` objects, and the other NTFSSecurity cmdlets accept them directly because their `-Path` parameters have the alias `FullName`.</maml:para>
<maml:para>The module defines the alias `dir2` for this cmdlet.</maml:para>
<maml:para>The default table view shows the `Mode`, `Inherits`, `LastWriteTime`, `Size(M)`, and `Name` columns. `Inherits` is `False` for an item whose access inheritance is disabled. Reading that value costs one access to the ACL of each displayed item, which slows down the display of large listings; to avoid it, select the properties you need, for example with `Format-Table -Property Mode, LastWriteTime, Length, Name`. Objects that you pipe to another command are not affected. Before 5.0.0, the column showed `True` for every item.</maml:para>
<maml:para>The `PrivateData` section of the module manifest `NTFSSecurity.psd1` contains two settings that this cmdlet reads when it starts. `GetFileSystemModeProperty` adds the calculated `Mode` property to every item. `IdentifyHardLinks` adds the `HardLinkCount` property to every file, which requires an extra call into the file system for each file and therefore slows down large listings noticeably. Set either value to `$false` in the manifest and import the module again if you prefer the faster enumeration over the additional properties.</maml:para>
<maml:para>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.</maml:para>
<maml:para>Before 5.0.0, a `-Path` value that points to a file stopped the cmdlet with an `InvalidCastException`.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -4041,7 +4048,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:description>
<maml:para>The `Get-FileHash2` cmdlet calculates the hash value of each file that `-Path` points to and returns the file object with the result attached. The returned object is the file object of the file, extended with a `Hash` property that holds the hash as an uppercase hexadecimal string and an `Algorithm` property that names the algorithm that was used. The module's formatting data displays those objects as a table with the `Algorithm`, `Hash`, and `FullName` columns.</maml:para>
<maml:para>`-Algorithm` selects the hash algorithm and accepts `SHA1`, `SHA256`, `SHA384`, `SHA512`, `MACTripleDES`, `MD5`, and `RIPEMD160`. The default is `SHA256`.</maml:para>
<maml:para>The cmdlet hashes files only. A path that points to a folder is skipped, and because the cmdlet stops processing the current input when it meets one, a folder in the middle of a `-Path` array suppresses the results of the paths that follow it in the same array. Pass only file paths, or filter folders out before you pipe items into the cmdlet. A path that does not exist produces a non-terminating `ReadFileError`.</maml:para>
<maml:para>The cmdlet hashes files only and skips paths that point to folders. A path that does not exist produces a non-terminating `ReadFileError`.</maml:para>
<maml:para>`-Path` accepts pipeline input by value and by property name through its `FullName` alias, so you can pipe the output of `Get-ChildItem2`, `Get-Item2`, or `Get-ChildItem` into the cmdlet; folders that arrive through the pipeline are skipped individually. Because the cmdlet reads files through the AlphaFS library, it also hashes files whose path exceeds the 260-character `MAX_PATH` limit. Relative paths are resolved against the current location.</maml:para>
</maml:description>
<command:syntax>
@ -4142,6 +4149,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:para>If the file cannot be opened because access is denied, the cmdlet takes ownership of the file with the account that runs it, calculates the hash, and restores the previous owner afterward. That fallback fails with a `GetHashError` when the account is not allowed to change the owner of the file.</maml:para>
<maml:para>The hash is returned as an uppercase hexadecimal string without separators, which differs from the lowercase output of some other hashing tools. Compare hash values case-insensitively.</maml:para>
<maml:para>`MACTripleDES` is a keyed message authentication code that is created with a key that is generated for each call, so its result is not reproducible across invocations and is not suitable for comparing files.</maml:para>
<maml:para>Before 5.0.0, a folder in a `-Path` array stopped the processing of that array, so the files that followed the folder were not hashed.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -4558,6 +4566,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.</maml:para>
<maml:para>Entries whose account cannot be translated into a name are returned with their SID. Use `Get-NTFSOrphanedAccess` to list only those entries.</maml:para>
<maml:para>Before 5.0.0, after a path whose ACL could not be read, the cmdlet returned the entries of the previous item again.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -4830,15 +4839,16 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:name>Security2.FileSystemAuditRule2</maml:name>
</dev:type>
<maml:description>
<maml:para>The cmdlet returns one object per audit entry, with the audited account, the audited access rights, the audit flags, the inheritance and propagation flags, the `IsInherited` flag, and the `InheritedFrom` path. When an item has no audit entries, or when the SACL cannot be read, the cmdlet returns nothing for that item.</maml:para>
<maml:para>The cmdlet returns one object per audit entry, with the audited account, the audited access rights, the audit flags, the inheritance and propagation flags, the `IsInherited` flag, and the `InheritedFrom` path. When an item has no audit entries, the cmdlet returns nothing for that item; when its SACL cannot be read, the cmdlet writes an error.</maml:para>
</maml:description>
</command:returnValue>
</command:returnValues>
<maml:alertSet>
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>Reading the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it the cmdlet falls back to reading the security descriptor without its SACL; it then returns no audit entries and reports no error, which looks the same as an item that is not audited at all.</maml:para>
<maml:para>If the security descriptor cannot be read because access is denied, the cmdlet takes ownership of the item, reads the descriptor again, and restores the previous owner. If the second attempt fails as well, the cmdlet writes an error, and the ownership change is not rolled back.</maml:para>
<maml:para>Reading the SACL requires the Security privilege (`SeSecurityPrivilege`, "Manage auditing and security log"), so run this cmdlet in an elevated session of an account that holds that privilege. Without it, the cmdlet writes the non-terminating error `ReadSecurityError` for each item, which reports "A required privilege is not held by the client". `Get-NTFSSecurityDescriptor` reads a security descriptor without its SACL when the privilege is missing; for such a descriptor, the cmdlet writes a `ReadSecurityError` as well.</maml:para>
<maml:para>If reading the audit entries is denied, the cmdlet writes a `ReadSecurityError` with the category `PermissionDenied`. It doesn't take ownership of the item, because ownership grants no access to the SACL.</maml:para>
<maml:para>Before 5.0.0, the cmdlet returned no entries and no error without the Security privilege, and after a path whose security descriptor could not be read, it returned the entries of the previous item again. The `InheritanceEnabled` property of the entries also reported whether the access entries were inherited instead of the audit entries.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -5325,7 +5335,7 @@ PS C:\&gt; Get-NTFSAudit -SecurityDescriptor $sd</dev:code>
<maml:description>
<maml:para>The `Get-NTFSInheritance` cmdlet reports whether a file or folder inherits access rules from its parent folder and whether it inherits audit rules. For each item it writes one `Security2.FileSystemInheritanceInfo` object with the `Name`, `FullName`, `AccessInheritanceEnabled`, and `AuditInheritanceEnabled` properties, plus the underlying file system object in the `Item` property. The default table view shows `Name`, `AccessInheritanceEnabled`, and `AuditInheritanceEnabled`.</maml:para>
<maml:para>`AccessInheritanceEnabled` is `$false` when the discretionary access control list (DACL) of the item is protected, which is the state that `Disable-NTFSAccessInheritance` produces. `AuditInheritanceEnabled` reports the same for the system access control list (SACL), which holds the audit rules. When the audit section cannot be read because the session does not hold the Security privilege, `AuditInheritanceEnabled` is `$null` and its column stays empty; the access value is still reported and no error is written.</maml:para>
<maml:para>In the `Path` parameter set the cmdlet reads the security descriptor of each item from disk. In the `SecurityDescriptor` parameter set it reads the state from the `Security2.FileSystemSecurity2` objects that `Get-NTFSSecurityDescriptor` returns, without touching the file system. Note that a descriptor that was retrieved without its audit section reports `AuditInheritanceEnabled` as `$true`, because the protection flag of a section that was never read is not set.</maml:para>
<maml:para>In the `Path` parameter set the cmdlet reads the security descriptor of each item from disk. In the `SecurityDescriptor` parameter set it reads the state from the `Security2.FileSystemSecurity2` objects that `Get-NTFSSecurityDescriptor` returns, without touching the file system. A descriptor that was read without its audit section, because the session doesn't hold the Security privilege, reports `AuditInheritanceEnabled` as `$null`, like the `Path` parameter set.</maml:para>
<maml:para>`-Path` accepts pipeline input by value and by property name through its `FullName` alias, so the output of `Get-ChildItem`, `Get-ChildItem2`, and `Get-Item2` binds to it. Relative paths are resolved against the current location, and when no path is supplied at all, the cmdlet reports the current location.</maml:para>
</maml:description>
<command:syntax>
@ -5422,8 +5432,10 @@ PS C:\&gt; Get-NTFSAudit -SecurityDescriptor $sd</dev:code>
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>Reading the audit section (SACL) of an item requires the Security privilege (`SeSecurityPrivilege`), which an account can only use in an elevated session. Without it, the cmdlet still reports the access state and sets `AuditInheritanceEnabled` to `$null` instead of writing an error.</maml:para>
<maml:para>Before 5.0.0, a security descriptor that was read without its audit section reported `AuditInheritanceEnabled` as `$true`.</maml:para>
<maml:para>If the security descriptor of an item cannot be opened because the account has no permission to it, the cmdlet takes ownership of the item, reads the state, and sets the previous owner back. That fallback only succeeds when the account can take ownership of the item and restore the original owner; otherwise the cmdlet writes an error and continues with the next item.</maml:para>
<maml:para>A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.</maml:para>
<maml:para>Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -6141,6 +6153,8 @@ PS C:\&gt; $orphaned | Select-Object FullName, Account, AccessRights, AuditFlags
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>The module also adds an `Owner` script property to `System.IO.FileInfo` and `System.IO.DirectoryInfo`, so `(Get-Item C:\Data).Owner` returns the owning account as a `Security2.IdentityReference2` object as well.</maml:para>
<maml:para>If the owner of an item cannot be read because access is denied, the cmdlet writes the non-terminating error `ReadSecurityError` with the category `PermissionDenied` and continues with the next path. It does not take ownership of the item, which would replace the owner that it reports.</maml:para>
<maml:para>Before 5.0.0, a command that stopped the pipeline early, such as `Select-Object -First 1`, made the cmdlet write a `ReadSecurityError` with the message "The pipeline has been stopped" for every path.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -9079,10 +9093,10 @@ PS C:\Data&gt; Get-NTFSSecurityDescriptor</dev:code>
<command:returnValues>
<command:returnValue>
<dev:type>
<maml:name>Security2.FileSystemAccessRule2</maml:name>
<maml:name>Security2.FileSystemAuditRule2</maml:name>
</dev:type>
<maml:description>
<maml:para>Without `-PassThru` the cmdlet writes nothing. With `-PassThru` the type depends on the parameter set: in the `Path` sets the cmdlet writes all access entries of the item, explicit and inherited ones, as `Security2.FileSystemAccessRule2` objects, while in the `SecurityDescriptor` sets it writes all audit entries of the descriptor as `Security2.FileSystemAuditRule2` objects. Use `Get-NTFSAudit` to check the audit entries of an item after the removal.</maml:para>
<maml:para>Without `-PassThru` the cmdlet writes nothing. With `-PassThru` the cmdlet writes all audit entries of the item or the security descriptor, explicit and inherited ones, as `Security2.FileSystemAuditRule2` objects. Before 5.0.0, the `Path` sets wrote the access entries of the item instead.</maml:para>
</maml:description>
</command:returnValue>
</command:returnValues>
@ -9169,7 +9183,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:description>
<maml:para>The `Set-NTFSInheritance` cmdlet turns the inheritance of access rules and audit rules on or off in a single call. It reads the current state of the item first and changes a section only when the requested value differs from the current one, which makes the cmdlet suitable for repeatedly applying a desired state to a folder tree.</maml:para>
<maml:para>The cmdlet performs the same operations as `Enable-NTFSAccessInheritance`, `Disable-NTFSAccessInheritance`, `Enable-NTFSAuditInheritance`, and `Disable-NTFSAuditInheritance`, but it does not expose their switches and it does not use their defaults. `-AccessInheritanceEnabled $false` discards the inherited access rules instead of copying them into the item's own DACL, `-AccessInheritanceEnabled $true` keeps the explicit access rules, `-AuditInheritanceEnabled $false` copies the inherited audit rules into the item's own SACL, and `-AuditInheritanceEnabled $true` removes the explicit audit rules. Use the individual Enable and Disable cmdlets when you need the opposite behavior.</maml:para>
<maml:para>Specify both `-AccessInheritanceEnabled` and `-AuditInheritanceEnabled`. The cmdlet compares the current state against the parameter value even when the parameter was not supplied, and when such a comparison reports a difference it fails with the non-terminating error "Nullable object must have a value". Omitting `-AccessInheritanceEnabled` always triggers that error, and omitting `-AuditInheritanceEnabled` triggers it in a session that can read the audit section. Changing the audit section requires the Security privilege and therefore an elevated session.</maml:para>
<maml:para>Omit `-AccessInheritanceEnabled` or `-AuditInheritanceEnabled` to leave that section unchanged. Changing the audit section requires the Security privilege and therefore an elevated session.</maml:para>
<maml:para>In the `Path` parameter set the cmdlet writes each changed section back to disk immediately. In the `SecurityDescriptor` parameter set it changes the `Security2.FileSystemSecurity2` object in memory only; nothing reaches the file system until you pass that object to `Set-NTFSSecurityDescriptor`. `-Path`, `-AccessInheritanceEnabled`, and `-AuditInheritanceEnabled` all accept pipeline input by property name, so a `Security2.FileSystemInheritanceInfo` object from `Get-NTFSInheritance` binds to all three at once.</maml:para>
</maml:description>
<command:syntax>
@ -9190,7 +9204,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AccessInheritanceEnabled</maml:name>
<maml:description>
<maml:para>Specifies whether the item inherits access rules from its parent folder. `$true` removes the protection from the DACL and keeps the access rules that are stored directly on the item; `$false` protects the DACL and discards the rules the item currently inherits, which leaves only its explicit rules. The section is left untouched when the requested value already matches the current state. Always supply this parameter, because the cmdlet fails with "Nullable object must have a value" when it has to compare against a value that was not provided.</maml:para>
<maml:para>Specifies whether the item inherits access rules from its parent folder. `$true` removes the protection from the DACL and keeps the access rules that are stored directly on the item; `$false` protects the DACL and discards the rules the item currently inherits, which leaves only its explicit rules. The section is left untouched when the requested value already matches the current state. When you omit the parameter, the access section is left unchanged.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">Boolean</command:parameterValue>
<dev:type>
@ -9202,7 +9216,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AuditInheritanceEnabled</maml:name>
<maml:description>
<maml:para>Specifies whether the item inherits audit rules from its parent folder. `$true` removes the protection from the SACL and removes the audit rules that are stored directly on the item; `$false` protects the SACL and copies the inherited audit rules into it. The section is left untouched when the requested value already matches the current state. Reading and writing the audit section requires the Security privilege and therefore an elevated session.</maml:para>
<maml:para>Specifies whether the item inherits audit rules from its parent folder. `$true` removes the protection from the SACL and removes the audit rules that are stored directly on the item; `$false` protects the SACL and copies the inherited audit rules into it. The section is left untouched when the requested value already matches the current state. When you omit the parameter, the audit section is left unchanged. Reading and writing the audit section requires the Security privilege and therefore an elevated session.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">Boolean</command:parameterValue>
<dev:type>
@ -9242,7 +9256,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AccessInheritanceEnabled</maml:name>
<maml:description>
<maml:para>Specifies whether the item inherits access rules from its parent folder. `$true` removes the protection from the DACL and keeps the access rules that are stored directly on the item; `$false` protects the DACL and discards the rules the item currently inherits, which leaves only its explicit rules. The section is left untouched when the requested value already matches the current state. Always supply this parameter, because the cmdlet fails with "Nullable object must have a value" when it has to compare against a value that was not provided.</maml:para>
<maml:para>Specifies whether the item inherits access rules from its parent folder. `$true` removes the protection from the DACL and keeps the access rules that are stored directly on the item; `$false` protects the DACL and discards the rules the item currently inherits, which leaves only its explicit rules. The section is left untouched when the requested value already matches the current state. When you omit the parameter, the access section is left unchanged.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">Boolean</command:parameterValue>
<dev:type>
@ -9254,7 +9268,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AuditInheritanceEnabled</maml:name>
<maml:description>
<maml:para>Specifies whether the item inherits audit rules from its parent folder. `$true` removes the protection from the SACL and removes the audit rules that are stored directly on the item; `$false` protects the SACL and copies the inherited audit rules into it. The section is left untouched when the requested value already matches the current state. Reading and writing the audit section requires the Security privilege and therefore an elevated session.</maml:para>
<maml:para>Specifies whether the item inherits audit rules from its parent folder. `$true` removes the protection from the SACL and removes the audit rules that are stored directly on the item; `$false` protects the SACL and copies the inherited audit rules into it. The section is left untouched when the requested value already matches the current state. When you omit the parameter, the audit section is left unchanged. Reading and writing the audit section requires the Security privilege and therefore an elevated session.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">Boolean</command:parameterValue>
<dev:type>
@ -9280,7 +9294,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AccessInheritanceEnabled</maml:name>
<maml:description>
<maml:para>Specifies whether the item inherits access rules from its parent folder. `$true` removes the protection from the DACL and keeps the access rules that are stored directly on the item; `$false` protects the DACL and discards the rules the item currently inherits, which leaves only its explicit rules. The section is left untouched when the requested value already matches the current state. Always supply this parameter, because the cmdlet fails with "Nullable object must have a value" when it has to compare against a value that was not provided.</maml:para>
<maml:para>Specifies whether the item inherits access rules from its parent folder. `$true` removes the protection from the DACL and keeps the access rules that are stored directly on the item; `$false` protects the DACL and discards the rules the item currently inherits, which leaves only its explicit rules. The section is left untouched when the requested value already matches the current state. When you omit the parameter, the access section is left unchanged.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">Boolean</command:parameterValue>
<dev:type>
@ -9292,7 +9306,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
<maml:name>AuditInheritanceEnabled</maml:name>
<maml:description>
<maml:para>Specifies whether the item inherits audit rules from its parent folder. `$true` removes the protection from the SACL and removes the audit rules that are stored directly on the item; `$false` protects the SACL and copies the inherited audit rules into it. The section is left untouched when the requested value already matches the current state. Reading and writing the audit section requires the Security privilege and therefore an elevated session.</maml:para>
<maml:para>Specifies whether the item inherits audit rules from its parent folder. `$true` removes the protection from the SACL and removes the audit rules that are stored directly on the item; `$false` protects the SACL and copies the inherited audit rules into it. The section is left untouched when the requested value already matches the current state. When you omit the parameter, the audit section is left unchanged. Reading and writing the audit section requires the Security privilege and therefore an elevated session.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">Boolean</command:parameterValue>
<dev:type>
@ -9382,6 +9396,8 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:para>The audit section of a security descriptor can only be read and written with the Security privilege (`SeSecurityPrivilege`), which an account can only use in an elevated session. Without it, a requested change of `-AuditInheritanceEnabled` produces a non-terminating error that reports Windows error 1314, "A required privilege is not held by the client". The access section is processed first, so a change of `-AccessInheritanceEnabled` in the same command is applied even when the audit change fails.</maml:para>
<maml:para>If the descriptor cannot be opened because the account has no permission to the item, the cmdlet takes ownership of the item, applies the changes, and sets the previous owner back. That fallback only succeeds when the account can take ownership of the item and restore the original owner; otherwise the cmdlet writes an error and continues with the next item.</maml:para>
<maml:para>A path that does not exist produces a non-terminating error and the cmdlet continues with the remaining paths.</maml:para>
<maml:para>Before 5.0.0, omitting `-AccessInheritanceEnabled` or `-AuditInheritanceEnabled` could fail with the error "Nullable object must have a value".</maml:para>
<maml:para>Before 5.0.0, the cmdlet enabled the privileges even when `EnablePrivileges` was `$false`, and left them enabled.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>

2
Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.GetFileSystemAuditRules.cs

@ -31,7 +31,7 @@ namespace Security2
foreach (FileSystemAuditRule ace in acl)
{
var ace2 = new FileSystemAuditRule2(ace) { FullName = sd.Item.FullName, InheritanceEnabled = !sd.SecurityDescriptor.AreAccessRulesProtected };
var ace2 = new FileSystemAuditRule2(ace) { FullName = sd.Item.FullName, InheritanceEnabled = !sd.SecurityDescriptor.AreAuditRulesProtected };
if (getInheritedFrom)
{
ace2.inheritedFrom = string.IsNullOrEmpty(inheritedFrom[aceCounter]) ? "" : inheritedFrom[aceCounter].Substring(0, inheritedFrom[aceCounter].Length - 1);

9
Security2/FileSystem/FileSystemInheritanceInfo.cs

@ -94,7 +94,14 @@ namespace Security2
public static FileSystemInheritanceInfo GetFileSystemInheritanceInfo(FileSystemSecurity2 sd)
{
return new FileSystemInheritanceInfo(sd.Item, !sd.SecurityDescriptor.AreAccessRulesProtected, !sd.SecurityDescriptor.AreAuditRulesProtected);
// Like for an item, the audit state is unknown when the SACL was not read.
bool? auditInheritanceEnabled = null;
if (sd.HasAuditSection)
{
auditInheritanceEnabled = !sd.SecurityDescriptor.AreAuditRulesProtected;
}
return new FileSystemInheritanceInfo(sd.Item, !sd.SecurityDescriptor.AreAccessRulesProtected, auditInheritanceEnabled);
}
#endregion GetFileSystemInheritanceInfo

12
Security2/FileSystem/FileSystemSecurity2.cs

@ -53,16 +53,19 @@ namespace Security2
try
{
sd = ((FileInfo)this.item).GetAccessControl(AccessControlSections.All);
sections = AccessControlSections.All;
}
catch
{
try
{
sd = ((FileInfo)this.item).GetAccessControl(AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group);
sections = AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group;
}
catch
{
sd = ((FileInfo)this.item).GetAccessControl(AccessControlSections.Access);
sections = AccessControlSections.Access;
}
}
@ -74,21 +77,30 @@ namespace Security2
try
{
sd = ((DirectoryInfo)this.item).GetAccessControl(AccessControlSections.All);
sections = AccessControlSections.All;
}
catch
{
try
{
sd = ((DirectoryInfo)this.item).GetAccessControl(AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group);
sections = AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group;
}
catch
{
sd = ((DirectoryInfo)this.item).GetAccessControl(AccessControlSections.Access);
sections = AccessControlSections.Access;
}
}
}
}
// Without the Security privilege, the security descriptor is read without its SACL.
internal bool HasAuditSection
{
get { return (sections & AccessControlSections.Audit) == AccessControlSections.Audit; }
}
public FileSystemSecurity SecurityDescriptor
{
get

3
Security2/Properties/AssemblyInfo.cs

@ -19,6 +19,9 @@ using System.Runtime.InteropServices;
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// Lets the cmdlets tell whether a security descriptor was read with its SACL.
[assembly: InternalsVisibleTo("NTFSSecurity")]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d89dc40a-9b43-4bce-972d-b995df8d2820")]

37
Tests/Access.Tests.ps1

@ -0,0 +1,37 @@
<#
Tests the access cmdlets of the module built in NTFSSecurity\bin\Release on files in a sandbox folder.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
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 'Access'
Push-Location -LiteralPath $sandbox
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Get-NTFSAccess' {
Context 'When a path fails after a readable path' {
# Before 5.0.0, the cmdlet wrote the entries of the previous item again for the failing path.
It 'Should return the entries of the first item once' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'Readable' -Directory
$denied = New-TestSandboxItem -Sandbox $sandbox -Name 'Denied'
Block-TestReadPermission -Sandbox $sandbox -Path $denied
$expected = @(Get-NTFSAccess -Path $folder).Count
$entries = @(Get-NTFSAccess -Path $folder, $denied -ErrorAction SilentlyContinue)
@($entries | Where-Object -Property FullName -EQ -Value $folder) | Should -HaveCount $expected
}
}
}

140
Tests/Audit.Tests.ps1

@ -0,0 +1,140 @@
<#
Tests the audit cmdlets of the module built in NTFSSecurity\bin\Release on files in a sandbox folder. Reading
and changing audit entries needs the Security privilege; tests that need it 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
$canReadAudit = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
}
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 'Audit'
Push-Location -LiteralPath $sandbox
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Get-NTFSAudit' {
Context 'When the audit entries cannot be read' {
It 'Should write an error without the Security privilege instead of returning nothing' -Skip:$canReadAudit {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NoPrivilege'
$entries = @(Get-NTFSAudit -Path $file -ErrorVariable auditErrors -ErrorAction SilentlyContinue)
$entries | Should -BeNullOrEmpty
$auditErrors | Should -HaveCount 1
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
}
It 'Should write an error for a security descriptor that was read without the audit entries' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AccessOnly'
$sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
(Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Access
)
$entries = @(Get-NTFSAudit -SecurityDescriptor $sd -ErrorVariable auditErrors -ErrorAction SilentlyContinue)
$entries | Should -BeNullOrEmpty
$auditErrors | Should -HaveCount 1
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
}
}
Context 'When a path fails after a path with audit entries' {
# Before 5.0.0, the cmdlet wrote the entries of the previous item again for the failing path. In CI, the deny
# entry made the original implementation, which also read the DACL, fail for the second path. The current one
# reads only the SACL, which the deny entry doesn't block; Access.Tests.ps1 guards the same loop fix in
# Get-NTFSAccess with a read that fails without elevation.
It 'Should return the entries of the first item once' -Skip:(-not $canReadAudit) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'Audited' -Directory
$denied = New-TestSandboxItem -Sandbox $sandbox -Name 'Denied'
Add-NTFSAudit -Path $folder -Account 'Everyone' -AccessRights Delete -AuditFlags Success
Block-TestReadPermission -Sandbox $sandbox -Path $denied
$entries = @(Get-NTFSAudit -Path $folder, $denied -ExcludeInherited -ErrorAction SilentlyContinue)
@($entries | Where-Object -Property FullName -EQ -Value $folder) | Should -HaveCount 1
}
}
}
Describe 'Add-NTFSAudit' {
Context 'Positional parameters' {
It 'Should take -Account at position 2 and -AccessRights at position 3 in the <_> parameter set' -ForEach @(
'PathSimple', 'PathComplex', 'SDSimple', 'SDComplex'
) {
$parameterSet = (Get-Command -Name Add-NTFSAudit).ParameterSets | Where-Object -Property Name -EQ -Value $_
$positions = @{}
$parameterSet.Parameters | Where-Object -Property Position -GE -Value 0 | ForEach-Object -Process {
$positions[$_.Name] = $_.Position
}
$positions['Account'] | Should -Be 2
$positions['AccessRights'] | Should -Be 3
}
It 'Should bind an account and access rights that are passed by position' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Positional'
$sd = Get-NTFSSecurityDescriptor -Path $file
Add-NTFSAudit -SecurityDescriptor $sd 'Everyone' 'ReadData' -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$rules = $sd.SecurityDescriptor.GetAuditRules($true, $false, [System.Security.Principal.SecurityIdentifier])
@($rules | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1
}
}
Context 'With -PassThru' {
It 'Should return the audit entries of a security descriptor, not its access entries' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'PassThru'
$sd = Get-NTFSSecurityDescriptor -Path $file
$result = @(Add-NTFSAudit -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -PassThru)
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ | Should -BeOfType [Security2.FileSystemAuditRule2] }
@($result | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-1-0' }) | Should -HaveCount 1
}
It 'Should report the inheritance of the audit entries in InheritanceEnabled, not that of the access entries' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditProtected'
$sd = Get-NTFSSecurityDescriptor -Path $file
$sd.SecurityDescriptor.SetAuditRuleProtection($true, $false)
$result = @(Add-NTFSAudit -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -PassThru)
$sd.SecurityDescriptor.AreAccessRulesProtected | Should -BeFalse
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_.InheritanceEnabled | Should -BeFalse }
}
}
}
Describe 'Remove-NTFSAudit' {
Context 'With -PassThru' {
It 'Should return the audit entries of the item, not its access entries' -Skip:(-not $canReadAudit) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'PassThru'
Add-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None
Add-NTFSAudit -Path $file -Account 'BUILTIN\Users' -AccessRights Delete -InheritanceFlags None -PropagationFlags None
$result = @(Remove-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -PassThru)
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ | Should -BeOfType [Security2.FileSystemAuditRule2] }
@($result | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-5-32-545' }) | Should -HaveCount 1
}
}
}

45
Tests/FileHash.Tests.ps1

@ -0,0 +1,45 @@
<#
Tests Get-FileHash2 of the module built in NTFSSecurity\bin\Release on files in a sandbox folder.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
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 'FileHash'
Push-Location -LiteralPath $sandbox
$folder = Join-Path -Path $sandbox -ChildPath 'Folder'
$first = Join-Path -Path $sandbox -ChildPath 'One.txt'
$second = Join-Path -Path $sandbox -ChildPath 'Two.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $folder, $first, $second
New-Item -ItemType Directory -Path $folder | Out-Null
Set-Content -LiteralPath $first -Value 'One'
Set-Content -LiteralPath $second -Value 'Two'
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Get-FileHash2' {
Context 'When -Path contains a folder' {
It 'Should skip the folder and hash the files that follow it' {
if ($PSVersionTable.PSEdition -eq 'Core') {
Set-ItResult -Skipped -Because 'Get-FileHash2 fails in PowerShell 7 until it no longer references RIPEMD160'
}
$results = @(Get-FileHash2 -Path $first, $folder, $second -ErrorVariable hashErrors -ErrorAction SilentlyContinue)
$hashErrors | Should -BeNullOrEmpty
$results.Name | Should -Be @('One.txt', 'Two.txt')
$results[1].Hash | Should -BeExactly (Get-FileHash -LiteralPath $second -Algorithm SHA256).Hash
}
}
}

97
Tests/Inheritance.Tests.ps1

@ -0,0 +1,97 @@
<#
Tests the inheritance cmdlets of the module built in NTFSSecurity\bin\Release on files in a sandbox folder.
Tests that change the audit section need the Security privilege and skip without it; CI runs them elevated.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeDiscovery {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$canChangeAudit = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
}
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 'Inheritance'
Push-Location -LiteralPath $sandbox
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Get-NTFSInheritance' {
Context 'With a security descriptor' {
BeforeEach {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Descriptor'
}
It 'Should report the same state as for the path of the item' {
$byPath = Get-NTFSInheritance -Path $file
$bySecurityDescriptor = Get-NTFSInheritance -SecurityDescriptor (Get-NTFSSecurityDescriptor -Path $file)
$bySecurityDescriptor.AccessInheritanceEnabled | Should -Be $byPath.AccessInheritanceEnabled
$bySecurityDescriptor.AuditInheritanceEnabled | Should -Be $byPath.AuditInheritanceEnabled
}
It 'Should report the audit inheritance as $null for a security descriptor without the audit entries' {
$sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
(Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Access
)
$result = Get-NTFSInheritance -SecurityDescriptor $sd
$result.AccessInheritanceEnabled | Should -BeTrue
$result.AuditInheritanceEnabled | Should -BeNullOrEmpty
}
}
}
Describe 'Set-NTFSInheritance' {
Context 'When -AccessInheritanceEnabled or -AuditInheritanceEnabled is omitted' {
BeforeEach {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'File'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
}
It 'Should change nothing and write no error when both are omitted' {
Set-NTFSInheritance -Path $file -ErrorVariable inheritanceErrors -ErrorAction SilentlyContinue
$inheritanceErrors | Should -BeNullOrEmpty
(Get-NTFSInheritance -Path $file).AccessInheritanceEnabled | Should -BeTrue
}
It 'Should leave a security descriptor unchanged when both are omitted' {
$sd = Get-NTFSSecurityDescriptor -Path $file
{ Set-NTFSInheritance -SecurityDescriptor $sd -ErrorAction Stop } | Should -Not -Throw
$sd.SecurityDescriptor.AreAccessRulesProtected | Should -BeFalse
}
It 'Should change only the access inheritance when -AuditInheritanceEnabled is omitted' {
$before = Get-NTFSInheritance -Path $file
Set-NTFSInheritance -Path $file -AccessInheritanceEnabled $false -ErrorVariable inheritanceErrors -ErrorAction SilentlyContinue
$inheritanceErrors | Should -BeNullOrEmpty
$after = Get-NTFSInheritance -Path $file
$after.AccessInheritanceEnabled | Should -BeFalse
$after.AuditInheritanceEnabled | Should -Be $before.AuditInheritanceEnabled
}
It 'Should change only the audit inheritance when -AccessInheritanceEnabled is omitted' -Skip:(-not $canChangeAudit) {
Set-NTFSInheritance -Path $file -AuditInheritanceEnabled $false -ErrorVariable inheritanceErrors -ErrorAction SilentlyContinue
$inheritanceErrors | Should -BeNullOrEmpty
$after = Get-NTFSInheritance -Path $file
$after.AccessInheritanceEnabled | Should -BeTrue
$after.AuditInheritanceEnabled | Should -BeFalse
}
}
}

106
Tests/ItemCmdlets.Tests.ps1

@ -0,0 +1,106 @@
<#
Tests the long-path item cmdlets of the module built in NTFSSecurity\bin\Release on files in a sandbox folder.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
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 'ItemCmdlets'
Push-Location -LiteralPath $sandbox
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Get-ChildItem2' {
Context 'When -Path is a file' {
BeforeAll {
$folder = Join-Path -Path $sandbox -ChildPath 'ChildItem'
$file = Join-Path -Path $folder -ChildPath 'File.txt'
$other = Join-Path -Path $folder -ChildPath 'Folder\Other.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $folder, $file, $other
New-Item -ItemType Directory -Path (Split-Path -Path $other -Parent) -Force | Out-Null
Set-Content -LiteralPath $file -Value 'File'
Set-Content -LiteralPath $other -Value 'Other'
}
It 'Should return the file itself, like Get-ChildItem' {
$items = @(Get-ChildItem2 -Path $file -ErrorVariable childItemErrors -ErrorAction SilentlyContinue)
$childItemErrors | Should -BeNullOrEmpty
$items | Should -HaveCount 1
$items[0] | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$items[0].Name | Should -BeExactly 'File.txt'
}
It 'Should continue with the next path after a file' {
$items = @(Get-ChildItem2 -Path $file, (Join-Path -Path $folder -ChildPath 'Folder') -ErrorAction SilentlyContinue)
$items.Name | Should -Be @('File.txt', 'Other.txt')
}
It 'Should return nothing for a file with -Directory' {
$items = @(Get-ChildItem2 -Path $file -Directory -ErrorVariable childItemErrors -ErrorAction SilentlyContinue)
$childItemErrors | Should -BeNullOrEmpty
$items | Should -BeNullOrEmpty
}
}
Context 'Default table view' {
BeforeAll {
$viewFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'View' -Directory
$blocked = Join-Path -Path $viewFolder -ChildPath 'Blocked.txt'
$inheriting = Join-Path -Path $viewFolder -ChildPath 'Inheriting.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $blocked, $inheriting
Set-Content -LiteralPath $blocked -Value 'Blocked'
Set-Content -LiteralPath $inheriting -Value 'Inheriting'
$acl = Get-Acl -LiteralPath $blocked
$acl.SetAccessRuleProtection($true, $true)
Set-Acl -LiteralPath $blocked -AclObject $acl
$lines = Get-ChildItem2 -Path $viewFolder | Out-String -Stream -Width 200
}
It 'Should show False in the Inherits column for a file whose inheritance is disabled' {
($lines | Where-Object -FilterScript { $_ -match 'Blocked\.txt\s*$' }) | Should -Match '\bFalse\b'
}
It 'Should show True in the Inherits column for a file that inherits' {
($lines | Where-Object -FilterScript { $_ -match 'Inheriting\.txt\s*$' }) | Should -Match '\bTrue\b'
}
}
}
Describe 'Copy-Item2' {
Context 'When -Path is a folder with files and subfolders' {
BeforeAll {
$source = New-TestSandboxItem -Sandbox $sandbox -Name 'Source' -Directory
$sourceFile = Join-Path -Path $source -ChildPath 'File.txt'
$sourceSubfolderFile = Join-Path -Path $source -ChildPath 'Subfolder\Other.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $sourceFile, $sourceSubfolderFile
New-Item -ItemType Directory -Path (Split-Path -Path $sourceSubfolderFile -Parent) | Out-Null
Set-Content -LiteralPath $sourceFile -Value 'File'
Set-Content -LiteralPath $sourceSubfolderFile -Value 'Other'
}
It 'Should copy the folder with its files and subfolders' {
$destination = Join-Path -Path $sandbox -ChildPath ('Copy-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
Assert-TestSandboxPath -Sandbox $sandbox -Path $destination
Copy-Item2 -Path $source -Destination $destination -ErrorVariable copyErrors -ErrorAction SilentlyContinue
$copyErrors | Should -BeNullOrEmpty
Join-Path -Path $destination -ChildPath 'File.txt' | Should -Exist
Join-Path -Path $destination -ChildPath 'Subfolder\Other.txt' | Should -Exist
}
}
}

54
Tests/Owner.Tests.ps1

@ -0,0 +1,54 @@
<#
Tests the owner cmdlets of the module built in NTFSSecurity\bin\Release on files in a sandbox folder.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeDiscovery {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
# With the Backup privilege, Windows may grant reading the owner despite a deny entry.
$canBypassDeny = Test-PrivilegeHeld -Name 'SeBackupPrivilege'
}
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 'Owner'
Push-Location -LiteralPath $sandbox
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Get-NTFSOwner' {
Context 'When a downstream command stops the pipeline' {
It 'Should stop without writing errors' {
$files = 1..3 | ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name "Owner$_" }
$result = @(Get-NTFSOwner -Path $files -ErrorVariable ownerErrors -ErrorAction SilentlyContinue | Select-Object -First 1)
$ownerErrors | Should -BeNullOrEmpty
$result | Should -HaveCount 1
}
}
Context 'When the owner cannot be read' {
It 'Should write one permission error and keep the owner' -Skip:$canBypassDeny {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Denied'
Block-TestReadPermission -Sandbox $sandbox -Path $file
$result = @(Get-NTFSOwner -Path $file -ErrorVariable ownerErrors -ErrorAction SilentlyContinue)
$result | Should -BeNullOrEmpty
$ownerErrors | Should -HaveCount 1
$ownerErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$ownerErrors[0].CategoryInfo.Category | Should -Be 'PermissionDenied'
}
}
}

88
Tests/Privileges.Tests.ps1

@ -0,0 +1,88 @@
<#
Tests how the cmdlets of the module built in NTFSSecurity\bin\Release handle the Backup, Restore, Take Ownership,
and Security privileges. These tests need an access token that holds the privileges, so they skip without them
and run in CI, whose runners are elevated. They change only the privileges of the test process and restore the
module setting EnablePrivileges.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeDiscovery {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$missingPrivileges = @('SeBackupPrivilege', 'SeRestorePrivilege', 'SeTakeOwnershipPrivilege', 'SeSecurityPrivilege') |
Where-Object -FilterScript { -not (Test-PrivilegeHeld -Name $_) }
$holdsPrivileges = -not $missingPrivileges
}
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 'Privileges'
Push-Location -LiteralPath $sandbox
$privateData = (Get-Module -Name NTFSSecurity).PrivateData
$enablePrivileges = $privateData['EnablePrivileges']
function Get-BackupPrivilegeState {
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Backup').PrivilegeState
}
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Disable-Privileges' {
Context 'When the module setting EnablePrivileges is $false' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
# Before 5.0.0, the cmdlet warned that it could not disable the privileges and left them enabled.
It 'Should disable the privileges that Enable-Privileges enabled' -Skip:(-not $holdsPrivileges) {
Enable-Privileges
Get-BackupPrivilegeState | Should -Be 'Enabled'
Disable-Privileges -WarningVariable privilegeWarnings -WarningAction SilentlyContinue
$privilegeWarnings | Should -BeNullOrEmpty
Get-BackupPrivilegeState | Should -Be 'Disabled'
}
}
}
Describe 'Inheritance cmdlets' {
Context 'When the module setting EnablePrivileges is $false' {
BeforeAll {
$privateData['EnablePrivileges'] = $false
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Inheritance'
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
# Before 5.0.0, the inheritance cmdlets enabled the privileges anyway and left them enabled.
It '<_> should leave the privileges disabled' -Skip:(-not $holdsPrivileges) -ForEach @(
'Get-NTFSInheritance', 'Set-NTFSInheritance', 'Enable-NTFSAccessInheritance',
'Disable-NTFSAccessInheritance', 'Enable-NTFSAuditInheritance', 'Disable-NTFSAuditInheritance'
) {
Get-BackupPrivilegeState | Should -Be 'Disabled'
& $_ -Path $file -ErrorAction SilentlyContinue | Out-Null
Get-BackupPrivilegeState | Should -Be 'Disabled'
}
}
}

146
Tests/TestHelpers.Tests.ps1

@ -0,0 +1,146 @@
<#
Tests the shared helpers in TestHelpers.psm1, which keep the tests that change files, links, and security
descriptors inside their sandbox folders. CI runs every *.Tests.ps1 file of this folder.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeAll {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
}
AfterAll {
Remove-Module -Name TestHelpers -Force -ErrorAction SilentlyContinue
}
Describe 'Test helpers' {
Context 'New-TestSandbox' {
BeforeAll {
$sandbox = New-TestSandbox -Name 'Helpers'
}
AfterAll {
Remove-TestSandbox -Sandbox $sandbox
}
It 'Should create an empty folder below $env:TEMP\NTFSSecurity.Tests' {
$sandbox | Should -Exist
Get-ChildItem -LiteralPath $sandbox -Force | Should -BeNullOrEmpty
$expectedParent = [IO.Path]::GetFullPath((Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath 'NTFSSecurity.Tests'))
Split-Path -Path $sandbox -Parent | Should -Be $expectedParent
Split-Path -Path $sandbox -Leaf | Should -BeLike 'Helpers-*'
}
}
Context 'Assert-TestSandboxPath' {
BeforeAll {
$sandbox = New-TestSandbox -Name 'Helpers'
Push-Location -LiteralPath $sandbox
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
}
It 'Should accept a full path inside the sandbox' {
{ Assert-TestSandboxPath -Sandbox $sandbox -Path (Join-Path -Path $sandbox -ChildPath 'Folder\File.txt') } |
Should -Not -Throw
}
It 'Should resolve a relative path against the current location' {
{ Assert-TestSandboxPath -Sandbox $sandbox -Path '.\File.txt', 'Folder\File.txt' } | Should -Not -Throw
}
It 'Should reject <_>' -ForEach @('..', '..\Other', 'C:\Windows', '\Windows') {
{ Assert-TestSandboxPath -Sandbox $sandbox -Path $_ } | Should -Throw -ExpectedMessage 'Refusing to change*'
}
It 'Should reject a sibling folder whose name starts with the name of the sandbox' {
{ Assert-TestSandboxPath -Sandbox $sandbox -Path "$sandbox-Other\File.txt" } |
Should -Throw -ExpectedMessage 'Refusing to change*'
}
It 'Should reject a sandbox that New-TestSandbox did not create' {
{ Assert-TestSandboxPath -Sandbox $env:TEMP -Path (Join-Path -Path $env:TEMP -ChildPath 'File.txt') } |
Should -Throw -ExpectedMessage '*is not a test sandbox*'
}
It 'Should reject a path below a link, which can point outside the sandbox' {
$otherSandbox = New-TestSandbox -Name 'Helpers'
try {
$link = Join-Path -Path $sandbox -ChildPath 'Link'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
New-Item -ItemType Junction -Path $link -Value $otherSandbox | Out-Null
{ Assert-TestSandboxPath -Sandbox $sandbox -Path (Join-Path -Path $link -ChildPath 'File.txt') } |
Should -Throw -ExpectedMessage '*is a link*'
}
finally {
Remove-TestSandbox -Sandbox $otherSandbox
}
}
}
Context 'Remove-TestSandbox' {
BeforeAll {
$sandbox = New-TestSandbox -Name 'Helpers'
$otherSandbox = New-TestSandbox -Name 'Helpers'
$target = Join-Path -Path $otherSandbox -ChildPath 'Target'
$link = Join-Path -Path $sandbox -ChildPath 'Link'
$locked = Join-Path -Path $sandbox -ChildPath 'Locked'
Assert-TestSandboxPath -Sandbox $otherSandbox -Path $target
New-Item -ItemType Directory -Path $target | Out-Null
Set-Content -LiteralPath (Join-Path -Path $target -ChildPath 'Keep.txt') -Value 'Keep'
# An explicit entry that a reset through the link would remove
$targetAcl = Get-Acl -LiteralPath $target
$targetAcl.AddAccessRule((New-Object -TypeName 'System.Security.AccessControl.FileSystemAccessRule' -ArgumentList (
(New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList 'S-1-1-0'),
[System.Security.AccessControl.FileSystemRights]::ReadData, [System.Security.AccessControl.AccessControlType]::Allow
)))
Set-Acl -LiteralPath $target -AclObject $targetAcl
$targetSddl = (Get-Acl -LiteralPath $target).Sddl
Assert-TestSandboxPath -Sandbox $sandbox -Path $link, $locked
New-Item -ItemType Junction -Path $link -Value $target | Out-Null
New-Item -ItemType Directory -Path $locked | Out-Null
Set-Content -LiteralPath (Join-Path -Path $locked -ChildPath 'File.txt') -Value 'Locked'
# An empty, protected DACL that grants nobody access, as some tests leave behind
& icacls.exe $locked /inheritance:r *> $null
Remove-TestSandbox -Sandbox $sandbox
}
AfterAll {
Remove-TestSandbox -Sandbox $otherSandbox
}
It 'Should remove the sandbox, even with a folder that denies access' {
$sandbox | Should -Not -Exist
}
It 'Should remove a junction without removing the files of its target' {
Join-Path -Path $target -ChildPath 'Keep.txt' | Should -Exist
}
It 'Should not change the ACL of the target of a junction' {
(Get-Acl -LiteralPath $target).Sddl | Should -BeExactly $targetSddl
}
}
Context 'Test-IsElevated and Test-PrivilegeHeld' {
It 'Should tell whether the process is elevated' {
Test-IsElevated | Should -BeOfType [bool]
}
It 'Should find SeChangeNotifyPrivilege, which every access token holds' {
Test-PrivilegeHeld -Name 'SeChangeNotifyPrivilege' | Should -BeTrue
}
It 'Should not find a privilege that does not exist' {
Test-PrivilegeHeld -Name 'SeNoSuchPrivilege' | Should -BeFalse
}
}
}

250
Tests/TestHelpers.psm1

@ -0,0 +1,250 @@
<#
Shared helpers for the Pester tests. A test that changes files, links, ACLs, owners, audit entries, or
inheritance works in its own sandbox folder below $env:TEMP\NTFSSecurity.Tests: it creates the sandbox with
New-TestSandbox, sets the location to it, checks every target with Assert-TestSandboxPath before the change,
and removes the sandbox with Remove-TestSandbox.
#>
$script:sandboxRoot = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath 'NTFSSecurity.Tests'
function New-TestSandbox {
<#
.SYNOPSIS
Creates an empty sandbox folder for one test file or block and returns its full path.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only creates sandboxes.'
)]
[CmdletBinding()]
[OutputType([string])]
param (
[ValidatePattern('^[\w-]+$')]
[string]
$Name = 'Sandbox'
)
$path = Join-Path -Path $script:sandboxRoot -ChildPath ('{0}-{1}' -f $Name, [guid]::NewGuid().ToString('N').Substring(0, 8))
(New-Item -ItemType Directory -Path $path -Force).FullName
}
function Assert-TestSandboxPath {
<#
.SYNOPSIS
Throws unless each path is inside the given sandbox, which itself must be a sandbox of New-TestSandbox.
Relative paths resolve against the current location.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]
$Sandbox,
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]
$Path
)
begin {
$sandboxFullName = [IO.Path]::GetFullPath($Sandbox).TrimEnd('\')
$rootFullName = [IO.Path]::GetFullPath($script:sandboxRoot).TrimEnd('\') + '\'
if (-not $sandboxFullName.StartsWith($rootFullName, [StringComparison]::OrdinalIgnoreCase) -or
$sandboxFullName.Length -le $rootFullName.Length) {
throw "'$Sandbox' is not a test sandbox below '$rootFullName'."
}
$sandboxPrefix = $sandboxFullName + '\'
}
process {
foreach ($item in $Path) {
$location = (Get-Location -PSProvider FileSystem).ProviderPath
$fullName = [IO.Path]::GetFullPath([IO.Path]::Combine($location, $item)).TrimEnd('\')
if (-not ($fullName + '\').StartsWith($sandboxPrefix, [StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to change '$fullName', which is outside the test sandbox '$sandboxFullName'."
}
# A link inside the sandbox can point outside of it, so no folder of the path may be a link.
$parent = [IO.Path]::GetDirectoryName($fullName)
while ($parent -and $parent.Length -gt $sandboxFullName.Length) {
if ([IO.Directory]::Exists($parent) -and
([IO.File]::GetAttributes($parent) -band [IO.FileAttributes]::ReparsePoint)) {
throw "Refusing to change '$fullName', because its folder '$parent' is a link."
}
$parent = [IO.Path]::GetDirectoryName($parent)
}
}
}
}
function Remove-TestSandbox {
<#
.SYNOPSIS
Removes a sandbox of New-TestSandbox: first its links, without following them, then its ACL changes, then
the folder.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only removes sandboxes.'
)]
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]
$Sandbox
)
Assert-TestSandboxPath -Sandbox $Sandbox -Path $Sandbox
if (-not (Test-Path -LiteralPath $Sandbox)) {
return
}
# icacls reports items it cannot reset on stderr, which Windows PowerShell turns into a terminating error when
# the caller uses ErrorAction Stop. Such items can still be deleted through the rights on their folder.
$ErrorActionPreference = 'Continue'
# Windows PowerShell 5.1 and icacls /T follow directory links, so the links go first. A folder that denies
# listing its content gets its own ACL reset, without /T, before it is listed.
$pending = New-Object -TypeName 'System.Collections.Generic.Stack[string]'
$pending.Push($Sandbox)
while ($pending.Count -gt 0) {
$folder = $pending.Pop()
try {
$entries = [IO.Directory]::GetFileSystemEntries($folder)
}
catch {
& icacls.exe $folder /reset /C /Q *> $null
$entries = [IO.Directory]::GetFileSystemEntries($folder)
}
foreach ($entry in $entries) {
$attributes = [IO.File]::GetAttributes($entry)
if ($attributes -band [IO.FileAttributes]::ReparsePoint) {
if ($attributes -band [IO.FileAttributes]::Directory) {
[IO.Directory]::Delete($entry, $false)
}
else {
[IO.File]::Delete($entry)
}
}
elseif ($attributes -band [IO.FileAttributes]::Directory) {
$pending.Push($entry)
}
}
}
& icacls.exe $Sandbox /reset /T /C /Q *> $null
Get-ChildItem -LiteralPath $Sandbox -Recurse -Force | ForEach-Object -Process {
$_.Attributes = [IO.FileAttributes]::Normal
}
Remove-Item -LiteralPath $Sandbox -Recurse -Force
try {
# Fails while another sandbox exists, also one of a test run in parallel
[IO.Directory]::Delete($script:sandboxRoot, $false)
}
catch {
Write-Verbose -Message "Keeping '$script:sandboxRoot': $($_.Exception.Message)"
}
}
function New-TestSandboxItem {
<#
.SYNOPSIS
Creates a file or folder with a unique name in the sandbox and returns its full path.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only writes to sandboxes.'
)]
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory)]
[string]
$Sandbox,
[ValidatePattern('^[\w-]+$')]
[string]
$Name = 'Item',
[switch]
$Directory
)
$path = Join-Path -Path $Sandbox -ChildPath ('{0}-{1}' -f $Name, [guid]::NewGuid().ToString('N').Substring(0, 8))
Assert-TestSandboxPath -Sandbox $Sandbox -Path $path
if ($Directory) {
New-Item -ItemType Directory -Path $path | Out-Null
}
else {
Set-Content -LiteralPath $path -Value $Name
}
$path
}
function Block-TestReadPermission {
<#
.SYNOPSIS
Denies the owner of an item in the sandbox to read its security descriptor, so that reading it fails.
.DESCRIPTION
Adds a deny entry for OWNER RIGHTS (S-1-3-4) with ReadPermissions, which replaces the implicit right of the
owner to read the security descriptor. Remove-TestSandbox resets it.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]
$Sandbox,
[Parameter(Mandatory)]
[string]
$Path
)
Assert-TestSandboxPath -Sandbox $Sandbox -Path $Path
$acl = Get-Acl -LiteralPath $Path
$ownerRights = New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList 'S-1-3-4'
$rule = New-Object -TypeName 'System.Security.AccessControl.FileSystemAccessRule' -ArgumentList (
$ownerRights, [System.Security.AccessControl.FileSystemRights]::ReadPermissions,
[System.Security.AccessControl.AccessControlType]::Deny
)
$acl.AddAccessRule($rule)
Set-Acl -LiteralPath $Path -AclObject $acl
}
function Test-IsElevated {
<#
.SYNOPSIS
Returns $true when the process runs elevated as an administrator.
#>
[CmdletBinding()]
[OutputType([bool])]
param ()
$principal = New-Object -TypeName 'Security.Principal.WindowsPrincipal' -ArgumentList (
[Security.Principal.WindowsIdentity]::GetCurrent()
)
$principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Test-PrivilegeHeld {
<#
.SYNOPSIS
Returns $true when the access token of the process holds the privilege, enabled or not.
.PARAMETER Name
The privilege name, such as SeSecurityPrivilege or SeBackupPrivilege.
#>
[CmdletBinding()]
[OutputType([bool])]
param (
[Parameter(Mandatory)]
[ValidatePattern('^Se\w+Privilege$')]
[string]
$Name
)
# The column names are localized; the privilege names in the first column are not.
$output = & whoami.exe /priv /fo csv 2>$null
if ($LASTEXITCODE -ne 0) {
return $false
}
[bool] ($output | Select-Object -Skip 1 | ConvertFrom-Csv -Header 'Name', 'Description', 'State' |
Where-Object -Property Name -EQ -Value $Name)
}
Export-ModuleMember -Function New-TestSandbox, Assert-TestSandboxPath, Remove-TestSandbox, New-TestSandboxItem,
Block-TestReadPermission, Test-IsElevated, Test-PrivilegeHeld
Loading…
Cancel
Save