iPERF2GO.psm1

<#
.SYNOPSIS
    IPERF2GO: A PowerShell module for staging, launching, and cleaning up iperf resources.
 
.DESCRIPTION
    This module downloads and stages iperf from a GitHub-hosted ZIP file into a temporary folder.
    It appends the staging folder to your session's PATH and auto-detects the iperf executable.
    Subsequent commands call the iperf executable by name without specifying its full path.
#>


#region Module Variables
$script:StagedIperfPath = $null
$script:StagedIperfExe  = $null
#endregion

#region Module Functions

function Initialize-Iperf {
    [CmdletBinding()]
    Param(
        [Parameter()]
        [string]$GitHubUrl = "https://github.com/Kalichuza/ninjaAdminScripts/raw/refs/heads/main/MISC/iperf3.18_64.zip",
        [Parameter(Mandatory = $false)]
        [string]$Destination = "$env:TEMP\IPERF2GO_$([guid]::NewGuid())"
    )
    Write-Verbose "Starting initialization of iperf resources."
    Write-Verbose "Destination folder: $Destination"
    Write-Verbose "GitHub URL: $GitHubUrl"

    # Create the destination folder if it doesn't exist.
    if (-not (Test-Path -Path $Destination)) {
        Write-Verbose "Creating destination folder..."
        try {
            New-Item -Path $Destination -ItemType Directory -Force | Out-Null
            Write-Verbose "Folder created."
        }
        catch {
            Write-Error "Failed to create destination folder: $_"
            return
        }
    }
    else {
        Write-Verbose "Destination folder already exists."
    }

    # Download the ZIP file.
    $zipFile = Join-Path -Path $Destination -ChildPath "iperf.zip"
    Write-Verbose "Downloading zip file from '$GitHubUrl' to '$zipFile'"
    try {
        Invoke-WebRequest -Uri $GitHubUrl -OutFile $zipFile -UseBasicParsing -ErrorAction Stop
        Write-Verbose "Download completed successfully."
    }
    catch {
        Write-Error "Download failed: $_"
        return
    }

    # Extract the ZIP file.
    Write-Verbose "Extracting zip file '$zipFile' to '$Destination'"
    try {
        Expand-Archive -Path $zipFile -DestinationPath $Destination -Force -ErrorAction Stop
        Write-Verbose "Extraction completed successfully."
    }
    catch {
        Write-Error "Extraction failed: $_"
        return
    }
    # Remove the zip file.
    try {
        Remove-Item -Path $zipFile -Force
        Write-Verbose "Removed zip file '$zipFile'"
    }
    catch {
        Write-Warning "Failed to remove zip file: $_"
    }

    # Determine the staging path.
    Write-Verbose "Determining the correct staging path..."
    $subDirs = Get-ChildItem -Path $Destination -Directory -ErrorAction SilentlyContinue
    if ($subDirs.Count -eq 1) {
        $candidate = $subDirs[0].FullName
        Write-Verbose "Single subdirectory found: '$candidate'"
        $filesInCandidate = Get-ChildItem -Path $candidate -Recurse -ErrorAction SilentlyContinue
        if ($filesInCandidate.Count -gt 0) {
            Write-Verbose "Candidate folder contains $($filesInCandidate.Count) files. Using candidate as staging path."
            $stagingPath = $candidate
        }
        else {
            Write-Verbose "Candidate folder is empty. Using destination folder as staging path."
            $stagingPath = $Destination
        }
    }
    else {
        Write-Verbose "Multiple or no subdirectories found. Using destination folder as staging path."
        $stagingPath = $Destination
    }

    # Append the staging path to the session's PATH.
    Write-Verbose "Appending staging path to session PATH: '$stagingPath'"
    $env:Path += ";" + $stagingPath

    # Detect the iperf executable in the staging path.
    $exeFiles = Get-ChildItem -Path $stagingPath -Recurse -Filter *.exe |
                Where-Object { $_.Name -like "*iperf*.exe" }
    if ($exeFiles -and $exeFiles.Count -ge 1) {
        $script:StagedIperfExe = $exeFiles[0].Name
        Write-Output "iperf resources have been initialized at: $stagingPath"
        Write-Output "Detected iperf executable: $script:StagedIperfExe (staged folder added to PATH)"
    }
    else {
        Write-Error "No iperf executable found in staged path: $stagingPath"
    }
    $script:StagedIperfPath = $stagingPath
}

function Start-IperfListener {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false)]
        [int]$Port = 5201,
        [Parameter(Mandatory = $false)]
        [switch]$Background
    )
    if (-not $script:StagedIperfExe) {
        Write-Error "No iperf executable detected. Please run Initialize-Iperf first."
        return
    }
    $iperfExe = $script:StagedIperfExe
    if (-not (Get-Command $iperfExe -ErrorAction SilentlyContinue)) {
        Write-Error "iperf executable '$iperfExe' not found in PATH. Ensure initialization succeeded."
        return
    }
    Write-Verbose "Starting iperf listener on port $Port..."
    $arguments = @("-s", "-p", $Port)
    if ($Background) {
        Write-Output "Starting iperf listener in background..."
        try {
            Start-Process -FilePath $iperfExe -ArgumentList $arguments -WindowStyle Hidden -ErrorAction Stop
        }
        catch {
            Write-Error "Failed to start iperf listener in background. Error: $_"
        }
    }
    else {
        Write-Output "Starting iperf listener in current session..."
        try {
            & $iperfExe @arguments
        }
        catch {
            Write-Error "Failed to run iperf listener. Error: $_"
        }
    }
}

function Start-IperfCaller {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Server,
        [Parameter(Mandatory = $false)]
        [int]$Port = 5201,
        [Parameter(Mandatory = $false)]
        [string]$AdditionalArguments
    )
    if (-not $script:StagedIperfExe) {
        Write-Error "No iperf executable detected. Please run Initialize-Iperf first."
        return
    }
    $iperfExe = $script:StagedIperfExe
    if (-not (Get-Command $iperfExe -ErrorAction SilentlyContinue)) {
        Write-Error "iperf executable '$iperfExe' not found in PATH. Ensure initialization succeeded."
        return
    }
    Write-Verbose "Starting iperf client to connect to server $Server on port $Port..."
    $arguments = @("-c", $Server, "-p", $Port)
    if ($AdditionalArguments) {
        $arguments += $AdditionalArguments.Split(" ")
    }
    Write-Output "Running iperf client..."
    try {
        & $iperfExe @arguments
    }
    catch {
        Write-Error "Failed to run iperf caller. Error: $_"
    }
}

function Remove-IperfSession {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false)]
        [string]$ResourcePath
    )
    if (-not $ResourcePath) {
        if ($script:StagedIperfPath) {
            $ResourcePath = $script:StagedIperfPath
        }
        else {
            Write-Error "No resource path provided and module has not been initialized."
            return
        }
    }
    Write-Verbose "Cleaning up iperf resources from $ResourcePath..."
    if (Test-Path -Path $ResourcePath) {
        try {
            Remove-Item -Path $ResourcePath -Recurse -Force -ErrorAction Stop
            Write-Output "Successfully cleaned up iperf resources from $ResourcePath."
            $script:StagedIperfPath = $null
            $script:StagedIperfExe  = $null
        }
        catch {
            Write-Error "Failed to clean up iperf resources. Error: $_"
        }
    }
    else {
        Write-Output "Resource path $ResourcePath does not exist."
    }
}

function Enable-IperfFirewallRule {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false)]
        [int]$Port = 5201
    )
    Write-Verbose "Enabling firewall rule for iperf on port $Port..."
    $ruleName = "IPERF2GO_Rule_$Port"
    try {
        New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -LocalPort $Port -Protocol TCP -Action Allow -Profile Any -ErrorAction Stop
        Write-Output "Firewall rule '$ruleName' has been enabled."
    }
    catch {
        Write-Error "Failed to enable firewall rule. Error: $_"
    }
}

function Disable-IperfFirewallRule {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false)]
        [int]$Port = 5201
    )
    Write-Verbose "Disabling firewall rule for iperf on port $Port..."
    $ruleName = "IPERF2GO_Rule_$Port"
    try {
        Remove-NetFirewallRule -DisplayName $ruleName -ErrorAction Stop
        Write-Output "Firewall rule '$ruleName' has been removed."
    }
    catch {
        Write-Error "Failed to disable firewall rule. Error: $_"
    }
}

#endregion

Export-ModuleMember -Function *