Cleanup-PathEnv.ps1


<#PSScriptInfo

.VERSION 1.0.0

.GUID 16a6fd80-10ea-4595-a14e-932a6dc559d3

.AUTHOR Jimmy Briggs

.COMPANYNAME jimbrig

.COPYRIGHT Jimmy Briggs | 2025

.TAGS PowerShell Script

.LICENSEURI https://github.com/jimbrig/PSScripts/blob/main/LICENSE

.PROJECTURI https://github.com/jimbrig/PSScripts/tree/main/Cleanup-PathEnv/

.ICONURI

.EXTERNALMODULEDEPENDENCIES

.REQUIREDSCRIPTS

.EXTERNALSCRIPTDEPENDENCIES

.RELEASENOTES
Initial Release

#>


<#
.SYNOPSIS
    Cleans up and reorganizes Windows PATH environment variables.

.DESCRIPTION
    This script helps clean up and optimize Windows PATH environment variables by:
    - Removing duplicate entries
    - Removing invalid/non-existent paths
    - Categorizing and reorganizing paths by type
    - Creating backups before making changes
    - Providing detailed reporting

.PARAMETER DryRun
    Runs the script without making any actual changes to PATH variables.

.PARAMETER NoBackup
    Skips the backup step before making changes.

.PARAMETER ExportReport
    Exports a detailed before/after report to HTML.

.PARAMETER RestoreBackup
    Restores PATH variables from a backup file. Specify 'latest' to use most recent backup
    or provide a specific timestamp in the format 'yyyy-MM-dd_HH-mm-ss'.

.PARAMETER Normalize
    Attempts to normalize paths by standardizing format and using environment variables.

.PARAMETER NoConfirm
    Skips the confirmation prompt before making changes.

.PARAMETER LogLevel
    Sets the logging level. Valid values: Silent, Error, Warning, Info, Verbose, Debug.
    Default is 'Info'.

.EXAMPLE
    .\Cleanup-PathEnv.ps1
    Runs the script with default settings, prompting for confirmation before changes.

.EXAMPLE
    .\Cleanup-PathEnv.ps1 -DryRun
    Shows what changes would be made without actually applying them.

.EXAMPLE
    .\Cleanup-PathEnv.ps1 -ExportReport -NoConfirm
    Cleans up PATH variables, exports a report, and skips confirmation.

.EXAMPLE
    .\Cleanup-PathEnv.ps1 -RestoreBackup latest
    Restores PATH variables from the most recent backup.

.NOTES
    Version: 2.0.0
    Author: Script Enhanced by Cline
    Creation Date: April 14, 2025
    Last Modified: April 14, 2025
#>

#Requires -RunAsAdministrator
[CmdletBinding(DefaultParameterSetName = 'Cleanup')]
Param(
    [Parameter(ParameterSetName = 'Cleanup')]
    [switch]$DryRun = $false,  # Run without making changes

    [Parameter(ParameterSetName = 'Cleanup')]
    [switch]$NoBackup = $false, # Skip backup step

    [Parameter(ParameterSetName = 'Cleanup')]
    [switch]$ExportReport = $false, # Export before/after report

    [Parameter(ParameterSetName = 'Restore', Mandatory = $true)]
    [string]$RestoreBackup, # Restore from backup

    [Parameter(ParameterSetName = 'Cleanup')]
    [switch]$Normalize = $false, # Normalize paths

    [Parameter(ParameterSetName = 'Cleanup')]
    [switch]$NoConfirm = $false, # Skip confirmation

    [Parameter(ParameterSetName = 'Cleanup')]
    [ValidateSet('Silent', 'Error', 'Warning', 'Info', 'Verbose', 'Debug')]
    [string]$LogLevel = 'Info' # Logging level
)

# Script Version
$script:Version = "2.0.0"

#region Script Variables

# Set backup directory
$script:backupDir = "$env:USERPROFILE\PathBackup"
$script:timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$script:logFile = "$script:backupDir\path_cleanup_$script:timestamp.log"
$script:reportFile = "$script:backupDir\path_report_$script:timestamp.html"

# Define log levels
$script:LogLevels = @{
    'Silent'  = 0
    'Error'   = 1
    'Warning' = 2
    'Info'    = 3
    'Verbose' = 4
    'Debug'   = 5
}

# Current log level value
$script:CurrentLogLevel = $script:LogLevels[$LogLevel]

# Path categories with expanded definitions
$script:PathCategories = @{
    # Windows system directories
    'Windows' = @(
        '*\Windows\*',
        '*\System32\*',
        '*\wbem\*',
        '*\WindowsPowerShell\*',
        '*\OpenSSH\*',
        '*\WindowsApps\*'
    )

    # Programming languages and runtimes
    'Languages' = @(
        '*\Python*',
        '*\jdk*',
        '*\java*',
        '*\R\*',
        '*\nodejs*',
        '*\dotnet*',
        '*\PowerShell*',
        '*\npm*',
        '*\.cargo*',
        '*\go\*',
        '*\Ruby*',
        '*\Perl*',
        '*\Rust*',
        '*\gcc*',
        '*\msys*',
        '*\mingw*'
    )

    # Developer tools
    'DevTools' = @(
        '*\git*',
        '*\GitHub*',
        '*\VS Code*',
        '*\Docker*',
        '*\AWS*',
        '*\Azure*',
        '*\Microsoft SQL Server*',
        '*\Android\*',
        '*\gradle*',
        '*\maven*',
        '*\terraform*',
        '*\kubectl*',
        '*\Microsoft\VisualStudio\*',
        '*\JetBrains\*',
        '*\cmake*'
    )

    # Package managers and utilities
    'PackageManagers' = @(
        '*\chocolatey*',
        '*\scoop*',
        '*\pip*',
        '*\winget*',
        '*\nuget*',
        '*\vcpkg*',
        '*\yarn*',
        '*\pnpm*',
        '*\brew*'
    )

    # Utilities and other applications
    'Utilities' = @(
        '*\bin*',
        '*\oh-my-posh*',
        '*\sysinternals*',
        '*\curl*',
        '*\ssh*',
        '*\putty*',
        '*\vagrant*',
        '*\ffmpeg*',
        '*\ImageMagick*',
        '*\GnuWin*'
    )
}

#endregion

#region Logging Functions

# Function to write log messages
function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter(Mandatory = $false)]
        [ValidateSet('Error', 'Warning', 'Info', 'Verbose', 'Debug')]
        [string]$Level = 'Info',

        [Parameter(Mandatory = $false)]
        [switch]$NoConsole
    )

    $levelValue = $script:LogLevels[$Level]

    # Skip if current log level is lower than this message's level
    if ($levelValue -gt $script:CurrentLogLevel) {
        return
    }

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] [$Level] $Message"

    # Write to log file
    if (-not $NoBackup) {
        Add-Content -Path $script:logFile -Value $logMessage
    }

    # Write to console with color based on level
    if (-not $NoConsole) {
        $color = switch ($Level) {
            'Error'   { 'Red' }
            'Warning' { 'Yellow' }
            'Info'    { 'Green' }
            'Verbose' { 'Cyan' }
            'Debug'   { 'Magenta' }
            default   { 'White' }
        }

        Write-Host $logMessage -ForegroundColor $color
    }
}

#endregion

#region Utility Functions

# Function to check if a path exists
function Test-PathValidity {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path,

        [Parameter(Mandatory = $false)]
        [switch]$Detailed
    )

    $result = [PSCustomObject]@{
        Path = $Path
        OriginalPath = $Path
        Exists = $false
        Expanded = $Path
        ErrorMessage = $null
    }

    try {
        # Expand environment variables if present
        if ($Path -match '%.*%') {
            $result.Expanded = [System.Environment]::ExpandEnvironmentVariables($Path)
        } elseif ($Path -match '\$env:') {
            # Handle PowerShell $env:VAR format
            $result.Expanded = $ExecutionContext.InvokeCommand.ExpandString($Path)
        }

        # Test if the path exists
        $result.Exists = (Test-Path -Path $result.Expanded -ErrorAction Stop)

        return $Detailed ? $result : $result.Exists
    }
    catch {
        $result.ErrorMessage = $_.Exception.Message
        Write-Log "Error testing path '$Path': $($_.Exception.Message)" -Level Error

        return $Detailed ? $result : $false
    }
}

# Function to normalize a path
function Get-NormalizedPath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    try {
        # Skip if path is empty or null
        if ([string]::IsNullOrWhiteSpace($Path)) {
            return $Path
        }

        # Expand the path (if it contains environment variables)
        $expandedPath = [System.Environment]::ExpandEnvironmentVariables($Path)

        # Try to get canonical path if it exists
        if (Test-Path -Path $expandedPath) {
            $expandedPath = (Resolve-Path -Path $expandedPath).Path
        }

        # Replace common paths with environment variables
        if ($Normalize) {
            # System drive
            if ($expandedPath -like "$env:SystemDrive\*") {
                $expandedPath = $expandedPath -replace [regex]::Escape($env:SystemDrive), '%SystemDrive%'
            }

            # Windows directory
            if ($expandedPath -like "$env:windir\*") {
                $expandedPath = $expandedPath -replace [regex]::Escape($env:windir), '%windir%'
            }

            # Program Files
            if ($expandedPath -like "$env:ProgramFiles\*") {
                $expandedPath = $expandedPath -replace [regex]::Escape($env:ProgramFiles), '%ProgramFiles%'
            }

            # Program Files (x86)
            if ($expandedPath -like "${env:ProgramFiles(x86)}\*") {
                $expandedPath = $expandedPath -replace [regex]::Escape("${env:ProgramFiles(x86)}"), '%ProgramFiles(x86)%'
            }

            # User profile
            if ($expandedPath -like "$env:USERPROFILE\*") {
                $expandedPath = $expandedPath -replace [regex]::Escape($env:USERPROFILE), '%USERPROFILE%'
            }
        }

        # Remove trailing backslash unless it's just a drive letter
        if ($expandedPath -match '^[A-Za-z]:$') {
            # It's just a drive letter, append backslash
            $expandedPath = "$expandedPath\"
        }
        else {
            # Remove trailing backslash if present
            $expandedPath = $expandedPath.TrimEnd('\')
        }

        return $expandedPath
    }
    catch {
        Write-Log "Error normalizing path '$Path': $($_.Exception.Message)" -Level Error
        return $Path
    }
}

# Function to remove duplicates while preserving order
function Remove-Duplicates {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$Paths
    )

    Write-Log "Removing duplicates from $($Paths.Count) path entries..." -Level Verbose

    $uniquePaths = @()
    $seenPaths = @{}
    $duplicateCount = 0

    foreach ($path in $Paths) {
        if ([string]::IsNullOrWhiteSpace($path)) {
            Write-Log "Skipping empty path entry" -Level Debug
            continue
        }

        # Normalize path for comparison (lowercase, remove trailing slashes)
        $normalizedPath = Get-NormalizedPath -Path $path
        $comparisonPath = $normalizedPath.TrimEnd('\').ToLower()

        if (-not $seenPaths.ContainsKey($comparisonPath)) {
            $uniquePaths += $normalizedPath
            $seenPaths[$comparisonPath] = $true
            Write-Log "Kept path: $normalizedPath" -Level Debug
        } else {
            $duplicateCount++
            Write-Log "Removing duplicate: $path" -Level Info
        }
    }

    Write-Log "Removed $duplicateCount duplicate path entries" -Level Info
    return $uniquePaths
}

# Function to get category for a path
function Get-PathCategory {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    $normalizedPath = $Path.ToLower()

    # Check each category's patterns
    foreach ($category in $script:PathCategories.Keys) {
        foreach ($pattern in $script:PathCategories[$category]) {
            if ($normalizedPath -like $pattern.ToLower()) {
                Write-Log "Path '$Path' categorized as '$category'" -Level Debug
                return $category
            }
        }
    }

    # Default category
    Write-Log "Path '$Path' categorized as 'Other'" -Level Debug
    return "Other"
}

# Function to restore from backup
function Restore-PathFromBackup {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$BackupIdentifier
    )

    Write-Log "Starting PATH restore process..." -Level Info

    # Create backup directory if it doesn't exist
    if (-not (Test-Path $script:backupDir)) {
        Write-Log "Creating backup directory: $script:backupDir" -Level Info
        New-Item -Path $script:backupDir -ItemType Directory -Force | Out-Null
    }

    # If 'latest', find the most recent backup
    if ($BackupIdentifier -eq 'latest') {
        Write-Log "Looking for most recent backup..." -Level Info

        $latestSystemBackup = Get-ChildItem -Path $script:backupDir -Filter "system_path_backup_*.txt" |
                             Sort-Object -Property LastWriteTime -Descending |
                             Select-Object -First 1

        $latestUserBackup = Get-ChildItem -Path $script:backupDir -Filter "user_path_backup_*.txt" |
                           Sort-Object -Property LastWriteTime -Descending |
                           Select-Object -First 1

        if (-not $latestSystemBackup -or -not $latestUserBackup) {
            Write-Log "No backups found to restore!" -Level Error
            return $false
        }

        $systemBackupFile = $latestSystemBackup.FullName
        $userBackupFile = $latestUserBackup.FullName

        $backupTimestamp = $latestSystemBackup.Name -replace 'system_path_backup_' -replace '.txt'
        Write-Log "Found most recent backup from $backupTimestamp" -Level Info
    }
    else {
        # Use the specified timestamp
        $systemBackupFile = "$script:backupDir\system_path_backup_$BackupIdentifier.txt"
        $userBackupFile = "$script:backupDir\user_path_backup_$BackupIdentifier.txt"

        if (-not (Test-Path $systemBackupFile) -or -not (Test-Path $userBackupFile)) {
            Write-Log "Backup with timestamp $BackupIdentifier not found!" -Level Error
            return $false
        }

        Write-Log "Using backup from $BackupIdentifier" -Level Info
    }

    # Read backup files
    try {
        $systemPath = Get-Content -Path $systemBackupFile -Raw
        $userPath = Get-Content -Path $userBackupFile -Raw

        # Confirm before restoring
        if (-not $NoConfirm) {
            $confirmation = Read-Host "Do you want to restore PATH variables from backup? (Y/N)"
            if ($confirmation -ne "Y" -and $confirmation -ne "y") {
                Write-Log "Restore operation cancelled by user" -Level Warning
                return $false
            }
        }

        # Set environment variables
        [Environment]::SetEnvironmentVariable("PATH", $systemPath, "Machine")
        [Environment]::SetEnvironmentVariable("PATH", $userPath, "User")

        # Update current session
        $env:PATH = $systemPath + ";" + $userPath

        Write-Log "PATH variables have been restored successfully!" -Level Info
        Write-Log "Current session's PATH has been updated." -Level Info

        return $true
    }
    catch {
        Write-Log "Error restoring PATH from backup: $($_.Exception.Message)" -Level Error
        return $false
    }
}

# Function to create HTML report
function Export-PathReport {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]$OriginalSystemPaths,

        [Parameter(Mandatory = $true)]
        [string[]]$OriginalUserPaths,

        [Parameter(Mandatory = $true)]
        [string[]]$NewSystemPaths,

        [Parameter(Mandatory = $true)]
        [string[]]$NewUserPaths,

        [Parameter(Mandatory = $false)]
        [hashtable]$SystemPathsByCategory,

        [Parameter(Mandatory = $false)]
        [hashtable]$UserPathsByCategory,

        [Parameter(Mandatory = $false)]
        [string]$OutputFile = $script:reportFile
    )

    Write-Log "Generating HTML report..." -Level Info

    try {
        # Ensure directory exists
        $reportDir = Split-Path -Path $OutputFile -Parent
        if (-not (Test-Path $reportDir)) {
            New-Item -Path $reportDir -ItemType Directory -Force | Out-Null
        }

        # Create HTML content
        $htmlContent = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PATH Environment Variable Report</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; color: #333; }
        h1, h2, h3 { color: #0066cc; }
        .container { max-width: 1200px; margin: 0 auto; }
        .summary { background-color: #f0f7ff; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
        .category { background-color: #e9ecef; padding: 10px; margin: 15px 0; border-radius: 5px; }
        .path-entry { margin: 5px 0; padding: 8px; border-left: 4px solid #ddd; }
        .path-entry.exists { border-color: #28a745; }
        .path-entry.missing { border-color: #dc3545; }
        .path-entry.duplicate { border-color: #ffc107; }
        .status-badge {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 3px;
            font-size: 12px;
            margin-right: 10px;
        }
        .status-exists { background-color: #d4edda; color: #155724; }
        .status-missing { background-color: #f8d7da; color: #721c24; }
        .status-duplicate { background-color: #fff3cd; color: #856404; }
        .stats { display: flex; justify-content: space-between; flex-wrap: wrap; }
        .stat-box { background-color: #e9ecef; padding: 10px; margin: 5px; border-radius: 5px; flex: 1; min-width: 200px; }
        .removed { background-color: #ffdddd; text-decoration: line-through; }
        .added { background-color: #ddffdd; }
        table { width: 100%; border-collapse: collapse; margin: 15px 0; }
        th, td { text-align: left; padding: 8px; border-bottom: 1px solid #ddd; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <div class="container">
        <h1>PATH Environment Variable Report</h1>
        <p>Generated on $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")</p>

        <div class="summary">
            <h2>Summary</h2>
            <div class="stats">
                <div class="stat-box">
                    <h3>System PATH</h3>
                    <p>Original entries: $($OriginalSystemPaths.Count)</p>
                    <p>New entries: $($NewSystemPaths.Count)</p>
                    <p>Removed: $($OriginalSystemPaths.Count - $NewSystemPaths.Count)</p>
                </div>
                <div class="stat-box">
                    <h3>User PATH</h3>
                    <p>Original entries: $($OriginalUserPaths.Count)</p>
                    <p>New entries: $($NewUserPaths.Count)</p>
                    <p>Removed: $($OriginalUserPaths.Count - $NewUserPaths.Count)</p>
                </div>
            </div>
        </div>

        <h2>System PATH Changes</h2>
        <table>
            <tr>
                <th>Original Path</th>
                <th>Status</th>
            </tr>
"@


        # Add system path entries
        foreach ($path in $OriginalSystemPaths) {
            $status = "Removed"
            $statusClass = "removed"

            if ($NewSystemPaths -contains $path) {
                $status = "Kept"
                $statusClass = ""
            }

            $exists = Test-PathValidity -Path $path
            $statusBadge = $exists ? "Valid" : "Invalid"
            $badgeClass = $exists ? "status-exists" : "status-missing"

            $htmlContent += @"
            <tr class="$statusClass">
                <td>$([System.Web.HttpUtility]::HtmlEncode($path))</td>
                <td>
                    <span class="status-badge $badgeClass">$statusBadge</span>
                    $status
                </td>
            </tr>
"@

        }

        # Check for new entries
        foreach ($path in $NewSystemPaths) {
            if ($OriginalSystemPaths -notcontains $path) {
                $exists = Test-PathValidity -Path $path
                $statusBadge = $exists ? "Valid" : "Invalid"
                $badgeClass = $exists ? "status-exists" : "status-missing"

                $htmlContent += @"
                <tr class="added">
                    <td>$([System.Web.HttpUtility]::HtmlEncode($path))</td>
                    <td>
                        <span class="status-badge $badgeClass">$statusBadge</span>
                        Added
                    </td>
                </tr>
"@

            }
        }

        $htmlContent += @"
        </table>

        <h2>User PATH Changes</h2>
        <table>
            <tr>
                <th>Original Path</th>
                <th>Status</th>
            </tr>
"@


        # Add user path entries
        foreach ($path in $OriginalUserPaths) {
            $status = "Removed"
            $statusClass = "removed"

            if ($NewUserPaths -contains $path) {
                $status = "Kept"
                $statusClass = ""
            }

            $exists = Test-PathValidity -Path $path
            $statusBadge = $exists ? "Valid" : "Invalid"
            $badgeClass = $exists ? "status-exists" : "status-missing"

            $htmlContent += @"
            <tr class="$statusClass">
                <td>$([System.Web.HttpUtility]::HtmlEncode($path))</td>
                <td>
                    <span class="status-badge $badgeClass">$statusBadge</span>
                    $status
                </td>
            </tr>
"@

        }

        # Check for new entries
        foreach ($path in $NewUserPaths) {
            if ($OriginalUserPaths -notcontains $path) {
                $exists = Test-PathValidity -Path $path
                $statusBadge = $exists ? "Valid" : "Invalid"
                $badgeClass = $exists ? "status-exists" : "status-missing"

                $htmlContent += @"
                <tr class="added">
                    <td>$([System.Web.HttpUtility]::HtmlEncode($path))</td>
                    <td>
                        <span class="status-badge $badgeClass">$statusBadge</span>
                        Added
                    </td>
                </tr>
"@

            }
        }

        $htmlContent += @"
        </table>
"@


        # Add categories if available
        if ($SystemPathsByCategory) {
            $htmlContent += @"

        <h2>System PATH by Category</h2>
"@


            foreach ($category in $SystemPathsByCategory.Keys) {
                $paths = $SystemPathsByCategory[$category]
                if ($paths.Count -gt 0) {
                    $htmlContent += @"
        <div class="category">
            <h3>$category ($($paths.Count) entries)</h3>
"@


                    foreach ($path in $paths) {
                        $exists = Test-PathValidity -Path $path
                        $statusClass = $exists ? "exists" : "missing"
                        $statusBadge = $exists ? "Valid" : "Invalid"
                        $badgeClass = $exists ? "status-exists" : "status-missing"

                        $htmlContent += @"
            <div class="path-entry $statusClass">
                <span class="status-badge $badgeClass">$statusBadge</span>
                $([System.Web.HttpUtility]::HtmlEncode($path))
            </div>
"@

                    }

                    $htmlContent += @"
        </div>
"@

                }
            }
        }

        # Add categories if available
        if ($UserPathsByCategory) {
            $htmlContent += @"

        <h2>User PATH by Category</h2>
"@


            foreach ($category in $UserPathsByCategory.Keys) {
                $paths = $UserPathsByCategory[$category]
                if ($paths.Count -gt 0) {
                    $htmlContent += @"
        <div class="category">
            <h3>$category ($($paths.Count) entries)</h3>
"@


                    foreach ($path in $paths) {
                        $exists = Test-PathValidity -Path $path
                        $statusClass = $exists ? "exists" : "missing"
                        $statusBadge = $exists ? "Valid" : "Invalid"
                        $badgeClass = $exists ? "status-exists" : "status-missing"

                        $htmlContent += @"
            <div class="path-entry $statusClass">
                <span class="status-badge $badgeClass">$statusBadge</span>
                $([System.Web.HttpUtility]::HtmlEncode($path))
            </div>
"@

                    }

                    $htmlContent += @"
        </div>
"@

                }
            }
        }

        $htmlContent += @"
    </div>
</body>
</html>
"@


        # Write HTML to file
        $htmlContent | Out-File -FilePath $OutputFile

        Write-Log "HTML report generated: $OutputFile" -Level Info
        return $true
    }
    catch {
        Write-Log "Error generating HTML report: $($_.Exception.Message)" -Level Error
        return $false
    }
}

#endregion

#region Main Script

# Check if restoration is requested
if ($PSCmdlet.ParameterSetName -eq 'Restore') {
    $result = Restore-PathFromBackup -BackupIdentifier $RestoreBackup

    if ($result) {
        Write-Log "PATH variables have been successfully restored from backup '$RestoreBackup'." -Level Info
    }
    else {
        Write-Log "Failed to restore PATH variables from backup." -Level Error
    }

    # Exit script after restore operation
    exit
}

Write-Log "PATH Environment Variable Cleanup Script v$script:Version" -Level Info
Write-Log "Starting cleanup process..." -Level Info

# Create backup directory if it doesn't exist and backup is enabled
if (-not $NoBackup) {
    if (-not (Test-Path $script:backupDir)) {
        Write-Log "Creating backup directory: $script:backupDir" -Level Info
        New-Item -Path $script:backupDir -ItemType Directory -Force | Out-Null
    }

    # Backup current PATH variables with timestamp
    $systemPathBackup = "$script:backupDir\system_path_backup_$script:timestamp.txt"
    $userPathBackup = "$script:backupDir\user_path_backup_$script:timestamp.txt"

    [Environment]::GetEnvironmentVariable("PATH", "Machine") | Out-File -FilePath $systemPathBackup
    [Environment]::GetEnvironmentVariable("PATH", "User") | Out-File -FilePath $userPathBackup

    Write-Log "Backed up current PATH variables to $script:backupDir" -Level Info
}

# Get current PATH variables
$systemPath = [Environment]::GetEnvironmentVariable("PATH", "Machine")
$userPath = [Environment]::GetEnvironmentVariable("PATH", "User")

$systemPaths = $systemPath -split ";" | Where-Object { $_ -and $_ -ne "" }
$userPaths = $userPath -split ";" | Where-Object { $_ -and $_ -ne "" }

$originalSystemPaths = $systemPaths.Clone()
$originalUserPaths = $userPaths.Clone()

Write-Log "Processing System PATH with $($systemPaths.Count) entries..." -Level Info
Write-Log "Processing User PATH with $($userPaths.Count) entries..." -Level Info

# Step 1: Remove duplicates
Write-Log "`n1. Removing duplicates..." -Level Info
$systemPaths = Remove-Duplicates -Paths $systemPaths
$userPaths = Remove-Duplicates -Paths $userPaths

Write-Log "After removing duplicates - System PATH: $($systemPaths.Count) entries" -Level Info
Write-Log "After removing duplicates - User PATH: $($userPaths.Count) entries" -Level Info

# Step 2: Remove invalid paths
Write-Log "`n2. Removing invalid paths..." -Level Info
$validSystemPaths = @()
$validUserPaths = @()

# Show a progress bar for system paths
$i = 0
$count = $systemPaths.Count
foreach ($path in $systemPaths) {
    $i++
    Write-Progress -Activity "Checking System PATH entries" -Status "Processing $i of $count" -PercentComplete (($i / $count) * 100)

    if ([string]::IsNullOrWhiteSpace($path)) {
        Write-Log "Skipping empty System PATH entry" -Level Debug
        continue
    }

    if (Test-PathValidity -Path $path) {
        $validSystemPaths += $path
    } else {
        Write-Log "Removing invalid System PATH: $path" -Level Warning
    }
}
Write-Progress -Activity "Checking System PATH entries" -Completed

# Show a progress bar for user paths
$i = 0
$count = $userPaths.Count
foreach ($path in $userPaths) {
    $i++
    Write-Progress -Activity "Checking User PATH entries" -Status "Processing $i of $count" -PercentComplete (($i / $count) * 100)

    if ([string]::IsNullOrWhiteSpace($path)) {
        Write-Log "Skipping empty User PATH entry" -Level Debug
        continue
    }

    if (Test-PathValidity -Path $path) {
        $validUserPaths += $path
    } else {
        Write-Log "Removing invalid User PATH: $path" -Level Warning
    }
}
Write-Progress -Activity "Checking User PATH entries" -Completed

Write-Log "After removing invalid paths - System PATH: $($validSystemPaths.Count) entries" -Level Info
Write-Log "After removing invalid paths - User PATH: $($validUserPaths.Count) entries" -Level Info

# Step 3: Normalize paths if requested
if ($Normalize) {
    Write-Log "`n3. Normalizing paths..." -Level Info
    $normalizedSystemPaths = @()
    $normalizedUserPaths = @()

    foreach ($path in $validSystemPaths) {
        $normalizedPath = Get-NormalizedPath -Path $path
        $normalizedSystemPaths += $normalizedPath

        if ($normalizedPath -ne $path) {
            Write-Log "Normalized System PATH: $path -> $normalizedPath" -Level Info
        }
    }

    foreach ($path in $validUserPaths) {
        $normalizedPath = Get-NormalizedPath -Path $path
        $normalizedUserPaths += $normalizedPath

        if ($normalizedPath -ne $path) {
            Write-Log "Normalized User PATH: $path -> $normalizedPath" -Level Info
        }
    }

    $validSystemPaths = $normalizedSystemPaths
    $validUserPaths = $normalizedUserPaths

    Write-Log "Path normalization complete" -Level Info
}

# Step 4: Reorganize paths by category
Write-Log "`n4. Reorganizing paths by category..." -Level Info
$systemCategories = @{
    "Windows" = @()
    "Languages" = @()
    "DevTools" = @()
    "PackageManagers" = @()
    "Utilities" = @()
    "Other" = @()
}

$userCategories = @{
    "Windows" = @()
    "Languages" = @()
    "DevTools" = @()
    "PackageManagers" = @()
    "Utilities" = @()
    "Other" = @()
}

foreach ($path in $validSystemPaths) {
    $category = Get-PathCategory -Path $path
    $systemCategories[$category] += $path
}

foreach ($path in $validUserPaths) {
    $category = Get-PathCategory -Path $path
    $userCategories[$category] += $path
}

# Create reorganized paths in the desired order
$reorganizedSystemPaths = @()
$reorganizedSystemPaths += $systemCategories["Windows"]
$reorganizedSystemPaths += $systemCategories["Languages"]
$reorganizedSystemPaths += $systemCategories["DevTools"]
$reorganizedSystemPaths += $systemCategories["PackageManagers"]
$reorganizedSystemPaths += $systemCategories["Utilities"]
$reorganizedSystemPaths += $systemCategories["Other"]

$reorganizedUserPaths = @()
$reorganizedUserPaths += $userCategories["Windows"]
$reorganizedUserPaths += $userCategories["Languages"]
$reorganizedUserPaths += $userCategories["DevTools"]
$reorganizedUserPaths += $userCategories["PackageManagers"]
$reorganizedUserPaths += $userCategories["Utilities"]
$reorganizedUserPaths += $userCategories["Other"]

# Generate HTML report if requested
if ($ExportReport) {
    Write-Log "`nGenerating detailed HTML report..." -Level Info
    $reportResult = Export-PathReport -OriginalSystemPaths $originalSystemPaths `
                                     -OriginalUserPaths $originalUserPaths `
                                     -NewSystemPaths $reorganizedSystemPaths `
                                     -NewUserPaths $reorganizedUserPaths `
                                     -SystemPathsByCategory $systemCategories `
                                     -UserPathsByCategory $userCategories

    if ($reportResult) {
        Write-Log "HTML report generated successfully: $script:reportFile" -Level Info
    }
    else {
        Write-Log "Failed to generate HTML report" -Level Error
    }
}

# Review changes
Write-Log "`n5. Review reorganized paths:" -Level Info

Write-Log "`nReorganized System PATH ($($reorganizedSystemPaths.Count) entries):" -Level Info
foreach ($category in @("Windows", "Languages", "DevTools", "PackageManagers", "Utilities", "Other")) {
    if ($systemCategories[$category].Count -gt 0) {
        Write-Log "`n ## $category" -Level Info
        $systemCategories[$category] | ForEach-Object { Write-Log " $_" -Level Verbose }
    }
}

Write-Log "`nReorganized User PATH ($($reorganizedUserPaths.Count) entries):" -Level Info
foreach ($category in @("Windows", "Languages", "DevTools", "PackageManagers", "Utilities", "Other")) {
    if ($userCategories[$category].Count -gt 0) {
        Write-Log "`n ## $category" -Level Info
        $userCategories[$category] | ForEach-Object { Write-Log " $_" -Level Verbose }
    }
}

# Join paths
$newSystemPath = $reorganizedSystemPaths -join ";"
$newUserPath = $reorganizedUserPaths -join ";"

# Display statistics
$systemRemoved = $originalSystemPaths.Count - $reorganizedSystemPaths.Count
$userRemoved = $originalUserPaths.Count - $reorganizedUserPaths.Count

Write-Log "`n6. Summary of changes:" -Level Info
Write-Log "System PATH: $($originalSystemPaths.Count) entries → $($reorganizedSystemPaths.Count) entries ($systemRemoved removed)" -Level Info
Write-Log "User PATH: $($originalUserPaths.Count) entries → $($reorganizedUserPaths.Count) entries ($userRemoved removed)" -Level Info

# If dry run, just display what would happen
if ($DryRun) {
    Write-Log "`n*** DRY RUN MODE - No changes will be made ***" -Level Warning
    Write-Log "The following PATH variables would be set:" -Level Info
    Write-Log "`nSystem PATH would be set to:" -Level Info
    Write-Log $newSystemPath -Level Verbose
    Write-Log "`nUser PATH would be set to:" -Level Info
    Write-Log $newUserPath -Level Verbose

    if ($ExportReport) {
        Write-Log "`nA detailed report has been generated at: $script:reportFile" -Level Info
        Write-Log "You can open this file to review all changes in detail." -Level Info
    }

    Write-Log "`nTo apply these changes, run the script again without the -DryRun switch." -Level Info
    exit
}

# Confirm before setting
if (-not $NoConfirm) {
    $confirmation = Read-Host "`nDo you want to set these new PATH variables? (Y/N)"
    if ($confirmation -ne "Y" -and $confirmation -ne "y") {
        Write-Log "Operation cancelled. Your PATH variables remain unchanged." -Level Warning

        if ($ExportReport) {
            Write-Log "A detailed report was still generated at: $script:reportFile" -Level Info
        }

        exit
    }
}

# Set variables
[Environment]::SetEnvironmentVariable("PATH", $newSystemPath, "Machine")
[Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User")

Write-Log "`nPATH variables have been updated successfully!" -Level Info
Write-Log "You may need to restart your applications or log off and on again for changes to take effect." -Level Warning

# Update current session's PATH
$env:PATH = $newSystemPath + ";" + $newUserPath
Write-Log "Current session's PATH has been updated." -Level Info

if ($ExportReport) {
    # Provide link to the report
    Write-Log "`nA detailed report has been generated at: $script:reportFile" -Level Info
    Write-Log "You can open this file to review all changes in detail." -Level Info

    # Try to open the report in the default browser
    try {
        Start-Process $script:reportFile
    }
    catch {
        Write-Log "Could not open the report automatically. Please open it manually." -Level Warning
    }
}

Write-Log "Script execution completed successfully." -Level Info

#endregion

# SIG # Begin signature block
# MIIbrQYJKoZIhvcNAQcCoIIbnjCCG5oCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCZb8r0d92brpvW
# AL+cUrDxobazWwPXeBYCs2hjNhbl+aCCFgMwggL8MIIB5KADAgECAhAeucN4UQWG
# pkfnuZqnJIqGMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC0ppbUJyaWdEZXZ0
# MB4XDTI1MDQxNDIxMzMzN1oXDTI2MDQxNDIxNTMzN1owFjEUMBIGA1UEAwwLSmlt
# QnJpZ0RldnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDD7cp3mivP
# JOT8BZr0/MwWf+1GQQNkQq0/LhovZnBmQvpGzlNCldN33Lv8cdyaWY3RSdzpV+xt
# og232PVcVe5IhCzRAtGjrkVBdSR+bdFL258guggMFuE4/R28V/yqyu104oCRxZTl
# tUYMCu00CsoL0Z+N1FlCWKZFgwQyPxzgbSlC31pPZzRqr3X0AeVWphLW/7MsXHUb
# AAQDwCTOTuOyr258LdwDZG9t0R0G68LmfYD1PYQP3LP5kxj7agZb9WaynCFqWmxN
# KRjkScPQV3sDn6M7VC5JUy3ZcyFLjJeCF5M8Pk5JfL7pyP+WiGZU60ai2Hxl+RlT
# F74gPpUv3zfxAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggr
# BgEFBQcDAzAdBgNVHQ4EFgQUHs0F61/78bZDiTmi8x921yt/pM4wDQYJKoZIhvcN
# AQELBQADggEBAIeMBkw6OPlr5DHF4cv+xgUfzpIl4ZWUJ4XAA3e/6ex8/QdarXz+
# 7degiiUMOv3j54XqV/cuATfVisbAlM7/jPasClKI86FXQ3EZAqO+IPYBYnrsWGoS
# lbmunslvRG/UlkR8zolPaTuE+r18Dh8G1sYuhi1Dtv1cfG9HygIXTEAbs2J5G9em
# VhYeH9VI41H6IRzfTEKna4TXan7LsSsGNZxUe4oyad0eB+p4YhDEP/JoHGpBofEr
# dqiZWg9tUStVLyKtYTSXkLE7QuXiGIgvWZ9mmAAsaDpAdTDen1heB/n4ZaKd7I2X
# I/ORBjn+1E7PP5nhhn59Dq53Nl2WNCK4RewwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqG
# SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMy
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcg
# Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXH
# JQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMf
# UBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w
# 1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRk
# tFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYb
# qMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUm
# cJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP6
# 5x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzK
# QtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo
# 80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjB
# Jgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXche
# MBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB
# /wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU
# 7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDig
# NqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v
# dEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZI
# hvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd
# 4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiC
# qBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl
# /Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeC
# RK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYT
# gAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/
# a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37
# xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmL
# NriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0
# YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJ
# RyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIG
# vDCCBKSgAwIBAgIQC65mvFq6f5WHxvnpBOMzBDANBgkqhkiG9w0BAQsFADBjMQsw
# CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRp
# Z2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENB
# MB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEyNTIzNTk1OVowQjELMAkGA1UEBhMCVVMx
# ETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAg
# MjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5qc5/2lSGrljC6
# W23mWaO16P2RHxjEiDtqmeOlwf0KMCBDEr4IxHRGd7+L660x5XltSVhhK64zi9Ce
# C9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo76EO7o5tLuslxdr9Qq82aKcpA9O//X6Q
# E+AcaU/byaCagLD/GLoUb35SfWHh43rOH3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0
# WMp8hMu60tZR0ChaV76Nhnj37DEYTX9ReNZ8hIOYe4jl7/r419CvEYVIrH6sN00y
# x49boUuumF9i2T8UuKGn9966fR5X6kgXj3o5WHhHVO+NBikDO0mlUh902wS/Eeh8
# F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTVDSupWJNstVkiqLq+ISTdEjJKGjVfIcsg
# A4l9cbk8Smlzddh4EfvFrpVNnes4c16Jidj5XiPVdsn5n10jxmGpxoMc6iPkoaDh
# i6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/CacBqU0R4k+8h6gYldp4FCMgrXdKWfM4N
# 0u25OEAuEa3JyidxW48jwBqIJqImd93NRxvd1aepSeNeREXAu2xUDEW8aqzFQDYm
# r9ZONuc2MhTMizchNULpUEoA6Vva7b1XCB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/
# JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMC
# B4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAE
# GTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3Mp
# dpovdYxqII+eyG8wHQYDVR0OBBYEFJ9XLAN3DigVkGalY17uT5IfdqBbMFoGA1Ud
# HwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRy
# dXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUF
# BwEBBIGDMIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w
# WAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZI
# hvcNAQELBQADggIBAD2tHh92mVvjOIQSR9lDkfYR25tOCB3RKE/P09x7gUsmXqt4
# 0ouRl3lj+8QioVYq3igpwrPvBmZdrlWBb0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+
# LaalTW0qVjvUBhcHzBMutB6HzeledbDCzFzUy34VarPnvIWrqVogK0qM8gJhh/+q
# DEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT
# 98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3WpByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+r
# abTFDZXoUke7zPgtd7/fvWTlCs30VAGEsshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tg
# em+mLptR7yIrpaidRJXrI+UzB6vAlk/8a1u7cIqV0yef4uaZFORNekUgQHTqddms
# PCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNFYagLDBzpmk9104WQzYuVNsxyoVLObhx3
# RugaEGru+SojW4dHPoWrUhftNpFC5H7QEY7MhKRyrBe7ucykW7eaCuWBsBb4HOKR
# FVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgEdeoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+
# dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/JceENc2Sg8h3KeFUCS7tpFk7CrDqkMYIF
# ADCCBPwCAQEwKjAWMRQwEgYDVQQDDAtKaW1CcmlnRGV2dAIQHrnDeFEFhqZH57ma
# pySKhjANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAA
# MBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgor
# BgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCA82OBmlts8R6l/wBpjSn5LyJIFND6M
# VjB/BC2HDiRDkDANBgkqhkiG9w0BAQEFAASCAQCKs31tGk2kfPxBUiMJCGyAH6fc
# a8p5eS88LEy9EYN5quGf3rAeTyb5yU58DtaeM8rLJI+bb267Nq0uwhpM99wKd3pa
# Ljrx7thCz1dpWf5zWzLvuZpOHV/2pIG2ZvN16Rhil1N/gpcnH4oZ4ad8HF1dRlkT
# lgn1nGA5EYEUeZvRvtKhHF8WZpn/X57rHRcTosAcHh2nSLWrw8t0y9O7JNgpWMlS
# BrgBiDQIR58DkeCH3UMwlWOy2eW1zj43moPu/S4e2ihJjOLRoCL/hjlrfsOYZ6Lj
# q+0XXjaADiaRS13C9BLxhwbqS5hE0a+PatzwAhV6P8UdC0AxHqu/wyy16JFroYID
# IDCCAxwGCSqGSIb3DQEJBjGCAw0wggMJAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAV
# BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVk
# IEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQQIQC65mvFq6f5WHxvnp
# BOMzBDANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw
# HAYJKoZIhvcNAQkFMQ8XDTI1MDQxNDIxNDYwNFowLwYJKoZIhvcNAQkEMSIEIBj8
# oA4SCq65LqSYUihymiAzw/lLVCY7l+Cd7Y2BJIkoMA0GCSqGSIb3DQEBAQUABIIC
# ALS1LG1Z2klYcNivsLi9COeGQJ1RKFOyCKlUc8fW8Dj01yv5ZLwJrtxPGG1E9N+e
# 0GlZS45ufXWysMrRvqfhinuPCyztfF91ySIeH2NK/uZ9gH3ai+Q+cmml1AAHww7R
# mmynmI7X2jhSXrCHzQ7XiwfYHq4gch/+GfJQ65ZYtaJkuxwj65aivvaUFaLoBF/u
# y8lziD4Ii4VjnRh0a6HesenrVNG9S/tqKqgQa8XbSM2nvhj3IOFcc29cdD1oQK5J
# 0pIENImgMJFjjWq/969pjxpSETxlCzpDIIs4hnrMejIFg4j29K20hkQ8ar/WAU8I
# erQw/UnB6BBiLK2rQ7k6sNjTvzISRof3yNEoeEgPwyksoEOlrof+ogEKoanjsEMd
# /FvoMMqz8eyOlT+M98DTGz6/D+D0fkuyMNfDCRS5+A/z+jiBOf6ktZIL9BLYGoKl
# L6W+lRQiOxFT1+DljUup8uzDzgiF2AHRFr0Y05gBLmfz7af1Yz3k/vMHSqZVxPqK
# OiF7dbJszjB0pAeCpQHzy2lBMsl3JD4J1YbTlAB3vX1OrzVZBRjH9qQuchd+/VEJ
# JsUNVZzhR55VrbsBAcBxbV0stKOwgtwXZzK3o47aVDT9f7LKVGk7eCIFs4zAjhum
# PjR9sRSRGH6m6XnORiDjjJz33OYfdmaHdomamm9WIRP2
# SIG # End signature block