OpenEndpointEvents.Uploader.psm1

# OpenEndpointEvents.Uploader.psm1
# Uploader companion module for OpenEndpointEvents.

Set-StrictMode -Version Latest

$script:DefaultBaseRoot = "C:\ProgramData\OpenEndpointEvents"
$script:DefaultUploadRoot = "C:\ProgramData\OpenEndpointEvents\Upload"
$script:DefaultLegacyScriptRoot = "C:\ProgramData\OpenEndpointEvents\Scripts"

function Import-OpenEndpointEventsQuiet {
    if (Get-Command Write-EndpointInfo -ErrorAction SilentlyContinue) {
        return
    }

    $module = Get-Module -ListAvailable OpenEndpointEvents |
        Sort-Object Version -Descending |
        Select-Object -First 1

    if (-not $module) {
        throw "OpenEndpointEvents module 1.1.0 or later is required."
    }

    if ($module.Version -lt [version]"1.1.0") {
        throw "OpenEndpointEvents 1.1.0 or later is required. Found $($module.Version)."
    }

    Import-Module $module.Path `
        -Force `
        -Global `
        -ErrorAction Stop `
        -Verbose:$false
}

function Get-OpenEndpointEventsUploaderModuleBase {
    $module = Get-Module -ListAvailable OpenEndpointEvents.Uploader |
        Sort-Object Version -Descending |
        Select-Object -First 1

    if (-not $module) {
        return $PSScriptRoot
    }

    return $module.ModuleBase
}

function Get-EndpointEventUploaderPath {
    [CmdletBinding()]
    param()

    $baseRoot = $script:DefaultBaseRoot
    $uploadRoot = $script:DefaultUploadRoot
    $scriptRoot = Join-Path $uploadRoot "Scripts"
    $configRoot = Join-Path $uploadRoot "Config"
    $stateRoot = Join-Path $uploadRoot "State"

    [pscustomobject]@{
        BaseRoot                = $baseRoot
        UploadRoot              = $uploadRoot
        ScriptRoot              = $scriptRoot
        ConfigRoot              = $configRoot
        StateRoot               = $stateRoot
        LogsRoot                = Join-Path $baseRoot "Logs"
        RefreshConfigPath       = Join-Path $configRoot "config-refresh.json"
        UploaderConfigPath      = Join-Path $configRoot "uploader-config.json"
        ConfigRefreshStatePath  = Join-Path $stateRoot "config-refresh-state.json"
        UploadStatePath         = Join-Path $stateRoot "upload-state.json"
        UploadRunStatePath      = Join-Path $stateRoot "upload-run-state.json"
        InstallScriptPath       = Join-Path $scriptRoot "Install-OpenEndpointEventsUploader.ps1"
        UploadScriptPath        = Join-Path $scriptRoot "Upload-EndpointEvents.ps1"
        ConfigRefreshScriptPath = Join-Path $scriptRoot "Update-OpenEndpointEventsConfig.ps1"
        LegacyScriptRoot        = $script:DefaultLegacyScriptRoot
    }
}

function Install-EndpointEventUploader {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ConfigUri,

        [switch]$StartNow,

        [switch]$ForceInstall
    )

    Import-OpenEndpointEventsQuiet

    $moduleBase = Get-OpenEndpointEventsUploaderModuleBase
    $installer = Join-Path $moduleBase "Scripts\Install-OpenEndpointEventsUploader.ps1"

    if (-not (Test-Path -Path $installer)) {
        throw "Installer script not found in module package: $installer"
    }

    $args = @(
        "-NoProfile",
        "-ExecutionPolicy", "Bypass",
        "-File", $installer,
        "-ConfigUri", $ConfigUri
    )

    if ($StartNow) {
        $args += "-StartNow"
    }

    if ($ForceInstall) {
        $args += "-ForceInstall"
    }

    if ($VerbosePreference -eq "Continue") {
        $args += "-Verbose"
    }

    & powershell.exe @args

    if ($LASTEXITCODE -ne 0) {
        throw "Uploader installer failed with exit code $LASTEXITCODE."
    }
}

function Invoke-EndpointEventUpload {
    [CmdletBinding()]
    param(
        [string]$ConfigPath,

        [ValidateSet("Hourly", "Daily", "Weekly", "AllChanged", "SinceDate")]
        [string]$Window = "AllChanged",

        [datetime]$Since,

        [switch]$Now,

        [switch]$ForceUpload
    )

    Import-OpenEndpointEventsQuiet

    $paths = Get-EndpointEventUploaderPath
    $uploadScript = $paths.UploadScriptPath

    if (-not (Test-Path -Path $uploadScript)) {
        throw "Upload script not found. Run Install-EndpointEventUploader first."
    }

    if ([string]::IsNullOrWhiteSpace($ConfigPath)) {
        $ConfigPath = $paths.UploaderConfigPath
    }

    $args = @(
        "-NoProfile",
        "-ExecutionPolicy", "Bypass",
        "-File", $uploadScript,
        "-ConfigPath", $ConfigPath,
        "-Window", $Window
    )

    if ($Since) {
        $args += @("-Since", $Since.ToString("o"))
    }

    if ($Now) {
        $args += "-Now"
    }

    if ($ForceUpload) {
        $args += "-ForceUpload"
    }

    if ($VerbosePreference -eq "Continue") {
        $args += "-Verbose"
    }

    & powershell.exe @args

    if ($LASTEXITCODE -ne 0) {
        throw "Endpoint event upload failed with exit code $LASTEXITCODE."
    }
}

function Update-EndpointEventUploaderConfig {
    [CmdletBinding()]
    param(
        [string]$RefreshConfigPath,

        [switch]$Force,

        [switch]$ApplySchedule,

        [switch]$StartUploaderAfterUpdate
    )

    Import-OpenEndpointEventsQuiet

    $paths = Get-EndpointEventUploaderPath
    $configScript = $paths.ConfigRefreshScriptPath

    if (-not (Test-Path -Path $configScript)) {
        throw "Config refresh script not found. Run Install-EndpointEventUploader first."
    }

    if ([string]::IsNullOrWhiteSpace($RefreshConfigPath)) {
        $RefreshConfigPath = $paths.RefreshConfigPath
    }

    $args = @(
        "-NoProfile",
        "-ExecutionPolicy", "Bypass",
        "-File", $configScript,
        "-RefreshConfigPath", $RefreshConfigPath
    )

    if ($Force) {
        $args += "-Force"
    }

    if ($ApplySchedule) {
        $args += "-ApplySchedule"
    }

    if ($StartUploaderAfterUpdate) {
        $args += "-StartUploaderAfterUpdate"
    }

    if ($VerbosePreference -eq "Continue") {
        $args += "-Verbose"
    }

    & powershell.exe @args

    if ($LASTEXITCODE -ne 0) {
        throw "Uploader config refresh failed with exit code $LASTEXITCODE."
    }
}

function Get-EndpointEventUploaderState {
    [CmdletBinding()]
    param()

    $paths = Get-EndpointEventUploaderPath

    $uploadState = $null
    $runState = $null
    $configRefreshState = $null

    if (Test-Path -Path $paths.UploadStatePath) {
        $uploadState = Get-Content -Path $paths.UploadStatePath -Raw | ConvertFrom-Json
    }

    if (Test-Path -Path $paths.UploadRunStatePath) {
        $runState = Get-Content -Path $paths.UploadRunStatePath -Raw | ConvertFrom-Json
    }

    if (Test-Path -Path $paths.ConfigRefreshStatePath) {
        $configRefreshState = Get-Content -Path $paths.ConfigRefreshStatePath -Raw | ConvertFrom-Json
    }

    [pscustomobject]@{
        UploadStatePath        = $paths.UploadStatePath
        UploadRunStatePath     = $paths.UploadRunStatePath
        ConfigRefreshStatePath = $paths.ConfigRefreshStatePath
        UploadState            = $uploadState
        UploadRunState         = $runState
        ConfigRefreshState     = $configRefreshState
    }
}

function Test-EndpointEventUploaderConfig {
    [CmdletBinding()]
    param()

    $paths = Get-EndpointEventUploaderPath

    $items = @(
        [pscustomobject]@{ Name = "UploadRoot"; Path = $paths.UploadRoot; Required = $true },
        [pscustomobject]@{ Name = "ConfigRoot"; Path = $paths.ConfigRoot; Required = $true },
        [pscustomobject]@{ Name = "StateRoot"; Path = $paths.StateRoot; Required = $true },
        [pscustomobject]@{ Name = "ScriptRoot"; Path = $paths.ScriptRoot; Required = $true },
        [pscustomobject]@{ Name = "RefreshConfig"; Path = $paths.RefreshConfigPath; Required = $true },
        [pscustomobject]@{ Name = "UploaderConfig"; Path = $paths.UploaderConfigPath; Required = $true },
        [pscustomobject]@{ Name = "UploadScript"; Path = $paths.UploadScriptPath; Required = $true },
        [pscustomobject]@{ Name = "ConfigRefreshScript"; Path = $paths.ConfigRefreshScriptPath; Required = $true }
    )

    $results = foreach ($item in $items) {
        [pscustomobject]@{
            Name     = $item.Name
            Path     = $item.Path
            Required = $item.Required
            Exists   = Test-Path -Path $item.Path
        }
    }

    $isValid = -not ($results | Where-Object { $_.Required -and -not $_.Exists })

    [pscustomobject]@{
        IsValid = $isValid
        Results = $results
    }
}

Export-ModuleMember -Function `
    Install-EndpointEventUploader, `
    Invoke-EndpointEventUpload, `
    Update-EndpointEventUploaderConfig, `
    Get-EndpointEventUploaderState, `
    Test-EndpointEventUploaderConfig, `
    Get-EndpointEventUploaderPath