Мониторим пользователей AD на коленке и бесплатно

от автора

По мере роста компании и возникновения большого кол-ва рабочих мест возникла идея сделать ресурс, на котором можно быстро найти информацию по пользователю, ПК и сделать какие-нибудь отчеты.

ПО такое есть, оно сложное и обычно стоит денег. Будем клепать свое и на коленке.

Также накладывается ряд ограничений из-за:

  • производительности контролеров домена
  • ПО безопасников (нефиг тут запускать левые exe и сервисы), в свое ПО не пустим.
  • настроек сетевиков (сложная архитектура на основе VLAN, ACL и запретов всего, что может куда-то коннектиться)
  • не прод ресурс на прод системе
  • требования безопасников и политика использования программ

Поставленные цели:
1. Куда пользователи ходят
2. Где они забывают выходить
3. Кто логиниться под своим/не своим логином на одном/нескольких ПК
4. Рассадка по местам
5. Отображение на схеме этажа
6. Отчет о рабочем времени (в т.ч. сколько пользователь был «активен» в системе)
7. Делегирование прав для инженеров с целью упрощения диагностики.

Немного о сети:
В сети существуют пользовательские VLAN с авторизацией в AD, связка Computer + User. При провале авторизации пользователи попадают в гостевые VLANs, где у них отсутствуют какие-либо доступы, кроме как выйти из системы.
VLAN максимально /23

Немного о DNS:
DNS AD integrated. DNS scavenging отключен. Соответственно, ввиду пункта выше, возникает очень много проблем с поиском ПК по DNS, дублирование A, PTR записей.

Немного об GPO:
Включена блокировка станции по не активности = 5 минут.
Используется раздел logon/logoff scripts.

Немного об AD:
Доступы на основе групп безопасности. Шаг в сторону считается побегом.
2 домена + Domain Trust

В моем распоряжении есть:

  • Шара, куда могут все писать (только создание, запись)
  • Виртуалка на базе Windows Server 2012R2

Список используемого ПО:
Windows Server 2012R2 (IIS)
Visual Studio 2012 (Разработка ПО для руководителей и пользователей)
Mysql 5.6 (База данных, в которой я храню данные)
Mysql Work Bench (Построение БД на коленке)
Perl for Windows (Портал + веб-сервис)

И языков программирования:
Perl
C#
Powershell

Для начала нам нужна БД:

Вырви глаз

SQL

-- -- Database: `logon` --  -- --------------------------------------------------------  -- -- Table structure for table `bcs` --  CREATE TABLE IF NOT EXISTS `bcs` (   `id` int(11) NOT NULL,   `bc` text NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `computers` --  CREATE TABLE IF NOT EXISTS `computers` (   `id` int(11) NOT NULL,   `domain_id` int(11) DEFAULT NULL,   `samaccountname` varchar(15) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `domains` --  CREATE TABLE IF NOT EXISTS `domains` (   `id` int(11) NOT NULL,   `name` varchar(15) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `events` --  CREATE TABLE IF NOT EXISTS `events` (   `id` int(11) NOT NULL,   `event` varchar(8) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `groups` --  CREATE TABLE IF NOT EXISTS `groups` (   `id` int(11) NOT NULL,   `name` varchar(64) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `ipnets` --  CREATE TABLE IF NOT EXISTS `ipnets` (   `id` int(11) NOT NULL,   `bc_id` int(11) NOT NULL,   `ipnet1` varchar(3) NOT NULL DEFAULT '172',   `ipnet2` varchar(3) NOT NULL DEFAULT '16',   `ipnet3` varchar(3) NOT NULL,   `mask` int(11) NOT NULL DEFAULT '24',   `vid` mediumint(4) DEFAULT NULL,   `name` varchar(20) DEFAULT NULL,   `description` varchar(50) DEFAULT NULL,   `tplname` varchar(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `ips` --  CREATE TABLE IF NOT EXISTS `ips` (   `id` int(11) NOT NULL,   `ip` varchar(15) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `latestdata` --  CREATE TABLE IF NOT EXISTS `latestdata` (   `domain_id` int(11) NOT NULL,   `computer_id` int(11) NOT NULL,   `computer_event_id` int(11) DEFAULT NULL,   `computer_dt` datetime DEFAULT NULL,   `ip_id` int(11) DEFAULT NULL,   `user_id` int(11) DEFAULT NULL,   `user_event_id` int(11) DEFAULT NULL,   `user_dt` datetime DEFAULT NULL,   `logonid` tinyint(4) DEFAULT NULL,   `logon_dt` datetime DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `latestlogons` --  CREATE TABLE IF NOT EXISTS `latestlogons` (   `domain_id` int(11) NOT NULL,   `computer_id` int(11) NOT NULL,   `ip_id` int(11) DEFAULT NULL,   `user_id` int(11) NOT NULL,   `user_event_id` int(11) DEFAULT NULL,   `user_dt` datetime DEFAULT NULL,   `processlogoff` tinyint(4) DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `logoncomputers` --  CREATE TABLE IF NOT EXISTS `logoncomputers` (   `id` int(11) NOT NULL,   `domain_id` int(11) NOT NULL,   `computer_id` int(11) NOT NULL,   `event_id` int(11) NOT NULL,   `ip_id` int(11) NOT NULL,   `dt` datetime DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `logonusers` --  CREATE TABLE IF NOT EXISTS `logonusers` (   `id` int(11) NOT NULL,   `domain_id` int(11) NOT NULL,   `user_id` int(11) NOT NULL,   `computer_id` int(11) NOT NULL,   `event_id` int(11) NOT NULL,   `ip_id` int(11) NOT NULL,   `dt` datetime DEFAULT '0000-00-00 00:00:00',   `dtnow` datetime DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- --------------------------------------------------------  -- -- Table structure for table `membership` --  CREATE TABLE IF NOT EXISTS `membership` (   `uid` int(11) NOT NULL,   `gid` int(11) NOT NULL,   `used` tinyint(4) NOT NULL,   `user_dt` datetime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;   -- --------------------------------------------------------  -- -- Table structure for table `users` --  CREATE TABLE IF NOT EXISTS `users` (   `id` int(11) NOT NULL,   `domain_id` int(11) NOT NULL,   `samaccountname` varchar(20) NOT NULL,   `name` varchar(50) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- -- Indexes for dumped tables --  -- -- Indexes for table `bcs` -- ALTER TABLE `bcs`   ADD PRIMARY KEY (`id`);  -- -- Indexes for table `computers` -- ALTER TABLE `computers`   ADD PRIMARY KEY (`id`),   ADD UNIQUE KEY `samaccountname` (`samaccountname`);  -- -- Indexes for table `domains` -- ALTER TABLE `domains`   ADD PRIMARY KEY (`id`),   ADD UNIQUE KEY `name` (`name`);  -- -- Indexes for table `events` -- ALTER TABLE `events`   ADD PRIMARY KEY (`id`),   ADD UNIQUE KEY `event` (`event`);  -- -- Indexes for table `groups` -- ALTER TABLE `groups`   ADD PRIMARY KEY (`id`,`name`),   ADD UNIQUE KEY `name` (`name`);  -- -- Indexes for table `ipnets` -- ALTER TABLE `ipnets`   ADD PRIMARY KEY (`id`);  -- -- Indexes for table `ips` -- ALTER TABLE `ips`   ADD PRIMARY KEY (`id`),   ADD UNIQUE KEY `ip` (`ip`);  -- -- Indexes for table `latestdata` -- ALTER TABLE `latestdata`   ADD PRIMARY KEY (`computer_id`);  -- -- Indexes for table `latestlogons` -- ALTER TABLE `latestlogons`   ADD PRIMARY KEY (`computer_id`,`user_id`);  -- -- Indexes for table `logoncomputers` -- ALTER TABLE `logoncomputers`   ADD PRIMARY KEY (`id`);  -- -- Indexes for table `logonusers` -- ALTER TABLE `logonusers`   ADD PRIMARY KEY (`id`);  -- -- Indexes for table `membership` -- ALTER TABLE `membership`   ADD PRIMARY KEY (`uid`,`gid`);  -- -- Indexes for table `templatedata` -- ALTER TABLE `templatedata`   ADD PRIMARY KEY (`template_id`,`row_id`);  -- -- Indexes for table `templates` -- ALTER TABLE `templates`   ADD PRIMARY KEY (`id`);  -- -- Indexes for table `users` -- ALTER TABLE `users`   ADD PRIMARY KEY (`id`);  -- -- AUTO_INCREMENT for dumped tables --  -- -- AUTO_INCREMENT for table `bcs` -- ALTER TABLE `bcs`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `computers` -- ALTER TABLE `computers`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `domains` -- ALTER TABLE `domains`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `events` -- ALTER TABLE `events`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `groups` -- ALTER TABLE `groups`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `ipnets` -- ALTER TABLE `ipnets`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `ips` -- ALTER TABLE `ips`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `logoncomputers` -- ALTER TABLE `logoncomputers`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `logonusers` -- ALTER TABLE `logonusers`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `templates` -- ALTER TABLE `templates`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `users` -- ALTER TABLE `users`   MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; 

Далее необходимо писать данные о входах пользователей.
Сделаем 2 скрипта (в каждом домене)

Для пользователя:

set t=%1
ipconfig | find «IPv4» > %temp%\tempip.txt
for /f «tokens=1* delims=.: » %%a in (%temp%\tempip.txt) do (echo %USERNAME%;%COMPUTERNAME%;%%b;%t%;%DATE%;%TIME% >> \\nas\log$\users\%USERNAME%.domain1.ru.txt)
del %temp%\tempip.txt

usage \\domain\netlogon\logon.cmd logon/logoff

Для ПК:

set t=%1
ipconfig | find «IPv4» > %temp%\tempip.txt
for /f «tokens=1* delims=.: » %%a in (%temp%\tempip.txt) do (echo %COMPUTERNAME%;%%b;%t%;%DATE%;%TIME% >> \\nas\log$\computers\%COMPUTERNAME%.domain1.ru.txt)
del %temp%\tempip.txt

Остается распарсить данные и куда-нибудь сохранить.
(2 модуля: пк и пользователи. Код без комментариев.)

И писать в базу с помощью powershell

import-module activedirectory [void][system.reflection.Assembly]::LoadWithPartialName("MySql.Data")  $server= "localhost" $username= "logon" $password= "" $database= "logon"  Set-Variable SqlConnection (New-Object MySql.Data.MySqlClient.MySqlConnection) -Scope Global -Option AllScope -Description "Personal variable for Sql Query functions" $SqlConnection.ConnectionString = "server=$server;user id=$username;password=$password;database=$database;pooling=false;Allow Zero Datetime=True;"  if (-not ($SqlConnection.State -like "Open")) { $SqlConnection.Open() }  function global:Get-SqlDataTable( $Query = $(if (-not ($Query -gt $null)) {Read-Host "Query to run"}) ) { #	if (-not ($SqlConnection.State -like "Open")) { $SqlConnection.Open() } 	$SqlCmd = New-Object MySql.Data.MySqlClient.MySqlCommand $Query, $SqlConnection 	$SqlAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter 	$SqlAdapter.SelectCommand = $SqlCmd 	$DataSet = New-Object System.Data.DataSet 	$SqlAdapter.Fill($DataSet) | Out-Null 	return $DataSet.Tables[0] }  function global:Insert-SqlDataTable( $Query = $(if (-not ($Query -gt $null)) {Read-Host "Query to run"}) ) { #	if (-not ($SqlConnection.State -like "Open")) { $SqlConnection.Open() } 	$SqlCmd = New-Object MySql.Data.MySqlClient.MySqlCommand $Query, $SqlConnection 	$SqlAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter         $SqlAdapter.InsertCommand = $SqlCmd          $SqlAdapter.InsertCommand.ExecuteNonQuery() }   $events = Get-SqlDataTable 'select * from events'  $domain = 1;  $fusers = $null  if ((get-childitem \\nas\log$\users | measure).count -gt 0){   $fusers += get-childitem \\nas\log$\users }  if ((get-childitem \\nas.d2.local\auth_log$\users | measure).count -gt 0){   $fusers += get-childitem \\nas.d2.local\auth_log$\users }   foreach ($fuser in $fusers){    if ($fuser.name -match "domain1"){     $domain = 1   }   if ($fuser.name -match "domain2"){     $domain = 2   }   write-host domain $domain -foregroundcolor blue     write-host "Пользователь:" $fuser.BaseName -foregroundcolor green   if (!(test-path $fuser.fullname)){continue}    $ustr = get-content $fuser.fullname      $error_code = $null   $res_sql_user = $null   $res_sql_latestdata = $null   $res_sql_latestlogons = $null    foreach ($line in $ustr){     $line1 = $line -replace "['|()]"     $arr = $line1.split(";")      if ($arr[0]){       if ($domain -eq 1){         $uname = (get-aduser $arr[0]).name       }       if ($domain -eq 2){         $uname = (get-aduser $arr[0] -server dc.domain2).name       }         $sql_str = "select id from users where lower(samaccountname) = lower('" + $arr[0] + "') and domain_id = $domain order by id limit 1;";        $userid = (Get-SqlDataTable $sql_str).id        if (!$userid){         $sql_str = "insert into users (domain_id, samaccountname, name) values ($domain, '" + $arr[0] + "', '$uname'); select id from users where lower(samaccountname) = lower('" + $arr[0] + "') and domain_id = $domain order by id limit 1;";          $userid = (Get-SqlDataTable $sql_str).id       }     }      if ($arr[1]){       $compname = $arr[1]       $cdomain = 1       if ($compname -match "cn1"){         $cdomain = 1       }       if ($compname -match "cn2"){         $cdomain = 2       }        $sql_str = "select id from computers where lower(samaccountname) = lower('" + $arr[1] + "') order by id limit 1;";        $computerid = (Get-SqlDataTable $sql_str).id       if (!$computerid){         $sql_str = "insert into computers (domain_id, samaccountname) values ($cdomain, '" + $arr[1] + "'); select id from computers where lower(samaccountname) = lower('" + $arr[1] + "') order by id limit 1;";          $computerid = (Get-SqlDataTable $sql_str).id         write-host $sql_str -foregroundcolor yellow       }     }      $ev = $events | ?{$_.event -eq $arr[3]}     switch(($ev | measure).count){       0 { 	write-host $arr[3] "error" -foregroundcolor red 	exit 	         write-host $arr[3] "will be added" -foregroundcolor yellow         $sql_str = "insert into events (event) values ('" + $arr[3] + "')"         Insert-SqlDataTable $sql_str         $events = Get-SqlDataTable 'select * from events' 	$sql_str  | out-file sql.txt -append       }       1 {         #write-host $arr[3] "select as current" -foregroundcolor white       }       default: {$error_code = 3; write-host $arr[3] "error" -foregroundcolor red; exit}     }                   $curip = $arr[2] -replace "Address. . . . . . . . . . . :","" -replace "IPv4 Address. . : ","" -replace "IPv4- ¤аҐб  . . . . : ",""     if ($curip){       $sql_str = "select id from ips where lower(ip) = lower('$curip') order by id limit 1;"        $ipid = (Get-SqlDataTable $sql_str).id       if (!$ipid){         $sql_str = "insert into ips (ip) values ('$curip'); select id from ips where lower(ip) = lower('$curip') order by id limit 1;";          write-host $sql_str -foregroundcolor yellow         $ipid = (Get-SqlDataTable $sql_str).id       }     }      if (!$userid){write-host u userid null; exit;}     if (!$computerid){write-host u computerid null; exit;}     if (!$ipid){write-host u ipid null; exit;}     $eventid = ($events | ?{$_.event -eq $arr[3]}).id | select -first 1        #$ip = $arr[2] -replace "Address. . . . . . . . . . . :","" -replace "IPv4 Address. . : ",""       #$dt = "{0:yyyy-MM-dd HH:mm:ss}" -f [datetime](($arr[4] + " " + $arr[5]) -replace '^(.*),.*$','$1')        #$dt = get-date(($arr[4] + " " + $arr[5]) -replace '^(.*),.*$','$1') -format "yyyy-MM-dd hh:mm:ss zzz"       #$dt = get-date(($arr[4] + " " + $arr[5])) -format "yyyy-MM-dd hh:mm:ss zzz"            $t = ($arr[5] -replace '^(.*),.*$','$1').split(":")     $dt = get-date((get-date($arr[4])).Addhours($t[0]).AddMinutes($t[1]).Addseconds($t[2])) -format "yyyy-MM-dd HH:mm:ss"          if ($arr[0]){       if ($domain -eq 1){         $groups = ((Get-ADuser $arr[0] -Property memberof).memberof | %{Get-ADGroup -Identity $_} | sort name).name       }       if ($domain -eq 2){         $groups = ((Get-ADuser $arr[0] -server dc.domain2 -Property memberof).memberof | %{Get-ADGroup -Identity $_ -server dc.domain2 } | sort name).name       }       #$groups = ((Get-ADuser $arr[0] -Property memberof).memberof | %{Get-ADGroup -Identity $_} | sort name).name       $val1 = ""       $val2 = ""       $groupscnt = ($groups | measure).count       #write-host $groupscnt -foregroundcolor red       for ($i = 0; $i -lt $groupscnt; $i++){         $gr = $groups[$i] -replace "'","\'"          $sql_str = "select id from groups where name = '$gr'"         $groupid = (Get-SqlDataTable $sql_str).id         if (!$groupid){           $sql_str = "insert into groups (name) values ('$gr');"           $res = Get-SqlDataTable $sql_str         }          $val2 = $val2 + "'" + $gr  + "'"         if ($i -lt $groupscnt - 1){$val2 = $val2 + ","}       }       $sql_str = "select id from groups where name in ($val2);"       $groupsid = (Get-SqlDataTable $sql_str).id        $groupscnt = ($groupsid | measure).count       $val3 = ""       for ($i = 0; $i -lt $groupscnt; $i++){         $val3 = $val3 + "($userid, " + $groupsid[$i] + ", 1, '$dt')"         if ($i -lt $groupscnt - 1){$val3 = $val3 + ","}       }       $sql_str = "insert into membership (uid, gid, used, user_dt) values $val3 on duplicate key update used=values(used), user_dt=values(user_dt); update membership SET used=0 WHERE uid = $userid and user_dt != '$dt'"       #$sql_str       $res = Get-SqlDataTable $sql_str       #$res     }   #      $datetime = "{0:yyyy-MM-dd hh:mm:ss}" -f [datetime]$dt #::ParseExact($dt, "dd.MM.yyyy hh:mm:ss",$null)       #$datetime = get-date($dt)  #      $datetime     $res_sql_user += "insert into logonusers (domain_id, user_id, computer_id, event_id, ip_id, dt) values ($domain, $userid, $computerid, $eventid, $ipid, '$dt');"     $res_sql_latestdata += "insert into latestdata (domain_id, computer_id, user_id, user_event_id, ip_id, user_dt) values ($domain, $computerid, $userid, $eventid, $ipid, '$dt') on duplicate key update computer_id=values(computer_id), user_id=values(user_id), user_event_id=values(user_event_id), user_dt=values(user_dt), ip_id=values(ip_id);"     if ($eventid -eq 2){       $res_sql_latestlogons += "insert into latestlogons (domain_id, computer_id, user_id, user_event_id, ip_id, user_dt, processlogoff) values ($domain, $computerid, $userid, $eventid, $ipid, '$dt', 0) on duplicate key update computer_id=values(computer_id), user_id=values(user_id), user_event_id=values(user_event_id), user_dt=values(user_dt), ip_id=values(ip_id), processlogoff=values(processlogoff);"     }     if ($eventid -eq 1){       $res_sql_latestlogons += "delete from latestlogons where computer_id = $computerid and user_id = $userid limit 1;"     }       #    $sql = "insert into logon () values";   }   if (!$error_code){     $out = Insert-SqlDataTable $res_sql_user     $res_sql_user     $out = Insert-SqlDataTable $res_sql_latestdata     $res_sql_latestdata     $out = Insert-SqlDataTable $res_sql_latestlogons     $res_sql_latestlogons     remove-item $fuser.fullname -force   } } ###################################################################### ###################################################################### ###################################################################### ###################################################################### ######################################################################  $fcomputers = $null  if ((get-childitem \\nas\log$\computers | measure).count -gt 0){   $fcomputers += get-childitem \\nas\log$\computers }  if ((get-childitem \\nas.d2.local\auth_log$\computers | measure).count -gt 0){   $fcomputers += get-childitem \\nas.d2.local\auth_log$\computers }   foreach ($fcomputer in $fcomputers){    if ($fcomputer.name -match "domain1"){     $domain = 1   }   if ($fcomputer.name -match "domain2"){     $domain = 2   } write-host domain $domain -foregroundcolor blue    write-host "ПК:" $fcomputer.BaseName -foregroundcolor green   $cstr = get-content $fcomputer.fullname     $error_code = $null   $res_sql_computer = $null   $res_sql_latestdata = $null    foreach ($line in $cstr){     $arr = $line.split(";")      if ($arr[0]){       $sql_str = "select id from computers where lower(samaccountname) = lower('" + $arr[0] + "') order by id limit 1;";        $computerid = (Get-SqlDataTable $sql_str).id       if (!$computerid){         $sql_str = "insert into computers (domain_id, samaccountname) values ($domain, '" + $arr[0] + "'); select id from computers where lower(samaccountname) = lower('" + $arr[0] + "') order by id limit 1;";          $computerid = (Get-SqlDataTable $sql_str).id        }     }      $ev = $events | ?{$_.event -eq $arr[2]}     switch(($ev | measure).count){       0 {         write-host $arr[2] "will be added" -foregroundcolor yellow         #$sql_str = "insert into events (event) values ('" + $arr[2] + "')"         Insert-SqlDataTable $sql_str         $events = Get-SqlDataTable 'select * from events'         $sql_str | out-file sql.txt -append       }       1 {         #write-host $arr[3] "select as current" -foregroundcolor white       }       default: {$error_code = 3; write-host $arr[2] "error" -foregroundcolor red}     }                   $curip = $arr[1] -replace "Address. . . . . . . . . . . :","" -replace "IPv4 Address. . : ","" -replace "IPv4- ¤аҐб  . . . . : ",""     #write-host $arr[1] $curip      #$error_code = 4      if ($curip){       $sql_str = "select id from ips where lower(ip) = lower('$curip') order by id limit 1;"        $ipid = (Get-SqlDataTable $sql_str).id       if (!$ipid){         $sql_str = "insert into ips (ip) values ('$curip'); select id from ips where lower(ip) = lower('$curip') order by id limit 1;";          $ipid = (Get-SqlDataTable $sql_str).id       }     }       if (!$error_code){       $eventid = ($events | ?{$_.event -eq $arr[2]}).id | select -first 1       if (!$computerid){write-host c computerid null; exit;}       if (!$ipid){write-host c ipid null; exit;}        $t = ($arr[4] -replace '^(.*),.*$','$1').split(":")       $dt = get-date((get-date($arr[3])).Addhours($t[0]).AddMinutes($t[1]).Addseconds($t[2])) -format "yyyy-MM-dd HH:mm:ss"        $res_sql_computer += "insert into logoncomputers (domain_id, computer_id, event_id, ip_id, dt) values ($domain, $computerid, $eventid, $ipid, '$dt');"       $res_sql_latestdata += "insert into latestdata (domain_id, computer_id, computer_event_id, computer_dt, ip_id) values ($domain, $computerid, $eventid, '$dt', $ipid) on duplicate key update computer_id=values(computer_id), computer_event_id=values(computer_event_id), computer_dt=values(computer_dt), ip_id=values(ip_id);"     }   }   if (!$error_code){     $res_sql_computer     $out = Insert-SqlDataTable $res_sql_computer     $res_sql_latestdata     $out = Insert-SqlDataTable $res_sql_latestdata     remove-item $fcomputer.fullname -force   } }  $SqlConnection.Close() 

Теперь у нас данные сохраняются в базу данных. Далее мы можем делать с ними все что угодно:

Например нарисовать план этажа и выводить статистику


Динамически формируется их Excel файлов на файловой шаре.
Поиск возможен по:

  • Имени пользователя
  • IP
  • Имени ПК
  • Группе

Пример портала

Отчеты которые мы можем получить

Сотрудники, имеющие входы на разных ПК за исключением администраторов

SELECT      (SELECT              COUNT(*)         FROM             latestlogons t         WHERE             t.user_id = ll.user_id) AS cnt,     u.samaccountname,     ll.user_id,     u.name,     c.samaccountname,     ll.computer_id,     i.ip,     ll.user_dt,     bc.bc,     ipn.tplname FROM     latestlogons ll         JOIN     (SELECT          *     FROM         users     WHERE         id NOT IN (SELECT DISTINCT                 u.id             FROM                 users u             JOIN membership m ON m.uid = u.id             JOIN groups gr ON gr.id = m.gid             WHERE                 gr.name IN ('Domain Admins'))) u ON u.id = ll.user_id         JOIN     computers c ON c.id = ll.computer_id         JOIN     ips i ON i.id = ll.ip_id ORDER BY cnt DESC , u.samaccountname 

или просто суммарно по активным сеансам

SELECT      COUNT(*) AS cnt,     u.samaccountname,     ll.user_id,     u.name,     c.samaccountname,     ll.computer_id,     i.ip FROM     latestlogons ll         JOIN     users u ON u.id = ll.user_id         JOIN     computers c ON c.id = ll.computer_id         JOIN     ips i ON i.id = ll.ip_id GROUP BY user_id ORDER BY cnt DESC 

Вход/Выход сотрудников за прошлые сутки (с 00:00:00 по 23:59:59)

SELECT      d.name,     u.samaccountname,     u.name,     c.samaccountname,     e.event,     lu.dt FROM     logonusers lu         JOIN     users u ON u.id = lu.user_id         JOIN     computers c ON c.id = lu.computer_id         JOIN     ips i ON i.id = lu.ip_id         JOIN     events e ON e.id = lu.event_id         JOIN     domains d ON d.id = lu.domain_id WHERE     (i.ip LIKE '172.16.16.%'         OR i.ip LIKE '172.16.32%'         OR i.ip LIKE '172.16.33%'         OR i.ip LIKE '172.16.34%'         OR i.ip LIKE '172.16.35%')         AND lu.dt >= CURDATE() - INTERVAL 1 DAY         AND lu.dt <= CURDATE() ORDER BY lu.dt 

Найти всех с определенной группой

SELECT DISTINCT     c.samaccountname, u.samaccountname, u.name, i.ip FROM     `latestdata` ld         LEFT JOIN     computers c ON c.id = ld.computer_id         LEFT JOIN     users u ON u.id = ld.user_id         LEFT JOIN     `events` e ON e.id = ld.computer_event_id         LEFT JOIN     `events` e2 ON e2.id = ld.user_event_id         LEFT JOIN     `ips` i ON ld.ip_id = i.id         LEFT JOIN     membership m ON m.uid = u.id         LEFT JOIN     groups g ON g.id = m.gid WHERE     LOWER(g.name) = LOWER('$key') 

Как определить залоченный экран у пользователя
Без аудита событий блокировки экрана можно сделать следующим способом (пишем в файл или используем вебсервис)

C#

         void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)         {             switch (e.Reason)             {                 // ...                 case SessionSwitchReason.SessionLock:                     // Do whatever you need to do for a lock                     // ...                     write_event("locked");                     //MessageBox.Show("Locked" + DateTime.Now);                     break;                 case SessionSwitchReason.SessionUnlock:                     // Do whatever you need to do for an unlock                     // ...                     write_event("unlocked");                     //MessageBox.Show("UnLocked" + DateTime.Now);                     break;                     //case SessionSwitchReason.                 // ...             }         }          void write_event (string e){             string computer = System.Environment.MachineName;             string user = Environment.UserName;              try              {                 StreamWriter w = File.AppendText(@"\\nas\log$\events\" + user + @".domain1.ru.txt");                 w.WriteLine(computer + ";" + user + ";" + e + ";" + DateTime.Now);                 w.Close();             }             catch {}          }          private void Form1_Load(object sender, EventArgs e)         {             this.Opacity = 0;             this.ShowInTaskbar = false;             SystemEvents.SessionSwitch += new SessionSwitchEventHandler(SystemEvents_SessionSwitch);         } 

Данное приложение все-таки придется стартовать у пользователей, поэтому добавим возможность апдейтить приложение на лету:

C#

            string source = @"\\nas\userstat$\Data\active_ws.exe";             string tempdir = System.IO.Path.GetTempPath();             string destFile = System.IO.Path.Combine(tempdir, "active_ws.exe");             if (File.Exists(source))              {                 try                 {                     System.IO.File.Copy(source, destFile, true);                     Process.Start(destFile);                 }                 catch (Exception e)                 {                  }             } 

Дадим пользователю предупреждение, что он залогинен на нескольких машинах и возможность выполнить выход

res = GET(«api/?», «s=status»);

Рисуем кнопки и навешываем событие

System.Windows.Forms.Label label = new System.Windows.Forms.Label();  label.Text = reader.Value.ToString(); label.Name = "c_" + label.Text; label.Width = 200; label.Height = 50; label.Top = 2 + (label.Height * label_cnt); label.Left = 2; label.Font = new System.Drawing.Font("Calibri", 24, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0))); label.ForeColor = Color.Red; panel1.Controls.Add(label);  System.Windows.Forms.Button button = new System.Windows.Forms.Button(); button.Name = reader.Value.ToString(); button.Text = "Выполнить выход на данном ПК"; button.Left = 210; button.Top = label.Top; button.Font = new System.Drawing.Font("Calibri", 12, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0))); button.Width = 250; button.Height = 50; if (button.Name == System.Environment.MachineName) {     button.Enabled = false;     button.Text = "Текущий ПК"; } button.Click += new EventHandler(btn_Click); panel1.Controls.Add(button); 

Обработчик кнопки

        private void btn_Click(object sender, EventArgs e)         {             string res = null;             foreach (Control item in panel1.Controls.OfType<Control>())             {                 if (item.Tag == sender || item == sender)                 {                     item.Enabled = false;                     item.Text = "Выход будет выполнен в течение 2-х минут";                     try                     {                         res = POST("http://api/?", "s=logout&c=" + item.Name); //item.Name - содержит имя ПК                     }                     catch (Exception exception)                     {                                             }                  }             }         } 

И не забываем проверить, что нужно выйти

                        if (c_logoff == System.Environment.MachineName)                         {                             Process.Start("shutdown", "-l -f");                         } 

Старт дан, дальше только полет фантазии.

ссылка на оригинал статьи http://habrahabr.ru/post/253937/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *