Диагностический пробник обнаружения сеансов в домене
PowerShell-скрипт для проверки, как поиск сеансов поведёт себя в незнакомом домене, до того как разворачивать там программу: список компьютеров из AD, отсев выключенных машин по порту 445, перечисление сеансов через WTS API и разрешение папки профиля через SID + удалённый реестр ProfileList. Сеансы читаются через P/Invoke к wtsapi32, а не разбором вывода quser — тот зависит от локали Windows и ломается на русской версии. Python на целевой машине не нужен, скрипт только читает данные и ничего не меняет. Показывает тайминги по шагам и группирует причины отказов — по ним видно, где закрыт RPC и где отключена служба RemoteRegistry. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,482 @@
|
||||
<#
|
||||
Пробник для v2.0 "Добавление баз 1С" — проверка обнаружения сеансов по домену.
|
||||
|
||||
Что проверяет:
|
||||
1. Окружение (домен, права, версия ОС/PS).
|
||||
2. Получение списка компьютеров из AD (через [adsisearcher], RSAT не нужен).
|
||||
3. Быстрый отсев мёртвых машин по порту 445 (асинхронно, как это делает Dameware).
|
||||
4. Перечисление сеансов через WTS API (P/Invoke, без парсинга quser — не зависит от локали).
|
||||
5. Разрешение реальной папки профиля пользователя (SID -> удалённый реестр ProfileList).
|
||||
|
||||
Запуск (на терминальном сервере, под доменным админом):
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File .\probe_sessions.ps1 -User Saturn | Tee-Object probe_report.txt
|
||||
|
||||
Быстрый прогон только по двум машинам, без опроса всего домена:
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File .\probe_sessions.ps1 -User Saturn -Computers "SERVERTERM,SERVERSQL"
|
||||
|
||||
Скрипт только читает данные: ничего не пишет на другие машины и не меняет настройки.
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# Имя пользователя, чьи сеансы ищем (без домена).
|
||||
[string]$User = "Saturn",
|
||||
|
||||
# Явный список машин через запятую. Если задан — AD не опрашивается.
|
||||
[string]$Computers = "",
|
||||
|
||||
# Ограничить число машин из AD (0 = без ограничения). Полезно для первого прогона.
|
||||
[int]$Limit = 0,
|
||||
|
||||
# Таймаут проверки порта 445, мс. Меньше = быстрее, но можно потерять медленные машины.
|
||||
[int]$PortTimeoutMs = 400,
|
||||
|
||||
# Число параллельных потоков опроса WTS.
|
||||
[int]$Threads = 64,
|
||||
|
||||
# Общий таймаут этапа опроса сеансов, секунд.
|
||||
[int]$WtsTimeoutSec = 90
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$swTotal = [Diagnostics.Stopwatch]::StartNew()
|
||||
|
||||
function Section($title) {
|
||||
Write-Output ""
|
||||
Write-Output ("=" * 78)
|
||||
Write-Output " $title"
|
||||
Write-Output ("=" * 78)
|
||||
}
|
||||
|
||||
function TableOut($objects, $props) {
|
||||
if (-not $objects -or $objects.Count -eq 0) { Write-Output " (пусто)"; return }
|
||||
$t = $objects | Format-Table -Property $props -AutoSize | Out-String
|
||||
Write-Output $t.TrimEnd()
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- ШАГ 0: окружение
|
||||
|
||||
Section "ШАГ 0. Окружение"
|
||||
|
||||
$os = Get-CimInstance Win32_OperatingSystem
|
||||
$cs = Get-CimInstance Win32_ComputerSystem
|
||||
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal($id)
|
||||
$isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
Write-Output " Компьютер : $($env:COMPUTERNAME)"
|
||||
Write-Output " ОС : $($os.Caption) ($($os.Version))"
|
||||
Write-Output " PowerShell : $($PSVersionTable.PSVersion)"
|
||||
Write-Output " Домен машины : $($cs.Domain) (в домене: $($cs.PartOfDomain))"
|
||||
Write-Output " Запущен от : $($id.Name)"
|
||||
Write-Output " Права локал. админа: $isAdmin"
|
||||
Write-Output " Ищем сеансы юзера : '$User'"
|
||||
|
||||
if (-not $cs.PartOfDomain) {
|
||||
Write-Output ""
|
||||
Write-Output " !! Машина не в домене — поиск по AD работать не будет."
|
||||
}
|
||||
if (-not $isAdmin) {
|
||||
Write-Output ""
|
||||
Write-Output " !! Нет прав локального администратора. Опрос WTS на удалённых машинах"
|
||||
Write-Output " почти наверняка вернёт 'Отказано в доступе'. Запусти из-под доменного админа."
|
||||
}
|
||||
|
||||
# ------------------------------------------------- Компиляция WTS-обёртки (P/Invoke)
|
||||
|
||||
$cs_code = @'
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public class WtsProbe
|
||||
{
|
||||
[DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr WTSOpenServerW(string pServerName);
|
||||
|
||||
[DllImport("wtsapi32.dll")]
|
||||
private static extern void WTSCloseServer(IntPtr hServer);
|
||||
|
||||
[DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int WTSEnumerateSessionsW(IntPtr hServer, int Reserved, int Version,
|
||||
ref IntPtr ppSessionInfo, ref int pCount);
|
||||
|
||||
[DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int WTSQuerySessionInformationW(IntPtr hServer, int sessionId, int wtsInfoClass,
|
||||
out IntPtr ppBuffer, out int pBytesReturned);
|
||||
|
||||
[DllImport("wtsapi32.dll")]
|
||||
private static extern void WTSFreeMemory(IntPtr pMemory);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WTS_SESSION_INFO
|
||||
{
|
||||
public int SessionId;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string pWinStationName;
|
||||
public int State;
|
||||
}
|
||||
|
||||
private const int WTSUserName = 5;
|
||||
private const int WTSDomainName = 7;
|
||||
private const int WTSClientName = 10;
|
||||
|
||||
private static string Query(IntPtr h, int sid, int infoClass)
|
||||
{
|
||||
IntPtr buf = IntPtr.Zero;
|
||||
int bytes = 0;
|
||||
if (WTSQuerySessionInformationW(h, sid, infoClass, out buf, out bytes) == 0) return "";
|
||||
try { string s = Marshal.PtrToStringUni(buf); return s == null ? "" : s; }
|
||||
finally { if (buf != IntPtr.Zero) WTSFreeMemory(buf); }
|
||||
}
|
||||
|
||||
// Возвращает строки "SessionId|Station|State|User|Domain|Client".
|
||||
// server = null/"" -> локальная машина.
|
||||
public static string[] Enumerate(string server)
|
||||
{
|
||||
bool remote = !string.IsNullOrEmpty(server);
|
||||
IntPtr h = IntPtr.Zero; // WTS_CURRENT_SERVER_HANDLE
|
||||
|
||||
if (remote)
|
||||
{
|
||||
h = WTSOpenServerW(server);
|
||||
if (h == IntPtr.Zero)
|
||||
throw new Exception("WTSOpenServer: win32 error " + Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
IntPtr pInfo = IntPtr.Zero;
|
||||
int count = 0;
|
||||
List<string> res = new List<string>();
|
||||
try
|
||||
{
|
||||
if (WTSEnumerateSessionsW(h, 0, 1, ref pInfo, ref count) == 0)
|
||||
throw new Exception("WTSEnumerateSessions: win32 error " + Marshal.GetLastWin32Error());
|
||||
|
||||
int sz = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
IntPtr cur = new IntPtr(pInfo.ToInt64() + (long)i * sz);
|
||||
WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure(cur, typeof(WTS_SESSION_INFO));
|
||||
|
||||
string user = Query(h, si.SessionId, WTSUserName);
|
||||
if (string.IsNullOrEmpty(user)) continue; // служебные сеансы пропускаем
|
||||
|
||||
res.Add(si.SessionId + "|" + si.pWinStationName + "|" + si.State + "|" +
|
||||
user + "|" + Query(h, si.SessionId, WTSDomainName) + "|" +
|
||||
Query(h, si.SessionId, WTSClientName));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pInfo != IntPtr.Zero) WTSFreeMemory(pInfo);
|
||||
if (remote && h != IntPtr.Zero) WTSCloseServer(h);
|
||||
}
|
||||
return res.ToArray();
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
# Компилируем в DLL: так её можно быстро загрузить в каждый параллельный runspace
|
||||
# (повторный Add-Type в каждом потоке стоил бы 1-2 секунды на компиляцию).
|
||||
$dllPath = Join-Path $env:TEMP ("WtsProbe_{0}.dll" -f $PID)
|
||||
try {
|
||||
Add-Type -TypeDefinition $cs_code -OutputAssembly $dllPath -OutputType Library
|
||||
Add-Type -Path $dllPath
|
||||
} catch {
|
||||
Write-Output ""
|
||||
Write-Output " !! Не удалось скомпилировать WTS-обёртку: $($_.Exception.Message)"
|
||||
Write-Output " Проверь, что доступен .NET-компилятор (csc) и %TEMP% доступен на запись."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$STATES = @{
|
||||
0 = "Active"; 1 = "Connected"; 2 = "ConnectQuery"; 3 = "Shadow"; 4 = "Disconnected";
|
||||
5 = "Idle"; 6 = "Listen"; 7 = "Reset"; 8 = "Down"; 9 = "Init"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------- ШАГ 1: список компьютеров
|
||||
|
||||
Section "ШАГ 1. Список компьютеров"
|
||||
|
||||
$machines = @()
|
||||
$adInfo = @{}
|
||||
|
||||
if ($Computers.Trim()) {
|
||||
$machines = $Computers.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
||||
Write-Output " Источник: задан вручную (-Computers)"
|
||||
Write-Output " Машин: $($machines.Count)"
|
||||
} else {
|
||||
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||
try {
|
||||
# (!userAccountControl:...:=2) — отсекаем отключённые учётки компьютеров
|
||||
$searcher = [adsisearcher]"(&(objectCategory=computer)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
|
||||
$searcher.PageSize = 1000
|
||||
[void]$searcher.PropertiesToLoad.AddRange(@('name', 'dnshostname', 'operatingsystem', 'lastlogontimestamp'))
|
||||
$found = $searcher.FindAll()
|
||||
|
||||
foreach ($r in $found) {
|
||||
$p = $r.Properties
|
||||
$nm = if ($p.dnshostname) { [string]$p.dnshostname[0] } else { [string]$p.name[0] }
|
||||
if (-not $nm) { continue }
|
||||
$llt = $null
|
||||
if ($p.lastlogontimestamp) {
|
||||
try { $llt = [DateTime]::FromFileTimeUtc([int64]$p.lastlogontimestamp[0]).ToLocalTime() } catch {}
|
||||
}
|
||||
$machines += $nm
|
||||
$adInfo[$nm] = [pscustomobject]@{
|
||||
OS = if ($p.operatingsystem) { [string]$p.operatingsystem[0] } else { "" }
|
||||
LastLogon = $llt
|
||||
}
|
||||
}
|
||||
$found.Dispose()
|
||||
$sw.Stop()
|
||||
Write-Output " Источник: Active Directory (LDAP)"
|
||||
Write-Output " Найдено компьютеров: $($machines.Count) (за $([math]::Round($sw.Elapsed.TotalSeconds,2)) c)"
|
||||
|
||||
$stale = @($adInfo.Values | Where-Object { $_.LastLogon -and $_.LastLogon -lt (Get-Date).AddDays(-30) }).Count
|
||||
Write-Output " Из них не логинились >30 дней: $stale (кандидаты на отсев)"
|
||||
} catch {
|
||||
Write-Output " !! Ошибка запроса к AD: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($Limit -gt 0 -and $machines.Count -gt $Limit) {
|
||||
$machines = $machines[0..($Limit - 1)]
|
||||
Write-Output " Ограничено параметром -Limit: $($machines.Count)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($machines.Count -eq 0) { Write-Output " Нет машин для опроса."; exit 1 }
|
||||
|
||||
# ------------------------------------------------- ШАГ 2: отсев мёртвых машин (порт 445)
|
||||
|
||||
Section "ШАГ 2. Проверка живости (TCP 445, асинхронно)"
|
||||
|
||||
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||
$pending = @()
|
||||
foreach ($m in $machines) {
|
||||
$client = New-Object Net.Sockets.TcpClient
|
||||
$ar = $null
|
||||
try { $ar = $client.BeginConnect($m, 445, $null, $null) } catch { }
|
||||
$pending += [pscustomobject]@{ Machine = $m; Client = $client; Async = $ar }
|
||||
}
|
||||
Start-Sleep -Milliseconds $PortTimeoutMs
|
||||
|
||||
$alive = @()
|
||||
foreach ($p in $pending) {
|
||||
$ok = $false
|
||||
try { if ($p.Client.Connected) { $ok = $true } } catch { }
|
||||
if ($ok) { $alive += $p.Machine }
|
||||
try { $p.Client.Close() } catch { }
|
||||
}
|
||||
$sw.Stop()
|
||||
|
||||
Write-Output " Живых машин: $($alive.Count) из $($machines.Count) (за $([math]::Round($sw.Elapsed.TotalSeconds,2)) c)"
|
||||
Write-Output " Недоступны : $($machines.Count - $alive.Count)"
|
||||
if ($alive.Count -eq 0) {
|
||||
Write-Output " !! Ни одна машина не ответила. Проверь таймаут (-PortTimeoutMs) и сетевую доступность."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------ ШАГ 3: опрос сеансов через WTS
|
||||
|
||||
Section "ШАГ 3. Опрос сеансов (WTS API, параллельно)"
|
||||
|
||||
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||
$pool = [runspacefactory]::CreateRunspacePool(1, [Math]::Max(1, $Threads))
|
||||
$pool.Open()
|
||||
|
||||
$worker = {
|
||||
param($dll, $machine)
|
||||
try {
|
||||
[void][Reflection.Assembly]::LoadFrom($dll)
|
||||
$rows = [WtsProbe]::Enumerate($machine)
|
||||
[pscustomobject]@{ Machine = $machine; Ok = $true; Rows = $rows; Err = $null }
|
||||
} catch {
|
||||
[pscustomobject]@{ Machine = $machine; Ok = $false; Rows = @(); Err = $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
$jobs = @()
|
||||
foreach ($m in $alive) {
|
||||
$ps = [powershell]::Create()
|
||||
$ps.RunspacePool = $pool
|
||||
[void]$ps.AddScript($worker).AddArgument($dllPath).AddArgument($m)
|
||||
$jobs += [pscustomobject]@{ Machine = $m; PS = $ps; Handle = $ps.BeginInvoke() }
|
||||
}
|
||||
|
||||
$deadline = (Get-Date).AddSeconds($WtsTimeoutSec)
|
||||
while (@($jobs | Where-Object { -not $_.Handle.IsCompleted }).Count -gt 0 -and (Get-Date) -lt $deadline) {
|
||||
Start-Sleep -Milliseconds 100
|
||||
}
|
||||
|
||||
$sessions = @()
|
||||
$failures = @()
|
||||
$timedOut = @()
|
||||
foreach ($j in $jobs) {
|
||||
if (-not $j.Handle.IsCompleted) {
|
||||
$timedOut += $j.Machine
|
||||
try { $j.PS.Stop() } catch { }
|
||||
try { $j.PS.Dispose() } catch { }
|
||||
continue
|
||||
}
|
||||
try {
|
||||
$res = $j.PS.EndInvoke($j.Handle)
|
||||
foreach ($r in $res) {
|
||||
if (-not $r.Ok) { $failures += [pscustomobject]@{ Machine = $r.Machine; Err = $r.Err }; continue }
|
||||
foreach ($row in $r.Rows) {
|
||||
$f = $row -split "\|"
|
||||
$sessions += [pscustomobject]@{
|
||||
Machine = $r.Machine
|
||||
SessionId = [int]$f[0]
|
||||
Station = $f[1]
|
||||
State = $STATES[[int]$f[2]]
|
||||
User = $f[3]
|
||||
Domain = $f[4]
|
||||
Client = $f[5]
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$failures += [pscustomobject]@{ Machine = $j.Machine; Err = $_.Exception.Message }
|
||||
}
|
||||
try { $j.PS.Dispose() } catch { }
|
||||
}
|
||||
$pool.Close()
|
||||
$pool.Dispose()
|
||||
$sw.Stop()
|
||||
|
||||
$wtsSec = [math]::Round($sw.Elapsed.TotalSeconds, 2)
|
||||
Write-Output " Опрошено машин : $($alive.Count) (за $wtsSec c, потоков: $Threads)"
|
||||
Write-Output " Успешно : $($alive.Count - $failures.Count - $timedOut.Count)"
|
||||
Write-Output " С ошибкой : $($failures.Count)"
|
||||
Write-Output " Не уложились в таймаут: $($timedOut.Count)"
|
||||
Write-Output " Всего сеансов с пользователями: $($sessions.Count)"
|
||||
|
||||
if ($failures.Count -gt 0) {
|
||||
Write-Output ""
|
||||
Write-Output " Ошибки, сгруппированные по причине:"
|
||||
$failures | Group-Object Err | Sort-Object Count -Descending | ForEach-Object {
|
||||
$sample = ($_.Group | Select-Object -First 3 | ForEach-Object { $_.Machine }) -join ", "
|
||||
Write-Output (" {0,4} шт. {1}" -f $_.Count, $_.Name)
|
||||
Write-Output (" напр.: {0}" -f $sample)
|
||||
}
|
||||
}
|
||||
if ($timedOut.Count -gt 0) {
|
||||
Write-Output ""
|
||||
Write-Output (" Зависли: " + (($timedOut | Select-Object -First 10) -join ", "))
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------ ШАГ 4: результат по юзеру
|
||||
|
||||
Section "ШАГ 4. Сеансы пользователя '$User'"
|
||||
|
||||
$mine = @($sessions | Where-Object { $_.User -like $User })
|
||||
if ($mine.Count -eq 0) {
|
||||
Write-Output " Сеансов не найдено."
|
||||
Write-Output " (Проверь имя. Ниже — сводка всех найденных сеансов.)"
|
||||
} else {
|
||||
TableOut $mine @("Machine", "SessionId", "Station", "State", "Domain", "User", "Client")
|
||||
}
|
||||
|
||||
Section "Все найденные сеансы (топ-40)"
|
||||
TableOut @($sessions | Sort-Object Machine, SessionId | Select-Object -First 40) `
|
||||
@("Machine", "SessionId", "Station", "State", "Domain", "User", "Client")
|
||||
|
||||
# ------------------------------------------- ШАГ 5: разрешение папки профиля
|
||||
|
||||
Section "ШАГ 5. Разрешение папки профиля (SID -> реестр ProfileList)"
|
||||
|
||||
if ($mine.Count -eq 0) {
|
||||
Write-Output " Пропущено: не найдено сеансов искомого пользователя."
|
||||
} else {
|
||||
foreach ($s in ($mine | Sort-Object Machine -Unique)) {
|
||||
Write-Output ""
|
||||
Write-Output " --- $($s.Machine) ---"
|
||||
|
||||
# 1) имя -> SID
|
||||
$sid = $null
|
||||
try {
|
||||
$acct = New-Object Security.Principal.NTAccount($s.Domain, $s.User)
|
||||
$sid = $acct.Translate([Security.Principal.SecurityIdentifier]).Value
|
||||
Write-Output " SID : $sid"
|
||||
} catch {
|
||||
Write-Output " SID : ОШИБКА — $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# 2) SID -> ProfileImagePath (удалённый реестр; нужна служба RemoteRegistry)
|
||||
$profilePath = $null
|
||||
if ($sid) {
|
||||
try {
|
||||
$rk = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $s.Machine)
|
||||
$sub = $rk.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid")
|
||||
if ($sub) {
|
||||
$profilePath = $sub.GetValue("ProfileImagePath")
|
||||
Write-Output " ProfileImagePath : $profilePath"
|
||||
$sub.Close()
|
||||
} else {
|
||||
Write-Output " ProfileImagePath : ключ для SID не найден (профиль ещё не создан?)"
|
||||
}
|
||||
$rk.Close()
|
||||
} catch {
|
||||
Write-Output " ProfileImagePath : ОШИБКА — $($_.Exception.Message)"
|
||||
Write-Output " (вероятно, служба RemoteRegistry остановлена)"
|
||||
}
|
||||
}
|
||||
|
||||
# 3) Фоллбэк: угадываем папку перебором и проверяем доступ по административной шаре
|
||||
if (-not $profilePath) {
|
||||
Write-Output " Фоллбэк (перебор папок):"
|
||||
$shortDom = $s.Domain
|
||||
foreach ($cand in @($s.User, "$($s.User).$shortDom", "$($s.User).000")) {
|
||||
$p = "\\$($s.Machine)\C`$\Users\$cand"
|
||||
$exists = $false
|
||||
try { $exists = Test-Path -LiteralPath $p } catch { }
|
||||
Write-Output (" {0,-45} {1}" -f $p, $(if ($exists) { "НАЙДЕНА" } else { "нет" }))
|
||||
if ($exists -and -not $profilePath) { $profilePath = "C:\Users\$cand" }
|
||||
}
|
||||
}
|
||||
|
||||
# 4) Проверяем доступность самого ibases.v8i по админской шаре
|
||||
if ($profilePath) {
|
||||
$unc = "\\$($s.Machine)\" + ($profilePath -replace '^([A-Za-z]):', '$1$')
|
||||
$v8i = Join-Path $unc "AppData\Roaming\1C\1CEStart\ibases.v8i"
|
||||
Write-Output " UNC до профиля : $unc"
|
||||
try {
|
||||
if (Test-Path -LiteralPath $v8i) {
|
||||
$fi = Get-Item -LiteralPath $v8i
|
||||
$cnt = @(Select-String -LiteralPath $v8i -Pattern '^\[' -ErrorAction SilentlyContinue).Count
|
||||
Write-Output " ibases.v8i : ЕСТЬ, $($fi.Length) байт, баз в файле: $cnt"
|
||||
Write-Output " Запись в профиль : доступна (файл читается)"
|
||||
} else {
|
||||
$dir = Split-Path $v8i -Parent
|
||||
$dirOk = Test-Path -LiteralPath $dir
|
||||
Write-Output " ibases.v8i : файла нет (каталог 1CEStart существует: $dirOk)"
|
||||
}
|
||||
} catch {
|
||||
Write-Output " ibases.v8i : ОШИБКА доступа — $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ Итоги
|
||||
|
||||
Section "ИТОГИ"
|
||||
|
||||
$swTotal.Stop()
|
||||
Write-Output " Машин из источника : $($machines.Count)"
|
||||
Write-Output " Живых (порт 445) : $($alive.Count)"
|
||||
Write-Output " Сеансов найдено : $($sessions.Count)"
|
||||
Write-Output " Сеансов юзера '$User' : $($mine.Count)"
|
||||
Write-Output " Время опроса сеансов : $wtsSec c"
|
||||
Write-Output " Общее время работы : $([math]::Round($swTotal.Elapsed.TotalSeconds,2)) c"
|
||||
Write-Output ""
|
||||
Write-Output " Уникальных машин с сеансами:"
|
||||
$byMachine = $sessions | Group-Object Machine | Sort-Object Count -Descending | Select-Object -First 10
|
||||
foreach ($g in $byMachine) { Write-Output (" {0,-30} сеансов: {1}" -f $g.Name, $g.Count) }
|
||||
|
||||
try { Remove-Item -LiteralPath $dllPath -Force -ErrorAction SilentlyContinue } catch { }
|
||||
Write-Output ""
|
||||
Write-Output "Готово."
|
||||
Reference in New Issue
Block a user