NetTrace-Service.ps1

#Requires -Version 5.1
#Requires -RunAsAdministrator

<#
.SYNOPSIS
    NetTrace Windows Service Implementation
 
.DESCRIPTION
    This script implements a Windows Service that provides true persistence for NetTrace functionality.
    The service handles network trace file monitoring and rotation independently of user sessions,
    ensuring continuous operation even after user logouts and system reboots.
 
.NOTES
    File Name : NetTrace-Service.ps1
    Version : 1.2.1
    Author : Naveed Khan
    Company : Hogwarts
    Copyright : (c) 2025 Naveed Khan. All rights reserved.
    License : MIT License
    Prerequisite : Windows 10/11 with Administrator privileges
    Requires : PowerShell 5.1 or PowerShell 7+
 
.LINK
    https://github.com/khannaveed2020/NetTrace
#>


# Service configuration
$ServiceName = "NetTraceService"
$ServiceDisplayName = "NetTrace Network Monitoring Service"
$ServiceDescription = "Provides persistent network trace monitoring with automatic file rotation and circular management"

# Service state file paths
$ServiceStateDir = "$env:ProgramData\NetTrace"
$ServiceConfigFile = "$ServiceStateDir\service_config.json"
$ServiceStatusFile = "$ServiceStateDir\service_status.json"
$ServiceLogFile = "$ServiceStateDir\service.log"

# Ensure service directory exists
if (!(Test-Path $ServiceStateDir)) {
    New-Item -Path $ServiceStateDir -ItemType Directory -Force | Out-Null
}

# Service logging function
function Write-ServiceLog {
    param(
        [string]$Message,
        [string]$Level = "INFO"
    )
    
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logEntry = "[$timestamp] [$Level] $Message"
    
    try {
        $logEntry | Out-File -FilePath $ServiceLogFile -Append -Encoding UTF8
    } catch {
        # Fallback to event log if file logging fails
        Write-EventLog -LogName Application -Source $ServiceName -EntryType Information -EventId 1001 -Message $Message -ErrorAction SilentlyContinue
    }
}

# Service configuration management
function Get-ServiceConfig {
    try {
        if (Test-Path $ServiceConfigFile) {
            $config = Get-Content $ServiceConfigFile -Raw | ConvertFrom-Json
            return $config
        }
    } catch {
        Write-ServiceLog "Failed to read service configuration: $($_.Exception.Message)" "ERROR"
    }
    return $null
}

function Set-ServiceConfig {
    param(
        [string]$Path,
        [int]$MaxFiles,
        [int]$MaxSizeMB,
        [bool]$LogOutput = $false,
        [bool]$EnableLogging = $false
    )
    
    $config = @{
        Path = $Path
        MaxFiles = $MaxFiles
        MaxSizeMB = $MaxSizeMB
        LogOutput = $LogOutput
        EnableLogging = $EnableLogging
        StartTime = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
        ServiceVersion = "1.2.1"
    }
    
    try {
        $config | ConvertTo-Json -Depth 10 | Out-File -FilePath $ServiceConfigFile -Encoding UTF8
        Write-ServiceLog "Service configuration saved: Path=$Path, MaxFiles=$MaxFiles, MaxSizeMB=$MaxSizeMB"
        return $true
    } catch {
        Write-ServiceLog "Failed to save service configuration: $($_.Exception.Message)" "ERROR"
        return $false
    }
}

# Service status management
function Get-ServiceStatus {
    try {
        if (Test-Path $ServiceStatusFile) {
            $status = Get-Content $ServiceStatusFile -Raw | ConvertFrom-Json
            return $status
        }
    } catch {
        Write-ServiceLog "Failed to read service status: $($_.Exception.Message)" "ERROR"
    }
    
    return @{
        IsRunning = $false
        FilesCreated = 0
        FilesRolled = 0
        CurrentFile = ""
        LastUpdate = ""
        ErrorMessage = ""
    }
}

function Set-ServiceStatus {
    param(
        [bool]$IsRunning,
        [int]$FilesCreated = 0,
        [int]$FilesRolled = 0,
        [string]$CurrentFile = "",
        [string]$ErrorMessage = ""
    )
    
    $status = @{
        IsRunning = $IsRunning
        FilesCreated = $FilesCreated
        FilesRolled = $FilesRolled
        CurrentFile = $CurrentFile
        LastUpdate = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
        ErrorMessage = $ErrorMessage
    }
    
    try {
        $status | ConvertTo-Json -Depth 10 | Out-File -FilePath $ServiceStatusFile -Encoding UTF8
    } catch {
        Write-ServiceLog "Failed to save service status: $($_.Exception.Message)" "ERROR"
    }
}

# Main service monitoring function
function Start-ServiceMonitoring {
    param(
        [string]$TracePath,
        [int]$MaxFiles,
        [int]$MaxSizeMB,
        [bool]$LogOutput = $false,
        [bool]$EnableLogging = $false
    )
    
    Write-ServiceLog "Starting service monitoring: Path=$TracePath, MaxFiles=$MaxFiles, MaxSizeMB=$MaxSizeMB"
    
    $fileNumber = 1
    $filesCreated = 0
    $filesRolled = 0
    $fileHistory = @()
    $computerName = $env:COMPUTERNAME
    $maxSizeBytes = $MaxSizeMB * 1MB
    
    # Service control flag
    $serviceFlag = "$ServiceStateDir\service_running.flag"
    "running" | Out-File -FilePath $serviceFlag -Force
    
    # Create user log file if logging is enabled
    $userLogFile = $null
    if ($EnableLogging) {
        $userLogFile = Join-Path $TracePath "NetTrace_$(Get-Date -Format 'yyyy-MM-dd_HHmmss').log"
        "NetTrace persistent service started at $(Get-Date)" | Out-File -FilePath $userLogFile -Encoding UTF8
        "Command: NetTrace -Persistence true -File $MaxFiles -FileSize $MaxSizeMB -Path '$TracePath'" | Out-File -FilePath $userLogFile -Append -Encoding UTF8
        "=" * 60 | Out-File -FilePath $userLogFile -Append -Encoding UTF8
        "" | Out-File -FilePath $userLogFile -Append -Encoding UTF8
    }
    
    # Function to write to user log file if logging is enabled
    function Write-ToUserLog {
        param($Message, $LogFile)
        if ($LogFile) {
            try {
                $Message | Out-File -FilePath $LogFile -Append -Encoding UTF8
            } catch {
                Write-ServiceLog "Failed to write to user log file: $($_.Exception.Message)" "ERROR"
            }
        }
    }
    
    # Force stop any existing netsh trace to ensure clean start
    & netsh trace stop 2>&1 | Out-Null
    Start-Sleep -Seconds 2
    
    # Main monitoring loop
    while (Test-Path $serviceFlag) {
        try {
            # Generate filename with computer name and timestamp
            $dateStamp = Get-Date -Format "dd-MM-yy"
            $timeStamp = Get-Date -Format "HHmmss"
            $traceFile = Join-Path $TracePath "$computerName`_$dateStamp-$timeStamp.etl"
            $fileName = [System.IO.Path]::GetFileName($traceFile)
            
            Write-ServiceLog "Creating File #$fileNumber : $fileName"
            Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - Creating File #$fileNumber : $fileName" -LogFile $userLogFile
            Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - Service-based persistence enabled - capture will survive user session termination" -LogFile $userLogFile
            
            # Start netsh trace with persistent mode
            $arguments = @("trace", "start", "capture=yes", "report=disabled", "overwrite=yes", "maxSize=$MaxSizeMB", "tracefile=`"$traceFile`"", "persistent=yes")
            
            $netshOutput = & netsh $arguments 2>&1
            $exitCode = $LASTEXITCODE
            
            # Log netsh output if requested
            if ($LogOutput) {
                $netshLogFile = Join-Path $TracePath "netsh_trace.log"
                "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - START TRACE (SERVICE):" | Out-File -FilePath $netshLogFile -Append -Encoding UTF8
                $netshOutput | Out-File -FilePath $netshLogFile -Append -Encoding UTF8
                "" | Out-File -FilePath $netshLogFile -Append -Encoding UTF8
            }
            
            Write-ServiceLog "Netsh trace started for: $fileName (Exit Code: $exitCode)"
            Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - Netsh trace started for: $fileName" -LogFile $userLogFile
            
            if ($exitCode -ne 0) {
                $errorMsg = "Failed to start trace. Exit code: $exitCode"
                Write-ServiceLog $errorMsg "ERROR"
                Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - ERROR: $errorMsg" -LogFile $userLogFile
                Set-ServiceStatus -IsRunning $false -FilesCreated $filesCreated -FilesRolled $filesRolled -ErrorMessage $errorMsg
                break
            }
            
            $filesCreated++
            
            # Add current file to history
            $fileHistory += @{
                Number = $fileNumber
                Path = $traceFile
                Name = $fileName
            }
            
            # Update service status
            Set-ServiceStatus -IsRunning $true -FilesCreated $filesCreated -FilesRolled $filesRolled -CurrentFile $fileName
            
            Write-ServiceLog "Monitoring file size (limit: $MaxSizeMB MB)..."
            Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - Monitoring file size (limit: $MaxSizeMB MB)..." -LogFile $userLogFile
            
            # Monitor file size until limit is reached
            $fileRotated = $false
            while (-not $fileRotated -and (Test-Path $serviceFlag)) {
                Start-Sleep -Seconds 2
                
                if (Test-Path $traceFile) {
                    $fileSize = (Get-Item $traceFile).Length
                    $sizeMB = [math]::Round($fileSize/1MB, 2)
                    
                    Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - File: $fileName - Size: $sizeMB MB / $MaxSizeMB MB" -LogFile $userLogFile
                    
                    if ($fileSize -ge $maxSizeBytes) {
                        Write-ServiceLog "Size limit reached for $fileName. Rolling to new file..."
                        Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - Size limit reached! Rolling to new file..." -LogFile $userLogFile
                        
                        # Stop current trace
                        $stopOutput = & netsh trace stop 2>&1
                        $stopExitCode = $LASTEXITCODE
                        
                        # Log stop output if requested
                        if ($LogOutput) {
                            $netshLogFile = Join-Path $TracePath "netsh_trace.log"
                            "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - STOP TRACE (SERVICE):" | Out-File -FilePath $netshLogFile -Append -Encoding UTF8
                            $stopOutput | Out-File -FilePath $netshLogFile -Append -Encoding UTF8
                            "" | Out-File -FilePath $netshLogFile -Append -Encoding UTF8
                        }
                        
                        Write-ServiceLog "Trace stopped for file: $fileName (Exit Code: $stopExitCode)"
                        Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - Trace stopped for file: $fileName" -LogFile $userLogFile
                        
                        if ($stopExitCode -eq 0) {
                            $filesRolled++
                            $fileRotated = $true
                            $fileNumber++
                            
                            # Circular file management
                            if ($fileHistory.Count -gt $MaxFiles) {
                                $oldestFile = $fileHistory[0]
                                if (Test-Path $oldestFile.Path) {
                                    Remove-Item $oldestFile.Path -Force -ErrorAction SilentlyContinue
                                    Write-ServiceLog "Removed oldest file: $($oldestFile.Name)"
                                    Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - Removed oldest file: $($oldestFile.Name)" -LogFile $userLogFile
                                }
                                $fileHistory = $fileHistory[1..($fileHistory.Count-1)]
                            }
                            
                            # Update service status
                            Set-ServiceStatus -IsRunning $true -FilesCreated $filesCreated -FilesRolled $filesRolled -CurrentFile ""
                        } else {
                            $errorMsg = "Failed to stop trace. Exit code: $stopExitCode"
                            Write-ServiceLog $errorMsg "ERROR"
                            Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - ERROR: $errorMsg" -LogFile $userLogFile
                            Set-ServiceStatus -IsRunning $false -FilesCreated $filesCreated -FilesRolled $filesRolled -ErrorMessage $errorMsg
                            break
                        }
                    }
                } else {
                    $errorMsg = "Trace file not found: $traceFile"
                    Write-ServiceLog $errorMsg "ERROR"
                    Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - ERROR: $errorMsg" -LogFile $userLogFile
                    Set-ServiceStatus -IsRunning $false -FilesCreated $filesCreated -FilesRolled $filesRolled -ErrorMessage $errorMsg
                    break
                }
            }
        } catch {
            $errorMsg = "Service monitoring error: $($_.Exception.Message)"
            Write-ServiceLog $errorMsg "ERROR"
            Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - ERROR: $errorMsg" -LogFile $userLogFile
            Set-ServiceStatus -IsRunning $false -FilesCreated $filesCreated -FilesRolled $filesRolled -ErrorMessage $errorMsg
            break
        }
    }
    
    # Cleanup when service stops
    Write-ServiceLog "Service monitoring stopped. Cleaning up..."
    Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - Service monitoring stopped" -LogFile $userLogFile
    Write-ToUserLog -Message "$(Get-Date -Format 'HH:mm:ss') - SUMMARY: Files created: $filesCreated, Files rolled: $filesRolled" -LogFile $userLogFile
    Write-ToUserLog -Message ("=" * 60) -LogFile $userLogFile
    
    & netsh trace stop 2>&1 | Out-Null
    Set-ServiceStatus -IsRunning $false -FilesCreated $filesCreated -FilesRolled $filesRolled -CurrentFile ""
}

# Service control functions
function Start-NetTraceService {
    param(
        [string]$Path,
        [int]$MaxFiles,
        [int]$MaxSizeMB,
        [bool]$LogOutput = $false,
        [bool]$EnableLogging = $false
    )
    
    Write-ServiceLog "Service start requested: Path=$Path, MaxFiles=$MaxFiles, MaxSizeMB=$MaxSizeMB"
    
    # Check if service is already running
    $currentStatus = Get-ServiceStatus
    if ($currentStatus.IsRunning) {
        Write-ServiceLog "Service is already running" "WARNING"
        return $false
    }
    
    # Save configuration
    if (-not (Set-ServiceConfig -Path $Path -MaxFiles $MaxFiles -MaxSizeMB $MaxSizeMB -LogOutput $LogOutput -EnableLogging $EnableLogging)) {
        Write-ServiceLog "Failed to save service configuration" "ERROR"
        return $false
    }
    
    # Start monitoring in background
    $job = Start-Job -ScriptBlock {
        param($ServiceScript, $TracePath, $MaxFiles, $MaxSizeMB, $LogOutput, $EnableLogging)
        
        # Import the service functions
        . $ServiceScript
        
        # Start monitoring
        Start-ServiceMonitoring -TracePath $TracePath -MaxFiles $MaxFiles -MaxSizeMB $MaxSizeMB -LogOutput $LogOutput -EnableLogging $EnableLogging
        
    } -ArgumentList $PSCommandPath, $Path, $MaxFiles, $MaxSizeMB, $LogOutput, $EnableLogging
    
    if ($job) {
        Write-ServiceLog "Service monitoring job started (Job ID: $($job.Id))"
        Set-ServiceStatus -IsRunning $true -FilesCreated 0 -FilesRolled 0
        return $true
    } else {
        Write-ServiceLog "Failed to start service monitoring job" "ERROR"
        return $false
    }
}

function Stop-NetTraceService {
    Write-ServiceLog "Service stop requested"
    
    # Remove service flag to stop monitoring
    $serviceFlag = "$ServiceStateDir\service_running.flag"
    if (Test-Path $serviceFlag) {
        Remove-Item $serviceFlag -Force -ErrorAction SilentlyContinue
        Write-ServiceLog "Service flag removed"
    }
    
    # Stop any running netsh trace
    & netsh trace stop 2>&1 | Out-Null
    Write-ServiceLog "Netsh trace stopped"
    
    # Update status
    $currentStatus = Get-ServiceStatus
    Set-ServiceStatus -IsRunning $false -FilesCreated $currentStatus.FilesCreated -FilesRolled $currentStatus.FilesRolled
    
    Write-ServiceLog "Service stopped successfully"
    return $true
}

# Export functions for use by other scripts
Export-ModuleMember -Function Start-NetTraceService, Stop-NetTraceService, Get-ServiceStatus, Get-ServiceConfig, Write-ServiceLog

# If script is run directly, provide basic service control
if ($MyInvocation.InvocationName -eq $MyInvocation.MyCommand.Name) {
    Write-Host "NetTrace Service Script v1.2.1"
    Write-Host "This script should be used through NetTrace-ServiceRunner.ps1 for proper service management."
    Write-Host "Direct execution is not recommended."
}