THE PROBLEM
File share permissions are where good intentions go to die.
Somebody needs quick access to a folder. Adding them to the right group means a ticket, an approval, and a wait. Granting Everyone full control takes four seconds and the problem goes away. Nobody comes back to fix it, because from the user's perspective nothing is broken.
Multiply that across five years and a few staff changes, and you end up with shares that expose HR documents to the entire domain, finance folders inheriting permissions nobody intended, and at least one share where Everyone has Full Control on something genuinely sensitive.
The reason this never gets cleaned up is that checking it manually is brutal. Right-click, Properties, Sharing, Advanced Sharing, Permissions, then Security tab, then Advanced, then repeat for every share on every server. Nobody does that voluntarily.
This week we automate the whole thing.
THE SCRIPT
Save the script below to:
C:\Scripts\SharePermissionAudit.ps1powershell
# File Share & NTFS Permission Auditor
# Automate & Operate — automateandoperate.com
# Email settings — update these
$from = "[email protected]"
$to = "[email protected]"
$smtpServer = "your.smtp.server"
$smtpPort = 587
$username = "[email protected]"
$password = ConvertTo-SecureString "yourpassword" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
$logFile = "C:\Logs\SharePermissionAudit.log"
$reportFile = "C:\Logs\SharePermissionReport.csv"
# File servers to audit
$servers = @(
"localhost",
"FILESERVER01",
"FILESERVER02"
)
# Shares to skip — administrative and print shares
$ignoreShares = @(
"ADMIN$", "IPC$", "print$", "SYSVOL", "NETLOGON"
)
# Identities that should never have broad access to a data share
$riskyIdentities = @(
"Everyone",
"BUILTIN\Users",
"NT AUTHORITY\Authenticated Users",
"NT AUTHORITY\ANONYMOUS LOGON",
"Domain Users"
)
# Rights considered dangerous when held by a risky identity
$riskyRights = @(
"FullControl",
"Modify",
"Write",
"ChangePermissions",
"TakeOwnership"
)
# How many subfolder levels below the share root to check NTFS on
# 0 = share root only. 1 or 2 is usually plenty. Higher gets slow fast.
$subfolderDepth = 1
# Create log folder if missing
if (-not (Test-Path "C:\Logs")) {
New-Item -ItemType Directory -Path "C:\Logs"
}
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$report = @()
$findings = @()
foreach ($server in $servers) {
try {
$shareData = Invoke-Command -ComputerName $server -ArgumentList $ignoreShares, $subfolderDepth -ScriptBlock {
param($ignoreShares, $subfolderDepth)
$results = @()
$shares = Get-SmbShare | Where-Object {
$ignoreShares -notcontains $_.Name -and $_.Path -ne ""
}
foreach ($share in $shares) {
# ---- Share-level permissions ----
try {
$shareAccess = Get-SmbShareAccess -Name $share.Name -ErrorAction Stop
foreach ($access in $shareAccess) {
$results += [PSCustomObject]@{
ShareName = $share.Name
SharePath = $share.Path
Scope = "SHARE"
TargetPath = $share.Path
Identity = $access.AccountName
Rights = "$($access.AccessRight)"
Type = "$($access.AccessControlType)"
Inherited = "N/A"
}
}
} catch {
$results += [PSCustomObject]@{
ShareName = $share.Name
SharePath = $share.Path
Scope = "SHARE"
TargetPath = $share.Path
Identity = "ERROR READING SHARE ACL"
Rights = "$_"
Type = "N/A"
Inherited = "N/A"
}
}
# ---- Build list of paths to check NTFS on ----
$pathsToCheck = @($share.Path)
if ($subfolderDepth -gt 0 -and (Test-Path $share.Path)) {
try {
$subFolders = Get-ChildItem -Path $share.Path -Directory -Depth ($subfolderDepth - 1) -ErrorAction SilentlyContinue
$pathsToCheck += $subFolders.FullName
} catch { }
}
# ---- NTFS permissions ----
foreach ($path in $pathsToCheck) {
try {
$acl = Get-Acl -Path $path -ErrorAction Stop
foreach ($ace in $acl.Access) {
$results += [PSCustomObject]@{
ShareName = $share.Name
SharePath = $share.Path
Scope = "NTFS"
TargetPath = $path
Identity = $ace.IdentityReference.Value
Rights = "$($ace.FileSystemRights)"
Type = "$($ace.AccessControlType)"
Inherited = "$($ace.IsInherited)"
}
}
} catch {
$results += [PSCustomObject]@{
ShareName = $share.Name
SharePath = $share.Path
Scope = "NTFS"
TargetPath = $path
Identity = "ERROR READING NTFS ACL"
Rights = "$_"
Type = "N/A"
Inherited = "N/A"
}
}
}
}
return $results
} -ErrorAction Stop
foreach ($entry in $shareData) {
# Decide whether this entry is a finding
$isRiskyIdentity = $false
foreach ($risky in $riskyIdentities) {
if ($entry.Identity -like "*$risky") { $isRiskyIdentity = $true; break }
}
$isRiskyRight = $false
foreach ($right in $riskyRights) {
if ($entry.Rights -like "*$right*") { $isRiskyRight = $true; break }
}
$isAllow = $entry.Type -eq "Allow"
$severity = if ($isRiskyIdentity -and $isRiskyRight -and $isAllow) {
if ($entry.Rights -like "*FullControl*") { "HIGH" } else { "MEDIUM" }
} elseif ($isRiskyIdentity -and $isAllow) {
"LOW"
} else {
"OK"
}
$record = [PSCustomObject]@{
Server = $server
ShareName = $entry.ShareName
SharePath = $entry.SharePath
Scope = $entry.Scope
TargetPath = $entry.TargetPath
Identity = $entry.Identity
Rights = $entry.Rights
Type = $entry.Type
Inherited = $entry.Inherited
Severity = $severity
}
$report += $record
if ($severity -eq "HIGH" -or $severity -eq "MEDIUM") {
$findings += $record
}
}
Add-Content -Path $logFile -Value "$timestamp — $server — $($shareData.Count) ACL entries reviewed"
} catch {
$report += [PSCustomObject]@{
Server = $server
ShareName = "UNREACHABLE"
SharePath = "N/A"
Scope = "N/A"
TargetPath = "N/A"
Identity = "N/A"
Rights = "N/A"
Type = "N/A"
Inherited = "N/A"
Severity = "ERROR"
}
Add-Content -Path $logFile -Value "$timestamp — $server — ERROR: $_"
}
}
# Export full report
$report | Sort-Object Severity, Server, ShareName | Export-Csv -Path $reportFile -NoTypeInformation
$highFindings = $findings | Where-Object { $_.Severity -eq "HIGH" }
$medFindings = $findings | Where-Object { $_.Severity -eq "MEDIUM" }
# ---------- BUILD EMAIL ----------
$emailBody = "File Share Permission Audit — $timestamp`n"
$emailBody += "Servers audited: $($servers.Count)`n"
$emailBody += "Subfolder depth checked: $subfolderDepth`n"
$emailBody += "Total ACL entries reviewed: $($report.Count)`n"
$emailBody += "HIGH severity findings: $($highFindings.Count)`n"
$emailBody += "MEDIUM severity findings: $($medFindings.Count)`n`n"
if ($highFindings.Count -gt 0) {
$emailBody += "HIGH — BROAD IDENTITY WITH FULL CONTROL:`n"
$emailBody += "=" * 60 + "`n"
foreach ($f in $highFindings) {
$emailBody += "Server: $($f.Server)`n"
$emailBody += "Share: $($f.ShareName)`n"
$emailBody += "Path: $($f.TargetPath)`n"
$emailBody += "Scope: $($f.Scope)`n"
$emailBody += "Identity: $($f.Identity)`n"
$emailBody += "Rights: $($f.Rights)`n"
$emailBody += "-" * 40 + "`n"
}
$emailBody += "`n"
}
if ($medFindings.Count -gt 0) {
$emailBody += "MEDIUM — BROAD IDENTITY WITH WRITE OR MODIFY:`n"
$emailBody += "=" * 60 + "`n"
foreach ($f in $medFindings) {
$emailBody += "$($f.Server) | $($f.ShareName) | $($f.Scope) | $($f.Identity) | $($f.Rights)`n"
$emailBody += " Path: $($f.TargetPath)`n"
}
$emailBody += "`n"
}
if ($findings.Count -eq 0) {
$emailBody += "No overly permissive share or NTFS entries detected.`n`n"
}
$emailBody += "SHARE INVENTORY:`n"
$emailBody += "=" * 60 + "`n"
$report | Where-Object { $_.Scope -eq "SHARE" } |
Select-Object Server, ShareName, SharePath -Unique |
ForEach-Object { $emailBody += "$($_.Server) — $($_.ShareName) — $($_.SharePath)`n" }
$emailBody += "`nFull report saved to: $reportFile`n"
$emailBody += "`nTo inspect a path in detail:`n"
$emailBody += "(Get-Acl 'PATH').Access | Format-Table IdentityReference, FileSystemRights, AccessControlType, IsInherited`n`n"
$emailBody += "Automate & Operate — automateandoperate.com"
$subject = if ($highFindings.Count -gt 0) {
"SHARE ALERT: $($highFindings.Count) high severity permission finding(s)"
} elseif ($medFindings.Count -gt 0) {
"Share Audit — $($medFindings.Count) medium severity finding(s) to review"
} else {
"Share Permission Audit — No overly permissive entries found"
}
Send-MailMessage `
-From $from -To $to `
-Subject $subject `
-Body $emailBody `
-SmtpServer $smtpServer -Port $smtpPort `
-UseSsl -Credential $credentialUpdate these lines:
$from/$to/$username/$password— your email details$smtpServer— your SMTP server (e.g.smtp.office365.com)$servers— your actual file servers$subfolderDepth— start at 1, see the note below$riskyIdentities— addYOURDOMAIN\Domain Usersin the exact format your environment returns
SHARE PERMISSIONS VS NTFS PERMISSIONS
This trips people up constantly, so it's worth being precise. The script checks both because they're two separate gates.
Share permissions apply only over the network. Most shares are set to Everyone / Full Control at the share level deliberately, with the real restrictions applied via NTFS. That's the standard modern approach and it's not automatically wrong.
NTFS permissions apply everywhere — network and local — and they're the ones that actually enforce your access model.
The effective permission is the more restrictive of the two. So Everyone / Full Control at the share level with tight NTFS underneath is fine. The dangerous combination is broad access on both, or broad access on NTFS regardless of the share.
The Scope column in the CSV tells you which layer each finding came from. Read the NTFS ones first — those are the real exposures.
A WORD ON SUBFOLDER DEPTH
$subfolderDepth = 1 checks the share root and its immediate children. That's usually where broken inheritance and one-off grants live.
Be careful raising this. Depth 3 on a file server with a deep folder tree can mean hundreds of thousands of ACL reads and a script that runs for hours. Start at 1, see how long it takes, and raise it only if you have a specific reason.
If you need a genuinely deep audit of one problem share, run it as a one-off against a single server with a higher depth rather than raising it for the whole environment.
READING THE SEVERITY LEVELS
HIGH — a broad identity like Everyone or Domain Users has Full Control. This is the one to act on. Full Control includes the ability to change permissions, which means a user can grant themselves or others whatever access they want and remove your audit trail while doing it.
MEDIUM — a broad identity has Write or Modify. Often intentional on shared working folders. Verify it's deliberate rather than inherited by accident.
LOW — a broad identity has Read only. Frequently fine. Worth a glance if the share name suggests sensitive content.
The severity is mechanical — it doesn't know whether a folder holds payroll data or the office lunch menu. Context is yours to apply. But the HIGH list is short and specific, which is exactly what makes it actionable.
SETTING UP TASK SCHEDULER
Press Windows key → type Task Scheduler → open it
Click "Create Basic Task" → name it
Share Permission AuditTrigger: Weekly → Sunday → 8:00pm
Action: Start a program
Program:
powershell.exeArguments:
-ExecutionPolicy Bypass -File "C:\Scripts\SharePermissionAudit.ps1"Click Next → Finish
Under Properties → General, check "Run with highest privileges" and use an account with admin rights on the target file servers.
Sunday evening is deliberate — ACL enumeration is I/O heavy and you don't want it competing with users on a Monday morning.
TEST IT
Open PowerShell as administrator and run:
powershell
powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\SharePermissionAudit.ps1"Open C:\Logs\SharePermissionReport.csv and filter the Severity column to HIGH. That's your work list.
To see what the script sees on a single path before running the full audit:
powershell
Get-SmbShare | Where-Object { $_.Path -ne "" } | Format-Table Name, Path
(Get-Acl "C:\YourShare").Access | Format-Table IdentityReference, FileSystemRights, AccessControlType, IsInheritedIf remote servers come back UNREACHABLE, enable PowerShell remoting on each target:
powershell
Enable-PSRemoting -ForceBEFORE YOU CHANGE ANYTHING
Permission changes break things faster than almost any other change you can make, and the breakage often shows up days later when somebody runs a monthly process.
Two rules that will save you:
Never remove an entry you don't understand. Find out what depends on it first. An Authenticated Users grant might be there because a service account needs it and nobody documented that.
Fix inheritance before fixing individual entries. If a subfolder has broken inheritance with a pile of one-off grants, restoring inheritance from a correctly configured parent is cleaner than editing twenty ACEs by hand.
Export the current ACL before you touch it:
powershell
Get-Acl "C:\YourShare" | Export-Clixml "C:\Logs\backup_share_acl.xml"And to restore if you need to:
powershell
$acl = Import-Clixml "C:\Logs\backup_share_acl.xml"
Set-Acl -Path "C:\YourShare" -AclObject $aclWHY THIS MATTERS
Overly permissive shares don't cause outages, which is exactly why they persist. Nothing breaks, no ticket gets raised, no alarm fires. The cost is invisible until it isn't — a ransomware event that encrypts far more than it should have, an insider who reads something they shouldn't, or an audit finding that turns into a remediation project with a deadline.
The operational benefit shows up sooner. Running this once usually reveals shares nobody knew still existed, folders where inheritance was broken years ago, and at least one path where a leaver's individual grant is still sitting in the ACL.
THIS WEEK'S ACTION
Run it against one file server today with depth 1. Filter the CSV to HIGH and count the rows. If that number is above zero — and for most environments running this for the first time, it is — you've found something worth fixing before anyone else finds it for you.
Reply to this email if you hit any issues — I read every reply.
Automate & Operate — automateandoperate.com
Granola Runs Revenue On Attio
"When I think of revenue, I think of Attio." - Shreman Shrestha, Head of Business at Granola
Here's what that adds up to:
Zero missed leads and 10x faster access to customer context
Lead triage 83% faster
Five hours saved per week with automated updates

