
Automate a Network Share Permissions Audit with PowerShell
Before you run this
This guide builds a read-only report. The script enumerates the SMB shares on a Windows file server, then lists two things for each one: the share-level permissions (the "who can connect" layer) and the NTFS permissions on the folder behind it (the "who can touch the files" layer). It writes both to CSV so you can review access in a spreadsheet instead of clicking through the Security tab share by share.
It does not change anything. It creates no shares, grants no rights, deletes nothing. There is no undo section at the end because there is nothing to undo — the only thing written is a CSV file you choose the path for.
That said, treat it with normal care:
- It needs an elevated session.
Get-SmbShareandGet-SmbShareAccessreturn data on a stock server, but to see every share and every ACL reliably you want to run this in a PowerShell console started as Administrator, ideally logged in as an account with local admin on the file server. Reading NTFS ACLs on folders your account can't traverse will otherwise throw access-denied errors and leave gaps in the report. - Read the script before you run it, and run it once against a test share on a non-production server so you can see the shape of the output before you point it at a live file server with hundreds of ACEs.
- The
SmbSharemodule used here ships with Windows Server 2012 and later and with Windows 8/10/11. If you are on something older, these cmdlets won't be present.
There is no firewall, routing, or console-lockout risk here — this is a local query, not a config change — so the usual "keep an out-of-band connection open" warning doesn't apply. Just don't confuse this read-only audit script with the ones that set permissions; those deserve far more caution.
What I'm assuming
- A domain-joined Windows Server 2019 or 2022 file server.
- Windows PowerShell 5.1 (the version in the box). The script also runs on PowerShell 7, but 5.1 is what you'll have by default on a server.
- You're running it locally on the file server, in an elevated console. Running against a remote server is covered briefly at the end.
- An output folder exists — I use
C:\Audit\in the examples. Change it to whatever you like; just make sure the folder exists first.
Step 1: List the shares you care about
Start by seeing what's actually shared. This one line lists everything and lets you eyeball it:
Get-SmbShare
You'll see the administrative shares too — C$, ADMIN$, IPC$. Those are hidden shares Windows creates itself; you usually don't want them in a permissions audit. The convention is that their names end in $, so filter them out:
# Grab every non-administrative share (skip C$, ADMIN$, IPC$, etc.)
$shares = Get-SmbShare | Where-Object { $_.Name -notlike '*$' }
$shares | Select-Object Name, Path, Description
Everything below works off that $shares variable.
Step 2: Report the share-level permissions
Share-level permissions are the first gate: they cap what a user can do over the network, regardless of NTFS. Get-SmbShareAccess returns them per share — an account, an allow/deny, and a right of Full, Change, or Read.
$shareReport = foreach ($share in $shares) {
Get-SmbShareAccess -Name $share.Name | ForEach-Object {
[PSCustomObject]@{
ShareName = $_.Name
Path = $share.Path
Account = $_.AccountName
AccessType = $_.AccessControlType # Allow or Deny
AccessRight = $_.AccessRight # Full / Change / Read
}
}
}
# Replace the path with your own audit folder
$shareReport | Export-Csv -Path 'C:\Audit\ShareLevel.csv' -NoTypeInformation -Encoding UTF8
Many shops leave share permissions at the default (Everyone / Full Control) and control access entirely through NTFS. That's a valid design, but the audit should still record it — a Full for Everyone at the share layer means NTFS is doing all the work, and that's worth knowing.
Step 3: Report the NTFS permissions behind each share
The NTFS ACL on the folder is where real access lives. Get-Acl returns it, and each entry in .Access is a rule with an identity, a set of rights, allow/deny, and whether it was inherited.
I wrap the Get-Acl call in a try/catch so one unreadable path doesn't kill the whole run — it records the failure and moves on:
$ntfsReport = foreach ($share in $shares) {
try {
$acl = Get-Acl -Path $share.Path -ErrorAction Stop
foreach ($ace in $acl.Access) {
[PSCustomObject]@{
ShareName = $share.Name
Path = $share.Path
Account = $ace.IdentityReference
Rights = $ace.FileSystemRights
AccessType = $ace.AccessControlType # Allow or Deny
Inherited = $ace.IsInherited
}
}
}
catch {
# Record the share we couldn't read rather than silently skipping it
[PSCustomObject]@{
ShareName = $share.Name
Path = $share.Path
Account = 'ERROR READING ACL'
Rights = $_.Exception.Message
AccessType = ''
Inherited = ''
}
}
}
# Replace the path with your own audit folder
$ntfsReport | Export-Csv -Path 'C:\Audit\NtfsLevel.csv' -NoTypeInformation -Encoding UTF8
A couple of things to read carefully in the output:
FileSystemRightssometimes shows a plain word likeModifyand sometimes a long numeric value or a comma list. That's normal — it reflects how the ACE was built. If you see a bare number, it's a combination of flags that didn't map to a friendly name; PowerShell isn't lying to you, it just can't name that exact bitmask.Inherited = Falseentries are the ones set directly on this folder. Those are usually the interesting ones in an audit — deliberately broken inheritance is where surprises hide.
Step 4: A single combined report (optional)
If you'd rather have one file, keep the two CSVs but also drop a quick on-screen summary so you can spot the obvious problems — Everyone or Authenticated Users with broad rights:
$ntfsReport |
Where-Object { $_.Account -match 'Everyone|Authenticated Users' } |
Format-Table ShareName, Account, Rights, AccessType -AutoSize
This doesn't decide anything for you — it just surfaces the entries most worth a second look.
Running it against a remote file server
The clean, supported way to hit another server is to run the same code inside Invoke-Command, so both the SMB cmdlets and Get-Acl execute on that server against its local paths:
Invoke-Command -ComputerName FILESERVER01 -ScriptBlock {
Get-SmbShare | Where-Object { $_.Name -notlike '*$' } |
ForEach-Object { Get-SmbShareAccess -Name $_.Name }
}
You need PowerShell Remoting (WinRM) enabled and the right to administer that box. Mixing local Get-Acl with a remote UNC path can behave differently because of how Windows evaluates access remotely, so remoting into the server and reading its local paths is the reliable pattern. Check Microsoft Learn for Invoke-Command and for Get-SmbShareAccess if you want the full parameter set.
Verify it worked
The report is only useful if you trust it. Confirm it two ways:
- Open the CSVs.
C:\Audit\ShareLevel.csvandC:\Audit\NtfsLevel.csvshould have one row per permission entry, no empty account columns except any you flagged asERROR READING ACL. If a whole share is missing, check that your account can read its path. - Spot-check against the GUI. Pick one share. Open
fsmgmt.msc(Shared Folders) and compare its Share Permissions tab to yourShareLevel.csvrows. Then right-click the folder in Explorer → Properties → Security and compare toNtfsLevel.csv. They should match line for line. If they do, the whole report is trustworthy.
Because nothing was modified, there's no rollback — delete the CSVs when you're done, or keep them as a dated baseline. Re-running this monthly and diffing the CSVs is a cheap way to catch permission drift before it becomes an incident.
For the underlying cmdlets, see Microsoft Learn for Get-SmbShare, Get-SmbShareAccess, and Get-Acl — each page lists every property these objects return, which is handy when you want to add columns to the report.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
