fix(worktime): isolate global session collector and reset RDP bridge flow
This commit is contained in:
@@ -31,8 +31,8 @@
|
|||||||
aw_windows_policy_engine_host: "{{ aw_windows_server_host }}"
|
aw_windows_policy_engine_host: "{{ aw_windows_server_host }}"
|
||||||
aw_windows_policy_engine_port: 5601
|
aw_windows_policy_engine_port: 5601
|
||||||
aw_windows_policy_engine_scheme: "http"
|
aw_windows_policy_engine_scheme: "http"
|
||||||
aw_windows_afk_enabled: true
|
aw_windows_afk_enabled_default: true
|
||||||
aw_windows_window_enabled: true
|
aw_windows_window_enabled_default: true
|
||||||
aw_windows_file_ops_enabled: true
|
aw_windows_file_ops_enabled: true
|
||||||
aw_windows_local_agent_logs_enabled: false
|
aw_windows_local_agent_logs_enabled: false
|
||||||
aw_windows_incident_capture_enabled: true
|
aw_windows_incident_capture_enabled: true
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
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_window_enabled: true
|
aw_windows_api_smoke_check_window_enabled_default: true
|
||||||
aw_windows_api_smoke_check_window_bucket: ""
|
aw_windows_api_smoke_check_window_bucket: ""
|
||||||
aw_windows_api_smoke_check_min_events: 1
|
aw_windows_api_smoke_check_min_events: 1
|
||||||
aw_windows_fail_on_validation_error: true
|
aw_windows_fail_on_validation_error: true
|
||||||
@@ -72,6 +72,12 @@
|
|||||||
- aw_windows_state_root is defined
|
- aw_windows_state_root is defined
|
||||||
fail_msg: "Не заданы обязательные переменные Windows-развёртывания."
|
fail_msg: "Не заданы обязательные переменные Windows-развёртывания."
|
||||||
|
|
||||||
|
- name: Нормализовать effective флаги collector'ов и smoke-check
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
aw_windows_afk_enabled_effective: "{{ (aw_windows_afk_enabled | default(aw_windows_afk_enabled_default)) | bool }}"
|
||||||
|
aw_windows_window_enabled_effective: "{{ (aw_windows_window_enabled | default(aw_windows_window_enabled_default)) | bool }}"
|
||||||
|
aw_windows_api_smoke_check_window_enabled_effective: "{{ (aw_windows_api_smoke_check_window_enabled | default(aw_windows_api_smoke_check_window_enabled_default)) | bool }}"
|
||||||
|
|
||||||
- name: Создать каталоги развёртывания
|
- name: Создать каталоги развёртывания
|
||||||
ansible.windows.win_file:
|
ansible.windows.win_file:
|
||||||
path: "{{ item }}"
|
path: "{{ item }}"
|
||||||
@@ -97,6 +103,7 @@
|
|||||||
- deploy-domain-users.ps1
|
- deploy-domain-users.ps1
|
||||||
- deploy-ensemble.ps1
|
- deploy-ensemble.ps1
|
||||||
- hardening-recovery.ps1
|
- hardening-recovery.ps1
|
||||||
|
- rebuild-worktime-tasks.ps1
|
||||||
- validate-deployment.ps1
|
- validate-deployment.ps1
|
||||||
- web-category-rules.example.json
|
- web-category-rules.example.json
|
||||||
- dlp-policy.example.json
|
- dlp-policy.example.json
|
||||||
@@ -155,8 +162,8 @@
|
|||||||
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
|
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
|
||||||
InstallRoot = "{{ aw_windows_install_root }}"
|
InstallRoot = "{{ aw_windows_install_root }}"
|
||||||
StateRoot = "{{ aw_windows_state_root }}"
|
StateRoot = "{{ aw_windows_state_root }}"
|
||||||
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }}
|
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled_effective | bool) else '$false' }}
|
||||||
WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }}
|
WindowEnabled = {{ '$true' if (aw_windows_window_enabled_effective | bool) else '$false' }}
|
||||||
FileOpsEnabled = {{ '$true' if (aw_windows_file_ops_enabled | bool) else '$false' }}
|
FileOpsEnabled = {{ '$true' if (aw_windows_file_ops_enabled | bool) else '$false' }}
|
||||||
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }}
|
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }}
|
||||||
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
|
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
|
||||||
@@ -225,18 +232,20 @@
|
|||||||
ForEach-Object {
|
ForEach-Object {
|
||||||
$key = Get-CollectorKey -CommandLine $_.CommandLine
|
$key = Get-CollectorKey -CommandLine $_.CommandLine
|
||||||
if ($key) {
|
if ($key) {
|
||||||
|
$groupKey = if ($key -eq 'worktime') { 'worktime::global' } else { '{0}::{1}' -f $key, ([int]$_.SessionId) }
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
ProcessId = [int]$_.ProcessId
|
ProcessId = [int]$_.ProcessId
|
||||||
SessionId = [int]$_.SessionId
|
SessionId = [int]$_.SessionId
|
||||||
CreationDate = $_.CreationDate
|
CreationDate = $_.CreationDate
|
||||||
CollectorKey = $key
|
CollectorKey = $key
|
||||||
|
GroupKey = $groupKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} |
|
} |
|
||||||
Where-Object { $_ -ne $null }
|
Where-Object { $_ -ne $null }
|
||||||
|
|
||||||
# Keep only one process per (collector, session): newest survives, older duplicates are stopped.
|
# Keep only one process per collector scope: worktime collector is global, others stay per-session.
|
||||||
foreach ($group in ($collectorProcs | Group-Object CollectorKey, SessionId)) {
|
foreach ($group in ($collectorProcs | Group-Object GroupKey)) {
|
||||||
$ordered = @($group.Group | Sort-Object CreationDate -Descending)
|
$ordered = @($group.Group | Sort-Object CreationDate -Descending)
|
||||||
if ($ordered.Count -le 1) { continue }
|
if ($ordered.Count -le 1) { continue }
|
||||||
foreach ($dup in $ordered | Select-Object -Skip 1) {
|
foreach ($dup in $ordered | Select-Object -Skip 1) {
|
||||||
@@ -271,7 +280,7 @@
|
|||||||
- name: Вычислить AW AFK smoke-check bucket
|
- name: Вычислить AW AFK smoke-check bucket
|
||||||
when:
|
when:
|
||||||
- aw_windows_api_smoke_check_enabled | bool
|
- aw_windows_api_smoke_check_enabled | bool
|
||||||
- aw_windows_afk_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: >-
|
||||||
@@ -284,8 +293,8 @@
|
|||||||
- name: Вычислить AW Window smoke-check bucket
|
- name: Вычислить AW Window smoke-check bucket
|
||||||
when:
|
when:
|
||||||
- aw_windows_api_smoke_check_enabled | bool
|
- aw_windows_api_smoke_check_enabled | bool
|
||||||
- aw_windows_api_smoke_check_window_enabled | bool
|
- aw_windows_api_smoke_check_window_enabled_effective | bool
|
||||||
- aw_windows_window_enabled | bool
|
- aw_windows_window_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_window_bucket_effective: >-
|
aw_windows_api_smoke_check_window_bucket_effective: >-
|
||||||
@@ -298,7 +307,7 @@
|
|||||||
- name: Выполнить AW API smoke-check (AFK bucket должен получать события)
|
- name: Выполнить AW API smoke-check (AFK bucket должен получать события)
|
||||||
when:
|
when:
|
||||||
- aw_windows_api_smoke_check_enabled | bool
|
- aw_windows_api_smoke_check_enabled | bool
|
||||||
- aw_windows_afk_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
|
||||||
@@ -318,8 +327,8 @@
|
|||||||
- name: Выполнить AW API smoke-check (Window bucket должен получать события)
|
- name: Выполнить AW API smoke-check (Window bucket должен получать события)
|
||||||
when:
|
when:
|
||||||
- aw_windows_api_smoke_check_enabled | bool
|
- aw_windows_api_smoke_check_enabled | bool
|
||||||
- aw_windows_api_smoke_check_window_enabled | bool
|
- aw_windows_api_smoke_check_window_enabled_effective | bool
|
||||||
- aw_windows_window_enabled | bool
|
- aw_windows_window_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_window_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_window_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
|
||||||
method: GET
|
method: GET
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ aw_windows_policy_engine_host: "{{ aw_windows_server_host }}"
|
|||||||
aw_windows_policy_engine_port: 5601
|
aw_windows_policy_engine_port: 5601
|
||||||
aw_windows_policy_engine_scheme: "http"
|
aw_windows_policy_engine_scheme: "http"
|
||||||
|
|
||||||
aw_windows_afk_enabled: true
|
aw_windows_afk_enabled: false
|
||||||
aw_windows_window_enabled: true
|
aw_windows_window_enabled: false
|
||||||
aw_windows_file_ops_enabled: true
|
aw_windows_file_ops_enabled: true
|
||||||
aw_windows_email_collector_enabled: true
|
aw_windows_email_collector_enabled: true
|
||||||
aw_windows_email_collector_mode: "both"
|
aw_windows_email_collector_mode: "both"
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ def _is_active_sample(data: dict) -> bool:
|
|||||||
except Exception:
|
except Exception:
|
||||||
sid = -1
|
sid = -1
|
||||||
user = str(data.get("username") or "").strip()
|
user = str(data.get("username") or "").strip()
|
||||||
if sid > 0 and user and (not _is_machine_user(user)):
|
session_name = str(data.get("sessionName") or "").strip().lower()
|
||||||
|
if sid > 0 and user and (not _is_machine_user(user)) and (session_name.startswith("rdp-") or session_name == "console"):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ def is_active(d):
|
|||||||
try: sid=int(d.get("sessionId"))
|
try: sid=int(d.get("sessionId"))
|
||||||
except: sid=-1
|
except: sid=-1
|
||||||
u=str(d.get("username","")).strip().lower()
|
u=str(d.get("username","")).strip().lower()
|
||||||
if sid>0 and u and (not u.endswith("$")): return True
|
sn=str(d.get("sessionName","")).strip().lower()
|
||||||
|
if sid>0 and u and (not u.endswith("$")) and (sn.startswith("rdp-") or sn=="console"): return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
rows=req("GET",f"/api/0/buckets/{sb}/events?limit=12000") or []
|
rows=req("GET",f"/api/0/buckets/{sb}/events?limit=12000") or []
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ def _is_session_active(row_data):
|
|||||||
except Exception:
|
except Exception:
|
||||||
sid = -1
|
sid = -1
|
||||||
user = str(row_data.get("username", "")).strip().lower()
|
user = str(row_data.get("username", "")).strip().lower()
|
||||||
if sid > 0 and user and (not user.endswith("$")):
|
session_name = str(row_data.get("sessionName", "")).strip().lower()
|
||||||
|
if sid > 0 and user and (not user.endswith("$")) and (session_name.startswith("rdp-") or session_name == "console"):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -111,7 +112,11 @@ def transform(events):
|
|||||||
ts = e.get("timestamp")
|
ts = e.get("timestamp")
|
||||||
if not ts:
|
if not ts:
|
||||||
continue
|
continue
|
||||||
grouped.setdefault(ts, []).append(e)
|
try:
|
||||||
|
normalized_ts = to_iso_utc(parse_iso_utc(ts).replace(microsecond=0).isoformat())
|
||||||
|
except Exception:
|
||||||
|
normalized_ts = ts
|
||||||
|
grouped.setdefault(normalized_ts, []).append(e)
|
||||||
|
|
||||||
ordered_ts = sorted(grouped.keys())
|
ordered_ts = sorted(grouped.keys())
|
||||||
parsed_ts = {}
|
parsed_ts = {}
|
||||||
|
|||||||
@@ -873,7 +873,6 @@ try {
|
|||||||
if (`$fileOpsEnabled) {
|
if (`$fileOpsEnabled) {
|
||||||
Start-CollectorScriptIfNeeded -ScriptPath `$fileCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
Start-CollectorScriptIfNeeded -ScriptPath `$fileCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
||||||
}
|
}
|
||||||
Start-CollectorScriptIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
|
||||||
if (`$emailEnabled -and (Test-Path -LiteralPath `$emailCollectorScript)) {
|
if (`$emailEnabled -and (Test-Path -LiteralPath `$emailCollectorScript)) {
|
||||||
Start-CollectorScriptIfNeeded -ScriptPath `$emailCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
Start-CollectorScriptIfNeeded -ScriptPath `$emailCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
||||||
}
|
}
|
||||||
@@ -1010,6 +1009,44 @@ function Start-TaskIfNotRunning {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Test-CollectorRunningGlobal {
|
||||||
|
param([string]`$ScriptPath)
|
||||||
|
if ([string]::IsNullOrWhiteSpace(`$ScriptPath)) {
|
||||||
|
return `$false
|
||||||
|
}
|
||||||
|
|
||||||
|
return [bool]@(
|
||||||
|
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object {
|
||||||
|
(`$_.Name -ieq 'powershell.exe' -or `$_.Name -ieq 'pwsh.exe') -and
|
||||||
|
`$_.CommandLine -match [Regex]::Escape(`$ScriptPath)
|
||||||
|
}
|
||||||
|
).Count
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-CollectorScriptGlobalIfNeeded {
|
||||||
|
param(
|
||||||
|
[string]`$ScriptPath,
|
||||||
|
[string]`$ConfigPath
|
||||||
|
)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace(`$ScriptPath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath `$ScriptPath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-CollectorRunningGlobal -ScriptPath `$ScriptPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
`$powershellExe = Join-Path `$env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
||||||
|
`$argumentList = @('-NoProfile', '-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass', '-File', `$ScriptPath, '-ConfigPath', `$ConfigPath)
|
||||||
|
Start-Process -FilePath `$powershellExe -ArgumentList `$argumentList -WindowStyle Hidden
|
||||||
|
}
|
||||||
|
|
||||||
`$recoveryLockPath = New-RecoveryLock -PrimaryConfigPath `$ConfigPath
|
`$recoveryLockPath = New-RecoveryLock -PrimaryConfigPath `$ConfigPath
|
||||||
if (-not `$recoveryLockPath) {
|
if (-not `$recoveryLockPath) {
|
||||||
return
|
return
|
||||||
@@ -1020,11 +1057,14 @@ try {
|
|||||||
`$sleepSeconds = 180
|
`$sleepSeconds = 180
|
||||||
try {
|
try {
|
||||||
`$configPaths = Get-RecoveryConfigPaths -PrimaryConfigPath `$ConfigPath
|
`$configPaths = Get-RecoveryConfigPaths -PrimaryConfigPath `$ConfigPath
|
||||||
|
`$config = Get-DeploymentConfig -Path `$ConfigPath
|
||||||
|
`$stateRoot = [string]`$config.paths.stateRoot
|
||||||
|
`$sessionCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]`$config.paths.sessionCollectorScript } else { Join-Path `$stateRoot 'worktime-session-collector.ps1' }
|
||||||
|
Start-CollectorScriptGlobalIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath
|
||||||
foreach (`$taskName in Get-RecoveryTaskNames -ConfigPaths `$configPaths) {
|
foreach (`$taskName in Get-RecoveryTaskNames -ConfigPaths `$configPaths) {
|
||||||
Start-TaskIfNotRunning -TaskName `$taskName
|
Start-TaskIfNotRunning -TaskName `$taskName
|
||||||
}
|
}
|
||||||
|
|
||||||
`$config = Get-DeploymentConfig -Path `$ConfigPath
|
|
||||||
if (`$config -and `$config.recovery -and `$config.recovery.intervalSeconds) {
|
if (`$config -and `$config.recovery -and `$config.recovery.intervalSeconds) {
|
||||||
`$sleepSeconds = [Math]::Max([int]`$config.recovery.intervalSeconds, 30)
|
`$sleepSeconds = [Math]::Max([int]`$config.recovery.intervalSeconds, 30)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
|
||||||
|
[string]$ModulePath = 'C:\Program Files\AWatch-rus\windows\ActivityWatch.Windows.Common.psm1'
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
Import-Module $ModulePath -Force
|
||||||
|
|
||||||
|
$config = Read-ActivityWatchDeploymentConfig -Path $ConfigPath
|
||||||
|
$launchScript = [string]$config.paths.launchScript
|
||||||
|
$recoveryScript = [string]$config.paths.recoveryScript
|
||||||
|
$sessionCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') {
|
||||||
|
[string]$config.paths.sessionCollectorScript
|
||||||
|
} else {
|
||||||
|
Join-Path ([string]$config.paths.stateRoot) 'worktime-session-collector.ps1'
|
||||||
|
}
|
||||||
|
$taskDefinitions = @($config.userTasks)
|
||||||
|
$recoveryTaskName = [string]$config.recovery.taskName
|
||||||
|
|
||||||
|
$collectorProcs = @(
|
||||||
|
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object {
|
||||||
|
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and
|
||||||
|
$_.CommandLine -and
|
||||||
|
$_.CommandLine -match [Regex]::Escape($sessionCollectorScript)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($proc in $collectorProcs) {
|
||||||
|
try {
|
||||||
|
Stop-Process -Id ([int]$proc.ProcessId) -Force -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
|
||||||
|
Write-ActivityWatchLaunchScript -Path $launchScript -ConfigPath $ConfigPath
|
||||||
|
Write-ActivityWatchRecoveryScript -Path $recoveryScript -ConfigPath $ConfigPath
|
||||||
|
Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $launchScript -ConfigPath $ConfigPath
|
||||||
|
Register-ActivityWatchRecoveryTask -TaskName $recoveryTaskName -RecoveryScriptPath $recoveryScript -ConfigPath $ConfigPath
|
||||||
|
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $recoveryTaskName
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
|
||||||
|
$running = @(
|
||||||
|
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object {
|
||||||
|
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and
|
||||||
|
$_.CommandLine -and
|
||||||
|
$_.CommandLine -match [Regex]::Escape($sessionCollectorScript)
|
||||||
|
} |
|
||||||
|
Select-Object Name, ProcessId, SessionId, CommandLine
|
||||||
|
)
|
||||||
|
|
||||||
|
[pscustomobject]@{
|
||||||
|
rebuiltAtUtc = (Get-Date).ToUniversalTime().ToString('o')
|
||||||
|
launchScript = $launchScript
|
||||||
|
recoveryScript = $recoveryScript
|
||||||
|
sessionCollectorScript = $sessionCollectorScript
|
||||||
|
runningSessionCollectors = @($running)
|
||||||
|
}
|
||||||
@@ -158,20 +158,32 @@ function Parse-SessionLines {
|
|||||||
if ($Lines.Count -gt 0 -and $Lines[0] -match '\b(USERNAME|UserName|USER)\b') { $startIndex = 1 }
|
if ($Lines.Count -gt 0 -and $Lines[0] -match '\b(USERNAME|UserName|USER)\b') { $startIndex = 1 }
|
||||||
|
|
||||||
for ($i = $startIndex; $i -lt $Lines.Count; $i++) {
|
for ($i = $startIndex; $i -lt $Lines.Count; $i++) {
|
||||||
$line = $Lines[$i].Trim()
|
$line = ($Lines[$i] -replace '^\s*>', '').Trim()
|
||||||
if (-not $line) { continue }
|
if (-not $line) { continue }
|
||||||
|
|
||||||
$m = [regex]::Match($line, '^\s*(?<user>\S+)\s+(?<sess>\S+)?\s+(?<id>\d+)\s+(?<state>\S+)', [System.Text.RegularExpressions.RegexOptions]::None)
|
$parts = $line -split '\s+'
|
||||||
if ($m.Success) {
|
if ($parts.Count -lt 3) { continue }
|
||||||
$user = $m.Groups['user'].Value; $sess = $m.Groups['sess'].Value; $id = [int]$m.Groups['id'].Value; $state = $m.Groups['state'].Value
|
$user = $parts[0]
|
||||||
|
$sess = ''
|
||||||
|
$id = -1
|
||||||
|
$state = ''
|
||||||
|
|
||||||
|
if ($parts.Count -ge 4 -and $parts[1] -match '^\d+$') {
|
||||||
|
$sess = ''
|
||||||
|
$id = [int]$parts[1]
|
||||||
|
$state = [string]$parts[2]
|
||||||
|
}
|
||||||
|
elseif ($parts.Count -ge 4 -and $parts[2] -match '^\d+$') {
|
||||||
|
$sess = [string]$parts[1]
|
||||||
|
$id = [int]$parts[2]
|
||||||
|
$state = [string]$parts[3]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$parts = $line -split '\s+'
|
continue
|
||||||
if ($parts.Count -lt 4) { continue }
|
|
||||||
$user = $parts[0]
|
|
||||||
if ($parts[1] -match '^\d+$') { $sess = ''; $id = [int]$parts[1]; $state = $parts[2] } else { $sess = $parts[1]; $id = [int]$parts[2]; $state = $parts[3] }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($id -lt 0) { continue }
|
||||||
|
|
||||||
$records += [pscustomobject]@{ username=$user; sessionName=$sess; sessionId=$id; state=$state }
|
$records += [pscustomobject]@{ username=$user; sessionName=$sess; sessionId=$id; state=$state }
|
||||||
}
|
}
|
||||||
return $records
|
return $records
|
||||||
|
|||||||
Reference in New Issue
Block a user