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.
 
.PARAMETER ServiceMode
    Indicates the script is running as a Windows Service. When this parameter is present,
    the script runs in service mode with direct monitoring execution.
 
.PARAMETER ConfigFile
    Path to the configuration file containing service parameters. Used in service mode.
 
.NOTES
    File Name : NetTrace-Service.ps1
    Version : 1.2.2
    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
#>


param(
    [Parameter(Mandatory=$false)]
    [switch]$ServiceMode,

    [Parameter(Mandatory=$false)]
    [string]$ConfigFile
)

# Service configuration
$ServiceName = "NetTraceService"

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

# 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 {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string]$Path,
        [int]$MaxFiles,
        [int]$MaxSizeMB,
        [bool]$LogOutput = $false,
        [bool]$EnableLogging = $false
    )

    if ($PSCmdlet.ShouldProcess("NetTrace Service Configuration", "Save configuration")) {
        $config = @{
            Path = $Path
            MaxFiles = $MaxFiles
            MaxSizeMB = $MaxSizeMB
            LogOutput = $LogOutput
            EnableLogging = $EnableLogging
            StartTime = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
            ServiceVersion = "1.2.2"
        }

        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
        }
    }
    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 {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [bool]$IsRunning,
        [int]$FilesCreated = 0,
        [int]$FilesRolled = 0,
        [string]$CurrentFile = "",
        [string]$ErrorMessage = ""
    )

    if ($PSCmdlet.ShouldProcess("NetTrace Service Status", "Update status")) {
        $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"
        }
    }
}

# Direct service monitoring function (replaces job-based monitoring)
function Start-DirectServiceMonitoring {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string]$TracePath,
        [int]$MaxFiles,
        [int]$MaxSizeMB,
        [bool]$LogOutput = $false,
        [bool]$EnableLogging = $false
    )

    if ($PSCmdlet.ShouldProcess("NetTrace Service", "Start direct monitoring")) {
        Write-ServiceLog "Starting direct service monitoring: Path=$TracePath, MaxFiles=$MaxFiles, MaxSizeMB=$MaxSizeMB"

    $fileNumber = 1
    $filesCreated = 0
    $filesRolled = 0
    $fileHistory = @()
    $computerName = $env:COMPUTERNAME
    $maxSizeBytes = $MaxSizeMB * 1MB

    # 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 - runs directly in service context
    while (-not (Test-Path $ServiceStopFlag)) {
        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') - True Windows Service 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 (WINDOWS 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 -not (Test-Path $ServiceStopFlag)) {
                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 (WINDOWS 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 ""

    # Remove stop flag
    if (Test-Path $ServiceStopFlag) {
        Remove-Item $ServiceStopFlag -Force -ErrorAction SilentlyContinue
    }
    }
}

# Service control functions (NEW: No longer uses jobs)
function Start-NetTraceService {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string]$Path,
        [int]$MaxFiles,
        [int]$MaxSizeMB,
        [bool]$LogOutput = $false,
        [bool]$EnableLogging = $false
    )

    if ($PSCmdlet.ShouldProcess("NetTrace Service", "Start service")) {
        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
        }

        # Remove any existing stop flag
        if (Test-Path $ServiceStopFlag) {
            Remove-Item $ServiceStopFlag -Force -ErrorAction SilentlyContinue
        }

        # Save configuration for service to read
        if (-not (Set-ServiceConfig -Path $Path -MaxFiles $MaxFiles -MaxSizeMB $MaxSizeMB -LogOutput $LogOutput -EnableLogging $EnableLogging)) {
            Write-ServiceLog "Failed to save service configuration" "ERROR"
            return $false
        }

        Write-ServiceLog "Service configuration saved. Ready for Windows Service to start."
        return $true
    }
    return $false
}

function Stop-NetTraceService {
    [CmdletBinding(SupportsShouldProcess)]
    param()

    if ($PSCmdlet.ShouldProcess("NetTrace Service", "Stop service")) {
        Write-ServiceLog "Service stop requested"

        # Create stop flag to signal service to stop
        try {
            "stop" | Out-File -FilePath $ServiceStopFlag -Force
            Write-ServiceLog "Service stop flag created"
        } catch {
            Write-ServiceLog "Failed to create service stop flag: $($_.Exception.Message)" "ERROR"
        }

        # 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
    }
    return $false
}

# Service mode execution
if ($ServiceMode) {
    Write-ServiceLog "Starting in Windows Service Mode"

    # Read configuration
    $config = Get-ServiceConfig
    if (-not $config) {
        Write-ServiceLog "No service configuration found" "ERROR"
        exit 1
    }

    Write-ServiceLog "Service configuration loaded: Path=$($config.Path), MaxFiles=$($config.MaxFiles), MaxSizeMB=$($config.MaxSizeMB)"

    # Start direct monitoring (no jobs)
    try {
        Start-DirectServiceMonitoring -TracePath $config.Path -MaxFiles $config.MaxFiles -MaxSizeMB $config.MaxSizeMB -LogOutput $config.LogOutput -EnableLogging $config.EnableLogging
    } catch {
        Write-ServiceLog "Service monitoring failed: $($_.Exception.Message)" "ERROR"
        exit 1
    }

    Write-ServiceLog "Service mode execution completed"
    exit 0
}

# 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 without service mode, provide guidance
if ($MyInvocation.InvocationName -eq $MyInvocation.MyCommand.Name) {
    Write-Information "NetTrace Service Script v1.2.2" -InformationAction Continue
    Write-Information "This script now implements a true Windows Service using NSSM." -InformationAction Continue
    Write-Information "This script should be used through NetTrace-ServiceRunner.ps1 for proper service management." -InformationAction Continue
    Write-Information "Direct execution without -ServiceMode is not recommended." -InformationAction Continue
}