mirror of https://github.com/raandree/NTFSSecurity
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
12 KiB
314 lines
12 KiB
using Alphaleonis.Win32.Filesystem;
|
|
using Security2;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Management.Automation;
|
|
using System.Security.AccessControl;
|
|
|
|
namespace NTFSSecurity
|
|
{
|
|
[Cmdlet(VerbsCommon.Add, "NTFSAccess")]
|
|
[OutputType(typeof(FileSystemAccessRule2))]
|
|
public class AddAccess : BaseCmdletWithPrivControl
|
|
{
|
|
private IdentityReference2[] account;
|
|
private FileSystemRights2 accessRights;
|
|
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")]
|
|
[Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
|
|
[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 = "SDSimple")]
|
|
[Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
|
|
[ValidateNotNullOrEmpty]
|
|
public FileSystemSecurity2[] SecurityDescriptor
|
|
{
|
|
get { return securityDescriptors.ToArray(); }
|
|
set
|
|
{
|
|
securityDescriptors.Clear();
|
|
securityDescriptors.AddRange(value);
|
|
}
|
|
}
|
|
|
|
[Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true)]
|
|
[Alias("IdentityReference", "ID")]
|
|
public IdentityReference2[] Account
|
|
{
|
|
get { return account; }
|
|
set { account = value; }
|
|
}
|
|
|
|
[Parameter(Mandatory = true, Position = 3, ValueFromPipelineByPropertyName = true)]
|
|
[Alias("FileSystemRights")]
|
|
public FileSystemRights2 AccessRights
|
|
{
|
|
get { return accessRights; }
|
|
set { accessRights = value; }
|
|
}
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName = true)]
|
|
[Alias("AccessControlType")]
|
|
[ArgumentCompleter(typeof(AccessControlTypeArgumentCompleter))]
|
|
public object AccessType
|
|
{
|
|
get { return accessType; }
|
|
set { accessType = value; }
|
|
}
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
|
|
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
|
|
[ArgumentCompleter(typeof(InheritanceFlagsArgumentCompleter))]
|
|
public object InheritanceFlags
|
|
{
|
|
get { return inheritanceFlags; }
|
|
set { inheritanceFlags = value; }
|
|
}
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "PathComplex")]
|
|
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "SDComplex")]
|
|
[ArgumentCompleter(typeof(PropagationFlagsArgumentCompleter))]
|
|
public object PropagationFlags
|
|
{
|
|
get { return propagationFlags; }
|
|
set { propagationFlags = value; }
|
|
}
|
|
|
|
// 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")]
|
|
[ArgumentCompleter(typeof(ApplyToArgumentCompleter))]
|
|
public object AppliesTo
|
|
{
|
|
get { return appliesTo; }
|
|
set { appliesTo = value; }
|
|
}
|
|
|
|
[Parameter]
|
|
public SwitchParameter PassThru
|
|
{
|
|
get { return passThru; }
|
|
set { passThru = value; }
|
|
}
|
|
|
|
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 = System.Security.AccessControl.InheritanceFlags.ContainerInherit | System.Security.AccessControl.InheritanceFlags.ObjectInherit;
|
|
var propagation = System.Security.AccessControl.PropagationFlags.None;
|
|
|
|
if (ParameterSetName.EndsWith("Simple"))
|
|
{
|
|
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"))
|
|
{
|
|
FileSystemInfo item = null;
|
|
|
|
foreach (var path in paths)
|
|
{
|
|
try
|
|
{
|
|
item = GetFileSystemInfo2(path);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WriteError(new ErrorRecord(ex, "ReadFileError", ErrorCategory.OpenError, path));
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
FileSystemAccessRule2.AddFileSystemAccessRule(item, account.ToList(), accessRights, accessTypeValue, inheritance, propagation);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
try
|
|
{
|
|
InvokeAsOwner(item, path, () =>
|
|
{
|
|
FileSystemAccessRule2.AddFileSystemAccessRule(item, account.ToList(), accessRights, accessTypeValue, inheritance, propagation);
|
|
});
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
// Not what a later command raises, for example when it takes the RestoreOwnerError record.
|
|
if (IsFromLaterCommand(ex2))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
WriteError(new ErrorRecord(ex2, "AddAceError", ErrorCategory.WriteError, path));
|
|
continue;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WriteError(new ErrorRecord(ex, "AddAceError", ErrorCategory.WriteError, path));
|
|
continue;
|
|
}
|
|
|
|
if (passThru == true)
|
|
{
|
|
FileSystemAccessRule2.GetFileSystemAccessRules(item, true, true).ForEach(ace => WriteObject(ace));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var sd in securityDescriptors)
|
|
{
|
|
try
|
|
{
|
|
FileSystemAccessRule2.AddFileSystemAccessRule(sd, account.ToList(), accessRights, accessTypeValue, inheritance, propagation);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WriteError(new ErrorRecord(ex, "AddAceError", ErrorCategory.WriteError, sd));
|
|
continue;
|
|
}
|
|
|
|
if (passThru == true)
|
|
{
|
|
FileSystemAccessRule2.GetFileSystemAccessRules(sd, true, true).ForEach(ace => WriteObject(ace));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
protected override void EndProcessing()
|
|
{
|
|
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. A named
|
|
// $null is bound too, whether it came from a literal $null, a variable, the alias -AccessControlType,
|
|
// splatting, or $PSDefaultParameterValues (MyInvocation.BoundParameters always has the canonical name), and
|
|
// is an error the same way; a $null that a piped object's property carries is not bound yet at this point,
|
|
// so it keeps the parameter's own default instead, as before.
|
|
private void ValidateNamedValue<T>(object value, string parameterName)
|
|
{
|
|
if (value == null)
|
|
{
|
|
if (!MyInvocation.BoundParameters.ContainsKey(parameterName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var nullException = new ArgumentException(ParameterConversion.DescribeNull<T>(parameterName));
|
|
ThrowTerminatingError(new ErrorRecord(nullException, "InvalidParameterValue", ErrorCategory.InvalidArgument, value));
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|