NetTrace-ServiceRunner.ps1

#Requires -Version 5.1
#Requires -RunAsAdministrator

<#
.SYNOPSIS
    NetTrace Service Runner and Management Script
 
.DESCRIPTION
    This script provides management functionality for the NetTrace Windows Service using NSSM (Non-Sucking Service Manager).
    It handles service installation, configuration, starting, stopping, and status monitoring for true Windows Service persistence.
 
.PARAMETER Install
    Installs the NetTrace Windows Service using NSSM
 
.PARAMETER Uninstall
    Uninstalls the NetTrace Windows Service
 
.PARAMETER Start
    Starts the NetTrace Windows Service with specified parameters
 
.PARAMETER Stop
    Stops the NetTrace Windows Service
 
.PARAMETER Status
    Shows the current status of the NetTrace Windows Service
 
.PARAMETER Path
    Directory path where trace files will be stored (required for Start)
 
.PARAMETER MaxFiles
    Maximum number of trace files to maintain (required for Start)
 
.PARAMETER MaxSizeMB
    Maximum size of each trace file in MB (required for Start)
 
.PARAMETER LogOutput
    Enable netsh output logging (optional)
 
.PARAMETER EnableLogging
    Enable detailed activity logging (optional)
 
.NOTES
    File Name : NetTrace-ServiceRunner.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+
    Dependencies : NSSM (Non-Sucking Service Manager) - automatically downloaded
 
.EXAMPLE
    .\NetTrace-ServiceRunner.ps1 -Install
    Installs the NetTrace Windows Service using NSSM
 
.EXAMPLE
    .\NetTrace-ServiceRunner.ps1 -Start -Path "C:\Traces" -MaxFiles 3 -MaxSizeMB 10 -EnableLogging
    Starts the service with specified parameters and logging enabled
 
.EXAMPLE
    .\NetTrace-ServiceRunner.ps1 -Stop
    Stops the NetTrace Windows Service
 
.EXAMPLE
    .\NetTrace-ServiceRunner.ps1 -Status
    Shows current service status
 
.EXAMPLE
    .\NetTrace-ServiceRunner.ps1 -Uninstall
    Uninstalls the NetTrace Windows Service
 
.LINK
    https://github.com/khannaveed2020/NetTrace
#>


[CmdletBinding(DefaultParameterSetName = 'Status')]
param(
    [Parameter(ParameterSetName = 'Install', Mandatory = $true)]
    [switch]$Install,

    [Parameter(ParameterSetName = 'Uninstall', Mandatory = $true)]
    [switch]$Uninstall,

    [Parameter(ParameterSetName = 'Start', Mandatory = $true)]
    [switch]$Start,

    [Parameter(ParameterSetName = 'Stop', Mandatory = $true)]
    [switch]$Stop,

    [Parameter(ParameterSetName = 'Status', Mandatory = $false)]
    [switch]$Status,

    [Parameter(ParameterSetName = 'Start', Mandatory = $true)]
    [string]$Path,

    [Parameter(ParameterSetName = 'Start', Mandatory = $true)]
    [int]$MaxFiles,

    [Parameter(ParameterSetName = 'Start', Mandatory = $true)]
    [int]$MaxSizeMB,

    [Parameter(ParameterSetName = 'Start', Mandatory = $false)]
    [switch]$LogOutput,

    [Parameter(ParameterSetName = 'Start', Mandatory = $false)]
    [switch]$EnableLogging
)

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

# Get script directory
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ServiceScript = Join-Path $ScriptDir "NetTrace-Service.ps1"

# Check if service script exists
if (!(Test-Path $ServiceScript)) {
    Write-Error "NetTrace-Service.ps1 not found in script directory: $ScriptDir"
    exit 1
}

# Import service functions
. $ServiceScript

# NSSM management functions
function Get-NSSM {
    <#
    .SYNOPSIS
        Downloads and prepares NSSM for service management
    .DESCRIPTION
        Downloads NSSM (Non-Sucking Service Manager) from official source if not available locally
    #>


    $nssmDir = "$env:TEMP\NetTrace-NSSM"
    $nssmPath = "$nssmDir\nssm.exe"

    # Check if NSSM is already available
    if (Test-Path $nssmPath) {
        Write-Information "NSSM found at: $nssmPath" -InformationAction Continue
        return $nssmPath
    }

    Write-Information "Downloading NSSM (Non-Sucking Service Manager)..." -InformationAction Continue

    try {
        # Create directory if it doesn't exist
        if (!(Test-Path $nssmDir)) {
            New-Item -Path $nssmDir -ItemType Directory -Force | Out-Null
        }

        # Download NSSM
        $nssmUrl = "https://nssm.cc/release/nssm-2.24.zip"
        $nssmZip = "$nssmDir\nssm.zip"

        # Use System.Net.WebClient for reliable download
        $webClient = New-Object System.Net.WebClient
        $webClient.DownloadFile($nssmUrl, $nssmZip)

        # Extract NSSM
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        $zip = [System.IO.Compression.ZipFile]::OpenRead($nssmZip)

        # Find the correct nssm.exe for current architecture
        $architecture = if ([Environment]::Is64BitOperatingSystem) { "win64" } else { "win32" }
        $nssmEntry = $zip.Entries | Where-Object { $_.FullName -like "*/$architecture/nssm.exe" }

        if ($nssmEntry) {
            $stream = $nssmEntry.Open()
            $fileStream = [System.IO.File]::Create($nssmPath)
            $stream.CopyTo($fileStream)
            $fileStream.Close()
            $stream.Close()
        } else {
            throw "Could not find nssm.exe for architecture: $architecture"
        }

        $zip.Dispose()

        # Clean up zip file
        Remove-Item $nssmZip -Force -ErrorAction SilentlyContinue

        if (Test-Path $nssmPath) {
            Write-Information "✓ NSSM downloaded successfully" -InformationAction Continue
            return $nssmPath
        } else {
            throw "NSSM download failed - file not found after extraction"
        }

    } catch {
        Write-Error "Failed to download NSSM: $($_.Exception.Message)"
        Write-Information "Manual installation: Download NSSM from https://nssm.cc/ and place nssm.exe in $nssmDir" -InformationAction Continue
        return $null
    }
}

function Install-NetTraceWindowsService {
    <#
    .SYNOPSIS
        Installs NetTrace as a Windows Service using NSSM
    .DESCRIPTION
        Uses NSSM to install NetTrace as a proper Windows Service for true persistence
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param()

    if ($PSCmdlet.ShouldProcess("NetTrace Windows Service", "Install")) {
        Write-Information "Installing NetTrace as Windows Service using NSSM..." -InformationAction Continue

        try {
            # Get NSSM
            $nssm = Get-NSSM
            if (-not $nssm) {
                throw "NSSM is required for service installation"
            }

            # Check if service already exists
            $existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
            if ($existingService) {
                Write-Warning "Service '$ServiceName' already exists. Use -Uninstall first to remove it."
                return $false
            }

            # Install service with NSSM
            $serviceScript = $ServiceScript
            Write-Information "Installing service with script: $serviceScript" -InformationAction Continue

            # Install the service
            $installResult = & $nssm install $ServiceName powershell.exe 2>&1
            if ($LASTEXITCODE -ne 0) {
                throw "NSSM install failed: $installResult"
            }

            # Configure service parameters
            & $nssm set $ServiceName Parameters "-ExecutionPolicy Bypass -File `"$serviceScript`" -ServiceMode"
            & $nssm set $ServiceName AppDirectory "$ScriptDir"
            & $nssm set $ServiceName DisplayName "$ServiceDisplayName"
            & $nssm set $ServiceName Description "$ServiceDescription"
            & $nssm set $ServiceName Start SERVICE_AUTO_START
            & $nssm set $ServiceName Type SERVICE_WIN32_OWN_PROCESS

            # Configure service recovery
            & $nssm set $ServiceName AppStopMethodSkip 0
            & $nssm set $ServiceName AppStopMethodConsole 1500
            & $nssm set $ServiceName AppStopMethodWindow 1500
            & $nssm set $ServiceName AppStopMethodThreads 1500

            # Set service to restart on failure
            & $nssm set $ServiceName AppExit Default Restart
            & $nssm set $ServiceName AppRestartDelay 60000

            # Configure logging
            $logDir = "$env:ProgramData\NetTrace\service_logs"
            if (!(Test-Path $logDir)) {
                New-Item -Path $logDir -ItemType Directory -Force | Out-Null
            }

            & $nssm set $ServiceName AppStdout "$logDir\service_stdout.log"
            & $nssm set $ServiceName AppStderr "$logDir\service_stderr.log"

            Write-Information "✓ NetTrace Windows Service installed successfully" -InformationAction Continue
            Write-Information " Service Name: $ServiceName" -InformationAction Continue
            Write-Information " Display Name: $ServiceDisplayName" -InformationAction Continue
            Write-Information " Startup Type: Automatic" -InformationAction Continue
            Write-Information " Service Type: Windows Service (NSSM)" -InformationAction Continue
            Write-Information "" -InformationAction Continue
            Write-Information "Service is ready to use. Use 'NetTrace-ServiceRunner.ps1 -Start' to start the service." -InformationAction Continue

            return $true

        } catch {
            Write-Error "Failed to install NetTrace Windows Service: $($_.Exception.Message)"
            return $false
        }
    }
}

function Uninstall-NetTraceWindowsService {
    <#
    .SYNOPSIS
        Uninstalls the NetTrace Windows Service
    .DESCRIPTION
        Removes the NetTrace Windows Service using NSSM
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param()

    if ($PSCmdlet.ShouldProcess("NetTrace Windows Service", "Uninstall")) {
        Write-Information "Uninstalling NetTrace Windows Service..." -InformationAction Continue

            try {
            # Get NSSM
            $nssm = Get-NSSM
            if (-not $nssm) {
                Write-Warning "NSSM not available. Attempting standard service removal..."
            }

            # Stop service if running
            $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
            if ($service) {
                if ($service.Status -eq 'Running') {
                    Write-Information "Stopping service..." -InformationAction Continue
                    if ($nssm) {
                        & $nssm stop $ServiceName
                    } else {
                        Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue
                    }
                    Start-Sleep -Seconds 3
                }

                # Remove service
                if ($nssm) {
                    $removeResult = & $nssm remove $ServiceName confirm 2>&1
                    if ($LASTEXITCODE -eq 0) {
                        Write-Information "✓ NetTrace Windows Service uninstalled successfully" -InformationAction Continue
                    } else {
                        throw "NSSM remove failed: $removeResult"
                    }
                } else {
                    # Fallback to standard removal
                    Remove-Service -Name $ServiceName -ErrorAction Stop
                    Write-Information "✓ NetTrace Windows Service uninstalled successfully (standard removal)" -InformationAction Continue
                }
            } else {
                Write-Information "NetTrace Windows Service is not installed" -InformationAction Continue
            }

            # Clean up service state directory
            $ServiceStateDir = "$env:ProgramData\NetTrace"
            if (Test-Path $ServiceStateDir) {
                Write-Information "Cleaning up service state directory..." -InformationAction Continue
                Remove-Item $ServiceStateDir -Recurse -Force -ErrorAction SilentlyContinue
            }

        return $true

    } catch {
        Write-Error "Failed to uninstall NetTrace Windows Service: $($_.Exception.Message)"
        return $false
    }
    }
}

function Start-WindowsService {
    <#
    .SYNOPSIS
        Starts the NetTrace Windows Service
    .DESCRIPTION
        Starts the Windows Service using NSSM or standard service controls
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param()

    if ($PSCmdlet.ShouldProcess("NetTrace Windows Service", "Start")) {
        try {
        # Get NSSM
        $nssm = Get-NSSM

        # Check if service exists
        $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
        if (-not $service) {
            throw "NetTrace Windows Service is not installed. Use -Install first."
        }

        # Start service
        if ($nssm) {
            $startResult = & $nssm start $ServiceName 2>&1
            if ($LASTEXITCODE -ne 0) {
                throw "NSSM start failed: $startResult"
            }
        } else {
            Start-Service -Name $ServiceName -ErrorAction Stop
        }

        # Wait for service to start
        Start-Sleep -Seconds 3

        # Verify service is running
        $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
        if ($service -and $service.Status -eq 'Running') {
            Write-Information "✓ NetTrace Windows Service started successfully" -InformationAction Continue
            return $true
        } else {
            throw "Service failed to start properly"
        }

    } catch {
        Write-Error "Failed to start NetTrace Windows Service: $($_.Exception.Message)"
        return $false
    }
    }
}

function Stop-WindowsService {
    <#
    .SYNOPSIS
        Stops the NetTrace Windows Service
    .DESCRIPTION
        Stops the Windows Service using NSSM or standard service controls
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param()

    if ($PSCmdlet.ShouldProcess("NetTrace Windows Service", "Stop")) {
        try {
        # Get NSSM
        $nssm = Get-NSSM

        # Check if service exists
        $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
        if (-not $service) {
            Write-Information "NetTrace Windows Service is not installed" -InformationAction Continue
            return $true
        }

        # Stop service
        if ($service.Status -eq 'Running') {
            # Signal service to stop gracefully first
            Stop-NetTraceService

            Start-Sleep -Seconds 2

            # Use NSSM or standard service stop
            if ($nssm) {
                $stopResult = & $nssm stop $ServiceName 2>&1
                if ($LASTEXITCODE -ne 0) {
                    Write-Warning "NSSM stop returned: $stopResult"
                }
            } else {
                Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue
            }

            # Wait for service to stop
            Start-Sleep -Seconds 3

            # Verify service is stopped
            $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
            if ($service -and $service.Status -eq 'Stopped') {
                Write-Information "✓ NetTrace Windows Service stopped successfully" -InformationAction Continue
                return $true
            } else {
                Write-Warning "Service may not have stopped cleanly"
                return $false
            }
        } else {
            Write-Information "NetTrace Windows Service is already stopped" -InformationAction Continue
            return $true
        }

    } catch {
        Write-Error "Failed to stop NetTrace Windows Service: $($_.Exception.Message)"
        return $false
    }
    }
}

function Start-NetTraceServiceRunner {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string]$TracePath,
        [int]$MaxFiles,
        [int]$MaxSizeMB,
        [bool]$LogOutput,
        [bool]$EnableLogging
    )

    if ($PSCmdlet.ShouldProcess("NetTrace Windows Service", "Start monitoring")) {
        Write-Information "Starting NetTrace Windows Service..." -InformationAction Continue

    try {
        # Validate parameters
        if ($MaxFiles -le 0) {
            throw "MaxFiles parameter must be a positive integer"
        }
        if ($MaxSizeMB -le 0) {
            throw "MaxSizeMB parameter must be a positive integer"
        }
        if ($MaxSizeMB -lt 10) {
            throw "MaxSizeMB must be at least 10 MB. Netsh trace has a minimum file size of 10MB."
        }
        if ([string]::IsNullOrWhiteSpace($TracePath)) {
            throw "Path parameter is required"
        }

        # Ensure directory exists
        if (!(Test-Path $TracePath)) {
            New-Item -Path $TracePath -ItemType Directory -Force | Out-Null
            Write-Information "Created directory: $TracePath" -InformationAction Continue
        }

        # Check if service is installed
        $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
        if (-not $service) {
            Write-Information "NetTrace Windows Service is not installed. Installing automatically..." -InformationAction Continue
            $installSuccess = Install-NetTraceWindowsService
            if (-not $installSuccess) {
                throw "Failed to install NetTrace Windows Service"
            }
        }

        # Check if service is already running
        $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
        if ($service -and $service.Status -eq 'Running') {
            Write-Warning "NetTrace Windows Service is already running. Use -Stop first to stop the current session."
            return $false
        }

        # Configure the service
        $configSuccess = Start-NetTraceService -Path $TracePath -MaxFiles $MaxFiles -MaxSizeMB $MaxSizeMB -LogOutput $LogOutput -EnableLogging $EnableLogging
        if (-not $configSuccess) {
            throw "Failed to configure NetTrace service"
        }

        # Start the Windows Service
        $startSuccess = Start-WindowsService
        if (-not $startSuccess) {
            throw "Failed to start NetTrace Windows Service"
        }

        Write-Information "✓ NetTrace Windows Service started successfully" -InformationAction Continue
        Write-Information " Path: $TracePath" -InformationAction Continue
        Write-Information " Max Files: $MaxFiles" -InformationAction Continue
        Write-Information " Max Size: $MaxSizeMB MB" -InformationAction Continue
        Write-Information " Logging: $(if ($EnableLogging) { 'Enabled' } else { 'Disabled' })" -InformationAction Continue
        Write-Information " NetSH Output: $(if ($LogOutput) { 'Enabled' } else { 'Disabled' })" -InformationAction Continue
        Write-Information "" -InformationAction Continue
        Write-Information "Service is now running as a true Windows Service with complete persistence." -InformationAction Continue
        Write-Information "The service will survive user logouts and system reboots." -InformationAction Continue
        Write-Information "Use 'NetTrace -Stop' or 'NetTrace-ServiceRunner.ps1 -Stop' to stop the service." -InformationAction Continue

        if ($EnableLogging) {
            Write-Information "Monitor progress with: Get-Content '$TracePath\NetTrace_*.log' -Wait" -InformationAction Continue
        }

        return $true

    } catch {
        Write-Error "Error starting NetTrace Windows Service: $($_.Exception.Message)"
        return $false
    }
    }
}

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

    if ($PSCmdlet.ShouldProcess("NetTrace Windows Service", "Stop monitoring")) {
        Write-Information "Stopping NetTrace Windows Service..." -InformationAction Continue

        try {
            $success = Stop-WindowsService

            if ($success) {
                # Show final status
                $finalStatus = Get-ServiceStatus
                Write-Information "Final Status:" -InformationAction Continue
                Write-Information " Files Created: $($finalStatus.FilesCreated)" -InformationAction Continue
                Write-Information " Files Rolled: $($finalStatus.FilesRolled)" -InformationAction Continue

                return $true
            } else {
                Write-Error "Failed to stop NetTrace Windows Service"
                return $false
            }
        } catch {
            Write-Error "Error stopping NetTrace Windows Service: $($_.Exception.Message)"
            return $false
        }
    }
}

function Show-NetTraceServiceStatus {
    Write-Information "NetTrace Windows Service Status" -InformationAction Continue
    Write-Information "===============================" -InformationAction Continue

    try {
        # Check Windows Service status
        $windowsService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
        if ($windowsService) {
            $serviceStatusText = "Windows Service: $($windowsService.Status)"
            Write-Information $serviceStatusText -InformationAction Continue
            Write-Information "Service Type: True Windows Service (NSSM)" -InformationAction Continue
        } else {
            Write-Information "Windows Service: Not Installed" -InformationAction Continue
            Write-Information "Use -Install to install the service" -InformationAction Continue
        }

        # Check NetTrace service status
        $netTraceStatus = Get-ServiceStatus
        $netTraceStatusText = "NetTrace Service: $(if ($netTraceStatus.IsRunning) { 'Running' } else { 'Stopped' })"
        Write-Information $netTraceStatusText -InformationAction Continue

        if ($netTraceStatus.IsRunning) {
            Write-Information "" -InformationAction Continue
            Write-Information "Service Details:" -InformationAction Continue
            Write-Information " Files Created: $($netTraceStatus.FilesCreated)" -InformationAction Continue
            Write-Information " Files Rolled: $($netTraceStatus.FilesRolled)" -InformationAction Continue
            Write-Information " Current File: $($netTraceStatus.CurrentFile)" -InformationAction Continue
            Write-Information " Last Update: $($netTraceStatus.LastUpdate)" -InformationAction Continue

            if ($netTraceStatus.ErrorMessage) {
                Write-Information " Error: $($netTraceStatus.ErrorMessage)" -InformationAction Continue
            }

            # Show configuration
            $config = Get-ServiceConfig
            if ($config) {
                Write-Information "" -InformationAction Continue
                Write-Information "Configuration:" -InformationAction Continue
                Write-Information " Path: $($config.Path)" -InformationAction Continue
                Write-Information " Max Files: $($config.MaxFiles)" -InformationAction Continue
                Write-Information " Max Size: $($config.MaxSizeMB) MB" -InformationAction Continue
                Write-Information " Logging: $(if ($config.EnableLogging) { 'Enabled' } else { 'Disabled' })" -InformationAction Continue
                Write-Information " NetSH Output: $(if ($config.LogOutput) { 'Enabled' } else { 'Disabled' })" -InformationAction Continue
                Write-Information " Started: $($config.StartTime)" -InformationAction Continue
                Write-Information " Service Version: $($config.ServiceVersion)" -InformationAction Continue
            }
        } else {
            if ($netTraceStatus.ErrorMessage) {
                Write-Information "Last Error: $($netTraceStatus.ErrorMessage)" -InformationAction Continue
            }
        }

        # Show persistence information
        if ($windowsService -and $windowsService.Status -eq 'Running') {
            Write-Information "" -InformationAction Continue
            Write-Information "Persistence Status:" -InformationAction Continue
            Write-Information " ✓ True Windows Service - survives user logouts" -InformationAction Continue
            Write-Information " ✓ Auto-start enabled - survives system reboots" -InformationAction Continue
            Write-Information " ✓ Service recovery configured - automatic restart on failure" -InformationAction Continue
        }

        return $true

    } catch {
        Write-Error "Error retrieving NetTrace Windows Service status: $($_.Exception.Message)"
        return $false
    }
}

# Main execution logic
try {
    Write-Information "NetTrace Service Runner v1.2.2" -InformationAction Continue
    Write-Information "True Windows Service Implementation" -InformationAction Continue
    Write-Information "===============================" -InformationAction Continue
    Write-Information "" -InformationAction Continue

    switch ($PSCmdlet.ParameterSetName) {
        'Install' {
            if ($Install) {
                Install-NetTraceWindowsService | Out-Null
            }
        }
        'Uninstall' {
            if ($Uninstall) {
                Uninstall-NetTraceWindowsService | Out-Null
            }
        }
        'Start' {
            if ($Start) {
                Start-NetTraceServiceRunner -TracePath $Path -MaxFiles $MaxFiles -MaxSizeMB $MaxSizeMB -LogOutput:$LogOutput -EnableLogging:$EnableLogging | Out-Null
            }
        }
        'Stop' {
            if ($Stop) {
                Stop-NetTraceServiceRunner | Out-Null
            }
        }
        'Status' {
            Show-NetTraceServiceStatus | Out-Null
        }
    }
} catch {
    Write-Error "Unexpected error: $($_.Exception.Message)"
    exit 1
}