I spent some time today experimenting with ways to “debloat” Windows trimming down unnecessary background services that quietly drain performance and slow startup times. While exploring different approaches, I ended up writing a few practical PowerShell scripts to make the process faster, safer, and easier to audit.
Managing Windows services manually through the Services console quickly becomes tedious, especially when you need to repeat tasks across multiple machines. PowerShell solves that problem by offering a simple, repeatable way to inspect, modify, and restore service configurations with structured output that can be logged or shared later.
In this post, I’ll walk through four PowerShell scripts I created to help manage and streamline Windows services effectively. Each script logs results to a CSV file, making it easy to track changes or share insights with others.
Before making changes:
- Create a system restore point or backup your system.
- Test scripts on a non-production machine first.
- Only modify services you understand and intend to change.
- Review the CSV output after running scripts to confirm changes.
These scripts are provided for educational purposes. Use them responsibly and at your own risk.
Listing all Windows services and exporting to CSV
The first script gathers an inventory of all Windows services on a system. It captures the service name, display name, startup type, and current status. Exporting this information to a CSV file creates a baseline snapshot that can be used for documentation, troubleshooting, or comparison later.
This is often the first step before making any service changes. Having a full export allows you to review what is running, what is disabled, and how services are configured across different machines.
# List current service status
# ***************************
$Timestamp = Get-Date -Format "yyyyMMddTHHmmss"
$FilePath = "C:\Temp\ServicesList_$Timestamp.csv"
Get-Service |
Select-Object Name, DisplayName, StartType, Status |
Sort-Object Name |
Export-Csv -Path $FilePath -NoTypeInformation
Disabling a predefined set of services
The second script focuses on change management. An initial array defines the services that should be disabled. The script processes each service in that array, disables it, and records the results in a CSV file.
This approach is safer and more intentional than disabling services one at a time. By working from a defined list, you reduce the risk of accidentally modifying the wrong service and you gain a clear record of what was changed and when.
# Disable Services
# ****************
# List of services to disable (edit this array)
$ServicesToDisable = @(
# Unused legacy features
"Fax",
# Xbox/Gaming (if you don't use Xbox apps)
"XblAuthManager",
"XblGameSave",
"XboxGipSvc",
# Search/Indexing (consider Manual instead if you use search)
# "WSearch",
# Telemetry/Privacy
"DiagTrack",
# Print/Fax/Scan (if you don't print/scan)
# "Spooler",
# Windows Insider (if not in Insider program)
# "wisvc",
# Remote features (if no remote desktop)
# "SessionEnv",
# "TermService",
# Maps/Location
"MapsBroker",
# Biometrics (if no fingerprint/face login)
"WbioSrvc"
)
# Create timestamped CSV log file
$Timestamp = Get-Date -Format "yyyyMMddTHHmmss"
$LogPath = "C:\Temp\ServicesDisabled_$Timestamp.csv"
Write-Host "Disabling AUTO services (1+2)... (Run as Administrator)" -ForegroundColor Yellow
$Results = @()
foreach ($ServiceName in $ServicesToDisable) {
try {
$Service = Get-Service -Name $ServiceName -ErrorAction Stop
# Use WMI for reliable startup type (no sc.exe parsing issues)
$WmiService = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" -ErrorAction Stop
$StartMode = $WmiService.StartMode # Automatic, Manual, Disabled
# Skip ONLY if already Manual or Disabled
if ($StartMode -in @("Manual", "Disabled")) {
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = $Service.DisplayName
StartType = $StartMode
PreviousStatus = $Service.Status
CurrentStatus = $Service.Status
CurrentStartType = $Service.StartType
Result = "SKIPPED - Already safe"
Error = ""
}
Write-Host "- $ServiceName already $StartMode - skipped" -ForegroundColor Gray
continue
}
# Process Automatic services only
if ($StartMode -eq "Auto") {
$PreviousStatus = $Service.Status
if ($PreviousStatus -eq 'Running') {
Stop-Service -Name $ServiceName -Force -ErrorAction Stop
Start-Sleep -Seconds 2
}
Set-Service -Name $ServiceName -StartupType Disabled
$UpdatedService = Get-Service -Name $ServiceName
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = $Service.DisplayName
StartType = $StartMode
PreviousStatus = $PreviousStatus
CurrentStatus = $UpdatedService.Status
CurrentStartType = $UpdatedService.StartType
Result = "DISABLED SUCCESSFULLY"
Error = ""
}
Write-Host "✓ $ServiceName disabled successfully" -ForegroundColor Green
}
}
catch {
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = "N/A"
StartType = "N/A"
PreviousStatus = "N/A"
CurrentStatus = "N/A"
CurrentStartType = "N/A"
Result = "FAILED"
Error = $_.Exception.Message
}
Write-Host "✗ $ServiceName failed: $($_.Exception.Message)" -ForegroundColor Red
}
}
# Export to CSV
$Results | Export-Csv -Path $LogPath -NoTypeInformation
Write-Host "`nCSV saved to: $LogPath" -ForegroundColor Cyan
Write-Host "Review before rebooting!" -ForegroundColor Yellow
Restoring previously disabled services
Any change should be reversible. The third script restores the services that were disabled earlier, returning them to their original startup configuration. The results are again exported to a CSV file so you have confirmation that each service was successfully restored.
This script is especially useful for maintenance windows or testing scenarios where services need to be temporarily disabled and then brought back online.
# Restore Services from csv
# *************************
# Find the most recent ServicesList CSV (or specify path manually)
$ServicesListFiles = Get-ChildItem "C:\Temp\ServicesList_*.csv" | Sort-Object LastWriteTime -Descending
$BackupCsv = $ServicesListFiles[0].FullName
if (-not $BackupCsv) {
Write-Error "No ServicesList CSV found in C:\Temp! Run the export script first."
exit
}
Write-Host "Restoring from: $BackupCsv" -ForegroundColor Cyan
# Create timestamped log file
$Timestamp = Get-Date -Format "yyyyMMddTHHmmss"
$LogPath = "C:\Temp\ServicesRestored_$Timestamp.csv"
# Import backup and track results
$BackupServices = Import-Csv $BackupCsv
$Results = @()
Write-Host "Restoring service startup types... (Run as Administrator)" -ForegroundColor Yellow
foreach ($Service in $BackupServices) {
$ServiceName = $Service.Name
$OriginalStartType = $Service.StartType
try {
$CurrentService = Get-Service -Name $ServiceName -ErrorAction Stop
$WmiService = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" -ErrorAction Stop
# Skip if already matches original
if ($WmiService.StartMode -eq $OriginalStartType) {
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = $Service.DisplayName
OriginalStartType = $OriginalStartType
CurrentStartType = $WmiService.StartMode
Result = "SKIPPED - Already correct"
Error = ""
}
Write-Host "- $ServiceName already $OriginalStartType - skipped" -ForegroundColor Gray
continue
}
# Restore original startup type
$StartTypeMap = @{
"Automatic" = "Automatic"
"AutomaticDelayedStart" = "Automatic"
"Manual" = "Manual"
"Disabled" = "Disabled"
}
$PowerShellStartType = $StartTypeMap[$OriginalStartType]
Set-Service -Name $ServiceName -StartupType $PowerShellStartType
$UpdatedService = Get-Service -Name $ServiceName
$UpdatedWmi = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'"
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = $Service.DisplayName
OriginalStartType = $OriginalStartType
CurrentStartType = $UpdatedWmi.StartMode
Result = "RESTORED SUCCESSFULLY"
Error = ""
}
Write-Host "✓ $ServiceName restored to $OriginalStartType" -ForegroundColor Green
}
catch {
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = $Service.DisplayName
OriginalStartType = $OriginalStartType
CurrentStartType = "N/A"
Result = "FAILED"
Error = $_.Exception.Message
}
Write-Host "✗ $ServiceName failed: $($_.Exception.Message)" -ForegroundColor Red
}
}
# Export results
$Results | Export-Csv -Path $LogPath -NoTypeInformation
Write-Host "`nRestore log: $LogPath" -ForegroundColor Cyan
Write-Host "Reboot recommended after restore!" -ForegroundColor Yellow
Setting specific services to Automatic startup
The final script sets a selected group of services to Automatic startup. Like the earlier examples, the services are defined in an array and the outcome of each change is written to a CSV file.
This is helpful when standardizing systems or enforcing configuration consistency across servers or workstations. Rather than manually checking each service, you can apply the same configuration every time with confidence.
# Set Service to Automatic
# ************************
# List of services to set to Automatic (edit this array)
$ServicesToAuto = @(
"Fax",
"DiagTrack"
)
$Timestamp = Get-Date -Format "yyyyMMddTHHmmss"
$LogPath = "C:\Temp\ServicesSetToAuto_$Timestamp.csv"
Write-Host "Setting services to Automatic and STARTING..." -ForegroundColor Yellow
$Results = @()
foreach ($ServiceName in $ServicesToAuto) {
$ServiceInfo = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $ServiceInfo) {
$Results += [PSCustomObject]@{
ServiceName = $ServiceName; DisplayName = "SERVICE NOT FOUND"
PreviousStartType = "N/A"; PreviousStatus = "N/A"
CurrentStartType = "N/A"; CurrentStatus = "N/A"
Result = "SERVICE MISSING"; Error = ""
}
Write-Host "✗ $ServiceName not found" -ForegroundColor Red
continue
}
$PreviousStartType = $ServiceInfo.StartType
$PreviousStatus = $ServiceInfo.Status
# Perform operations
$OperationError = ""
try {
if ($ServiceInfo.StartType -ne "Automatic") {
Set-Service -Name $ServiceName -StartupType Automatic -ErrorAction Stop
Write-Host " → $($ServiceInfo.DisplayName) → Automatic" -ForegroundColor Cyan
}
if ($ServiceInfo.Status -ne "Running") {
Start-Service -Name $ServiceName -ErrorAction Stop
Write-Host " → $($ServiceInfo.DisplayName) started" -ForegroundColor Cyan
}
}
catch {
$OperationError = $_.Exception.Message
Write-Host "✗ $($ServiceInfo.DisplayName) operation error: $OperationError" -ForegroundColor Red
}
# Get FINAL state
try {
$FinalService = Get-Service -Name $ServiceName -ErrorAction Stop
$CurrentStartType = $FinalService.StartType
$CurrentStatus = $FinalService.Status
# **DETERMINE RESULT BASED ON FINAL STATE**
if ($CurrentStartType -eq "Automatic" -and $CurrentStatus -eq "Running") {
$Result = "FULL SUCCESS - Auto + Running"
}
elseif ($CurrentStartType -eq "Automatic") {
$Result = "PARTIAL - Auto but Not Running"
}
elseif ($CurrentStatus -eq "Running") {
$Result = "PARTIAL - Running but Not Auto"
}
else {
$Result = "FAILED - Neither Auto nor Running"
}
}
catch {
$CurrentStartType = "ERROR"
$CurrentStatus = "ERROR"
$Result = "FINAL CHECK FAILED"
$OperationError = $_.Exception.Message
}
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = $ServiceInfo.DisplayName
PreviousStartType = $PreviousStartType
PreviousStatus = $PreviousStatus
CurrentStartType = $CurrentStartType
CurrentStatus = $CurrentStatus
Result = $Result
Error = $OperationError
}
Write-Host "✓ $($ServiceInfo.DisplayName): $CurrentStartType + $CurrentStatus" -ForegroundColor Green
}
$Results | Export-Csv -Path $LogPath -NoTypeInformation
Write-Host "`nCSV: $LogPath" -ForegroundColor Cyan
Enforcing administrative privileges in service management scripts
Managing Windows services requires elevated permissions. Without administrative privileges, PowerShell cannot reliably change service startup types or stop and start services. Scripts that attempt to do so without elevation often fail partway through execution, which can lead to confusing errors or incomplete results.
To avoid this, each service management script should begin by verifying that it is running in an elevated PowerShell session. This check runs before any service logic executes. If the script detects that it does not have administrative privileges, it exits immediately and displays a clear message explaining what is required. This ensures the script fails safely and predictably.
Once administrative privileges are confirmed, the script continues with its intended logic. In the example below, the privilege check is combined with a script that disables a predefined set of Windows services and logs the results to a CSV file.
The basic concept being:
# Check for administrative privileges
$CurrentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = New-Object Security.Principal.WindowsPrincipal($CurrentIdentity)
$IsAdmin = $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $IsAdmin) {
Write-Host "WARNING: This script is not running with administrative privileges." -ForegroundColor Red
Write-Host "Operation Cancelled. Reopen PowerShell as an administrator to enable full functionality." -ForegroundColor Yellow
} else {
Write-Host "Administrative privileges confirmed. Continuing execution." -ForegroundColor Green
# Code requiring Elevated privileges goes here
}
Below is a modified version of the Disable Services script from earlier which incorporates this extra check.
# Check for administrative privileges
$CurrentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = New-Object Security.Principal.WindowsPrincipal($CurrentIdentity)
$IsAdmin = $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $IsAdmin) {
Write-Host "WARNING: This script is not running with administrative privileges." -ForegroundColor Red
Write-Host "Service changes will be skipped. Reopen PowerShell as an administrator to enable full functionality." -ForegroundColor Yellow
} else {
Write-Host "Administrative privileges confirmed. Continuing execution." -ForegroundColor Green
# Disable Services
# ****************
# List of services to disable
$ServicesToDisable = @(
"Fax",
"XblAuthManager",
"XblGameSave",
"XboxGipSvc",
"DiagTrack",
"MapsBroker",
"WbioSrvc"
)
# Create timestamped CSV log file
$Timestamp = Get-Date -Format "yyyyMMddTHHmmss"
$LogPath = "C:\Temp\ServicesDisabled_$Timestamp.csv"
Write-Host "Disabling selected services." -ForegroundColor Yellow
$Results = @()
foreach ($ServiceName in $ServicesToDisable) {
try {
$Service = Get-Service -Name $ServiceName -ErrorAction Stop
$WmiService = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" -ErrorAction Stop
$StartMode = $WmiService.StartMode
if ($StartMode -in @("Manual", "Disabled")) {
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = $Service.DisplayName
StartType = $StartMode
PreviousStatus = $Service.Status
CurrentStatus = $Service.Status
CurrentStartType = $Service.StartType
Result = "SKIPPED"
Error = ""
}
continue
}
if ($StartMode -eq "Auto") {
$PreviousStatus = $Service.Status
if ($PreviousStatus -eq "Running") {
Stop-Service -Name $ServiceName -Force -ErrorAction Stop
Start-Sleep -Seconds 2
}
Set-Service -Name $ServiceName -StartupType Disabled
$UpdatedService = Get-Service -Name $ServiceName
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = $Service.DisplayName
StartType = $StartMode
PreviousStatus = $PreviousStatus
CurrentStatus = $UpdatedService.Status
CurrentStartType = $UpdatedService.StartType
Result = "DISABLED SUCCESSFULLY"
Error = ""
}
}
}
catch {
$Results += [PSCustomObject]@{
ServiceName = $ServiceName
DisplayName = "N/A"
StartType = "N/A"
PreviousStatus = "N/A"
CurrentStatus = "N/A"
CurrentStartType = "N/A"
Result = "FAILED"
Error = $_.Exception.Message
}
}
}
$Results | Export-Csv -Path $LogPath -NoTypeInformation
Write-Host ""
Write-Host "CSV saved to $LogPath" -ForegroundColor Cyan
Write-Host "Review the output before rebooting." -ForegroundColor Yellow
}
This pattern can be reused across all service related scripts in this post. By enforcing administrative privileges up front, you ensure consistent behavior, clearer errors, and safer automation.
Final thoughts
These four scripts demonstrate a simple but effective pattern for managing Windows services with PowerShell. Define your intent in an array, apply the change programmatically, and export the results for visibility and auditing.
By combining automation with CSV output, you gain repeatability, transparency, and control. This makes service management safer, faster, and easier to maintain over time.