Pull to refresh

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

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

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

Также накладывается ряд ограничений из-за:
  • производительности контролеров домена
  • ПО безопасников (нефиг тут запускать левые 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");
                        }




Старт дан, дальше только полет фантазии.
Tags:
Hubs:
+12
Comments12

Articles