The Audit That Found 47 Ghost Admins
A manufacturing client came to us eighteen months after their SharePoint 2016 farm went live. They had grown from two site collections to sixty-three. Their IT team had added SharePoint 2016 site collection administrators by hand, one request at a time, through Central Administration. Nobody had ever run a full inventory. When we did, we found forty-seven user accounts with site collection admin rights that belonged to employees who had left the company. Fourteen of those accounts were fully enabled in Active Directory. That is not a SharePoint problem. That is a governance failure with a SharePoint-shaped hole in it.
This checklist exists because that scenario is not unusual. Most organizations running SharePoint 2016 on-premises have never conducted a formal audit of who holds site collection administrator rights across their farm. The Central Administration interface makes individual management easy enough that teams never feel the pressure to automate it — until they need to account for sixty-three site collections at once and the answer becomes humiliating.
Work through each checkpoint below. For every checkpoint, we include a pass/fail criterion, the PowerShell to get you the data you need, and the remediation path if you fail. By the end, you will have a reusable audit framework you can schedule quarterly.
What This Checklist Covers
This checklist targets SharePoint 2016 on-premises farms. The administrative model differs meaningfully from SharePoint Online, where Set-SPOUser handles admin rights through the SharePoint Online Management Shell. For on-premises, you are working with the SharePoint 2016 PowerShell snap-in and the Microsoft.SharePoint.PowerShell module loaded through the SharePoint Management Shell. All examples here assume that shell context.
The checklist has six checkpoints:
- Complete Admin Inventory
- Policy Documentation
- Provisioning Process
- Stale and Orphaned Accounts
- Admin Revocation Process
- Change Logging and Review
Each checkpoint includes a pass criterion, a fail criterion, and the PowerShell to determine which side of that line you are on. Save the scripts. Run them quarterly at minimum. The Microsoft PowerShell documentation covers the foundational module concepts if your team needs a reference point for the SharePoint snap-in syntax.
Checkpoint 1: Complete Admin Inventory
Pass: You can produce a current, accurate list of every site collection administrator across every site collection in your farm, on demand, in under five minutes.
Fail: You cannot produce that list without clicking through Central Administration one site collection at a time.
Most teams fail this one on the first audit. Here is the script we standardized across our managed SharePoint environments. It enumerates every site collection in a web application, pulls the full administrator list from each, and exports the result to CSV for review.
# Load the SharePoint snap-in if not already loaded
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
$webAppUrl = "http://intranet.contoso.com"
$outputPath = "C:\Audit\SiteCollectionAdmins_$(Get-Date -Format 'yyyyMMdd').csv"
$results = @()
Get-SPSite -WebApplication $webAppUrl -Limit All | ForEach-Object {
$site = $_
try {
foreach ($admin in $site.RootWeb.SiteAdministrators) {
$results += [PSCustomObject]@{
SiteURL = $site.Url
LoginName = $admin.LoginName
DisplayName = $admin.DisplayName
Email = $admin.Email
IsSiteAdmin = $admin.IsSiteAdmin
}
}
}
finally {
$site.Dispose()
}
}
$results | Export-Csv -Path $outputPath -NoTypeInformation
Write-Host "Exported $($results.Count) admin records to $outputPath"
A few notes on this script. The -Limit All parameter on Get-SPSite is essential — without it, the cmdlet returns only the first 200 site collections by default. On farms with more than 200 site collections, that silent truncation has burned teams before. The try/finally block with $site.Dispose() is not optional; leaving SPSite objects undisposed causes memory pressure on the application servers over repeated runs. We cover the performance implications of parallel operations in SharePoint PowerShell in more detail in our article on PowerShell ThrottleLimit and optimizing concurrent network operations — the same principles apply when you start scaling this script across multiple web applications.
Run this script, open the CSV, and count the rows. That number is your baseline.
Checkpoint 2: Policy Documentation
Pass: A written policy exists that defines who is authorized to hold site collection administrator rights, under what conditions those rights are granted, and who approves new requests.
Fail: Admins are added when someone asks and a team member has the access to do it.
This is where I take a firm position: site collection administrator is not a convenience role. It grants full control of every document library, list, permission structure, and configuration setting within that site collection. It is the SharePoint equivalent of local administrator on a server. You would not hand that out without a documented approval process, and you should not do it here either.
The policy does not need to be elaborate. It needs to answer four questions: Who can request admin rights? Who approves the request? How long does the access last (time-bound vs. permanent)? What triggers a review? Document those answers, get sign-off from your information security owner, and store the policy somewhere people can find it. Your SharePoint governance team — or if you do not have one, your IT manager — owns this.
Organizations that skip this step end up exactly where our manufacturing client did. The technical fix took us four hours. The conversation with their legal team about the fourteen enabled ex-employee accounts took considerably longer.
Checkpoint 3: Provisioning Process
Pass: Admin rights are granted through a repeatable, documented PowerShell process, not manually through Central Administration.
Fail: Provisioning happens through the GUI, with no record of who made the change or when.
Central Administration makes it easy to add an administrator to a single site collection. That ease is the problem. There is no audit log entry in the SharePoint ULS logs specific enough to tell you which admin made the change. There is no ticket number. There is no second set of eyes. The right approach is a provisioning script that takes a username and site URL as parameters, enforces your naming convention, logs the action to a text file or your ITSM platform, and sends a confirmation to the requestor.
Granting Site Collection Admin Rights via PowerShell
function Add-SPSiteCollectionAdmin {
param(
[Parameter(Mandatory)][string]$SiteUrl,
[Parameter(Mandatory)][string]$LoginName,
[string]$TicketNumber = "MANUAL"
)
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
$site = Get-SPSite $SiteUrl
try {
$user = $site.RootWeb.EnsureUser($LoginName)
$user.IsSiteAdmin = $true
$user.Update()
$logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | GRANT | $SiteUrl | $LoginName | Ticket: $TicketNumber | By: $env:USERNAME"
Add-Content -Path "C:\Audit\AdminChanges.log" -Value $logEntry
Write-Host "Admin rights granted to $LoginName on $SiteUrl"
}
finally {
$site.Dispose()
}
}
The EnsureUser method deserves a note: it either retrieves the existing user profile or creates a new one in the site’s user information list if the account has not previously accessed the site. This is the correct approach for provisioning rather than assuming the user object already exists. Pass a ticket number from your change management system as the -TicketNumber parameter and your log file becomes a lightweight but traceable audit trail.
For bulk provisioning — say, standing up a new departmental SharePoint environment with multiple site collections — you can pipe this function against a CSV. We do exactly this when onboarding new SharePoint environments for clients, combining it with New-SPSite to create the site collections and wire up the initial admin accounts in a single repeatable script. The Microsoft Learn documentation for New-SPSite covers the full parameter set, including -OwnerAlias for the primary site collection owner at creation time.
Checkpoint 4: Stale and Orphaned Accounts
Pass: No disabled Active Directory accounts hold site collection admin rights. Admin access is reviewed when a user leaves the organization.
Fail: You have never cross-referenced your SharePoint admin list against Active Directory account status.
This is the checkpoint that hurts the most on first audit. Stale admin accounts accumulate because the off-boarding process does not include a SharePoint step. HR disables the AD account. IT disables the mailbox. Nobody touches SharePoint. The account cannot log in interactively, but depending on your authentication configuration it may still be reachable through service accounts or legacy authentication paths. More importantly, it represents an access control gap that any compliance auditor will flag immediately.
Cross-Referencing Admins Against Active Directory
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
Import-Module ActiveDirectory
$webAppUrl = "http://intranet.contoso.com"
$staleAccounts = @()
Get-SPSite -WebApplication $webAppUrl -Limit All | ForEach-Object {
$site = $_
try {
foreach ($admin in $site.RootWeb.SiteAdministrators) {
# Strip domain prefix for AD lookup
$samAccount = $admin.LoginName -replace '^.*\\', ''
try {
$adUser = Get-ADUser -Identity $samAccount -Properties Enabled -ErrorAction Stop
if (-not $adUser.Enabled) {
$staleAccounts += [PSCustomObject]@{
SiteURL = $site.Url
LoginName = $admin.LoginName
DisplayName = $admin.DisplayName
ADEnabled = $false
}
}
}
catch {
# Account not found in AD at all
$staleAccounts += [PSCustomObject]@{
SiteURL = $site.Url
LoginName = $admin.LoginName
DisplayName = $admin.DisplayName
ADEnabled = "NOT FOUND"
}
}
}
}
finally {
$site.Dispose()
}
}
$staleAccounts | Export-Csv -Path "C:\Audit\StaleAdmins_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
Write-Host "Found $($staleAccounts.Count) stale or orphaned admin accounts"
The output of this script is your remediation list. Any account where ADEnabled is $false or NOT FOUND should be removed from site collection admin access immediately. Do not wait for the next scheduled review. This is a P1-level finding from a governance standpoint.
One caveat worth acknowledging: if your SharePoint environment uses claims-based authentication with a non-AD identity provider, the login name format will differ. The -replace pattern in the script above assumes NTLM/Kerberos Windows integrated authentication. Adjust the parsing logic for your authentication topology before running this in production.
Checkpoint 5: Admin Revocation Process
Pass: Admin rights can be removed from a specific user across all site collections in under ten minutes, with a logged record of the change.
Fail: Revocation requires manual visits to each site collection in Central Administration.
When someone leaves the organization or moves to a different role, revocation speed matters. The longer elevated access persists after it is no longer needed, the larger your attack surface and your compliance gap. A law firm we consult for operates under strict data governance requirements — when an attorney transitions out of a practice group, their SharePoint admin access to that group’s site collections needs to be revoked within the same business day. The only way to meet that SLA consistently is with a scripted revocation process.
Removing Admin Rights Across All Site Collections
function Remove-SPSiteCollectionAdmin {
param(
[Parameter(Mandatory)][string]$LoginName,
[string]$WebApplicationUrl = "http://intranet.contoso.com",
[string]$TicketNumber = "MANUAL"
)
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
$removedFrom = @()
Get-SPSite -WebApplication $WebApplicationUrl -Limit All | ForEach-Object {
$site = $_
try {
$user = $site.RootWeb.SiteUsers | Where-Object { $_.LoginName -eq $LoginName }
if ($user -and $user.IsSiteAdmin) {
$user.IsSiteAdmin = $false
$user.Update()
$removedFrom += $site.Url
$logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | REVOKE | $($site.Url) | $LoginName | Ticket: $TicketNumber | By: $env:USERNAME"
Add-Content -Path "C:\Audit\AdminChanges.log" -Value $logEntry
}
}
finally {
$site.Dispose()
}
}
Write-Host "Removed admin rights from $LoginName on $($removedFrom.Count) site collections"
return $removedFrom
}
This function iterates across all site collections, checks whether the target user holds admin rights in each, and revokes them where found. The function returns the list of affected site collections so your change management ticket can document exactly what was touched. Pair this with your off-boarding runbook and you have a defensible, repeatable process.
Note that this script does not remove the user from the site entirely — it only removes the site collection administrator flag. If your policy requires full user removal, add a $user.Delete() call after the update, but do so deliberately. Deleting a user from a site will re-assign their content to the system account, which may not be the behavior you want for active content they created.
Checkpoint 6: Change Logging and Review
Pass: Every grant and revocation of site collection admin rights is logged with a timestamp, the affected account, the site collection URL, and the identity of the person who made the change. Logs are reviewed quarterly.
Fail: You are relying on SharePoint’s ULS logs or the Windows Event Log to reconstruct admin changes after the fact.
The provisioning and revocation scripts above both write to C:\Audit\AdminChanges.log. That file is the beginning of your audit trail, not the end of it. At minimum, that log file should be:
- Stored on a path accessible to your monitoring infrastructure, not just the SharePoint server itself
- Included in your standard backup job
- Reviewed by your IT manager or security team on a quarterly schedule
- Cross-referenced against your ITSM ticket history to verify every change has a corresponding approved ticket
If you have a SIEM platform in your environment, forward the log entries there. If your change management process uses a tool like ServiceNow or Jira Service Management, consider writing the log entries directly to those systems via API rather than a flat file. The flat file approach is pragmatic for teams getting started, but it does not scale to enterprise compliance requirements indefinitely.
For organizations subject to frameworks like NIST Cybersecurity Framework, the Protect and Detect functions both require documented access control processes and audit log retention. A SharePoint admin audit trail that you can produce on demand satisfies a meaningful portion of those control requirements, provided your log retention meets your framework’s minimum period.
Scheduling the Audit
Running this checklist once is useful. Running it quarterly is a program. The inventory and stale account scripts above are straightforward candidates for scheduled tasks on your SharePoint application server. Set them to run on the first Monday of each quarter, write the output CSVs to a shared network location, and assign a team member to review the results and open remediation tickets for any findings.
If you want to go further, the AdminChanges.log file from the provisioning and revocation scripts gives you delta data between audit runs. Build a simple comparison script that identifies net-new admin accounts that appeared since the last audit date. Any account that appears in the current inventory but does not have a corresponding log entry is a gap — someone used Central Administration to make a change outside your controlled process. That gap is itself a finding.
We also recommend maintaining a separate record at the site collection provisioning level. When a new site collection is created with New-SPSite and the -OwnerAlias parameter, log that at creation time. Your admin inventory then has a natural baseline: the primary owner is the original provisioning record, and every subsequent grant beyond that should have a corresponding ticket. This is the kind of operational discipline that makes compliance audits boring in the best possible way.
For environments where you manage Group Policy alongside SharePoint permissions — particularly around access control for the SharePoint server infrastructure itself — our article on backing up and restoring Group Policy Objects with PowerShell covers the complementary discipline of keeping your GPO configurations documented and recoverable.
Audit Summary: Scoring Your Checkpoints
Six checkpoints. Here is how to read your results:
- 6/6 Pass: You have a mature SharePoint governance posture. Schedule your next quarterly review and move on.
- 4–5 Pass: Solid foundation with gaps. Prioritize any failure on Checkpoints 1 or 4 — inventory and stale accounts — as those represent active risk.
- 2–3 Pass: You have the tools, but not the process. Focus on Checkpoints 2 and 6 — policy and logging — before automating further. Automation without policy just automates the chaos.
- 0–1 Pass: Start with the inventory script. You cannot govern what you cannot see. Run Checkpoint 1 today, then work through the list in order.
SharePoint 2016 site collection administrator sprawl is one of the most common findings we see in environments that have been running for more than two years. It is not a sign of negligence — it is a sign of growth without governance. The fix is not complex, but it does require someone to own the process. Assign it, schedule it, and run it.
If your team needs help standing up a SharePoint governance framework or running an initial audit, reach out to us at SSE. We have run this process across farms of every size, and we have the scripts, templates, and runbooks to get you from zero to auditable in a single engagement.


