1278 lines
54 KiB
PowerShell
1278 lines
54 KiB
PowerShell
param(
|
|
[string]$ServerUrl = "",
|
|
[string]$AgentId = "",
|
|
[string]$ConfigPath = "C:\ProgramData\SFERA\WindowsAgent\agent-config.json",
|
|
[string[]]$NetworkRoot = @(),
|
|
[int]$PollSeconds = 5
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
$AgentVersion = "0.2.31"
|
|
$startedAt = (Get-Date).ToUniversalTime().ToString("o")
|
|
$JsonContentType = "application/json; charset=utf-8"
|
|
|
|
function Write-AgentLog {
|
|
param([string]$Message)
|
|
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
Write-Host "[$stamp] $Message"
|
|
}
|
|
|
|
function Get-AgentApiBase {
|
|
param([object]$Config)
|
|
if ($Config.PSObject.Properties.Name -contains "api_url" -and ![string]::IsNullOrWhiteSpace($Config.api_url)) {
|
|
return ([string]$Config.api_url).TrimEnd("/")
|
|
}
|
|
try {
|
|
$uri = [System.Uri]([string]$Config.server_url)
|
|
if ($uri.Port -eq 49230) {
|
|
$builder = New-Object System.UriBuilder($uri)
|
|
$builder.Port = 49280
|
|
return $builder.Uri.AbsoluteUri.TrimEnd("/")
|
|
}
|
|
} catch {
|
|
# Fall back to the web proxy below.
|
|
}
|
|
return "$(([string]$Config.server_url).TrimEnd("/"))/api/sfera"
|
|
}
|
|
|
|
function Agent-ApiUrl {
|
|
param([string]$Path)
|
|
return "$script:AgentApiBase$Path"
|
|
}
|
|
|
|
function Invoke-AgentRestMethod {
|
|
param(
|
|
[string]$Method,
|
|
[string]$Uri,
|
|
[string]$ContentType = $null,
|
|
[string]$Body = $null
|
|
)
|
|
$arguments = @{
|
|
Method = $Method
|
|
Uri = $Uri
|
|
TimeoutSec = 20
|
|
}
|
|
if ($ContentType) { $arguments.ContentType = $ContentType }
|
|
if ($Body) { $arguments.Body = $Body }
|
|
return Invoke-RestMethod @arguments
|
|
}
|
|
|
|
function Invoke-AgentWebRequest {
|
|
param(
|
|
[string]$Method = "Get",
|
|
[string]$Uri,
|
|
[string]$OutFile = $null,
|
|
[string]$ContentType = $null,
|
|
[string]$InFile = $null,
|
|
[int]$TimeoutSec = 20
|
|
)
|
|
$arguments = @{
|
|
Method = $Method
|
|
Uri = $Uri
|
|
TimeoutSec = $TimeoutSec
|
|
}
|
|
if ($OutFile) { $arguments.OutFile = $OutFile }
|
|
if ($ContentType) { $arguments.ContentType = $ContentType }
|
|
if ($InFile) { $arguments.InFile = $InFile }
|
|
return Invoke-WebRequest -UseBasicParsing @arguments
|
|
}
|
|
|
|
function Get-AgentPowerShellPath {
|
|
$windowsPowerShell = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
|
|
if (Test-Path -LiteralPath $windowsPowerShell -PathType Leaf) {
|
|
return $windowsPowerShell
|
|
}
|
|
return Join-Path $PSHOME "powershell.exe"
|
|
}
|
|
|
|
$script:TrayIcon = $null
|
|
$script:TrayServer = $null
|
|
$script:TrayGreenIcon = $null
|
|
$script:TrayRedIcon = $null
|
|
|
|
function New-AgentStatusIcon {
|
|
param([string]$ColorName)
|
|
$bitmap = New-Object System.Drawing.Bitmap 16, 16
|
|
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
|
$graphics.Clear([System.Drawing.Color]::Transparent)
|
|
$brush = if ($ColorName -eq "Green") {
|
|
New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(37, 160, 80))
|
|
} else {
|
|
New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(210, 62, 62))
|
|
}
|
|
$border = New-Object System.Drawing.Pen ([System.Drawing.Color]::White), 2
|
|
$graphics.FillEllipse($brush, 2, 2, 12, 12)
|
|
$graphics.DrawEllipse($border, 2, 2, 12, 12)
|
|
$handle = $bitmap.GetHicon()
|
|
$icon = [System.Drawing.Icon]::FromHandle($handle).Clone()
|
|
$graphics.Dispose()
|
|
$brush.Dispose()
|
|
$border.Dispose()
|
|
$bitmap.Dispose()
|
|
return $icon
|
|
}
|
|
|
|
function Initialize-TrayIcon {
|
|
param([string]$Server, [string]$AgentId)
|
|
try {
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
Add-Type -AssemblyName System.Drawing
|
|
$script:TrayServer = $Server
|
|
$script:TrayGreenIcon = New-AgentStatusIcon -ColorName "Green"
|
|
$script:TrayRedIcon = New-AgentStatusIcon -ColorName "Red"
|
|
|
|
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
$openItem = New-Object System.Windows.Forms.ToolStripMenuItem("Open SFERA")
|
|
$openItem.add_Click({ try { Start-Process $script:TrayServer } catch {} })
|
|
$exitItem = New-Object System.Windows.Forms.ToolStripMenuItem("Exit agent")
|
|
$exitItem.add_Click({
|
|
if ($script:TrayIcon) {
|
|
$script:TrayIcon.Visible = $false
|
|
$script:TrayIcon.Dispose()
|
|
}
|
|
exit 0
|
|
})
|
|
[void]$menu.Items.Add($openItem)
|
|
[void]$menu.Items.Add($exitItem)
|
|
|
|
$script:TrayIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
$script:TrayIcon.Icon = $script:TrayRedIcon
|
|
$script:TrayIcon.ContextMenuStrip = $menu
|
|
$script:TrayIcon.Visible = $true
|
|
$script:TrayIcon.Text = "SFERA Agent: connecting"
|
|
Write-AgentLog "Tray icon initialized for $AgentId."
|
|
} catch {
|
|
Write-AgentLog "Tray icon unavailable: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Update-TrayIcon {
|
|
param([bool]$Online, [string]$Message)
|
|
if ($null -eq $script:TrayIcon) { return }
|
|
$text = if ($Online) { "SFERA Agent: online" } else { "SFERA Agent: offline" }
|
|
if (![string]::IsNullOrWhiteSpace($Message)) {
|
|
$text = "$text - $Message"
|
|
}
|
|
if ($text.Length -gt 63) {
|
|
$text = $text.Substring(0, 60) + "..."
|
|
}
|
|
$script:TrayIcon.Icon = if ($Online) { $script:TrayGreenIcon } else { $script:TrayRedIcon }
|
|
$script:TrayIcon.Text = $text
|
|
[System.Windows.Forms.Application]::DoEvents()
|
|
}
|
|
|
|
function Pump-TrayIcon {
|
|
if ($null -ne $script:TrayIcon) {
|
|
[System.Windows.Forms.Application]::DoEvents()
|
|
}
|
|
}
|
|
|
|
function Read-AgentConfig {
|
|
if (Test-Path -LiteralPath $ConfigPath -PathType Leaf) {
|
|
return Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Write-AgentConfig {
|
|
param([object]$Config)
|
|
$dir = Split-Path -Path $ConfigPath -Parent
|
|
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
$Config | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $ConfigPath -Encoding UTF8
|
|
}
|
|
|
|
function Initialize-AgentConfig {
|
|
$config = Read-AgentConfig
|
|
if ($null -eq $config) {
|
|
if ([string]::IsNullOrWhiteSpace($ServerUrl)) {
|
|
throw "ServerUrl is required for first start or install from SFERA web."
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($AgentId)) {
|
|
$AgentId = "sfera-$env:COMPUTERNAME"
|
|
}
|
|
$config = [ordered]@{
|
|
server_url = $ServerUrl.TrimEnd("/")
|
|
api_url = ""
|
|
agent_id = $AgentId
|
|
poll_seconds = $PollSeconds
|
|
network_roots = @($NetworkRoot)
|
|
}
|
|
Write-AgentConfig -Config $config
|
|
return $config
|
|
}
|
|
if (![string]::IsNullOrWhiteSpace($ServerUrl)) { $config.server_url = $ServerUrl.TrimEnd("/") }
|
|
if (![string]::IsNullOrWhiteSpace($AgentId)) { $config.agent_id = $AgentId }
|
|
if ($NetworkRoot.Count -gt 0) { $config.network_roots = @($NetworkRoot) }
|
|
if ($PollSeconds -gt 0) { $config.poll_seconds = $PollSeconds }
|
|
Write-AgentConfig -Config $config
|
|
return $config
|
|
}
|
|
|
|
function Find-1CPlatformBins {
|
|
$roots = @(
|
|
"${env:ProgramFiles}\1cv8",
|
|
"${env:ProgramFiles(x86)}\1cv8"
|
|
) | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Container) }
|
|
$bins = foreach ($root in $roots) {
|
|
Get-ChildItem -LiteralPath $root -Recurse -Filter "1cv8.exe" -ErrorAction SilentlyContinue |
|
|
Select-Object -ExpandProperty FullName
|
|
}
|
|
return @($bins | Sort-Object -Descending | Select-Object -First 20)
|
|
}
|
|
|
|
function Get-AgentNetworkRoots {
|
|
param([object]$Config)
|
|
$roots = @($Config.network_roots) | Where-Object { ![string]::IsNullOrWhiteSpace($_) }
|
|
$mappedRoots = Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue |
|
|
Where-Object { ![string]::IsNullOrWhiteSpace($_.DisplayRoot) } |
|
|
Select-Object -ExpandProperty DisplayRoot
|
|
return @($roots + $mappedRoots | Sort-Object -Unique)
|
|
}
|
|
|
|
function Sync-AgentConfigFromServer {
|
|
param([object]$Config)
|
|
try {
|
|
$agentQuery = [System.Uri]::EscapeDataString($Config.agent_id)
|
|
$remote = Invoke-AgentRestMethod -Method Get -Uri (Agent-ApiUrl "/agent/config?agent_id=$agentQuery")
|
|
$roots = @($Config.network_roots)
|
|
foreach ($project in @($remote.projects)) {
|
|
foreach ($root in @($project.network_roots)) {
|
|
if (![string]::IsNullOrWhiteSpace($root) -and $roots -notcontains $root) {
|
|
$roots += $root
|
|
}
|
|
}
|
|
}
|
|
$Config.network_roots = @($roots)
|
|
if ($remote.poll_seconds -gt 0) {
|
|
$Config.poll_seconds = [int]$remote.poll_seconds
|
|
}
|
|
Write-AgentConfig -Config $Config
|
|
} catch {
|
|
Write-AgentLog "Config sync skipped: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Test-AgentUpdate {
|
|
param()
|
|
try {
|
|
$manifest = Invoke-AgentRestMethod -Method Get -Uri (Agent-ApiUrl "/agent/windows/manifest")
|
|
$localHash = (Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
$remoteVersion = [string]$manifest.version
|
|
$remoteHash = [string]$manifest.script_hash
|
|
$needsUpdate = $false
|
|
if (![string]::IsNullOrWhiteSpace($remoteVersion)) {
|
|
$needsUpdate = $remoteVersion -ne $AgentVersion
|
|
} elseif (![string]::IsNullOrWhiteSpace($remoteHash)) {
|
|
$needsUpdate = !$localHash.StartsWith($remoteHash)
|
|
}
|
|
if ($needsUpdate) {
|
|
$tmp = "$PSCommandPath.new"
|
|
Invoke-AgentWebRequest -Uri $manifest.script_url -OutFile $tmp
|
|
Move-Item -LiteralPath $tmp -Destination $PSCommandPath -Force
|
|
Write-AgentLog "Agent updated from server. Restarting agent process."
|
|
$powerShellPath = Get-AgentPowerShellPath
|
|
$arguments = @("-STA", "-NoProfile", "-WindowStyle", "Hidden", "-ExecutionPolicy", "Bypass", "-File", "`"$PSCommandPath`"", "-ConfigPath", "`"$ConfigPath`"")
|
|
Start-Process -FilePath $powerShellPath -ArgumentList $arguments -WindowStyle Hidden
|
|
exit 0
|
|
}
|
|
} catch {
|
|
Write-AgentLog "Update check skipped: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Send-Heartbeat {
|
|
param([object]$Config, [string[]]$PlatformBins)
|
|
$networkRoots = Get-AgentNetworkRoots -Config $Config
|
|
$body = @{
|
|
agent_id = $Config.agent_id
|
|
host = $env:COMPUTERNAME
|
|
user = "$env:USERDOMAIN\$env:USERNAME"
|
|
version = $AgentVersion
|
|
started_at = $startedAt
|
|
network_roots = @($networkRoots)
|
|
platform_bins = @($PlatformBins)
|
|
}
|
|
Invoke-AgentRestMethod `
|
|
-Method Post `
|
|
-Uri (Agent-ApiUrl "/agent/heartbeat") `
|
|
-ContentType $JsonContentType `
|
|
-Body ($body | ConvertTo-Json -Depth 8) | Out-Null
|
|
}
|
|
|
|
function Send-JobLogs {
|
|
param([string]$JobId, [string[]]$Logs, [string]$Status = $null)
|
|
$body = @{ logs = $Logs }
|
|
if ($Status) { $body.status = $Status }
|
|
Invoke-AgentRestMethod -Method Post -Uri (Agent-ApiUrl "/agent/jobs/$JobId/logs") -ContentType $JsonContentType -Body ($body | ConvertTo-Json -Depth 6) | Out-Null
|
|
}
|
|
|
|
function Complete-Job {
|
|
param([string]$JobId, [string]$Status, [string[]]$Logs, [string]$ErrorMessage = $null)
|
|
$body = @{ status = $Status; logs = $Logs }
|
|
if ($ErrorMessage) { $body.error = $ErrorMessage }
|
|
Invoke-AgentRestMethod -Method Post -Uri (Agent-ApiUrl "/agent/jobs/$JobId/result") -ContentType $JsonContentType -Body ($body | ConvertTo-Json -Depth 8) | Out-Null
|
|
}
|
|
|
|
function Upload-Zip {
|
|
param([string]$JobId, [string]$ZipPath)
|
|
if (!(Test-Path -LiteralPath $ZipPath -PathType Leaf)) {
|
|
throw "Agent archive was not created: $ZipPath"
|
|
}
|
|
$zipSize = (Get-Item -LiteralPath $ZipPath).Length
|
|
$zipSizeMb = [Math]::Round($zipSize / 1MB, 1)
|
|
Send-JobLogs -JobId $JobId -Logs @("Uploading archive to SFERA server. Size: $zipSizeMb MB.")
|
|
$name = [System.Uri]::EscapeDataString([System.IO.Path]::GetFileName($ZipPath))
|
|
$uploadUri = Agent-ApiUrl "/agent/jobs/$JobId/upload?filename=$name"
|
|
$request = [System.Net.HttpWebRequest]::Create($uploadUri)
|
|
$request.Method = "POST"
|
|
$request.ContentType = "application/octet-stream"
|
|
$request.ContentLength = $zipSize
|
|
$request.Timeout = 7200000
|
|
$request.ReadWriteTimeout = 7200000
|
|
$request.AllowWriteStreamBuffering = $false
|
|
$buffer = New-Object byte[] (1024 * 1024)
|
|
$sent = 0L
|
|
$nextLogAt = 256MB
|
|
$fileStream = [System.IO.File]::Open($ZipPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
|
|
$requestStream = $request.GetRequestStream()
|
|
try {
|
|
while (($read = $fileStream.Read($buffer, 0, $buffer.Length)) -gt 0) {
|
|
$requestStream.Write($buffer, 0, $read)
|
|
$sent += $read
|
|
if ($sent -ge $nextLogAt -or $sent -eq $zipSize) {
|
|
$sentMb = [Math]::Round($sent / 1MB, 1)
|
|
$percent = if ($zipSize -gt 0) { [Math]::Round(($sent * 100.0) / $zipSize, 1) } else { 100 }
|
|
Send-JobLogs -JobId $JobId -Logs @("Uploaded $sentMb / $zipSizeMb MB ($percent%).")
|
|
$nextLogAt = $sent + 256MB
|
|
}
|
|
}
|
|
} finally {
|
|
$requestStream.Dispose()
|
|
$fileStream.Dispose()
|
|
}
|
|
$response = $request.GetResponse()
|
|
try {
|
|
if ([int]$response.StatusCode -lt 200 -or [int]$response.StatusCode -ge 300) {
|
|
throw "Archive upload failed with HTTP status $([int]$response.StatusCode) $($response.StatusDescription)."
|
|
}
|
|
} finally {
|
|
$response.Dispose()
|
|
}
|
|
Send-JobLogs -JobId $JobId -Logs @("Archive upload completed.")
|
|
}
|
|
|
|
function Get-AgentLongPath {
|
|
param([string]$Path)
|
|
if ($Path.StartsWith("\\?\")) { return $Path }
|
|
if ($Path.StartsWith("\\")) {
|
|
return "\\?\UNC\" + $Path.Substring(2)
|
|
}
|
|
return "\\?\" + $Path
|
|
}
|
|
|
|
function New-AgentZipArchive {
|
|
param([string]$SourceDir, [string]$ZipPath, [string]$JobId)
|
|
Add-Type -AssemblyName System.IO.Compression
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
$resolvedSource = (Resolve-Path -LiteralPath $SourceDir).Path.TrimEnd("\")
|
|
$longSource = Get-AgentLongPath -Path $resolvedSource
|
|
$longZip = Get-AgentLongPath -Path $ZipPath
|
|
$zipStream = [System.IO.File]::Open($longZip, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
|
$archive = [System.IO.Compression.ZipArchive]::new($zipStream, [System.IO.Compression.ZipArchiveMode]::Create, $false, [System.Text.Encoding]::UTF8)
|
|
$added = 0
|
|
$skipped = 0
|
|
try {
|
|
$files = [System.IO.Directory]::EnumerateFiles($longSource, "*", [System.IO.SearchOption]::AllDirectories)
|
|
foreach ($file in $files) {
|
|
try {
|
|
$normalFile = if ($file.StartsWith("\\?\UNC\")) { "\\" + $file.Substring(8) } elseif ($file.StartsWith("\\?\")) { $file.Substring(4) } else { $file }
|
|
$relative = $normalFile.Substring($resolvedSource.Length).TrimStart("\").Replace("\", "/")
|
|
if ([string]::IsNullOrWhiteSpace($relative)) {
|
|
continue
|
|
}
|
|
$entry = $archive.CreateEntry($relative, [System.IO.Compression.CompressionLevel]::Optimal)
|
|
$entryStream = $entry.Open()
|
|
$fileStream = [System.IO.File]::Open($file, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
|
try {
|
|
$fileStream.CopyTo($entryStream)
|
|
} finally {
|
|
$fileStream.Dispose()
|
|
$entryStream.Dispose()
|
|
}
|
|
$added += 1
|
|
if ($added % 5000 -eq 0) {
|
|
Send-JobLogs -JobId $JobId -Logs @("Packed $added files, skipped $skipped files.")
|
|
}
|
|
} catch {
|
|
$skipped += 1
|
|
if ($skipped -le 10 -or $skipped % 100 -eq 0) {
|
|
Send-JobLogs -JobId $JobId -Logs @("Skipped file while packing: $($_.Exception.Message)")
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
$archive.Dispose()
|
|
$zipStream.Dispose()
|
|
}
|
|
if ($added -eq 0) {
|
|
throw "Agent archive is empty. Source directory contains no readable files: $SourceDir"
|
|
}
|
|
return @{ added = $added; skipped = $skipped }
|
|
}
|
|
|
|
function Get-JobMetadataValue {
|
|
param([object]$Job, [string]$Key)
|
|
if ($null -eq $Job -or $null -eq $Job.metadata) { return "" }
|
|
if ($Job.metadata.PSObject.Properties.Name -contains $Key) {
|
|
return [string]$Job.metadata.$Key
|
|
}
|
|
return ""
|
|
}
|
|
|
|
function Get-JobMetadataBoolean {
|
|
param([object]$Job, [string]$Key, [bool]$DefaultValue = $false)
|
|
if ($null -eq $Job -or $null -eq $Job.metadata) { return $DefaultValue }
|
|
if (!($Job.metadata.PSObject.Properties.Name -contains $Key)) { return $DefaultValue }
|
|
$value = $Job.metadata.$Key
|
|
if ($null -eq $value) { return $DefaultValue }
|
|
if ($value -is [bool]) { return [bool]$value }
|
|
$text = ([string]$value).Trim().ToLowerInvariant()
|
|
if (@("true", "1", "yes") -contains $text) { return $true }
|
|
if (@("false", "0", "no") -contains $text) { return $false }
|
|
return $DefaultValue
|
|
}
|
|
|
|
function Get-JobMetadataList {
|
|
param([object]$Job, [string]$Key)
|
|
if ($null -eq $Job -or $null -eq $Job.metadata) { return @() }
|
|
if (!($Job.metadata.PSObject.Properties.Name -contains $Key)) { return @() }
|
|
$value = $Job.metadata.$Key
|
|
if ($null -eq $value) { return @() }
|
|
if ($value -is [System.Array]) {
|
|
return @($value | ForEach-Object { [string]$_ } | Where-Object { ![string]::IsNullOrWhiteSpace($_) })
|
|
}
|
|
return @(([string]$value).Split(",;`n`r", [System.StringSplitOptions]::RemoveEmptyEntries) | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
|
}
|
|
|
|
function Get-DesignerBinPath {
|
|
param([object]$Job, [string[]]$PlatformBins)
|
|
if (![string]::IsNullOrWhiteSpace($Job.bin_path)) {
|
|
return [string]$Job.bin_path
|
|
}
|
|
$configured = Get-JobMetadataValue -Job $Job -Key "one_c_bin"
|
|
if (![string]::IsNullOrWhiteSpace($configured)) {
|
|
return $configured
|
|
}
|
|
if ($PlatformBins.Count -gt 0) {
|
|
return [string]$PlatformBins[0]
|
|
}
|
|
throw "1C Designer executable was not found. Set path to 1cv8.exe in project agent settings."
|
|
}
|
|
|
|
function Get-AgentExportInfobaseArg {
|
|
param([object]$Job)
|
|
$connection = if (![string]::IsNullOrWhiteSpace($Job.infobase)) { [string]$Job.infobase } else { Get-JobMetadataValue -Job $Job -Key "one_c_connection" }
|
|
if (![string]::IsNullOrWhiteSpace($connection)) {
|
|
if ($connection -match '^[^;=]+\\[^;=]+$') {
|
|
return @("/S", $connection)
|
|
}
|
|
if ($connection -match '(?i)^\s*(Srvr|File|ws|Usr|Pwd)\s*=') {
|
|
return @("/IBConnectionString", $connection)
|
|
}
|
|
if ($connection -match '^\s*https?://') {
|
|
return @("/WS", $connection)
|
|
}
|
|
return @("/IBConnectionString", $connection)
|
|
}
|
|
$server = Get-JobMetadataValue -Job $Job -Key "one_c_server"
|
|
$infobase = Get-JobMetadataValue -Job $Job -Key "one_c_infobase"
|
|
if ([string]::IsNullOrWhiteSpace($server) -or [string]::IsNullOrWhiteSpace($infobase)) {
|
|
throw "1C server and infobase are required for CF/CFE export."
|
|
}
|
|
return @("/S", "$server/$infobase")
|
|
}
|
|
|
|
function Convert-AgentProcessArgument {
|
|
param([string]$Value)
|
|
if ($Value -match '[\s"]') {
|
|
return '"' + ($Value -replace '"', '\"') + '"'
|
|
}
|
|
return $Value
|
|
}
|
|
|
|
function Invoke-DesignerCommand {
|
|
param([string]$DesignerPath, [string[]]$Arguments, [string]$LogPath, [string]$JobId, [string]$ActionTitle, [int]$TimeoutSeconds = 900)
|
|
if (!(Test-Path -LiteralPath $DesignerPath -PathType Leaf)) {
|
|
throw "1C Designer executable not found: $DesignerPath"
|
|
}
|
|
$fullArguments = @("DESIGNER", "/DisableStartupDialogs", "/DisableStartupMessages") + $Arguments + @("/Out", $LogPath, "-NoTruncate")
|
|
Send-JobLogs -JobId $JobId -Logs @("$ActionTitle started.")
|
|
$argumentLine = ($fullArguments | ForEach-Object { Convert-AgentProcessArgument -Value ([string]$_) }) -join " "
|
|
$safeArguments = New-Object System.Collections.Generic.List[string]
|
|
$maskNext = $false
|
|
foreach ($argument in $fullArguments) {
|
|
$value = [string]$argument
|
|
if ($maskNext) {
|
|
$safeArguments.Add("***")
|
|
$maskNext = $false
|
|
continue
|
|
}
|
|
$safeArguments.Add((Convert-AgentProcessArgument -Value $value))
|
|
if ($value -eq "/P") {
|
|
$maskNext = $true
|
|
}
|
|
}
|
|
$safeArgumentLine = $safeArguments -join " "
|
|
Send-JobLogs -JobId $JobId -Logs @("$ActionTitle command: `"$DesignerPath`" $safeArgumentLine")
|
|
$process = Start-Process -FilePath $DesignerPath -ArgumentList $argumentLine -PassThru -WindowStyle Hidden
|
|
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
|
while (!$process.HasExited) {
|
|
if ($stopwatch.Elapsed.TotalSeconds -gt $TimeoutSeconds) {
|
|
try { $process.Kill() } catch {}
|
|
$tail = ""
|
|
if (Test-Path -LiteralPath $LogPath -PathType Leaf) {
|
|
$logTextOnTimeout = Get-Content -LiteralPath $LogPath -Raw -ErrorAction SilentlyContinue
|
|
if (![string]::IsNullOrWhiteSpace($logTextOnTimeout)) {
|
|
$tail = if ($logTextOnTimeout.Length -gt 1200) { $logTextOnTimeout.Substring($logTextOnTimeout.Length - 1200) } else { $logTextOnTimeout }
|
|
}
|
|
}
|
|
throw "$ActionTitle timed out after $TimeoutSeconds seconds. Designer likely waited for an interactive prompt. $tail"
|
|
}
|
|
Start-Sleep -Seconds 2
|
|
try { $process.Refresh() } catch {}
|
|
}
|
|
$logText = ""
|
|
if (Test-Path -LiteralPath $LogPath -PathType Leaf) {
|
|
$logText = Get-Content -LiteralPath $LogPath -Raw -ErrorAction SilentlyContinue
|
|
}
|
|
if ($process.ExitCode -ne 0) {
|
|
$tail = if ($logText.Length -gt 1000) { $logText.Substring($logText.Length - 1000) } else { $logText }
|
|
throw "$ActionTitle failed with exit code $($process.ExitCode). $tail"
|
|
}
|
|
if (![string]::IsNullOrWhiteSpace($logText)) {
|
|
$tail = if ($logText.Length -gt 700) { $logText.Substring($logText.Length - 700) } else { $logText }
|
|
Send-JobLogs -JobId $JobId -Logs @("$ActionTitle log: $tail")
|
|
}
|
|
Send-JobLogs -JobId $JobId -Logs @("$ActionTitle finished.")
|
|
}
|
|
|
|
function Invoke-1CCommand {
|
|
param([string]$PlatformPath, [string[]]$Arguments, [string]$LogPath, [string]$JobId, [string]$ActionTitle, [int]$TimeoutSeconds = 180)
|
|
if (!(Test-Path -LiteralPath $PlatformPath -PathType Leaf)) {
|
|
throw "1C executable not found: $PlatformPath"
|
|
}
|
|
$fullArguments = $Arguments + @("/Out", $LogPath, "-NoTruncate")
|
|
Send-JobLogs -JobId $JobId -Logs @("$ActionTitle started.")
|
|
$argumentLine = ($fullArguments | ForEach-Object { Convert-AgentProcessArgument -Value ([string]$_) }) -join " "
|
|
Send-JobLogs -JobId $JobId -Logs @("$ActionTitle command: `"$PlatformPath`" $argumentLine")
|
|
$process = Start-Process -FilePath $PlatformPath -ArgumentList $argumentLine -PassThru -WindowStyle Hidden
|
|
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
|
while (!$process.HasExited) {
|
|
if ($stopwatch.Elapsed.TotalSeconds -gt $TimeoutSeconds) {
|
|
try { $process.Kill() } catch {}
|
|
throw "$ActionTitle timed out after $TimeoutSeconds seconds."
|
|
}
|
|
Start-Sleep -Seconds 2
|
|
try { $process.Refresh() } catch {}
|
|
}
|
|
$logText = ""
|
|
if (Test-Path -LiteralPath $LogPath -PathType Leaf) {
|
|
$logText = Get-Content -LiteralPath $LogPath -Raw -ErrorAction SilentlyContinue
|
|
}
|
|
if ($process.ExitCode -ne 0) {
|
|
$tail = if ($logText.Length -gt 1000) { $logText.Substring($logText.Length - 1000) } else { $logText }
|
|
throw "$ActionTitle failed with exit code $($process.ExitCode). $tail"
|
|
}
|
|
if (![string]::IsNullOrWhiteSpace($logText)) {
|
|
$tail = if ($logText.Length -gt 700) { $logText.Substring($logText.Length - 700) } else { $logText }
|
|
Send-JobLogs -JobId $JobId -Logs @("$ActionTitle log: $tail")
|
|
}
|
|
Send-JobLogs -JobId $JobId -Logs @("$ActionTitle finished.")
|
|
}
|
|
|
|
function Get-InfobaseExtensionNames {
|
|
param([string]$DesignerPath, [string[]]$BaseArgs, [string]$ExportRoot, [string]$JobId)
|
|
$logPath = Join-Path $ExportRoot "designer-dumpdbcfglist.log"
|
|
Invoke-DesignerCommand -DesignerPath $DesignerPath -Arguments (@($BaseArgs) + @("/DumpDBCfgList", "-AllExtensions")) -LogPath $logPath -JobId $JobId -ActionTitle "1C DumpDBCfgList"
|
|
if (!(Test-Path -LiteralPath $logPath -PathType Leaf)) {
|
|
return @()
|
|
}
|
|
$lines = Get-Content -LiteralPath $logPath -ErrorAction SilentlyContinue
|
|
$names = foreach ($line in $lines) {
|
|
$value = ([string]$line).Trim()
|
|
if ([string]::IsNullOrWhiteSpace($value)) { continue }
|
|
if ($value -match '^(INFO|ERROR|WARNING)[:\s]') { continue }
|
|
if ($value -match '^[0-9a-fA-F]{32,}$') { continue }
|
|
$first = ($value -split '\s+')[0].Trim()
|
|
if (![string]::IsNullOrWhiteSpace($first)) { $first }
|
|
}
|
|
return @($names | Sort-Object -Unique)
|
|
}
|
|
|
|
function Export-CfOrCfeFromInfobase {
|
|
param([object]$Job, [string[]]$PlatformBins)
|
|
$workRoot = Join-Path $env:TEMP "sfera-agent"
|
|
$exportRoot = Join-Path $workRoot "$($Job.job_id)-export"
|
|
if (Test-Path -LiteralPath $exportRoot) { Remove-Item -LiteralPath $exportRoot -Recurse -Force }
|
|
New-Item -ItemType Directory -Force -Path $exportRoot | Out-Null
|
|
|
|
$designerPath = Get-DesignerBinPath -Job $Job -PlatformBins $PlatformBins
|
|
$baseArgs = Get-AgentExportInfobaseArg -Job $Job
|
|
$user = Get-JobMetadataValue -Job $Job -Key "one_c_user"
|
|
$password = Get-JobMetadataValue -Job $Job -Key "one_c_password"
|
|
if (![string]::IsNullOrWhiteSpace($user)) {
|
|
$baseArgs += @("/N", $user)
|
|
}
|
|
if (![string]::IsNullOrWhiteSpace($password)) {
|
|
$baseArgs += @("/P", $password)
|
|
}
|
|
|
|
$isSingleExtensionExport = [string]$Job.source -eq "CFE_FILE"
|
|
$extensionName = Get-JobMetadataValue -Job $Job -Key "one_c_extension"
|
|
if ($isSingleExtensionExport -and [string]::IsNullOrWhiteSpace($extensionName)) {
|
|
throw "Extension name is required for CFE export."
|
|
}
|
|
|
|
$fileName = if ($isSingleExtensionExport) { "$extensionName.cfe" } else { "$($Job.project_id).cf" }
|
|
$artifactsRoot = Join-Path $exportRoot "artifacts"
|
|
New-Item -ItemType Directory -Force -Path $artifactsRoot | Out-Null
|
|
$dumpFile = Join-Path $exportRoot $fileName
|
|
$logPath = Join-Path $exportRoot "designer-dumpcfg.log"
|
|
$dumpArgs = @($baseArgs) + @("/DumpCfg", $dumpFile)
|
|
if ($isSingleExtensionExport) {
|
|
$dumpArgs += @("-Extension", $extensionName)
|
|
}
|
|
Invoke-DesignerCommand -DesignerPath $designerPath -Arguments $dumpArgs -LogPath $logPath -JobId $Job.job_id -ActionTitle "1C DumpCfg"
|
|
if (!(Test-Path -LiteralPath $dumpFile -PathType Leaf)) {
|
|
throw "1C DumpCfg completed but output file was not created: $dumpFile"
|
|
}
|
|
$dumpSizeMb = [Math]::Round((Get-Item -LiteralPath $dumpFile).Length / 1MB, 1)
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Exported $fileName from infobase. Size: $dumpSizeMb MB.")
|
|
Copy-Item -LiteralPath $dumpFile -Destination (Join-Path $artifactsRoot $fileName) -Force
|
|
|
|
$metadataRoot = if ($isSingleExtensionExport) { Join-Path $exportRoot "extension" } else { Join-Path $exportRoot "configuration" }
|
|
New-Item -ItemType Directory -Force -Path $metadataRoot | Out-Null
|
|
$metadataLogPath = Join-Path $exportRoot "designer-dumpconfigtofiles.log"
|
|
$metadataArgs = @($baseArgs) + @("/DumpConfigToFiles", $metadataRoot, "-Format", "Hierarchical")
|
|
if ($isSingleExtensionExport) {
|
|
$metadataArgs += @("-Extension", $extensionName)
|
|
}
|
|
try {
|
|
Invoke-DesignerCommand -DesignerPath $designerPath -Arguments $metadataArgs -LogPath $metadataLogPath -JobId $Job.job_id -ActionTitle "1C DumpConfigToFiles"
|
|
Copy-Item -LiteralPath $dumpFile -Destination (Join-Path $metadataRoot $fileName) -Force
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Metadata files exported for server-side parsing.")
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Metadata files export failed for main configuration, binary file is still included. $($_.Exception.Message)")
|
|
}
|
|
|
|
if (!$isSingleExtensionExport -and (Get-JobMetadataBoolean -Job $Job -Key "include_extensions" -DefaultValue $true)) {
|
|
$extensionNames = Get-JobMetadataList -Job $Job -Key "one_c_extensions"
|
|
if ($extensionNames.Count -eq 0) {
|
|
$extensionNames = Get-InfobaseExtensionNames -DesignerPath $designerPath -BaseArgs $baseArgs -ExportRoot $exportRoot -JobId $Job.job_id
|
|
}
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Extensions selected for export: $($extensionNames.Count).")
|
|
foreach ($name in $extensionNames) {
|
|
$safeName = ($name -replace '[\\/:*?"<>|]', "_")
|
|
$extensionFile = Join-Path $exportRoot "$safeName.cfe"
|
|
$extensionLog = Join-Path $exportRoot "designer-dumpcfg-$safeName.log"
|
|
Invoke-DesignerCommand -DesignerPath $designerPath -Arguments (@($baseArgs) + @("/DumpCfg", $extensionFile, "-Extension", $name)) -LogPath $extensionLog -JobId $Job.job_id -ActionTitle "1C DumpCfg extension $name"
|
|
Copy-Item -LiteralPath $extensionFile -Destination (Join-Path $artifactsRoot "$safeName.cfe") -Force
|
|
$extensionMetadataRoot = Join-Path (Join-Path $exportRoot "extensions") $safeName
|
|
New-Item -ItemType Directory -Force -Path $extensionMetadataRoot | Out-Null
|
|
try {
|
|
Invoke-DesignerCommand -DesignerPath $designerPath -Arguments (@($baseArgs) + @("/DumpConfigToFiles", $extensionMetadataRoot, "-Format", "Hierarchical", "-Extension", $name)) -LogPath (Join-Path $exportRoot "designer-dumpconfigtofiles-$safeName.log") -JobId $Job.job_id -ActionTitle "1C DumpConfigToFiles extension $name"
|
|
Copy-Item -LiteralPath $extensionFile -Destination (Join-Path $extensionMetadataRoot "$safeName.cfe") -Force
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Metadata files export failed for extension $name, CFE is still included. $($_.Exception.Message)")
|
|
}
|
|
}
|
|
}
|
|
|
|
return $exportRoot
|
|
}
|
|
|
|
function Convert-LocalCfOrCfeToMetadataExport {
|
|
param([object]$Job, [string[]]$PlatformBins)
|
|
$payloadPath = [string]$Job.local_path
|
|
if ([string]::IsNullOrWhiteSpace($payloadPath)) {
|
|
throw "local_path is required for direct CF/CFE conversion."
|
|
}
|
|
if (!(Test-Path -LiteralPath $payloadPath)) {
|
|
throw "Local CF/CFE path not found on agent machine: $payloadPath"
|
|
}
|
|
|
|
$designerPath = Get-DesignerBinPath -Job $Job -PlatformBins $PlatformBins
|
|
$workRoot = Join-Path $env:TEMP "sfera-agent"
|
|
$exportRoot = Join-Path $workRoot "$($Job.job_id)-local-binary"
|
|
if (Test-Path -LiteralPath $exportRoot) { Remove-Item -LiteralPath $exportRoot -Recurse -Force }
|
|
New-Item -ItemType Directory -Force -Path $exportRoot | Out-Null
|
|
|
|
$builderInfobase = Join-Path $exportRoot "builder-infobase"
|
|
$createLog = Join-Path $exportRoot "create-builder-infobase.log"
|
|
Invoke-1CCommand `
|
|
-PlatformPath $designerPath `
|
|
-Arguments @("CREATEINFOBASE", "File=$builderInfobase;") `
|
|
-LogPath $createLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C CREATEINFOBASE for local CF/CFE conversion" `
|
|
-TimeoutSeconds 180
|
|
|
|
$builderArgs = @("/F", $builderInfobase)
|
|
$sourceKind = [string]$Job.source
|
|
$fileName = [System.IO.Path]::GetFileName($payloadPath)
|
|
$artifactsRoot = Join-Path $exportRoot "artifacts"
|
|
New-Item -ItemType Directory -Force -Path $artifactsRoot | Out-Null
|
|
Copy-Item -LiteralPath $payloadPath -Destination (Join-Path $artifactsRoot $fileName) -Force
|
|
|
|
if ($sourceKind -eq "CF_FILE") {
|
|
$loadLog = Join-Path $exportRoot "designer-loadcfg-local-cf.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($builderArgs) + @("/LoadCfg", $payloadPath)) `
|
|
-LogPath $loadLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C LoadCfg local CF" `
|
|
-TimeoutSeconds 180
|
|
|
|
$metadataRoot = Join-Path $exportRoot "configuration"
|
|
New-Item -ItemType Directory -Force -Path $metadataRoot | Out-Null
|
|
$metadataLog = Join-Path $exportRoot "designer-dumpconfigtofiles-local-cf.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($builderArgs) + @("/DumpConfigToFiles", $metadataRoot, "-Format", "Hierarchical")) `
|
|
-LogPath $metadataLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C DumpConfigToFiles from local CF" `
|
|
-TimeoutSeconds 180
|
|
Copy-Item -LiteralPath $payloadPath -Destination (Join-Path $metadataRoot $fileName) -Force
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Local .cf converted to metadata export for server-side parsing.")
|
|
return $exportRoot
|
|
}
|
|
|
|
if ($sourceKind -eq "CFE_FILE") {
|
|
$extensionName = Get-JobMetadataValue -Job $Job -Key "one_c_extension"
|
|
if ([string]::IsNullOrWhiteSpace($extensionName)) {
|
|
$extensionName = [System.IO.Path]::GetFileNameWithoutExtension($payloadPath)
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($extensionName)) {
|
|
throw "Extension name is required for local CFE conversion."
|
|
}
|
|
|
|
$loadLog = Join-Path $exportRoot "designer-loadcfg-local-cfe.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($builderArgs) + @("/LoadCfg", $payloadPath, "-Extension", $extensionName, "/UpdateDBCfg")) `
|
|
-LogPath $loadLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C LoadCfg local CFE" `
|
|
-TimeoutSeconds 180
|
|
|
|
$metadataRoot = Join-Path $exportRoot "extension"
|
|
New-Item -ItemType Directory -Force -Path $metadataRoot | Out-Null
|
|
$metadataLog = Join-Path $exportRoot "designer-dumpconfigtofiles-local-cfe.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($builderArgs) + @("/DumpConfigToFiles", $metadataRoot, "-Format", "Hierarchical", "-Extension", $extensionName)) `
|
|
-LogPath $metadataLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C DumpConfigToFiles from local CFE" `
|
|
-TimeoutSeconds 180
|
|
Copy-Item -LiteralPath $payloadPath -Destination (Join-Path $metadataRoot $fileName) -Force
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Local .cfe converted to metadata export for server-side parsing.")
|
|
return $exportRoot
|
|
}
|
|
|
|
throw "Unsupported source for local CF/CFE conversion: $sourceKind"
|
|
}
|
|
|
|
function Install-SferaExtensionJob {
|
|
param([object]$Job, [string[]]$PlatformBins)
|
|
$workRoot = Join-Path $env:TEMP "sfera-agent"
|
|
$installRoot = Join-Path $workRoot "$($Job.job_id)-sfera-extension"
|
|
if (Test-Path -LiteralPath $installRoot) { Remove-Item -LiteralPath $installRoot -Recurse -Force }
|
|
New-Item -ItemType Directory -Force -Path $installRoot | Out-Null
|
|
|
|
$designerPath = Get-DesignerBinPath -Job $Job -PlatformBins $PlatformBins
|
|
$baseArgs = Get-AgentExportInfobaseArg -Job $Job
|
|
$user = Get-JobMetadataValue -Job $Job -Key "one_c_user"
|
|
$password = Get-JobMetadataValue -Job $Job -Key "one_c_password"
|
|
if (![string]::IsNullOrWhiteSpace($user)) {
|
|
$baseArgs += @("/N", $user)
|
|
}
|
|
if (![string]::IsNullOrWhiteSpace($password)) {
|
|
$baseArgs += @("/P", $password)
|
|
}
|
|
|
|
$extensionName = Get-JobMetadataValue -Job $Job -Key "extension_name"
|
|
if ([string]::IsNullOrWhiteSpace($extensionName)) {
|
|
$extensionName = "SFERA"
|
|
}
|
|
$forceReinstall = Get-JobMetadataBoolean -Job $Job -Key "force_reinstall" -DefaultValue $true
|
|
if ($forceReinstall) {
|
|
$deleteLog = Join-Path $installRoot "designer-delete-sfera-extension.log"
|
|
$deletedExistingExtension = $false
|
|
try {
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($baseArgs) + @("/DeleteCfg", "-Extension", $extensionName)) `
|
|
-LogPath $deleteLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C DeleteCfg stale SFERA extension" `
|
|
-TimeoutSeconds 180
|
|
$deletedExistingExtension = $true
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("DeleteCfg skipped or extension was absent. $($_.Exception.Message)")
|
|
}
|
|
if ($deletedExistingExtension) {
|
|
$deleteUpdateLog = Join-Path $installRoot "designer-updatedbcfg-delete-sfera-extension.log"
|
|
try {
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($baseArgs) + @("/UpdateDBCfg", "-Extension", $extensionName)) `
|
|
-LogPath $deleteUpdateLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C UpdateDBCfg after stale SFERA extension delete" `
|
|
-TimeoutSeconds 180
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("UpdateDBCfg after DeleteCfg skipped. $($_.Exception.Message)")
|
|
}
|
|
}
|
|
$rollbackMainLog = Join-Path $installRoot "designer-rollback-main-config.log"
|
|
try {
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($baseArgs) + @("/RollbackCfg")) `
|
|
-LogPath $rollbackMainLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C RollbackCfg main configuration changes" `
|
|
-TimeoutSeconds 180
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("RollbackCfg main configuration skipped. $($_.Exception.Message)")
|
|
}
|
|
$rollbackLog = Join-Path $installRoot "designer-rollback-stale-main-config.log"
|
|
try {
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($baseArgs) + @("/RollbackCfg", "-Extension", $extensionName)) `
|
|
-LogPath $rollbackLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C RollbackCfg stale main configuration changes" `
|
|
-TimeoutSeconds 180
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("RollbackCfg skipped. $($_.Exception.Message)")
|
|
}
|
|
}
|
|
$cfePath = Get-JobMetadataValue -Job $Job -Key "extension_cfe_path"
|
|
$builtCfeFromXml = $false
|
|
if ([string]::IsNullOrWhiteSpace($cfePath)) {
|
|
$packageUrl = Get-JobMetadataValue -Job $Job -Key "extension_package_url"
|
|
$sourceRoot = ""
|
|
if (![string]::IsNullOrWhiteSpace($packageUrl)) {
|
|
$packagePath = Join-Path $installRoot "sfera-extension-source.zip"
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Downloading SFERA extension source package from server.")
|
|
Invoke-AgentWebRequest -Method Get -Uri $packageUrl -OutFile $packagePath -TimeoutSec 120 | Out-Null
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Downloaded SFERA extension source package to $packagePath.")
|
|
$sourceRoot = Join-Path $installRoot "source"
|
|
New-Item -ItemType Directory -Force -Path $sourceRoot | Out-Null
|
|
Expand-Archive -LiteralPath $packagePath -DestinationPath $sourceRoot -Force
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($sourceRoot)) {
|
|
throw "SFERA extension package URL is empty and compiled .cfe path is not configured."
|
|
}
|
|
|
|
$xmlRootCandidates = @(
|
|
(Join-Path $sourceRoot "xml"),
|
|
(Join-Path $sourceRoot "configuration"),
|
|
(Join-Path $sourceRoot "src"),
|
|
$sourceRoot
|
|
)
|
|
$xmlRoot = $xmlRootCandidates | Where-Object { Test-Path -LiteralPath $_ -PathType Container } | Select-Object -First 1
|
|
if ([string]::IsNullOrWhiteSpace($xmlRoot)) {
|
|
throw "SFERA extension source package does not contain a loadable folder."
|
|
}
|
|
|
|
$builtRoot = Join-Path $env:ProgramData "SFERA\WindowsAgent\extensions"
|
|
New-Item -ItemType Directory -Force -Path $builtRoot | Out-Null
|
|
$cfePath = Join-Path $builtRoot "$extensionName.cfe"
|
|
|
|
$builderInfobase = Join-Path $installRoot "builder-infobase"
|
|
if (Test-Path -LiteralPath $builderInfobase) { Remove-Item -LiteralPath $builderInfobase -Recurse -Force }
|
|
New-Item -ItemType Directory -Force -Path (Split-Path -Path $builderInfobase -Parent) | Out-Null
|
|
$builderArgs = @("/F", $builderInfobase)
|
|
$builderReady = $false
|
|
try {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Building SFERA extension .cfe in isolated temporary file infobase: $builderInfobase")
|
|
$createBuilderLog = Join-Path $installRoot "create-builder-infobase.log"
|
|
Invoke-1CCommand `
|
|
-PlatformPath $designerPath `
|
|
-Arguments @("CREATEINFOBASE", "File=$builderInfobase;") `
|
|
-LogPath $createBuilderLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C CREATEINFOBASE for SFERA extension builder" `
|
|
-TimeoutSeconds 180
|
|
|
|
$loadXmlLog = Join-Path $installRoot "designer-load-sfera-extension-from-files.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($builderArgs) + @("/LoadConfigFromFiles", $xmlRoot, "-Extension", $extensionName, "-Format", "Hierarchical", "-updateConfigDumpInfo")) `
|
|
-LogPath $loadXmlLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C LoadConfigFromFiles SFERA extension" `
|
|
-TimeoutSeconds 180
|
|
$builderReady = $true
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Isolated file infobase build is unavailable. Falling back to selected infobase builder. $($_.Exception.Message)")
|
|
$builderArgs = @($baseArgs)
|
|
$loadXmlLog = Join-Path $installRoot "designer-load-sfera-extension-from-files-target-builder.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($builderArgs) + @("/LoadConfigFromFiles", $xmlRoot, "-Extension", $extensionName, "-Format", "Hierarchical", "-updateConfigDumpInfo")) `
|
|
-LogPath $loadXmlLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C LoadConfigFromFiles SFERA extension in selected infobase builder" `
|
|
-TimeoutSeconds 180
|
|
$builtCfeFromXml = $true
|
|
$builderReady = $true
|
|
}
|
|
|
|
if (!$builderReady) {
|
|
throw "SFERA extension builder was not prepared."
|
|
}
|
|
$dumpLog = Join-Path $installRoot "designer-dump-sfera-extension-cfe.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($builderArgs) + @("/DumpCfg", $cfePath, "-Extension", $extensionName)) `
|
|
-LogPath $dumpLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C DumpCfg SFERA extension to .cfe" `
|
|
-TimeoutSeconds 180
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Built SFERA extension .cfe without EDT: $cfePath")
|
|
if (($builtCfeFromXml) -or (($builderArgs -join "|") -eq (@($baseArgs) -join "|"))) {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Selected infobase was used only as a temporary .cfe builder. The staged XML extension will be removed before installing the built .cfe.")
|
|
}
|
|
} else {
|
|
if (!(Test-Path -LiteralPath $cfePath -PathType Leaf)) {
|
|
throw "SFERA extension .cfe not found on agent machine: $cfePath"
|
|
}
|
|
}
|
|
|
|
if ($builtCfeFromXml) {
|
|
$deleteStagedLog = Join-Path $installRoot "designer-delete-staged-sfera-extension.log"
|
|
try {
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($baseArgs) + @("/DeleteCfg", "-Extension", $extensionName)) `
|
|
-LogPath $deleteStagedLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C DeleteCfg staged SFERA extension before .cfe install" `
|
|
-TimeoutSeconds 180
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("DeleteCfg staged extension skipped. $($_.Exception.Message)")
|
|
}
|
|
$deleteStagedUpdateLog = Join-Path $installRoot "designer-updatedbcfg-delete-staged-sfera-extension.log"
|
|
try {
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($baseArgs) + @("/UpdateDBCfg", "-Extension", $extensionName)) `
|
|
-LogPath $deleteStagedUpdateLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C UpdateDBCfg after staged SFERA extension delete" `
|
|
-TimeoutSeconds 180
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("UpdateDBCfg after staged DeleteCfg skipped. $($_.Exception.Message)")
|
|
}
|
|
$rollbackStagedLog = Join-Path $installRoot "designer-rollback-staged-main-config.log"
|
|
try {
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($baseArgs) + @("/RollbackCfg", "-Extension", $extensionName)) `
|
|
-LogPath $rollbackStagedLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C RollbackCfg after staged extension build" `
|
|
-TimeoutSeconds 180
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("RollbackCfg after staged build skipped. $($_.Exception.Message)")
|
|
}
|
|
}
|
|
|
|
$loadLog = Join-Path $installRoot "designer-load-sfera-extension.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $designerPath `
|
|
-Arguments (@($baseArgs) + @("/LoadCfg", $cfePath, "-Extension", $extensionName, "/UpdateDBCfg")) `
|
|
-LogPath $loadLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C LoadCfg and UpdateDBCfg SFERA extension" `
|
|
-TimeoutSeconds 180
|
|
Test-SferaExtensionInstall -Job $Job -DesignerPath $designerPath -BaseArgs $baseArgs -ExtensionName $extensionName -InstallRoot $installRoot
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("SFERA extension install/update finished for extension '$extensionName'.")
|
|
}
|
|
|
|
function Test-SferaExtensionInstall {
|
|
param([object]$Job, [string]$DesignerPath, [object[]]$BaseArgs, [string]$ExtensionName, [string]$InstallRoot)
|
|
$checkModulesLog = Join-Path $InstallRoot "designer-checkmodules-sfera-extension.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $DesignerPath `
|
|
-Arguments (@($BaseArgs) + @("/CheckModules", "-Server", "-ExternalConnection", "-Extension", $ExtensionName)) `
|
|
-LogPath $checkModulesLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C CheckModules SFERA extension" `
|
|
-TimeoutSeconds 300
|
|
|
|
$checkApplyLog = Join-Path $InstallRoot "designer-checkapply-sfera-extension.log"
|
|
Invoke-DesignerCommand `
|
|
-DesignerPath $DesignerPath `
|
|
-Arguments (@($BaseArgs) + @("/CheckCanApplyConfigurationExtensions", "-Extension", $ExtensionName)) `
|
|
-LogPath $checkApplyLog `
|
|
-JobId $Job.job_id `
|
|
-ActionTitle "1C CheckCanApplyConfigurationExtensions SFERA extension" `
|
|
-TimeoutSeconds 300
|
|
}
|
|
|
|
function Ensure-SferaHttpServicesPublished {
|
|
param([object]$Job, [string]$ExtensionName)
|
|
$vrdPath = Get-JobMetadataValue -Job $Job -Key "published_vrd_path"
|
|
if ([string]::IsNullOrWhiteSpace($vrdPath)) {
|
|
$infobase = Get-JobMetadataValue -Job $Job -Key "one_c_infobase"
|
|
if (![string]::IsNullOrWhiteSpace($infobase)) {
|
|
$candidate = Join-Path (Join-Path $env:SystemDrive "inetpub\wwwroot") (Join-Path $infobase "default.vrd")
|
|
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
|
|
$vrdPath = $candidate
|
|
}
|
|
}
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($vrdPath)) {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("default.vrd path is not configured. If /hs/sfera/health returns 404, set published_vrd_path or run the agent on the web server and republish extension HTTP services.")
|
|
return
|
|
}
|
|
if (!(Test-Path -LiteralPath $vrdPath -PathType Leaf)) {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("default.vrd not found: $vrdPath. Extension was installed, but HTTP services from extensions may remain unpublished.")
|
|
return
|
|
}
|
|
|
|
[xml]$xml = Get-Content -LiteralPath $vrdPath -Raw
|
|
$root = $xml.DocumentElement
|
|
if ($null -eq $root) {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("default.vrd is empty or invalid XML: $vrdPath")
|
|
return
|
|
}
|
|
$namespace = $root.NamespaceURI
|
|
$httpServices = $null
|
|
foreach ($child in $root.ChildNodes) {
|
|
if ($child.LocalName -eq "httpServices") {
|
|
$httpServices = $child
|
|
break
|
|
}
|
|
}
|
|
if ($null -eq $httpServices) {
|
|
$httpServices = if ([string]::IsNullOrWhiteSpace($namespace)) {
|
|
$xml.CreateElement("httpServices")
|
|
} else {
|
|
$xml.CreateElement("httpServices", $namespace)
|
|
}
|
|
[void]$root.AppendChild($httpServices)
|
|
}
|
|
$httpServices.SetAttribute("publishByDefault", "true")
|
|
$httpServices.SetAttribute("publishExtensionsByDefault", "true")
|
|
$serviceRoot = Get-JobMetadataValue -Job $Job -Key "published_extension_service_root"
|
|
if ([string]::IsNullOrWhiteSpace($serviceRoot)) {
|
|
$serviceRoot = "sfera"
|
|
}
|
|
$serviceName = Get-JobMetadataValue -Job $Job -Key "published_extension_service_name"
|
|
if ([string]::IsNullOrWhiteSpace($serviceName)) {
|
|
$serviceName = "BridgeHTTP"
|
|
}
|
|
$existingServices = @()
|
|
foreach ($child in $httpServices.ChildNodes) {
|
|
if ($child.LocalName -eq "service") {
|
|
$nameMatches = $child.GetAttribute("name") -eq $serviceName
|
|
$rootMatches = $child.GetAttribute("rootUrl") -eq $serviceRoot
|
|
if ($nameMatches -or $rootMatches) {
|
|
$existingServices += $child
|
|
}
|
|
}
|
|
}
|
|
foreach ($service in $existingServices) {
|
|
[void]$httpServices.RemoveChild($service)
|
|
}
|
|
$serviceNode = if ([string]::IsNullOrWhiteSpace($namespace)) {
|
|
$xml.CreateElement("service")
|
|
} else {
|
|
$xml.CreateElement("service", $namespace)
|
|
}
|
|
$serviceNode.SetAttribute("name", $serviceName)
|
|
$serviceNode.SetAttribute("rootUrl", $serviceRoot)
|
|
$serviceNode.SetAttribute("enable", "true")
|
|
$serviceNode.SetAttribute("reuseSessions", "autouse")
|
|
$serviceNode.SetAttribute("sessionMaxAge", "20")
|
|
[void]$httpServices.AppendChild($serviceNode)
|
|
$backupPath = "$vrdPath.sfera-backup-$(Get-Date -Format 'yyyyMMddHHmmss')"
|
|
Copy-Item -LiteralPath $vrdPath -Destination $backupPath -Force
|
|
$xml.Save($vrdPath)
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Updated default.vrd for extension HTTP services: $vrdPath. Service: $serviceName/$serviceRoot. Backup: $backupPath")
|
|
try {
|
|
$iisreset = Join-Path $env:SystemRoot "System32\iisreset.exe"
|
|
if (Test-Path -LiteralPath $iisreset -PathType Leaf) {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("Restarting IIS to reload default.vrd publication settings.")
|
|
$process = Start-Process -FilePath $iisreset -ArgumentList @("/restart") -NoNewWindow -PassThru -Wait
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("IIS restart finished with exit code $($process.ExitCode).")
|
|
} else {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("iisreset.exe not found. Restart IIS or recycle the publication manually if /hs/sfera/health is still unavailable.")
|
|
}
|
|
} catch {
|
|
Send-JobLogs -JobId $Job.job_id -Logs @("IIS restart skipped or failed: $($_.Exception.Message)")
|
|
}
|
|
}
|
|
|
|
function Complete-Browse {
|
|
param([string]$RequestId, [string]$Status, [object[]]$Entries = @(), [string]$ParentPath = $null, [string]$ErrorMessage = $null)
|
|
$body = @{ status = $Status; entries = $Entries }
|
|
if ($ParentPath) { $body.parent_path = $ParentPath }
|
|
if ($ErrorMessage) { $body.error = $ErrorMessage }
|
|
Invoke-AgentRestMethod -Method Post -Uri (Agent-ApiUrl "/agent/browse/$RequestId/result") -ContentType $JsonContentType -Body ($body | ConvertTo-Json -Depth 8) | Out-Null
|
|
}
|
|
|
|
function Handle-BrowseRequest {
|
|
param([string]$Server, [object]$Browse, [object]$Config)
|
|
$path = $Browse.path
|
|
if ([string]::IsNullOrWhiteSpace($path)) {
|
|
$driveEntries = Get-PSDrive -PSProvider FileSystem | ForEach-Object {
|
|
$name = "$($_.Name):\"
|
|
if (![string]::IsNullOrWhiteSpace($_.DisplayRoot)) {
|
|
$name = "$name ($($_.DisplayRoot))"
|
|
}
|
|
@{ name = $name; path = "$($_.Name):\"; is_directory = $true }
|
|
}
|
|
$networkEntries = Get-AgentNetworkRoots -Config $Config | ForEach-Object {
|
|
@{ name = $_; path = $_; is_directory = $true }
|
|
}
|
|
Complete-Browse -RequestId $Browse.request_id -Status "SUCCEEDED" -Entries @($driveEntries + $networkEntries)
|
|
return
|
|
}
|
|
if (!(Test-Path -LiteralPath $path -PathType Container)) {
|
|
throw "Folder not found on agent machine: $path"
|
|
}
|
|
$resolved = (Resolve-Path -LiteralPath $path).Path
|
|
$parent = Split-Path -Path $resolved -Parent
|
|
$entries = Get-ChildItem -LiteralPath $resolved -Directory -Force |
|
|
Sort-Object Name |
|
|
Select-Object -First 200 |
|
|
ForEach-Object { @{ name = $_.Name; path = $_.FullName; is_directory = $true } }
|
|
Complete-Browse -RequestId $Browse.request_id -Status "SUCCEEDED" -Entries @($entries) -ParentPath $parent
|
|
}
|
|
|
|
$config = Initialize-AgentConfig
|
|
$server = ([string]$config.server_url).TrimEnd("/")
|
|
$script:AgentApiBase = Get-AgentApiBase -Config $config
|
|
$platformBins = Find-1CPlatformBins
|
|
Write-AgentLog "SFERA Windows Agent started. Server=$server AgentId=$($config.agent_id)"
|
|
Write-AgentLog "API=$script:AgentApiBase"
|
|
Write-AgentLog "Config=$ConfigPath"
|
|
Initialize-TrayIcon -Server $server -AgentId $config.agent_id
|
|
Update-TrayIcon -Online $false -Message "connecting"
|
|
|
|
$lastUpdateCheck = [DateTime]::MinValue
|
|
$lastConfigSync = [DateTime]::MinValue
|
|
$lastHeartbeatLog = [DateTime]::MinValue
|
|
|
|
while ($true) {
|
|
$job = $null
|
|
try {
|
|
$config = Read-AgentConfig
|
|
$server = ([string]$config.server_url).TrimEnd("/")
|
|
$script:AgentApiBase = Get-AgentApiBase -Config $config
|
|
if (((Get-Date) - $lastUpdateCheck).TotalMinutes -ge 15) {
|
|
Test-AgentUpdate
|
|
$lastUpdateCheck = Get-Date
|
|
}
|
|
if (((Get-Date) - $lastConfigSync).TotalSeconds -ge 60) {
|
|
Sync-AgentConfigFromServer -Config $config
|
|
$platformBins = Find-1CPlatformBins
|
|
$lastConfigSync = Get-Date
|
|
}
|
|
Send-Heartbeat -Config $config -PlatformBins $platformBins
|
|
Update-TrayIcon -Online $true -Message $config.agent_id
|
|
if (((Get-Date) - $lastHeartbeatLog).TotalSeconds -ge 60) {
|
|
Write-AgentLog "Heartbeat sent. AgentId=$($config.agent_id)"
|
|
$lastHeartbeatLog = Get-Date
|
|
}
|
|
|
|
$agentQuery = [System.Uri]::EscapeDataString($config.agent_id)
|
|
$browse = Invoke-AgentRestMethod -Method Get -Uri (Agent-ApiUrl "/agent/browse/next?agent_id=$agentQuery")
|
|
if ($null -ne $browse -and ![string]::IsNullOrWhiteSpace($browse.request_id)) {
|
|
try {
|
|
Write-AgentLog "Browse request $($browse.request_id): $($browse.path)"
|
|
Handle-BrowseRequest -Server $server -Browse $browse -Config $config
|
|
} catch {
|
|
Complete-Browse -RequestId $browse.request_id -Status "FAILED" -ErrorMessage $_.Exception.Message
|
|
}
|
|
continue
|
|
}
|
|
|
|
$versionQuery = [System.Uri]::EscapeDataString($AgentVersion)
|
|
$job = Invoke-AgentRestMethod -Method Get -Uri (Agent-ApiUrl "/agent/jobs/next?agent_id=$agentQuery&version=$versionQuery")
|
|
if ($null -eq $job -or [string]::IsNullOrWhiteSpace($job.job_id)) {
|
|
Pump-TrayIcon
|
|
Start-Sleep -Seconds ([int]$config.poll_seconds)
|
|
continue
|
|
}
|
|
|
|
Write-AgentLog "Claimed job $($job.job_id): $($job.source) $($job.mode)"
|
|
Send-JobLogs -JobId $job.job_id -Logs @("Windows agent claimed job on $env:COMPUTERNAME.")
|
|
|
|
$workRoot = Join-Path $env:TEMP "sfera-agent"
|
|
New-Item -ItemType Directory -Force -Path $workRoot | Out-Null
|
|
$agentAction = Get-JobMetadataValue -Job $job -Key "agent_action"
|
|
if ($agentAction -eq "INSTALL_SFERA_EXTENSION") {
|
|
Install-SferaExtensionJob -Job $job -PlatformBins $platformBins
|
|
Complete-Job -JobId $job.job_id -Status "SUCCEEDED" -Logs @("SFERA extension install/update completed.")
|
|
Write-AgentLog "Completed service job $($job.job_id)"
|
|
continue
|
|
}
|
|
$payloadPath = $job.local_path
|
|
if (($job.source -eq "CF_FILE") -or ($job.source -eq "CFE_FILE")) {
|
|
if (![string]::IsNullOrWhiteSpace($payloadPath)) {
|
|
$payloadPath = Convert-LocalCfOrCfeToMetadataExport -Job $job -PlatformBins $platformBins
|
|
} else {
|
|
$payloadPath = Export-CfOrCfeFromInfobase -Job $job -PlatformBins $platformBins
|
|
}
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($payloadPath)) {
|
|
throw "Job does not contain local_path or enough 1C infobase settings for agent export."
|
|
}
|
|
if (!(Test-Path -LiteralPath $payloadPath)) {
|
|
throw "Local path not found on agent machine: $payloadPath"
|
|
}
|
|
|
|
$zipSource = $payloadPath
|
|
if (Test-Path -LiteralPath $payloadPath -PathType Leaf) {
|
|
$singleFileRoot = Join-Path $workRoot "$($job.job_id)-single-file"
|
|
if (Test-Path -LiteralPath $singleFileRoot) { Remove-Item -LiteralPath $singleFileRoot -Recurse -Force }
|
|
New-Item -ItemType Directory -Force -Path $singleFileRoot | Out-Null
|
|
Copy-Item -LiteralPath $payloadPath -Destination (Join-Path $singleFileRoot ([System.IO.Path]::GetFileName($payloadPath))) -Force
|
|
$zipSource = $singleFileRoot
|
|
}
|
|
|
|
$zipPath = Join-Path $workRoot "$($job.job_id).zip"
|
|
if (Test-Path -LiteralPath $zipPath) { Remove-Item -LiteralPath $zipPath -Force }
|
|
$packStats = New-AgentZipArchive -SourceDir $zipSource -ZipPath $zipPath -JobId $job.job_id
|
|
if (!(Test-Path -LiteralPath $zipPath -PathType Leaf)) {
|
|
throw "Agent failed to create archive: $zipPath"
|
|
}
|
|
Send-JobLogs -JobId $job.job_id -Logs @("Packed payload $payloadPath to $zipPath. Files: $($packStats.added), skipped: $($packStats.skipped).")
|
|
Upload-Zip -JobId $job.job_id -ZipPath $zipPath
|
|
Complete-Job -JobId $job.job_id -Status "SUCCEEDED" -Logs @("Uploaded payload and requested server import.")
|
|
Write-AgentLog "Completed job $($job.job_id)"
|
|
} catch {
|
|
Write-AgentLog "ERROR: $($_.Exception.Message)"
|
|
Update-TrayIcon -Online $false -Message $_.Exception.Message
|
|
if ($job -and $job.job_id) {
|
|
Complete-Job -JobId $job.job_id -Status "FAILED" -Logs @("Agent failed.") -ErrorMessage $_.Exception.Message
|
|
}
|
|
Pump-TrayIcon
|
|
Start-Sleep -Seconds ([int]$config.poll_seconds)
|
|
}
|
|
}
|