How to sperate each group/user in AccessToString when using Get-ACL folder.

Copper Contributor

Good Evening All, 

Currently I am writing a script that will allow me to remove User/groups from folders if those groups are in a list, however when calling for "AccessToString" Property but its not separated it into a list.

Can anyone help me will writing something that would sperate the below in to a list.  

$Folder_name = "Admin", "Logs"
$TestMandatorySecurityGroups = "NT AUTHORITY\SYSTEM", "Administrators", "Users"

foreach($Name in $Folder_name){ 

    # Gets the security descriptor for a resource a file and export to csv file
    Get-Acl "C:\temp\$Name" | Export-csv -Path C:\temp\ps\csv\GDrive_ACL_stats.csv -Append
}

# Foreach Folder in csv Check security and remove unwanted user/groups
foreach($Folder in $Folder_ACL){

    $Access = $Folder.AccessToString 
    $Group = $Folder.Group
    Write-Host "--------------------------------------------------------"
    Write-host "User/Security Groups who have acces to $($folder.PSChildName) folder"
    write-host ""
    Write-Host $Access
    write-host $Group
    write-host ""


    # Foreach $SecurityGroup in $Access check for security and remove them
    Foreach($SecurityGroup in $Access){

        if($TestMandatorySecurityGroups -contains $SecurityGroup){

            Write-Warning "Removing $SecurityGroup from $Folder"

            $acl = Get-Acl "C:\temp\$Folder"
            $AccessRule = New-Object System.Security.Principal.Ntaccount($SecurityGroup)
            $acl.PurgeAccessRules($AccessRule)
            $acl | Set-Acl "C:\temp\$Folder"
        }
     }

     Foreach($SecurityGroup in $Group){

        if($TestMandatorySecurityGroups -contains $SecurityGroup){

            Write-Warning "Removing $SecurityGroup from $Folder"

            $acl = Get-Acl "C:\temp\$Folder"
            $AccessRule = New-Object System.Security.Principal.Ntaccount($SecurityGroup)
            $acl.PurgeAccessRules($AccessRule)
            $acl | Set-Acl "C:\temp\$Folder"
        }
     }



When out putting the "Get-ACL" to CSV the AccessToString Colum has all the user/security groups in one sentence but I need to separate each of them and add it to a list so that I can validate them. 

8 Replies

@Hjb118 

 

There's a number of issues with this script but I have to ask: are you sure you want to remove the groups listed in $TestMandatoryGroups?

 

This is what you appear to be trying to do in your ForEach loop starting on line 24.

 

I ask because the variable name, $TestMandatorySecurityGroups, somewhat implies that those are the groups you want to keep, not remove.

 

Cheers,

Lain

@LainRobertson 

 

Yes the groups in $TestMandatoryGroups are ones I do want to keep.

 

I know their are issues, I'm slowly working my way thought them.

 

Thank you

@Hjb118 

 

Yep, no problem. I just wanted to be sure we were on the same page so you didn't end up removing the ACEs you actually meant to keep.

 

So, here's a brief rundown of the issues in the original script:

 

  1. Exporting the full object on line 7 results in a messy CSV that is hard to work with - particularly if you needed to use it for restoring permissions;
  2. The ForEach loop was written to remove the mandatory groups instead of retain them;
  3. One line 11, $Folder_ACL is being referenced yet it hasn't actually been defined beforehand, meaning it's a $null reference;
  4. AccessToString is not your friend. Even if you do split it, you'll run into additional parsing issues if the IdentityReference value contains spaces;
  5. In the second ForEach loop (lines 24 to 35), you don't need to call Get-Acl again as everything can be done in the one pass;
  6. The third ForEach loop (lines 37 to 48) isn't necessary.

 

Here's a basic example script you can alter to suit your needs.

 

I've changed the folders names to suit my test environment, so you will likely need to change those. You could also get away from having them hard-coded in the script and provide them via a parameter but I didn't bother going this far.

 

Reset-Acl.ps1

[cmdletbinding()]
Param(
    [parameter()][switch]$Commit,
    [parameter()][string]$LogFile = ".\GDrive_ACL_stats.csv"
)

$Folder_name = @(
    "D:\Data\Temp\Bogus1",
    "D:\Data\Temp\Bogus2"
);

$TestMandatorySecurityGroups = @(
    "NT AUTHORITY\SYSTEM",
    "BUILTIN\Administrators",
    "BUILTIN\Users"
);

# Remove the CSV file to avoid conflicts from repeated runs.
Remove-Item -Path $LogFile -Force -ErrorAction:SilentlyContinue;

foreach($Name in $Folder_name)
{ 
    # Fetch the ACL.
    $Acl = Get-Acl -Path $Name;
    $FilePath = ($Acl.Path -split "::")[1].ToLowerInvariant();

    # Enumerate the owner before moving onto the Access Control Entries (ACEs.)
    [PSCustomObject] @{
        Path = $FilePath;
        AccessControlType = "Allow";        # Strictly speaking, there is no Allow or Deny for the Owner, but it's useful in filtering scenarios, so in it goes.
        IdentityReference = $Acl.Owner;
        FileSystemRights = "Owner";         # From here down, nothing else applies to the Owner, so set them as $null and head out to the ACEs.
        IsInherited = $null;
        InheritanceFlags = $null;
        PropagationFlags = $null;
    } | Export-csv -NoTypeInformation -Path $LogFile -Append;

    # Enumerate the ACEs, being sure to exclude those that are inherited. After all, you can't remove an inherited ACE.
    foreach ($Ace in $Acl.Access)
    {
        if (-not $Ace.IsInherited)
        {
            # Log the entry first.
            [PSCustomObject] @{
                Path = $FilePath;
                AccessControlType = $Ace.AccessControlType;     # Strictly speaking, there is no Allow or Deny for the Owner, but it's useful in filtering scenarios, so in it goes.
                IdentityReference = $Ace.IdentityReference;
                FileSystemRights = $Ace.FileSystemRights;       # From here down, nothing else applies to the Owner, so set them as $null and head out to the ACEs.
                IsInherited = $Ace.IsInherited;
                InheritanceFlags = $Ace.InheritanceFlags;
                PropagationFlags = $Ace.PropagationFlags;
            } | Export-csv -NoTypeInformation -Path $LogFile -Append;

            # Then see if we need to remove it.
            if ($TestMandatorySecurityGroups -notcontains $Ace.IdentityReference)
            {
                if ($Commit.IsPresent)
                {
                    Write-Warning -Message "Removing ""$($Ace.IdentityReference)"" from $Name";
                }
                else
                {
                    Write-Warning -Message """$($Ace.IdentityReference)"" would be removed from $Name";
                }

                $null = $Acl.RemoveAccessRule($Ace);
            }
        }
    }

    # Commit the ACL changes. Not bothering to wrap in a try..catch block since it's the final statement and there's value in letting any exceptions (such as "access denied") surface.
    if ($Commit.IsPresent)
    {
        Set-Acl -Path $FilePath -AclObject $Acl;
    }
}

 

Here's a sample of the output from calling Reset-Acl.ps1 three times:

 

  1. First, to show what would happen but without committing any changes;
  2. Second, to commit the changes;
  3. Third, purely as confirmation that the changes have been committed.

 

LainRobertson_0-1654262315543.png

 

Lastly, this is what you get in the CSV file.

LainRobertson_1-1654262521428.png

 

Cheers,

Lain

Good Morning Lain,


Thank you for your response this helps a lot.

What would be the best way to turn this in to a function? As it will ran as option admin script.
1. we don't really need commit it as this function makes sure the Mandatory security groups applies.
2. Then the function will apply 1 Security group depending on the folder name.

The folders that we are trying to change are created in another function and inheritances in then disabled on creation.

 

 

 

 

 

@Hjb118 

 

Simply wrap the code in a function declaration, like so:

 

function Reset-Acl
{
    # Blah
    # Blah
}

 

For example:

function Reset-Acl
{
    [cmdletbinding()]
    Param(
        [parameter()][switch]$Commit,
        [parameter()][string]$LogFile = ".\GDrive_ACL_stats.csv"
    )

    $Folder_name = @(
        "D:\Data\Temp\Bogus1",
        "D:\Data\Temp\Bogus2"
    );

    $TestMandatorySecurityGroups = @(
        "NT AUTHORITY\SYSTEM",
        "BUILTIN\Administrators",
        "BUILTIN\Users"
    );

    # Remove the CSV file to avoid conflicts from repeated runs.
    Remove-Item -Path $LogFile -Force -ErrorAction:SilentlyContinue;

    foreach($Name in $Folder_name)
    { 
        # Fetch the ACL.
        $Acl = Get-Acl -Path $Name;
        $FilePath = ($Acl.Path -split "::")[1].ToLowerInvariant();

        # Enumerate the owner before moving onto the Access Control Entries (ACEs.)
        [PSCustomObject] @{
            Path = $FilePath;
            AccessControlType = "Allow";        # Strictly speaking, there is no Allow or Deny for the Owner, but it's useful in filtering scenarios, so in it goes.
            IdentityReference = $Acl.Owner;
            FileSystemRights = "Owner";         # From here down, nothing else applies to the Owner, so set them as $null and head out to the ACEs.
            IsInherited = $null;
            InheritanceFlags = $null;
            PropagationFlags = $null;
        } | Export-csv -NoTypeInformation -Path $LogFile -Append;

        # Enumerate the ACEs, being sure to exclude those that are inherited. After all, you can't remove an inherited ACE.
        foreach ($Ace in $Acl.Access)
        {
            if (-not $Ace.IsInherited)
            {
                # Log the entry first.
                [PSCustomObject] @{
                    Path = $FilePath;
                    AccessControlType = $Ace.AccessControlType;     # Strictly speaking, there is no Allow or Deny for the Owner, but it's useful in filtering scenarios, so in it goes.
                    IdentityReference = $Ace.IdentityReference;
                    FileSystemRights = $Ace.FileSystemRights;       # From here down, nothing else applies to the Owner, so set them as $null and head out to the ACEs.
                    IsInherited = $Ace.IsInherited;
                    InheritanceFlags = $Ace.InheritanceFlags;
                    PropagationFlags = $Ace.PropagationFlags;
                } | Export-csv -NoTypeInformation -Path $LogFile -Append;

                # Then see if we need to remove it.
                if ($TestMandatorySecurityGroups -notcontains $Ace.IdentityReference)
                {
                    if ($Commit.IsPresent)
                    {
                        Write-Warning -Message "Removing ""$($Ace.IdentityReference)"" from $Name";
                    }
                    else
                    {
                        Write-Warning -Message """$($Ace.IdentityReference)"" would be removed from $Name";
                    }

                    $null = $Acl.RemoveAccessRule($Ace);
                }
            }
        }

        # Commit the ACL changes. Not bothering to wrap in a try..catch block since it's the final statement and there's value in letting any exceptions (such as "access denied") surface.
        if ($Commit.IsPresent)
        {
            Set-Acl -Path $FilePath -AclObject $Acl;
        }
    }
}

 

Cheers,

Lain

Good Afternoon @LainRobertson

Thank you I have now Modified it will details and works amazingly thank you.

Are you able to help me abit more please.

Currently I am trying to Add ACE to folders and give specific Security group following permissions 1. Write
2. ReadAndExecute
But I can only seem to at one but not both?

Thanks

@Hjb118 

 

It would have been good to know this from the start, along with your statement earlier about breaking inheritance.

 

It seems to me that you want a precise ACL to be applied, and anything else not in that ACL to be removed. As you stated in a more recent post, you now also want to break inheritance.

 

If both of those statements are true then we're going about this the long way, and this could be done more simply.

 

I've provided an updated version of the script below which takes a different approach.

 

The previous versions of the script, as per your original post, took a "blacklist" approach. The new version below takes a "whitelist" approach.

 

Adopting a whitelist approach reduces complexity, and more easily delivers the two new requirements you've mentioned above.

 

All you need to focus on now is the contents of the $MandatoryPrincipals variable near the top of the script.

 

This variable contains the access control entries (ACEs) you want to see in the ACL applied to the folder. The script body will take care of the rest.

 

Within this variable, I have specified the following "defaults" as a guess, which you can change as you see fit.

 

NT AUTHORITY\SYSTEMFull control
BUILTIN\AdministratorsFull control
BUILTIN\UsersRead and execute

 

There is also a fourth bogus principal which you should change (otherwise it will throw an error) to match the group you mentioned you wish to include in your previous post, as this bogus entry has the Read and execute + Write rights you requested. Add new or adjust the existing entries as you see fit.

 

If you've got any extra requirements, it'd be good if you could list them all in one go rather than continually adding new ones onto this thread.

 

function Reset-Acl
{
    [cmdletbinding()]
    Param(
        [parameter()][switch]$Commit,
        [parameter()][string]$LogFile = ".\GDrive_ACL_stats.csv"
    )

    $Folder_name = @(
        "D:\Data\Temp\Bogus1",
        "D:\Data\Temp\Bogus2"
    );

    $MandatoryPrincipals = @(
        # Access rule for NT AUTHORITY\SYSTEM.
        [System.Security.AccessControl.FileSystemAccessRule]::new(
            "NT AUTHORITY\SYSTEM",
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit,
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        ),
        # Access rule for BUILTIN\Administrators.
        [System.Security.AccessControl.FileSystemAccessRule]::new(
            "BUILTIN\Administrators",
            [System.Security.AccessControl.FileSystemRights]::FullControl,
            [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit,
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        ),
        # Access rule for BUILTIN\Users.
        [System.Security.AccessControl.FileSystemAccessRule]::new(
            "BUILTIN\Users",
            [System.Security.AccessControl.FileSystemRights]::ReadAndExecute,
            [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit,
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        ),
        # Sample access rule demonstrating granting Read and execute + Write access.
        [System.Security.AccessControl.FileSystemAccessRule]::new(
            "YourDomain\RandomGroupName",
            [System.Security.AccessControl.FileSystemRights]::ReadAndExecute -bor [System.Security.AccessControl.FileSystemRights]::Write,
            [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit,
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        )
    );



    # Remove the CSV file to avoid conflicts from repeated runs.
    Remove-Item -Path $LogFile -Force -ErrorAction:SilentlyContinue;

    foreach($Name in $Folder_name)
    { 
        # Fetch the ACL.
        $Acl = Get-Acl -Path $Name;
        $FilePath = ($Acl.Path -split "::")[1].ToLowerInvariant();

        # Enumerate the owner before moving onto the Access Control Entries (ACEs.)
        [PSCustomObject] @{
            Path = $FilePath;
            AccessControlType = "Allow";        # Strictly speaking, there is no Allow or Deny for the Owner, but it's useful in filtering scenarios, so in it goes.
            IdentityReference = $Acl.Owner;
            FileSystemRights = "Owner";         # From here down, nothing else applies to the Owner, so set them as $null and head out to the ACEs.
            IsInherited = $null;
            InheritanceFlags = $null;
            PropagationFlags = $null;
        } | Export-csv -NoTypeInformation -Path $LogFile -Append;

        #region Step 1: Audit the ACEs, being sure to exclude those that are inherited. After all, you can't remove an inherited ACE.
        foreach ($Ace in $Acl.Access)
        {
            if (-not $Ace.IsInherited)
            {
                # Log the entry first.
                [PSCustomObject] @{
                    Path = $FilePath;
                    AccessControlType = $Ace.AccessControlType;     # Strictly speaking, there is no Allow or Deny for the Owner, but it's useful in filtering scenarios, so in it goes.
                    IdentityReference = $Ace.IdentityReference;
                    FileSystemRights = $Ace.FileSystemRights;       # From here down, nothing else applies to the Owner, so set them as $null and head out to the ACEs.
                    IsInherited = $Ace.IsInherited;
                    InheritanceFlags = $Ace.InheritanceFlags;
                    PropagationFlags = $Ace.PropagationFlags;
                } | Export-csv -NoTypeInformation -Path $LogFile -Append;

                # Then see if we need to remove it.
                if ($MandatoryPrincipals.IdentityReference -notcontains $Ace.IdentityReference)
                {
                    if ($Commit.IsPresent)
                    {
                        Write-Warning -Message "Removing ""$($Ace.IdentityReference)"" from $Name";
                    }
                    else
                    {
                        Write-Warning -Message """$($Ace.IdentityReference)"" would be removed from $Name";
                    }
                }
            }
        }
        #endregion

        #region Step 2: Reset the ACL to conform to the template defined in $MandatoryPrincipals. Not bothering to wrap in a try..catch block since it's the final statement and there's value in letting any exceptions (such as "access denied") surface.
        if ($Commit.IsPresent)
        {
            # Create a new, empty ACL.
            $NewAcl = [System.Security.AccessControl.DirectorySecurity]::new();
            $NewAcl.SetAccessRuleProtection($true, $false);

            # Set the Owner to BUILTIN\Administrators.
            $NewAcl.SetOwner([System.Security.Principal.NTAccount]::new("BUILTIN", "Administrators"));

            # Add the templated access control entries (ACEs) defined in the $MandatoryPrincipals variable.
            foreach ($Ace in $MandatoryPrincipals)
            {
                $null = $NewAcl.AddAccessRule($Ace);
            }

            # Commit the ACL back to the file system.
            Set-Acl -Path $FilePath -AclObject $NewAcl;
        }
        #endregion
    }
}

 

Cheers,

Lain

@LainRobertson 

Thank you for your help, I do apologies that I didn't mention all the requirements at the start. I thought I could work it our myself but clearing I'm still learning. and making mistakes.

I have modified the code fit my purpose:

function Reset-Acl
{
    [cmdletbinding()]
    Param(
        [parameter()][switch]$Commit,
        [parameter()][string]$LogFile = "GDrive_ACL_stats.csv"
    )

    $Folder_name = @("Admin", "LOG");  
    $Path = "C:\"                                      
    $Top_folder = read-host "Please enter Name of you top level folder"                                       # Prompts for Tasking Name to be entered
    
    write-host ""

    if (-not(Test-Path -Path "$($Path)\$Top_folder")) {

    Write-Warning "The $Top_folder folder does not exist in GroupDatae. Please choose option * to create folder top level folder and sub folders in Group Data."
    return

    }

    write-host ""

    $TestMandatorySecurityGroups = @(
        "NT AUTHORITY\SYSTEM",
        "BUILTIN\Administrators",
        "BUILTIN\Users"
    );


    # Remove the CSV file to avoid conflicts from repeated runs.
    Remove-Item -Path $LogFile -Force -ErrorAction:SilentlyContinue;

    foreach($Name in $Folder_name)
    { 

        write-host "---------------------------------------------------------------"
        # Fetch the ACL.
        $Acl = Get-Acl -Path "$($Path)\$Top_folder\$Name";
        $FilePath = ($Acl.Path -split "::")[1].ToLowerInvariant();

        # Enumerate the owner before moving onto the Access Control Entries (ACEs.)
        [PSCustomObject] @{
            Path = $FilePath;
            AccessControlType = "Allow";        # Strictly speaking, there is no Allow or Deny for the Owner, but it's useful in filtering scenarios, so in it goes.
            IdentityReference = $Acl.Owner;
            FileSystemRights = "Owner";         # From here down, nothing else applies to the Owner, so set them as $null and head out to the ACEs.
            IsInherited = $null;
            InheritanceFlags = $null;
            PropagationFlags = $null;
        } | Export-csv -NoTypeInformation -Path $LogFile -Append;

        # Enumerate the ACEs, being sure to exclude those that are inherited. After all, you can't remove an inherited ACE.
        foreach ($Ace in $Acl.Access)
        {
            if (-not $Ace.IsInherited)
            {
                # Log the entry first.
                [PSCustomObject] @{
                    Path = $FilePath;
                    AccessControlType = $Ace.AccessControlType;     # Strictly speaking, there is no Allow or Deny for the Owner, but it's useful in filtering scenarios, so in it goes.
                    IdentityReference = $Ace.IdentityReference;
                    FileSystemRights = $Ace.FileSystemRights;       # From here down, nothing else applies to the Owner, so set them as $null and head out to the ACEs.
                    IsInherited = $Ace.IsInherited;
                    InheritanceFlags = $Ace.InheritanceFlags;
                    PropagationFlags = $Ace.PropagationFlags;
                } | Export-csv -NoTypeInformation -Path $LogFile -Append;

                # Then see if we need to remove it.
                if ($TestMandatorySecurityGroups -notcontains $Ace.IdentityReference)
                {
                    if ($Commit.IsPresent)
                    {
                        Write-Warning -Message "Removing ""$($Ace.IdentityReference)"" from $Name";
                    }
                    else
                    {
                        Write-Warning -Message """$($Ace.IdentityReference)"" would be removed from $Name";
                    }

                    $null = $Acl.RemoveAccessRule($Ace);
                }
            }
        }

        # Commit the ACL changes. Not bothering to wrap in a try..catch block since it's the final statement and there's value in letting any exceptions (such as "access denied") surface.
        if ($Commit.IsPresent)
        {
            Set-Acl -Path $FilePath -AclObject $Acl;
        }
    }

    Write-host ""
    write-host "================ Adding Security Groups to your Folders ================" -ForegroundColor Green -BackgroundColor Black
    Write-host ""

    # Adds the EX/OP folder security group to that folder in Group data
    Write-Host "Added TSK-D_D-TEST_Group Data_$($Top_folder) to $Top_folder folder" -ForegroundColor Green

    $MandatoryPrincipals = 
       # granting Read to Top level folder security group
        [System.Security.AccessControl.FileSystemAccessRule]::new(
            "DC2016\TSK-D_D-TEST_Group Data_$($Top_folder)",
            [System.Security.AccessControl.FileSystemRights]::Read,
            [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit,
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        )

     $acl = Get-Acl -Path "$($Path)\$Top_folder"
     $acl.SetAccessRule($MandatoryPrincipals)
     $acl | Set-Acl -Path "$($Path)\$Top_folder"

    # Adds the security group to sub folders in Group data
    foreach($Name in $Folder_name){

      $MandatoryPrincipals = 
        # access rule granting Read and execute + Write access to sub folder security group.
        [System.Security.AccessControl.FileSystemAccessRule]::new(
            "DC2016\TSK-D_D-TEST_Group Data_$($Top_folder)_$($name)",
            [System.Security.AccessControl.FileSystemRights]::ReadAndExecute -bor [System.Security.AccessControl.FileSystemRights]::Write,
            [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit,
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow
        )
    
      Write-Host "Added TSK-D_D-TEST_Group Data_$($Top_folder)_$($name) to $Name folder" -ForegroundColor Green
      $acl = Get-Acl -Path "$($Path)\$Top_folder\$name"
      $acl.SetAccessRule($MandatoryPrincipals)
      $acl | Set-Acl -Path "$($Path)\$Top_folder\$name"
    }

}


Start-Transcript -Path c:\temp\ps\log\Rest-acl.txt

Reset-Acl -commit

Stop-Transcript



Thanks again for the help.