Русифицировать DLP PowerShell и исправить имя документа печати
This commit is contained in:
@@ -5,7 +5,7 @@ function Assert-Administrator {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'Run this script from an elevated PowerShell session.'
|
||||
throw 'Запустите этот скрипт из PowerShell с правами администратора.'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ function Get-ActivityWatchPackageRoot {
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $afkBinary) {
|
||||
throw "Cannot find aw-watcher-afk.exe under $ExpandedRoot."
|
||||
throw "Не удалось найти aw-watcher-afk.exe в $ExpandedRoot."
|
||||
}
|
||||
|
||||
return (Split-Path -Path (Split-Path -Path $afkBinary.FullName -Parent) -Parent)
|
||||
@@ -130,7 +130,7 @@ function Get-ActivityWatchExecutableMap {
|
||||
|
||||
foreach ($entry in $map.GetEnumerator()) {
|
||||
if (-not (Test-Path -LiteralPath $entry.Value)) {
|
||||
throw "Missing required ActivityWatch binary: $($entry.Value)"
|
||||
throw "Не найден обязательный исполняемый файл ActivityWatch: $($entry.Value)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ function Normalize-ActivityWatchUsers {
|
||||
Sort-Object -Unique
|
||||
|
||||
if (-not $normalized -or $normalized.Count -eq 0) {
|
||||
throw 'No target users resolved. Provide -Users or -UserListPath.'
|
||||
throw 'Не удалось определить целевых пользователей. Укажите -Users или -UserListPath.'
|
||||
}
|
||||
|
||||
return @($normalized)
|
||||
@@ -243,6 +243,8 @@ function Copy-ActivityWatchCollectorAssets {
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$EndpointCollectorScriptSource,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$SessionCollectorScriptSource,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ExampleRulesSource,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ExamplePolicySource,
|
||||
@@ -256,6 +258,7 @@ function Copy-ActivityWatchCollectorAssets {
|
||||
|
||||
$collectorTarget = Join-Path $StateRoot 'browser-domains-native-collector.ps1'
|
||||
$endpointCollectorTarget = Join-Path $StateRoot 'dlp-endpoint-signals-collector.ps1'
|
||||
$sessionCollectorTarget = Join-Path $StateRoot 'worktime-session-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'
|
||||
@@ -263,6 +266,7 @@ function Copy-ActivityWatchCollectorAssets {
|
||||
|
||||
Copy-Item -LiteralPath $CollectorScriptSource -Destination $collectorTarget -Force
|
||||
Copy-Item -LiteralPath $EndpointCollectorScriptSource -Destination $endpointCollectorTarget -Force
|
||||
Copy-Item -LiteralPath $SessionCollectorScriptSource -Destination $sessionCollectorTarget -Force
|
||||
Copy-Item -LiteralPath $ExampleRulesSource -Destination $exampleRulesTarget -Force
|
||||
Copy-Item -LiteralPath $ExamplePolicySource -Destination $examplePolicyTarget -Force
|
||||
|
||||
@@ -282,6 +286,7 @@ function Copy-ActivityWatchCollectorAssets {
|
||||
return [pscustomobject]@{
|
||||
CollectorScript = $collectorTarget
|
||||
EndpointCollectorScript = $endpointCollectorTarget
|
||||
SessionCollectorScript = $sessionCollectorTarget
|
||||
ExampleRules = $exampleRulesTarget
|
||||
ActiveRules = $rulesTarget
|
||||
ExamplePolicy = $examplePolicyTarget
|
||||
@@ -308,6 +313,8 @@ function New-ActivityWatchDeploymentConfig {
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$EndpointCollectorScript,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$SessionCollectorScript,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$RulesPath,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PolicyPath,
|
||||
@@ -349,6 +356,7 @@ function New-ActivityWatchDeploymentConfig {
|
||||
logsRoot = $LogsRoot
|
||||
collectorScript = $CollectorScript
|
||||
endpointCollectorScript = $EndpointCollectorScript
|
||||
sessionCollectorScript = $SessionCollectorScript
|
||||
rulesPath = $RulesPath
|
||||
policyPath = $PolicyPath
|
||||
launchScript = $LaunchScriptPath
|
||||
@@ -413,7 +421,7 @@ function Read-ActivityWatchDeploymentConfig {
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "Deployment config not found: $Path"
|
||||
throw "Конфигурация развёртывания не найдена: $Path"
|
||||
}
|
||||
|
||||
return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
||||
@@ -631,6 +639,7 @@ function Start-CollectorScriptIfNeeded {
|
||||
`$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 { '' }
|
||||
`$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)
|
||||
@@ -639,11 +648,11 @@ function Start-CollectorScriptIfNeeded {
|
||||
`$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 "Missing aw-watcher-afk.exe: `$afkExe"
|
||||
throw "Не найден aw-watcher-afk.exe: `$afkExe"
|
||||
}
|
||||
|
||||
if (`$windowEnabled -and -not (Test-Path -LiteralPath `$windowExe)) {
|
||||
throw "Missing aw-watcher-window.exe: `$windowExe"
|
||||
throw "Не найден aw-watcher-window.exe: `$windowExe"
|
||||
}
|
||||
|
||||
if (`$afkEnabled -and -not (Test-ProcessInSession -Name 'aw-watcher-afk' -SessionId `$sessionId)) {
|
||||
@@ -661,6 +670,7 @@ catch {
|
||||
}
|
||||
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
|
||||
@@ -850,7 +860,7 @@ function Set-ActivityWatchScheduledTaskAction {
|
||||
$taskCommand = ('"{0}" {1}' -f $Execute, $Arguments)
|
||||
& schtasks.exe /Change /TN $TaskName /TR $taskCommand | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "schtasks.exe /Change failed for $TaskName"
|
||||
throw "schtasks.exe /Change завершился с ошибкой для $TaskName"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -951,17 +961,17 @@ function Set-ActivityWatchAcl {
|
||||
|
||||
& icacls $InstallRoot /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)(F)' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-32-545:(OI)(CI)(RX)' | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "icacls failed for $InstallRoot"
|
||||
throw "icacls завершился с ошибкой для $InstallRoot"
|
||||
}
|
||||
|
||||
& icacls $StateRoot /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)(F)' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-32-545:(OI)(CI)(RX)' | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "icacls failed for $StateRoot"
|
||||
throw "icacls завершился с ошибкой для $StateRoot"
|
||||
}
|
||||
|
||||
& icacls $LogsRoot /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)(F)' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-32-545:(OI)(CI)(M)' | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "icacls failed for $LogsRoot"
|
||||
throw "icacls завершился с ошибкой для $LogsRoot"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ function Get-DeploymentConfig {
|
||||
}
|
||||
|
||||
$deploymentConfig = Get-DeploymentConfig -Path $ConfigPath
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'ServerHost is required.' }
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'Укажите ServerHost или подготовьте deployment-config.json.' }
|
||||
$resolvedServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($deploymentConfig) { [int]$deploymentConfig.server.port } else { 5600 }
|
||||
$resolvedServerScheme = if ($ServerScheme) { $ServerScheme } elseif ($deploymentConfig) { [string]$deploymentConfig.server.scheme } else { 'http' }
|
||||
$resolvedRulesPath = if ($RulesPath) { $RulesPath } elseif ($deploymentConfig) { [string]$deploymentConfig.paths.rulesPath } else { 'C:\ProgramData\ActivityWatch\web-category-rules.json' }
|
||||
@@ -263,11 +263,11 @@ function Load-CustomCategoryRules {
|
||||
|
||||
if ($rules.Count -gt 0) {
|
||||
$script:CategoryRules = @($rules) + @($script:CategoryRules)
|
||||
Write-CollectorLog ("custom rules loaded: {0}" -f $rules.Count)
|
||||
Write-CollectorLog ("пользовательские правила загружены: {0}" -f $rules.Count)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("custom rules load failed: {0}" -f $_.Exception.Message)
|
||||
Write-CollectorLog ("не удалось загрузить пользовательские правила: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ function Load-DlpPolicy {
|
||||
param([string]$Path)
|
||||
|
||||
if (-not $Path -or -not (Test-Path -LiteralPath $Path)) {
|
||||
Write-CollectorLog ("dlp policy not found, disabled: {0}" -f $Path)
|
||||
Write-CollectorLog ("DLP-политика не найдена, DLP отключен: {0}" -f $Path)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ function Load-DlpPolicy {
|
||||
enabled = if ($rule.PSObject.Properties.Name -contains 'enabled') { [bool]$rule.enabled } else { $true }
|
||||
action = if ($rule.action) { [string]$rule.action } else { [string]$script:DlpDefaults.action }
|
||||
severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:DlpDefaults.severity }
|
||||
message = if ($rule.message) { [string]$rule.message } else { "DLP rule matched: $($rule.id)" }
|
||||
message = if ($rule.message) { [string]$rule.message } else { "Сработало DLP-правило: $($rule.id)" }
|
||||
cooldownSeconds = if ($rule.cooldownSeconds) { [int]$rule.cooldownSeconds } else { [int]$script:DlpDefaults.cooldownSeconds }
|
||||
when = [pscustomobject]@{
|
||||
domains = if ($when.PSObject.Properties.Name -contains 'domains') { @($when.domains | ForEach-Object { ([string]$_).Trim().ToLowerInvariant() } | Where-Object { $_ }) } else { @() }
|
||||
@@ -388,10 +388,10 @@ function Load-DlpPolicy {
|
||||
}
|
||||
|
||||
$script:DlpRules = @($loaded)
|
||||
Write-CollectorLog ("dlp policy loaded: enabled={0}, rules={1}" -f $script:DlpDefaults.enabled, $script:DlpRules.Count)
|
||||
Write-CollectorLog ("DLP-политика загружена: включена={0}, правил={1}" -f $script:DlpDefaults.enabled, $script:DlpRules.Count)
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("dlp policy parse failed: {0}" -f $_.Exception.Message)
|
||||
Write-CollectorLog ("не удалось разобрать DLP-политику: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +625,7 @@ function Capture-IncidentScreenshot {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("screenshot capture failed: {0}" -f $_.Exception.Message)
|
||||
Write-CollectorLog ("не удалось сделать снимок инцидента: {0}" -f $_.Exception.Message)
|
||||
return @{}
|
||||
}
|
||||
}
|
||||
@@ -775,7 +775,7 @@ function Send-CategoryHeartbeat {
|
||||
|
||||
Load-CustomCategoryRules -Path $resolvedRulesPath
|
||||
Load-DlpPolicy -Path $resolvedPolicyPath
|
||||
Write-CollectorLog ("collector started against {0}" -f $script:ApiBase)
|
||||
Write-CollectorLog ("коллектор запущен для {0}" -f $script:ApiBase)
|
||||
|
||||
while ($true) {
|
||||
try {
|
||||
@@ -815,7 +815,7 @@ while ($true) {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("collector error: {0}" -f $_.Exception.Message)
|
||||
Write-CollectorLog ("ошибка коллектора: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $resolvedPollSeconds
|
||||
|
||||
@@ -44,6 +44,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'
|
||||
$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'
|
||||
|
||||
@@ -57,6 +58,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null
|
||||
$assetResult = Copy-ActivityWatchCollectorAssets `
|
||||
-CollectorScriptSource $collectorSource `
|
||||
-EndpointCollectorScriptSource $endpointCollectorSource `
|
||||
-SessionCollectorScriptSource $sessionCollectorSource `
|
||||
-ExampleRulesSource $exampleRulesSource `
|
||||
-ExamplePolicySource $examplePolicySource `
|
||||
-StateRoot $StateRoot `
|
||||
@@ -76,6 +78,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-LogsRoot $logsRoot `
|
||||
-CollectorScript $assetResult.CollectorScript `
|
||||
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
|
||||
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
||||
-RulesPath $assetResult.ActiveRules `
|
||||
-PolicyPath $assetResult.ActivePolicy `
|
||||
-PollSeconds $PollSeconds `
|
||||
@@ -100,8 +103,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-Host 'ActivityWatch deployed for users:'
|
||||
Write-Host 'ActivityWatch развёрнут для пользователей:'
|
||||
$targetUsers | ForEach-Object { Write-Host " - $_" }
|
||||
Write-Host "Server: ${ServerScheme}://$ServerHost`:$ServerPort"
|
||||
Write-Host "State root: $StateRoot"
|
||||
Write-Host "Policy file: $($assetResult.ActivePolicy)"
|
||||
Write-Host "Сервер: ${ServerScheme}://$ServerHost`:$ServerPort"
|
||||
Write-Host "Каталог данных: $StateRoot"
|
||||
Write-Host "Файл DLP-политики: $($assetResult.ActivePolicy)"
|
||||
|
||||
@@ -46,7 +46,7 @@ $hardeningScript = Join-Path $PSScriptRoot 'hardening-recovery.ps1'
|
||||
$validationScript = Join-Path $PSScriptRoot 'validate-deployment.ps1'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $deployScript)) {
|
||||
throw "Missing script: $deployScript"
|
||||
throw "Не найден скрипт: $deployScript"
|
||||
}
|
||||
|
||||
& $deployScript `
|
||||
@@ -118,7 +118,7 @@ $report = [ordered]@{
|
||||
|
||||
if ($ValidateAfterDeploy) {
|
||||
if (-not (Test-Path -LiteralPath $validationScript)) {
|
||||
throw "Missing script: $validationScript"
|
||||
throw "Не найден скрипт: $validationScript"
|
||||
}
|
||||
|
||||
$validation = & $validationScript -ConfigPath (Join-Path $StateRoot 'deployment-config.json')
|
||||
@@ -132,6 +132,6 @@ if ($reportDirectory) {
|
||||
|
||||
$report | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $effectiveReportPath -Encoding UTF8
|
||||
|
||||
Write-Host 'ActivityWatch ensemble deploy completed.'
|
||||
Write-Host "Users: $($resolvedUsers -join ', ')"
|
||||
Write-Host "Report: $effectiveReportPath"
|
||||
Write-Host 'Комплексное развёртывание ActivityWatch завершено.'
|
||||
Write-Host "Пользователи: $($resolvedUsers -join ', ')"
|
||||
Write-Host "Отчёт: $effectiveReportPath"
|
||||
|
||||
@@ -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'
|
||||
$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'
|
||||
|
||||
@@ -55,6 +56,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null
|
||||
$assetResult = Copy-ActivityWatchCollectorAssets `
|
||||
-CollectorScriptSource $collectorSource `
|
||||
-EndpointCollectorScriptSource $endpointCollectorSource `
|
||||
-SessionCollectorScriptSource $sessionCollectorSource `
|
||||
-ExampleRulesSource $exampleRulesSource `
|
||||
-ExamplePolicySource $examplePolicySource `
|
||||
-StateRoot $StateRoot `
|
||||
@@ -74,6 +76,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-LogsRoot $logsRoot `
|
||||
-CollectorScript $assetResult.CollectorScript `
|
||||
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
|
||||
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
||||
-RulesPath $assetResult.ActiveRules `
|
||||
-PolicyPath $assetResult.ActivePolicy `
|
||||
-PollSeconds $PollSeconds `
|
||||
@@ -98,9 +101,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-Host "ActivityWatch deployed for $TargetUser"
|
||||
Write-Host "Server: ${ServerScheme}://$ServerHost`:$ServerPort"
|
||||
Write-Host "Install root: $InstallRoot"
|
||||
Write-Host "State root: $StateRoot"
|
||||
Write-Host "Rules file: $($assetResult.ActiveRules)"
|
||||
Write-Host "Policy file: $($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)"
|
||||
|
||||
@@ -210,7 +210,7 @@ function Capture-IncidentScreenshot {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("screenshot capture failed: {0}" -f $_.Exception.Message)
|
||||
Write-EndpointLog ("не удалось сделать снимок инцидента: {0}" -f $_.Exception.Message)
|
||||
return @{}
|
||||
}
|
||||
}
|
||||
@@ -246,7 +246,7 @@ function Load-DlpPolicy {
|
||||
}
|
||||
|
||||
if (-not $Path -or -not (Test-Path -LiteralPath $Path)) {
|
||||
Write-EndpointLog ("policy not found, using defaults: {0}" -f $Path)
|
||||
Write-EndpointLog ("DLP-политика не найдена, используются значения по умолчанию: {0}" -f $Path)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ function Load-DlpPolicy {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("policy parse failed: {0}" -f $_.Exception.Message)
|
||||
Write-EndpointLog ("не удалось разобрать DLP-политику: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,13 +319,13 @@ function Evaluate-ClipboardRules {
|
||||
|
||||
$action = if ($rule.action) { [string]$rule.action } else { [string]$script:Policy.defaults.action }
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Clipboard rule matched: $ruleId" }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Сработало правило буфера обмена: $ruleId" }
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'clipboard' -Data @{
|
||||
clipboardHash = $ClipboardHash
|
||||
clipboardLength = $ClipboardText.Length
|
||||
}
|
||||
Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2}" -f $ruleId, $action, $severity)
|
||||
Write-EndpointLog ("инцидент буфера обмена правило={0} действие={1} важность={2}" -f $ruleId, $action, $severity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,13 +347,13 @@ function Evaluate-UsbRules {
|
||||
|
||||
$action = if ($rule.action) { [string]$rule.action } else { [string]$script:Policy.defaults.action }
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "USB rule matched: $ruleId" }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Сработало правило USB-носителя: $ruleId" }
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'usb_insert' -Data @{
|
||||
driveLetter = $DriveLetter
|
||||
volumeName = $VolumeName
|
||||
}
|
||||
Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3}" -f $ruleId, $action, $severity, $DriveLetter)
|
||||
Write-EndpointLog ("инцидент USB правило={0} действие={1} важность={2} диск={3}" -f $ruleId, $action, $severity, $DriveLetter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,14 +385,14 @@ function Evaluate-PrintRules {
|
||||
|
||||
$action = if ($rule.action) { [string]$rule.action } else { [string]$script:Policy.defaults.action }
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Print rule matched: $ruleId" }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Сработало правило печати: $ruleId" }
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'print_job' -Data @{
|
||||
printerName = $PrinterName
|
||||
documentName = $DocumentName
|
||||
owner = $Owner
|
||||
}
|
||||
Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3}" -f $ruleId, $action, $severity, $PrinterName)
|
||||
Write-EndpointLog ("инцидент печати правило={0} действие={1} важность={2} принтер={3}" -f $ruleId, $action, $severity, $PrinterName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,6 +402,52 @@ function Test-LooksLikeMojibakeQuestionMarks {
|
||||
return $Value -match '\?{2,}'
|
||||
}
|
||||
|
||||
function Test-DocumentNameNeedsFallback {
|
||||
param([AllowNull()][string]$Value)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) { return $true }
|
||||
$trimmed = $Value.Trim()
|
||||
if (Test-LooksLikeMojibakeQuestionMarks -Value $trimmed) { return $true }
|
||||
if ($trimmed -match '^[0-9]+$') { return $true }
|
||||
if ($trimmed -match '^(?i)(print document|document|local downlevel document)$') { return $true }
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-EventXmlValue {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][xml]$EventXml,
|
||||
[Parameter(Mandatory = $true)][string]$Name
|
||||
)
|
||||
|
||||
$node = $EventXml.Event.UserData.DocumentPrinted.$Name
|
||||
if ($null -ne $node) {
|
||||
return [string]$node
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function Get-PrintJobPrinterName {
|
||||
param(
|
||||
[AllowNull()][string]$JobName,
|
||||
[AllowNull()][string]$FallbackPrinterName
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($JobName)) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($FallbackPrinterName)) {
|
||||
return $FallbackPrinterName.Trim()
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
$parts = $JobName -split ',', 2
|
||||
if ($parts.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($parts[0])) {
|
||||
return $parts[0].Trim()
|
||||
}
|
||||
|
||||
return $JobName.Trim()
|
||||
}
|
||||
|
||||
function Normalize-OwnerForMatch {
|
||||
param([AllowNull()][string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) { return '' }
|
||||
@@ -469,13 +515,50 @@ function Get-PrintServiceEventSummary {
|
||||
$propertyValues += [string]$prop.Value
|
||||
}
|
||||
|
||||
$xml = $null
|
||||
try {
|
||||
$xml = [xml]$Event.ToXml()
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
$jobId = ''
|
||||
$documentName = ''
|
||||
$owner = ''
|
||||
$portName = ''
|
||||
$printerName = ''
|
||||
$sizeBytes = ''
|
||||
$pageCount = ''
|
||||
|
||||
if ($xml) {
|
||||
$jobId = Get-EventXmlValue -EventXml $xml -Name 'Param1'
|
||||
$documentName = Get-EventXmlValue -EventXml $xml -Name 'Param2'
|
||||
$owner = Get-EventXmlValue -EventXml $xml -Name 'Param3'
|
||||
$portName = Get-EventXmlValue -EventXml $xml -Name 'Param4'
|
||||
$printerName = Get-EventXmlValue -EventXml $xml -Name 'Param5'
|
||||
$sizeBytes = Get-EventXmlValue -EventXml $xml -Name 'Param7'
|
||||
$pageCount = Get-EventXmlValue -EventXml $xml -Name 'Param8'
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($jobId) -and $props.Count -ge 1) { $jobId = [string]$props[0].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($documentName) -and $props.Count -ge 2) { $documentName = [string]$props[1].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($owner) -and $props.Count -ge 3) { $owner = [string]$props[2].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($portName) -and $props.Count -ge 4) { $portName = [string]$props[3].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($printerName) -and $props.Count -ge 5) { $printerName = [string]$props[4].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($sizeBytes) -and $props.Count -ge 7) { $sizeBytes = [string]$props[6].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($pageCount) -and $props.Count -ge 8) { $pageCount = [string]$props[7].Value }
|
||||
|
||||
[pscustomobject]@{
|
||||
RecordId = [string]$Event.RecordId
|
||||
TimeCreated = if ($Event.TimeCreated) { $Event.TimeCreated.ToString('o') } else { '' }
|
||||
PropertyCount = $props.Count
|
||||
DocumentName = if ($props.Count -ge 1) { [string]$props[0].Value } else { '' }
|
||||
Owner = if ($props.Count -ge 2) { [string]$props[1].Value } else { '' }
|
||||
PrinterName = if ($props.Count -ge 4) { [string]$props[3].Value } else { '' }
|
||||
JobId = $jobId
|
||||
DocumentName = $documentName
|
||||
Owner = $owner
|
||||
PortName = $portName
|
||||
PrinterName = $printerName
|
||||
SizeBytes = $sizeBytes
|
||||
PageCount = $pageCount
|
||||
PropertyValues = $propertyValues
|
||||
}
|
||||
}
|
||||
@@ -488,7 +571,7 @@ function Get-PrintServiceDocumentFallback {
|
||||
)
|
||||
|
||||
$preferred = [string]$EventSummary.DocumentName
|
||||
if (-not (Test-LooksLikeMojibakeQuestionMarks -Value $preferred) -and $preferred -notmatch '^[0-9]+$') {
|
||||
if (-not (Test-DocumentNameNeedsFallback -Value $preferred)) {
|
||||
return $preferred
|
||||
}
|
||||
|
||||
@@ -499,9 +582,10 @@ function Get-PrintServiceDocumentFallback {
|
||||
$candidate = [string]$value
|
||||
if ([string]::IsNullOrWhiteSpace($candidate)) { continue }
|
||||
if ($candidate -eq $preferred) { continue }
|
||||
if ($EventSummary.JobId -and $candidate -eq [string]$EventSummary.JobId) { continue }
|
||||
if ($Owner -and $candidate -like "*$Owner*") { continue }
|
||||
if ($PrinterName -and $candidate -like "*$PrinterName*") { continue }
|
||||
if (Test-LooksLikeMojibakeQuestionMarks -Value $candidate) { continue }
|
||||
if (Test-DocumentNameNeedsFallback -Value $candidate) { continue }
|
||||
|
||||
if ($candidate -match '[\\/:]' -and $candidate -match '\.[A-Za-z0-9]{1,8}$') {
|
||||
$pathCandidates.Add($candidate)
|
||||
@@ -546,7 +630,7 @@ function Write-PrintServiceEventTrace {
|
||||
}
|
||||
|
||||
Write-EndpointLog (
|
||||
'printservice-307 phase={0} recordId={1} time={2} owner={3} printer={4} document={5} resolved={6} properties=[{7}] reason={8}' -f
|
||||
'printservice-307 этап={0} recordId={1} время={2} владелец={3} принтер={4} документ={5} итоговыйДокумент={6} свойства=[{7}] причина={8}' -f
|
||||
$Phase,
|
||||
$EventSummary.RecordId,
|
||||
$EventSummary.TimeCreated,
|
||||
@@ -561,6 +645,7 @@ function Write-PrintServiceEventTrace {
|
||||
|
||||
function Get-BetterDocumentNameFromPrintServiceEvents {
|
||||
param(
|
||||
[string]$JobId,
|
||||
[string]$Owner,
|
||||
[string]$PrinterName
|
||||
)
|
||||
@@ -578,32 +663,41 @@ function Get-BetterDocumentNameFromPrintServiceEvents {
|
||||
$summary = Get-PrintServiceEventSummary -Event $event
|
||||
$resolvedDocument = Get-PrintServiceDocumentFallback -EventSummary $summary -Owner $Owner -PrinterName $PrinterName
|
||||
|
||||
$jobMatches = if ($JobId) { [string]$summary.JobId -eq [string]$JobId } else { $true }
|
||||
$ownerMatches = if ($Owner) { Test-OwnerLooseMatch -Expected $Owner -Actual $summary.Owner } else { $true }
|
||||
$printerMatches = if ($PrinterName) { Test-PrinterLooseMatch -Expected $PrinterName -Actual $summary.PrinterName } else { $true }
|
||||
|
||||
if ($pass -eq 'strict') {
|
||||
if ($JobId -and -not $jobMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-jobid-strict' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
if ($Owner -and -not $ownerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'owner-mismatch-strict' -ResolvedDocument $resolvedDocument
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-владельца-strict' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
if ($PrinterName -and -not $printerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'printer-mismatch-strict' -ResolvedDocument $resolvedDocument
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-принтера-strict' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($Owner -and $PrinterName -and -not $ownerMatches -and -not $printerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'owner-and-printer-mismatch-relaxed' -ResolvedDocument $resolvedDocument
|
||||
if ($JobId -and (-not $jobMatches) -and $Owner -and $PrinterName -and -not $ownerMatches -and -not $printerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-владельца-и-принтера-relaxed' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
if ((-not $JobId) -and $Owner -and $PrinterName -and -not $ownerMatches -and -not $printerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-владельца-и-принтера-relaxed' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($resolvedDocument)) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason ('no-document-candidate-' + $pass) -ResolvedDocument ''
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason ('нет-кандидата-документа-' + $pass) -ResolvedDocument ''
|
||||
continue
|
||||
}
|
||||
|
||||
$matchReasonBase = if (Test-LooksLikeMojibakeQuestionMarks -Value $summary.DocumentName) { 'fallback-used' } else { 'direct' }
|
||||
$matchReasonBase = if (Test-DocumentNameNeedsFallback -Value $summary.DocumentName) { 'использован-резервный-вариант' } else { 'напрямую' }
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'selected' -MatchReason ($matchReasonBase + '-' + $pass) -ResolvedDocument $resolvedDocument
|
||||
return $resolvedDocument
|
||||
}
|
||||
@@ -616,7 +710,7 @@ function Get-BetterDocumentNameFromPrintServiceEvents {
|
||||
}
|
||||
|
||||
$deploymentConfig = Get-DeploymentConfig -Path $ConfigPath
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'ServerHost is required.' }
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'Укажите ServerHost или подготовьте deployment-config.json.' }
|
||||
$resolvedServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($deploymentConfig) { [int]$deploymentConfig.server.port } else { 5600 }
|
||||
$resolvedServerScheme = if ($ServerScheme) { $ServerScheme } elseif ($deploymentConfig) { [string]$deploymentConfig.server.scheme } else { 'http' }
|
||||
$resolvedPolicyPath = if ($PolicyPath) { $PolicyPath } elseif ($deploymentConfig -and $deploymentConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$deploymentConfig.paths.policyPath } else { 'C:\ProgramData\ActivityWatch\dlp-policy.json' }
|
||||
@@ -648,7 +742,7 @@ $script:IncidentScreenshotEnabled = $resolvedIncidentScreenshotEnabled
|
||||
$script:ScreenshotTypesLoaded = $false
|
||||
|
||||
Load-DlpPolicy -Path $resolvedPolicyPath
|
||||
Write-EndpointLog ("endpoint collector started against {0}" -f $script:ApiBase)
|
||||
Write-EndpointLog ("endpoint-коллектор запущен для {0}" -f $script:ApiBase)
|
||||
|
||||
while ($true) {
|
||||
try {
|
||||
@@ -709,13 +803,13 @@ while ($true) {
|
||||
if ($script:SeenPrintJob.ContainsKey($jobId)) { continue }
|
||||
$script:SeenPrintJob[$jobId] = (Get-Date).ToUniversalTime()
|
||||
|
||||
$printerName = [string]$job.Name
|
||||
$printerName = Get-PrintJobPrinterName -JobName ([string]$job.Name) -FallbackPrinterName ([string]$job.DriverName)
|
||||
$documentName = [string]$job.Document
|
||||
$owner = [string]$job.Owner
|
||||
$documentNameOriginal = $documentName
|
||||
|
||||
if (Test-LooksLikeMojibakeQuestionMarks -Value $documentName) {
|
||||
$eventDocumentName = Get-BetterDocumentNameFromPrintServiceEvents -Owner $owner -PrinterName $printerName
|
||||
if (Test-DocumentNameNeedsFallback -Value $documentName) {
|
||||
$eventDocumentName = Get-BetterDocumentNameFromPrintServiceEvents -JobId $jobId -Owner $owner -PrinterName $printerName
|
||||
if ($eventDocumentName) {
|
||||
$documentName = $eventDocumentName
|
||||
}
|
||||
@@ -726,6 +820,7 @@ while ($true) {
|
||||
documentName = $documentName
|
||||
documentNameOriginal = $documentNameOriginal
|
||||
owner = $owner
|
||||
printJobId = $jobId
|
||||
}
|
||||
Evaluate-PrintRules -PrinterName $printerName -DocumentName $documentName -Owner $owner
|
||||
}
|
||||
@@ -789,7 +884,7 @@ while ($true) {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("collector error: {0}" -f $_.Exception.Message)
|
||||
Write-EndpointLog ("ошибка коллектора: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $resolvedPollSeconds
|
||||
|
||||
@@ -42,7 +42,7 @@ if (Test-Path -LiteralPath $ConfigPath) {
|
||||
}
|
||||
|
||||
if (-not $existingConfig -and (-not $ServerHost)) {
|
||||
throw 'deployment-config.json is missing. Provide -ServerHost and user parameters, or run a deploy script first.'
|
||||
throw 'deployment-config.json отсутствует. Укажите -ServerHost и параметры пользователей либо сначала выполните скрипт развёртывания.'
|
||||
}
|
||||
|
||||
$effectiveStateRoot = if ($StateRoot) { $StateRoot } elseif ($existingConfig) { [string]$existingConfig.paths.stateRoot } else { 'C:\ProgramData\ActivityWatch' }
|
||||
@@ -78,7 +78,7 @@ elseif ($existingConfig) {
|
||||
@($existingConfig.userTasks | ForEach-Object { [string]$_.userId })
|
||||
}
|
||||
else {
|
||||
throw 'Target users are missing.'
|
||||
throw 'Не указаны целевые пользователи.'
|
||||
}
|
||||
|
||||
New-ActivityWatchDirectory -Path $effectiveStateRoot
|
||||
@@ -96,6 +96,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') `
|
||||
-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') `
|
||||
-StateRoot $effectiveStateRoot `
|
||||
@@ -139,6 +140,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-Host 'ActivityWatch hardening/recovery completed.'
|
||||
Write-Host "Config: $effectiveConfigPath"
|
||||
Write-Host "Users repaired: $($effectiveUsers -join ', ')"
|
||||
Write-Host 'Укрепление и восстановление ActivityWatch завершены.'
|
||||
Write-Host "Конфигурация: $effectiveConfigPath"
|
||||
Write-Host "Пользователи восстановлены: $($effectiveUsers -join ', ')"
|
||||
|
||||
@@ -14,6 +14,7 @@ $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' }
|
||||
$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
|
||||
@@ -24,6 +25,7 @@ $requiredFiles = @(
|
||||
(Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe'),
|
||||
$collectorScript,
|
||||
$endpointCollectorScript,
|
||||
$sessionCollectorScript,
|
||||
$rulesPath,
|
||||
$policyPath,
|
||||
$launchScript,
|
||||
@@ -37,6 +39,12 @@ $missingFiles = @(
|
||||
|
||||
$processNames = @('aw-watcher-afk', 'aw-watcher-window')
|
||||
$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) {
|
||||
@@ -57,7 +65,7 @@ $tasks = foreach ($taskName in $taskNames) {
|
||||
else {
|
||||
[pscustomobject]@{
|
||||
taskName = $taskName
|
||||
state = 'Missing'
|
||||
state = 'Отсутствует'
|
||||
present = $false
|
||||
}
|
||||
}
|
||||
@@ -81,7 +89,11 @@ $result = [ordered]@{
|
||||
}
|
||||
processes = [ordered]@{
|
||||
list = @($runningProcesses)
|
||||
ok = [bool](($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge 2)
|
||||
sessionCollectors = @($sessionCollectorProcesses)
|
||||
ok = [bool](
|
||||
(($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge 2) -and
|
||||
($sessionCollectorProcesses.Count -ge 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
param(
|
||||
[string]$ConfigPath = 'C:\ProgramData\ActivityWatch\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
|
||||
|
||||
Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId" -Json $body
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$sessionId = 0
|
||||
if ($parts[2] -match '^\d+$') {
|
||||
$sessionId = [int]$parts[2]
|
||||
}
|
||||
|
||||
$records += [pscustomobject]@{
|
||||
username = $parts[0]
|
||||
sessionName = $parts[1]
|
||||
sessionId = $sessionId
|
||||
state = $parts[3]
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
return $records
|
||||
}
|
||||
|
||||
$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 = ($rec.state -match 'Active')
|
||||
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
|
||||
}
|
||||
@@ -5,7 +5,7 @@ function Assert-Administrator {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'Run this script from an elevated PowerShell session.'
|
||||
throw 'Запустите этот скрипт из PowerShell с правами администратора.'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ function Get-ActivityWatchPackageRoot {
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $afkBinary) {
|
||||
throw "Cannot find aw-watcher-afk.exe under $ExpandedRoot."
|
||||
throw "Не удалось найти aw-watcher-afk.exe в $ExpandedRoot."
|
||||
}
|
||||
|
||||
return (Split-Path -Path (Split-Path -Path $afkBinary.FullName -Parent) -Parent)
|
||||
@@ -130,7 +130,7 @@ function Get-ActivityWatchExecutableMap {
|
||||
|
||||
foreach ($entry in $map.GetEnumerator()) {
|
||||
if (-not (Test-Path -LiteralPath $entry.Value)) {
|
||||
throw "Missing required ActivityWatch binary: $($entry.Value)"
|
||||
throw "Не найден обязательный исполняемый файл ActivityWatch: $($entry.Value)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ function Normalize-ActivityWatchUsers {
|
||||
Sort-Object -Unique
|
||||
|
||||
if (-not $normalized -or $normalized.Count -eq 0) {
|
||||
throw 'No target users resolved. Provide -Users or -UserListPath.'
|
||||
throw 'Не удалось определить целевых пользователей. Укажите -Users или -UserListPath.'
|
||||
}
|
||||
|
||||
return @($normalized)
|
||||
@@ -421,7 +421,7 @@ function Read-ActivityWatchDeploymentConfig {
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "Deployment config not found: $Path"
|
||||
throw "Конфигурация развёртывания не найдена: $Path"
|
||||
}
|
||||
|
||||
return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
||||
@@ -648,11 +648,11 @@ function Start-CollectorScriptIfNeeded {
|
||||
`$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 "Missing aw-watcher-afk.exe: `$afkExe"
|
||||
throw "Не найден aw-watcher-afk.exe: `$afkExe"
|
||||
}
|
||||
|
||||
if (`$windowEnabled -and -not (Test-Path -LiteralPath `$windowExe)) {
|
||||
throw "Missing aw-watcher-window.exe: `$windowExe"
|
||||
throw "Не найден aw-watcher-window.exe: `$windowExe"
|
||||
}
|
||||
|
||||
if (`$afkEnabled -and -not (Test-ProcessInSession -Name 'aw-watcher-afk' -SessionId `$sessionId)) {
|
||||
@@ -860,7 +860,7 @@ function Set-ActivityWatchScheduledTaskAction {
|
||||
$taskCommand = ('"{0}" {1}' -f $Execute, $Arguments)
|
||||
& schtasks.exe /Change /TN $TaskName /TR $taskCommand | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "schtasks.exe /Change failed for $TaskName"
|
||||
throw "schtasks.exe /Change завершился с ошибкой для $TaskName"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,17 +961,17 @@ function Set-ActivityWatchAcl {
|
||||
|
||||
& icacls $InstallRoot /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)(F)' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-32-545:(OI)(CI)(RX)' | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "icacls failed for $InstallRoot"
|
||||
throw "icacls завершился с ошибкой для $InstallRoot"
|
||||
}
|
||||
|
||||
& icacls $StateRoot /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)(F)' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-32-545:(OI)(CI)(RX)' | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "icacls failed for $StateRoot"
|
||||
throw "icacls завершился с ошибкой для $StateRoot"
|
||||
}
|
||||
|
||||
& icacls $LogsRoot /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)(F)' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-32-545:(OI)(CI)(M)' | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "icacls failed for $LogsRoot"
|
||||
throw "icacls завершился с ошибкой для $LogsRoot"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ function Get-DeploymentConfig {
|
||||
}
|
||||
|
||||
$deploymentConfig = Get-DeploymentConfig -Path $ConfigPath
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'ServerHost is required.' }
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'Укажите ServerHost или подготовьте deployment-config.json.' }
|
||||
$resolvedServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($deploymentConfig) { [int]$deploymentConfig.server.port } else { 5600 }
|
||||
$resolvedServerScheme = if ($ServerScheme) { $ServerScheme } elseif ($deploymentConfig) { [string]$deploymentConfig.server.scheme } else { 'http' }
|
||||
$resolvedRulesPath = if ($RulesPath) { $RulesPath } elseif ($deploymentConfig) { [string]$deploymentConfig.paths.rulesPath } else { 'C:\ProgramData\ActivityWatch\web-category-rules.json' }
|
||||
@@ -263,11 +263,11 @@ function Load-CustomCategoryRules {
|
||||
|
||||
if ($rules.Count -gt 0) {
|
||||
$script:CategoryRules = @($rules) + @($script:CategoryRules)
|
||||
Write-CollectorLog ("custom rules loaded: {0}" -f $rules.Count)
|
||||
Write-CollectorLog ("пользовательские правила загружены: {0}" -f $rules.Count)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("custom rules load failed: {0}" -f $_.Exception.Message)
|
||||
Write-CollectorLog ("не удалось загрузить пользовательские правила: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ function Load-DlpPolicy {
|
||||
param([string]$Path)
|
||||
|
||||
if (-not $Path -or -not (Test-Path -LiteralPath $Path)) {
|
||||
Write-CollectorLog ("dlp policy not found, disabled: {0}" -f $Path)
|
||||
Write-CollectorLog ("DLP-политика не найдена, DLP отключен: {0}" -f $Path)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ function Load-DlpPolicy {
|
||||
enabled = if ($rule.PSObject.Properties.Name -contains 'enabled') { [bool]$rule.enabled } else { $true }
|
||||
action = if ($rule.action) { [string]$rule.action } else { [string]$script:DlpDefaults.action }
|
||||
severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:DlpDefaults.severity }
|
||||
message = if ($rule.message) { [string]$rule.message } else { "DLP rule matched: $($rule.id)" }
|
||||
message = if ($rule.message) { [string]$rule.message } else { "Сработало DLP-правило: $($rule.id)" }
|
||||
cooldownSeconds = if ($rule.cooldownSeconds) { [int]$rule.cooldownSeconds } else { [int]$script:DlpDefaults.cooldownSeconds }
|
||||
when = [pscustomobject]@{
|
||||
domains = if ($when.PSObject.Properties.Name -contains 'domains') { @($when.domains | ForEach-Object { ([string]$_).Trim().ToLowerInvariant() } | Where-Object { $_ }) } else { @() }
|
||||
@@ -388,10 +388,10 @@ function Load-DlpPolicy {
|
||||
}
|
||||
|
||||
$script:DlpRules = @($loaded)
|
||||
Write-CollectorLog ("dlp policy loaded: enabled={0}, rules={1}" -f $script:DlpDefaults.enabled, $script:DlpRules.Count)
|
||||
Write-CollectorLog ("DLP-политика загружена: включена={0}, правил={1}" -f $script:DlpDefaults.enabled, $script:DlpRules.Count)
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("dlp policy parse failed: {0}" -f $_.Exception.Message)
|
||||
Write-CollectorLog ("не удалось разобрать DLP-политику: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +625,7 @@ function Capture-IncidentScreenshot {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("screenshot capture failed: {0}" -f $_.Exception.Message)
|
||||
Write-CollectorLog ("не удалось сделать снимок инцидента: {0}" -f $_.Exception.Message)
|
||||
return @{}
|
||||
}
|
||||
}
|
||||
@@ -775,7 +775,7 @@ function Send-CategoryHeartbeat {
|
||||
|
||||
Load-CustomCategoryRules -Path $resolvedRulesPath
|
||||
Load-DlpPolicy -Path $resolvedPolicyPath
|
||||
Write-CollectorLog ("collector started against {0}" -f $script:ApiBase)
|
||||
Write-CollectorLog ("коллектор запущен для {0}" -f $script:ApiBase)
|
||||
|
||||
while ($true) {
|
||||
try {
|
||||
@@ -815,7 +815,7 @@ while ($true) {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("collector error: {0}" -f $_.Exception.Message)
|
||||
Write-CollectorLog ("ошибка коллектора: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $resolvedPollSeconds
|
||||
|
||||
@@ -103,8 +103,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-Host 'ActivityWatch deployed for users:'
|
||||
Write-Host 'ActivityWatch развёрнут для пользователей:'
|
||||
$targetUsers | ForEach-Object { Write-Host " - $_" }
|
||||
Write-Host "Server: ${ServerScheme}://$ServerHost`:$ServerPort"
|
||||
Write-Host "State root: $StateRoot"
|
||||
Write-Host "Policy file: $($assetResult.ActivePolicy)"
|
||||
Write-Host "Сервер: ${ServerScheme}://$ServerHost`:$ServerPort"
|
||||
Write-Host "Каталог данных: $StateRoot"
|
||||
Write-Host "Файл DLP-политики: $($assetResult.ActivePolicy)"
|
||||
|
||||
@@ -46,7 +46,7 @@ $hardeningScript = Join-Path $PSScriptRoot 'hardening-recovery.ps1'
|
||||
$validationScript = Join-Path $PSScriptRoot 'validate-deployment.ps1'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $deployScript)) {
|
||||
throw "Missing script: $deployScript"
|
||||
throw "Не найден скрипт: $deployScript"
|
||||
}
|
||||
|
||||
& $deployScript `
|
||||
@@ -118,7 +118,7 @@ $report = [ordered]@{
|
||||
|
||||
if ($ValidateAfterDeploy) {
|
||||
if (-not (Test-Path -LiteralPath $validationScript)) {
|
||||
throw "Missing script: $validationScript"
|
||||
throw "Не найден скрипт: $validationScript"
|
||||
}
|
||||
|
||||
$validation = & $validationScript -ConfigPath (Join-Path $StateRoot 'deployment-config.json')
|
||||
@@ -132,6 +132,6 @@ if ($reportDirectory) {
|
||||
|
||||
$report | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $effectiveReportPath -Encoding UTF8
|
||||
|
||||
Write-Host 'ActivityWatch ensemble deploy completed.'
|
||||
Write-Host "Users: $($resolvedUsers -join ', ')"
|
||||
Write-Host "Report: $effectiveReportPath"
|
||||
Write-Host 'Комплексное развёртывание ActivityWatch завершено.'
|
||||
Write-Host "Пользователи: $($resolvedUsers -join ', ')"
|
||||
Write-Host "Отчёт: $effectiveReportPath"
|
||||
|
||||
@@ -101,9 +101,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-Host "ActivityWatch deployed for $TargetUser"
|
||||
Write-Host "Server: ${ServerScheme}://$ServerHost`:$ServerPort"
|
||||
Write-Host "Install root: $InstallRoot"
|
||||
Write-Host "State root: $StateRoot"
|
||||
Write-Host "Rules file: $($assetResult.ActiveRules)"
|
||||
Write-Host "Policy file: $($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)"
|
||||
|
||||
@@ -210,7 +210,7 @@ function Capture-IncidentScreenshot {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("screenshot capture failed: {0}" -f $_.Exception.Message)
|
||||
Write-EndpointLog ("не удалось сделать снимок инцидента: {0}" -f $_.Exception.Message)
|
||||
return @{}
|
||||
}
|
||||
}
|
||||
@@ -246,7 +246,7 @@ function Load-DlpPolicy {
|
||||
}
|
||||
|
||||
if (-not $Path -or -not (Test-Path -LiteralPath $Path)) {
|
||||
Write-EndpointLog ("policy not found, using defaults: {0}" -f $Path)
|
||||
Write-EndpointLog ("DLP-политика не найдена, используются значения по умолчанию: {0}" -f $Path)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ function Load-DlpPolicy {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("policy parse failed: {0}" -f $_.Exception.Message)
|
||||
Write-EndpointLog ("не удалось разобрать DLP-политику: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,13 +319,13 @@ function Evaluate-ClipboardRules {
|
||||
|
||||
$action = if ($rule.action) { [string]$rule.action } else { [string]$script:Policy.defaults.action }
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Clipboard rule matched: $ruleId" }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Сработало правило буфера обмена: $ruleId" }
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'clipboard' -Data @{
|
||||
clipboardHash = $ClipboardHash
|
||||
clipboardLength = $ClipboardText.Length
|
||||
}
|
||||
Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2}" -f $ruleId, $action, $severity)
|
||||
Write-EndpointLog ("инцидент буфера обмена правило={0} действие={1} важность={2}" -f $ruleId, $action, $severity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,13 +347,13 @@ function Evaluate-UsbRules {
|
||||
|
||||
$action = if ($rule.action) { [string]$rule.action } else { [string]$script:Policy.defaults.action }
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "USB rule matched: $ruleId" }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Сработало правило USB-носителя: $ruleId" }
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'usb_insert' -Data @{
|
||||
driveLetter = $DriveLetter
|
||||
volumeName = $VolumeName
|
||||
}
|
||||
Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3}" -f $ruleId, $action, $severity, $DriveLetter)
|
||||
Write-EndpointLog ("инцидент USB правило={0} действие={1} важность={2} диск={3}" -f $ruleId, $action, $severity, $DriveLetter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,14 +385,14 @@ function Evaluate-PrintRules {
|
||||
|
||||
$action = if ($rule.action) { [string]$rule.action } else { [string]$script:Policy.defaults.action }
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Print rule matched: $ruleId" }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Сработало правило печати: $ruleId" }
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'print_job' -Data @{
|
||||
printerName = $PrinterName
|
||||
documentName = $DocumentName
|
||||
owner = $Owner
|
||||
}
|
||||
Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3}" -f $ruleId, $action, $severity, $PrinterName)
|
||||
Write-EndpointLog ("инцидент печати правило={0} действие={1} важность={2} принтер={3}" -f $ruleId, $action, $severity, $PrinterName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,6 +402,52 @@ function Test-LooksLikeMojibakeQuestionMarks {
|
||||
return $Value -match '\?{2,}'
|
||||
}
|
||||
|
||||
function Test-DocumentNameNeedsFallback {
|
||||
param([AllowNull()][string]$Value)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) { return $true }
|
||||
$trimmed = $Value.Trim()
|
||||
if (Test-LooksLikeMojibakeQuestionMarks -Value $trimmed) { return $true }
|
||||
if ($trimmed -match '^[0-9]+$') { return $true }
|
||||
if ($trimmed -match '^(?i)(print document|document|local downlevel document)$') { return $true }
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-EventXmlValue {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][xml]$EventXml,
|
||||
[Parameter(Mandatory = $true)][string]$Name
|
||||
)
|
||||
|
||||
$node = $EventXml.Event.UserData.DocumentPrinted.$Name
|
||||
if ($null -ne $node) {
|
||||
return [string]$node
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function Get-PrintJobPrinterName {
|
||||
param(
|
||||
[AllowNull()][string]$JobName,
|
||||
[AllowNull()][string]$FallbackPrinterName
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($JobName)) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($FallbackPrinterName)) {
|
||||
return $FallbackPrinterName.Trim()
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
$parts = $JobName -split ',', 2
|
||||
if ($parts.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($parts[0])) {
|
||||
return $parts[0].Trim()
|
||||
}
|
||||
|
||||
return $JobName.Trim()
|
||||
}
|
||||
|
||||
function Normalize-OwnerForMatch {
|
||||
param([AllowNull()][string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) { return '' }
|
||||
@@ -469,13 +515,50 @@ function Get-PrintServiceEventSummary {
|
||||
$propertyValues += [string]$prop.Value
|
||||
}
|
||||
|
||||
$xml = $null
|
||||
try {
|
||||
$xml = [xml]$Event.ToXml()
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
$jobId = ''
|
||||
$documentName = ''
|
||||
$owner = ''
|
||||
$portName = ''
|
||||
$printerName = ''
|
||||
$sizeBytes = ''
|
||||
$pageCount = ''
|
||||
|
||||
if ($xml) {
|
||||
$jobId = Get-EventXmlValue -EventXml $xml -Name 'Param1'
|
||||
$documentName = Get-EventXmlValue -EventXml $xml -Name 'Param2'
|
||||
$owner = Get-EventXmlValue -EventXml $xml -Name 'Param3'
|
||||
$portName = Get-EventXmlValue -EventXml $xml -Name 'Param4'
|
||||
$printerName = Get-EventXmlValue -EventXml $xml -Name 'Param5'
|
||||
$sizeBytes = Get-EventXmlValue -EventXml $xml -Name 'Param7'
|
||||
$pageCount = Get-EventXmlValue -EventXml $xml -Name 'Param8'
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($jobId) -and $props.Count -ge 1) { $jobId = [string]$props[0].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($documentName) -and $props.Count -ge 2) { $documentName = [string]$props[1].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($owner) -and $props.Count -ge 3) { $owner = [string]$props[2].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($portName) -and $props.Count -ge 4) { $portName = [string]$props[3].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($printerName) -and $props.Count -ge 5) { $printerName = [string]$props[4].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($sizeBytes) -and $props.Count -ge 7) { $sizeBytes = [string]$props[6].Value }
|
||||
if ([string]::IsNullOrWhiteSpace($pageCount) -and $props.Count -ge 8) { $pageCount = [string]$props[7].Value }
|
||||
|
||||
[pscustomobject]@{
|
||||
RecordId = [string]$Event.RecordId
|
||||
TimeCreated = if ($Event.TimeCreated) { $Event.TimeCreated.ToString('o') } else { '' }
|
||||
PropertyCount = $props.Count
|
||||
DocumentName = if ($props.Count -ge 1) { [string]$props[0].Value } else { '' }
|
||||
Owner = if ($props.Count -ge 2) { [string]$props[1].Value } else { '' }
|
||||
PrinterName = if ($props.Count -ge 4) { [string]$props[3].Value } else { '' }
|
||||
JobId = $jobId
|
||||
DocumentName = $documentName
|
||||
Owner = $owner
|
||||
PortName = $portName
|
||||
PrinterName = $printerName
|
||||
SizeBytes = $sizeBytes
|
||||
PageCount = $pageCount
|
||||
PropertyValues = $propertyValues
|
||||
}
|
||||
}
|
||||
@@ -488,7 +571,7 @@ function Get-PrintServiceDocumentFallback {
|
||||
)
|
||||
|
||||
$preferred = [string]$EventSummary.DocumentName
|
||||
if (-not (Test-LooksLikeMojibakeQuestionMarks -Value $preferred) -and $preferred -notmatch '^[0-9]+$') {
|
||||
if (-not (Test-DocumentNameNeedsFallback -Value $preferred)) {
|
||||
return $preferred
|
||||
}
|
||||
|
||||
@@ -499,9 +582,10 @@ function Get-PrintServiceDocumentFallback {
|
||||
$candidate = [string]$value
|
||||
if ([string]::IsNullOrWhiteSpace($candidate)) { continue }
|
||||
if ($candidate -eq $preferred) { continue }
|
||||
if ($EventSummary.JobId -and $candidate -eq [string]$EventSummary.JobId) { continue }
|
||||
if ($Owner -and $candidate -like "*$Owner*") { continue }
|
||||
if ($PrinterName -and $candidate -like "*$PrinterName*") { continue }
|
||||
if (Test-LooksLikeMojibakeQuestionMarks -Value $candidate) { continue }
|
||||
if (Test-DocumentNameNeedsFallback -Value $candidate) { continue }
|
||||
|
||||
if ($candidate -match '[\\/:]' -and $candidate -match '\.[A-Za-z0-9]{1,8}$') {
|
||||
$pathCandidates.Add($candidate)
|
||||
@@ -546,7 +630,7 @@ function Write-PrintServiceEventTrace {
|
||||
}
|
||||
|
||||
Write-EndpointLog (
|
||||
'printservice-307 phase={0} recordId={1} time={2} owner={3} printer={4} document={5} resolved={6} properties=[{7}] reason={8}' -f
|
||||
'printservice-307 этап={0} recordId={1} время={2} владелец={3} принтер={4} документ={5} итоговыйДокумент={6} свойства=[{7}] причина={8}' -f
|
||||
$Phase,
|
||||
$EventSummary.RecordId,
|
||||
$EventSummary.TimeCreated,
|
||||
@@ -561,6 +645,7 @@ function Write-PrintServiceEventTrace {
|
||||
|
||||
function Get-BetterDocumentNameFromPrintServiceEvents {
|
||||
param(
|
||||
[string]$JobId,
|
||||
[string]$Owner,
|
||||
[string]$PrinterName
|
||||
)
|
||||
@@ -578,32 +663,41 @@ function Get-BetterDocumentNameFromPrintServiceEvents {
|
||||
$summary = Get-PrintServiceEventSummary -Event $event
|
||||
$resolvedDocument = Get-PrintServiceDocumentFallback -EventSummary $summary -Owner $Owner -PrinterName $PrinterName
|
||||
|
||||
$jobMatches = if ($JobId) { [string]$summary.JobId -eq [string]$JobId } else { $true }
|
||||
$ownerMatches = if ($Owner) { Test-OwnerLooseMatch -Expected $Owner -Actual $summary.Owner } else { $true }
|
||||
$printerMatches = if ($PrinterName) { Test-PrinterLooseMatch -Expected $PrinterName -Actual $summary.PrinterName } else { $true }
|
||||
|
||||
if ($pass -eq 'strict') {
|
||||
if ($JobId -and -not $jobMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-jobid-strict' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
if ($Owner -and -not $ownerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'owner-mismatch-strict' -ResolvedDocument $resolvedDocument
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-владельца-strict' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
if ($PrinterName -and -not $printerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'printer-mismatch-strict' -ResolvedDocument $resolvedDocument
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-принтера-strict' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($Owner -and $PrinterName -and -not $ownerMatches -and -not $printerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'owner-and-printer-mismatch-relaxed' -ResolvedDocument $resolvedDocument
|
||||
if ($JobId -and (-not $jobMatches) -and $Owner -and $PrinterName -and -not $ownerMatches -and -not $printerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-владельца-и-принтера-relaxed' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
if ((-not $JobId) -and $Owner -and $PrinterName -and -not $ownerMatches -and -not $printerMatches) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason 'несовпадение-владельца-и-принтера-relaxed' -ResolvedDocument $resolvedDocument
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($resolvedDocument)) {
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason ('no-document-candidate-' + $pass) -ResolvedDocument ''
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'scan' -MatchReason ('нет-кандидата-документа-' + $pass) -ResolvedDocument ''
|
||||
continue
|
||||
}
|
||||
|
||||
$matchReasonBase = if (Test-LooksLikeMojibakeQuestionMarks -Value $summary.DocumentName) { 'fallback-used' } else { 'direct' }
|
||||
$matchReasonBase = if (Test-DocumentNameNeedsFallback -Value $summary.DocumentName) { 'использован-резервный-вариант' } else { 'напрямую' }
|
||||
Write-PrintServiceEventTrace -EventSummary $summary -Phase 'selected' -MatchReason ($matchReasonBase + '-' + $pass) -ResolvedDocument $resolvedDocument
|
||||
return $resolvedDocument
|
||||
}
|
||||
@@ -616,7 +710,7 @@ function Get-BetterDocumentNameFromPrintServiceEvents {
|
||||
}
|
||||
|
||||
$deploymentConfig = Get-DeploymentConfig -Path $ConfigPath
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'ServerHost is required.' }
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'Укажите ServerHost или подготовьте deployment-config.json.' }
|
||||
$resolvedServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($deploymentConfig) { [int]$deploymentConfig.server.port } else { 5600 }
|
||||
$resolvedServerScheme = if ($ServerScheme) { $ServerScheme } elseif ($deploymentConfig) { [string]$deploymentConfig.server.scheme } else { 'http' }
|
||||
$resolvedPolicyPath = if ($PolicyPath) { $PolicyPath } elseif ($deploymentConfig -and $deploymentConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$deploymentConfig.paths.policyPath } else { 'C:\ProgramData\ActivityWatch\dlp-policy.json' }
|
||||
@@ -648,7 +742,7 @@ $script:IncidentScreenshotEnabled = $resolvedIncidentScreenshotEnabled
|
||||
$script:ScreenshotTypesLoaded = $false
|
||||
|
||||
Load-DlpPolicy -Path $resolvedPolicyPath
|
||||
Write-EndpointLog ("endpoint collector started against {0}" -f $script:ApiBase)
|
||||
Write-EndpointLog ("endpoint-коллектор запущен для {0}" -f $script:ApiBase)
|
||||
|
||||
while ($true) {
|
||||
try {
|
||||
@@ -709,13 +803,13 @@ while ($true) {
|
||||
if ($script:SeenPrintJob.ContainsKey($jobId)) { continue }
|
||||
$script:SeenPrintJob[$jobId] = (Get-Date).ToUniversalTime()
|
||||
|
||||
$printerName = [string]$job.Name
|
||||
$printerName = Get-PrintJobPrinterName -JobName ([string]$job.Name) -FallbackPrinterName ([string]$job.DriverName)
|
||||
$documentName = [string]$job.Document
|
||||
$owner = [string]$job.Owner
|
||||
$documentNameOriginal = $documentName
|
||||
|
||||
if (Test-LooksLikeMojibakeQuestionMarks -Value $documentName) {
|
||||
$eventDocumentName = Get-BetterDocumentNameFromPrintServiceEvents -Owner $owner -PrinterName $printerName
|
||||
if (Test-DocumentNameNeedsFallback -Value $documentName) {
|
||||
$eventDocumentName = Get-BetterDocumentNameFromPrintServiceEvents -JobId $jobId -Owner $owner -PrinterName $printerName
|
||||
if ($eventDocumentName) {
|
||||
$documentName = $eventDocumentName
|
||||
}
|
||||
@@ -726,6 +820,7 @@ while ($true) {
|
||||
documentName = $documentName
|
||||
documentNameOriginal = $documentNameOriginal
|
||||
owner = $owner
|
||||
printJobId = $jobId
|
||||
}
|
||||
Evaluate-PrintRules -PrinterName $printerName -DocumentName $documentName -Owner $owner
|
||||
}
|
||||
@@ -789,7 +884,7 @@ while ($true) {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("collector error: {0}" -f $_.Exception.Message)
|
||||
Write-EndpointLog ("ошибка коллектора: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $resolvedPollSeconds
|
||||
|
||||
@@ -42,7 +42,7 @@ if (Test-Path -LiteralPath $ConfigPath) {
|
||||
}
|
||||
|
||||
if (-not $existingConfig -and (-not $ServerHost)) {
|
||||
throw 'deployment-config.json is missing. Provide -ServerHost and user parameters, or run a deploy script first.'
|
||||
throw 'deployment-config.json отсутствует. Укажите -ServerHost и параметры пользователей либо сначала выполните скрипт развёртывания.'
|
||||
}
|
||||
|
||||
$effectiveStateRoot = if ($StateRoot) { $StateRoot } elseif ($existingConfig) { [string]$existingConfig.paths.stateRoot } else { 'C:\ProgramData\ActivityWatch' }
|
||||
@@ -78,7 +78,7 @@ elseif ($existingConfig) {
|
||||
@($existingConfig.userTasks | ForEach-Object { [string]$_.userId })
|
||||
}
|
||||
else {
|
||||
throw 'Target users are missing.'
|
||||
throw 'Не указаны целевые пользователи.'
|
||||
}
|
||||
|
||||
New-ActivityWatchDirectory -Path $effectiveStateRoot
|
||||
@@ -96,6 +96,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') `
|
||||
-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') `
|
||||
-StateRoot $effectiveStateRoot `
|
||||
@@ -139,6 +140,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-Host 'ActivityWatch hardening/recovery completed.'
|
||||
Write-Host "Config: $effectiveConfigPath"
|
||||
Write-Host "Users repaired: $($effectiveUsers -join ', ')"
|
||||
Write-Host 'Укрепление и восстановление ActivityWatch завершены.'
|
||||
Write-Host "Конфигурация: $effectiveConfigPath"
|
||||
Write-Host "Пользователи восстановлены: $($effectiveUsers -join ', ')"
|
||||
|
||||
@@ -65,7 +65,7 @@ $tasks = foreach ($taskName in $taskNames) {
|
||||
else {
|
||||
[pscustomobject]@{
|
||||
taskName = $taskName
|
||||
state = 'Missing'
|
||||
state = 'Отсутствует'
|
||||
present = $false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ function Get-Config {
|
||||
param([string]$Path)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "Config not found: $Path"
|
||||
throw "Конфигурация не найдена: $Path"
|
||||
}
|
||||
|
||||
Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
||||
|
||||
Reference in New Issue
Block a user