WinBatchOrchestrator.psm1

# WinBatchOrchestrator.psm1

function Initialize-OrchestratorEnvironment {
    <#
    .SYNOPSIS
        Initializes the C:\Ops environment.
    .PARAMETER ServiceAccount
        The service account user to grant access to secrets.
    #>

    param (
        [string]$ServiceAccount
    )

    $OpsRoot = "C:\Ops"
    $Dirs = @("Bin", "Scripts", "Logs", "Secrets")

    foreach ($Dir in $Dirs) {
        $Path = Join-Path $OpsRoot $Dir
        if (-not (Test-Path $Path)) {
            New-Item -ItemType Directory -Path $Path -Force | Out-Null
            Write-Host "Created $Path"
        }
    }

    # Event Source Creation (Requires Admin)
    $EventSource = "WinBatchOrchestrator"
    try {
        if (-not ([System.Diagnostics.EventLog]::SourceExists($EventSource))) {
            New-EventLog -LogName Application -Source $EventSource
            Write-Host "Created Event Source: $EventSource"
        }
    } catch {
        Write-Warning "Could not check or create Event Source '$EventSource'. Ensure you are running as Administrator if this is the first run. Error: $_"
    }

    # Version Check & Asset Deployment
    $ModuleRoot = $PSScriptRoot
    $AssetsDir = Join-Path $ModuleRoot "Assets"
    $VersionFile = Join-Path $OpsRoot "Bin\version.txt"
    $CurrentVersion = $MyInvocation.MyCommand.Module.Version.ToString()
    $DeployedVersion = "0.0.0"

    if (Test-Path $VersionFile) {
        $DeployedVersion = Get-Content -Path $VersionFile -Raw
    }

    if ($CurrentVersion -ne $DeployedVersion) {
        Write-Host "Upgrading Assets from $DeployedVersion to $CurrentVersion..."
        if (Test-Path $AssetsDir) {
      # Copy everything except Dashboard to Bin
      Get-ChildItem -Path $AssetsDir -Exclude "Dashboard" | Copy-Item -Destination "$OpsRoot\Bin" -Recurse -Force
            
      # Deploy Dashboard
      $DashboardAsset = Join-Path $AssetsDir "Dashboard"
      $DashboardDest = Join-Path $OpsRoot "Dashboard"
      if (Test-Path $DashboardAsset) {
        if (-not (Test-Path $DashboardDest)) {
          New-Item -ItemType Directory -Path $DashboardDest -Force | Out-Null
        }
        Copy-Item -Path "$DashboardAsset\*" -Destination $DashboardDest -Recurse -Force
        Write-Host "Deployed Dashboard to $DashboardDest"
      }

            $CurrentVersion | Set-Content -Path $VersionFile
            Write-Host "Deployed Assets to $OpsRoot\Bin"
        }
    } else {
        Write-Host "Assets are up to date ($CurrentVersion)."
    }

    # Move Templates (Always ensure they are present)
    $Templates = Get-ChildItem -Path "$OpsRoot\Bin" -Filter "Template-*"
    foreach ($Template in $Templates) {
        $Dest = Join-Path "$OpsRoot\Scripts" $Template.Name
        if (-not (Test-Path $Dest)) {
            Move-Item -Path $Template.FullName -Destination "$OpsRoot\Scripts" -Force
            Write-Host "Moved Template $($Template.Name) to $OpsRoot\Scripts"
        }
    }

    # Security for Secrets
    $SecretsPath = Join-Path $OpsRoot "Secrets"
    $Acl = Get-Acl -Path $SecretsPath
    
    # Disable inheritance and copy existing rules
    $Acl.SetAccessRuleProtection($true, $false)
    
    # Grant Admin/System Full Control
    $AdminRule = New-Object System.Security.AccessControl.FileSystemAccessRule("Administrators", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")
    $SystemRule = New-Object System.Security.AccessControl.FileSystemAccessRule("SYSTEM", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")
    $Acl.AddAccessRule($AdminRule)
    $Acl.AddAccessRule($SystemRule)

    # Grant Service Account Read/Execute if provided
    if (-not [string]::IsNullOrWhiteSpace($ServiceAccount)) {
        $ServiceRule = New-Object System.Security.AccessControl.FileSystemAccessRule($ServiceAccount, "ReadAndExecute", "ContainerInherit,ObjectInherit", "None", "Allow")
        $Acl.AddAccessRule($ServiceRule)
    }

    Set-Acl -Path $SecretsPath -AclObject $Acl
    Write-Host "Secured $SecretsPath"

    # Register Maintenance Task (Cleanup Logs)
    $CleanupScript = "C:\Ops\Bin\Cleanup-OpsLogs.ps1"
    if (Test-Path -Path $CleanupScript) {
        $Params = @{
            JobName         = "OpsMaintenance-LogCleanup"
            ScriptPath      = $CleanupScript
            ScheduleTime    = [datetime]::Parse("02:00")
            ScriptArguments = @("-DaysToKeep", "30")
        }
        if (-not [string]::IsNullOrWhiteSpace($ServiceAccount)) {
            $Params["ServiceAccountUser"] = $ServiceAccount
        }
        # Use the internal helper or call the script directly if function not available in scope yet
        & "C:\Ops\Bin\New-OpsJob.ps1" @Params
        Write-Host "Registered Maintenance Task: OpsMaintenance-LogCleanup"
    }
}
# End of Initialize-OrchestratorEnvironment

function Invoke-OpsScript {
    param (
        [string]$ScriptName,
        [hashtable]$Arguments = @{}
    )

    $ScriptPath = Join-Path "C:\Ops\Bin" $ScriptName
    if (-not (Test-Path $ScriptPath)) {
        throw "Script not found: $ScriptPath. Run Initialize-OrchestratorEnvironment first."
    }

    & $ScriptPath @Arguments
}

# Proxy Functions

function New-OpsJob {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$JobName,
        [Parameter(Mandatory=$true)][string]$ScriptPath,
        [Parameter(Mandatory=$true)][datetime]$ScheduleTime,
        [string]$ServiceAccountUser,
        [timespan]$Interval,
        [string[]]$ScriptArguments,
        [string[]]$EmailRecipients,
        [string]$AlertWebhookUrl,
        [string[]]$RequiredSecrets
    )
    Invoke-OpsScript -ScriptName "New-OpsJob.ps1" -Arguments $PSBoundParameters
}

function Register-OpsJobs {
    [CmdletBinding()]
    param([Parameter(Mandatory=$true)][string]$ConfigPath)
    Invoke-OpsScript -ScriptName "Register-OpsJobs.ps1" -Arguments $PSBoundParameters
}

function New-OpsCreds {
    [CmdletBinding()]
    param([Parameter(Mandatory=$true)][string]$Name)
    Invoke-OpsScript -ScriptName "New-OpsCreds.ps1" -Arguments $PSBoundParameters
}

function Get-OpsJob {
    [CmdletBinding()]
    param([string]$JobName)
    Invoke-OpsScript -ScriptName "Get-OpsJob.ps1" -Arguments $PSBoundParameters
}

function Start-OpsJob {
    [CmdletBinding()]
    param([Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)][string]$JobName)
    process {
        Invoke-OpsScript -ScriptName "Start-OpsJob.ps1" -Arguments @{ JobName = $JobName }
    }
}


function Stop-OpsJob {
  [CmdletBinding()]
  param([Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$JobName)
  process {
    Invoke-OpsScript -ScriptName "Stop-OpsJob.ps1" -Arguments @{ JobName = $JobName }
  }
}

function Remove-OpsJob {
  [CmdletBinding()]
  param([Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$JobName)
  process {
    Invoke-OpsScript -ScriptName "Remove-OpsJob.ps1" -Arguments @{ JobName = $JobName }
  }
}

function Get-OpsJobHistory {
  [CmdletBinding()]
  param([string]$JobName, [int]$Count)
  Invoke-OpsScript -ScriptName "Get-OpsJobHistory.ps1" -Arguments $PSBoundParameters
}

function Get-OpsJobLog {
  [CmdletBinding()]
  param([string]$JobName, [int]$Count)
  Invoke-OpsScript -ScriptName "Get-OpsJobLog.ps1" -Arguments $PSBoundParameters
}

Export-ModuleMember -Function Initialize-OrchestratorEnvironment, New-OpsJob, Register-OpsJobs, New-OpsCreds, Get-OpsJob, Start-OpsJob, Stop-OpsJob, Remove-OpsJob, Get-OpsJobHistory, Get-OpsJobLog