Created
March 21, 2025 19:08
-
-
Save davidlu1001/5029b933e3ebe624350efd06e3bdbc09 to your computer and use it in GitHub Desktop.
Fix-gMSAService.ps1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# gMSA Service AutoFix Solution | |
# This script creates a reliable startup fix for services using gMSA accounts | |
# Save as Fix-gMSAService.ps1 | |
param( | |
[Parameter(Mandatory = $true)] | |
[string]$ServiceName, | |
[Parameter(Mandatory = $false)] | |
[int]$StartupDelaySeconds = 120, | |
[Parameter(Mandatory = $false)] | |
[switch]$InstallOnly = $false, | |
[Parameter(Mandatory = $false)] | |
[switch]$Verbose = $false | |
) | |
# Function to write detailed logs | |
function Write-VerboseLog { | |
param([string]$Message) | |
if ($Verbose) { | |
Write-Host $Message -ForegroundColor Cyan | |
} | |
} | |
function Install-ServiceFix { | |
param( | |
[string]$ServiceName, | |
[int]$StartupDelaySeconds | |
) | |
Write-Host "Installing gMSA service fix for '$ServiceName'..." -ForegroundColor Yellow | |
# ----- Validation ----- | |
# Check if the service exists | |
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue | |
if ($null -eq $service) { | |
Write-Error "Service '$ServiceName' does not exist. Please verify the service name." | |
return $false | |
} | |
# Check if service is using a gMSA account | |
$serviceWmi = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" | |
$accountName = $serviceWmi.StartName | |
Write-Host "Service logon account: $accountName" -ForegroundColor Cyan | |
if (-not ($accountName -like "*$")) { | |
Write-Warning "The service does not appear to be using a gMSA account (account name doesn't end with $)." | |
$continue = Read-Host "Continue anyway? (Y/N)" | |
if ($continue -ne "Y") { | |
return $false | |
} | |
} | |
# ----- Create Fix Script ----- | |
Write-VerboseLog "Creating service fix script..." | |
# Uniquely identify this service fix | |
$serviceId = $ServiceName -replace '[^a-zA-Z0-9]', '' | |
$scriptName = "gMSAFix_$serviceId.ps1" | |
$scriptPath = "$env:ProgramData\$scriptName" | |
# Create the fix script content | |
$scriptContent = @" | |
# gMSA Service Fix Script for $ServiceName | |
# Generated $(Get-Date) | |
`$ErrorActionPreference = 'Stop' | |
`$logFile = "`$env:TEMP\${serviceId}_gMSA_fix.log" | |
# Create log function | |
function Write-Log { | |
param(`$message) | |
`$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
"`$timestamp - `$message" | Out-File -FilePath `$logFile -Append -Force | |
# Also write to event log for tracking | |
try { | |
if (-not [System.Diagnostics.EventLog]::SourceExists("gMSAServiceFix")) { | |
New-EventLog -LogName Application -Source "gMSAServiceFix" | |
} | |
Write-EventLog -LogName Application -Source "gMSAServiceFix" -EntryType Information -EventId 1000 -Message `$message | |
} catch { | |
# Continue even if event log writing fails | |
} | |
} | |
# Clear previous log if it exists | |
if (Test-Path `$logFile) { | |
Remove-Item `$logFile -Force | |
} | |
Write-Log "Starting gMSA service fix for '$ServiceName'" | |
try { | |
# First, ensure Active Directory services are available | |
Write-Log "Ensuring AD services are available..." | |
# Test domain connectivity with timeout | |
`$domainReady = `$false | |
`$attempt = 1 | |
`$maxAttempts = 10 | |
while (-not `$domainReady -and `$attempt -le `$maxAttempts) { | |
try { | |
Write-Log "AD connectivity attempt `$attempt of `$maxAttempts..." | |
# Try to get domain controller | |
`$dc = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().DomainControllers[0].Name | |
Write-Log "Successfully contacted domain controller: `$dc" | |
`$domainReady = `$true | |
} | |
catch { | |
Write-Log "Domain not yet available. Waiting 5 seconds... (Error: `$_)" | |
Start-Sleep -Seconds 5 | |
`$attempt++ | |
} | |
} | |
if (-not `$domainReady) { | |
Write-Log "Failed to connect to domain after `$maxAttempts attempts. Will try to reset service anyway." | |
} | |
# Check if service exists | |
Write-Log "Checking if service exists..." | |
`$service = Get-Service -Name "$ServiceName" -ErrorAction SilentlyContinue | |
if (`$null -eq `$service) { | |
Write-Log "ERROR: Service '$ServiceName' not found!" | |
exit 1 | |
} | |
# Get current service status | |
Write-Log "Current service status: `$(`$service.Status)" | |
# If service is running, we don't need to fix it | |
if (`$service.Status -eq "Running") { | |
Write-Log "Service is already running. No fix needed." | |
exit 0 | |
} | |
# Get service WMI object | |
Write-Log "Getting service WMI object..." | |
`$serviceWmi = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" | |
`$accountName = `$serviceWmi.StartName | |
Write-Log "Service logon account: `$accountName" | |
# Reset the service credentials (this is the key fix) | |
Write-Log "Resetting service credentials..." | |
`$result = `$serviceWmi.Change( | |
`$null, # DisplayName | |
`$null, # PathName | |
`$null, # ServiceType | |
`$null, # ErrorControl | |
`$null, # StartMode | |
`$null, # DesktopInteract | |
`$accountName, # StartName (reuse current account name) | |
`$null, # StartPassword (null forces Windows to retrieve gMSA password) | |
`$null, # LoadOrderGroup | |
`$null, # LoadOrderGroupDependencies | |
`$null # ServiceDependencies | |
) | |
if (`$result.ReturnValue -eq 0) { | |
Write-Log "Successfully reset service credentials" | |
# Try to start the service | |
Write-Log "Attempting to start service..." | |
try { | |
Start-Service -Name "$ServiceName" | |
Start-Sleep -Seconds 3 | |
# Check service status again | |
`$service = Get-Service -Name "$ServiceName" | |
Write-Log "Service status after start attempt: `$(`$service.Status)" | |
if (`$service.Status -eq "Running") { | |
Write-Log "SUCCESS: Service started successfully!" | |
exit 0 | |
} else { | |
Write-Log "WARNING: Service not in Running state. Current state: `$(`$service.Status)" | |
# One more attempt with a delayed start, in case we need to wait for dependencies | |
Write-Log "Attempting one more delayed start..." | |
Start-Sleep -Seconds 10 | |
Start-Service -Name "$ServiceName" -ErrorAction SilentlyContinue | |
Start-Sleep -Seconds 5 | |
# Final check | |
`$service = Get-Service -Name "$ServiceName" | |
if (`$service.Status -eq "Running") { | |
Write-Log "SUCCESS: Service started successfully on second attempt!" | |
exit 0 | |
} else { | |
Write-Log "ERROR: Service failed to start, final status: `$(`$service.Status)" | |
exit 1 | |
} | |
} | |
} catch { | |
Write-Log "ERROR starting service: `$_" | |
exit 1 | |
} | |
} else { | |
Write-Log "ERROR: Failed to reset service credentials. Return value: `$(`$result.ReturnValue)" | |
exit 1 | |
} | |
} catch { | |
Write-Log "CRITICAL ERROR: `$_" | |
exit 1 | |
} | |
"@ | |
# Save the script | |
$scriptContent | Out-File -FilePath $scriptPath -Encoding UTF8 -Force | |
Write-Host "Created service fix script at: $scriptPath" -ForegroundColor Green | |
# ----- Create Scheduled Task ----- | |
Write-VerboseLog "Creating scheduled task..." | |
$taskName = "gMSAFix_$serviceId" | |
# Check if task already exists and remove it | |
$existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue | |
if ($existingTask) { | |
Write-VerboseLog "Removing existing task..." | |
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | |
} | |
# Create a startup trigger with specified delay | |
$trigger = New-ScheduledTaskTrigger -AtStartup | |
$trigger.Delay = "PT${StartupDelaySeconds}S" # Format: PT<seconds>S | |
# Create the action to run the PowerShell script | |
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$scriptPath`"" | |
# Run with highest privileges as SYSTEM | |
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest | |
# Create task settings | |
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RunOnlyIfNetworkAvailable | |
# Register the task | |
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null | |
Write-Host "Created scheduled task '$taskName' to run at system startup with a ${StartupDelaySeconds}-second delay" -ForegroundColor Green | |
# ----- Test Key Services ----- | |
Write-VerboseLog "Testing key services..." | |
$keyIsoService = Get-Service -Name "KeyIso" -ErrorAction SilentlyContinue | |
if ($keyIsoService.Status -ne "Running") { | |
Write-Warning "The Key Isolation service (KeyIso) is not running. This service is critical for gMSA functionality." | |
Write-Host "Attempting to start KeyIso service..." -ForegroundColor Yellow | |
Start-Service -Name "KeyIso" -ErrorAction SilentlyContinue | |
} | |
# ----- Add Service Dependency ----- | |
# This ensures our service starts after the Key Distribution Service | |
Write-VerboseLog "Setting service dependencies..." | |
try { | |
$currentDependencies = (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceName" -Name "DependOnService" -ErrorAction SilentlyContinue).DependOnService | |
if ($null -eq $currentDependencies) { | |
$currentDependencies = @() | |
} | |
# Add KeyIso dependency if it doesn't exist | |
if (-not $currentDependencies.Contains("KeyIso")) { | |
$newDependencies = $currentDependencies + @("KeyIso") | |
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceName" -Name "DependOnService" -Value $newDependencies | |
Write-Host "Added KeyIso service dependency to ensure proper startup order" -ForegroundColor Green | |
} | |
} | |
catch { | |
Write-Warning "Could not modify service dependencies: $_" | |
} | |
return $true | |
} | |
function Test-ServiceNow { | |
param([string]$ServiceName) | |
Write-Host "Testing service fix immediately..." -ForegroundColor Yellow | |
# Get the script path | |
$serviceId = $ServiceName -replace '[^a-zA-Z0-9]', '' | |
$scriptPath = "$env:ProgramData\gMSAFix_$serviceId.ps1" | |
if (-not (Test-Path $scriptPath)) { | |
Write-Error "Fix script not found at: $scriptPath" | |
return $false | |
} | |
# Execute the script | |
try { | |
Write-Host "Executing fix script..." -ForegroundColor Cyan | |
& PowerShell.exe -NoProfile -ExecutionPolicy Bypass -File "$scriptPath" | |
# Check result | |
$service = Get-Service -Name $ServiceName | |
if ($service.Status -eq "Running") { | |
Write-Host "SUCCESS: Service is now running!" -ForegroundColor Green | |
return $true | |
} | |
else { | |
Write-Host "WARNING: Service is not running. Current status: $($service.Status)" -ForegroundColor Yellow | |
return $false | |
} | |
} | |
catch { | |
Write-Error "Error executing fix script: $_" | |
return $false | |
} | |
} | |
# Main execution | |
if (Install-ServiceFix -ServiceName $ServiceName -StartupDelaySeconds $StartupDelaySeconds) { | |
Write-Host "`ngMSA service fix for '$ServiceName' has been successfully installed." -ForegroundColor Green | |
Write-Host "The fix will run automatically after every system reboot with a ${StartupDelaySeconds}-second delay." -ForegroundColor Cyan | |
# Test immediately unless InstallOnly is specified | |
if (-not $InstallOnly) { | |
Write-Host "`n=================================================" -ForegroundColor Yellow | |
if (Test-ServiceNow -ServiceName $ServiceName) { | |
Write-Host "`nImmediate fix test passed. The service is now running." -ForegroundColor Green | |
Write-Host "The fix will also run automatically after every reboot." -ForegroundColor Green | |
} | |
else { | |
Write-Host "`nImmediate fix test did not start the service." -ForegroundColor Yellow | |
Write-Host "Please check the service configuration and try again." -ForegroundColor Yellow | |
Write-Host "Run this command for detailed manual testing:" -ForegroundColor Yellow | |
Write-Host "PowerShell.exe -NoProfile -ExecutionPolicy Bypass -File `"$env:ProgramData\gMSAFix_$($ServiceName -replace '[^a-zA-Z0-9]', '').ps1`"" -ForegroundColor Cyan | |
} | |
} | |
# Display usage instructions | |
Write-Host "`n=================================================" -ForegroundColor Yellow | |
Write-Host "USAGE INSTRUCTIONS:" -ForegroundColor Yellow | |
Write-Host "1. To fix the service manually anytime:" | |
Write-Host " PowerShell.exe -NoProfile -ExecutionPolicy Bypass -File `"$env:ProgramData\gMSAFix_$($ServiceName -replace '[^a-zA-Z0-9]', '').ps1`"" | |
Write-Host "2. To uninstall the fix:" | |
Write-Host " Unregister-ScheduledTask -TaskName `"gMSAFix_$($ServiceName -replace '[^a-zA-Z0-9]', '')`" -Confirm:`$false" | |
Write-Host "3. To view fix logs:" | |
Write-Host " Get-Content -Path `"`$env:TEMP\$($ServiceName -replace '[^a-zA-Z0-9]', '')_gMSA_fix.log`"" | |
Write-Host "=================================================" -ForegroundColor Yellow | |
} | |
else { | |
Write-Host "Failed to install gMSA service fix." -ForegroundColor Red | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment