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." } |