Set-Acl is causing the error message about not having the 'SeSecurityPrivilege' (technically this isn't the Set-Acl Cmdlet's fault, but instead PowerShell's FileSystem provider). That privilege is only needed if you're trying to modify the System ACL (used
for auditing), and you're not doing that in this case. You actually see this error when the AreAccessRulesProtected on the on-disk security descriptor and AreAuditRulesProtected properties on the in memory security descriptor don't match. Here's a recreation
of the issue:
# Run this in a non-elevated prompt!
# First, create a dummy file
$FilePath = "$env:temp\test_file.txt"
"This is a test" | Out-File -FilePath $FilePath
# This should work just fine
$SD = Get-Acl $FilePath # Get securty descriptor
$SD | Set-Acl # Set SD w/o making any changes
# Notice this returns $true
(Get-Acl $FilePath).AreAccessRulesProtected -eq $SD.AreAuditRulesProtected
# Now disable DACL inheritance:
$SD = Get-Acl $FilePath
$SD.SetAccessRuleProtection($true, $true)
# This is still true, so Set-Acl will work:
(Get-Acl $FilePath).AreAccessRulesProtected -eq $SD.AreAuditRulesProtected
$SD | Set-Acl
# Now the same command that was run earlier will fail:
$SD = Get-Acl $FilePath # Get securty descriptor
$SD | Set-Acl # Set SD w/o making any changes
Running as an elevated admin will hide the issue (you don't see an error, but the SACL will be overwritten, which is a very bad thing if you're trying to audit your filesystem).
The best thing to do is always use the file/folder's SetAccessControl() method. It would look something like this:
$SD = Get-Acl $FilePath
(Get-Item $FilePath).SetAccessControl($SD)
I suggest always avoiding Set-Acl while working with the file system. If you'd like to fix get it fixed, you can vote the Connect bugs
here and
here up.
All that aside, replacing all ACEs like you're asking isn't that hard to do. Of course, you need to be EXTREMELY CAREFUL when trying to do something like that. Just eyeballing it, it looks like your code might actually do what you want (assuming the formatting
errors were introduced when you pasted your code in this post) if you run elevated or change your Set-Acl calls to SetAccessControl() calls. There are at least three things that do stand out as needing some attention, though:
- The code will probably take a 'Deny' ACE and turn it into an ACE granting 'Read' access.
- The code doesn't take inheritance and propagation flags into account (it will create ACEs that apply directly to the folders, which isn't the default for a folder)
- The code will probably create explicit ACEs for each inherited ACE, which will create a mess
What about something like this?
$VerbosePreference = "Continue"
dir C:\path\to\folder -Recurse -Directory | ForEach-Object {
$FSO = $_
Write-Verbose ("Current file system object: {0}" -f $FSO.FullName)
try {
$SD = $FSO | Get-Acl
}
catch {
Write-Error ("Error getting security descriptor for '{0}': {1}" -f $FSO.FullName, $_.Exception.Message)
return
}
# Just look for explicit ACEs:
foreach ($Ace in $SD.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])) {
if ($Ace.AccessControlType -ne "Allow") { continue } # I'm assuming you don't want to loosen Deny ACEs
Write-Verbose (" Found ACE granting '{0}' access to '{1}'; replacing with 'Read' access" -f $Ace.FileSystemRights, $Ace.IdentityReference)
# Create an identical ACE, except with 'Read' access only (this preserves the inheritance and propagation flags)
$ReadAce = New-Object System.Security.AccessControl.FileSystemAccessRule(
$Ace.IdentityReference,
"Read",
$Ace.InheritanceFlags,
$Ace.PropagationFlags,
$Ace.AccessControlType
)
# SetAccessRule will actually replace the existing ACE. No need to call Remove() then Add()
$SD.SetAccessRule($ReadAce)
}
Write-Verbose " Saving changes..."
# You'll have to uncomment this line:
# $FSO.SetAccessControl($SD)
}
Notice that the last line that actually makes changes is commented out. It won't save anything unless you remove the '#', but please make sure you've tested this thoroughly before running it in a production environment.