Revert "merge: apply windows standalone service installer and awHostname hardening"

This reverts commit e643576aa9, reversing
changes made to 669501f20a.
This commit is contained in:
igor04091968
2026-05-08 00:41:00 +03:00
parent e643576aa9
commit 6f5e5eb751
71 changed files with 23918 additions and 19579 deletions
+1 -3
View File
@@ -376,7 +376,6 @@ function New-ActivityWatchDeploymentConfig {
[string]$LaunchScriptPath,
[Parameter(Mandatory = $true)]
[string]$RecoveryScriptPath,
[string]$AwHostname,
[Parameter(Mandatory = $true)]
[pscustomobject[]]$UserTasks,
[string]$PackageVersion = 'v0.13.2'
@@ -387,7 +386,6 @@ function New-ActivityWatchDeploymentConfig {
return [pscustomobject]@{
version = 1
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
awHostname = if ([string]::IsNullOrWhiteSpace($AwHostname)) { [string]$env:COMPUTERNAME } else { [string]$AwHostname }
server = [pscustomobject]@{
host = $ServerHost
port = $ServerPort
@@ -744,7 +742,7 @@ function Start-CollectorScriptIfNeeded {
`$installRoot = [string]`$config.paths.installRoot
`$stateRoot = [string]`$config.paths.stateRoot
`$script:ApiBase = '{0}://{1}:{2}/api/0' -f [string]`$config.server.scheme, [string]`$config.server.host, [string]`$config.server.port
`$script:Hostname = if (`$config.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]`$config.awHostname)) { [string]`$config.awHostname } else { `$env:COMPUTERNAME }
`$script:Hostname = `$env:COMPUTERNAME
`$script:KnownBuckets = @{}
`$collectorScript = [string]`$config.paths.collectorScript
`$endpointCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]`$config.paths.endpointCollectorScript } else { Join-Path `$stateRoot 'dlp-endpoint-signals-collector.ps1' }
-88
View File
@@ -1,88 +0,0 @@
[CmdletBinding()]
param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[int]$LoopSeconds = 20
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-Config {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Config not found: $Path"
}
Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
}
function Write-ServiceLog {
param([string]$Message)
try {
Add-Content -LiteralPath $script:LogPath -Value ('{0} {1}' -f (Get-Date -Format s), $Message)
}
catch {}
}
function Start-CollectorIfNeeded {
param(
[string]$ScriptPath,
[string]$ConfigPath
)
if ([string]::IsNullOrWhiteSpace($ScriptPath) -or -not (Test-Path -LiteralPath $ScriptPath)) {
return
}
$escaped = [Regex]::Escape($ScriptPath)
$running = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object {
$_.Name -eq 'powershell.exe' -and
$_.CommandLine -match $escaped -and
$_.CommandLine -match [Regex]::Escape($ConfigPath)
} |
Select-Object -First 1
if ($running) {
return
}
$args = @('-NoProfile', '-ExecutionPolicy', 'Bypass')
if ($ScriptPath -like '*dlp-endpoint-signals*') {
$args += '-STA'
}
$args += @('-File', $ScriptPath, '-ConfigPath', $ConfigPath)
Start-Process -FilePath 'powershell.exe' -ArgumentList $args -WindowStyle Hidden | Out-Null
Write-ServiceLog ("started collector: {0}" -f $ScriptPath)
}
$cfg = Get-Config -Path $ConfigPath
$stateRoot = if ($cfg.paths -and $cfg.paths.stateRoot) { [string]$cfg.paths.stateRoot } else { 'C:\ProgramData\AWatch-rus' }
$logsRoot = Join-Path $stateRoot 'logs'
if (-not (Test-Path -LiteralPath $logsRoot)) {
New-Item -Path $logsRoot -ItemType Directory -Force | Out-Null
}
$script:LogPath = Join-Path $logsRoot 'standalone-agent-service.log'
Write-ServiceLog ('service loop started, config={0}' -f $ConfigPath)
while ($true) {
try {
$cfg = Get-Config -Path $ConfigPath
$paths = $cfg.paths
Start-CollectorIfNeeded -ScriptPath ([string]$paths.collectorScript) -ConfigPath $ConfigPath
Start-CollectorIfNeeded -ScriptPath ([string]$paths.endpointCollectorScript) -ConfigPath $ConfigPath
Start-CollectorIfNeeded -ScriptPath ([string]$paths.fileCollectorScript) -ConfigPath $ConfigPath
if ($paths.PSObject.Properties.Name -contains 'emailCollectorScript') {
Start-CollectorIfNeeded -ScriptPath ([string]$paths.emailCollectorScript) -ConfigPath $ConfigPath
}
if ($paths.PSObject.Properties.Name -contains 'sessionCollectorScript') {
Start-CollectorIfNeeded -ScriptPath ([string]$paths.sessionCollectorScript) -ConfigPath $ConfigPath
}
}
catch {
Write-ServiceLog ("loop error: {0}" -f $_.Exception.Message)
}
Start-Sleep -Seconds ([Math]::Max($LoopSeconds, 5))
}
+35 -61
View File
@@ -1,4 +1,4 @@
[CmdletBinding()]
[CmdletBinding()]
param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[string]$ServerHost,
@@ -59,18 +59,16 @@ $resolvedPulseSeconds = if ($PSBoundParameters.ContainsKey('PulseSeconds')) { $P
$resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\AWatch-rus\logs' }
$resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot ("browser-domains-{0}.log" -f $env:USERNAME) }
$resolvedIncidentLogPath = if ($IncidentLogPath) { $IncidentLogPath } else { Join-Path $resolvedLogsRoot ("dlp-incidents-{0}.log" -f $env:USERNAME) }
$resolvedHealthPath = Join-Path $resolvedLogsRoot ("health-browser-domains-{0}.json" -f $env:USERNAME)
$resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true }
$resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'AWatch-rus\\incident-artifacts' }
$resolvedIncidentScreenshotEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$deploymentConfig.incidentCapture.screenshotEnabled } else { $true }
$resolvedHostname = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$deploymentConfig.awHostname)) { [string]$deploymentConfig.awHostname } else { [string]$env:COMPUTERNAME }
if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) {
New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null
}
$script:ApiBase = '{0}://{1}:{2}/api/0' -f $resolvedServerScheme, $resolvedServerHost, $resolvedServerPort
$script:Hostname = $resolvedHostname
$script:Hostname = $env:COMPUTERNAME
$script:SessionId = (Get-Process -Id $PID).SessionId
$script:KnownBuckets = @{}
$script:LocalAgentLogsEnabled = $resolvedLocalAgentLogsEnabled
@@ -87,7 +85,6 @@ $script:DlpDefaults = [ordered]@{
action = 'log'
severity = 'low'
}
$script:HealthPath = $resolvedHealthPath
$script:BrowserMap = @{
msedge = 'edge'
chrome = 'chrome'
@@ -137,23 +134,6 @@ function Write-DlpIncidentLog {
}
}
function Write-CollectorHealth {
param([string]$Status = 'running')
try {
$health = @{
collector = 'browser-domains-native'
hostname = $script:Hostname
sessionId = $script:SessionId
status = $Status
apiBase = $script:ApiBase
ts = (Get-Date).ToUniversalTime().ToString('o')
} | ConvertTo-Json -Depth 4
Set-Content -LiteralPath $script:HealthPath -Value $health -Encoding UTF8
}
catch {
}
}
function Test-DomainMatch {
param(
[string]$DomainHost,
@@ -810,52 +790,46 @@ Load-CustomCategoryRules -Path $resolvedRulesPath
Load-DlpPolicy -Path $resolvedPolicyPath
Write-CollectorLog ("коллектор запущен для {0}" -f $script:ApiBase)
try {
while ($true) {
try {
Write-CollectorHealth -Status 'running'
$context = Get-ForegroundWindowContext
if ($context -and $script:BrowserMap.ContainsKey($context.ProcessName)) {
$url = Get-BrowserUrlFromWindow -Handle $context.Handle
if ($url) {
$browserKey = $script:BrowserMap[$context.ProcessName]
$domain = Get-HostFromUrl -Url $url
if (-not $domain) {
$domain = 'unknown'
}
while ($true) {
try {
$context = Get-ForegroundWindowContext
if ($context -and $script:BrowserMap.ContainsKey($context.ProcessName)) {
$url = Get-BrowserUrlFromWindow -Handle $context.Handle
if ($url) {
$browserKey = $script:BrowserMap[$context.ProcessName]
$domain = Get-HostFromUrl -Url $url
if (-not $domain) {
$domain = 'unknown'
}
$rootDomain = Get-RootDomain -DomainHost $domain
if (-not $rootDomain) {
$rootDomain = $domain
}
$rootDomain = Get-RootDomain -DomainHost $domain
if (-not $rootDomain) {
$rootDomain = $domain
}
$category = Get-WebCategory -DomainHost $domain
$bucketId = 'aw-watcher-web-{0}_{1}' -f $browserKey, $script:Hostname
Ensure-Bucket -BucketId $bucketId -ClientName ('aw-watcher-web-' + $browserKey)
Send-Heartbeat -BucketId $bucketId -Url $url -Title $context.Title -BrowserKey $browserKey -ProcessName $context.ProcessName
Send-CategoryHeartbeat -Url $url -Title $context.Title -BrowserKey $browserKey -ProcessName $context.ProcessName -Domain $domain -RootDomain $rootDomain -Category $category.Name -CategoryGroup $category.Group -CategoryRule $category.Rule
$category = Get-WebCategory -DomainHost $domain
$bucketId = 'aw-watcher-web-{0}_{1}' -f $browserKey, $script:Hostname
Ensure-Bucket -BucketId $bucketId -ClientName ('aw-watcher-web-' + $browserKey)
Send-Heartbeat -BucketId $bucketId -Url $url -Title $context.Title -BrowserKey $browserKey -ProcessName $context.ProcessName
Send-CategoryHeartbeat -Url $url -Title $context.Title -BrowserKey $browserKey -ProcessName $context.ProcessName -Domain $domain -RootDomain $rootDomain -Category $category.Name -CategoryGroup $category.Group -CategoryRule $category.Rule
$decision = Get-DlpDecision -Domain $domain -RootDomain $rootDomain -Url $url -Title $context.Title -BrowserKey $browserKey -Category $category.Name -CategoryGroup $category.Group
if ($decision) {
$fingerprint = '{0}|{1}|{2}|{3}' -f $decision.id, $browserKey, $rootDomain, $env:USERNAME
$cooldown = [Math]::Max([int]$decision.cooldownSeconds, 30)
if (Should-EmitIncident -Fingerprint $fingerprint -CooldownSeconds $cooldown) {
Write-DlpIncidentLog ("{0} {1} {2} {3}" -f $decision.severity, $decision.action, $decision.id, $url)
if (@('alert', 'block', 'quarantine') -contains ([string]$decision.action).ToLowerInvariant()) {
Send-DlpIncidentHeartbeat -Decision $decision -Url $url -Title $context.Title -BrowserKey $browserKey -ProcessName $context.ProcessName -Domain $domain -RootDomain $rootDomain -Category $category.Name -CategoryGroup $category.Group
}
$decision = Get-DlpDecision -Domain $domain -RootDomain $rootDomain -Url $url -Title $context.Title -BrowserKey $browserKey -Category $category.Name -CategoryGroup $category.Group
if ($decision) {
$fingerprint = '{0}|{1}|{2}|{3}' -f $decision.id, $browserKey, $rootDomain, $env:USERNAME
$cooldown = [Math]::Max([int]$decision.cooldownSeconds, 30)
if (Should-EmitIncident -Fingerprint $fingerprint -CooldownSeconds $cooldown) {
Write-DlpIncidentLog ("{0} {1} {2} {3}" -f $decision.severity, $decision.action, $decision.id, $url)
if (@('alert', 'block', 'quarantine') -contains ([string]$decision.action).ToLowerInvariant()) {
Send-DlpIncidentHeartbeat -Decision $decision -Url $url -Title $context.Title -BrowserKey $browserKey -ProcessName $context.ProcessName -Domain $domain -RootDomain $rootDomain -Category $category.Name -CategoryGroup $category.Group
}
}
}
}
}
catch {
Write-CollectorLog ("ошибка коллектора: {0}" -f $_.Exception.Message)
}
Start-Sleep -Seconds $resolvedPollSeconds
}
}
finally {
Write-CollectorHealth -Status 'stopped'
catch {
Write-CollectorLog ("ошибка коллектора: {0}" -f $_.Exception.Message)
}
Start-Sleep -Seconds $resolvedPollSeconds
}
+6 -8
View File
@@ -1,4 +1,4 @@
[CmdletBinding()]
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ServerHost,
@@ -24,7 +24,6 @@ param(
[bool]$IncidentScreenshotEnabled = $true,
[string]$IncidentArtifactsRoot,
[bool]$LogonMarkerEnabled = $true,
[string]$AwHostname,
[string]$CustomRulesPath,
[string]$CustomPolicyPath
)
@@ -101,7 +100,6 @@ $config = New-ActivityWatchDeploymentConfig `
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
-IncidentArtifactsRoot $IncidentArtifactsRoot `
-LogonMarkerEnabled $LogonMarkerEnabled `
-AwHostname $AwHostname `
-LaunchScriptPath $launchScriptPath `
-RecoveryScriptPath $recoveryScriptPath `
-UserTasks $taskDefinitions `
@@ -114,8 +112,8 @@ Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptP
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $recoveryScriptPath -ConfigPath $configPath
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
Write-Output 'ActivityWatch развёрнут для пользователей:'
$targetUsers | ForEach-Object { Write-Output " - $_" }
Write-Output "Сервер: ${ServerScheme}://$ServerHost`:$ServerPort"
Write-Output "Каталог данных: $StateRoot"
Write-Output "Файл DLP-политики: $($assetResult.ActivePolicy)"
Write-Host 'ActivityWatch развёрнут для пользователей:'
$targetUsers | ForEach-Object { Write-Host " - $_" }
Write-Host "Сервер: ${ServerScheme}://$ServerHost`:$ServerPort"
Write-Host "Каталог данных: $StateRoot"
Write-Host "Файл DLP-политики: $($assetResult.ActivePolicy)"
+4 -7
View File
@@ -1,4 +1,4 @@
[CmdletBinding()]
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ServerHost,
@@ -24,7 +24,6 @@ param(
[bool]$IncidentScreenshotEnabled = $true,
[string]$IncidentArtifactsRoot,
[bool]$LogonMarkerEnabled = $true,
[string]$AwHostname,
[string]$CustomRulesPath,
[string]$CustomPolicyPath,
[string]$ReportPath,
@@ -72,7 +71,6 @@ if (-not (Test-Path -LiteralPath $deployScript)) {
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
-IncidentArtifactsRoot $IncidentArtifactsRoot `
-LogonMarkerEnabled $LogonMarkerEnabled `
-AwHostname $AwHostname `
-CustomRulesPath $CustomRulesPath `
-CustomPolicyPath $CustomPolicyPath
@@ -96,7 +94,6 @@ if (-not $SkipHardening) {
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
-IncidentArtifactsRoot $IncidentArtifactsRoot `
-LogonMarkerEnabled $LogonMarkerEnabled `
-AwHostname $AwHostname `
-CustomRulesPath $CustomRulesPath `
-CustomPolicyPath $CustomPolicyPath
}
@@ -139,6 +136,6 @@ if ($reportDirectory) {
$report | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $effectiveReportPath -Encoding UTF8
Write-Output 'Комплексное развёртывание ActivityWatch завершено.'
Write-Output "Пользователи: $($resolvedUsers -join ', ')"
Write-Output "Отчёт: $effectiveReportPath"
Write-Host 'Комплексное развёртывание ActivityWatch завершено.'
Write-Host "Пользователи: $($resolvedUsers -join ', ')"
Write-Host "Отчёт: $effectiveReportPath"
+7 -9
View File
@@ -1,4 +1,4 @@
[CmdletBinding()]
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ServerHost,
@@ -22,7 +22,6 @@ param(
[bool]$IncidentScreenshotEnabled = $true,
[string]$IncidentArtifactsRoot,
[bool]$LogonMarkerEnabled = $true,
[string]$AwHostname,
[string]$CustomRulesPath,
[string]$CustomPolicyPath
)
@@ -93,7 +92,6 @@ $config = New-ActivityWatchDeploymentConfig `
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
-IncidentArtifactsRoot $IncidentArtifactsRoot `
-LogonMarkerEnabled $LogonMarkerEnabled `
-AwHostname $AwHostname `
-LaunchScriptPath $launchScriptPath `
-RecoveryScriptPath $recoveryScriptPath `
-UserTasks $taskDefinitions `
@@ -106,9 +104,9 @@ Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptP
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $recoveryScriptPath -ConfigPath $configPath
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
Write-Output "ActivityWatch развёрнут для пользователя: $TargetUser"
Write-Output "Сервер: ${ServerScheme}://$ServerHost`:$ServerPort"
Write-Output "Каталог установки: $InstallRoot"
Write-Output "Каталог данных: $StateRoot"
Write-Output "Файл правил: $($assetResult.ActiveRules)"
Write-Output "Файл DLP-политики: $($assetResult.ActivePolicy)"
Write-Host "ActivityWatch развёрнут для пользователя: $TargetUser"
Write-Host "Сервер: ${ServerScheme}://$ServerHost`:$ServerPort"
Write-Host "Каталог установки: $InstallRoot"
Write-Host "Каталог данных: $StateRoot"
Write-Host "Файл правил: $($assetResult.ActiveRules)"
Write-Host "Файл DLP-политики: $($assetResult.ActivePolicy)"
File diff suppressed because it is too large Load Diff
+30 -122
View File
@@ -53,79 +53,13 @@ function Write-CollectorLog {
catch { }
}
function Add-WalEntry {
function Invoke-AwJsonPost {
param(
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$Json
)
if ([string]::IsNullOrWhiteSpace($script:WalPath)) { return }
try {
$entry = @{ ts = (Get-Date).ToUniversalTime().ToString('o'); uri = $Uri; json = $Json } | ConvertTo-Json -Compress
Add-Content -LiteralPath $script:WalPath -Value $entry -Encoding UTF8
} catch {}
}
function Flush-Wal {
if ([string]::IsNullOrWhiteSpace($script:WalPath) -or -not (Test-Path -LiteralPath $script:WalPath)) { return }
$remaining = New-Object System.Collections.Generic.List[string]
try {
$script:WalFlushing = $true
foreach ($line in (Get-Content -LiteralPath $script:WalPath -ErrorAction SilentlyContinue)) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
try {
$entry = $line | ConvertFrom-Json
if ($null -eq $entry -or -not $entry.uri -or -not $entry.json) { continue }
if (-not (Invoke-AwJsonPost -Uri ([string]$entry.uri) -Json ([string]$entry.json))) { $remaining.Add($line) }
} catch { $remaining.Add($line) }
}
if ($remaining.Count -eq 0) {
Remove-Item -LiteralPath $script:WalPath -Force -ErrorAction SilentlyContinue
} else {
Set-Content -LiteralPath $script:WalPath -Value ($remaining -join [Environment]::NewLine) -Encoding UTF8
}
} finally {
$script:WalFlushing = $false
}
}
function Write-CollectorHealth {
param([string]$Status = 'running')
if ([string]::IsNullOrWhiteSpace($script:HealthPath)) { return }
try {
$walDepth = 0
if ($script:WalPath -and (Test-Path -LiteralPath $script:WalPath)) { $walDepth = @((Get-Content -LiteralPath $script:WalPath)).Count }
$health = @{
collector = 'email-outbound'; hostname = $script:Hostname; sessionId = $script:SessionId;
status = $Status; apiBase = $script:ApiBase; walDepth = $walDepth; ts = (Get-Date).ToUniversalTime().ToString('o')
} | ConvertTo-Json -Depth 5
Set-Content -LiteralPath $script:HealthPath -Value $health -Encoding UTF8
} catch {}
}
function Invoke-AwJsonPost {
param(
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$Json,
[int]$MaxAttempts = 5,
[int]$InitialBackoffMs = 500
)
$attempt = 1
$backoff = [Math]::Max(100, $InitialBackoffMs)
$bytes = [Text.Encoding]::UTF8.GetBytes($Json)
while ($attempt -le $MaxAttempts) {
try {
Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null
return $true
} catch {
if ($attempt -ge $MaxAttempts) {
if (-not $script:WalFlushing) { Add-WalEntry -Uri $Uri -Json $Json }
return $false
}
Start-Sleep -Milliseconds $backoff
$backoff = [Math]::Min($backoff * 2, 10000)
$attempt++
}
}
Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null
}
function Ensure-Bucket {
@@ -583,10 +517,6 @@ $script:SeenSmtpConnections = @{}
$script:PulseSeconds = [Math]::Max($resolvedPollSeconds * 3, 30)
$script:LocalAgentLogsEnabled = $resolvedLocalAgentLogsEnabled
$script:LogPath = $resolvedLogPath
$stateRoot = if ($deploymentConfig -and $deploymentConfig.paths -and $deploymentConfig.paths.stateRoot) { [string]$deploymentConfig.paths.stateRoot } else { 'C:\ProgramData\AWatch-rus' }
$script:WalPath = Join-Path $stateRoot 'wal-email-outbound.ndjson'
$script:HealthPath = Join-Path $stateRoot 'health-email-outbound.json'
$script:WalFlushing = $false
$script:OutlookApp = $null
$script:OutlookNamespace = $null
$script:SentFolder = $null
@@ -610,65 +540,43 @@ if ($useOutlook) {
# Main loop
# ---------------------------------------------------------------------------
try {
while ($true) {
try {
Flush-Wal
Write-CollectorHealth -Status 'running'
if (-not $script:Policy.defaults.enabled) {
Start-Sleep -Seconds $resolvedPollSeconds
continue
}
while ($true) {
try {
if (-not $script:Policy.defaults.enabled) {
Start-Sleep -Seconds $resolvedPollSeconds
continue
}
if ($useOutlook) {
if (-not $outlookReady) {
$outlookReady = Initialize-OutlookCom
}
if ($outlookReady) {
try {
Poll-OutlookSentItems
}
catch {
Write-CollectorLog ("outlook poll error: {0}" -f $_.Exception.Message)
$outlookReady = $false
$script:OutlookApp = $null
$script:OutlookNamespace = $null
$script:SentFolder = $null
}
}
if ($useOutlook) {
if (-not $outlookReady) {
$outlookReady = Initialize-OutlookCom
}
if ($useSmtp) {
if ($outlookReady) {
try {
Poll-SmtpConnections
Poll-OutlookSentItems
}
catch {
Write-CollectorLog ("smtp poll error: {0}" -f $_.Exception.Message)
Write-CollectorLog ("outlook poll error: {0}" -f $_.Exception.Message)
$outlookReady = $false
$script:OutlookApp = $null
$script:OutlookNamespace = $null
$script:SentFolder = $null
}
}
}
catch {
Write-CollectorLog ("collector error: {0}" -f $_.Exception.Message)
}
Start-Sleep -Seconds $resolvedPollSeconds
}
}
finally {
Write-CollectorHealth -Status 'stopped'
try {
if ($null -ne $script:SentFolder) {
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($script:SentFolder)
$script:SentFolder = $null
}
if ($null -ne $script:OutlookNamespace) {
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($script:OutlookNamespace)
$script:OutlookNamespace = $null
}
if ($null -ne $script:OutlookApp) {
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($script:OutlookApp)
$script:OutlookApp = $null
if ($useSmtp) {
try {
Poll-SmtpConnections
}
catch {
Write-CollectorLog ("smtp poll error: {0}" -f $_.Exception.Message)
}
}
}
catch {}
catch {
Write-CollectorLog ("collector error: {0}" -f $_.Exception.Message)
}
Start-Sleep -Seconds $resolvedPollSeconds
}
+15 -85
View File
@@ -1,4 +1,4 @@
[CmdletBinding()]
[CmdletBinding()]
param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[string]$ServerHost,
@@ -22,9 +22,6 @@ Add-Type -AssemblyName System.Net.Http
$script:KnownBuckets = @{}
$script:Hostname = $env:COMPUTERNAME
$script:SessionId = [System.Diagnostics.Process]::GetCurrentProcess().SessionId
$script:WalPath = $null
$script:HealthPath = $null
$script:WalFlushing = $false
# Настройка логирования
$script:LogPath = $LogPath
@@ -46,88 +43,28 @@ function Write-FileCollectorLog {
} catch {}
}
function Add-WalEntry {
function Invoke-AwJsonPost {
param(
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$Json
)
if ([string]::IsNullOrWhiteSpace($script:WalPath)) { return }
$httpClient = $null
try {
$entry = @{ ts = (Get-Date).ToUniversalTime().ToString('o'); uri = $Uri; json = $Json } | ConvertTo-Json -Compress
Add-Content -LiteralPath $script:WalPath -Value $entry -Encoding UTF8
} catch {}
}
function Flush-Wal {
if ([string]::IsNullOrWhiteSpace($script:WalPath) -or -not (Test-Path -LiteralPath $script:WalPath)) { return }
$remaining = New-Object System.Collections.Generic.List[string]
try {
$script:WalFlushing = $true
foreach ($line in (Get-Content -LiteralPath $script:WalPath -ErrorAction SilentlyContinue)) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
try {
$entry = $line | ConvertFrom-Json
if ($null -eq $entry -or -not $entry.uri -or -not $entry.json) { continue }
if (-not (Invoke-AwJsonPost -Uri ([string]$entry.uri) -Json ([string]$entry.json))) { $remaining.Add($line) }
} catch { $remaining.Add($line) }
}
if ($remaining.Count -eq 0) {
Remove-Item -LiteralPath $script:WalPath -Force -ErrorAction SilentlyContinue
} else {
Set-Content -LiteralPath $script:WalPath -Value ($remaining -join [Environment]::NewLine) -Encoding UTF8
$httpClient = New-Object System.Net.Http.HttpClient
$content = New-Object System.Net.Http.StringContent($Json, [System.Text.Encoding]::UTF8, "application/json")
$response = $httpClient.PostAsync($Uri, $content).Result
if (-not $response.IsSuccessStatusCode) {
$status = [int]$response.StatusCode
$reason = [string]$response.ReasonPhrase
$body = $response.Content.ReadAsStringAsync().Result
Write-FileCollectorLog ("POST failed: uri={0} status={1} reason={2} body={3}" -f $Uri, $status, $reason, $body)
}
} catch {
Write-FileCollectorLog "POST Error: $($_.Exception.Message)"
} finally {
$script:WalFlushing = $false
}
}
function Write-CollectorHealth {
param([string]$Status = 'running')
if ([string]::IsNullOrWhiteSpace($script:HealthPath)) { return }
try {
$walDepth = 0
if ($script:WalPath -and (Test-Path -LiteralPath $script:WalPath)) { $walDepth = @((Get-Content -LiteralPath $script:WalPath)).Count }
$health = @{
collector = 'file-operations'; hostname = $script:Hostname; sessionId = $script:SessionId;
status = $Status; apiBase = $script:ApiBase; walDepth = $walDepth; ts = (Get-Date).ToUniversalTime().ToString('o')
} | ConvertTo-Json -Depth 5
Set-Content -LiteralPath $script:HealthPath -Value $health -Encoding UTF8
} catch {}
}
function Invoke-AwJsonPost {
param(
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$Json,
[int]$MaxAttempts = 5,
[int]$InitialBackoffMs = 500
)
$attempt = 1
$backoff = [Math]::Max(100, $InitialBackoffMs)
while ($attempt -le $MaxAttempts) {
$httpClient = $null
try {
$httpClient = New-Object System.Net.Http.HttpClient
$content = New-Object System.Net.Http.StringContent($Json, [System.Text.Encoding]::UTF8, "application/json")
$response = $httpClient.PostAsync($Uri, $content).Result
if ($response.IsSuccessStatusCode) { return $true }
if ($attempt -ge $MaxAttempts) {
if (-not $script:WalFlushing) { Add-WalEntry -Uri $Uri -Json $Json }
return $false
}
} catch {
if ($attempt -ge $MaxAttempts) {
if (-not $script:WalFlushing) { Add-WalEntry -Uri $Uri -Json $Json }
return $false
}
} finally {
if ($null -ne $httpClient) {
$httpClient.Dispose()
}
if ($null -ne $httpClient) {
$httpClient.Dispose()
}
Start-Sleep -Milliseconds $backoff
$backoff = [Math]::Min($backoff * 2, 10000)
$attempt++
}
}
@@ -208,15 +145,11 @@ function Send-FileOperationEvent {
$config = Get-DeploymentConfig -Path $ConfigPath
if (-not $config) { throw "Configuration file not found: $ConfigPath" }
$script:Hostname = if ($config.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$config.awHostname)) { [string]$config.awHostname } else { [string]$env:COMPUTERNAME }
$scheme = if ($ServerScheme) { $ServerScheme } elseif ($config.server.scheme) { $config.server.scheme } else { 'http' }
$hostName = if ($ServerHost) { $ServerHost } elseif ($config.server.host) { $config.server.host } else { 'localhost' }
$port = if ($ServerPort) { $ServerPort } elseif ($config.server.port) { $config.server.port } else { 5600 }
$script:ApiBase = "{0}://{1}:{2}/api/0" -f $scheme, $hostName, $port
$stateRoot = if ($config.paths -and $config.paths.stateRoot) { [string]$config.paths.stateRoot } else { 'C:\ProgramData\AWatch-rus' }
$script:WalPath = Join-Path $stateRoot 'wal-file-operations.ndjson'
$script:HealthPath = Join-Path $stateRoot 'health-file-operations.json'
$bucketId = 'aw-file-operations_' + $script:Hostname
Ensure-Bucket -BucketId $bucketId -ClientName 'aw-file-operations' -BucketType 'aw.file.operation'
@@ -273,14 +206,11 @@ Write-FileCollectorLog "Collector started. Waiting for events..."
try {
while ($true) {
Flush-Wal
Write-CollectorHealth -Status 'running'
Start-Sleep -Seconds $PollSeconds
}
}
finally {
Write-FileCollectorLog "Stopping collector..."
Write-CollectorHealth -Status 'stopped'
foreach ($sub in @($subscriptions)) {
try {
if ($sub -and $sub.Id) {
+4 -7
View File
@@ -1,4 +1,4 @@
[CmdletBinding()]
[CmdletBinding()]
param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[string]$ServerHost,
@@ -21,7 +21,6 @@ param(
[bool]$IncidentScreenshotEnabled,
[string]$IncidentArtifactsRoot,
[bool]$LogonMarkerEnabled,
[string]$AwHostname,
[string]$CustomRulesPath,
[string]$CustomPolicyPath,
[switch]$RepairPackage,
@@ -74,7 +73,6 @@ $effectiveIncidentCaptureEnabled = if ($PSBoundParameters.ContainsKey('IncidentC
$effectiveIncidentScreenshotEnabled = if ($PSBoundParameters.ContainsKey('IncidentScreenshotEnabled')) { [bool]$IncidentScreenshotEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$existingConfig.incidentCapture.screenshotEnabled } else { $true }
$effectiveIncidentArtifactsRoot = if ($PSBoundParameters.ContainsKey('IncidentArtifactsRoot') -and $IncidentArtifactsRoot) { $IncidentArtifactsRoot } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$existingConfig.incidentCapture.artifactsRoot } else { Join-Path $effectiveStateRoot 'incident-artifacts' }
$effectiveLogonMarkerEnabled = if ($PSBoundParameters.ContainsKey('LogonMarkerEnabled')) { [bool]$LogonMarkerEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'sessionEvents' -and $existingConfig.sessionEvents.PSObject.Properties.Name -contains 'logonEnabled') { [bool]$existingConfig.sessionEvents.logonEnabled } else { $true }
$effectiveAwHostname = if ($PSBoundParameters.ContainsKey('AwHostname') -and -not [string]::IsNullOrWhiteSpace($AwHostname)) { [string]$AwHostname } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$existingConfig.awHostname)) { [string]$existingConfig.awHostname } else { [string]$env:COMPUTERNAME }
$effectiveVersion = if ($Version) { $Version } elseif ($existingConfig) { [string]$existingConfig.package.version } else { 'v0.13.2' }
$effectiveUsers = if ($Users -or $UserListPath) {
@@ -141,7 +139,6 @@ $config = New-ActivityWatchDeploymentConfig `
-IncidentScreenshotEnabled $effectiveIncidentScreenshotEnabled `
-IncidentArtifactsRoot $effectiveIncidentArtifactsRoot `
-LogonMarkerEnabled $effectiveLogonMarkerEnabled `
-AwHostname $effectiveAwHostname `
-LaunchScriptPath $effectiveLaunchScript `
-RecoveryScriptPath $effectiveRecoveryScript `
-UserTasks $taskDefinitions `
@@ -154,6 +151,6 @@ Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptP
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $effectiveRecoveryScript -ConfigPath $effectiveConfigPath
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
Write-Output 'Укрепление и восстановление ActivityWatch завершены.'
Write-Output "Конфигурация: $effectiveConfigPath"
Write-Output "Пользователи восстановлены: $($effectiveUsers -join ', ')"
Write-Host 'Укрепление и восстановление ActivityWatch завершены.'
Write-Host "Конфигурация: $effectiveConfigPath"
Write-Host "Пользователи восстановлены: $($effectiveUsers -join ', ')"
-121
View File
@@ -1,121 +0,0 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ServerHost,
[int]$ServerPort = 5600,
[ValidateSet('http', 'https')]
[string]$ServerScheme = 'http',
[string]$StateRoot = 'C:\ProgramData\AWatch-rus',
[string]$InstallRoot = 'C:\Program Files\AWatch-rus\bin',
[string]$ServiceName = 'AWatchRusStandaloneAgent',
[string]$AwHostname
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Assert-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = [Security.Principal.WindowsPrincipal]::new($id)
if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'Run as Administrator.'
}
}
function Ensure-Dir {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -Path $Path -ItemType Directory -Force | Out-Null
}
}
Assert-Admin
$logsRoot = Join-Path $StateRoot 'logs'
Ensure-Dir -Path $StateRoot
Ensure-Dir -Path $logsRoot
$collectorScript = Join-Path $StateRoot 'browser-domains-native-collector.ps1'
$endpointCollectorScript = Join-Path $StateRoot 'dlp-endpoint-signals-collector.ps1'
$fileCollectorScript = Join-Path $StateRoot 'file-operations-collector.ps1'
$emailCollectorScript = Join-Path $StateRoot 'email-outbound-collector.ps1'
$sessionCollectorScript = Join-Path $StateRoot 'worktime-session-collector.ps1'
$rulesPath = Join-Path $StateRoot 'web-category-rules.json'
$policyPath = Join-Path $StateRoot 'dlp-policy.json'
$configPath = Join-Path $StateRoot 'deployment-config.json'
$serviceScriptPath = Join-Path $PSScriptRoot 'aw-standalone-service.ps1'
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1') -Destination $collectorScript -Force
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1') -Destination $endpointCollectorScript -Force
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'file-operations-collector.ps1') -Destination $fileCollectorScript -Force
if (Test-Path -LiteralPath (Join-Path $PSScriptRoot 'email-outbound-collector.ps1')) {
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'email-outbound-collector.ps1') -Destination $emailCollectorScript -Force
}
if (Test-Path -LiteralPath (Join-Path $PSScriptRoot 'worktime-session-collector.ps1')) {
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'worktime-session-collector.ps1') -Destination $sessionCollectorScript -Force
}
if (-not (Test-Path -LiteralPath $rulesPath)) {
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'web-category-rules.example.json') -Destination $rulesPath -Force
}
if (-not (Test-Path -LiteralPath $policyPath)) {
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'dlp-policy.example.json') -Destination $policyPath -Force
}
$effectiveHostname = if ([string]::IsNullOrWhiteSpace($AwHostname)) { [string]$env:COMPUTERNAME } else { [string]$AwHostname }
$config = [pscustomobject]@{
version = 1
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
awHostname = $effectiveHostname
server = [pscustomobject]@{
host = $ServerHost
port = $ServerPort
scheme = $ServerScheme
}
paths = [pscustomobject]@{
installRoot = $InstallRoot
stateRoot = $StateRoot
logsRoot = $logsRoot
collectorScript = $collectorScript
endpointCollectorScript = $endpointCollectorScript
fileCollectorScript = $fileCollectorScript
emailCollectorScript = $emailCollectorScript
sessionCollectorScript = $sessionCollectorScript
rulesPath = $rulesPath
policyPath = $policyPath
}
collector = [pscustomobject]@{
pollSeconds = 5
pulseSeconds = 30
}
collectors = [pscustomobject]@{
afkEnabled = $false
windowEnabled = $false
fileOpsEnabled = $true
emailEnabled = $true
}
logging = [pscustomobject]@{
localAgentLogsEnabled = $true
}
}
$config | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $configPath -Encoding UTF8
$existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($existing) {
sc.exe stop $ServiceName | Out-Null
Start-Sleep -Seconds 1
sc.exe delete $ServiceName | Out-Null
Start-Sleep -Seconds 1
}
$binPath = "`"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`" -NoProfile -ExecutionPolicy Bypass -File `"$serviceScriptPath`" -ConfigPath `"$configPath`""
sc.exe create $ServiceName binPath= "$binPath" start= auto DisplayName= "AWatch-rus Standalone Agent" | Out-Null
sc.exe description $ServiceName "Standalone AWatch-rus DLP agent service wrapper" | Out-Null
sc.exe failure $ServiceName reset= 60 actions= restart/5000/restart/5000/restart/5000 | Out-Null
sc.exe start $ServiceName | Out-Null
Write-Output "Standalone service installed: $ServiceName"
Write-Output "Config: $configPath"
Write-Output ("Host: {0} -> {1}://{2}:{3}" -f $effectiveHostname, $ServerScheme, $ServerHost, $ServerPort)
@@ -34,8 +34,6 @@ Name: "validate"; Description: "Запустить validate-deployment (чере
[Files]
Source: "..\..\ActivityWatch.Windows.Common.psd1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\ActivityWatch.Windows.Common.psm1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\install-standalone-service.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\aw-standalone-service.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\deploy-single-user.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\deploy-domain-users.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\deploy-ensemble.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
@@ -45,7 +43,6 @@ Source: "..\..\migrate-awatch-rus-paths.ps1"; DestDir: "{app}\windows"; Flags: i
Source: "..\..\worktime-session-collector.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\browser-domains-native-collector.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\dlp-endpoint-signals-collector.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\file-operations-collector.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\email-outbound-collector.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\web-category-rules.example.json"; DestDir: "{app}\windows"; Flags: ignoreversion
Source: "..\..\dlp-policy.example.json"; DestDir: "{app}\windows"; Flags: ignoreversion
@@ -54,11 +51,95 @@ Source: "payload\{#AwDefaultZipName}"; DestDir: "{app}\payload"; Flags: ignoreve
Source: "innosetup-rdp-package-filelist.md"; DestDir: "{app}\windows\installkit\innosetup"; Flags: ignoreversion
[Run]
Filename: "powershell.exe"; Parameters: "{code:GetStandaloneInstallParams}"; Flags: runhidden; Tasks: deploy
Filename: "powershell.exe"; Parameters: "{code:GetDeployEnsembleParams}"; Flags: runhidden; Tasks: deploy
[Code]
var
ServerHostPage: TInputQueryWizardPage;
UsersPage: TInputQueryWizardPage;
OptionsPage: TInputOptionWizardPage;
function NormalizeUserCsv(const UserCsv: string): string;
var
i: Integer;
s: string;
token: string;
begin
Result := '';
s := UserCsv;
while True do
begin
i := Pos(',', s);
if i = 0 then
begin
token := Trim(s);
s := '';
end
else
begin
token := Trim(Copy(s, 1, i - 1));
Delete(s, 1, i);
end;
if token <> '' then
begin
if Result <> '' then
Result := Result + ',';
Result := Result + token;
end;
if s = '' then
Break;
end;
end;
function BuildUsersPowerShellArg(const UserCsv: string): string;
var
i: Integer;
s: string;
token: string;
quoted: string;
begin
Result := '';
s := UserCsv;
while True do
begin
i := Pos(',', s);
if i = 0 then
begin
token := Trim(s);
s := '';
end
else
begin
token := Trim(Copy(s, 1, i - 1));
Delete(s, 1, i);
end;
if token <> '' then
begin
quoted := '"' + token + '"';
if Result <> '' then
Result := Result + ',';
Result := Result + quoted;
end;
if s = '' then
Break;
end;
if Result <> '' then
Result := '-Users ' + Result;
end;
function PayloadZipPath: string;
begin
Result := ExpandConstant('{app}\payload\{#AwDefaultZipName}');
end;
function HasPayloadZip: Boolean;
begin
Result := FileExists(ExpandConstant('{src}\payload\{#AwDefaultZipName}'));
end;
procedure InitializeWizard;
begin
@@ -74,24 +155,62 @@ begin
ServerHostPage.Add('ServerPort', False);
ServerHostPage.Values[0] := '{#AwDefaultServerHost}';
ServerHostPage.Values[1] := '{#AwDefaultServerPort}';
UsersPage := CreateInputQueryPage(
ServerHostPage.ID,
'Пользователи (RDP)',
'Перечень пользователей, для которых разворачиваем агенты.',
'Введите список через запятую. Пример: user1,user2,user3'
);
UsersPage.Add('Users (CSV)', False);
UsersPage.Values[0] := '{#AwDefaultUsers}';
OptionsPage := CreateInputOptionPage(
UsersPage.ID,
'Опции деплоя',
'Выберите опции для установки/валидации.',
'',
False,
False
);
OptionsPage.Add('Использовать offline payload (встроенный ZIP)');
OptionsPage.Add('Запустить validate-deployment после деплоя');
OptionsPage.Values[0] := HasPayloadZip;
OptionsPage.Values[1] := True;
end;
function GetStandaloneInstallParams(Param: string): string;
function GetDeployEnsembleParams(Param: string): string;
var
serverHost: string;
serverPort: string;
usersCsv: string;
usersArg: string;
zipArg: string;
validateArg: string;
begin
serverHost := Trim(ServerHostPage.Values[0]);
serverPort := Trim(ServerHostPage.Values[1]);
if serverHost = '' then
RaiseException('ServerHost is empty.');
if serverPort = '' then
RaiseException('ServerPort is empty.');
usersCsv := NormalizeUserCsv(UsersPage.Values[0]);
usersArg := BuildUsersPowerShellArg(usersCsv);
if usersArg = '' then
RaiseException('Users list is empty.');
zipArg := '';
if OptionsPage.Values[0] then
zipArg := ' -PackageZipPath "' + PayloadZipPath + '"';
validateArg := '';
if OptionsPage.Values[1] and WizardIsTaskSelected('validate') then
validateArg := ' -ValidateAfterDeploy';
Result :=
'-NoProfile -ExecutionPolicy Bypass -File "' + ExpandConstant('{app}\windows\install-standalone-service.ps1') + '"' +
'-NoProfile -ExecutionPolicy Bypass -File "' + ExpandConstant('{app}\windows\deploy-ensemble.ps1') + '"' +
' -ServerHost "' + serverHost + '"' +
' -ServerPort ' + serverPort +
' ' + usersArg +
zipArg +
' -InstallRoot "{#AwDefaultInstallRoot}"' +
' -StateRoot "{#AwDefaultStateRoot}"';
' -StateRoot "{#AwDefaultStateRoot}"' +
validateArg;
end;
+1 -1
View File
@@ -1,4 +1,4 @@
[CmdletBinding(SupportsShouldProcess = $true)]
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[string]$OldInstallRoot = 'C:\Program Files\ActivityWatch-Phase2',
[string]$OldStateRoot = 'C:\ProgramData\ActivityWatch-Phase2',
+82 -22
View File
@@ -1,6 +1,6 @@
[CmdletBinding()]
param(
[string]$ConfigPath = 'C:\ProgramData\ActivityWatch\deployment-config.json'
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json'
)
Set-StrictMode -Version Latest
@@ -13,26 +13,70 @@ $config = Read-ActivityWatchDeploymentConfig -Path $ConfigPath
$installRoot = [string]$config.paths.installRoot
$stateRoot = [string]$config.paths.stateRoot
$collectorScript = [string]$config.paths.collectorScript
$endpointCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$config.paths.endpointCollectorScript } else { Join-Path $stateRoot 'dlp-endpoint-signals-collector.ps1' }
$fileCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'fileCollectorScript') { [string]$config.paths.fileCollectorScript } else { Join-Path $stateRoot 'file-operations-collector.ps1' }
$sessionCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$config.paths.sessionCollectorScript } else { Join-Path $stateRoot 'worktime-session-collector.ps1' }
$rulesPath = [string]$config.paths.rulesPath
$policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$config.paths.policyPath } else { Join-Path $stateRoot 'dlp-policy.json' }
$launchScript = [string]$config.paths.launchScript
$recoveryScript = [string]$config.paths.recoveryScript
$afkExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'afkEnabled') { [bool]$config.collectors.afkEnabled } else { $true }
$windowExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'windowEnabled') { [bool]$config.collectors.windowEnabled } else { $true }
$fileOpsExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'fileOpsEnabled') { [bool]$config.collectors.fileOpsEnabled } else { $true }
$printServiceOperationalEnabled = $false
try {
$printServiceLog = Get-WinEvent -ListLog 'Microsoft-Windows-PrintService/Operational' -ErrorAction Stop
$printServiceOperationalEnabled = [bool]$printServiceLog.IsEnabled
}
catch {
}
$printJobTitlePolicyEnabled = $false
try {
$printPolicy = Get-ItemProperty -LiteralPath 'HKLM:\Software\Policies\Microsoft\Windows NT\Printers' -Name 'ShowJobTitleInEventLogs' -ErrorAction Stop
$printJobTitlePolicyEnabled = ([int]$printPolicy.ShowJobTitleInEventLogs -eq 1)
}
catch {
}
$requiredFiles = @(
(Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe'),
(Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe'),
$collectorScript,
$endpointCollectorScript,
$sessionCollectorScript,
$rulesPath,
$policyPath,
$launchScript,
$recoveryScript,
$ConfigPath
)
if ($fileOpsExpected) {
$requiredFiles += $fileCollectorScript
}
if ($afkExpected) {
$requiredFiles += (Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe')
}
if ($windowExpected) {
$requiredFiles += (Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe')
}
$missingFiles = @(
$requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) }
)
$processNames = @('aw-watcher-afk', 'aw-watcher-window')
$runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId
$processNames = @()
if ($afkExpected) { $processNames += 'aw-watcher-afk' }
if ($windowExpected) { $processNames += 'aw-watcher-window' }
$runningProcesses = @()
if ($processNames.Count -gt 0) {
$runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId
}
$sessionCollectorProcesses = @(
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object {
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and
$_.CommandLine -match [Regex]::Escape($sessionCollectorScript)
} |
Select-Object Name, ProcessId, SessionId, CommandLine
)
$taskNames = @()
if ($config.userTasks) {
@@ -41,25 +85,28 @@ if ($config.userTasks) {
$taskNames += [string]$config.recovery.taskName
$taskNames = $taskNames | Sort-Object -Unique
$tasks = foreach ($taskName in $taskNames) {
$task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($task) {
[pscustomobject]@{
taskName = $task.TaskName
state = [string]$task.State
present = $true
$tasks = @(
foreach ($taskName in $taskNames) {
$task = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -eq $taskName } | Select-Object -First 1
if ($task) {
[pscustomobject]@{
taskName = $task.TaskName
state = [string]$task.State
present = $true
}
}
else {
[pscustomobject]@{
taskName = $taskName
state = 'Отсутствует'
present = $false
}
}
}
else {
[pscustomobject]@{
taskName = $taskName
state = 'Missing'
present = $false
}
}
}
)
$serverUrl = '{0}://{1}:{2}' -f [string]$config.server.scheme, [string]$config.server.host, [int]$config.server.port
$uniqueRunningProcessNames = @($runningProcesses | Select-Object -ExpandProperty Name -Unique)
$result = [ordered]@{
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
configPath = $ConfigPath
@@ -76,11 +123,24 @@ $result = [ordered]@{
ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present }))
}
processes = [ordered]@{
expected = $processNames
list = @($runningProcesses)
ok = [bool](($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge 2)
sessionCollectors = @($sessionCollectorProcesses)
ok = [bool](
(
($processNames.Count -eq 0) -or
($uniqueRunningProcessNames.Count -ge $processNames.Count)
) -and
($sessionCollectorProcesses.Count -ge 1)
)
}
printTelemetry = [ordered]@{
operationalLogEnabled = $printServiceOperationalEnabled
jobTitlePolicyEnabled = $printJobTitlePolicyEnabled
ok = [bool]($printServiceOperationalEnabled -and $printJobTitlePolicyEnabled)
}
}
$result.overallOk = [bool]($result.files.ok -and $result.tasks.ok -and $result.processes.ok)
$result.overallOk = [bool]($result.files.ok -and $result.tasks.ok -and $result.processes.ok -and $result.printTelemetry.ok)
$result
+1 -496
View File
@@ -1,268 +1,4 @@
param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[string]$Hostname,
[int]$PollSeconds = 30
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-Config {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Конфигурация не найдена: $Path"
}
Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
}
function Invoke-AwJsonPost {
param(
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$Json
)
$bytes = [Text.Encoding]::UTF8.GetBytes($Json)
Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null
}
function Ensure-Bucket {
param(
[Parameter(Mandatory = $true)][string]$ApiBase,
[Parameter(Mandatory = $true)][string]$BucketId,
[Parameter(Mandatory = $true)][string]$HostnameValue
)
try {
Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null
return
}
catch { Write-Error param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[string]$Hostname,
[int]$PollSeconds = 30
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-Config {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Конфигурация не найдена: $Path"
}
Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
}
function Invoke-AwJsonPost {
param(
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$Json
)
$bytes = [Text.Encoding]::UTF8.GetBytes($Json)
Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null
}
function Ensure-Bucket {
param(
[Parameter(Mandatory = $true)][string]$ApiBase,
[Parameter(Mandatory = $true)][string]$BucketId,
[Parameter(Mandatory = $true)][string]$HostnameValue
)
try {
Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null
return
}
catch {
}
$body = @{
client = 'aw-worktime-session-collector'
type = 'aw.worktime.session'
hostname = $HostnameValue
} | ConvertTo-Json -Compress
try {
Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId" -Json $body
}
catch {
Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null
}
}
function Get-SessionRecords {
$records = @()
try {
$lines = quser 2>$null
if (-not $lines) {
return @()
}
foreach ($line in ($lines | Select-Object -Skip 1)) {
$clean = ($line -replace '^\s*>?', '').Trim()
if (-not $clean) {
continue
}
$parts = $clean -split '\s+'
if ($parts.Count -lt 4) {
continue
}
$sessionName = ''
$sessionIdIndex = 2
if ($parts[1] -match '^\d+$') {
$sessionIdIndex = 1
}
else {
$sessionName = $parts[1]
}
$sessionId = 0
if ($parts[$sessionIdIndex] -match '^\d+$') {
$sessionId = [int]$parts[$sessionIdIndex]
}
$records += [pscustomobject]@{
username = $parts[0]
sessionName = $sessionName
sessionId = $sessionId
state = $parts[$sessionIdIndex + 1]
}
}
}
catch {
}
return $records
}
function Test-SessionIsActive {
param([AllowNull()][string]$State)
if ([string]::IsNullOrWhiteSpace($State)) { return $false }
$s = $State.Trim().ToLowerInvariant()
return ($s -eq 'active') -or ($s -like 'актив*')
}
$cfg = Get-Config -Path $ConfigPath
$hostValue = if ($Hostname) { $Hostname } elseif ($cfg.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$cfg.awHostname)) { [string]$cfg.awHostname } else { [string]$env:COMPUTERNAME }
$apiBase = '{0}://{1}:{2}/api/0' -f [string]$cfg.server.scheme, [string]$cfg.server.host, [string]$cfg.server.port
$bucketId = 'aw-worktime-sessions_' + $hostValue
$pulse = 120
$sleepSec = if ($PollSeconds -gt 0) {
$PollSeconds
}
elseif ($cfg.collector -and $cfg.collector.pollSeconds) {
[int]$cfg.collector.pollSeconds
}
else {
30
}
Ensure-Bucket -ApiBase $apiBase -BucketId $bucketId -HostnameValue $hostValue
while ($true) {
$now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
$records = Get-SessionRecords
if (-not $records -or $records.Count -eq 0) {
$records = @([pscustomobject]@{
username = $env:USERNAME
sessionName = ''
sessionId = (Get-Process -Id $PID).SessionId
state = 'Unknown'
})
}
foreach ($rec in $records) {
$payload = @{
timestamp = $now
duration = 0
data = @{
username = [string]$rec.username
userId = "$($env:USERDOMAIN)\$($rec.username)"
sessionId = [int]$rec.sessionId
sessionName = [string]$rec.sessionName
state = [string]$rec.state
active = (Test-SessionIsActive -State ([string]$rec.state))
hostname = $hostValue
source = 'worktime-session-collector'
}
} | ConvertTo-Json -Depth 6 -Compress
try {
Invoke-AwJsonPost -Uri "$apiBase/buckets/$bucketId/heartbeat?pulsetime=$pulse" -Json $payload
}
catch {
}
}
Start-Sleep -Seconds $sleepSec
}
; }
$body = @{
client = 'aw-worktime-session-collector'
type = 'aw.worktime.session'
hostname = $HostnameValue
} | ConvertTo-Json -Compress
try {
Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId" -Json $body
}
catch {
Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null
}
}
function Get-SessionRecords {
$records = @()
try {
$lines = quser 2>$null
if (-not $lines) {
return @()
}
foreach ($line in ($lines | Select-Object -Skip 1)) {
$clean = ($line -replace '^\s*>?', '').Trim()
if (-not $clean) {
continue
}
$parts = $clean -split '\s+'
if ($parts.Count -lt 4) {
continue
}
$sessionName = ''
$sessionIdIndex = 2
if ($parts[1] -match '^\d+$') {
$sessionIdIndex = 1
}
else {
$sessionName = $parts[1]
}
$sessionId = 0
if ($parts[$sessionIdIndex] -match '^\d+$') {
$sessionId = [int]$parts[$sessionIdIndex]
}
$records += [pscustomobject]@{
username = $parts[0]
sessionName = $sessionName
sessionId = $sessionId
state = $parts[$sessionIdIndex + 1]
}
}
}
catch { Write-Error param(
param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[string]$Hostname,
[int]$PollSeconds = 30
@@ -428,234 +164,3 @@ while ($true) {
Start-Sleep -Seconds $sleepSec
}
; }
return $records
}
function Test-SessionIsActive {
param([AllowNull()][string]$State)
if ([string]::IsNullOrWhiteSpace($State)) { return $false }
$s = $State.Trim().ToLowerInvariant()
return ($s -eq 'active') -or ($s -like 'актив*')
}
$cfg = Get-Config -Path $ConfigPath
$hostValue = if ($Hostname) { $Hostname } else { [string]$env:COMPUTERNAME }
$apiBase = '{0}://{1}:{2}/api/0' -f [string]$cfg.server.scheme, [string]$cfg.server.host, [string]$cfg.server.port
$bucketId = 'aw-worktime-sessions_' + $hostValue
$pulse = 120
$sleepSec = if ($PollSeconds -gt 0) {
$PollSeconds
}
elseif ($cfg.collector -and $cfg.collector.pollSeconds) {
[int]$cfg.collector.pollSeconds
}
else {
30
}
Ensure-Bucket -ApiBase $apiBase -BucketId $bucketId -HostnameValue $hostValue
while ($true) {
$now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
$records = Get-SessionRecords
if (-not $records -or $records.Count -eq 0) {
$records = @([pscustomobject]@{
username = $env:USERNAME
sessionName = ''
sessionId = (Get-Process -Id $PID).SessionId
state = 'Unknown'
})
}
foreach ($rec in $records) {
$payload = @{
timestamp = $now
duration = 0
data = @{
username = [string]$rec.username
userId = "$($env:USERDOMAIN)\$($rec.username)"
sessionId = [int]$rec.sessionId
sessionName = [string]$rec.sessionName
state = [string]$rec.state
active = (Test-SessionIsActive -State ([string]$rec.state))
hostname = $hostValue
source = 'worktime-session-collector'
}
} | ConvertTo-Json -Depth 6 -Compress
try {
Invoke-AwJsonPost -Uri "$apiBase/buckets/$bucketId/heartbeat?pulsetime=$pulse" -Json $payload
}
catch { Write-Error param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[string]$Hostname,
[int]$PollSeconds = 30
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-Config {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Конфигурация не найдена: $Path"
}
Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
}
function Invoke-AwJsonPost {
param(
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$Json
)
$bytes = [Text.Encoding]::UTF8.GetBytes($Json)
Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null
}
function Ensure-Bucket {
param(
[Parameter(Mandatory = $true)][string]$ApiBase,
[Parameter(Mandatory = $true)][string]$BucketId,
[Parameter(Mandatory = $true)][string]$HostnameValue
)
try {
Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null
return
}
catch {
}
$body = @{
client = 'aw-worktime-session-collector'
type = 'aw.worktime.session'
hostname = $HostnameValue
} | ConvertTo-Json -Compress
try {
Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId" -Json $body
}
catch {
Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null
}
}
function Get-SessionRecords {
$records = @()
try {
$lines = quser 2>$null
if (-not $lines) {
return @()
}
foreach ($line in ($lines | Select-Object -Skip 1)) {
$clean = ($line -replace '^\s*>?', '').Trim()
if (-not $clean) {
continue
}
$parts = $clean -split '\s+'
if ($parts.Count -lt 4) {
continue
}
$sessionName = ''
$sessionIdIndex = 2
if ($parts[1] -match '^\d+$') {
$sessionIdIndex = 1
}
else {
$sessionName = $parts[1]
}
$sessionId = 0
if ($parts[$sessionIdIndex] -match '^\d+$') {
$sessionId = [int]$parts[$sessionIdIndex]
}
$records += [pscustomobject]@{
username = $parts[0]
sessionName = $sessionName
sessionId = $sessionId
state = $parts[$sessionIdIndex + 1]
}
}
}
catch {
}
return $records
}
function Test-SessionIsActive {
param([AllowNull()][string]$State)
if ([string]::IsNullOrWhiteSpace($State)) { return $false }
$s = $State.Trim().ToLowerInvariant()
return ($s -eq 'active') -or ($s -like 'актив*')
}
$cfg = Get-Config -Path $ConfigPath
$hostValue = if ($Hostname) { $Hostname } else { [string]$env:COMPUTERNAME }
$apiBase = '{0}://{1}:{2}/api/0' -f [string]$cfg.server.scheme, [string]$cfg.server.host, [string]$cfg.server.port
$bucketId = 'aw-worktime-sessions_' + $hostValue
$pulse = 120
$sleepSec = if ($PollSeconds -gt 0) {
$PollSeconds
}
elseif ($cfg.collector -and $cfg.collector.pollSeconds) {
[int]$cfg.collector.pollSeconds
}
else {
30
}
Ensure-Bucket -ApiBase $apiBase -BucketId $bucketId -HostnameValue $hostValue
while ($true) {
$now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
$records = Get-SessionRecords
if (-not $records -or $records.Count -eq 0) {
$records = @([pscustomobject]@{
username = $env:USERNAME
sessionName = ''
sessionId = (Get-Process -Id $PID).SessionId
state = 'Unknown'
})
}
foreach ($rec in $records) {
$payload = @{
timestamp = $now
duration = 0
data = @{
username = [string]$rec.username
userId = "$($env:USERDOMAIN)\$($rec.username)"
sessionId = [int]$rec.sessionId
sessionName = [string]$rec.sessionName
state = [string]$rec.state
active = (Test-SessionIsActive -State ([string]$rec.state))
hostname = $hostValue
source = 'worktime-session-collector'
}
} | ConvertTo-Json -Depth 6 -Compress
try {
Invoke-AwJsonPost -Uri "$apiBase/buckets/$bucketId/heartbeat?pulsetime=$pulse" -Json $payload
}
catch {
}
}
Start-Sleep -Seconds $sleepSec
}
; }
}
Start-Sleep -Seconds $sleepSec
}