Browse Source

fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn't valid

Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
silently kept the default value of AccessType, AuditFlags,
InheritanceFlags, PropagationFlags, and AppliesTo when a piped object's
property could not be converted to the parameter's type: a misspelled
AccessType such as "Denyy", or a blank CSV cell, created an Allow entry
with no message. For a permissions tool, a typo for Deny that silently
grants access is a security-relevant defect (Decision 27).

The five parameters are now declared as object and converted inside
the cmdlet, the way the PowerShell binder converts a typed parameter
(names in any case, comma-separated flag lists, numbers, enum
instances). A value that cannot be converted, including a blank one,
is now an error named InvalidParameterValue: terminating and
catchable when it is named on the command line, exactly as the binder
reported it before; a non-terminating error targeting that path or
descriptor when it comes from a piped object, which leaves that item
unchanged and lets the pipeline continue with the next one. A $null
value is still not an error and keeps the parameter's default. Tab
completion of the five parameters keeps working through explicit
argument completers. Choosing between the Simple and Complex
parameter sets is unaffected and still rejects AppliesTo together
with InheritanceFlags or PropagationFlags as AmbiguousParameterSet,
for every mix of string and enum values.

The four cmdlet pages document the parameters as Type: Object, list
the valid values, and describe the new error; a sentence that said a
piped AppliesTo typo or blank cell was silently ignored is corrected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/131/head
Raimund Andree 1 week ago
parent
commit
abdf212fe4
  1. 30
      Docs/Cmdlets/Add-NTFSAccess.md
  2. 28
      Docs/Cmdlets/Add-NTFSAudit.md
  3. 30
      Docs/Cmdlets/Remove-NTFSAccess.md
  4. 28
      Docs/Cmdlets/Remove-NTFSAudit.md
  5. 128
      NTFSSecurity/AccessCmdlets/AddAccess.cs
  6. 128
      NTFSSecurity/AccessCmdlets/RemoveAccess.cs
  7. 128
      NTFSSecurity/AuditCmdlets/AddAudit.cs
  8. 128
      NTFSSecurity/AuditCmdlets/RemoveAudit.cs
  9. 87
      NTFSSecurity/EnumArgumentCompleters.cs
  10. 2
      NTFSSecurity/NTFSSecurity.csproj
  11. 67
      NTFSSecurity/ParameterConversion.cs
  12. 337
      Tests/ParameterSets.Access.Tests.ps1
  13. 344
      Tests/ParameterSets.Audit.Tests.ps1

30
Docs/Cmdlets/Add-NTFSAccess.md

@ -16,28 +16,28 @@ Adds an access control entry (ACE) to a file, a folder, or a security descriptor
### PathSimple
```
Add-NTFSAccess [-Path] <String[]> [-Account] <IdentityReference2[]> [-AccessRights] <FileSystemRights2>
[-AccessType <AccessControlType>] -AppliesTo <ApplyTo> [-PassThru] [<CommonParameters>]
[-AccessType <Object>] -AppliesTo <Object> [-PassThru] [<CommonParameters>]
```
### PathComplex
```
Add-NTFSAccess [-Path] <String[]> [-Account] <IdentityReference2[]> [-AccessRights] <FileSystemRights2>
[-AccessType <AccessControlType>] [-InheritanceFlags <InheritanceFlags>]
[-PropagationFlags <PropagationFlags>] [-PassThru] [<CommonParameters>]
[-AccessType <Object>] [-InheritanceFlags <Object>]
[-PropagationFlags <Object>] [-PassThru] [<CommonParameters>]
```
### SDSimple
```
Add-NTFSAccess [-SecurityDescriptor] <FileSystemSecurity2[]> [-Account] <IdentityReference2[]>
[-AccessRights] <FileSystemRights2> [-AccessType <AccessControlType>] -AppliesTo <ApplyTo> [-PassThru]
[-AccessRights] <FileSystemRights2> [-AccessType <Object>] -AppliesTo <Object> [-PassThru]
[<CommonParameters>]
```
### SDComplex
```
Add-NTFSAccess [-SecurityDescriptor] <FileSystemSecurity2[]> [-Account] <IdentityReference2[]>
[-AccessRights] <FileSystemRights2> [-AccessType <AccessControlType>] [-InheritanceFlags <InheritanceFlags>]
[-PropagationFlags <PropagationFlags>] [-PassThru] [<CommonParameters>]
[-AccessRights] <FileSystemRights2> [-AccessType <Object>] [-InheritanceFlags <Object>]
[-PropagationFlags <Object>] [-PassThru] [<CommonParameters>]
```
## DESCRIPTION
@ -46,7 +46,7 @@ Adds an access control entry (ACE) to the discretionary access control list (DAC
`-AccessRights` accepts the basic rights such as `Read`, `Modify`, and `FullControl` as well as the granular rights such as `CreateFiles` or `WriteAttributes`, and several values can be combined, for example `-AccessRights ReadData, WriteData, Delete`. For the mapping between the values of this module, the rights that Windows displays, and the entries of the advanced security dialog, see [Concepts](../Concepts.md).
The cmdlet has four parameter sets. The `Path` sets read the item from disk and write the changed DACL back immediately, while the `SD` sets change a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor` in memory until `Set-NTFSSecurityDescriptor` writes it back. The `Simple` sets take `-AppliesTo`, the `Complex` sets take `-InheritanceFlags` and `-PropagationFlags`; both describe the same ACE flags. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo` that PowerShell can't convert to a scope, such as the blank cell of a CSV file or a misspelled name, is ignored, and the object is handled as one without `AppliesTo`: it uses its own `InheritanceFlags` and `PropagationFlags`, or the default scope if it has none. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets.
The cmdlet has four parameter sets. The `Path` sets read the item from disk and write the changed DACL back immediately, while the `SD` sets change a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor` in memory until `Set-NTFSSecurityDescriptor` writes it back. The `Simple` sets take `-AppliesTo`, the `Complex` sets take `-InheritanceFlags` and `-PropagationFlags`; both describe the same ACE flags. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo`, `AccessType`, `InheritanceFlags`, or `PropagationFlags` that PowerShell can't convert to its type, such as the blank cell of a CSV file or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets.
When `-AccessType`, `-AppliesTo`, `-InheritanceFlags`, and `-PropagationFlags` are omitted, the cmdlet adds an `Allow` ACE that applies to this folder, subfolders, and files, which corresponds to the inheritance flags `ContainerInherit, ObjectInherit` and no propagation flags. An `Allow` ACE always receives the `Synchronize` right in addition to the requested rights, inheritance and propagation flags are ignored on files, and rights for an account that already has an ACE with the same access type and the same flags are merged into that ACE. The cmdlet writes no output unless `-PassThru` is used, and a failure on one item is reported as a non-terminating error while the remaining items are processed.
@ -107,10 +107,10 @@ Accept wildcard characters: False
### -AccessType
Specifies whether the ACE allows or denies the rights in `-AccessRights`. The default is `Allow`. A `Deny` ACE takes precedence over `Allow` ACEs that grant the same rights.
Specifies whether the ACE allows or denies the rights in `-AccessRights`. The default is `Allow`. A `Deny` ACE takes precedence over `Allow` ACEs that grant the same rights. A value that cannot be converted to `Allow` or `Deny`, including a blank one, is an `InvalidParameterValue` error: terminating when `-AccessType` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: AccessControlType
Type: Object
Parameter Sets: (All)
Aliases: AccessControlType
Accepted values: Allow, Deny
@ -140,10 +140,10 @@ Accept wildcard characters: False
### -AppliesTo
Specifies the scope of the ACE in the wording of the Windows security dialog, for example `ThisFolderOnly`, `ThisFolderAndSubfolders`, or `SubfoldersAndFilesOnly`. Without `-AppliesTo`, the cmdlet uses `-InheritanceFlags` and `-PropagationFlags`, whose defaults describe `ThisFolderSubfoldersAndFiles`. The cmdlet translates the value into the equivalent inheritance and propagation flags, so this parameter and the pair `-InheritanceFlags` and `-PropagationFlags` are two ways to describe the same ACE. The values ending in `OneLevel` limit inheritance to the direct children of the folder.
Specifies the scope of the ACE in the wording of the Windows security dialog, for example `ThisFolderOnly`, `ThisFolderAndSubfolders`, or `SubfoldersAndFilesOnly`. Without `-AppliesTo`, the cmdlet uses `-InheritanceFlags` and `-PropagationFlags`, whose defaults describe `ThisFolderSubfoldersAndFiles`. The cmdlet translates the value into the equivalent inheritance and propagation flags, so this parameter and the pair `-InheritanceFlags` and `-PropagationFlags` are two ways to describe the same ACE. The values ending in `OneLevel` limit inheritance to the direct children of the folder. A value that cannot be converted to one of these scopes, including a blank one, is an `InvalidParameterValue` error: terminating when `-AppliesTo` is named on the command line, and reported for just that path or descriptor, unchanged, when it comes from the pipeline.
```yaml
Type: ApplyTo
Type: Object
Parameter Sets: PathSimple, SDSimple
Aliases:
Accepted values: ThisFolderOnly, ThisFolderSubfoldersAndFiles, ThisFolderAndSubfolders, ThisFolderAndFiles, SubfoldersAndFilesOnly, SubfoldersOnly, FilesOnly, ThisFolderSubfoldersAndFilesOneLevel, ThisFolderAndSubfoldersOneLevel, ThisFolderAndFilesOneLevel, SubfoldersAndFilesOnlyOneLevel, SubfoldersOnlyOneLevel, FilesOnlyOneLevel
@ -157,12 +157,12 @@ Accept wildcard characters: False
### -InheritanceFlags
Specifies which kind of child objects inherit the ACE. `ContainerInherit` passes the ACE on to child folders, `ObjectInherit` passes it on to child files, and `None` keeps the ACE on the item itself. The default is `ContainerInherit, ObjectInherit`. Inheritance flags have no effect on files, where the ACE is always created with `None`.
Specifies which kind of child objects inherit the ACE. `ContainerInherit` passes the ACE on to child folders, `ObjectInherit` passes it on to child files, and `None` keeps the ACE on the item itself. The default is `ContainerInherit, ObjectInherit`. Inheritance flags have no effect on files, where the ACE is always created with `None`. A value that cannot be converted to one of these flags, including a blank one, is an `InvalidParameterValue` error: terminating when `-InheritanceFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
For details about the flags, see the .NET documentation of the [InheritanceFlags Enum](https://learn.microsoft.com/en-us/dotnet/api/system.security.accesscontrol.inheritanceflags).
```yaml
Type: InheritanceFlags
Type: Object
Parameter Sets: PathComplex, SDComplex
Aliases:
Accepted values: None, ContainerInherit, ObjectInherit
@ -208,10 +208,10 @@ Accept wildcard characters: False
### -PropagationFlags
Specifies how the ACE is propagated to child objects. `None` propagates the ACE to all levels that the inheritance flags allow, `InheritOnly` keeps the ACE from applying to the item it is defined on, and `NoPropagateInherit` limits inheritance to the direct children of the folder. The default is `None`, and propagation flags only have an effect in combination with `-InheritanceFlags`.
Specifies how the ACE is propagated to child objects. `None` propagates the ACE to all levels that the inheritance flags allow, `InheritOnly` keeps the ACE from applying to the item it is defined on, and `NoPropagateInherit` limits inheritance to the direct children of the folder. The default is `None`, and propagation flags only have an effect in combination with `-InheritanceFlags`. A value that cannot be converted to one of these flags, including a blank one, is an `InvalidParameterValue` error: terminating when `-PropagationFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: PropagationFlags
Type: Object
Parameter Sets: PathComplex, SDComplex
Aliases:
Accepted values: None, NoPropagateInherit, InheritOnly

28
Docs/Cmdlets/Add-NTFSAudit.md

@ -16,28 +16,28 @@ Adds an audit entry to a file or folder.
### PathSimple
```
Add-NTFSAudit [-Path] <String[]> [-Account] <IdentityReference2[]> [-AccessRights] <FileSystemRights2>
[-AuditFlags <AuditFlags>] -AppliesTo <ApplyTo> [-PassThru] [<CommonParameters>]
[-AuditFlags <Object>] -AppliesTo <Object> [-PassThru] [<CommonParameters>]
```
### PathComplex
```
Add-NTFSAudit [-Path] <String[]> [-Account] <IdentityReference2[]> [-AccessRights] <FileSystemRights2>
[-AuditFlags <AuditFlags>] [-InheritanceFlags <InheritanceFlags>] [-PropagationFlags <PropagationFlags>]
[-AuditFlags <Object>] [-InheritanceFlags <Object>] [-PropagationFlags <Object>]
[-PassThru] [<CommonParameters>]
```
### SDSimple
```
Add-NTFSAudit [-SecurityDescriptor] <FileSystemSecurity2[]> [-Account] <IdentityReference2[]>
[-AccessRights] <FileSystemRights2> [-AuditFlags <AuditFlags>] -AppliesTo <ApplyTo> [-PassThru]
[-AccessRights] <FileSystemRights2> [-AuditFlags <Object>] -AppliesTo <Object> [-PassThru]
[<CommonParameters>]
```
### SDComplex
```
Add-NTFSAudit [-SecurityDescriptor] <FileSystemSecurity2[]> [-Account] <IdentityReference2[]>
[-AccessRights] <FileSystemRights2> [-AuditFlags <AuditFlags>] [-InheritanceFlags <InheritanceFlags>]
[-PropagationFlags <PropagationFlags>] [-PassThru] [<CommonParameters>]
[-AccessRights] <FileSystemRights2> [-AuditFlags <Object>] [-InheritanceFlags <Object>]
[-PropagationFlags <Object>] [-PassThru] [<CommonParameters>]
```
## DESCRIPTION
@ -46,7 +46,7 @@ The `Add-NTFSAudit` cmdlet adds an audit entry to the system access control list
In the `PathSimple` and `PathComplex` parameter sets the cmdlet reads the security descriptor of every item in `-Path`, adds the entry, and writes the descriptor back right away. In the `SDSimple` and `SDComplex` parameter sets it adds the entry to an in-memory `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned; that change only reaches the file system when you pass the object to `Set-NTFSSecurityDescriptor`. The simple sets describe the scope of the entry with the single `-AppliesTo` parameter, the complex sets with `-InheritanceFlags` and `-PropagationFlags`.
A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo` that PowerShell can't convert to a scope, such as the blank cell of a CSV file or a misspelled name, is ignored, and the object is handled as one without `AppliesTo`: it uses its own `InheritanceFlags` and `PropagationFlags`, or the default scope if it has none. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets.
A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo`, `AuditFlags`, `InheritanceFlags`, or `PropagationFlags` that PowerShell can't convert to its type, such as the blank cell of a CSV file or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets.
When you omit them, `-AuditFlags` is `Success, Failure`, `-InheritanceFlags` is `ContainerInherit, ObjectInherit`, `-PropagationFlags` is `None`, and `-AppliesTo` is `ThisFolderSubfoldersAndFiles`, so both the simple and the complex set audit the item, its subfolders, and its files by default. Inheritance applies to folders only: when the item is a file, the cmdlet stores the entry without inheritance and propagation flags.
@ -125,10 +125,10 @@ Accept wildcard characters: False
### -AppliesTo
Specifies the scope of the audit entry with a single value instead of the `-InheritanceFlags` and `-PropagationFlags` pair, in the same wording the Advanced Security Settings dialog uses. `ThisFolderOnly` audits the folder itself, `ThisFolderSubfoldersAndFiles` audits the folder and everything below it, `SubfoldersAndFilesOnly` audits the content but not the folder itself, and the values ending in `OneLevel` limit inheritance to the direct children. Without `-AppliesTo`, the cmdlet uses `-InheritanceFlags` and `-PropagationFlags`, whose defaults describe `ThisFolderSubfoldersAndFiles`.
Specifies the scope of the audit entry with a single value instead of the `-InheritanceFlags` and `-PropagationFlags` pair, in the same wording the Advanced Security Settings dialog uses. `ThisFolderOnly` audits the folder itself, `ThisFolderSubfoldersAndFiles` audits the folder and everything below it, `SubfoldersAndFilesOnly` audits the content but not the folder itself, and the values ending in `OneLevel` limit inheritance to the direct children. Without `-AppliesTo`, the cmdlet uses `-InheritanceFlags` and `-PropagationFlags`, whose defaults describe `ThisFolderSubfoldersAndFiles`. A value that cannot be converted to one of these scopes, including a blank one, is an `InvalidParameterValue` error: terminating when `-AppliesTo` is named on the command line, and reported for just that path or descriptor, unchanged, when it comes from the pipeline.
```yaml
Type: ApplyTo
Type: Object
Parameter Sets: PathSimple, SDSimple
Aliases:
Accepted values: ThisFolderOnly, ThisFolderSubfoldersAndFiles, ThisFolderAndSubfolders, ThisFolderAndFiles, SubfoldersAndFilesOnly, SubfoldersOnly, FilesOnly, ThisFolderSubfoldersAndFilesOneLevel, ThisFolderAndSubfoldersOneLevel, ThisFolderAndFilesOneLevel, SubfoldersAndFilesOnlyOneLevel, SubfoldersOnlyOneLevel, FilesOnlyOneLevel
@ -142,10 +142,10 @@ Accept wildcard characters: False
### -AuditFlags
Specifies which access attempts are audited. `Success` audits attempts that succeeded, `Failure` audits attempts that were denied, and `Success, Failure` audits both. The default is `Success, Failure`.
Specifies which access attempts are audited. `Success` audits attempts that succeeded, `Failure` audits attempts that were denied, and `Success, Failure` audits both. The default is `Success, Failure`. A value that cannot be converted to one of these, including a blank one, is an `InvalidParameterValue` error: terminating when `-AuditFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: AuditFlags
Type: Object
Parameter Sets: (All)
Aliases:
Accepted values: None, Success, Failure
@ -159,10 +159,10 @@ Accept wildcard characters: False
### -InheritanceFlags
Specifies which child items inherit the audit entry. `ContainerInherit` passes the entry on to child folders, `ObjectInherit` passes it on to child files, and `None` keeps the entry on the item itself. The values can be combined, and the default is `ContainerInherit, ObjectInherit`. Use `-PropagationFlags` to control whether the entry also applies to the item itself and how far it propagates.
Specifies which child items inherit the audit entry. `ContainerInherit` passes the entry on to child folders, `ObjectInherit` passes it on to child files, and `None` keeps the entry on the item itself. The values can be combined, and the default is `ContainerInherit, ObjectInherit`. Use `-PropagationFlags` to control whether the entry also applies to the item itself and how far it propagates. A value that cannot be converted to one of these flags, including a blank one, is an `InvalidParameterValue` error: terminating when `-InheritanceFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: InheritanceFlags
Type: Object
Parameter Sets: PathComplex, SDComplex
Aliases:
Accepted values: None, ContainerInherit, ObjectInherit
@ -208,10 +208,10 @@ Accept wildcard characters: False
### -PropagationFlags
Specifies how the inheritance selected with `-InheritanceFlags` propagates. `None` applies the entry to the item itself and to all inheriting child items, `InheritOnly` applies it to the inheriting child items but not to the item itself, and `NoPropagateInherit` limits inheritance to the direct children. The values `InheritOnly` and `NoPropagateInherit` can be combined, and the default is `None`. The parameter has no effect when `-InheritanceFlags` is `None`.
Specifies how the inheritance selected with `-InheritanceFlags` propagates. `None` applies the entry to the item itself and to all inheriting child items, `InheritOnly` applies it to the inheriting child items but not to the item itself, and `NoPropagateInherit` limits inheritance to the direct children. The values `InheritOnly` and `NoPropagateInherit` can be combined, and the default is `None`. The parameter has no effect when `-InheritanceFlags` is `None`. A value that cannot be converted to one of these flags, including a blank one, is an `InvalidParameterValue` error: terminating when `-PropagationFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: PropagationFlags
Type: Object
Parameter Sets: PathComplex, SDComplex
Aliases:
Accepted values: None, NoPropagateInherit, InheritOnly

30
Docs/Cmdlets/Remove-NTFSAccess.md

@ -16,28 +16,28 @@ Removes rights from the access control entries (ACEs) of a file, a folder, or a
### PathSimple
```
Remove-NTFSAccess [-Path] <String[]> [-Account] <IdentityReference2[]> [-AccessRights] <FileSystemRights2>
[-AccessType <AccessControlType>] -AppliesTo <ApplyTo> [-RemoveSpecific] [-PassThru] [<CommonParameters>]
[-AccessType <Object>] -AppliesTo <Object> [-RemoveSpecific] [-PassThru] [<CommonParameters>]
```
### PathComplex
```
Remove-NTFSAccess [-Path] <String[]> [-Account] <IdentityReference2[]> [-AccessRights] <FileSystemRights2>
[-AccessType <AccessControlType>] [-InheritanceFlags <InheritanceFlags>]
[-PropagationFlags <PropagationFlags>] [-RemoveSpecific] [-PassThru] [<CommonParameters>]
[-AccessType <Object>] [-InheritanceFlags <Object>]
[-PropagationFlags <Object>] [-RemoveSpecific] [-PassThru] [<CommonParameters>]
```
### SDSimple
```
Remove-NTFSAccess [-SecurityDescriptor] <FileSystemSecurity2[]> [-Account] <IdentityReference2[]>
[-AccessRights] <FileSystemRights2> [-AccessType <AccessControlType>] -AppliesTo <ApplyTo> [-RemoveSpecific]
[-AccessRights] <FileSystemRights2> [-AccessType <Object>] -AppliesTo <Object> [-RemoveSpecific]
[-PassThru] [<CommonParameters>]
```
### SDComplex
```
Remove-NTFSAccess [-SecurityDescriptor] <FileSystemSecurity2[]> [-Account] <IdentityReference2[]>
[-AccessRights] <FileSystemRights2> [-AccessType <AccessControlType>] [-InheritanceFlags <InheritanceFlags>]
[-PropagationFlags <PropagationFlags>] [-RemoveSpecific] [-PassThru] [<CommonParameters>]
[-AccessRights] <FileSystemRights2> [-AccessType <Object>] [-InheritanceFlags <Object>]
[-PropagationFlags <Object>] [-RemoveSpecific] [-PassThru] [<CommonParameters>]
```
## DESCRIPTION
@ -48,7 +48,7 @@ Only the specified rights are taken away: when an entry grants more than `-Acces
Inherited entries cannot be removed from the item that inherits them. Remove them from the folder named in the `InheritedFrom` property, or run `Disable-NTFSAccessInheritance` on the item first, which copies the inherited entries into it as explicit ones that this cmdlet can then remove.
The cmdlet has four parameter sets. The `Path` sets read the item from disk and write the changed DACL back immediately, while the `SD` sets change a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor` in memory until `Set-NTFSSecurityDescriptor` writes it back. The `Simple` sets take `-AppliesTo`, the `Complex` sets take `-InheritanceFlags` and `-PropagationFlags`. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo` that PowerShell can't convert to a scope, such as the blank cell of a CSV file or a misspelled name, is ignored, and the object is handled as one without `AppliesTo`: it uses its own `InheritanceFlags` and `PropagationFlags`, or the default scope if it has none. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. All relevant parameters bind by property name, so the output of `Get-NTFSAccess` and `Get-NTFSOrphanedAccess` can be piped directly into this cmdlet. The cmdlet writes no output unless `-PassThru` is used.
The cmdlet has four parameter sets. The `Path` sets read the item from disk and write the changed DACL back immediately, while the `SD` sets change a `Security2.FileSystemSecurity2` object returned by `Get-NTFSSecurityDescriptor` in memory until `Set-NTFSSecurityDescriptor` writes it back. The `Simple` sets take `-AppliesTo`, the `Complex` sets take `-InheritanceFlags` and `-PropagationFlags`. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo`, `AccessType`, `InheritanceFlags`, or `PropagationFlags` that PowerShell can't convert to its type, such as the blank cell of a CSV file or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets. All relevant parameters bind by property name, so the output of `Get-NTFSAccess` and `Get-NTFSOrphanedAccess` can be piped directly into this cmdlet. The cmdlet writes no output unless `-PassThru` is used.
## EXAMPLES
@ -105,10 +105,10 @@ Accept wildcard characters: False
### -AccessType
Specifies whether an `Allow` or a `Deny` entry is addressed. The default is `Allow`. An entry of the other type is not touched.
Specifies whether an `Allow` or a `Deny` entry is addressed. The default is `Allow`. An entry of the other type is not touched. A value that cannot be converted to `Allow` or `Deny`, including a blank one, is an `InvalidParameterValue` error: terminating when `-AccessType` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: AccessControlType
Type: Object
Parameter Sets: (All)
Aliases: AccessControlType
Accepted values: Allow, Deny
@ -138,10 +138,10 @@ Accept wildcard characters: False
### -AppliesTo
Specifies the scope of the entry that is addressed, in the wording of the Windows security dialog, for example `ThisFolderOnly`, `ThisFolderAndSubfolders`, or `SubfoldersAndFilesOnly`. The cmdlet translates the value into the equivalent inheritance and propagation flags, so this parameter and the pair `-InheritanceFlags` and `-PropagationFlags` are two ways to describe the same entry. Use the scope that `Get-NTFSAccess` shows in the "Applies to" column of the entry.
Specifies the scope of the entry that is addressed, in the wording of the Windows security dialog, for example `ThisFolderOnly`, `ThisFolderAndSubfolders`, or `SubfoldersAndFilesOnly`. The cmdlet translates the value into the equivalent inheritance and propagation flags, so this parameter and the pair `-InheritanceFlags` and `-PropagationFlags` are two ways to describe the same entry. Use the scope that `Get-NTFSAccess` shows in the "Applies to" column of the entry. A value that cannot be converted to one of these scopes, including a blank one, is an `InvalidParameterValue` error: terminating when `-AppliesTo` is named on the command line, and reported for just that path or descriptor, unchanged, when it comes from the pipeline.
```yaml
Type: ApplyTo
Type: Object
Parameter Sets: PathSimple, SDSimple
Aliases:
Accepted values: ThisFolderOnly, ThisFolderSubfoldersAndFiles, ThisFolderAndSubfolders, ThisFolderAndFiles, SubfoldersAndFilesOnly, SubfoldersOnly, FilesOnly, ThisFolderSubfoldersAndFilesOneLevel, ThisFolderAndSubfoldersOneLevel, ThisFolderAndFilesOneLevel, SubfoldersAndFilesOnlyOneLevel, SubfoldersOnlyOneLevel, FilesOnlyOneLevel
@ -155,10 +155,10 @@ Accept wildcard characters: False
### -InheritanceFlags
Specifies the inheritance flags of the entry that is addressed. `ContainerInherit` marks an entry that child folders inherit, `ObjectInherit` marks an entry that child files inherit, and `None` marks an entry that is not inherited at all. The default is `ContainerInherit, ObjectInherit`, which is the scope `ThisFolderSubfoldersAndFiles`. Entries on files always carry `None`.
Specifies the inheritance flags of the entry that is addressed. `ContainerInherit` marks an entry that child folders inherit, `ObjectInherit` marks an entry that child files inherit, and `None` marks an entry that is not inherited at all. The default is `ContainerInherit, ObjectInherit`, which is the scope `ThisFolderSubfoldersAndFiles`. Entries on files always carry `None`. A value that cannot be converted to one of these flags, including a blank one, is an `InvalidParameterValue` error: terminating when `-InheritanceFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: InheritanceFlags
Type: Object
Parameter Sets: PathComplex, SDComplex
Aliases:
Accepted values: None, ContainerInherit, ObjectInherit
@ -204,10 +204,10 @@ Accept wildcard characters: False
### -PropagationFlags
Specifies the propagation flags of the entry that is addressed. `None` marks an entry that is inherited by all levels allowed by its inheritance flags, `InheritOnly` marks an entry that does not apply to the item it is defined on, and `NoPropagateInherit` marks an entry that is only inherited by the direct children of the folder. The default is `None`.
Specifies the propagation flags of the entry that is addressed. `None` marks an entry that is inherited by all levels allowed by its inheritance flags, `InheritOnly` marks an entry that does not apply to the item it is defined on, and `NoPropagateInherit` marks an entry that is only inherited by the direct children of the folder. The default is `None`. A value that cannot be converted to one of these flags, including a blank one, is an `InvalidParameterValue` error: terminating when `-PropagationFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: PropagationFlags
Type: Object
Parameter Sets: PathComplex, SDComplex
Aliases:
Accepted values: None, NoPropagateInherit, InheritOnly

28
Docs/Cmdlets/Remove-NTFSAudit.md

@ -16,28 +16,28 @@ Removes an audit entry from a file or folder.
### PathSimple
```
Remove-NTFSAudit [-Path] <String[]> [-Account] <IdentityReference2[]> [-AccessRights] <FileSystemRights2>
[-AuditFlags <AuditFlags>] -AppliesTo <ApplyTo> [-RemoveSpecific] [-PassThru] [<CommonParameters>]
[-AuditFlags <Object>] -AppliesTo <Object> [-RemoveSpecific] [-PassThru] [<CommonParameters>]
```
### PathComplex
```
Remove-NTFSAudit [-Path] <String[]> [-Account] <IdentityReference2[]> [-AccessRights] <FileSystemRights2>
[-AuditFlags <AuditFlags>] [-InheritanceFlags <InheritanceFlags>] [-PropagationFlags <PropagationFlags>]
[-AuditFlags <Object>] [-InheritanceFlags <Object>] [-PropagationFlags <Object>]
[-RemoveSpecific] [-PassThru] [<CommonParameters>]
```
### SDSimple
```
Remove-NTFSAudit [-SecurityDescriptor] <FileSystemSecurity2[]> [-Account] <IdentityReference2[]>
[-AccessRights] <FileSystemRights2> [-AuditFlags <AuditFlags>] -AppliesTo <ApplyTo> [-RemoveSpecific]
[-AccessRights] <FileSystemRights2> [-AuditFlags <Object>] -AppliesTo <Object> [-RemoveSpecific]
[-PassThru] [<CommonParameters>]
```
### SDComplex
```
Remove-NTFSAudit [-SecurityDescriptor] <FileSystemSecurity2[]> [-Account] <IdentityReference2[]>
[-AccessRights] <FileSystemRights2> [-AuditFlags <AuditFlags>] [-InheritanceFlags <InheritanceFlags>]
[-PropagationFlags <PropagationFlags>] [-RemoveSpecific] [-PassThru] [<CommonParameters>]
[-AccessRights] <FileSystemRights2> [-AuditFlags <Object>] [-InheritanceFlags <Object>]
[-PropagationFlags <Object>] [-RemoveSpecific] [-PassThru] [<CommonParameters>]
```
## DESCRIPTION
@ -46,7 +46,7 @@ The `Remove-NTFSAudit` cmdlet removes an audit entry from the system access cont
Because the inheritance and propagation flags take part in the match, they must describe the entry you want to remove. `-AppliesTo ThisFolderOnly` removes an entry that is not inherited by child items, which is also the shape of every audit entry on a file, while the default of the complex parameter sets removes an entry that applies to the folder, its subfolders, and its files. An entry that an item inherits from a parent folder is stored on that parent, so remove it there, or use `Clear-NTFSAudit` with `-DisableInheritance` to drop the inherited entries on the item.
In the `PathSimple` and `PathComplex` parameter sets the cmdlet reads the security descriptor of every item in `-Path` and writes it back right away. In the `SDSimple` and `SDComplex` parameter sets it changes an in-memory `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned, and the change reaches the file system only when you pass the object to `Set-NTFSSecurityDescriptor`. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo` that PowerShell can't convert to a scope, such as the blank cell of a CSV file or a misspelled name, is ignored, and the object is handled as one without `AppliesTo`: it uses its own `InheritanceFlags` and `PropagationFlags`, or the default scope if it has none. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets.
In the `PathSimple` and `PathComplex` parameter sets the cmdlet reads the security descriptor of every item in `-Path` and writes it back right away. In the `SDSimple` and `SDComplex` parameter sets it changes an in-memory `Security2.FileSystemSecurity2` object that `Get-NTFSSecurityDescriptor` returned, and the change reaches the file system only when you pass the object to `Set-NTFSSecurityDescriptor`. A command without `-AppliesTo` uses a `Complex` set, also when it works on a security descriptor, and a command that has `-AppliesTo` together with `-InheritanceFlags` or `-PropagationFlags` fails, because PowerShell can't choose between the sets. The same holds for an object that you pipe in: its property `AppliesTo` selects a `Simple` set, its properties `InheritanceFlags` and `PropagationFlags` select a `Complex` set, and an object that has both kinds, an object that has both `Path` and `SecurityDescriptor`, or one whose `AppliesTo` is `$null`, fails. `Import-Csv` gives `$null` for a column that a row lacks because it has fewer fields than the header. An `AppliesTo`, `AuditFlags`, `InheritanceFlags`, or `PropagationFlags` that PowerShell can't convert to its type, such as the blank cell of a CSV file or a misspelled name, is an `InvalidParameterValue` error for that object: the object is left unchanged, and the next one in the pipeline is still processed; the same bad value named on the command line fails the whole command instead. Before 5.0.0, such a value was silently ignored, and the object was handled as one without it. Before 5.0.0, a command that used `-SecurityDescriptor` without `-AppliesTo`, `-InheritanceFlags`, or `-PropagationFlags` failed, because PowerShell couldn't choose between the two `SD` sets.
When you omit them, `-AuditFlags` is `Success, Failure`, `-InheritanceFlags` is `ContainerInherit, ObjectInherit`, `-PropagationFlags` is `None`. All parameters bind by property name, and `-Path` also binds by value and through its `FullName` alias, so you can pipe the output of `Get-NTFSAudit`, `Get-ChildItem`, `Get-ChildItem2`, and `Get-Item2` into the cmdlet. The cmdlet writes no object unless you use `-PassThru`.
@ -123,10 +123,10 @@ Accept wildcard characters: False
### -AppliesTo
Specifies the scope of the audit entry to remove with a single value instead of the `-InheritanceFlags` and `-PropagationFlags` pair, in the same wording the Advanced Security Settings dialog uses. The value must describe the entry as `Get-NTFSAudit` reports it, otherwise nothing is removed. Without `-AppliesTo`, the cmdlet uses `-InheritanceFlags` and `-PropagationFlags`, whose defaults describe `ThisFolderSubfoldersAndFiles`.
Specifies the scope of the audit entry to remove with a single value instead of the `-InheritanceFlags` and `-PropagationFlags` pair, in the same wording the Advanced Security Settings dialog uses. The value must describe the entry as `Get-NTFSAudit` reports it, otherwise nothing is removed. Without `-AppliesTo`, the cmdlet uses `-InheritanceFlags` and `-PropagationFlags`, whose defaults describe `ThisFolderSubfoldersAndFiles`. A value that cannot be converted to one of these scopes, including a blank one, is an `InvalidParameterValue` error: terminating when `-AppliesTo` is named on the command line, and reported for just that path or descriptor, unchanged, when it comes from the pipeline.
```yaml
Type: ApplyTo
Type: Object
Parameter Sets: PathSimple, SDSimple
Aliases:
Accepted values: ThisFolderOnly, ThisFolderSubfoldersAndFiles, ThisFolderAndSubfolders, ThisFolderAndFiles, SubfoldersAndFilesOnly, SubfoldersOnly, FilesOnly, ThisFolderSubfoldersAndFilesOneLevel, ThisFolderAndSubfoldersOneLevel, ThisFolderAndFilesOneLevel, SubfoldersAndFilesOnlyOneLevel, SubfoldersOnlyOneLevel, FilesOnlyOneLevel
@ -140,10 +140,10 @@ Accept wildcard characters: False
### -AuditFlags
Specifies which audited access attempts are removed from the entry. `Success` removes the auditing of successful attempts, `Failure` removes the auditing of denied attempts, and `Success, Failure` removes both. An entry that audits the flag you did not specify stays in place with that flag. The default is `Success, Failure`.
Specifies which audited access attempts are removed from the entry. `Success` removes the auditing of successful attempts, `Failure` removes the auditing of denied attempts, and `Success, Failure` removes both. An entry that audits the flag you did not specify stays in place with that flag. The default is `Success, Failure`. A value that cannot be converted to one of these, including a blank one, is an `InvalidParameterValue` error: terminating when `-AuditFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: AuditFlags
Type: Object
Parameter Sets: (All)
Aliases:
Accepted values: None, Success, Failure
@ -157,10 +157,10 @@ Accept wildcard characters: False
### -InheritanceFlags
Specifies the inheritance flags of the audit entry to remove. `ContainerInherit` addresses an entry that child folders inherit, `ObjectInherit` an entry that child files inherit, and `None` an entry that stays on the item itself. The values can be combined, and the default is `ContainerInherit, ObjectInherit`. The flags must match the entry as `Get-NTFSAudit` reports it, otherwise nothing is removed.
Specifies the inheritance flags of the audit entry to remove. `ContainerInherit` addresses an entry that child folders inherit, `ObjectInherit` an entry that child files inherit, and `None` an entry that stays on the item itself. The values can be combined, and the default is `ContainerInherit, ObjectInherit`. The flags must match the entry as `Get-NTFSAudit` reports it, otherwise nothing is removed. A value that cannot be converted to one of these flags, including a blank one, is an `InvalidParameterValue` error: terminating when `-InheritanceFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: InheritanceFlags
Type: Object
Parameter Sets: PathComplex, SDComplex
Aliases:
Accepted values: None, ContainerInherit, ObjectInherit
@ -206,10 +206,10 @@ Accept wildcard characters: False
### -PropagationFlags
Specifies the propagation flags of the audit entry to remove. `None` addresses an entry that applies to the item itself and to all inheriting child items, `InheritOnly` an entry that applies to the child items only, and `NoPropagateInherit` an entry whose inheritance stops at the direct children. The values `InheritOnly` and `NoPropagateInherit` can be combined, and the default is `None`.
Specifies the propagation flags of the audit entry to remove. `None` addresses an entry that applies to the item itself and to all inheriting child items, `InheritOnly` an entry that applies to the child items only, and `NoPropagateInherit` an entry whose inheritance stops at the direct children. The values `InheritOnly` and `NoPropagateInherit` can be combined, and the default is `None`. A value that cannot be converted to one of these flags, including a blank one, is an `InvalidParameterValue` error: terminating when `-PropagationFlags` is named on the command line, and reported for just that item, unchanged, when it comes from the pipeline.
```yaml
Type: PropagationFlags
Type: Object
Parameter Sets: PathComplex, SDComplex
Aliases:
Accepted values: None, NoPropagateInherit, InheritOnly

128
NTFSSecurity/AccessCmdlets/AddAccess.cs

@ -13,10 +13,10 @@ namespace NTFSSecurity
{
private IdentityReference2[] account;
private FileSystemRights2 accessRights;
private AccessControlType accessType = AccessControlType.Allow;
private InheritanceFlags inheritanceFlags = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
private PropagationFlags propagationFlags = PropagationFlags.None;
private ApplyTo appliesTo = ApplyTo.ThisFolderSubfoldersAndFiles;
private object accessType;
private object inheritanceFlags;
private object propagationFlags;
private object appliesTo;
private bool passThru;
[Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathSimple")]
@ -65,7 +65,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true)]
[Alias("AccessControlType")]
public AccessControlType AccessType
[ArgumentCompleter(typeof(AccessControlTypeArgumentCompleter))]
public object AccessType
{
get { return accessType; }
set { accessType = value; }
@ -73,7 +74,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
public InheritanceFlags InheritanceFlags
[ArgumentCompleter(typeof(InheritanceFlagsArgumentCompleter))]
public object InheritanceFlags
{
get { return inheritanceFlags; }
set { inheritanceFlags = value; }
@ -81,7 +83,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
public PropagationFlags PropagationFlags
[ArgumentCompleter(typeof(PropagationFlagsArgumentCompleter))]
public object PropagationFlags
{
get { return propagationFlags; }
set { propagationFlags = value; }
@ -90,7 +93,8 @@ namespace NTFSSecurity
// Mandatory, so that a command without it resolves to the Complex parameter set, also for -SecurityDescriptor.
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathSimple")]
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "SDSimple")]
public ApplyTo AppliesTo
[ArgumentCompleter(typeof(ApplyToArgumentCompleter))]
public object AppliesTo
{
get { return appliesTo; }
set { appliesTo = value; }
@ -105,19 +109,54 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
// Checked here, before privileges are enabled, because only a value that came from the command line is
// already bound at this point; a bad value from the pipeline is handled per record in ProcessRecord.
ValidateNamedValue<AccessControlType>(accessType, "AccessType");
ValidateNamedValue<InheritanceFlags>(inheritanceFlags, "InheritanceFlags");
ValidateNamedValue<PropagationFlags>(propagationFlags, "PropagationFlags");
ValidateNamedValue<ApplyTo>(appliesTo, "AppliesTo");
base.BeginProcessing();
}
protected override void ProcessRecord()
{
AccessControlType accessTypeValue;
if (!TryConvertValue(accessType, "AccessType", AccessControlType.Allow, out accessTypeValue))
{
return;
}
// The flags of this record are locals: the fields behind -InheritanceFlags and -PropagationFlags keep the value of the
// previous piped object when the next one binds neither, so the scope that -AppliesTo converts to must not stay in them.
var inheritance = inheritanceFlags;
var propagation = propagationFlags;
var inheritance = System.Security.AccessControl.InheritanceFlags.ContainerInherit | System.Security.AccessControl.InheritanceFlags.ObjectInherit;
var propagation = System.Security.AccessControl.PropagationFlags.None;
if (ParameterSetName.EndsWith("Simple"))
{
FileSystemSecurity2.ConvertToFileSystemFlags(appliesTo, out inheritance, out propagation);
ApplyTo appliesToValue;
if (!TryConvertValue(appliesTo, "AppliesTo", default(ApplyTo), out appliesToValue))
{
return;
}
FileSystemSecurity2.ConvertToFileSystemFlags(appliesToValue, out inheritance, out propagation);
}
else
{
InheritanceFlags inheritanceValue;
if (!TryConvertValue(inheritanceFlags, "InheritanceFlags", inheritance, out inheritanceValue))
{
return;
}
inheritance = inheritanceValue;
PropagationFlags propagationValue;
if (!TryConvertValue(propagationFlags, "PropagationFlags", propagation, out propagationValue))
{
return;
}
propagation = propagationValue;
}
if (ParameterSetName.StartsWith("Path"))
@ -138,7 +177,7 @@ namespace NTFSSecurity
try
{
FileSystemAccessRule2.AddFileSystemAccessRule(item, account.ToList(), accessRights, accessType, inheritance, propagation);
FileSystemAccessRule2.AddFileSystemAccessRule(item, account.ToList(), accessRights, accessTypeValue, inheritance, propagation);
}
catch (UnauthorizedAccessException)
{
@ -146,7 +185,7 @@ namespace NTFSSecurity
{
InvokeAsOwner(item, path, () =>
{
FileSystemAccessRule2.AddFileSystemAccessRule(item, account.ToList(), accessRights, accessType, inheritance, propagation);
FileSystemAccessRule2.AddFileSystemAccessRule(item, account.ToList(), accessRights, accessTypeValue, inheritance, propagation);
});
}
catch (Exception ex2)
@ -171,7 +210,7 @@ namespace NTFSSecurity
{
foreach (var sd in securityDescriptors)
{
FileSystemAccessRule2.AddFileSystemAccessRule(sd, account.ToList(), accessRights, accessType, inheritance, propagation);
FileSystemAccessRule2.AddFileSystemAccessRule(sd, account.ToList(), accessRights, accessTypeValue, inheritance, propagation);
if (passThru == true)
{
@ -185,5 +224,66 @@ namespace NTFSSecurity
{
base.EndProcessing();
}
// A bad value named on the command line is already bound when BeginProcessing runs, so it is reported there
// as a terminating error, the way the binder itself would have reported it for a typed parameter.
private void ValidateNamedValue<T>(object value, string parameterName)
{
if (value == null)
{
return;
}
try
{
ParameterConversion.Convert<T>(value, parameterName);
}
catch (ArgumentException ex)
{
ThrowTerminatingError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, value));
}
}
// A bad value that a piped object bound converts here, once per record. A null rawValue means the property
// was not bound for this record, so the parameter's own default applies. A bad value is reported as one
// non-terminating error per path or per descriptor, and the caller leaves the record alone and returns.
private bool TryConvertValue<T>(object rawValue, string parameterName, T defaultValue, out T result)
{
if (rawValue == null)
{
result = defaultValue;
return true;
}
try
{
result = ParameterConversion.Convert<T>(rawValue, parameterName);
return true;
}
catch (ArgumentException ex)
{
result = defaultValue;
WriteInvalidParameterValueErrors(ex);
return false;
}
}
private void WriteInvalidParameterValueErrors(Exception ex)
{
if (ParameterSetName.StartsWith("Path"))
{
foreach (var path in paths)
{
WriteError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, path));
}
}
else
{
foreach (var sd in securityDescriptors)
{
WriteError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, sd));
}
}
}
}
}

128
NTFSSecurity/AccessCmdlets/RemoveAccess.cs

@ -13,10 +13,10 @@ namespace NTFSSecurity
{
private IdentityReference2[] account;
private FileSystemRights2 accessRights;
private AccessControlType accessType = AccessControlType.Allow;
private InheritanceFlags inheritanceFlags = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
private PropagationFlags propagationFlags = PropagationFlags.None;
private ApplyTo appliesTo = ApplyTo.ThisFolderSubfoldersAndFiles;
private object accessType;
private object inheritanceFlags;
private object propagationFlags;
private object appliesTo;
private bool removeSpecific;
private bool passThru;
@ -66,7 +66,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true)]
[Alias("AccessControlType")]
public AccessControlType AccessType
[ArgumentCompleter(typeof(AccessControlTypeArgumentCompleter))]
public object AccessType
{
get { return accessType; }
set { accessType = value; }
@ -74,7 +75,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
public InheritanceFlags InheritanceFlags
[ArgumentCompleter(typeof(InheritanceFlagsArgumentCompleter))]
public object InheritanceFlags
{
get { return inheritanceFlags; }
set { inheritanceFlags = value; }
@ -82,7 +84,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
public PropagationFlags PropagationFlags
[ArgumentCompleter(typeof(PropagationFlagsArgumentCompleter))]
public object PropagationFlags
{
get { return propagationFlags; }
set { propagationFlags = value; }
@ -91,7 +94,8 @@ namespace NTFSSecurity
// Mandatory, so that a command without it resolves to the Complex parameter set, also for -SecurityDescriptor.
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathSimple")]
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "SDSimple")]
public ApplyTo AppliesTo
[ArgumentCompleter(typeof(ApplyToArgumentCompleter))]
public object AppliesTo
{
get { return appliesTo; }
set { appliesTo = value; }
@ -116,19 +120,54 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
// Checked here, before privileges are enabled, because only a value that came from the command line is
// already bound at this point; a bad value from the pipeline is handled per record in ProcessRecord.
ValidateNamedValue<AccessControlType>(accessType, "AccessType");
ValidateNamedValue<InheritanceFlags>(inheritanceFlags, "InheritanceFlags");
ValidateNamedValue<PropagationFlags>(propagationFlags, "PropagationFlags");
ValidateNamedValue<ApplyTo>(appliesTo, "AppliesTo");
base.BeginProcessing();
}
protected override void ProcessRecord()
{
AccessControlType accessTypeValue;
if (!TryConvertValue(accessType, "AccessType", AccessControlType.Allow, out accessTypeValue))
{
return;
}
// The flags of this record are locals: the fields behind -InheritanceFlags and -PropagationFlags keep the value of the
// previous piped object when the next one binds neither, so the scope that -AppliesTo converts to must not stay in them.
var inheritance = inheritanceFlags;
var propagation = propagationFlags;
var inheritance = System.Security.AccessControl.InheritanceFlags.ContainerInherit | System.Security.AccessControl.InheritanceFlags.ObjectInherit;
var propagation = System.Security.AccessControl.PropagationFlags.None;
if (ParameterSetName.EndsWith("Simple"))
{
FileSystemSecurity2.ConvertToFileSystemFlags(appliesTo, out inheritance, out propagation);
ApplyTo appliesToValue;
if (!TryConvertValue(appliesTo, "AppliesTo", default(ApplyTo), out appliesToValue))
{
return;
}
FileSystemSecurity2.ConvertToFileSystemFlags(appliesToValue, out inheritance, out propagation);
}
else
{
InheritanceFlags inheritanceValue;
if (!TryConvertValue(inheritanceFlags, "InheritanceFlags", inheritance, out inheritanceValue))
{
return;
}
inheritance = inheritanceValue;
PropagationFlags propagationValue;
if (!TryConvertValue(propagationFlags, "PropagationFlags", propagation, out propagationValue))
{
return;
}
propagation = propagationValue;
}
if (ParameterSetName.StartsWith("Path"))
@ -149,7 +188,7 @@ namespace NTFSSecurity
try
{
FileSystemAccessRule2.RemoveFileSystemAccessRule(item, account.ToList(), accessRights, accessType, inheritance, propagation, removeSpecific);
FileSystemAccessRule2.RemoveFileSystemAccessRule(item, account.ToList(), accessRights, accessTypeValue, inheritance, propagation, removeSpecific);
}
catch (UnauthorizedAccessException)
{
@ -157,7 +196,7 @@ namespace NTFSSecurity
{
InvokeAsOwner(item, path, () =>
{
FileSystemAccessRule2.RemoveFileSystemAccessRule(item, account.ToList(), accessRights, accessType, inheritance, propagation, removeSpecific);
FileSystemAccessRule2.RemoveFileSystemAccessRule(item, account.ToList(), accessRights, accessTypeValue, inheritance, propagation, removeSpecific);
});
}
catch (Exception ex2)
@ -182,7 +221,7 @@ namespace NTFSSecurity
{
foreach (var sd in securityDescriptors)
{
FileSystemAccessRule2.RemoveFileSystemAccessRule(sd, account.ToList(), accessRights, accessType, inheritance, propagation, removeSpecific);
FileSystemAccessRule2.RemoveFileSystemAccessRule(sd, account.ToList(), accessRights, accessTypeValue, inheritance, propagation, removeSpecific);
if (passThru == true)
{
@ -196,5 +235,66 @@ namespace NTFSSecurity
{
base.EndProcessing();
}
// A bad value named on the command line is already bound when BeginProcessing runs, so it is reported there
// as a terminating error, the way the binder itself would have reported it for a typed parameter.
private void ValidateNamedValue<T>(object value, string parameterName)
{
if (value == null)
{
return;
}
try
{
ParameterConversion.Convert<T>(value, parameterName);
}
catch (ArgumentException ex)
{
ThrowTerminatingError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, value));
}
}
// A bad value that a piped object bound converts here, once per record. A null rawValue means the property
// was not bound for this record, so the parameter's own default applies. A bad value is reported as one
// non-terminating error per path or per descriptor, and the caller leaves the record alone and returns.
private bool TryConvertValue<T>(object rawValue, string parameterName, T defaultValue, out T result)
{
if (rawValue == null)
{
result = defaultValue;
return true;
}
try
{
result = ParameterConversion.Convert<T>(rawValue, parameterName);
return true;
}
catch (ArgumentException ex)
{
result = defaultValue;
WriteInvalidParameterValueErrors(ex);
return false;
}
}
private void WriteInvalidParameterValueErrors(Exception ex)
{
if (ParameterSetName.StartsWith("Path"))
{
foreach (var path in paths)
{
WriteError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, path));
}
}
else
{
foreach (var sd in securityDescriptors)
{
WriteError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, sd));
}
}
}
}
}

128
NTFSSecurity/AuditCmdlets/AddAudit.cs

@ -13,10 +13,10 @@ namespace NTFSSecurity
{
private IdentityReference2[] account;
private FileSystemRights2 accessRights;
private AuditFlags auditFlags = AuditFlags.Failure | AuditFlags.Success;
private InheritanceFlags inheritanceFlags = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
private PropagationFlags propagationFlags = PropagationFlags.None;
private ApplyTo appliesTo = ApplyTo.ThisFolderSubfoldersAndFiles;
private object auditFlags;
private object inheritanceFlags;
private object propagationFlags;
private object appliesTo;
private bool passThru;
[Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathSimple")]
@ -64,7 +64,8 @@ namespace NTFSSecurity
}
[Parameter(ValueFromPipelineByPropertyName = true)]
public AuditFlags AuditFlags
[ArgumentCompleter(typeof(AuditFlagsArgumentCompleter))]
public object AuditFlags
{
get { return auditFlags; }
set { auditFlags = value; }
@ -72,7 +73,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
public InheritanceFlags InheritanceFlags
[ArgumentCompleter(typeof(InheritanceFlagsArgumentCompleter))]
public object InheritanceFlags
{
get { return inheritanceFlags; }
set { inheritanceFlags = value; }
@ -80,7 +82,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
public PropagationFlags PropagationFlags
[ArgumentCompleter(typeof(PropagationFlagsArgumentCompleter))]
public object PropagationFlags
{
get { return propagationFlags; }
set { propagationFlags = value; }
@ -89,7 +92,8 @@ namespace NTFSSecurity
// Mandatory, so that a command without it resolves to the Complex parameter set, also for -SecurityDescriptor.
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathSimple")]
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "SDSimple")]
public ApplyTo AppliesTo
[ArgumentCompleter(typeof(ApplyToArgumentCompleter))]
public object AppliesTo
{
get { return appliesTo; }
set { appliesTo = value; }
@ -104,19 +108,54 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
// Checked here, before privileges are enabled, because only a value that came from the command line is
// already bound at this point; a bad value from the pipeline is handled per record in ProcessRecord.
ValidateNamedValue<AuditFlags>(auditFlags, "AuditFlags");
ValidateNamedValue<InheritanceFlags>(inheritanceFlags, "InheritanceFlags");
ValidateNamedValue<PropagationFlags>(propagationFlags, "PropagationFlags");
ValidateNamedValue<ApplyTo>(appliesTo, "AppliesTo");
base.BeginProcessing();
}
protected override void ProcessRecord()
{
AuditFlags auditFlagsValue;
if (!TryConvertValue(auditFlags, "AuditFlags", System.Security.AccessControl.AuditFlags.Failure | System.Security.AccessControl.AuditFlags.Success, out auditFlagsValue))
{
return;
}
// The flags of this record are locals: the fields behind -InheritanceFlags and -PropagationFlags keep the value of the
// previous piped object when the next one binds neither, so the scope that -AppliesTo converts to must not stay in them.
var inheritance = inheritanceFlags;
var propagation = propagationFlags;
var inheritance = System.Security.AccessControl.InheritanceFlags.ContainerInherit | System.Security.AccessControl.InheritanceFlags.ObjectInherit;
var propagation = System.Security.AccessControl.PropagationFlags.None;
if (ParameterSetName.EndsWith("Simple"))
{
FileSystemSecurity2.ConvertToFileSystemFlags(appliesTo, out inheritance, out propagation);
ApplyTo appliesToValue;
if (!TryConvertValue(appliesTo, "AppliesTo", default(ApplyTo), out appliesToValue))
{
return;
}
FileSystemSecurity2.ConvertToFileSystemFlags(appliesToValue, out inheritance, out propagation);
}
else
{
InheritanceFlags inheritanceValue;
if (!TryConvertValue(inheritanceFlags, "InheritanceFlags", inheritance, out inheritanceValue))
{
return;
}
inheritance = inheritanceValue;
PropagationFlags propagationValue;
if (!TryConvertValue(propagationFlags, "PropagationFlags", propagation, out propagationValue))
{
return;
}
propagation = propagationValue;
}
if (ParameterSetName.StartsWith("Path"))
@ -137,7 +176,7 @@ namespace NTFSSecurity
try
{
FileSystemAuditRule2.AddFileSystemAuditRule(item, account.ToList(), accessRights, auditFlags, inheritance, propagation);
FileSystemAuditRule2.AddFileSystemAuditRule(item, account.ToList(), accessRights, auditFlagsValue, inheritance, propagation);
}
catch (UnauthorizedAccessException)
{
@ -145,7 +184,7 @@ namespace NTFSSecurity
{
InvokeAsOwner(item, path, () =>
{
FileSystemAuditRule2.AddFileSystemAuditRule(item, account.ToList(), accessRights, auditFlags, inheritance, propagation);
FileSystemAuditRule2.AddFileSystemAuditRule(item, account.ToList(), accessRights, auditFlagsValue, inheritance, propagation);
});
}
catch (Exception ex2)
@ -175,7 +214,7 @@ namespace NTFSSecurity
continue;
}
FileSystemAuditRule2.AddFileSystemAuditRule(sd, account.ToList(), accessRights, auditFlags, inheritance, propagation);
FileSystemAuditRule2.AddFileSystemAuditRule(sd, account.ToList(), accessRights, auditFlagsValue, inheritance, propagation);
if (passThru == true)
{
@ -189,5 +228,66 @@ namespace NTFSSecurity
{
base.EndProcessing();
}
// A bad value named on the command line is already bound when BeginProcessing runs, so it is reported there
// as a terminating error, the way the binder itself would have reported it for a typed parameter.
private void ValidateNamedValue<T>(object value, string parameterName)
{
if (value == null)
{
return;
}
try
{
ParameterConversion.Convert<T>(value, parameterName);
}
catch (ArgumentException ex)
{
ThrowTerminatingError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, value));
}
}
// A bad value that a piped object bound converts here, once per record. A null rawValue means the property
// was not bound for this record, so the parameter's own default applies. A bad value is reported as one
// non-terminating error per path or per descriptor, and the caller leaves the record alone and returns.
private bool TryConvertValue<T>(object rawValue, string parameterName, T defaultValue, out T result)
{
if (rawValue == null)
{
result = defaultValue;
return true;
}
try
{
result = ParameterConversion.Convert<T>(rawValue, parameterName);
return true;
}
catch (ArgumentException ex)
{
result = defaultValue;
WriteInvalidParameterValueErrors(ex);
return false;
}
}
private void WriteInvalidParameterValueErrors(Exception ex)
{
if (ParameterSetName.StartsWith("Path"))
{
foreach (var path in paths)
{
WriteError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, path));
}
}
else
{
foreach (var sd in securityDescriptors)
{
WriteError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, sd));
}
}
}
}
}

128
NTFSSecurity/AuditCmdlets/RemoveAudit.cs

@ -13,10 +13,10 @@ namespace NTFSSecurity
{
private IdentityReference2[] account;
private FileSystemRights2 accessRights;
private AuditFlags auditFlags = AuditFlags.Failure | AuditFlags.Success;
private InheritanceFlags inheritanceFlags = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
private PropagationFlags propagationFlags = PropagationFlags.None;
private ApplyTo appliesTo = ApplyTo.ThisFolderSubfoldersAndFiles;
private object auditFlags;
private object inheritanceFlags;
private object propagationFlags;
private object appliesTo;
private bool removeSpecific;
private bool passThru;
@ -65,7 +65,8 @@ namespace NTFSSecurity
}
[Parameter(ValueFromPipelineByPropertyName = true)]
public AuditFlags AuditFlags
[ArgumentCompleter(typeof(AuditFlagsArgumentCompleter))]
public object AuditFlags
{
get { return auditFlags; }
set { auditFlags = value; }
@ -73,7 +74,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
public InheritanceFlags InheritanceFlags
[ArgumentCompleter(typeof(InheritanceFlagsArgumentCompleter))]
public object InheritanceFlags
{
get { return inheritanceFlags; }
set { inheritanceFlags = value; }
@ -81,7 +83,8 @@ namespace NTFSSecurity
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
public PropagationFlags PropagationFlags
[ArgumentCompleter(typeof(PropagationFlagsArgumentCompleter))]
public object PropagationFlags
{
get { return propagationFlags; }
set { propagationFlags = value; }
@ -90,7 +93,8 @@ namespace NTFSSecurity
// Mandatory, so that a command without it resolves to the Complex parameter set, also for -SecurityDescriptor.
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathSimple")]
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "SDSimple")]
public ApplyTo AppliesTo
[ArgumentCompleter(typeof(ApplyToArgumentCompleter))]
public object AppliesTo
{
get { return appliesTo; }
set { appliesTo = value; }
@ -115,19 +119,54 @@ namespace NTFSSecurity
protected override void BeginProcessing()
{
// Checked here, before privileges are enabled, because only a value that came from the command line is
// already bound at this point; a bad value from the pipeline is handled per record in ProcessRecord.
ValidateNamedValue<AuditFlags>(auditFlags, "AuditFlags");
ValidateNamedValue<InheritanceFlags>(inheritanceFlags, "InheritanceFlags");
ValidateNamedValue<PropagationFlags>(propagationFlags, "PropagationFlags");
ValidateNamedValue<ApplyTo>(appliesTo, "AppliesTo");
base.BeginProcessing();
}
protected override void ProcessRecord()
{
AuditFlags auditFlagsValue;
if (!TryConvertValue(auditFlags, "AuditFlags", System.Security.AccessControl.AuditFlags.Failure | System.Security.AccessControl.AuditFlags.Success, out auditFlagsValue))
{
return;
}
// The flags of this record are locals: the fields behind -InheritanceFlags and -PropagationFlags keep the value of the
// previous piped object when the next one binds neither, so the scope that -AppliesTo converts to must not stay in them.
var inheritance = inheritanceFlags;
var propagation = propagationFlags;
var inheritance = System.Security.AccessControl.InheritanceFlags.ContainerInherit | System.Security.AccessControl.InheritanceFlags.ObjectInherit;
var propagation = System.Security.AccessControl.PropagationFlags.None;
if (ParameterSetName.EndsWith("Simple"))
{
FileSystemSecurity2.ConvertToFileSystemFlags(appliesTo, out inheritance, out propagation);
ApplyTo appliesToValue;
if (!TryConvertValue(appliesTo, "AppliesTo", default(ApplyTo), out appliesToValue))
{
return;
}
FileSystemSecurity2.ConvertToFileSystemFlags(appliesToValue, out inheritance, out propagation);
}
else
{
InheritanceFlags inheritanceValue;
if (!TryConvertValue(inheritanceFlags, "InheritanceFlags", inheritance, out inheritanceValue))
{
return;
}
inheritance = inheritanceValue;
PropagationFlags propagationValue;
if (!TryConvertValue(propagationFlags, "PropagationFlags", propagation, out propagationValue))
{
return;
}
propagation = propagationValue;
}
if (ParameterSetName.StartsWith("Path"))
@ -148,7 +187,7 @@ namespace NTFSSecurity
try
{
FileSystemAuditRule2.RemoveFileSystemAuditRule(item, account.ToList(), accessRights, auditFlags, inheritance, propagation, removeSpecific);
FileSystemAuditRule2.RemoveFileSystemAuditRule(item, account.ToList(), accessRights, auditFlagsValue, inheritance, propagation, removeSpecific);
}
catch (UnauthorizedAccessException)
{
@ -156,7 +195,7 @@ namespace NTFSSecurity
{
InvokeAsOwner(item, path, () =>
{
FileSystemAuditRule2.RemoveFileSystemAuditRule(item, account.ToList(), accessRights, auditFlags, inheritance, propagation, removeSpecific);
FileSystemAuditRule2.RemoveFileSystemAuditRule(item, account.ToList(), accessRights, auditFlagsValue, inheritance, propagation, removeSpecific);
});
}
catch (Exception ex2)
@ -186,7 +225,7 @@ namespace NTFSSecurity
continue;
}
FileSystemAuditRule2.RemoveFileSystemAuditRule(sd, account.ToList(), accessRights, auditFlags, inheritance, propagation, removeSpecific);
FileSystemAuditRule2.RemoveFileSystemAuditRule(sd, account.ToList(), accessRights, auditFlagsValue, inheritance, propagation, removeSpecific);
if (passThru == true)
{
@ -200,5 +239,66 @@ namespace NTFSSecurity
{
base.EndProcessing();
}
// A bad value named on the command line is already bound when BeginProcessing runs, so it is reported there
// as a terminating error, the way the binder itself would have reported it for a typed parameter.
private void ValidateNamedValue<T>(object value, string parameterName)
{
if (value == null)
{
return;
}
try
{
ParameterConversion.Convert<T>(value, parameterName);
}
catch (ArgumentException ex)
{
ThrowTerminatingError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, value));
}
}
// A bad value that a piped object bound converts here, once per record. A null rawValue means the property
// was not bound for this record, so the parameter's own default applies. A bad value is reported as one
// non-terminating error per path or per descriptor, and the caller leaves the record alone and returns.
private bool TryConvertValue<T>(object rawValue, string parameterName, T defaultValue, out T result)
{
if (rawValue == null)
{
result = defaultValue;
return true;
}
try
{
result = ParameterConversion.Convert<T>(rawValue, parameterName);
return true;
}
catch (ArgumentException ex)
{
result = defaultValue;
WriteInvalidParameterValueErrors(ex);
return false;
}
}
private void WriteInvalidParameterValueErrors(Exception ex)
{
if (ParameterSetName.StartsWith("Path"))
{
foreach (var path in paths)
{
WriteError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, path));
}
}
else
{
foreach (var sd in securityDescriptors)
{
WriteError(new ErrorRecord(ex, "InvalidParameterValue", ErrorCategory.InvalidArgument, sd));
}
}
}
}
}

87
NTFSSecurity/EnumArgumentCompleters.cs

@ -0,0 +1,87 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Security.AccessControl;
using Security2;
namespace NTFSSecurity
{
// Completes the names of an enumeration for a parameter that is declared as object (Decision 27, fix B) and so
// does not get the completion that a parameter of an enumeration type gets from PowerShell itself.
internal static class EnumArgumentCompletion
{
/// <summary>
/// Returns the names of the enumeration that start with the word the user already typed, compared without
/// regard to case, in the order Enum.GetNames returns them.
/// </summary>
/// <param name="enumType">The enumeration to complete the names of.</param>
/// <param name="wordToComplete">The word the user already typed; empty or null completes every name.</param>
internal static IEnumerable<CompletionResult> Complete(Type enumType, string wordToComplete)
{
var word = wordToComplete ?? string.Empty;
var results = new List<CompletionResult>();
foreach (var name in Enum.GetNames(enumType))
{
if (name.StartsWith(word, StringComparison.OrdinalIgnoreCase))
{
results.Add(new CompletionResult(name, name, CompletionResultType.ParameterValue, name));
}
}
return results;
}
}
/// <summary>Completes the names of System.Security.AccessControl.AccessControlType for -AccessType.</summary>
public class AccessControlTypeArgumentCompleter : IArgumentCompleter
{
public IEnumerable<CompletionResult> CompleteArgument(
string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters)
{
return EnumArgumentCompletion.Complete(typeof(AccessControlType), wordToComplete);
}
}
/// <summary>Completes the names of System.Security.AccessControl.AuditFlags for -AuditFlags.</summary>
public class AuditFlagsArgumentCompleter : IArgumentCompleter
{
public IEnumerable<CompletionResult> CompleteArgument(
string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters)
{
return EnumArgumentCompletion.Complete(typeof(AuditFlags), wordToComplete);
}
}
/// <summary>Completes the names of System.Security.AccessControl.InheritanceFlags for -InheritanceFlags.</summary>
public class InheritanceFlagsArgumentCompleter : IArgumentCompleter
{
public IEnumerable<CompletionResult> CompleteArgument(
string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters)
{
return EnumArgumentCompletion.Complete(typeof(InheritanceFlags), wordToComplete);
}
}
/// <summary>Completes the names of System.Security.AccessControl.PropagationFlags for -PropagationFlags.</summary>
public class PropagationFlagsArgumentCompleter : IArgumentCompleter
{
public IEnumerable<CompletionResult> CompleteArgument(
string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters)
{
return EnumArgumentCompletion.Complete(typeof(PropagationFlags), wordToComplete);
}
}
/// <summary>Completes the names of Security2.ApplyTo for -AppliesTo.</summary>
public class ApplyToArgumentCompleter : IArgumentCompleter
{
public IEnumerable<CompletionResult> CompleteArgument(
string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters)
{
return EnumArgumentCompletion.Complete(typeof(ApplyTo), wordToComplete);
}
}
}

2
NTFSSecurity/NTFSSecurity.csproj

@ -73,6 +73,8 @@
<Compile Include="AuditCmdlets\RemoveAudit.cs" />
<Compile Include="BaseCmdlets.cs" />
<Compile Include="CodeMembers.cs" />
<Compile Include="EnumArgumentCompleters.cs" />
<Compile Include="ParameterConversion.cs" />
<Compile Include="InheritanceCmdlets\SetInheritance.cs" />
<Compile Include="InheritanceCmdlets\DisableAccessInheritance.cs" />
<Compile Include="InheritanceCmdlets\DisableAuditInheritance.cs" />

67
NTFSSecurity/ParameterConversion.cs

@ -0,0 +1,67 @@
using System;
using System.Management.Automation;
namespace NTFSSecurity
{
/// <summary>
/// Converts the raw value of a parameter that is declared as object back into the enumeration the parameter
/// describes, the way the PowerShell binder converts an argument for a typed parameter. AccessType, AuditFlags,
/// InheritanceFlags, PropagationFlags, and AppliesTo of the access and audit cmdlets are declared as object
/// (Decision 27, fix B), so that the binder does not silently skip a piped property it cannot convert and keep
/// the parameter at its default; this class gives the cmdlet the same conversion, with a message it can report
/// as its own error instead of a binding error. This class writes nothing; the cmdlet writes the error.
/// </summary>
internal static class ParameterConversion
{
/// <summary>
/// Converts the raw value of a parameter to T, the way the PowerShell binder converts an argument for a
/// parameter of that type: names in any case, a comma-separated list for a flags enumeration, a number, or
/// an enumeration instance.
/// </summary>
/// <param name="value">
/// The raw value of the parameter. Not null: the caller keeps the default of the parameter for null instead
/// of calling this method, because null means that the parameter was not specified.
/// </param>
/// <param name="parameterName">The name of the parameter, without the leading dash, for the message.</param>
/// <returns>The converted value.</returns>
/// <exception cref="ArgumentException">
/// The value is blank (an empty or a white-space-only string, the usual blank CSV cell) or the conversion
/// failed. The message names the value, unless it is blank, the parameter, and the valid names of T; the
/// inner exception of a failed conversion is the error that LanguagePrimitives.ConvertTo raised.
/// </exception>
internal static T Convert<T>(object value, string parameterName)
{
var text = value as string;
if (text != null && string.IsNullOrWhiteSpace(text))
{
throw new ArgumentException(string.Format(
"The value for parameter -{0} is blank. {1}", parameterName, DescribeValidValues(typeof(T))));
}
try
{
return (T)LanguagePrimitives.ConvertTo(value, typeof(T));
}
catch (Exception ex)
{
throw new ArgumentException(
string.Format("The value '{0}' for parameter -{1} is not valid. {2}", value, parameterName, DescribeValidValues(typeof(T))),
ex);
}
}
// The valid names of the enumeration, in declaration order, with a note for a flags enumeration that a
// combination of them is written separated with commas.
private static string DescribeValidValues(Type enumType)
{
var names = string.Join(", ", Enum.GetNames(enumType));
if (enumType.IsDefined(typeof(FlagsAttribute), false))
{
return string.Format("Valid values: {0}. Combine several with a comma.", names);
}
return string.Format("Valid values: {0}.", names);
}
}
}

337
Tests/ParameterSets.Access.Tests.ps1

@ -201,9 +201,10 @@ Describe 'Add-NTFSAccess' {
$scopes -join '|' | Should -BeExactly 'None/None|ContainerInherit/None|ContainerInherit, ObjectInherit/InheritOnly'
}
# A blank cell that Import-Csv reads is an empty string: not $null, and not a scope name. The binder cannot convert it, so
# PathSimple does not bind, and the row gets the default scope, as an object without the property does.
It 'Should give the row of a CSV file whose AppliesTo cell is blank the default scope, and the other rows their own' {
# A blank cell that Import-Csv reads is an empty string: not $null, and not a scope name. Since -AppliesTo is
# now an object parameter (Decision 27, fix B), the blank string still binds it, which selects PathSimple,
# so the row gets an InvalidParameterValue error instead of the default scope; the other rows are unaffected.
It 'Should write an InvalidParameterValue error for the row of a CSV file whose AppliesTo cell is blank, leave its folder alone, and still give the other rows their own scope' {
$folders = @('AddCsvBlankFirst', 'AddCsvBlankCell', 'AddCsvBlankLast' |
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory })
$csv = Join-Path -Path $sandbox -ChildPath 'AddCsvBlank.csv'
@ -215,10 +216,15 @@ Describe 'Add-NTFSAccess' {
('"{0}",S-1-5-32-546,ReadData,SubfoldersAndFilesOnly' -f $folders[2])
)
Import-Csv -LiteralPath $csv | Add-NTFSAccess -ErrorAction Stop
Import-Csv -LiteralPath $csv | Add-NTFSAccess -ErrorVariable addErrors -ErrorAction SilentlyContinue
$addErrors | Should -HaveCount 1
$addErrors[0].FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$addErrors[0].CategoryInfo.Category | Should -Be 'InvalidArgument'
$addErrors[0].TargetObject | Should -Be $folders[1]
$addErrors[0].Exception.Message | Should -Match 'AppliesTo'
$scopes = $folders | ForEach-Object -Process { Get-TestAccountScope -Acl (Get-Acl -LiteralPath $_) -Account 'S-1-5-32-546' }
$scopes -join '|' | Should -BeExactly 'None/None|ContainerInherit, ObjectInherit/None|ContainerInherit, ObjectInherit/InheritOnly'
$scopes -join '|' | Should -BeExactly 'None/None||ContainerInherit, ObjectInherit/InheritOnly'
}
# $null cannot be bound at all (ConvertFrom-Csv makes it of a trailing blank cell): the object fails, loudly, and the cmdlet goes
@ -1223,6 +1229,327 @@ Describe 'Get-NTFSEffectiveAccess' {
}
}
# Decision 27 (fix B): AccessType, InheritanceFlags, PropagationFlags, and AppliesTo of Add-NTFSAccess and
# Remove-NTFSAccess are object parameters, converted in the cmdlet with LanguagePrimitives.ConvertTo, so that a
# piped value the binder cannot convert to the enumeration is an error instead of a silently kept default.
Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationFlags, and AppliesTo' {
BeforeDiscovery {
$accessBadShapes = @(
@{ Shape = 'a misspelled name'; Value = 'Typoo'; ValueText = 'Typoo' }
@{ Shape = 'a blank string'; Value = ''; ValueText = $null }
@{ Shape = 'a white-space string'; Value = ' '; ValueText = $null }
@{ Shape = 'an undefined number'; Value = 99; ValueText = '99' }
@{ Shape = 'a hashtable'; Value = @{ Unrelated = 1 }; ValueText = $null }
)
$accessTypeBadShapes = @(
@{ Shape = 'a misspelled name'; Value = 'Denyy'; ValueText = 'Denyy' }
@{ Shape = 'a blank string'; Value = ''; ValueText = $null }
@{ Shape = 'a white-space string'; Value = ' '; ValueText = $null }
@{ Shape = 'an undefined number'; Value = 99; ValueText = '99' }
@{ Shape = 'a hashtable'; Value = @{ Unrelated = 1 }; ValueText = $null }
)
# The cross product of the two cmdlets that share these parameters and every bad shape: 2 * (5 + 3 * 5) = 40 rows.
$accessInvalidParameterCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') {
foreach ($shapeCase in $accessTypeBadShapes) {
@{ Cmdlet = $cmdlet; Parameter = 'AccessType'; Shape = $shapeCase.Shape; Value = $shapeCase.Value; ValueText = $shapeCase.ValueText }
}
foreach ($parameterName in 'InheritanceFlags', 'PropagationFlags', 'AppliesTo') {
foreach ($shapeCase in $accessBadShapes) {
@{ Cmdlet = $cmdlet; Parameter = $parameterName; Shape = $shapeCase.Shape; Value = $shapeCase.Value; ValueText = $shapeCase.ValueText }
}
}
}
$accessSdInvalidParameterCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') {
@{ Cmdlet = $cmdlet; Parameter = 'AccessType'; Value = 'Denyy'; ValueText = 'Denyy' }
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; Value = 'Typoo'; ValueText = 'Typoo' }
}
$accessNoCarryOverCases = @(
@{ Cmdlet = 'Add-NTFSAccess'; Parameter = 'AccessType'; Value = 'Denyy' }
@{ Cmdlet = 'Add-NTFSAccess'; Parameter = 'InheritanceFlags'; Value = 'Typoo' }
@{ Cmdlet = 'Add-NTFSAccess'; Parameter = 'PropagationFlags'; Value = 'Typoo' }
@{ Cmdlet = 'Add-NTFSAccess'; Parameter = 'AppliesTo'; Value = 'Typoo' }
)
$accessNamedInvalidParameterCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') {
@{ Cmdlet = $cmdlet; Parameter = 'AccessType'; Value = 'Denyy' }
@{ Cmdlet = $cmdlet; Parameter = 'InheritanceFlags'; Value = 'Typoo' }
@{ Cmdlet = $cmdlet; Parameter = 'PropagationFlags'; Value = 'Typoo' }
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; Value = 'Typoo' }
}
$accessValidAccessTypeShapeCases = @(
@{ Description = 'the exact name'; Value = 'Deny' }
@{ Description = 'a different case'; Value = 'deny' }
@{ Description = 'the enum value'; Value = [System.Security.AccessControl.AccessControlType]::Deny }
@{ Description = 'an integer'; Value = 1 }
)
$accessValidFlagsShapeCases = @(
@{ Description = 'a comma list without spaces'; Value = 'ContainerInherit,ObjectInherit' }
@{ Description = 'a comma list with spaces'; Value = 'ContainerInherit, ObjectInherit' }
@{ Description = 'the enum value'; Value = [System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit' }
)
$accessAmbiguousValueCases = @(
@{ AppliesToShape = 'a string'; AppliesToIsEnum = $false; FlagShape = 'a string'; FlagIsEnum = $false }
@{ AppliesToShape = 'an enum value'; AppliesToIsEnum = $true; FlagShape = 'a string'; FlagIsEnum = $false }
@{ AppliesToShape = 'a string'; AppliesToIsEnum = $false; FlagShape = 'an enum value'; FlagIsEnum = $true }
@{ AppliesToShape = 'an enum value'; AppliesToIsEnum = $true; FlagShape = 'an enum value'; FlagIsEnum = $true }
)
$accessCompleterCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') {
@{ Cmdlet = $cmdlet; Parameter = 'AccessType'; EnumTypeName = 'System.Security.AccessControl.AccessControlType' }
@{ Cmdlet = $cmdlet; Parameter = 'InheritanceFlags'; EnumTypeName = 'System.Security.AccessControl.InheritanceFlags' }
@{ Cmdlet = $cmdlet; Parameter = 'PropagationFlags'; EnumTypeName = 'System.Security.AccessControl.PropagationFlags' }
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; EnumTypeName = 'Security2.ApplyTo' }
}
}
Context 'A bad value bound from the pipeline' {
It 'Should write one InvalidParameterValue error for <Cmdlet> -<Parameter> (<Shape>), change nothing, and still process the next object' -ForEach $accessInvalidParameterCases {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidValue' -Directory
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidValueNext' -Directory
$before = (Get-Acl -LiteralPath $path).Sddl
$extra = @{ }
$extra[$Parameter] = $Value
$rows = @(
[pscustomobject] (@{ Path = $path; Account = 'S-1-1-0'; AccessRights = 'ReadData' } + $extra)
[pscustomobject]@{ Path = $next; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
)
$result = @($rows | & $Cmdlet -PassThru -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue)
$pipelineErrors | Should -HaveCount 1
$pipelineErrors[0].FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$pipelineErrors[0].CategoryInfo.Category | Should -Be 'InvalidArgument'
$pipelineErrors[0].TargetObject | Should -Be $path
$pipelineErrors[0].Exception.Message | Should -Match ([regex]::Escape($Parameter))
if ($ValueText) {
$pipelineErrors[0].Exception.Message | Should -Match ([regex]::Escape($ValueText))
}
(Get-Acl -LiteralPath $path).Sddl | Should -BeExactly $before
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_.FullName | Should -Be $next }
}
It 'Should write one InvalidParameterValue error per descriptor for <Cmdlet> -<Parameter> bound to a piped descriptor, and leave the descriptors unchanged' -ForEach $accessSdInvalidParameterCases {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidSdFirst' -Directory
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidSdSecond' -Directory
$descriptors = @(Get-NTFSSecurityDescriptor -Path $first, $second -ErrorAction Stop)
$before = $descriptors[0].SecurityDescriptor.GetSecurityDescriptorSddlForm('Access')
$extra = @{ }
$extra[$Parameter] = $Value
$rows = @(
[pscustomobject] (@{ SecurityDescriptor = $descriptors[0]; Account = 'S-1-1-0'; AccessRights = 'ReadData' } + $extra)
[pscustomobject]@{ SecurityDescriptor = $descriptors[1]; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
)
$result = @($rows | & $Cmdlet -PassThru -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue)
$pipelineErrors | Should -HaveCount 1
$pipelineErrors[0].FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$pipelineErrors[0].CategoryInfo.Category | Should -Be 'InvalidArgument'
$pipelineErrors[0].TargetObject | Should -Be $descriptors[0]
$descriptors[0].SecurityDescriptor.GetSecurityDescriptorSddlForm('Access') | Should -BeExactly $before
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_.FullName | Should -Be $second }
}
}
Context 'Continuation and no carry-over across piped objects' {
It 'Should apply both good objects when a bad AccessType value is between them, in the order of the rows' {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueFirst' -Directory
$middle = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueMiddle' -Directory
$last = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueLast' -Directory
$rows = @(
[pscustomobject]@{ Path = $first; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AccessType = 'Deny' }
[pscustomobject]@{ Path = $middle; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AccessType = 'Denyy' }
[pscustomobject]@{ Path = $last; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AccessType = 'Deny' }
)
$result = @($rows | Add-NTFSAccess -PassThru -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue)
$pipelineErrors | Should -HaveCount 1
$pipelineErrors[0].TargetObject | Should -Be $middle
$firstRule = @((Get-Acl -LiteralPath $first).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$firstRule | Should -HaveCount 1
$firstRule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Deny)
$middleRule = @((Get-Acl -LiteralPath $middle).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$middleRule | Should -BeNullOrEmpty
$lastRule = @((Get-Acl -LiteralPath $last).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$lastRule | Should -HaveCount 1
$lastRule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Deny)
# The rows are applied in order: both surviving entries show up, $middle's does not, and nothing reports twice.
@($result | Where-Object -FilterScript { $_.FullName -eq $first -and $_.Account.Sid -eq 'S-1-1-0' }) | Should -HaveCount 1
@($result | Where-Object -FilterScript { $_.FullName -eq $last -and $_.Account.Sid -eq 'S-1-1-0' }) | Should -HaveCount 1
@($result | Where-Object -FilterScript { $_.FullName -eq $middle -and $_.Account.Sid -eq 'S-1-1-0' }) | Should -BeNullOrEmpty
}
It 'Should give the row without AccessType the default Allow after a preceding Deny row, with no carry-over' {
$denyFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverDeny' -Directory
$defaultFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverDefault' -Directory
@(
[pscustomobject]@{ Path = $denyFolder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AccessType = 'Deny' }
[pscustomobject]@{ Path = $defaultFolder; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
) | Add-NTFSAccess -ErrorAction Stop
$denyRule = @((Get-Acl -LiteralPath $denyFolder).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$denyRule | Should -HaveCount 1
$denyRule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Deny)
$defaultRule = @((Get-Acl -LiteralPath $defaultFolder).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$defaultRule | Should -HaveCount 1
$defaultRule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Allow)
}
It 'Should give the row without <Parameter> its own default after a preceding row with an invalid value, with no carry-over' -ForEach $accessNoCarryOverCases {
$badFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverBad' -Directory
$defaultFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverAfterBad' -Directory
$extra = @{ }
$extra[$Parameter] = $Value
@(
[pscustomobject] (@{ Path = $badFolder; Account = 'S-1-1-0'; AccessRights = 'ReadData' } + $extra)
[pscustomobject]@{ Path = $defaultFolder; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
) | & $Cmdlet -ErrorAction SilentlyContinue
$defaultRule = @((Get-Acl -LiteralPath $defaultFolder).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$defaultRule | Should -HaveCount 1
$defaultRule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Allow)
$defaultRule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit')
$defaultRule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags]::None)
}
}
Context 'Valid values are unchanged for every shape' {
It 'Should add a Deny entry for AccessType given as <Description>' -ForEach $accessValidAccessTypeShapeCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidAccessType' -Directory
[pscustomobject]@{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AccessType = $Value } | Add-NTFSAccess -ErrorAction Stop
$rule = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$rule | Should -HaveCount 1
$rule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Deny)
}
It 'Should keep the default AccessType Allow for a $null value' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidAccessTypeNull' -Directory
[pscustomobject]@{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AccessType = $null } | Add-NTFSAccess -ErrorAction Stop
$rule = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$rule | Should -HaveCount 1
$rule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Allow)
}
It 'Should scope the entry with InheritanceFlags given as <Description>' -ForEach $accessValidFlagsShapeCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidInheritanceFlags' -Directory
[pscustomobject]@{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = $Value; PropagationFlags = 'None' } | Add-NTFSAccess -ErrorAction Stop
$rule = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$rule | Should -HaveCount 1
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit')
}
It 'Should keep the default flags for $null InheritanceFlags and PropagationFlags' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidFlagsNull' -Directory
[pscustomobject]@{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = $null; PropagationFlags = $null } | Add-NTFSAccess -ErrorAction Stop
$rule = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$rule | Should -HaveCount 1
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit')
$rule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags]::None)
}
}
Context 'A bad value named on the command line' {
It 'Should throw a terminating InvalidParameterValue error for <Cmdlet> -<Parameter>, catchable with try/catch, and change nothing' -ForEach $accessNamedInvalidParameterCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedInvalid' -Directory
$before = (Get-Acl -LiteralPath $folder).Sddl
$namedArguments = @{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
$namedArguments[$Parameter] = $Value
$caughtId = $null
try {
& $Cmdlet @namedArguments -ErrorAction Stop
}
catch {
$caughtId = $_.FullyQualifiedErrorId
}
$caughtId | Should -BeLike 'InvalidParameterValue,*'
(Get-Acl -LiteralPath $folder).Sddl | Should -BeExactly $before
}
It 'Should stop before the first piped path when -AccessType is named and bad' {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedBadFirst' -Directory
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedBadSecond' -Directory
$before = @((Get-Acl -LiteralPath $first).Sddl, (Get-Acl -LiteralPath $second).Sddl)
$caughtId = $null
try {
$first, $second | Add-NTFSAccess -Account 'S-1-1-0' -AccessRights ReadData -AccessType Denyy -ErrorAction Stop
}
catch {
$caughtId = $_.FullyQualifiedErrorId
}
$caughtId | Should -BeLike 'InvalidParameterValue,*'
(Get-Acl -LiteralPath $first).Sddl | Should -BeExactly $before[0]
(Get-Acl -LiteralPath $second).Sddl | Should -BeExactly $before[1]
}
}
Context 'Choosing the parameter set with a mix of string and enum values' {
It 'Should reject a piped object whose AppliesTo is <AppliesToShape> combined with InheritanceFlags as <FlagShape> with an AmbiguousParameterSet error' -ForEach $accessAmbiguousValueCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousPipedMix' -Directory
$before = (Get-Acl -LiteralPath $folder).Sddl
$appliesToValue = if ($AppliesToIsEnum) { [Security2.ApplyTo]::ThisFolderOnly } else { 'ThisFolderOnly' }
$flagValue = if ($FlagIsEnum) { [System.Security.AccessControl.InheritanceFlags]::None } else { 'None' }
$object = [pscustomobject]@{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AppliesTo = $appliesToValue; InheritanceFlags = $flagValue }
{ $object | Add-NTFSAccess -ErrorAction Stop } | Should -Throw -ErrorId 'AmbiguousParameterSet*'
(Get-Acl -LiteralPath $folder).Sddl | Should -BeExactly $before
}
It 'Should reject a named call whose -AppliesTo is <AppliesToShape> combined with -PropagationFlags as <FlagShape> with an AmbiguousParameterSet error' -ForEach $accessAmbiguousValueCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousNamedMix' -Directory
$before = (Get-Acl -LiteralPath $folder).Sddl
$appliesToValue = if ($AppliesToIsEnum) { [Security2.ApplyTo]::ThisFolderOnly } else { 'ThisFolderOnly' }
$flagValue = if ($FlagIsEnum) { [System.Security.AccessControl.InheritanceFlags]::None } else { 'None' }
{ Add-NTFSAccess -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -AppliesTo $appliesToValue -PropagationFlags $flagValue -ErrorAction Stop } |
Should -Throw -ErrorId 'AmbiguousParameterSet*'
(Get-Acl -LiteralPath $folder).Sddl | Should -BeExactly $before
}
}
Context 'Tab completion and the parameter type' {
It '<Cmdlet> -<Parameter> should complete exactly the names of its enumeration' -ForEach $accessCompleterCases {
$inputScript = '{0} -{1} ' -f $Cmdlet, $Parameter
$enumType = [type] $EnumTypeName
$completion = [System.Management.Automation.CommandCompletion]::CompleteInput($inputScript, $inputScript.Length, $null)
@($completion.CompletionMatches | ForEach-Object -Process { $_.CompletionText }) | Should -BeExactly ([Enum]::GetNames($enumType))
}
It '<Cmdlet> should declare -<Parameter> as [object]' -ForEach $accessCompleterCases {
(Get-Command -Name $Cmdlet).Parameters[$Parameter].ParameterType | Should -Be ([object])
}
}
}
Describe 'Declared output types and parameter set boundaries' {
It '<Name> should declare <Types> as its output type' -ForEach $declaredOutputTypes {
@((Get-Command -Name $Name).OutputType.Name) | Should -Be $Types

344
Tests/ParameterSets.Audit.Tests.ps1

@ -276,9 +276,10 @@ Describe 'Add-NTFSAudit' {
$scopes -join '|' | Should -BeExactly 'None/None|ContainerInherit/None|ContainerInherit, ObjectInherit/InheritOnly'
}
# A blank cell that Import-Csv reads is an empty string, not $null: the row gets the default scope (ParameterSets.Access.Tests.ps1
# has the same rows and says why).
It 'Should give the row of a CSV file whose AppliesTo cell is blank the default scope, and the other rows their own' -Skip:(-not $canReadAudit) {
# A blank cell that Import-Csv reads is an empty string, not $null. Since -AppliesTo is now an object
# parameter (Decision 27, fix B), the blank string still binds it, which selects PathSimple, so the row gets
# an InvalidParameterValue error instead of the default scope (ParameterSets.Access.Tests.ps1 has the same rows).
It 'Should write an InvalidParameterValue error for the row of a CSV file whose AppliesTo cell is blank, leave its folder alone, and still give the other rows their own scope' -Skip:(-not $canReadAudit) {
$folders = @('AddCsvAuditBlankFirst', 'AddCsvAuditBlankCell', 'AddCsvAuditBlankLast' |
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory })
$csv = Join-Path -Path $sandbox -ChildPath 'AddCsvAuditBlank.csv'
@ -290,10 +291,14 @@ Describe 'Add-NTFSAudit' {
('"{0}",{1},ReadData,SubfoldersAndFilesOnly' -f $folders[2], $probe)
)
Import-Csv -LiteralPath $csv | Add-NTFSAudit -ErrorAction Stop
Import-Csv -LiteralPath $csv | Add-NTFSAudit -ErrorVariable auditErrors -ErrorAction SilentlyContinue
$auditErrors | Should -HaveCount 1
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$auditErrors[0].CategoryInfo.Category | Should -Be 'InvalidArgument'
$auditErrors[0].TargetObject | Should -Be $folders[1]
$scopes = $folders | ForEach-Object -Process { Get-TestAuditScope -Acl (Get-Acl -LiteralPath $_ -Audit) }
$scopes -join '|' | Should -BeExactly 'None/None|ContainerInherit, ObjectInherit/None|ContainerInherit, ObjectInherit/InheritOnly'
$scopes -join '|' | Should -BeExactly 'None/None||ContainerInherit, ObjectInherit/InheritOnly'
}
# $null cannot be bound at all: the object fails, loudly, and the cmdlet goes on with the next one.
@ -914,3 +919,332 @@ Describe 'Get-NTFSOrphanedAudit' {
}
}
}
# Decision 27 (fix B): AuditFlags, InheritanceFlags, PropagationFlags, and AppliesTo of Add-NTFSAudit and
# Remove-NTFSAudit are object parameters, converted in the cmdlet with LanguagePrimitives.ConvertTo, so that a piped
# value the binder cannot convert to the enumeration is an error instead of a silently kept default. A bad value is
# caught by the conversion before the cmdlet reads or writes the SACL, so none of these tests need the Security
# privilege, unlike most of the tests above.
Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationFlags, and AppliesTo' {
BeforeDiscovery {
$auditBadShapes = @(
@{ Shape = 'a misspelled name'; Value = 'Typoo'; ValueText = 'Typoo' }
@{ Shape = 'a blank string'; Value = ''; ValueText = $null }
@{ Shape = 'a white-space string'; Value = ' '; ValueText = $null }
@{ Shape = 'an undefined number'; Value = 99; ValueText = '99' }
@{ Shape = 'a hashtable'; Value = @{ Unrelated = 1 }; ValueText = $null }
)
$auditFlagsBadShapes = @(
@{ Shape = 'a misspelled name'; Value = 'Failurre'; ValueText = 'Failurre' }
@{ Shape = 'a blank string'; Value = ''; ValueText = $null }
@{ Shape = 'a white-space string'; Value = ' '; ValueText = $null }
@{ Shape = 'an undefined number'; Value = 99; ValueText = '99' }
@{ Shape = 'a hashtable'; Value = @{ Unrelated = 1 }; ValueText = $null }
)
# The cross product of the two cmdlets that share these parameters and every bad shape: 2 * (5 + 3 * 5) = 40 rows.
$auditInvalidParameterCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') {
foreach ($shapeCase in $auditFlagsBadShapes) {
@{ Cmdlet = $cmdlet; Parameter = 'AuditFlags'; Shape = $shapeCase.Shape; Value = $shapeCase.Value; ValueText = $shapeCase.ValueText }
}
foreach ($parameterName in 'InheritanceFlags', 'PropagationFlags', 'AppliesTo') {
foreach ($shapeCase in $auditBadShapes) {
@{ Cmdlet = $cmdlet; Parameter = $parameterName; Shape = $shapeCase.Shape; Value = $shapeCase.Value; ValueText = $shapeCase.ValueText }
}
}
}
$auditSdInvalidParameterCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') {
@{ Cmdlet = $cmdlet; Parameter = 'AuditFlags'; Value = 'Failurre'; ValueText = 'Failurre' }
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; Value = 'Typoo'; ValueText = 'Typoo' }
}
$auditNoCarryOverCases = @(
@{ Parameter = 'AuditFlags'; Value = 'Failurre' }
@{ Parameter = 'InheritanceFlags'; Value = 'Typoo' }
@{ Parameter = 'PropagationFlags'; Value = 'Typoo' }
@{ Parameter = 'AppliesTo'; Value = 'Typoo' }
)
$auditNamedInvalidParameterCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') {
@{ Cmdlet = $cmdlet; Parameter = 'AuditFlags'; Value = 'Failurre' }
@{ Cmdlet = $cmdlet; Parameter = 'InheritanceFlags'; Value = 'Typoo' }
@{ Cmdlet = $cmdlet; Parameter = 'PropagationFlags'; Value = 'Typoo' }
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; Value = 'Typoo' }
}
$auditValidAuditFlagsShapeCases = @(
@{ Description = 'the exact name'; Value = 'Failure' }
@{ Description = 'a different case'; Value = 'failure' }
@{ Description = 'a comma list without spaces'; Value = 'Success,Failure' }
@{ Description = 'a comma list with spaces'; Value = 'Success, Failure' }
@{ Description = 'the enum value'; Value = [System.Security.AccessControl.AuditFlags]::Failure }
@{ Description = 'an integer'; Value = 2 }
)
$auditValidFlagsShapeCases = @(
@{ Description = 'a comma list without spaces'; Value = 'ContainerInherit,ObjectInherit' }
@{ Description = 'a comma list with spaces'; Value = 'ContainerInherit, ObjectInherit' }
@{ Description = 'the enum value'; Value = [System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit' }
)
$auditAmbiguousValueCases = @(
@{ AppliesToShape = 'a string'; AppliesToIsEnum = $false; FlagShape = 'a string'; FlagIsEnum = $false }
@{ AppliesToShape = 'an enum value'; AppliesToIsEnum = $true; FlagShape = 'a string'; FlagIsEnum = $false }
@{ AppliesToShape = 'a string'; AppliesToIsEnum = $false; FlagShape = 'an enum value'; FlagIsEnum = $true }
@{ AppliesToShape = 'an enum value'; AppliesToIsEnum = $true; FlagShape = 'an enum value'; FlagIsEnum = $true }
)
$auditCompleterCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') {
@{ Cmdlet = $cmdlet; Parameter = 'AuditFlags'; EnumTypeName = 'System.Security.AccessControl.AuditFlags' }
@{ Cmdlet = $cmdlet; Parameter = 'InheritanceFlags'; EnumTypeName = 'System.Security.AccessControl.InheritanceFlags' }
@{ Cmdlet = $cmdlet; Parameter = 'PropagationFlags'; EnumTypeName = 'System.Security.AccessControl.PropagationFlags' }
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; EnumTypeName = 'Security2.ApplyTo' }
}
}
Context 'A bad value bound from the pipeline' {
# Writing an audit entry needs the Security privilege, so without it the "good" object right next to the bad
# one fails too, with its own (unrelated) privilege error; that part of the test runs only with the privilege,
# the same way most of Audit.Tests.ps1 does. The conversion failure that this test pins happens before the
# cmdlet ever touches the SACL, so the core assertions below run in every configuration.
It 'Should write one InvalidParameterValue error for <Cmdlet> -<Parameter> (<Shape>), change nothing, and still process the next object' -ForEach $auditInvalidParameterCases {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidValue' -Directory
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidValueNext' -Directory
$before = if ($canReadAudit) { Get-TestAuditSddl -Path $path }
$extra = @{ }
$extra[$Parameter] = $Value
$rows = @(
[pscustomobject] (@{ FullName = $path; Account = 'S-1-1-0'; AccessRights = 'ReadData' } + $extra)
[pscustomobject]@{ FullName = $next; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
)
$result = @($rows | & $Cmdlet -PassThru -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue)
$invalidValueErrors = @($pipelineErrors | Where-Object -FilterScript { $_.TargetObject -eq $path })
$invalidValueErrors | Should -HaveCount 1
$invalidValueErrors[0].FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$invalidValueErrors[0].CategoryInfo.Category | Should -Be 'InvalidArgument'
$invalidValueErrors[0].Exception.Message | Should -Match ([regex]::Escape($Parameter))
if ($ValueText) {
$invalidValueErrors[0].Exception.Message | Should -Match ([regex]::Escape($ValueText))
}
if ($canReadAudit) {
Get-TestAuditSddl -Path $path | Should -BeExactly $before
$pipelineErrors | Should -HaveCount 1
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_.FullName | Should -Be $next }
}
}
# A descriptor that Get-NTFSSecurityDescriptor read without the Security privilege has no audit section, so
# without the privilege the "good" descriptor also gets its own (unrelated) ReadSecurityError; see above.
It 'Should write one InvalidParameterValue error per descriptor for <Cmdlet> -<Parameter> bound to a piped descriptor, and leave the descriptors unchanged' -ForEach $auditSdInvalidParameterCases {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidSdFirst' -Directory
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidSdSecond' -Directory
$descriptors = @(Get-NTFSSecurityDescriptor -Path $first, $second -ErrorAction Stop)
$before = $descriptors[0].SecurityDescriptor.GetSecurityDescriptorSddlForm('Access')
$extra = @{ }
$extra[$Parameter] = $Value
$rows = @(
[pscustomobject] (@{ SecurityDescriptor = $descriptors[0]; Account = 'S-1-1-0'; AccessRights = 'ReadData' } + $extra)
[pscustomobject]@{ SecurityDescriptor = $descriptors[1]; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
)
$result = @($rows | & $Cmdlet -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue)
$invalidValueErrors = @($pipelineErrors | Where-Object -FilterScript { $_.TargetObject -eq $descriptors[0] })
$invalidValueErrors | Should -HaveCount 1
$invalidValueErrors[0].FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$invalidValueErrors[0].CategoryInfo.Category | Should -Be 'InvalidArgument'
$descriptors[0].SecurityDescriptor.GetSecurityDescriptorSddlForm('Access') | Should -BeExactly $before
if ($canReadAudit) {
$pipelineErrors | Should -HaveCount 1
}
}
}
Context 'Continuation and no carry-over across piped objects' {
It 'Should apply both good objects when a bad AuditFlags value is between them, in the order of the rows' -Skip:(-not $canReadAudit) {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueFirst' -Directory
$middle = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueMiddle' -Directory
$last = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueLast' -Directory
$rows = @(
[pscustomobject]@{ FullName = $first; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AuditFlags = 'Failure' }
[pscustomobject]@{ FullName = $middle; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AuditFlags = 'Failurre' }
[pscustomobject]@{ FullName = $last; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AuditFlags = 'Failure' }
)
$rows | Add-NTFSAudit -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue
$pipelineErrors | Should -HaveCount 1
$pipelineErrors[0].TargetObject | Should -Be $middle
$firstRule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $first -Audit) -Account 'S-1-1-0'
$firstRule | Should -HaveCount 1
$firstRule[0].AuditFlags | Should -Be ([System.Security.AccessControl.AuditFlags]::Failure)
$middleRule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $middle -Audit) -Account 'S-1-1-0'
$middleRule | Should -BeNullOrEmpty
$lastRule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $last -Audit) -Account 'S-1-1-0'
$lastRule | Should -HaveCount 1
$lastRule[0].AuditFlags | Should -Be ([System.Security.AccessControl.AuditFlags]::Failure)
}
It 'Should give the row without AuditFlags the default Success and Failure after a preceding Failure-only row, with no carry-over' -Skip:(-not $canReadAudit) {
$failureFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverFailure' -Directory
$defaultFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverDefault' -Directory
@(
[pscustomobject]@{ FullName = $failureFolder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AuditFlags = 'Failure' }
[pscustomobject]@{ FullName = $defaultFolder; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
) | Add-NTFSAudit -ErrorAction Stop
$failureRule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $failureFolder -Audit) -Account 'S-1-1-0'
$failureRule | Should -HaveCount 1
$failureRule[0].AuditFlags | Should -Be ([System.Security.AccessControl.AuditFlags]::Failure)
$defaultRule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $defaultFolder -Audit) -Account 'S-1-1-0'
$defaultRule | Should -HaveCount 1
$defaultRule[0].AuditFlags | Should -Be ([System.Security.AccessControl.AuditFlags] 'Success, Failure')
}
It 'Should give the row without <Parameter> its own default after a preceding row with an invalid value, with no carry-over' -ForEach $auditNoCarryOverCases -Skip:(-not $canReadAudit) {
$badFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverBad' -Directory
$defaultFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverAfterBad' -Directory
$extra = @{ }
$extra[$Parameter] = $Value
@(
[pscustomobject] (@{ FullName = $badFolder; Account = 'S-1-1-0'; AccessRights = 'ReadData' } + $extra)
[pscustomobject]@{ FullName = $defaultFolder; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
) | Add-NTFSAudit -ErrorAction SilentlyContinue
$defaultRule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $defaultFolder -Audit) -Account 'S-1-1-0'
$defaultRule | Should -HaveCount 1
$defaultRule[0].AuditFlags | Should -Be ([System.Security.AccessControl.AuditFlags] 'Success, Failure')
$defaultRule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit')
$defaultRule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags]::None)
}
}
Context 'Valid values are unchanged for every shape' {
It 'Should add a Failure-only entry for AuditFlags given as <Description>' -ForEach $auditValidAuditFlagsShapeCases -Skip:(-not $canReadAudit) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidAuditFlags' -Directory
[pscustomobject]@{ FullName = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AuditFlags = $Value } | Add-NTFSAudit -ErrorAction Stop
$rule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $folder -Audit) -Account 'S-1-1-0'
$rule | Should -HaveCount 1
$rule[0].AuditFlags.HasFlag([System.Security.AccessControl.AuditFlags]::Failure) | Should -BeTrue
}
It 'Should keep the default AuditFlags Success and Failure for a $null value' -Skip:(-not $canReadAudit) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidAuditFlagsNull' -Directory
[pscustomobject]@{ FullName = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AuditFlags = $null } | Add-NTFSAudit -ErrorAction Stop
$rule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $folder -Audit) -Account 'S-1-1-0'
$rule | Should -HaveCount 1
$rule[0].AuditFlags | Should -Be ([System.Security.AccessControl.AuditFlags] 'Success, Failure')
}
It 'Should scope the entry with InheritanceFlags given as <Description>' -ForEach $auditValidFlagsShapeCases -Skip:(-not $canReadAudit) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidInheritanceFlags' -Directory
[pscustomobject]@{ FullName = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = $Value; PropagationFlags = 'None' } | Add-NTFSAudit -ErrorAction Stop
$rule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $folder -Audit) -Account 'S-1-1-0'
$rule | Should -HaveCount 1
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit')
}
It 'Should keep the default flags for $null InheritanceFlags and PropagationFlags' -Skip:(-not $canReadAudit) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidFlagsNull' -Directory
[pscustomobject]@{ FullName = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = $null; PropagationFlags = $null } | Add-NTFSAudit -ErrorAction Stop
$rule = Get-TestAccountAuditRule -Acl (Get-Acl -LiteralPath $folder -Audit) -Account 'S-1-1-0'
$rule | Should -HaveCount 1
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit')
$rule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags]::None)
}
}
Context 'A bad value named on the command line' {
It 'Should throw a terminating InvalidParameterValue error for <Cmdlet> -<Parameter>, catchable with try/catch, and change nothing' -ForEach $auditNamedInvalidParameterCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedInvalid' -Directory
$before = if ($canReadAudit) { Get-TestAuditSddl -Path $folder }
$namedArguments = @{ Path = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData' }
$namedArguments[$Parameter] = $Value
$caughtId = $null
try {
& $Cmdlet @namedArguments -ErrorAction Stop
}
catch {
$caughtId = $_.FullyQualifiedErrorId
}
$caughtId | Should -BeLike 'InvalidParameterValue,*'
if ($canReadAudit) {
Get-TestAuditSddl -Path $folder | Should -BeExactly $before
}
}
It 'Should stop before the first piped path when -AuditFlags is named and bad' {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedBadFirst' -Directory
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedBadSecond' -Directory
$before = if ($canReadAudit) { @((Get-TestAuditSddl -Path $first), (Get-TestAuditSddl -Path $second)) }
$caughtId = $null
try {
$first, $second | Add-NTFSAudit -Account 'S-1-1-0' -AccessRights ReadData -AuditFlags Failurre -ErrorAction Stop
}
catch {
$caughtId = $_.FullyQualifiedErrorId
}
$caughtId | Should -BeLike 'InvalidParameterValue,*'
if ($canReadAudit) {
Get-TestAuditSddl -Path $first | Should -BeExactly $before[0]
Get-TestAuditSddl -Path $second | Should -BeExactly $before[1]
}
}
}
Context 'Choosing the parameter set with a mix of string and enum values' {
It 'Should reject a piped object whose AppliesTo is <AppliesToShape> combined with InheritanceFlags as <FlagShape> with an AmbiguousParameterSet error' -ForEach $auditAmbiguousValueCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousPipedMix' -Directory
$appliesToValue = if ($AppliesToIsEnum) { [Security2.ApplyTo]::ThisFolderOnly } else { 'ThisFolderOnly' }
$flagValue = if ($FlagIsEnum) { [System.Security.AccessControl.InheritanceFlags]::None } else { 'None' }
$object = [pscustomobject]@{ FullName = $folder; Account = 'S-1-1-0'; AccessRights = 'ReadData'; AppliesTo = $appliesToValue; InheritanceFlags = $flagValue }
{ $object | Add-NTFSAudit -ErrorAction Stop } | Should -Throw -ErrorId 'AmbiguousParameterSet*'
}
It 'Should reject a named call whose -AppliesTo is <AppliesToShape> combined with -PropagationFlags as <FlagShape> with an AmbiguousParameterSet error' -ForEach $auditAmbiguousValueCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousNamedMix' -Directory
$appliesToValue = if ($AppliesToIsEnum) { [Security2.ApplyTo]::ThisFolderOnly } else { 'ThisFolderOnly' }
$flagValue = if ($FlagIsEnum) { [System.Security.AccessControl.InheritanceFlags]::None } else { 'None' }
{ Add-NTFSAudit -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -AppliesTo $appliesToValue -PropagationFlags $flagValue -ErrorAction Stop } |
Should -Throw -ErrorId 'AmbiguousParameterSet*'
}
}
Context 'Tab completion and the parameter type' {
It '<Cmdlet> -<Parameter> should complete exactly the names of its enumeration' -ForEach $auditCompleterCases {
$inputScript = '{0} -{1} ' -f $Cmdlet, $Parameter
$enumType = [type] $EnumTypeName
$completion = [System.Management.Automation.CommandCompletion]::CompleteInput($inputScript, $inputScript.Length, $null)
@($completion.CompletionMatches | ForEach-Object -Process { $_.CompletionText }) | Should -BeExactly ([Enum]::GetNames($enumType))
}
It '<Cmdlet> should declare -<Parameter> as [object]' -ForEach $auditCompleterCases {
(Get-Command -Name $Cmdlet).Parameters[$Parameter].ParameterType | Should -Be ([object])
}
}
}

Loading…
Cancel
Save