This article came about from my previous posts about excessive SYSVOL open files on the domain controllers that were reserving read locks and in some rare cases write locks, this article is here
This could be particularly troublesome if you’re trying to update a Group policy object when you get random access denied or file is already use error messages, obviously, as I have touched with other posts, these error messages can sometimes be very misleading and confusing.
Note : This script has been adapted for Domain Controllers - but it quite easily be retro fitted to apply to any SMB server - where you can communicate on port TCP:445
To way this script works is as follows:
- Enter the Name of a GPO
- Script search for that GPO name
- Presents a list of GPO objects with numbers next to them
- User selects a number
- Script confirm the name and GUID of the GPO
- User then chooses the Domain Controller where the "open locks" should be close
- Script confirm Domain Controller selected
- Script then confirms all the "open locks" that will be closed
- User confirm this with a Yes/No
- Script ask user if they would like to clear "open locks" on other domain controllers
- Script terminates if "no" is selected, and returns to step 6 is "no is chosen.
Lets get some visuals of that for reference, so we first start with the GPO selection
Lets use "Default Domain Policy" as the example, so enter that into the search box:
The script will then confirm the GPO and the GUID and ask you which DC you would like to check for "open locks" you should select the DC you are editing the GPO from, find that DC number and enter that option:
This will then connect to that DC and list all the "open locks" that can be closed on that server as below:
Then when the "open locks" list is fully displayed you will then get the option to choose to close all the open files, after which you will be asked if you would like to apply this to other DC's, if not the script will quit as below:
Script : GPOOpenFiles.ps1
# Requires -RunAsAdministrator
# Requires -Modules GroupPolicy, ActiveDirectory
[CmdletBinding(SupportsShouldProcess=$true)]
param()
# Get script directory
$ScriptPath = $PSScriptRoot
$ScriptPath = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
}
# Initialize logging
$Timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$script:ScriptLogFile = Join-Path -Path $ScriptPath -ChildPath "GPOSessionLog_${Timestamp}.log"
#$script:TranscriptFile = Join-Path -Path $ScriptPath -ChildPath "GPOSessionTranscript_${Timestamp}.log"
$script:TranscriptStarted = $false
function Write-LogMessage {
param(
[Parameter(Mandatory=$true)]
[string]$Message,
[Parameter(Mandatory=$false)
[ValidateSet('Info', 'Warning', 'Error', 'Success')]
[string]$Type = 'Info'
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logMessage = "[${timestamp}] [${Type}] ${Message}"
# Define colors for different message types
$colors = @{
'Info' = 'White'
'Warning' = 'Yellow'
'Error' = 'Red'
'Success' = 'Green'
}
# Write to console with appropriate color
Write-Host $logMessage -ForegroundColor $colors[$Type]
# Write to script local log file
$logMessage | Out-File -FilePath $script:ScriptLogFile -Append
}
function Stop-Logging {
if ($script:TranscriptStarted) {
Stop-Transcript
$script:TranscriptStarted = $false
}
}
function Test-AdminPrivileges {
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object System.Security.Principal.WindowsPrincipal($identity)
return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-MatchingGPOs {
param(
[Parameter(Mandatory=$true)]
[string]$SearchPattern
)
Write-LogMessage "Searching for GPOs matching pattern: ${SearchPattern}" -Type Info
try {
$gpos = Get-GPO -All | Where-Object { $_.DisplayName -like "*${SearchPattern}*" }
Write-LogMessage "Found $($gpos.Count) matching GPOs" -Type Success
return $gpos
}
catch {
$errorMessage = $_.Exception.Message
return $null
}
}
function Show-GPOSelection {
param(
[Parameter(Mandatory=$true)]
[Microsoft.GroupPolicy.Gpo[]]$GPOs
)
Write-LogMessage "Displaying GPO selection menu" -Type Info
Write-Host "`nMatching Group Policy Objects:`n" -ForegroundColor Cyan
for ($i = 0; $i -lt $GPOs.Count; $i++) {
Write-Host "$($i + 1). $($GPOs[$i].DisplayName)"
}
do {
$selection = Read-Host "`nEnter the number of the GPO you want to manage(1-$($GPOs.Count))"
$index = [int]$selection - 1
} while ($index -lt 0 -or $index -ge $GPOs.Count)
return $GPOs[$index]
}
function Close-GPOSessions {
param(
[Parameter(Mandatory=$true)]
[string]$GPOGUID
)
try {
# Get domain controllers
$dcs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
Write-LogMessage "Found $($dcs.Count) domain controllers" -Type Info
# Display DC selection
Write-Host "`n=== Available Domain Controllers ===" -ForegroundColor Cyan
for ($i = 0; $i -lt $dcs.Count; $i++) {
Write-Host "$($i + 1). $($dcs[$i])"
}
Write-Host "`nSelect Domain Controllers (comma-separated numbers, or 'A' for all):"
$selection = Read-Host
if ($selection -ne 'A') {
$selectedIndices = $selection.Split(',') | ForEach-Object { [int]$_.Trim() - 1 }
$dcs = $selectedIndices | ForEach-Object { $dcs[$_] }
}
Write-LogMessage "Will process these DCs: $($dcs -join ', ')" -Type Info
foreach ($dc in $dcs) {
Write-Host "`nAnalyzing DC: ${dc}" -ForegroundColor Yellow
try {
$session = New-CimSession -ComputerName $dc -ErrorAction Stop -Verbose:$false
# Get and display open files
$openFiles = Get-SmbOpenFile -CimSession $session -Verbose:$false |
Where-Object {
$_.Path -like "*\Policies\{$GPOGUID}*" -or
$_.Path -like "*\GPO\{$GPOGUID}*"
}
Write-Host "Files that will be closed on ${dc}:" -ForegroundColor Cyan
if ($openFiles) {
foreach ($file in $openFiles) {
Write-Host " - Path: $($file.Path)"
Write-Host " User: $($file.ClientUserName)"
Write-Host " Open Since: $($file.CreationTime)"
}
Write-Host "`nFound $($openFiles.Count) sessions to close."
} else {
Write-Host " No matching files found"
}
Write-Host "`nWould you like to close these sessions on ${dc}? (Y/N)"
$confirm = Read-Host
if ($confirm -eq 'Y') {
foreach ($file in $openFiles) {
try {
Close-SmbOpenFile -Force -InputObject $file -CimSession $session -Verbose:$false
Write-LogMessage "Closed: $($file.Path)" -Type Success
}
catch {
Write-LogMessage "Failed to close: $($file.Path). Error: $($_.Exception.Message)" -Type Error
}
}
Write-LogMessage "Completed closing sessions on ${dc}" -Type Success
}
else {
Write-LogMessage "Skipped closing sessions on ${dc}" -Type Info
}
Remove-CimSession -CimSession $session -Verbose:$false
}
catch {
Write-LogMessage "Failed to process DC ${dc}. Error: $($_.Exception.Message)" -Type Error
}
}
Write-Host "`nWould you like to process additional Domain Controllers? (Y/N)"
$processMore = Read-Host
return ($processMore -eq 'Y')
}
catch {
Write-LogMessage "Error in Close-GPOSessions: $($_.Exception.Message)" -Type Error
return $false
}
}
# Main script execution
Write-LogMessage "Script execution started" -Type Info
Write-LogMessage "Log file location: ${ScriptLogFile}" -Type Info
#Write-LogMessage "Transcript file location: ${TranscriptFile}" -Type Info
# Check for admin privileges
if (-not (Test-AdminPrivileges)) {
Write-LogMessage "This script requires administrative privileges to run. Please restart PowerShell as an administrator." -Type Error
Stop-Logging
exit
}
Write-LogMessage "Administrative privileges confirmed" -Type Success
try {
do {
# Get search pattern from user
Write-LogMessage "Prompting user for GPO search pattern" -Type Info
$searchPattern = Read-Host "Enter partial name of the Group Policy Object to search for"
# Get matching GPOs
$matchingGPOs = Get-MatchingGPOs -SearchPattern $searchPattern
if (-not $matchingGPOs) {
Write-LogMessage "No GPOs found matching '${searchPattern}'" -Type Warning
Stop-Logging
exit
}
# Show GPO selection and get user choice
$selectedGPO = Show-GPOSelection -GPOs $matchingGPOs
# Display selected GPO details
Write-LogMessage "Selected GPO Details:" -Type Info
Write-LogMessage "Name: $($selectedGPO.DisplayName)" -Type Info
Write-LogMessage "GUID: {$($selectedGPO.Id)}" -Type Info
# Process DC sessions
$continueProcessing = Close-GPOSessions -GPOGUID $selectedGPO.Id
} while ($continueProcessing)
Write-LogMessage "Script execution completed successfully" -Type Success
}
catch {
Write-LogMessage "An unexpected error occurred: $($_.Exception.Message)" -Type Error
Write-LogMessage "Stack Trace: $($_.ScriptStackTrace)" -Type Error
}
finally {
Write-LogMessage "Script execution ended" -Type Info
Stop-Logging
}