4 PowerShell Scripts for Managing Windows Services (List, Disable, Restore, Automate)

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.
 

Note: Handle Windows Services with Care
Modifying Windows services can have serious consequences if done incorrectly. Stopping, disabling, or changing the startup type of certain services may break system functionality, prevent Windows features from working, or even cause your system to become unstable.

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.