From a35a8a51bdffa0bbf18ee776e4c5c1f542410cbb Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 12:34:50 +0000 Subject: [PATCH] =?UTF-8?q?fix(print):=20detect=20generic=20doc=20names=20?= =?UTF-8?q?(=D0=9F=D0=B5=D1=87=D0=B0=D1=82=D1=8C=20=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0)=20+=20integrate=20email?= =?UTF-8?q?=20collector=20into=20deploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Print document naming: - Add Test-IsGenericDocumentName() to detect placeholder names like 'Печать документа', 'Print Document', 'Remote Downlevel Document', etc. - Add Test-NeedsBetterDocumentName() that combines mojibake, generic and numeric-only checks into a single gate for PrintService event log fallback - Replace Test-LooksLikeMojibakeQuestionMarks with Test-NeedsBetterDocumentName in Win32_PrintJob polling and Get-PrintServiceDocumentFallback Email collector deployment integration: - Ansible: add email-outbound-collector.ps1 to deploy_aw_windows.yml file list - Ansible: add aw_windows_email_collector_enabled/mode vars - InnoSetup: add email-outbound-collector.ps1 to [Files] section - Common module: add EmailCollectorScript to Copy-ActivityWatchCollectorAssets, New-ActivityWatchDeploymentConfig, launch-watchers script - Deploy scripts: pass EmailCollectorScript through pipeline - Deployment configs: add emailCollectorScript path - Sync all changes to install-kit copy Co-Authored-By: Fashion Lisa --- ansible/deploy_aw_windows.yml | 1 + ansible/group_vars/aw_windows.yml | 2 + .../MANIFEST.txt | 1 + .../ansible/deploy_aw_windows.yml | 1 + .../awatch-rus-admin.deployment-config.json | 1 + .../awatch-rus-u2u5.deployment-config.json | 1 + .../awatch-rus-user1.deployment-config.json | 1 + .../windows/ActivityWatch.Windows.Common.psm1 | 298 +++++++++++++++--- .../windows/deploy-domain-users.ps1 | 9 + .../windows/deploy-ensemble.ps1 | 4 + .../windows/deploy-single-user.ps1 | 3 + .../dlp-endpoint-signals-collector.ps1 | 41 ++- .../windows/hardening-recovery.ps1 | 10 + windows/ActivityWatch.Windows.Common.psm1 | 14 + windows/deploy-domain-users.ps1 | 3 + windows/deploy-single-user.ps1 | 3 + windows/dlp-endpoint-signals-collector.ps1 | 39 ++- windows/hardening-recovery.ps1 | 2 + .../innosetup/AWatch-rus-InnoSetup.iss | 1 + 19 files changed, 383 insertions(+), 52 deletions(-) diff --git a/ansible/deploy_aw_windows.yml b/ansible/deploy_aw_windows.yml index 92a352b..52ed6c2 100644 --- a/ansible/deploy_aw_windows.yml +++ b/ansible/deploy_aw_windows.yml @@ -78,6 +78,7 @@ - ActivityWatch.Windows.Common.psm1 - browser-domains-native-collector.ps1 - dlp-endpoint-signals-collector.ps1 + - email-outbound-collector.ps1 - file-operations-collector.ps1 - worktime-session-collector.ps1 - migrate-awatch-rus-paths.ps1 diff --git a/ansible/group_vars/aw_windows.yml b/ansible/group_vars/aw_windows.yml index 4ab2f5f..db4fa40 100644 --- a/ansible/group_vars/aw_windows.yml +++ b/ansible/group_vars/aw_windows.yml @@ -28,6 +28,8 @@ aw_windows_state_root: "C:\\ProgramData\\AWatch-rus" aw_windows_afk_enabled: true aw_windows_window_enabled: true aw_windows_file_ops_enabled: true +aw_windows_email_collector_enabled: true +aw_windows_email_collector_mode: "both" aw_windows_local_agent_logs_enabled: false aw_windows_incident_capture_enabled: true aw_windows_incident_screenshot_enabled: true diff --git a/install-kit-awindows-20260427-211240/MANIFEST.txt b/install-kit-awindows-20260427-211240/MANIFEST.txt index 4f5c2f0..253acb5 100644 --- a/install-kit-awindows-20260427-211240/MANIFEST.txt +++ b/install-kit-awindows-20260427-211240/MANIFEST.txt @@ -32,6 +32,7 @@ b497400a1ba57cddf28dc8e217115dc85eccb67150cbdbb6a81abd804ed20109 install-kit-aw 973db51854fc744539a7b75e13c6749e822a08a79f47447485611f07e8f902a8 install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 0d66dcb551889e6b7bc21b29d53b77e46f41d61dd2e4e0d9913dbf0f8bd5eb18 install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 8857f3e17f3f3ed6f211ce7f0a0c46c586a2548541078401ee3befaa20924b3d install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 +7362e1aecb56d8863b8b2542f262f28827febf9b25a2a14b382750dc6368664f install-kit-awindows-20260427-211240/windows/email-outbound-collector.ps1 aef0032edd9b1e0c54f7b575664ed511dfc6cb53364e7496cbc95e137678e11a install-kit-awindows-20260427-211240/windows/dlp-policy.example.json f03886caf56c6838e8a163d6b48d1f229e83a5682aeeb447c3a65f13d62dbca4 install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 71911cd53ad0abd8bf83994f8a79bbbfe2c0eaf4f4c5f6c2d636dd7786191c2f install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1 diff --git a/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml b/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml index 5d18277..c78830c 100644 --- a/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml +++ b/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml @@ -77,6 +77,7 @@ - ActivityWatch.Windows.Common.psm1 - browser-domains-native-collector.ps1 - dlp-endpoint-signals-collector.ps1 + - email-outbound-collector.ps1 - worktime-session-collector.ps1 - migrate-awatch-rus-paths.ps1 - deploy-domain-users.ps1 diff --git a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-admin.deployment-config.json b/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-admin.deployment-config.json index 2899c58..188bc41 100644 --- a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-admin.deployment-config.json +++ b/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-admin.deployment-config.json @@ -12,6 +12,7 @@ "logsRoot": "C:\\ProgramData\\AWatch-rus\\logs", "collectorScript": "C:\\ProgramData\\AWatch-rus\\browser-domains-native-collector.ps1", "endpointCollectorScript": "C:\\ProgramData\\AWatch-rus\\dlp-endpoint-signals-collector.ps1", + "emailCollectorScript": "C:\\ProgramData\\AWatch-rus\\email-outbound-collector.ps1", "rulesPath": "C:\\ProgramData\\AWatch-rus\\web-category-rules.json", "policyPath": "C:\\ProgramData\\AWatch-rus\\dlp-policy.json", "launchScript": "C:\\ProgramData\\AWatch-rus\\launch-watchers.ps1", diff --git a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-u2u5.deployment-config.json b/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-u2u5.deployment-config.json index cdac8f6..be320bf 100644 --- a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-u2u5.deployment-config.json +++ b/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-u2u5.deployment-config.json @@ -12,6 +12,7 @@ "logsRoot": "C:\\ProgramData\\AWatch-rus\\logs", "collectorScript": "C:\\ProgramData\\AWatch-rus\\browser-domains-native-collector.ps1", "endpointCollectorScript": "C:\\ProgramData\\AWatch-rus\\dlp-endpoint-signals-collector.ps1", + "emailCollectorScript": "C:\\ProgramData\\AWatch-rus\\email-outbound-collector.ps1", "rulesPath": "C:\\ProgramData\\AWatch-rus\\web-category-rules.json", "policyPath": "C:\\ProgramData\\AWatch-rus\\dlp-policy.json", "launchScript": "C:\\ProgramData\\AWatch-rus\\launch-watchers.ps1", diff --git a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-user1.deployment-config.json b/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-user1.deployment-config.json index 8a42851..0c2e4b0 100644 --- a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-user1.deployment-config.json +++ b/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-user1.deployment-config.json @@ -12,6 +12,7 @@ "logsRoot": "C:\\ProgramData\\AWatch-rus\\logs", "collectorScript": "C:\\ProgramData\\AWatch-rus\\browser-domains-native-collector.ps1", "endpointCollectorScript": "C:\\ProgramData\\AWatch-rus\\dlp-endpoint-signals-collector.ps1", + "emailCollectorScript": "C:\\ProgramData\\AWatch-rus\\email-outbound-collector.ps1", "rulesPath": "C:\\ProgramData\\AWatch-rus\\web-category-rules.json", "policyPath": "C:\\ProgramData\\AWatch-rus\\dlp-policy.json", "launchScript": "C:\\ProgramData\\AWatch-rus\\launch-watchers.ps1", diff --git a/install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 b/install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 index 9c9002c..e3a30b5 100755 --- a/install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 +++ b/install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 @@ -20,6 +20,17 @@ function New-ActivityWatchDirectory { } } +function Enable-ActivityWatchPrintTelemetry { + $policyPath = 'HKLM:\Software\Policies\Microsoft\Windows NT\Printers' + if (-not (Test-Path -LiteralPath $policyPath)) { + New-Item -Path $policyPath -Force | Out-Null + } + New-ItemProperty -Path $policyPath -Name 'ShowJobTitleInEventLogs' -Value 1 -PropertyType DWord -Force | Out-Null + + & wevtutil.exe sl 'Microsoft-Windows-PrintService/Operational' /e:true | Out-Null +} + + function Get-ActivityWatchPackageUrl { param( [string]$Version = 'v0.13.2' @@ -49,7 +60,9 @@ function Get-ActivityWatchArchive { } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - $archivePath = Join-Path $WorkingRoot ("activitywatch-{0}.zip" -f $Version.TrimStart('v')) + $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' + $suffix = ([guid]::NewGuid().Guid.Substring(0, 8)) + $archivePath = Join-Path $WorkingRoot ("activitywatch-{0}-{1}-{2}.zip" -f $Version.TrimStart('v'), $stamp, $suffix) Invoke-WebRequest -Uri $PackageUrl -OutFile $archivePath return $archivePath } @@ -85,6 +98,16 @@ function Install-ActivityWatchPackage { New-ActivityWatchDirectory -Path $WorkingRoot New-ActivityWatchDirectory -Path $BackupRoot + # Ensure nothing is holding locks inside InstallRoot during upgrade. + foreach ($procName in @('aw-watcher-afk', 'aw-watcher-window', 'aw-server', 'aw-qt')) { + try { + Get-Process -Name $procName -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue + } + catch { + } + } + Start-Sleep -Seconds 2 + $extractRoot = Join-Path $WorkingRoot ('extract-' + [guid]::NewGuid().Guid) if (Test-Path -LiteralPath $extractRoot) { Remove-Item -LiteralPath $extractRoot -Recurse -Force @@ -243,7 +266,10 @@ function Copy-ActivityWatchCollectorAssets { [Parameter(Mandatory = $true)] [string]$EndpointCollectorScriptSource, [Parameter(Mandatory = $true)] + [string]$FileCollectorScriptSource, + [Parameter(Mandatory = $true)] [string]$SessionCollectorScriptSource, + [string]$EmailCollectorScriptSource, [Parameter(Mandatory = $true)] [string]$ExampleRulesSource, [Parameter(Mandatory = $true)] @@ -258,7 +284,9 @@ function Copy-ActivityWatchCollectorAssets { $collectorTarget = Join-Path $StateRoot 'browser-domains-native-collector.ps1' $endpointCollectorTarget = Join-Path $StateRoot 'dlp-endpoint-signals-collector.ps1' + $fileCollectorTarget = Join-Path $StateRoot 'file-operations-collector.ps1' $sessionCollectorTarget = Join-Path $StateRoot 'worktime-session-collector.ps1' + $emailCollectorTarget = Join-Path $StateRoot 'email-outbound-collector.ps1' $exampleRulesTarget = Join-Path $StateRoot 'web-category-rules.example.json' $rulesTarget = Join-Path $StateRoot 'web-category-rules.json' $examplePolicyTarget = Join-Path $StateRoot 'dlp-policy.example.json' @@ -266,7 +294,11 @@ function Copy-ActivityWatchCollectorAssets { Copy-Item -LiteralPath $CollectorScriptSource -Destination $collectorTarget -Force Copy-Item -LiteralPath $EndpointCollectorScriptSource -Destination $endpointCollectorTarget -Force + Copy-Item -LiteralPath $FileCollectorScriptSource -Destination $fileCollectorTarget -Force Copy-Item -LiteralPath $SessionCollectorScriptSource -Destination $sessionCollectorTarget -Force + if ($EmailCollectorScriptSource -and (Test-Path -LiteralPath $EmailCollectorScriptSource)) { + Copy-Item -LiteralPath $EmailCollectorScriptSource -Destination $emailCollectorTarget -Force + } Copy-Item -LiteralPath $ExampleRulesSource -Destination $exampleRulesTarget -Force Copy-Item -LiteralPath $ExamplePolicySource -Destination $examplePolicyTarget -Force @@ -286,7 +318,9 @@ function Copy-ActivityWatchCollectorAssets { return [pscustomobject]@{ CollectorScript = $collectorTarget EndpointCollectorScript = $endpointCollectorTarget + FileCollectorScript = $fileCollectorTarget SessionCollectorScript = $sessionCollectorTarget + EmailCollectorScript = $emailCollectorTarget ExampleRules = $exampleRulesTarget ActiveRules = $rulesTarget ExamplePolicy = $examplePolicyTarget @@ -313,7 +347,10 @@ function New-ActivityWatchDeploymentConfig { [Parameter(Mandatory = $true)] [string]$EndpointCollectorScript, [Parameter(Mandatory = $true)] + [string]$FileCollectorScript, + [Parameter(Mandatory = $true)] [string]$SessionCollectorScript, + [string]$EmailCollectorScript, [Parameter(Mandatory = $true)] [string]$RulesPath, [Parameter(Mandatory = $true)] @@ -326,6 +363,7 @@ function New-ActivityWatchDeploymentConfig { [int]$RecoveryIntervalSeconds, [bool]$AfkEnabled = $true, [bool]$WindowEnabled = $true, + [bool]$FileOpsEnabled = $true, [bool]$LocalAgentLogsEnabled = $true, [bool]$IncidentCaptureEnabled = $true, [bool]$IncidentScreenshotEnabled = $true, @@ -356,6 +394,8 @@ function New-ActivityWatchDeploymentConfig { logsRoot = $LogsRoot collectorScript = $CollectorScript endpointCollectorScript = $EndpointCollectorScript + emailCollectorScript = $EmailCollectorScript + fileCollectorScript = $FileCollectorScript sessionCollectorScript = $SessionCollectorScript rulesPath = $RulesPath policyPath = $PolicyPath @@ -369,6 +409,8 @@ function New-ActivityWatchDeploymentConfig { collectors = [pscustomobject]@{ afkEnabled = $AfkEnabled windowEnabled = $WindowEnabled + fileOpsEnabled = $FileOpsEnabled + emailEnabled = ($null -ne $EmailCollectorScript -and $EmailCollectorScript -ne '') } logging = [pscustomobject]@{ localAgentLogsEnabled = $LocalAgentLogsEnabled @@ -443,6 +485,10 @@ param( Set-StrictMode -Version Latest `$ErrorActionPreference = 'Stop' +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 +Add-Type -AssemblyName System.Net.Http +`$script:MaxCollectorPowerShellProcesses = 24 + function Get-DeploymentConfig { param([string]`$Path) return Get-Content -LiteralPath `$Path -Raw | ConvertFrom-Json @@ -474,14 +520,67 @@ function Test-CollectorRunning { return [bool](`$processes | Select-Object -First 1) } +function Get-CollectorPowerShellProcessCount { + `$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | + Where-Object { + (`$_.Name -ieq 'powershell.exe' -or `$_.Name -ieq 'pwsh.exe') -and + `$_.CommandLine -match 'AWatch-rus' -and + `$_.CommandLine -match '\.ps1' + } + + return @(`$processes).Count +} + +function New-LaunchLock { + param([string]`$StateRoot, [int]`$SessionId) + + if (-not (Test-Path -LiteralPath `$StateRoot)) { + New-Item -Path `$StateRoot -ItemType Directory -Force | Out-Null + } + + `$lockPath = Join-Path `$StateRoot ("launch-watchers-session-{0}.lock" -f `$SessionId) + if (Test-Path -LiteralPath `$lockPath) { + try { + `$lockData = Get-Content -LiteralPath `$lockPath -Raw | ConvertFrom-Json + `$existingPid = [int]`$lockData.pid + if (`$existingPid -gt 0 -and (Get-Process -Id `$existingPid -ErrorAction SilentlyContinue)) { + return `$null + } + } + catch { + } + } + + `$payload = @{ + pid = `$PID + sessionId = `$SessionId + createdAt = (Get-Date).ToUniversalTime().ToString('o') + } | ConvertTo-Json -Compress + Set-Content -LiteralPath `$lockPath -Value `$payload -Encoding UTF8 + return `$lockPath +} + 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 + `$httpClient = New-Object System.Net.Http.HttpClient + try { + `$content = New-Object System.Net.Http.StringContent(`$Json, [System.Text.Encoding]::UTF8, 'application/json') + `$response = `$httpClient.PostAsync(`$Uri, `$content).Result + if (-not `$response.IsSuccessStatusCode) { + return `$false + } + return `$true + } + catch { + return `$false + } + finally { + `$httpClient.Dispose() + } } function Ensure-Bucket { @@ -510,7 +609,9 @@ function Ensure-Bucket { } | ConvertTo-Json -Compress try { - Invoke-AwJsonPost -Uri "`$(`$script:ApiBase)/buckets/`$BucketId" -Json `$body + if (-not (Invoke-AwJsonPost -Uri "`$(`$script:ApiBase)/buckets/`$BucketId" -Json `$body)) { + return + } } catch { try { @@ -614,6 +715,10 @@ function Start-CollectorScriptIfNeeded { [int]`$SessionId ) + if ([string]::IsNullOrWhiteSpace(`$ScriptPath)) { + return + } + if (-not (Test-Path -LiteralPath `$ScriptPath)) { return } @@ -622,6 +727,10 @@ function Start-CollectorScriptIfNeeded { return } + if ((Get-CollectorPowerShellProcessCount) -ge `$script:MaxCollectorPowerShellProcesses) { + return + } + Start-Process -FilePath `$PowerShellExe -ArgumentList @( '-NoProfile', '-WindowStyle', 'Hidden', @@ -634,43 +743,65 @@ function Start-CollectorScriptIfNeeded { `$config = Get-DeploymentConfig -Path `$ConfigPath `$sessionId = (Get-Process -Id `$PID).SessionId `$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 = `$env:COMPUTERNAME `$script:KnownBuckets = @{} `$collectorScript = [string]`$config.paths.collectorScript -`$endpointCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]`$config.paths.endpointCollectorScript } else { '' } -`$sessionCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]`$config.paths.sessionCollectorScript } else { '' } +`$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' } `$afkExe = Join-Path `$installRoot 'aw-watcher-afk\aw-watcher-afk.exe' `$windowExe = Join-Path `$installRoot 'aw-watcher-window\aw-watcher-window.exe' `$serverArgs = @('--host', [string]`$config.server.host, '--port', [string]`$config.server.port) `$powershellExe = Join-Path `$env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' `$afkEnabled = if (`$config.PSObject.Properties.Name -contains 'collectors' -and `$config.collectors.PSObject.Properties.Name -contains 'afkEnabled') { [bool]`$config.collectors.afkEnabled } else { `$true } `$windowEnabled = if (`$config.PSObject.Properties.Name -contains 'collectors' -and `$config.collectors.PSObject.Properties.Name -contains 'windowEnabled') { [bool]`$config.collectors.windowEnabled } else { `$true } - -if (`$afkEnabled -and -not (Test-Path -LiteralPath `$afkExe)) { - throw "Не найден aw-watcher-afk.exe: `$afkExe" -} - -if (`$windowEnabled -and -not (Test-Path -LiteralPath `$windowExe)) { - throw "Не найден aw-watcher-window.exe: `$windowExe" -} - -if (`$afkEnabled -and -not (Test-ProcessInSession -Name 'aw-watcher-afk' -SessionId `$sessionId)) { - Start-Process -FilePath `$afkExe -ArgumentList `$serverArgs -WindowStyle Hidden -} - -if (`$windowEnabled -and -not (Test-ProcessInSession -Name 'aw-watcher-window' -SessionId `$sessionId)) { - Start-Process -FilePath `$windowExe -ArgumentList `$serverArgs -WindowStyle Hidden +`$fileOpsEnabled = if (`$config.PSObject.Properties.Name -contains 'collectors' -and `$config.collectors.PSObject.Properties.Name -contains 'fileOpsEnabled') { [bool]`$config.collectors.fileOpsEnabled } else { `$true } +`$emailEnabled = if (`$config.PSObject.Properties.Name -contains 'collectors' -and `$config.collectors.PSObject.Properties.Name -contains 'emailEnabled') { [bool]`$config.collectors.emailEnabled } else { `$false } +`$emailCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'emailCollectorScript') { [string]`$config.paths.emailCollectorScript } else { Join-Path `$stateRoot 'email-outbound-collector.ps1' } +`$launchLockPath = New-LaunchLock -StateRoot `$stateRoot -SessionId `$sessionId +if (-not `$launchLockPath) { + return } try { - Send-LogonMarkerIfNeeded -Config `$config -SessionId `$sessionId + if (`$afkEnabled -and -not (Test-Path -LiteralPath `$afkExe)) { + throw "Не найден aw-watcher-afk.exe: `$afkExe" + } + + if (`$windowEnabled -and -not (Test-Path -LiteralPath `$windowExe)) { + throw "Не найден aw-watcher-window.exe: `$windowExe" + } + + if (`$afkEnabled -and -not (Test-ProcessInSession -Name 'aw-watcher-afk' -SessionId `$sessionId)) { + Start-Process -FilePath `$afkExe -ArgumentList `$serverArgs -WindowStyle Hidden + } + + if (`$windowEnabled -and -not (Test-ProcessInSession -Name 'aw-watcher-window' -SessionId `$sessionId)) { + Start-Process -FilePath `$windowExe -ArgumentList `$serverArgs -WindowStyle Hidden + } + + try { + Send-LogonMarkerIfNeeded -Config `$config -SessionId `$sessionId + } + catch { + } + Start-CollectorScriptIfNeeded -ScriptPath `$collectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId + Start-CollectorScriptIfNeeded -ScriptPath `$endpointCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId + if (`$fileOpsEnabled) { + Start-CollectorScriptIfNeeded -ScriptPath `$fileCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId + } + Start-CollectorScriptIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId + if (`$emailEnabled -and (Test-Path -LiteralPath `$emailCollectorScript)) { + Start-CollectorScriptIfNeeded -ScriptPath `$emailCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId + } } -catch { +finally { + if (`$launchLockPath -and (Test-Path -LiteralPath `$launchLockPath)) { + Remove-Item -LiteralPath `$launchLockPath -Force -ErrorAction SilentlyContinue + } } -Start-CollectorScriptIfNeeded -ScriptPath `$collectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId -Start-CollectorScriptIfNeeded -ScriptPath `$endpointCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId -Start-CollectorScriptIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId "@ Set-Content -LiteralPath $Path -Value $content -Encoding UTF8 @@ -749,23 +880,84 @@ function Get-RecoveryTaskNames { return @(`$taskNames) } -while (`$true) { - `$sleepSeconds = 180 - try { - `$configPaths = Get-RecoveryConfigPaths -PrimaryConfigPath `$ConfigPath - foreach (`$taskName in Get-RecoveryTaskNames -ConfigPaths `$configPaths) { - Start-ScheduledTask -TaskName `$taskName -ErrorAction SilentlyContinue - } +function New-RecoveryLock { + param([string]`$PrimaryConfigPath) - `$config = Get-DeploymentConfig -Path `$ConfigPath - if (`$config -and `$config.recovery -and `$config.recovery.intervalSeconds) { - `$sleepSeconds = [Math]::Max([int]`$config.recovery.intervalSeconds, 30) + `$stateRoot = if (`$PrimaryConfigPath) { Split-Path -Path `$PrimaryConfigPath -Parent } else { Join-Path `$env:ProgramData 'AWatch-rus' } + if (-not (Test-Path -LiteralPath `$stateRoot)) { + New-Item -Path `$stateRoot -ItemType Directory -Force | Out-Null + } + + `$lockPath = Join-Path `$stateRoot 'recovery-loop.lock' + if (Test-Path -LiteralPath `$lockPath) { + try { + `$lockData = Get-Content -LiteralPath `$lockPath -Raw | ConvertFrom-Json + `$existingPid = [int]`$lockData.pid + if (`$existingPid -gt 0 -and (Get-Process -Id `$existingPid -ErrorAction SilentlyContinue)) { + return `$null + } } + catch { + } + } + + `$payload = @{ + pid = `$PID + createdAt = (Get-Date).ToUniversalTime().ToString('o') + } | ConvertTo-Json -Compress + Set-Content -LiteralPath `$lockPath -Value `$payload -Encoding UTF8 + return `$lockPath +} + +function Start-TaskIfNotRunning { + param([string]`$TaskName) + if ([string]::IsNullOrWhiteSpace(`$TaskName)) { + return + } + + try { + `$task = Get-ScheduledTask -TaskName `$TaskName -ErrorAction SilentlyContinue + if (-not `$task) { + return + } + if ([string]`$task.State -eq 'Running') { + return + } + Start-ScheduledTask -TaskName `$TaskName -ErrorAction SilentlyContinue } catch { } +} - Start-Sleep -Seconds `$sleepSeconds +`$recoveryLockPath = New-RecoveryLock -PrimaryConfigPath `$ConfigPath +if (-not `$recoveryLockPath) { + return +} + +try { + while (`$true) { + `$sleepSeconds = 180 + try { + `$configPaths = Get-RecoveryConfigPaths -PrimaryConfigPath `$ConfigPath + foreach (`$taskName in Get-RecoveryTaskNames -ConfigPaths `$configPaths) { + Start-TaskIfNotRunning -TaskName `$taskName + } + + `$config = Get-DeploymentConfig -Path `$ConfigPath + if (`$config -and `$config.recovery -and `$config.recovery.intervalSeconds) { + `$sleepSeconds = [Math]::Max([int]`$config.recovery.intervalSeconds, 30) + } + } + catch { + } + + Start-Sleep -Seconds `$sleepSeconds + } +} +finally { + if (`$recoveryLockPath -and (Test-Path -LiteralPath `$recoveryLockPath)) { + Remove-Item -LiteralPath `$recoveryLockPath -Force -ErrorAction SilentlyContinue + } } "@ @@ -891,6 +1083,37 @@ function Get-ActivityWatchScheduledTaskByCommand { return $null } +function Remove-StaleActivityWatchUserTasks { + param( + [Parameter(Mandatory = $true)] + [pscustomobject[]]$TaskDefinitions, + [Parameter(Mandatory = $true)] + [string]$LaunchScriptPath + ) + + $launcherPath = Get-ActivityWatchHiddenLauncherPath -ScriptPath $LaunchScriptPath + $desiredTaskNames = @($TaskDefinitions | ForEach-Object { [string]$_.LaunchTaskName }) + + foreach ($candidate in @(Get-ScheduledTask | Where-Object { $_.TaskName -like 'ActivityWatch Launch*' })) { + $taskName = [string]$candidate.TaskName + if ($desiredTaskNames -contains $taskName) { + continue + } + + $usesCurrentLauncher = $false + foreach ($action in @($candidate.Actions)) { + if ([string]$action.Arguments -like "*$launcherPath*") { + $usesCurrentLauncher = $true + break + } + } + + if ($usesCurrentLauncher) { + Remove-ActivityWatchScheduledTask -TaskName $taskName + } + } +} + function Register-ActivityWatchUserTasks { param( [Parameter(Mandatory = $true)] @@ -904,6 +1127,7 @@ function Register-ActivityWatchUserTasks { $wscriptExe = Join-Path $env:SystemRoot 'System32\wscript.exe' $launcherPath = Get-ActivityWatchHiddenLauncherPath -ScriptPath $LaunchScriptPath Write-ActivityWatchHiddenPowerShellWrapper -Path $launcherPath -ScriptPath $LaunchScriptPath -ConfigPath $ConfigPath + Remove-StaleActivityWatchUserTasks -TaskDefinitions $TaskDefinitions -LaunchScriptPath $LaunchScriptPath foreach ($definition in $TaskDefinitions) { $action = New-ScheduledTaskAction -Execute $wscriptExe -Argument "//B //NoLogo `"$launcherPath`"" diff --git a/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 b/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 index 8518ba5..92e5494 100755 --- a/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 +++ b/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 @@ -18,6 +18,7 @@ param( [int]$RecoveryIntervalSeconds = 180, [bool]$AfkEnabled = $true, [bool]$WindowEnabled = $true, + [bool]$FileOpsEnabled = $true, [bool]$LocalAgentLogsEnabled = $false, [bool]$IncidentCaptureEnabled = $true, [bool]$IncidentScreenshotEnabled = $true, @@ -44,12 +45,15 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1' $recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1' $collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1' $endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1' +$emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1' +$fileCollectorSource = Join-Path $PSScriptRoot 'file-operations-collector.ps1' $sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1' $exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json' $examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json' New-ActivityWatchDirectory -Path $StateRoot New-ActivityWatchDirectory -Path $logsRoot +Enable-ActivityWatchPrintTelemetry $archivePath = Get-ActivityWatchArchive -PackageZipPath $PackageZipPath -PackageUrl $PackageUrl -Version $Version -WorkingRoot $workingRoot Install-ActivityWatchPackage -ArchivePath $archivePath -InstallRoot $InstallRoot -WorkingRoot $workingRoot -BackupRoot $backupRoot | Out-Null @@ -58,6 +62,8 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null $assetResult = Copy-ActivityWatchCollectorAssets ` -CollectorScriptSource $collectorSource ` -EndpointCollectorScriptSource $endpointCollectorSource ` + -EmailCollectorScriptSource $emailCollectorSource ` + -FileCollectorScriptSource $fileCollectorSource ` -SessionCollectorScriptSource $sessionCollectorSource ` -ExampleRulesSource $exampleRulesSource ` -ExamplePolicySource $examplePolicySource ` @@ -78,6 +84,8 @@ $config = New-ActivityWatchDeploymentConfig ` -LogsRoot $logsRoot ` -CollectorScript $assetResult.CollectorScript ` -EndpointCollectorScript $assetResult.EndpointCollectorScript ` + -EmailCollectorScript $assetResult.EmailCollectorScript ` + -FileCollectorScript $assetResult.FileCollectorScript ` -SessionCollectorScript $assetResult.SessionCollectorScript ` -RulesPath $assetResult.ActiveRules ` -PolicyPath $assetResult.ActivePolicy ` @@ -86,6 +94,7 @@ $config = New-ActivityWatchDeploymentConfig ` -RecoveryIntervalSeconds $RecoveryIntervalSeconds ` -AfkEnabled $AfkEnabled ` -WindowEnabled $WindowEnabled ` + -FileOpsEnabled $FileOpsEnabled ` -LocalAgentLogsEnabled $LocalAgentLogsEnabled ` -IncidentCaptureEnabled $IncidentCaptureEnabled ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` diff --git a/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 b/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 index 24c873a..fa6fd75 100644 --- a/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 +++ b/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 @@ -18,6 +18,7 @@ param( [int]$RecoveryIntervalSeconds = 180, [bool]$AfkEnabled = $true, [bool]$WindowEnabled = $true, + [bool]$FileOpsEnabled = $true, [bool]$LocalAgentLogsEnabled = $false, [bool]$IncidentCaptureEnabled = $true, [bool]$IncidentScreenshotEnabled = $true, @@ -64,6 +65,7 @@ if (-not (Test-Path -LiteralPath $deployScript)) { -RecoveryIntervalSeconds $RecoveryIntervalSeconds ` -AfkEnabled $AfkEnabled ` -WindowEnabled $WindowEnabled ` + -FileOpsEnabled $FileOpsEnabled ` -LocalAgentLogsEnabled $LocalAgentLogsEnabled ` -IncidentCaptureEnabled $IncidentCaptureEnabled ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` @@ -86,6 +88,7 @@ if (-not $SkipHardening) { -RecoveryIntervalSeconds $RecoveryIntervalSeconds ` -AfkEnabled $AfkEnabled ` -WindowEnabled $WindowEnabled ` + -FileOpsEnabled $FileOpsEnabled ` -LocalAgentLogsEnabled $LocalAgentLogsEnabled ` -IncidentCaptureEnabled $IncidentCaptureEnabled ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` @@ -112,6 +115,7 @@ $report = [ordered]@{ collectors = [ordered]@{ afkEnabled = $AfkEnabled windowEnabled = $WindowEnabled + fileOpsEnabled = $FileOpsEnabled } hardeningApplied = (-not $SkipHardening) } diff --git a/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 b/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 index 9b035e4..160265d 100755 --- a/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 +++ b/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 @@ -42,6 +42,7 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1' $recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1' $collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1' $endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1' +$emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1' $sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1' $exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json' $examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json' @@ -56,6 +57,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null $assetResult = Copy-ActivityWatchCollectorAssets ` -CollectorScriptSource $collectorSource ` -EndpointCollectorScriptSource $endpointCollectorSource ` + -EmailCollectorScriptSource $emailCollectorSource ` -SessionCollectorScriptSource $sessionCollectorSource ` -ExampleRulesSource $exampleRulesSource ` -ExamplePolicySource $examplePolicySource ` @@ -76,6 +78,7 @@ $config = New-ActivityWatchDeploymentConfig ` -LogsRoot $logsRoot ` -CollectorScript $assetResult.CollectorScript ` -EndpointCollectorScript $assetResult.EndpointCollectorScript ` + -EmailCollectorScript $assetResult.EmailCollectorScript ` -SessionCollectorScript $assetResult.SessionCollectorScript ` -RulesPath $assetResult.ActiveRules ` -PolicyPath $assetResult.ActivePolicy ` diff --git a/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 b/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 index de84fdf..8dd67bd 100644 --- a/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 +++ b/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 @@ -1,4 +1,4 @@ -[CmdletBinding()] +[CmdletBinding()] param( [string]$ConfigPath = 'C:\ProgramData\ActivityWatch\deployment-config.json', [string]$ServerHost, @@ -516,6 +516,35 @@ function Test-LooksLikeMojibakeQuestionMarks { return $Value -match '\?{2,}' } +function Test-IsGenericDocumentName { + param([AllowNull()][string]$Value) + if ([string]::IsNullOrWhiteSpace($Value)) { return $true } + $generic = @( + '^\s*Печать документа\s*$', + '^\s*Print Document\s*$', + '^\s*Document\s*$', + '^\s*Документ\s*$', + '^\s*Remote Downlevel Document\s*$', + '^\s*Local Downlevel Document\s*$', + '^\s*Untitled\s*$', + '^\s*Без имени\s*$', + '^\s*Без названия\s*$' + ) + foreach ($pattern in $generic) { + if ($Value -match $pattern) { return $true } + } + return $false +} + +function Test-NeedsBetterDocumentName { + param([AllowNull()][string]$Value) + if ([string]::IsNullOrWhiteSpace($Value)) { return $true } + if (Test-LooksLikeMojibakeQuestionMarks -Value $Value) { return $true } + if (Test-IsGenericDocumentName -Value $Value) { return $true } + if ($Value -match '^[0-9]+$') { return $true } + return $false +} + function Normalize-OwnerForMatch { param([AllowNull()][string]$Value) if ([string]::IsNullOrWhiteSpace($Value)) { return '' } @@ -602,7 +631,7 @@ function Get-PrintServiceDocumentFallback { ) $preferred = [string]$EventSummary.DocumentName - if (-not (Test-LooksLikeMojibakeQuestionMarks -Value $preferred) -and $preferred -notmatch '^[0-9]+$') { + if (-not (Test-NeedsBetterDocumentName -Value $preferred)) { return $preferred } @@ -615,17 +644,13 @@ function Get-PrintServiceDocumentFallback { if ($candidate -eq $preferred) { continue } if ($Owner -and $candidate -like "*$Owner*") { continue } if ($PrinterName -and $candidate -like "*$PrinterName*") { continue } - if (Test-LooksLikeMojibakeQuestionMarks -Value $candidate) { continue } + if (Test-NeedsBetterDocumentName -Value $candidate) { continue } if ($candidate -match '[\\/:]' -and $candidate -match '\.[A-Za-z0-9]{1,8}$') { $pathCandidates.Add($candidate) continue } - if ($candidate -match '^[0-9]+$') { - continue - } - $textCandidates.Add($candidate) } @@ -828,7 +853,7 @@ while ($true) { $owner = [string]$job.Owner $documentNameOriginal = $documentName - if (Test-LooksLikeMojibakeQuestionMarks -Value $documentName) { + if (Test-NeedsBetterDocumentName -Value $documentName) { $eventDocumentName = Get-BetterDocumentNameFromPrintServiceEvents -Owner $owner -PrinterName $printerName if ($eventDocumentName) { $documentName = $eventDocumentName diff --git a/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 b/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 index 64e8d73..d382f41 100755 --- a/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 +++ b/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 @@ -15,6 +15,7 @@ param( [int]$RecoveryIntervalSeconds, [bool]$AfkEnabled, [bool]$WindowEnabled, + [bool]$FileOpsEnabled, [bool]$LocalAgentLogsEnabled, [bool]$IncidentCaptureEnabled, [bool]$IncidentScreenshotEnabled, @@ -53,6 +54,8 @@ $effectiveLaunchScript = Join-Path $effectiveStateRoot 'launch-watchers.ps1' $effectiveRecoveryScript = Join-Path $effectiveStateRoot 'recovery-loop.ps1' $effectiveCollector = Join-Path $effectiveStateRoot 'browser-domains-native-collector.ps1' $effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$existingConfig.paths.endpointCollectorScript } else { Join-Path $effectiveStateRoot 'dlp-endpoint-signals-collector.ps1' } +$effectiveFileCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'fileCollectorScript') { [string]$existingConfig.paths.fileCollectorScript } else { Join-Path $effectiveStateRoot 'file-operations-collector.ps1' } +$effectiveSessionCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$existingConfig.paths.sessionCollectorScript } else { Join-Path $effectiveStateRoot 'worktime-session-collector.ps1' } $effectiveRules = Join-Path $effectiveStateRoot 'web-category-rules.json' $effectivePolicy = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$existingConfig.paths.policyPath } else { Join-Path $effectiveStateRoot 'dlp-policy.json' } @@ -64,6 +67,7 @@ $effectivePulseSeconds = if ($PSBoundParameters.ContainsKey('PulseSeconds')) { $ $effectiveRecoveryInterval = if ($PSBoundParameters.ContainsKey('RecoveryIntervalSeconds')) { $RecoveryIntervalSeconds } elseif ($existingConfig) { [int]$existingConfig.recovery.intervalSeconds } else { 180 } $effectiveAfkEnabled = if ($PSBoundParameters.ContainsKey('AfkEnabled')) { [bool]$AfkEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'collectors' -and $existingConfig.collectors.PSObject.Properties.Name -contains 'afkEnabled') { [bool]$existingConfig.collectors.afkEnabled } else { $true } $effectiveWindowEnabled = if ($PSBoundParameters.ContainsKey('WindowEnabled')) { [bool]$WindowEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'collectors' -and $existingConfig.collectors.PSObject.Properties.Name -contains 'windowEnabled') { [bool]$existingConfig.collectors.windowEnabled } else { $true } +$effectiveFileOpsEnabled = if ($PSBoundParameters.ContainsKey('FileOpsEnabled')) { [bool]$FileOpsEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'collectors' -and $existingConfig.collectors.PSObject.Properties.Name -contains 'fileOpsEnabled') { [bool]$existingConfig.collectors.fileOpsEnabled } else { $true } $effectiveLocalAgentLogsEnabled = if ($PSBoundParameters.ContainsKey('LocalAgentLogsEnabled')) { [bool]$LocalAgentLogsEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'logging' -and $existingConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$existingConfig.logging.localAgentLogsEnabled } else { $false } $effectiveIncidentCaptureEnabled = if ($PSBoundParameters.ContainsKey('IncidentCaptureEnabled')) { [bool]$IncidentCaptureEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'enabled') { [bool]$existingConfig.incidentCapture.enabled } else { $true } $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 } @@ -83,6 +87,7 @@ else { New-ActivityWatchDirectory -Path $effectiveStateRoot New-ActivityWatchDirectory -Path $effectiveLogsRoot +Enable-ActivityWatchPrintTelemetry if ($RepairPackage) { $workingRoot = Join-Path $env:TEMP 'activitywatch-windows-deploy' @@ -96,6 +101,8 @@ Get-ActivityWatchExecutableMap -InstallRoot $effectiveInstallRoot | Out-Null $assetResult = Copy-ActivityWatchCollectorAssets ` -CollectorScriptSource (Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1') ` -EndpointCollectorScriptSource (Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1') ` + -EmailCollectorScriptSource (Join-Path $PSScriptRoot 'email-outbound-collector.ps1') ` + -FileCollectorScriptSource (Join-Path $PSScriptRoot 'file-operations-collector.ps1') ` -SessionCollectorScriptSource (Join-Path $PSScriptRoot 'worktime-session-collector.ps1') ` -ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') ` -ExamplePolicySource (Join-Path $PSScriptRoot 'dlp-policy.example.json') ` @@ -116,6 +123,8 @@ $config = New-ActivityWatchDeploymentConfig ` -LogsRoot $effectiveLogsRoot ` -CollectorScript $effectiveCollector ` -EndpointCollectorScript $effectiveEndpointCollector ` + -EmailCollectorScript $assetResult.EmailCollectorScript ` + -FileCollectorScript $effectiveFileCollector ` -SessionCollectorScript $effectiveSessionCollector ` -RulesPath $effectiveRules ` -PolicyPath $effectivePolicy ` @@ -124,6 +133,7 @@ $config = New-ActivityWatchDeploymentConfig ` -RecoveryIntervalSeconds $effectiveRecoveryInterval ` -AfkEnabled $effectiveAfkEnabled ` -WindowEnabled $effectiveWindowEnabled ` + -FileOpsEnabled $effectiveFileOpsEnabled ` -LocalAgentLogsEnabled $effectiveLocalAgentLogsEnabled ` -IncidentCaptureEnabled $effectiveIncidentCaptureEnabled ` -IncidentScreenshotEnabled $effectiveIncidentScreenshotEnabled ` diff --git a/windows/ActivityWatch.Windows.Common.psm1 b/windows/ActivityWatch.Windows.Common.psm1 index 6c869f7..e3a30b5 100755 --- a/windows/ActivityWatch.Windows.Common.psm1 +++ b/windows/ActivityWatch.Windows.Common.psm1 @@ -269,6 +269,7 @@ function Copy-ActivityWatchCollectorAssets { [string]$FileCollectorScriptSource, [Parameter(Mandatory = $true)] [string]$SessionCollectorScriptSource, + [string]$EmailCollectorScriptSource, [Parameter(Mandatory = $true)] [string]$ExampleRulesSource, [Parameter(Mandatory = $true)] @@ -285,6 +286,7 @@ function Copy-ActivityWatchCollectorAssets { $endpointCollectorTarget = Join-Path $StateRoot 'dlp-endpoint-signals-collector.ps1' $fileCollectorTarget = Join-Path $StateRoot 'file-operations-collector.ps1' $sessionCollectorTarget = Join-Path $StateRoot 'worktime-session-collector.ps1' + $emailCollectorTarget = Join-Path $StateRoot 'email-outbound-collector.ps1' $exampleRulesTarget = Join-Path $StateRoot 'web-category-rules.example.json' $rulesTarget = Join-Path $StateRoot 'web-category-rules.json' $examplePolicyTarget = Join-Path $StateRoot 'dlp-policy.example.json' @@ -294,6 +296,9 @@ function Copy-ActivityWatchCollectorAssets { Copy-Item -LiteralPath $EndpointCollectorScriptSource -Destination $endpointCollectorTarget -Force Copy-Item -LiteralPath $FileCollectorScriptSource -Destination $fileCollectorTarget -Force Copy-Item -LiteralPath $SessionCollectorScriptSource -Destination $sessionCollectorTarget -Force + if ($EmailCollectorScriptSource -and (Test-Path -LiteralPath $EmailCollectorScriptSource)) { + Copy-Item -LiteralPath $EmailCollectorScriptSource -Destination $emailCollectorTarget -Force + } Copy-Item -LiteralPath $ExampleRulesSource -Destination $exampleRulesTarget -Force Copy-Item -LiteralPath $ExamplePolicySource -Destination $examplePolicyTarget -Force @@ -315,6 +320,7 @@ function Copy-ActivityWatchCollectorAssets { EndpointCollectorScript = $endpointCollectorTarget FileCollectorScript = $fileCollectorTarget SessionCollectorScript = $sessionCollectorTarget + EmailCollectorScript = $emailCollectorTarget ExampleRules = $exampleRulesTarget ActiveRules = $rulesTarget ExamplePolicy = $examplePolicyTarget @@ -344,6 +350,7 @@ function New-ActivityWatchDeploymentConfig { [string]$FileCollectorScript, [Parameter(Mandatory = $true)] [string]$SessionCollectorScript, + [string]$EmailCollectorScript, [Parameter(Mandatory = $true)] [string]$RulesPath, [Parameter(Mandatory = $true)] @@ -387,6 +394,7 @@ function New-ActivityWatchDeploymentConfig { logsRoot = $LogsRoot collectorScript = $CollectorScript endpointCollectorScript = $EndpointCollectorScript + emailCollectorScript = $EmailCollectorScript fileCollectorScript = $FileCollectorScript sessionCollectorScript = $SessionCollectorScript rulesPath = $RulesPath @@ -402,6 +410,7 @@ function New-ActivityWatchDeploymentConfig { afkEnabled = $AfkEnabled windowEnabled = $WindowEnabled fileOpsEnabled = $FileOpsEnabled + emailEnabled = ($null -ne $EmailCollectorScript -and $EmailCollectorScript -ne '') } logging = [pscustomobject]@{ localAgentLogsEnabled = $LocalAgentLogsEnabled @@ -749,6 +758,8 @@ function Start-CollectorScriptIfNeeded { `$afkEnabled = if (`$config.PSObject.Properties.Name -contains 'collectors' -and `$config.collectors.PSObject.Properties.Name -contains 'afkEnabled') { [bool]`$config.collectors.afkEnabled } else { `$true } `$windowEnabled = if (`$config.PSObject.Properties.Name -contains 'collectors' -and `$config.collectors.PSObject.Properties.Name -contains 'windowEnabled') { [bool]`$config.collectors.windowEnabled } else { `$true } `$fileOpsEnabled = if (`$config.PSObject.Properties.Name -contains 'collectors' -and `$config.collectors.PSObject.Properties.Name -contains 'fileOpsEnabled') { [bool]`$config.collectors.fileOpsEnabled } else { `$true } +`$emailEnabled = if (`$config.PSObject.Properties.Name -contains 'collectors' -and `$config.collectors.PSObject.Properties.Name -contains 'emailEnabled') { [bool]`$config.collectors.emailEnabled } else { `$false } +`$emailCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'emailCollectorScript') { [string]`$config.paths.emailCollectorScript } else { Join-Path `$stateRoot 'email-outbound-collector.ps1' } `$launchLockPath = New-LaunchLock -StateRoot `$stateRoot -SessionId `$sessionId if (-not `$launchLockPath) { return @@ -782,6 +793,9 @@ try { Start-CollectorScriptIfNeeded -ScriptPath `$fileCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId } Start-CollectorScriptIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId + if (`$emailEnabled -and (Test-Path -LiteralPath `$emailCollectorScript)) { + Start-CollectorScriptIfNeeded -ScriptPath `$emailCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId + } } finally { if (`$launchLockPath -and (Test-Path -LiteralPath `$launchLockPath)) { diff --git a/windows/deploy-domain-users.ps1 b/windows/deploy-domain-users.ps1 index 5a6ee7a..92e5494 100755 --- a/windows/deploy-domain-users.ps1 +++ b/windows/deploy-domain-users.ps1 @@ -45,6 +45,7 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1' $recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1' $collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1' $endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1' +$emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1' $fileCollectorSource = Join-Path $PSScriptRoot 'file-operations-collector.ps1' $sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1' $exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json' @@ -61,6 +62,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null $assetResult = Copy-ActivityWatchCollectorAssets ` -CollectorScriptSource $collectorSource ` -EndpointCollectorScriptSource $endpointCollectorSource ` + -EmailCollectorScriptSource $emailCollectorSource ` -FileCollectorScriptSource $fileCollectorSource ` -SessionCollectorScriptSource $sessionCollectorSource ` -ExampleRulesSource $exampleRulesSource ` @@ -82,6 +84,7 @@ $config = New-ActivityWatchDeploymentConfig ` -LogsRoot $logsRoot ` -CollectorScript $assetResult.CollectorScript ` -EndpointCollectorScript $assetResult.EndpointCollectorScript ` + -EmailCollectorScript $assetResult.EmailCollectorScript ` -FileCollectorScript $assetResult.FileCollectorScript ` -SessionCollectorScript $assetResult.SessionCollectorScript ` -RulesPath $assetResult.ActiveRules ` diff --git a/windows/deploy-single-user.ps1 b/windows/deploy-single-user.ps1 index 9b035e4..160265d 100755 --- a/windows/deploy-single-user.ps1 +++ b/windows/deploy-single-user.ps1 @@ -42,6 +42,7 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1' $recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1' $collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1' $endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1' +$emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1' $sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1' $exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json' $examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json' @@ -56,6 +57,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null $assetResult = Copy-ActivityWatchCollectorAssets ` -CollectorScriptSource $collectorSource ` -EndpointCollectorScriptSource $endpointCollectorSource ` + -EmailCollectorScriptSource $emailCollectorSource ` -SessionCollectorScriptSource $sessionCollectorSource ` -ExampleRulesSource $exampleRulesSource ` -ExamplePolicySource $examplePolicySource ` @@ -76,6 +78,7 @@ $config = New-ActivityWatchDeploymentConfig ` -LogsRoot $logsRoot ` -CollectorScript $assetResult.CollectorScript ` -EndpointCollectorScript $assetResult.EndpointCollectorScript ` + -EmailCollectorScript $assetResult.EmailCollectorScript ` -SessionCollectorScript $assetResult.SessionCollectorScript ` -RulesPath $assetResult.ActiveRules ` -PolicyPath $assetResult.ActivePolicy ` diff --git a/windows/dlp-endpoint-signals-collector.ps1 b/windows/dlp-endpoint-signals-collector.ps1 index 1052c02..8dd67bd 100644 --- a/windows/dlp-endpoint-signals-collector.ps1 +++ b/windows/dlp-endpoint-signals-collector.ps1 @@ -516,6 +516,35 @@ function Test-LooksLikeMojibakeQuestionMarks { return $Value -match '\?{2,}' } +function Test-IsGenericDocumentName { + param([AllowNull()][string]$Value) + if ([string]::IsNullOrWhiteSpace($Value)) { return $true } + $generic = @( + '^\s*Печать документа\s*$', + '^\s*Print Document\s*$', + '^\s*Document\s*$', + '^\s*Документ\s*$', + '^\s*Remote Downlevel Document\s*$', + '^\s*Local Downlevel Document\s*$', + '^\s*Untitled\s*$', + '^\s*Без имени\s*$', + '^\s*Без названия\s*$' + ) + foreach ($pattern in $generic) { + if ($Value -match $pattern) { return $true } + } + return $false +} + +function Test-NeedsBetterDocumentName { + param([AllowNull()][string]$Value) + if ([string]::IsNullOrWhiteSpace($Value)) { return $true } + if (Test-LooksLikeMojibakeQuestionMarks -Value $Value) { return $true } + if (Test-IsGenericDocumentName -Value $Value) { return $true } + if ($Value -match '^[0-9]+$') { return $true } + return $false +} + function Normalize-OwnerForMatch { param([AllowNull()][string]$Value) if ([string]::IsNullOrWhiteSpace($Value)) { return '' } @@ -602,7 +631,7 @@ function Get-PrintServiceDocumentFallback { ) $preferred = [string]$EventSummary.DocumentName - if (-not (Test-LooksLikeMojibakeQuestionMarks -Value $preferred) -and $preferred -notmatch '^[0-9]+$') { + if (-not (Test-NeedsBetterDocumentName -Value $preferred)) { return $preferred } @@ -615,17 +644,13 @@ function Get-PrintServiceDocumentFallback { if ($candidate -eq $preferred) { continue } if ($Owner -and $candidate -like "*$Owner*") { continue } if ($PrinterName -and $candidate -like "*$PrinterName*") { continue } - if (Test-LooksLikeMojibakeQuestionMarks -Value $candidate) { continue } + if (Test-NeedsBetterDocumentName -Value $candidate) { continue } if ($candidate -match '[\\/:]' -and $candidate -match '\.[A-Za-z0-9]{1,8}$') { $pathCandidates.Add($candidate) continue } - if ($candidate -match '^[0-9]+$') { - continue - } - $textCandidates.Add($candidate) } @@ -828,7 +853,7 @@ while ($true) { $owner = [string]$job.Owner $documentNameOriginal = $documentName - if (Test-LooksLikeMojibakeQuestionMarks -Value $documentName) { + if (Test-NeedsBetterDocumentName -Value $documentName) { $eventDocumentName = Get-BetterDocumentNameFromPrintServiceEvents -Owner $owner -PrinterName $printerName if ($eventDocumentName) { $documentName = $eventDocumentName diff --git a/windows/hardening-recovery.ps1 b/windows/hardening-recovery.ps1 index 0a7123f..d382f41 100755 --- a/windows/hardening-recovery.ps1 +++ b/windows/hardening-recovery.ps1 @@ -101,6 +101,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $effectiveInstallRoot | Out-Null $assetResult = Copy-ActivityWatchCollectorAssets ` -CollectorScriptSource (Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1') ` -EndpointCollectorScriptSource (Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1') ` + -EmailCollectorScriptSource (Join-Path $PSScriptRoot 'email-outbound-collector.ps1') ` -FileCollectorScriptSource (Join-Path $PSScriptRoot 'file-operations-collector.ps1') ` -SessionCollectorScriptSource (Join-Path $PSScriptRoot 'worktime-session-collector.ps1') ` -ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') ` @@ -122,6 +123,7 @@ $config = New-ActivityWatchDeploymentConfig ` -LogsRoot $effectiveLogsRoot ` -CollectorScript $effectiveCollector ` -EndpointCollectorScript $effectiveEndpointCollector ` + -EmailCollectorScript $assetResult.EmailCollectorScript ` -FileCollectorScript $effectiveFileCollector ` -SessionCollectorScript $effectiveSessionCollector ` -RulesPath $effectiveRules ` diff --git a/windows/installkit/innosetup/AWatch-rus-InnoSetup.iss b/windows/installkit/innosetup/AWatch-rus-InnoSetup.iss index 22c0dde..99dc92d 100644 --- a/windows/installkit/innosetup/AWatch-rus-InnoSetup.iss +++ b/windows/installkit/innosetup/AWatch-rus-InnoSetup.iss @@ -42,6 +42,7 @@ 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: "..\..\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 ; Offline payload (optional): place ZIP into windows/installkit/innosetup/payload/ before compiling.