hardening: strengthen deploy and validation gates
This commit is contained in:
@@ -330,6 +330,10 @@
|
|||||||
AW_DLP_INFLUX_TOKEN={{ aw_dlp_influx_token | default('') }}
|
AW_DLP_INFLUX_TOKEN={{ aw_dlp_influx_token | default('') }}
|
||||||
AW_DLP_AW_API_BASE=http://127.0.0.1:5600/api/0
|
AW_DLP_AW_API_BASE=http://127.0.0.1:5600/api/0
|
||||||
AW_DLP_CASE_API_BASE=http://127.0.0.1:5602/api/0/dlp/cases
|
AW_DLP_CASE_API_BASE=http://127.0.0.1:5602/api/0/dlp/cases
|
||||||
|
AW_EXPECT_START_OF_DAY={{ aw_worktime_start_of_day | default('') }}
|
||||||
|
AW_EXPECT_ALWAYS_ACTIVE_PATTERN={{ aw_server_always_active_pattern | default('') }}
|
||||||
|
AW_EXPECT_LANDINGPAGE={{ aw_server_landingpage | default('') }}
|
||||||
|
AW_HEALTH_STRICT_FILEOPS={{ aw_health_strict_fileops | default(0) }}
|
||||||
|
|
||||||
- name: Создать каталог DLP policy engine
|
- name: Создать каталог DLP policy engine
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -265,6 +265,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Force managed collectors/watchers to reload the freshly deployed scripts.
|
||||||
|
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object {
|
||||||
|
(
|
||||||
|
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and
|
||||||
|
$_.CommandLine -match 'C:\\ProgramData\\AWatch-rus\\' -and
|
||||||
|
$_.CommandLine -match '(collector|launch-watchers|recovery-loop)\.ps1'
|
||||||
|
) -or
|
||||||
|
($_.Name -ieq 'aw-watcher-afk.exe') -or
|
||||||
|
($_.Name -ieq 'aw-watcher-window.exe')
|
||||||
|
} |
|
||||||
|
ForEach-Object {
|
||||||
|
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
Start-Sleep -Seconds 2
|
Start-Sleep -Seconds 2
|
||||||
|
|
||||||
# Ensure tasks are enabled (some environments keep them disabled, causing "0s" in WebUI).
|
# Ensure tasks are enabled (some environments keep them disabled, causing "0s" in WebUI).
|
||||||
@@ -277,9 +292,15 @@
|
|||||||
try { Enable-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue | Out-Null } catch {}
|
try { Enable-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue | Out-Null } catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
|
$recoveryTask = Get-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" -ErrorAction SilentlyContinue
|
||||||
|
if ($recoveryTask -and $recoveryTask.State -notin @('Running', 'Queued')) {
|
||||||
|
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
|
||||||
|
}
|
||||||
foreach ($taskDef in @($config.userTasks)) {
|
foreach ($taskDef in @($config.userTasks)) {
|
||||||
Start-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue
|
$launchTask = Get-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue
|
||||||
|
if ($launchTask -and $launchTask.State -notin @('Running', 'Queued')) {
|
||||||
|
Start-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- name: Получить Windows hostname для AW smoke-check bucket
|
- name: Получить Windows hostname для AW smoke-check bucket
|
||||||
@@ -289,17 +310,16 @@
|
|||||||
register: aw_windows_hostname_result
|
register: aw_windows_hostname_result
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: Вычислить AW AFK smoke-check bucket
|
- name: Вычислить AW worktime smoke-check bucket
|
||||||
when:
|
when:
|
||||||
- aw_windows_api_smoke_check_enabled | bool
|
- aw_windows_api_smoke_check_enabled | bool
|
||||||
- aw_windows_afk_enabled_effective | bool
|
|
||||||
- aw_windows_hostname_result.stdout is defined
|
- aw_windows_hostname_result.stdout is defined
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
aw_windows_api_smoke_check_bucket_effective: >-
|
aw_windows_api_smoke_check_bucket_effective: >-
|
||||||
{{
|
{{
|
||||||
aw_windows_api_smoke_check_bucket
|
aw_windows_api_smoke_check_bucket
|
||||||
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
|
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
|
||||||
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
|
else 'aw-worktime-sessions_' ~ (aw_windows_hostname_result.stdout | trim)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
- name: Вычислить AW Window smoke-check bucket
|
- name: Вычислить AW Window smoke-check bucket
|
||||||
@@ -316,10 +336,9 @@
|
|||||||
else 'aw-watcher-window_' ~ (aw_windows_hostname_result.stdout | trim)
|
else 'aw-watcher-window_' ~ (aw_windows_hostname_result.stdout | trim)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
- name: Выполнить AW API smoke-check (AFK bucket должен получать события)
|
- name: Выполнить AW API smoke-check (worktime bucket должен получать события)
|
||||||
when:
|
when:
|
||||||
- aw_windows_api_smoke_check_enabled | bool
|
- aw_windows_api_smoke_check_enabled | bool
|
||||||
- aw_windows_afk_enabled_effective | bool
|
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
|
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
|
||||||
method: GET
|
method: GET
|
||||||
@@ -330,6 +349,12 @@
|
|||||||
until: >-
|
until: >-
|
||||||
(aw_windows_api_smoke_result.status | default(0)) == 200
|
(aw_windows_api_smoke_result.status | default(0)) == 200
|
||||||
and ((aw_windows_api_smoke_result.json | default([])) | length) >= aw_windows_api_smoke_check_min_events
|
and ((aw_windows_api_smoke_result.json | default([])) | length) >= aw_windows_api_smoke_check_min_events
|
||||||
|
and (
|
||||||
|
(aw_windows_api_smoke_result.json | default([]))
|
||||||
|
| selectattr('data.source', 'equalto', 'worktime-session-collector')
|
||||||
|
| list
|
||||||
|
| length
|
||||||
|
) > 0
|
||||||
retries: 12
|
retries: 12
|
||||||
delay: 5
|
delay: 5
|
||||||
ignore_errors: "{{ (not aw_windows_fail_on_validation_error | bool) }}"
|
ignore_errors: "{{ (not aw_windows_fail_on_validation_error | bool) }}"
|
||||||
@@ -363,7 +388,7 @@
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$result = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
|
$result = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
|
||||||
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
|
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
|
||||||
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
|
$result | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
|
||||||
return $result
|
return $result
|
||||||
|
|
||||||
- name: Создать локальную директорию для отчётов валидации
|
- name: Создать локальную директорию для отчётов валидации
|
||||||
@@ -386,7 +411,8 @@
|
|||||||
with open('{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json', 'r', encoding='utf-8-sig') as f:
|
with open('{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json', 'r', encoding='utf-8-sig') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
if not data.get('overallOk', False):
|
if not data.get('overallOk', False):
|
||||||
print(f"Validation failed for {{ inventory_hostname }}: {data.get('summary', 'Unknown error')}")
|
failed = ", ".join(data.get("summary", {}).get("failedSections", [])) or "unknown"
|
||||||
|
print(f"Validation failed for {{ inventory_hostname }}: {failed}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
PY
|
PY
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ aw_worktime_to: "17:00"
|
|||||||
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
|
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
|
||||||
aw_server_always_active_pattern: "aw-watcher-window"
|
aw_server_always_active_pattern: "aw-watcher-window"
|
||||||
aw_server_landingpage: "/activity/SHARKON2025/view/"
|
aw_server_landingpage: "/activity/SHARKON2025/view/"
|
||||||
|
aw_health_strict_fileops: 0
|
||||||
|
|
||||||
aw_dlp_policy_engine_enabled: true
|
aw_dlp_policy_engine_enabled: true
|
||||||
aw_dlp_policy_engine_bind_host: "0.0.0.0"
|
aw_dlp_policy_engine_bind_host: "0.0.0.0"
|
||||||
|
|||||||
@@ -58,3 +58,6 @@ aw_server_post_deploy_health_check_enabled: true
|
|||||||
aw_worktime_from: "00:00"
|
aw_worktime_from: "00:00"
|
||||||
aw_worktime_to: "17:00"
|
aw_worktime_to: "17:00"
|
||||||
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
|
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
|
||||||
|
aw_server_always_active_pattern: "aw-watcher-window"
|
||||||
|
aw_server_landingpage: "/activity/SHARKON2025/view/"
|
||||||
|
aw_health_strict_fileops: 0
|
||||||
|
|||||||
@@ -18,16 +18,21 @@
|
|||||||
aw_windows_api_smoke_check_enabled: true
|
aw_windows_api_smoke_check_enabled: true
|
||||||
aw_windows_api_smoke_check_bucket: ""
|
aw_windows_api_smoke_check_bucket: ""
|
||||||
aw_windows_api_smoke_check_limit: 10
|
aw_windows_api_smoke_check_limit: 10
|
||||||
|
aw_windows_api_smoke_check_wait_seconds: 20
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Принудительно запустить ActivityWatch recovery и launch tasks
|
- name: Аккуратно запустить ActivityWatch recovery и launch tasks, если они не в Running
|
||||||
when: aw_windows_force_task_restart | bool
|
when: aw_windows_force_task_restart | bool
|
||||||
ansible.windows.win_powershell:
|
ansible.windows.win_powershell:
|
||||||
script: |
|
script: |
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
|
$recovery = Get-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" -ErrorAction SilentlyContinue
|
||||||
|
if ($recovery -and $recovery.State -notin @('Running', 'Queued')) {
|
||||||
|
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
|
||||||
|
}
|
||||||
Get-ScheduledTask |
|
Get-ScheduledTask |
|
||||||
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
|
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
|
||||||
|
Where-Object State -notin @('Running', 'Queued') |
|
||||||
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
|
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
|
||||||
|
|
||||||
- name: Получить Windows hostname для AW smoke-check bucket
|
- name: Получить Windows hostname для AW smoke-check bucket
|
||||||
@@ -43,10 +48,15 @@
|
|||||||
{{
|
{{
|
||||||
aw_windows_api_smoke_check_bucket
|
aw_windows_api_smoke_check_bucket
|
||||||
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
|
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
|
||||||
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
|
else 'aw-worktime-sessions_' ~ (aw_windows_hostname_result.stdout | trim)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
- name: Дождаться свежих AFK событий на AW server
|
- name: Дождаться короткого окна стабилизации после перезапуска задач
|
||||||
|
when: aw_windows_api_smoke_check_enabled | bool
|
||||||
|
ansible.builtin.pause:
|
||||||
|
seconds: "{{ aw_windows_api_smoke_check_wait_seconds | int }}"
|
||||||
|
|
||||||
|
- name: Дождаться свежих worktime-событий на AW server
|
||||||
when: aw_windows_api_smoke_check_enabled | bool
|
when: aw_windows_api_smoke_check_enabled | bool
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
@@ -60,7 +70,7 @@
|
|||||||
(aw_windows_api_smoke.json | length) > 0 and
|
(aw_windows_api_smoke.json | length) > 0 and
|
||||||
(
|
(
|
||||||
aw_windows_api_smoke.json
|
aw_windows_api_smoke.json
|
||||||
| selectattr('data.status', 'equalto', 'not-afk')
|
| selectattr('data.source', 'equalto', 'worktime-session-collector')
|
||||||
| list
|
| list
|
||||||
| length
|
| length
|
||||||
) > 0
|
) > 0
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ AW_LOG_TO_FILE=true
|
|||||||
# Health Check Configuration
|
# Health Check Configuration
|
||||||
AW_HEALTH_CHECK_ENABLED=true
|
AW_HEALTH_CHECK_ENABLED=true
|
||||||
AW_HEALTH_CHECK_INTERVAL=60
|
AW_HEALTH_CHECK_INTERVAL=60
|
||||||
|
AW_EXPECT_START_OF_DAY=00:00
|
||||||
|
AW_EXPECT_ALWAYS_ACTIVE_PATTERN=aw-watcher-window
|
||||||
|
AW_EXPECT_LANDINGPAGE=/activity/SHARKON2025/view/
|
||||||
|
AW_HEALTH_STRICT_FILEOPS=0
|
||||||
|
|
||||||
# Integration Test Configuration
|
# Integration Test Configuration
|
||||||
AW_INTEGRATION_TEST_ENABLED=false
|
AW_INTEGRATION_TEST_ENABLED=false
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ SERVICES=("activitywatch-server" "aw-worktime-api" "aw-worktime-ui-bridge")
|
|||||||
UNHEALTHY_SERVICES=()
|
UNHEALTHY_SERVICES=()
|
||||||
WARNINGS=()
|
WARNINGS=()
|
||||||
|
|
||||||
|
if [[ -f /etc/activitywatch/aw-server.env ]]; then
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/activitywatch/aw-server.env
|
||||||
|
fi
|
||||||
|
|
||||||
check_service() {
|
check_service() {
|
||||||
local service=$1
|
local service=$1
|
||||||
if [[ "$service" == "aw-worktime-ui-bridge" ]]; then
|
if [[ "$service" == "aw-worktime-ui-bridge" ]]; then
|
||||||
@@ -39,6 +44,38 @@ check_api_endpoint() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
read_setting_value() {
|
||||||
|
local key=$1
|
||||||
|
curl -fsS --max-time 10 "http://127.0.0.1:5600/api/0/settings/${key}" 2>/dev/null | \
|
||||||
|
python3 -c 'import json,sys; print(json.load(sys.stdin))'
|
||||||
|
}
|
||||||
|
|
||||||
|
check_expected_setting() {
|
||||||
|
local key=$1
|
||||||
|
local expected=$2
|
||||||
|
local label=${3:-$1}
|
||||||
|
|
||||||
|
if [[ -z "$expected" ]]; then
|
||||||
|
echo "⚠ expected value for ${label} is not configured, skipping drift check"
|
||||||
|
WARNINGS+=("${key}-expected-missing")
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local actual
|
||||||
|
if ! actual="$(read_setting_value "$key" 2>/dev/null)"; then
|
||||||
|
echo "✗ failed to read setting ${label}"
|
||||||
|
UNHEALTHY_SERVICES+=("setting-${key}")
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$actual" == "$expected" ]]; then
|
||||||
|
echo "✓ ${label} matches expected value (${expected})"
|
||||||
|
else
|
||||||
|
echo "✗ ${label} drift detected: actual='${actual}' expected='${expected}'"
|
||||||
|
UNHEALTHY_SERVICES+=("setting-${key}")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
check_dlp_transport_freshness() {
|
check_dlp_transport_freshness() {
|
||||||
local dlp_health="${DLP_HEALTH_BIN:-/usr/local/bin/dlp-health-check}"
|
local dlp_health="${DLP_HEALTH_BIN:-/usr/local/bin/dlp-health-check}"
|
||||||
local result
|
local result
|
||||||
@@ -92,6 +129,9 @@ echo
|
|||||||
check_api_endpoint "http://127.0.0.1:5600/api/0/info" "activitywatch-server"
|
check_api_endpoint "http://127.0.0.1:5600/api/0/info" "activitywatch-server"
|
||||||
check_api_endpoint "http://127.0.0.1:5610/reports/worktime/today" "aw-worktime-api"
|
check_api_endpoint "http://127.0.0.1:5610/reports/worktime/today" "aw-worktime-api"
|
||||||
check_dlp_transport_freshness "http://127.0.0.1:5600/api/0" "900" "${AW_HEALTH_STRICT_FILEOPS:-0}"
|
check_dlp_transport_freshness "http://127.0.0.1:5600/api/0" "900" "${AW_HEALTH_STRICT_FILEOPS:-0}"
|
||||||
|
check_expected_setting "startOfDay" "${AW_EXPECT_START_OF_DAY:-}" "startOfDay"
|
||||||
|
check_expected_setting "always_active_pattern" "${AW_EXPECT_ALWAYS_ACTIVE_PATTERN:-}" "always_active_pattern"
|
||||||
|
check_expected_setting "landingpage" "${AW_EXPECT_LANDINGPAGE:-}" "landingpage"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|||||||
+414
-95
@@ -22,74 +22,19 @@ $policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath')
|
|||||||
$policyClientScript = if ($config.paths.PSObject.Properties.Name -contains 'policyClientScript') { [string]$config.paths.policyClientScript } else { Join-Path $stateRoot 'dlp-policy-client.ps1' }
|
$policyClientScript = if ($config.paths.PSObject.Properties.Name -contains 'policyClientScript') { [string]$config.paths.policyClientScript } else { Join-Path $stateRoot 'dlp-policy-client.ps1' }
|
||||||
$launchScript = [string]$config.paths.launchScript
|
$launchScript = [string]$config.paths.launchScript
|
||||||
$recoveryScript = [string]$config.paths.recoveryScript
|
$recoveryScript = [string]$config.paths.recoveryScript
|
||||||
|
$awHostname = if ($config.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$config.awHostname)) { [string]$config.awHostname } else { [string]$env:COMPUTERNAME }
|
||||||
|
$serverUrl = '{0}://{1}:{2}' -f [string]$config.server.scheme, [string]$config.server.host, [int]$config.server.port
|
||||||
|
$apiBase = "$serverUrl/api/0"
|
||||||
|
$pollSeconds = if ($config.PSObject.Properties.Name -contains 'collector' -and $config.collector.PSObject.Properties.Name -contains 'pollSeconds') { [int]$config.collector.pollSeconds } else { 5 }
|
||||||
|
$pulseSeconds = if ($config.PSObject.Properties.Name -contains 'collector' -and $config.collector.PSObject.Properties.Name -contains 'pulseSeconds') { [int]$config.collector.pulseSeconds } else { [Math]::Max($pollSeconds * 3, 30) }
|
||||||
|
$freshnessSeconds = [Math]::Max($pollSeconds * 3, 30)
|
||||||
|
$sessionFreshnessSeconds = [Math]::Max($pollSeconds * 4, 45)
|
||||||
|
$transportStaleSeconds = [Math]::Max($pollSeconds * 12, 180)
|
||||||
|
$queueMaxDepth = 1000
|
||||||
|
|
||||||
$afkExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'afkEnabled') { [bool]$config.collectors.afkEnabled } else { $true }
|
$afkExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'afkEnabled') { [bool]$config.collectors.afkEnabled } else { $true }
|
||||||
$windowExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'windowEnabled') { [bool]$config.collectors.windowEnabled } else { $true }
|
$windowExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'windowEnabled') { [bool]$config.collectors.windowEnabled } else { $true }
|
||||||
$fileOpsExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'fileOpsEnabled') { [bool]$config.collectors.fileOpsEnabled } else { $true }
|
$fileOpsExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'fileOpsEnabled') { [bool]$config.collectors.fileOpsEnabled } else { $true }
|
||||||
$printServiceOperationalEnabled = $false
|
|
||||||
try {
|
|
||||||
$printServiceLog = Get-WinEvent -ListLog 'Microsoft-Windows-PrintService/Operational' -ErrorAction Stop
|
|
||||||
$printServiceOperationalEnabled = [bool]$printServiceLog.IsEnabled
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
}
|
|
||||||
$printJobTitlePolicyEnabled = $false
|
|
||||||
try {
|
|
||||||
$printPolicy = Get-ItemProperty -LiteralPath 'HKLM:\Software\Policies\Microsoft\Windows NT\Printers' -Name 'ShowJobTitleInEventLogs' -ErrorAction Stop
|
|
||||||
$printJobTitlePolicyEnabled = ([int]$printPolicy.ShowJobTitleInEventLogs -eq 1)
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
}
|
|
||||||
$requiredFiles = @(
|
|
||||||
$collectorScript,
|
|
||||||
$endpointCollectorScript,
|
|
||||||
$sessionCollectorScript,
|
|
||||||
$evtxExportScript,
|
|
||||||
$rulesPath,
|
|
||||||
$policyPath,
|
|
||||||
$policyClientScript,
|
|
||||||
$launchScript,
|
|
||||||
$recoveryScript,
|
|
||||||
$ConfigPath
|
|
||||||
)
|
|
||||||
if ($fileOpsExpected) {
|
|
||||||
$requiredFiles += $fileCollectorScript
|
|
||||||
}
|
|
||||||
if ($afkExpected) {
|
|
||||||
$requiredFiles += (Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe')
|
|
||||||
}
|
|
||||||
if ($windowExpected) {
|
|
||||||
$requiredFiles += (Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe')
|
|
||||||
}
|
|
||||||
|
|
||||||
$missingFiles = @(
|
|
||||||
$requiredFiles |
|
|
||||||
Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } |
|
|
||||||
Where-Object { -not (Test-Path -LiteralPath $_) }
|
|
||||||
)
|
|
||||||
|
|
||||||
$processNames = @()
|
|
||||||
if ($afkExpected) { $processNames += 'aw-watcher-afk' }
|
|
||||||
if ($windowExpected) { $processNames += 'aw-watcher-window' }
|
|
||||||
$runningProcesses = @()
|
|
||||||
if ($processNames.Count -gt 0) {
|
|
||||||
$runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId
|
|
||||||
}
|
|
||||||
$sessionCollectorProcesses = @(
|
|
||||||
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
|
||||||
Where-Object {
|
|
||||||
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and
|
|
||||||
$_.CommandLine -match [Regex]::Escape($sessionCollectorScript)
|
|
||||||
} |
|
|
||||||
Select-Object Name, ProcessId, SessionId, CommandLine
|
|
||||||
)
|
|
||||||
|
|
||||||
$taskNames = @()
|
|
||||||
if ($config.userTasks) {
|
|
||||||
$taskNames += @($config.userTasks | ForEach-Object { [string]$_.launchTaskName })
|
|
||||||
}
|
|
||||||
$taskNames += [string]$config.recovery.taskName
|
|
||||||
$taskNames = $taskNames | Sort-Object -Unique
|
|
||||||
|
|
||||||
function Get-LoggedOnUsers {
|
function Get-LoggedOnUsers {
|
||||||
$users = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
$users = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
@@ -122,6 +67,7 @@ function Test-UserHasSession {
|
|||||||
[string]$UserId,
|
[string]$UserId,
|
||||||
[string[]]$LoggedOnUsers
|
[string[]]$LoggedOnUsers
|
||||||
)
|
)
|
||||||
|
|
||||||
if ([string]::IsNullOrWhiteSpace($UserId)) { return $false }
|
if ([string]::IsNullOrWhiteSpace($UserId)) { return $false }
|
||||||
$candidateIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
$candidateIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
[void]$candidateIds.Add($UserId)
|
[void]$candidateIds.Add($UserId)
|
||||||
@@ -140,42 +86,380 @@ function Test-UserHasSession {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-CollectorProcesses {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$ScriptPath
|
||||||
|
)
|
||||||
|
|
||||||
|
return @(
|
||||||
|
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object {
|
||||||
|
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and
|
||||||
|
$_.CommandLine -and
|
||||||
|
$_.CommandLine -match [Regex]::Escape($ScriptPath)
|
||||||
|
} |
|
||||||
|
Select-Object @{ Name = 'Name'; Expression = { $_.Name } }, @{ Name = 'Id'; Expression = { [int]$_.ProcessId } }, @{ Name = 'SessionId'; Expression = { [int]$_.SessionId } }, @{ Name = 'CommandLine'; Expression = { [string]$_.CommandLine } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-DuplicateProcessGroups {
|
||||||
|
param(
|
||||||
|
[object[]]$Processes,
|
||||||
|
[bool]$PerSession = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $Processes -or @($Processes).Count -eq 0) { return @() }
|
||||||
|
$groups = if ($PerSession) {
|
||||||
|
$Processes | Group-Object -Property Name, SessionId
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$Processes | Group-Object -Property Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return @(
|
||||||
|
$groups |
|
||||||
|
Where-Object { $_.Count -gt 1 } |
|
||||||
|
ForEach-Object {
|
||||||
|
[pscustomobject]@{
|
||||||
|
name = [string]$_.Name
|
||||||
|
count = [int]$_.Count
|
||||||
|
members = @($_.Group | Select-Object Name, Id, SessionId, CommandLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Convert-ToUtcDate {
|
||||||
|
param($Value)
|
||||||
|
|
||||||
|
if ($null -eq $Value) { return $null }
|
||||||
|
try {
|
||||||
|
return ([DateTimeOffset]::Parse([string]$Value)).UtcDateTime
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-BucketHealth {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$BucketId,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$MaxAgeSeconds,
|
||||||
|
[bool]$Required = $true,
|
||||||
|
[bool]$RequireFreshEvent = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
$events = @()
|
||||||
|
$queryOk = $false
|
||||||
|
$errorMessage = $null
|
||||||
|
try {
|
||||||
|
$response = Invoke-RestMethod -Method Get -Uri "$apiBase/buckets/$BucketId/events?limit=25" -TimeoutSec 15 -DisableKeepAlive -ErrorAction Stop
|
||||||
|
$events = @($response)
|
||||||
|
$queryOk = $true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$errorMessage = $_.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
$latestTimestampUtc = $null
|
||||||
|
$ageSeconds = $null
|
||||||
|
if ($events.Count -gt 0) {
|
||||||
|
$latestTimestampUtc = @(
|
||||||
|
$events |
|
||||||
|
ForEach-Object { Convert-ToUtcDate $_.timestamp } |
|
||||||
|
Where-Object { $null -ne $_ } |
|
||||||
|
Sort-Object -Descending
|
||||||
|
) | Select-Object -First 1
|
||||||
|
if ($null -ne $latestTimestampUtc) {
|
||||||
|
$ageSeconds = [int][Math]::Floor(((Get-Date).ToUniversalTime() - $latestTimestampUtc).TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasFreshEvent = ($null -ne $ageSeconds -and $ageSeconds -le $MaxAgeSeconds)
|
||||||
|
$hasAnyEvent = ($events.Count -gt 0)
|
||||||
|
$ok = if (-not $Required) { $true } elseif ($RequireFreshEvent) { $queryOk -and $hasFreshEvent } else { $queryOk -and $hasAnyEvent }
|
||||||
|
|
||||||
|
return [pscustomobject]@{
|
||||||
|
bucketId = $BucketId
|
||||||
|
required = [bool]$Required
|
||||||
|
requireFreshEvent = [bool]$RequireFreshEvent
|
||||||
|
maxAgeSeconds = [int]$MaxAgeSeconds
|
||||||
|
queryOk = [bool]$queryOk
|
||||||
|
latestTimestampUtc = if ($null -ne $latestTimestampUtc) { $latestTimestampUtc.ToString('o') } else { $null }
|
||||||
|
ageSeconds = if ($null -ne $ageSeconds) { [int]$ageSeconds } else { $null }
|
||||||
|
count = [int]$events.Count
|
||||||
|
ok = [bool]$ok
|
||||||
|
error = $errorMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-TransportQueueHealth {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Name,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$QueuePath,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$LockPath,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$StaleAfterSeconds,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$MaxDepth,
|
||||||
|
[int]$ActiveProcessCount = 0,
|
||||||
|
[bool]$Required = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
$queueExists = Test-Path -LiteralPath $QueuePath
|
||||||
|
$depth = 0
|
||||||
|
$sizeBytes = 0
|
||||||
|
$ageSeconds = $null
|
||||||
|
$lastWriteUtc = $null
|
||||||
|
if ($queueExists) {
|
||||||
|
$item = Get-Item -LiteralPath $QueuePath -ErrorAction SilentlyContinue
|
||||||
|
if ($item) {
|
||||||
|
$sizeBytes = [int64]$item.Length
|
||||||
|
$lastWriteUtc = $item.LastWriteTimeUtc
|
||||||
|
$ageSeconds = [int][Math]::Floor(((Get-Date).ToUniversalTime() - $lastWriteUtc).TotalSeconds)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$depth = [int]((Get-Content -LiteralPath $QueuePath -ErrorAction SilentlyContinue | Measure-Object).Count)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$depth = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$lockExists = Test-Path -LiteralPath $LockPath
|
||||||
|
$lockHeld = $false
|
||||||
|
if ($lockExists) {
|
||||||
|
try {
|
||||||
|
$lockHandle = [System.IO.File]::Open($LockPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
||||||
|
$lockHandle.Dispose()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$lockHeld = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$staleQueue = ($depth -gt 0 -and $null -ne $ageSeconds -and $ageSeconds -gt $StaleAfterSeconds -and -not $lockHeld)
|
||||||
|
$orphanedQueue = ($depth -gt 0 -and $ActiveProcessCount -le 0 -and $null -ne $ageSeconds -and $ageSeconds -gt $StaleAfterSeconds)
|
||||||
|
$oversizedQueue = ($depth -gt $MaxDepth)
|
||||||
|
$ok = if (-not $Required) { $true } else { -not ($staleQueue -or $orphanedQueue -or $oversizedQueue) }
|
||||||
|
|
||||||
|
return [pscustomobject]@{
|
||||||
|
name = $Name
|
||||||
|
required = [bool]$Required
|
||||||
|
queuePath = $QueuePath
|
||||||
|
queueExists = [bool]$queueExists
|
||||||
|
depth = [int]$depth
|
||||||
|
sizeBytes = [int64]$sizeBytes
|
||||||
|
lastWriteUtc = if ($null -ne $lastWriteUtc) { $lastWriteUtc.ToString('o') } else { $null }
|
||||||
|
ageSeconds = if ($null -ne $ageSeconds) { [int]$ageSeconds } else { $null }
|
||||||
|
lockPath = $LockPath
|
||||||
|
lockExists = [bool]$lockExists
|
||||||
|
lockHeld = [bool]$lockHeld
|
||||||
|
activeProcessCount = [int]$ActiveProcessCount
|
||||||
|
staleAfterSeconds = [int]$StaleAfterSeconds
|
||||||
|
maxDepth = [int]$MaxDepth
|
||||||
|
staleQueue = [bool]$staleQueue
|
||||||
|
orphanedQueue = [bool]$orphanedQueue
|
||||||
|
oversizedQueue = [bool]$oversizedQueue
|
||||||
|
ok = [bool]$ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-TaskSnapshot {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string[]]$TaskNames
|
||||||
|
)
|
||||||
|
|
||||||
|
return @(
|
||||||
|
foreach ($taskName in @($TaskNames | Sort-Object -Unique)) {
|
||||||
|
$task = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -eq $taskName } | Select-Object -First 1
|
||||||
|
if ($null -eq $task) {
|
||||||
|
[pscustomobject]@{
|
||||||
|
taskName = $taskName
|
||||||
|
present = $false
|
||||||
|
enabled = $false
|
||||||
|
state = 'Отсутствует'
|
||||||
|
lastResult = $null
|
||||||
|
ok = $false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$taskInfo = $null
|
||||||
|
try {
|
||||||
|
$taskInfo = Get-ScheduledTaskInfo -TaskName $task.TaskName -TaskPath $task.TaskPath -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
$enabled = $true
|
||||||
|
try {
|
||||||
|
if ($task.Settings.PSObject.Properties.Name -contains 'Enabled') {
|
||||||
|
$enabled = [bool]$task.Settings.Enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
[pscustomobject]@{
|
||||||
|
taskName = [string]$task.TaskName
|
||||||
|
present = $true
|
||||||
|
enabled = [bool]$enabled
|
||||||
|
state = [string]$task.State
|
||||||
|
lastResult = if ($taskInfo) { [int64]$taskInfo.LastTaskResult } else { $null }
|
||||||
|
ok = [bool]($enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$requiredFiles = @(
|
||||||
|
$collectorScript,
|
||||||
|
$endpointCollectorScript,
|
||||||
|
$sessionCollectorScript,
|
||||||
|
$evtxExportScript,
|
||||||
|
$rulesPath,
|
||||||
|
$policyPath,
|
||||||
|
$policyClientScript,
|
||||||
|
$launchScript,
|
||||||
|
$recoveryScript,
|
||||||
|
$ConfigPath
|
||||||
|
)
|
||||||
|
if ($fileOpsExpected) {
|
||||||
|
$requiredFiles += $fileCollectorScript
|
||||||
|
}
|
||||||
|
if ($afkExpected) {
|
||||||
|
$requiredFiles += (Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe')
|
||||||
|
}
|
||||||
|
if ($windowExpected) {
|
||||||
|
$requiredFiles += (Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe')
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingFiles = @(
|
||||||
|
$requiredFiles |
|
||||||
|
Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } |
|
||||||
|
Where-Object { -not (Test-Path -LiteralPath $_) }
|
||||||
|
)
|
||||||
|
|
||||||
|
$runningWatchers = @()
|
||||||
|
$expectedWatcherNames = @()
|
||||||
|
if ($afkExpected) { $expectedWatcherNames += 'aw-watcher-afk' }
|
||||||
|
if ($windowExpected) { $expectedWatcherNames += 'aw-watcher-window' }
|
||||||
|
if ($expectedWatcherNames.Count -gt 0) {
|
||||||
|
$runningWatchers = @(Get-Process -Name $expectedWatcherNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
$sessionCollectorProcesses = @(Get-CollectorProcesses -ScriptPath $sessionCollectorScript)
|
||||||
|
$endpointCollectorProcesses = @(Get-CollectorProcesses -ScriptPath $endpointCollectorScript)
|
||||||
|
$fileCollectorProcesses = if ($fileOpsExpected) { @(Get-CollectorProcesses -ScriptPath $fileCollectorScript) } else { @() }
|
||||||
|
$browserCollectorProcesses = @(Get-CollectorProcesses -ScriptPath $collectorScript)
|
||||||
|
|
||||||
$loggedOnUsers = Get-LoggedOnUsers
|
$loggedOnUsers = Get-LoggedOnUsers
|
||||||
$sessionBoundUsers = @(
|
$sessionBoundUsers = @(
|
||||||
@($config.userTasks) |
|
@($config.userTasks) |
|
||||||
Where-Object { Test-UserHasSession -UserId ([string]$_.userId) -LoggedOnUsers $loggedOnUsers } |
|
Where-Object { Test-UserHasSession -UserId ([string]$_.userId) -LoggedOnUsers $loggedOnUsers } |
|
||||||
ForEach-Object { [string]$_.userId }
|
ForEach-Object { [string]$_.userId }
|
||||||
)
|
)
|
||||||
|
$sessionScopedExpectedCount = [int]$sessionBoundUsers.Count
|
||||||
|
$sessionScopedCollectorsRequired = ($sessionScopedExpectedCount -gt 0)
|
||||||
|
|
||||||
$tasks = @(
|
$taskNames = @()
|
||||||
foreach ($taskName in $taskNames) {
|
if ($config.userTasks) {
|
||||||
$task = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -eq $taskName } | Select-Object -First 1
|
$taskNames += @($config.userTasks | ForEach-Object { [string]$_.launchTaskName })
|
||||||
if ($task) {
|
}
|
||||||
[pscustomobject]@{
|
$taskNames += [string]$config.recovery.taskName
|
||||||
taskName = $task.TaskName
|
$tasks = @(Get-TaskSnapshot -TaskNames $taskNames)
|
||||||
state = [string]$task.State
|
|
||||||
present = $true
|
$watcherDuplicates = @(Get-DuplicateProcessGroups -Processes $runningWatchers -PerSession $true)
|
||||||
}
|
$sessionCollectorDuplicates = @(Get-DuplicateProcessGroups -Processes $sessionCollectorProcesses -PerSession $false)
|
||||||
}
|
$endpointCollectorDuplicates = @(Get-DuplicateProcessGroups -Processes $endpointCollectorProcesses -PerSession $true)
|
||||||
else {
|
$fileCollectorDuplicates = @(Get-DuplicateProcessGroups -Processes $fileCollectorProcesses -PerSession $true)
|
||||||
[pscustomobject]@{
|
$browserCollectorDuplicates = @(Get-DuplicateProcessGroups -Processes $browserCollectorProcesses -PerSession $true)
|
||||||
taskName = $taskName
|
|
||||||
state = 'Отсутствует'
|
$watcherByName = @{}
|
||||||
present = $false
|
foreach ($watcher in $runningWatchers) {
|
||||||
}
|
if (-not $watcherByName.ContainsKey([string]$watcher.Name)) {
|
||||||
}
|
$watcherByName[[string]$watcher.Name] = 0
|
||||||
}
|
}
|
||||||
)
|
$watcherByName[[string]$watcher.Name]++
|
||||||
|
}
|
||||||
|
|
||||||
|
$bucketChecks = @(
|
||||||
|
Get-BucketHealth -BucketId ('aw-worktime-sessions_' + $awHostname) -MaxAgeSeconds $sessionFreshnessSeconds -Required $true -RequireFreshEvent $true
|
||||||
|
)
|
||||||
|
if ($sessionScopedCollectorsRequired -and $afkExpected) {
|
||||||
|
$bucketChecks += Get-BucketHealth -BucketId ('aw-watcher-afk_' + $awHostname) -MaxAgeSeconds $freshnessSeconds -Required $true -RequireFreshEvent $false
|
||||||
|
}
|
||||||
|
if ($sessionScopedCollectorsRequired -and $windowExpected) {
|
||||||
|
$bucketChecks += Get-BucketHealth -BucketId ('aw-watcher-window_' + $awHostname) -MaxAgeSeconds $freshnessSeconds -Required $true -RequireFreshEvent $false
|
||||||
|
}
|
||||||
|
if ($sessionScopedCollectorsRequired) {
|
||||||
|
$bucketChecks += Get-BucketHealth -BucketId ('aw-dlp-endpoint-signals_' + $awHostname) -MaxAgeSeconds $freshnessSeconds -Required $true -RequireFreshEvent $true
|
||||||
|
}
|
||||||
|
if ($sessionScopedCollectorsRequired -and $fileOpsExpected) {
|
||||||
|
$bucketChecks += Get-BucketHealth -BucketId ('aw-file-operations_' + $awHostname) -MaxAgeSeconds $transportStaleSeconds -Required $false -RequireFreshEvent $true
|
||||||
|
}
|
||||||
|
|
||||||
|
$queueChecks = @(
|
||||||
|
Get-TransportQueueHealth -Name 'endpoint' -QueuePath (Join-Path $stateRoot 'dlp-endpoint-signals-queue.jsonl') -LockPath (Join-Path $stateRoot 'dlp-endpoint-signals-queue.lock') -StaleAfterSeconds $transportStaleSeconds -MaxDepth $queueMaxDepth -ActiveProcessCount @($endpointCollectorProcesses).Count -Required $sessionScopedCollectorsRequired
|
||||||
|
)
|
||||||
|
if ($fileOpsExpected) {
|
||||||
|
$queueChecks += Get-TransportQueueHealth -Name 'fileops' -QueuePath (Join-Path $stateRoot 'file-operations-queue.jsonl') -LockPath (Join-Path $stateRoot 'file-operations-queue.lock') -StaleAfterSeconds $transportStaleSeconds -MaxDepth $queueMaxDepth -ActiveProcessCount @($fileCollectorProcesses).Count -Required $sessionScopedCollectorsRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
$printServiceOperationalEnabled = $false
|
||||||
|
try {
|
||||||
|
$printServiceLog = Get-WinEvent -ListLog 'Microsoft-Windows-PrintService/Operational' -ErrorAction Stop
|
||||||
|
$printServiceOperationalEnabled = [bool]$printServiceLog.IsEnabled
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
$printJobTitlePolicyEnabled = $false
|
||||||
|
try {
|
||||||
|
$printPolicy = Get-ItemProperty -LiteralPath 'HKLM:\Software\Policies\Microsoft\Windows NT\Printers' -Name 'ShowJobTitleInEventLogs' -ErrorAction Stop
|
||||||
|
$printJobTitlePolicyEnabled = ([int]$printPolicy.ShowJobTitleInEventLogs -eq 1)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
$watcherCountsOk = $true
|
||||||
|
if ($sessionScopedCollectorsRequired) {
|
||||||
|
if ($afkExpected) {
|
||||||
|
$watcherCountsOk = $watcherCountsOk -and (($watcherByName['aw-watcher-afk'] | ForEach-Object { [int]$_ }) -ge $sessionScopedExpectedCount)
|
||||||
|
}
|
||||||
|
if ($windowExpected) {
|
||||||
|
$watcherCountsOk = $watcherCountsOk -and (($watcherByName['aw-watcher-window'] | ForEach-Object { [int]$_ }) -ge $sessionScopedExpectedCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$endpointProcessOk = if (-not $sessionScopedCollectorsRequired) { $true } else { (@($endpointCollectorProcesses).Count -ge $sessionScopedExpectedCount) }
|
||||||
|
$fileProcessOk = if (-not $fileOpsExpected -or -not $sessionScopedCollectorsRequired) { $true } else { (@($fileCollectorProcesses).Count -ge $sessionScopedExpectedCount) }
|
||||||
|
$browserProcessOk = if (-not $sessionScopedCollectorsRequired) { $true } else { (@($browserCollectorProcesses).Count -ge $sessionScopedExpectedCount) }
|
||||||
|
$sessionCollectorOk = (@($sessionCollectorProcesses).Count -eq 1)
|
||||||
|
|
||||||
$serverUrl = '{0}://{1}:{2}' -f [string]$config.server.scheme, [string]$config.server.host, [int]$config.server.port
|
|
||||||
$uniqueRunningProcessNames = @($runningProcesses | Select-Object -ExpandProperty Name -Unique)
|
|
||||||
$sessionBoundCollectorsExpected = ($sessionBoundUsers.Count -gt 0)
|
|
||||||
$result = [ordered]@{
|
$result = [ordered]@{
|
||||||
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
|
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
|
||||||
configPath = $ConfigPath
|
configPath = $ConfigPath
|
||||||
serverUrl = $serverUrl
|
serverUrl = $serverUrl
|
||||||
|
apiBase = $apiBase
|
||||||
|
awHostname = $awHostname
|
||||||
installRoot = $installRoot
|
installRoot = $installRoot
|
||||||
stateRoot = $stateRoot
|
stateRoot = $stateRoot
|
||||||
|
timing = [ordered]@{
|
||||||
|
pollSeconds = [int]$pollSeconds
|
||||||
|
pulseSeconds = [int]$pulseSeconds
|
||||||
|
freshnessSeconds = [int]$freshnessSeconds
|
||||||
|
sessionFreshnessSeconds = [int]$sessionFreshnessSeconds
|
||||||
|
transportStaleSeconds = [int]$transportStaleSeconds
|
||||||
|
}
|
||||||
files = [ordered]@{
|
files = [ordered]@{
|
||||||
required = $requiredFiles
|
required = $requiredFiles
|
||||||
missing = $missingFiles
|
missing = $missingFiles
|
||||||
@@ -183,22 +467,42 @@ $result = [ordered]@{
|
|||||||
}
|
}
|
||||||
tasks = [ordered]@{
|
tasks = [ordered]@{
|
||||||
list = $tasks
|
list = $tasks
|
||||||
ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present }))
|
ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present -or -not $_.enabled }))
|
||||||
}
|
}
|
||||||
processes = [ordered]@{
|
processes = [ordered]@{
|
||||||
expected = $processNames
|
|
||||||
sessionBoundUsers = $sessionBoundUsers
|
sessionBoundUsers = $sessionBoundUsers
|
||||||
list = @($runningProcesses)
|
sessionScopedExpectedCount = [int]$sessionScopedExpectedCount
|
||||||
|
watchers = @($runningWatchers)
|
||||||
|
watcherDuplicates = @($watcherDuplicates)
|
||||||
sessionCollectors = @($sessionCollectorProcesses)
|
sessionCollectors = @($sessionCollectorProcesses)
|
||||||
|
sessionCollectorDuplicates = @($sessionCollectorDuplicates)
|
||||||
|
browserCollectors = @($browserCollectorProcesses)
|
||||||
|
browserCollectorDuplicates = @($browserCollectorDuplicates)
|
||||||
|
endpointCollectors = @($endpointCollectorProcesses)
|
||||||
|
endpointCollectorDuplicates = @($endpointCollectorDuplicates)
|
||||||
|
fileCollectors = @($fileCollectorProcesses)
|
||||||
|
fileCollectorDuplicates = @($fileCollectorDuplicates)
|
||||||
ok = [bool](
|
ok = [bool](
|
||||||
(
|
$watcherCountsOk -and
|
||||||
(-not $sessionBoundCollectorsExpected) -or
|
$sessionCollectorOk -and
|
||||||
($processNames.Count -eq 0) -or
|
$browserProcessOk -and
|
||||||
($uniqueRunningProcessNames.Count -ge $processNames.Count)
|
$endpointProcessOk -and
|
||||||
) -and
|
$fileProcessOk -and
|
||||||
($sessionCollectorProcesses.Count -ge 1)
|
($watcherDuplicates.Count -eq 0) -and
|
||||||
|
($sessionCollectorDuplicates.Count -eq 0) -and
|
||||||
|
($browserCollectorDuplicates.Count -eq 0) -and
|
||||||
|
($endpointCollectorDuplicates.Count -eq 0) -and
|
||||||
|
($fileCollectorDuplicates.Count -eq 0)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
buckets = [ordered]@{
|
||||||
|
list = @($bucketChecks)
|
||||||
|
ok = [bool](-not ($bucketChecks | Where-Object { -not $_.ok }))
|
||||||
|
}
|
||||||
|
queues = [ordered]@{
|
||||||
|
list = @($queueChecks)
|
||||||
|
ok = [bool](-not ($queueChecks | Where-Object { -not $_.ok }))
|
||||||
|
}
|
||||||
printTelemetry = [ordered]@{
|
printTelemetry = [ordered]@{
|
||||||
operationalLogEnabled = $printServiceOperationalEnabled
|
operationalLogEnabled = $printServiceOperationalEnabled
|
||||||
jobTitlePolicyEnabled = $printJobTitlePolicyEnabled
|
jobTitlePolicyEnabled = $printJobTitlePolicyEnabled
|
||||||
@@ -218,6 +522,21 @@ $result = [ordered]@{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$result.overallOk = [bool]($result.files.ok -and $result.tasks.ok -and $result.processes.ok -and $result.printTelemetry.ok -and $result.forensics.ok)
|
$result.summary = [ordered]@{
|
||||||
|
failedSections = @(
|
||||||
|
'files', 'tasks', 'processes', 'buckets', 'queues', 'printTelemetry', 'forensics' |
|
||||||
|
Where-Object { -not [bool]$result.$_.ok }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$result.overallOk = [bool](
|
||||||
|
$result.files.ok -and
|
||||||
|
$result.tasks.ok -and
|
||||||
|
$result.processes.ok -and
|
||||||
|
$result.buckets.ok -and
|
||||||
|
$result.queues.ok -and
|
||||||
|
$result.printTelemetry.ok -and
|
||||||
|
$result.forensics.ok
|
||||||
|
)
|
||||||
|
|
||||||
$result
|
$result
|
||||||
|
|||||||
Reference in New Issue
Block a user