|
|
|
|
using Alphaleonis.Win32.Filesystem;
|
|
|
|
|
using Security2;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Management.Automation;
|
|
|
|
|
|
|
|
|
|
namespace NTFSSecurity
|
|
|
|
|
{
|
|
|
|
|
[Cmdlet(VerbsLifecycle.Enable, "NTFSAuditInheritance", DefaultParameterSetName = "Path")]
|
|
|
|
|
[OutputType(typeof(FileSystemInheritanceInfo))]
|
|
|
|
|
public class EnableAuditInheritance : BaseCmdletWithPrivControl
|
|
|
|
|
{
|
|
|
|
|
private bool removeExplicitAuditRules;
|
|
|
|
|
private bool passThru;
|
|
|
|
|
|
|
|
|
|
[Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")]
|
|
|
|
|
[ValidateNotNullOrEmpty]
|
|
|
|
|
[Alias("FullName")]
|
|
|
|
|
[FileSystemPathTransformation]
|
|
|
|
|
public string[] Path
|
|
|
|
|
{
|
|
|
|
|
get { return paths.ToArray(); }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
paths.Clear();
|
|
|
|
|
paths.AddRange(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "SecurityDescriptor")]
|
|
|
|
|
[ValidateNotNullOrEmpty]
|
|
|
|
|
public FileSystemSecurity2[] SecurityDescriptor
|
|
|
|
|
{
|
|
|
|
|
get { return securityDescriptors.ToArray(); }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
securityDescriptors.Clear();
|
|
|
|
|
securityDescriptors.AddRange(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
public SwitchParameter PassThru
|
|
|
|
|
{
|
|
|
|
|
get { return passThru; }
|
|
|
|
|
set { passThru = value; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes the explicit audit entries of the item. Before 5.0.0, the switch was named
|
|
|
|
|
/// RemoveExplicitAccessRules, which remains an alias.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Alias("RemoveExplicitAccessRules")]
|
|
|
|
|
public SwitchParameter RemoveExplicitAuditRules
|
|
|
|
|
{
|
|
|
|
|
get { return removeExplicitAuditRules; }
|
|
|
|
|
set { removeExplicitAuditRules = value; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void BeginProcessing()
|
|
|
|
|
{
|
|
|
|
|
base.BeginProcessing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void ProcessRecord()
|
|
|
|
|
{
|
|
|
|
|
if (ParameterSetName == "Path")
|
|
|
|
|
{
|
|
|
|
|
foreach (var path in paths)
|
|
|
|
|
{
|
|
|
|
|
FileSystemInfo item = null;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
item = GetFileSystemInfo2(path);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
WriteError(new ErrorRecord(ex, "ReadFileError", ErrorCategory.OpenError, path));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
FileSystemInheritanceInfo.EnableAuditInheritance(item, removeExplicitAuditRules);
|
|
|
|
|
}
|
|
|
|
|
catch (UnauthorizedAccessException)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
InvokeAsOwner(item, path, () =>
|
|
|
|
|
{
|
|
|
|
|
FileSystemInheritanceInfo.EnableAuditInheritance(item, removeExplicitAuditRules);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex2)
|
|
|
|
|
{
|
fix(owner): pass on what a later command raises at the RestoreOwnerError record
InvokeAsOwner (and Set-NTFSSecurityDescriptor's WriteChangesAsOwner) take
ownership of an item, run the action, and in a finally restore the
previous owner, writing a non-terminating RestoreOwnerError through
WriteError when that restore fails. WriteError runs the later commands
of the pipeline while it writes, so in the rare case that a later
command throws exactly when it sees that RestoreOwnerError record (for
example `... 2>&1 | ForEach-Object { if ($_.FullyQualifiedErrorId -like
'RestoreOwnerError,*') { throw } else { $_ } }`), that exception used to
leave InvokeAsOwner through the finally and reach the handler around the
call, which reported it as the item's own error (AddAceError,
ReadSecurityError, ModifySdError, and so on) and moved on to the next
path. The caller never saw the exception.
Thirteen handlers now check IsFromLaterCommand as the first statement of
their catch, the same pattern SetSecurityDescriptor.cs already used in
its second handler and GetSecurityDescriptor.cs already used: Add/Clear
and Get-NTFSAccess, Get-NTFSOrphanedAccess, Get-NTFSEffectiveAccess,
Clear-NTFSAudit, Get-FileHash2, the six inheritance cmdlets, and the
owner-retry of Set-NTFSSecurityDescriptor. The four add/remove access and
audit cmdlets have the same flaw and belong to another work item.
Tests\PipelineControl.Tests.ps1 adds one table-driven It, elevated only,
that denies the current user the right a cmdlet's action needs, gives
the item an owner only the Restore privilege can set back (which the
test turns off), and asserts that a later command's exception reaches
the caller, no item-error record was written for it, and the next path
is untouched. Six of the thirteen handlers are pinned this way (the
other seven need the current user to both fail and succeed the same
access check at once, which Windows does not allow; see the comment
above the Describe block and the probes under ..\probes-wi2\ for the
empirical evidence). The four add and remove cmdlets, which have the same
flaw, follow in the next commit of this item.
Get-NTFSInheritance, the one of the thirteen that writes its result in a
finally, writes it after the try instead, so that the object does not go to
the later command that has just thrown.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
|
|
|
// Not what a later command raises, for example when it takes the RestoreOwnerError record.
|
|
|
|
|
if (IsFromLaterCommand(ex2))
|
|
|
|
|
{
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WriteError(new ErrorRecord(ex2, "ModifySdError", ErrorCategory.WriteError, path));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
WriteError(new ErrorRecord(ex, "ModifySdError", ErrorCategory.WriteError, path));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only after a successful change, so that a failure doesn't report the unchanged state
|
|
|
|
|
if (passThru)
|
|
|
|
|
{
|
|
|
|
|
WriteObject(FileSystemInheritanceInfo.GetFileSystemInheritanceInfo(item));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
foreach (var sd in securityDescriptors)
|
|
|
|
|
{
|
|
|
|
|
FileSystemInheritanceInfo.EnableAuditInheritance(sd, removeExplicitAuditRules);
|
|
|
|
|
|
|
|
|
|
if (passThru)
|
|
|
|
|
{
|
|
|
|
|
WriteObject(FileSystemInheritanceInfo.GetFileSystemInheritanceInfo(sd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|