Помимо основной задачи предотвращения утечек конфиденциальной информации у DLP-системы могут быть и второстепенные (дополнительные) задачи. К ним относятся:

  • копирование передаваемых сообщений для расследования инцидентов безопасности, в будущем;
  • устранение возможности оправки не только конфиденциальной информации, но и различной нежелательной (спама, оскорбительных выражений, информации эротического содержания, огромных объёмов данных и т.п.);
  • фильтрация нежелательной информации при получении, а не только при отправке;
  • устранения способов использования информационных ресурсов, сотрудниками, в личных целях;
  • уменьшение трафика, оптимизация нагрузки каналов;
  • контроль рабочего времени сотрудников.

Наша ручная DLP система не будет решать все поставленные задачи, а сосредоточиться только на:

  • поиске определенных файлов в сети;
  • составление отчетов и доставка отчетов системному администратору или офицеру безопасности;
  • выполнение удаления, по определенным критериям.

Поиск файлов на ОС Windows, простая и тривиальная задача. Даже поиск на удаленной машине не намного сложнее. А вот если нужно, что то найти на сотни машинах, тут уж возникает вопрос как? Не руками же проходиться по всем ПК. Данная задача довольно часто встречается в работе Windows админов, когда например, нужно провести аудит по хранению информации. Вы скажете, что есть примеры реализации, да, но они больше нацелены на поиск запрещенной для хранения данных (фильмов, игр и т.п), предложенный мной вариант реализует одну из задач DLP системы.

И так что умеет скрипт (точнее набор скриптов)? Что бы не утруждать администратора и не выгружать список ПК для проверки, скрипт интегрируется с AD и получает нужную ему информацию, имеется возможность фильтра по расширению и имени, добавления исключений, формирования отчета в папке и с возможностью отослать сообщение администратору и пользователю (Оповещение об обнаруженных файлах и рекомендациям, которое необходимо предпринять). Параметры можно комбинировать и менять, получая нужный функционал. После поиска формируется список файлов с именами ПК в которых хранится информация по поиску. Вторая часть скрипта это удаление найденных файлов, всех или по определенным критериям.

Мы используем этот скрипт с целью слежения за пользователями и сообщения им о необходимости хранить рабочие документы в определенном месте (личном сетевом диске), скрипт определяет активного пользователя на ПК, берет данные о почтовом ящике из АД и отсылает уведомления о том какой файл и где обнаружен на локальном диске, который пользователь должен переместить на сетевое хранилище либо он будет удален. В конечном итоге удаляем, то, что не по регламенту. Таким образом, реализуем одну из функций DLP-систем по контролю хранения конфиденциальной информации.

Алгоритм скрипта по поиску файлов




  1. Получает перечень рабочих станций из определенной OU
  2. Проверяет данные в атрибуте HomePage, если он имеет значение «Pass», пропускает поиск файлов, так как на этом компьютере уже осуществлялся поиск
  3. Проверяет доступность
  4. Если не доступен, записывает об этом в файл
  5. Если доступен, выполняет поиск файлов
  6. По окончанию поиска, записывает в атрибут HomePage – значение «Pass»
  7. Формируется файл и именем машины и перечнем найденных файлов
  8. Оправляется сообщение администратору с вложением
  9. Определяет имя локального пользователя
  10. Узнает в AD почтовый адрес пользователя
  11. Отправляет копию отчета

Алгоритм скрипта сброса обхода




  1. Получает перечень машин из AD
  2. Устанавливает значение атрибута notpass

Таким образом все машины, попадают в поле обработки скрипта, в том числе которые уже были отсканированы.

Алгоритм скрипта удаления файлов




  1. Загружает содержимое скрипта
  2. По каждой строке из списка (результата), удаляет объект на удаленном компьютере

Настройка скрипта и запуск (аудит файлов)


Скрипт выполняется как команда с заданными параметрами. Ниже приведены примеры запуска скрипта и его параметры. Start-AuditFiles – команда выполняющая скрипт. Параметры можно комбинировать, так как того требует поставленная задача.

Пример 1


Start-AuditFiles -OU "OU=Test,DC=root,DC=local" -SMTP smtp.server.com -AdminMail administrator@server.com -IncludeFile *.doc,*.docx,*.sys -ExclusionFile *File1*,*File2* -ExclusionFolder “*Folder1*,*Folder2*” -ReportPath \\server\reports\ - Throttle 5

В этом примере осуществляется поиск на компьютерах из OU, файлы все с расширением (*.doc,*.docx,*.sys) кроме файлов (*File1*,*File2*), кроме каталогов (*Folder1*,*Folder2*), отчет дублируется в каталог (\\server\reports\). Отчет отсылается пользователю и администратору. Количество потоков равно 5-ти.

Пример 2


Start-AuditFiles -RemoteComputer ws-pc-4902,ws-pc-0982 -SMTP smtp.server.com -AdminMail administrator@server.com -Include *.doc,*.docx,*.sys -ExclusionFile *New*,*au* -AdminOnly - Throttle 10

В этом примере осуществляется поиск на компьютерах (ws-pc-4902,ws-pc-098), файлы все с расширением (*.doc,*.docx,*.sys) кроме файлов (*File1*,*File2*). Отчет отсылается только администратору. Количество потоков равно 10-ти.

Параметры целевых компьютеров


OU (обязательный или необходимо задать RemoteComputer) – путь к организационной единице с целевыми компьютерами, если этот параметр не указан, следует указать параметр RemoteComputer. Один из двух этих параметров должен быть использован в скрипте.

Пример: -OU «OU=Test,DC=root,DC=local» или -OU $Computerlist (задается переменная в комбинации с другими скриптами).

RemoteComputer (обязательный или необходимо задать OU) – задается если необходимо выполнить скрипт только для определенных компьютеров из списка, либо один определенный, либо несколько указав из через запятую.

Пример: -RemoteComputer ws-pc-4902,ws-pc-0982

Параметры поиска


IncludeFile (обязательный, возможно использование маски *) – перечень файлов или их расширения, поиск которых необходимо выполнить (может быть списком).

ExclusionFile (опциональный) – перечень файлов, которые необходимо исключить из поиска (может быть списком).

ExclusionFolder (опциональный) – перечень исключенных из поиска каталогов.

Параметры отчетов


ReportPath (опциональный) – путь к сетевому ресурсу или локальному каталогу, в который будет выполняться копирование результатов сканирования.

AdminMail (опциональный) – адрес от имени которого выполняется отправка отчета, на этот же адрес будут доставляться отчеты предназначенные администратору.

SMTP (опциональный) – имя SMTP сервере, используемый в качестве шлюза отправки сообщений.

AdminOnly (опциональный) – включает режим отправки отчетов только администратору.

Throttle (обязательный, числовое значение от 1 – до 99) – устанавливает количество потоков сканирования.

Установка модулей


Необходимо в каталог «C:\Windows\system32\WindowsPowerShell\v1.0\Modules», скопировать файлы:
Invoke-Parallel.psm1
Start-AuditFiles.psm1


Перед выполнением скрипта модули необходимо импортировать:

Import-Module C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Invoke-Parallel.psm1
Import-Module C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Start-AuditFiles.psm1

Скрипт (модуль)


Данный скрипт необходимо сохранить как файл Invoke-Parallel.psm1.

Скрипт Invoke-Parallel.psm1

function Invoke-Parallel {

    [cmdletbinding(DefaultParameterSetName='ScriptBlock')]
    Param (   
        [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')]
            [System.Management.Automation.ScriptBlock]$ScriptBlock,

        [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')]
        [ValidateScript({test-path $_ -pathtype leaf})]
            $ScriptFile,

        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [Alias('CN','__Server','IPAddress','Server','ComputerName')]    
            [PSObject]$InputObject,

            [PSObject]$Parameter,

            [switch]$ImportVariables,

            [switch]$ImportModules,

            [int]$Throttle = 20,

            [int]$SleepTimer = 200,

            [int]$RunspaceTimeout = 0,

			[switch]$NoCloseOnTimeout = $false,

            [int]$MaxQueue,

        [validatescript({Test-Path (Split-Path $_ -parent)})]
            [string]$LogFile = "C:\temp\log.log",

			[switch] $Quiet = $false
    )
    
    Begin {
                
        
        if( -not $PSBoundParameters.ContainsKey('MaxQueue') )
        {
            if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle }
            else{ $script:MaxQueue = $Throttle * 3 }
        }
        else
        {
            $script:MaxQueue = $MaxQueue
        }

        Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue' logFile '$logFile'"

        
        if ($ImportVariables -or $ImportModules)
        {
            $StandardUserEnv = [powershell]::Create().addscript({

                $Modules = Get-Module | Select -ExpandProperty Name
                $Snapins = Get-PSSnapin | Select -ExpandProperty Name

               
                $Variables = Get-Variable | Select -ExpandProperty Name
                
                
                @{
                    Variables = $Variables
                    Modules = $Modules
                    Snapins = $Snapins
                }
            }).invoke()[0]
            
            if ($ImportVariables) {
               
                Function _temp {[cmdletbinding()] param() }
                $VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables )
                Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")"

               
                $UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) 
                Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n"

            }

            if ($ImportModules) 
            {
                $UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path )
                $UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) 
            }
        }

        
            
            Function Get-RunspaceData {
                [cmdletbinding()]
                param( [switch]$Wait )

               
                Do {

                   
                    $more = $false

                   
                    if (-not $Quiet) {
						Write-Progress  -Activity "Running Query" -Status "Starting threads"`
							-CurrentOperation "$startedCount threads defined - $totalCount input objects - $script:completedCount input objects processed"`
							-PercentComplete $( Try { $script:completedCount / $totalCount * 100 } Catch {0} )
					}

                   
                    Foreach($runspace in $runspaces) {
                    
                       
                        $currentdate = Get-Date
                        $runtime = $currentdate - $runspace.startTime
                        $runMin = [math]::Round( $runtime.totalminutes ,2 )

          
                        $log = "" | select Date, Action, Runtime, Status, Details
                        $log.Action = "Removing:'$($runspace.object)'"
                        $log.Date = $currentdate
                        $log.Runtime = "$runMin minutes"

                      
                        If ($runspace.Runspace.isCompleted) {
                            
                            $script:completedCount++
                        
                         
                            if($runspace.powershell.Streams.Error.Count -gt 0) {
                                
                               
                                $log.status = "CompletedWithErrors"
                                Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1]
                                foreach($ErrorRecord in $runspace.powershell.Streams.Error) {
                                    Write-Error -ErrorRecord $ErrorRecord
                                }
                            }
                            else {
                                
                            
                                $log.status = "Completed"
                                Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1]
                            }

        
                            $runspace.powershell.EndInvoke($runspace.Runspace)
                            $runspace.powershell.dispose()
                            $runspace.Runspace = $null
                            $runspace.powershell = $null

                        }

                        ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) {
                            
                            $script:completedCount++
                            $timedOutTasks = $true
                            
						
                            $log.status = "TimedOut"
                            Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1]
                            Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)"

                           
                            if (!$noCloseOnTimeout) { $runspace.powershell.dispose() }
                            $runspace.Runspace = $null
                            $runspace.powershell = $null
                            $completedCount++

                        }
                   
                     
                        ElseIf ($runspace.Runspace -ne $null ) {
                            $log = $null
                            $more = $true
                        }

                    
                        if($logFile -and $log){
                            ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | out-file $LogFile -append
                        }
                    }

                
                    $temphash = $runspaces.clone()
                    $temphash | Where { $_.runspace -eq $Null } | ForEach {
                        $Runspaces.remove($_)
                    }


                    if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer }


                } while ($more -and $PSBoundParameters['Wait'])
                
  
            }

        

            if($PSCmdlet.ParameterSetName -eq 'ScriptFile')
            {
                $ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) )
            }
            elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock')
            {
        
                [string[]]$ParamsToAdd = '$_'
                if( $PSBoundParameters.ContainsKey('Parameter') )
                {
                    $ParamsToAdd += '$Parameter'
                }

                $UsingVariableData = $Null
                

                
                
                if($PSVersionTable.PSVersion.Major -gt 2)
                {
                    
                    $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True)    

                    If ($UsingVariables)
                    {
                        $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]'
                        ForEach ($Ast in $UsingVariables)
                        {
                            [void]$list.Add($Ast.SubExpression)
                        }

                        $UsingVar = $UsingVariables | Group SubExpression | ForEach {$_.Group | Select -First 1}
        
              
                        $UsingVariableData = ForEach ($Var in $UsingVar) {
                            Try
                            {
                                $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop
                                [pscustomobject]@{
                                    Name = $Var.SubExpression.Extent.Text
                                    Value = $Value.Value
                                    NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath)
                                    NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath)
                                }
                            }
                            Catch
                            {
                                Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!"
                            }
                        }
                        $ParamsToAdd += $UsingVariableData | Select -ExpandProperty NewName -Unique

                        $NewParams = $UsingVariableData.NewName -join ', '
                        $Tuple = [Tuple]::Create($list, $NewParams)
                        $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance"
                        $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags))
        
                        $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple))

                        $ScriptBlock = [scriptblock]::Create($StringScriptBlock)

                        Write-Verbose $StringScriptBlock
                    }
                }
                
                $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString())
            }
            else
            {
                Throw "Must provide ScriptBlock or ScriptFile"; Break
            }

            Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)"
            Write-Verbose "Creating runspace pool and session states"

            
            $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
            if ($ImportVariables)
            {
                if($UserVariables.count -gt 0)
                {
                    foreach($Variable in $UserVariables)
                    {
                        $sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) )
                    }
                }
            }
            if ($ImportModules)
            {
                if($UserModules.count -gt 0)
                {
                    foreach($ModulePath in $UserModules)
                    {
                        $sessionstate.ImportPSModule($ModulePath)
                    }
                }
                if($UserSnapins.count -gt 0)
                {
                    foreach($PSSnapin in $UserSnapins)
                    {
                        [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null)
                    }
                }
            }

          
            $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host)
            $runspacepool.Open() 

            Write-Verbose "Creating empty collection to hold runspace jobs"
            $Script:runspaces = New-Object System.Collections.ArrayList        
        
         
            $bound = $PSBoundParameters.keys -contains "InputObject"
            if(-not $bound)
            {
                [System.Collections.ArrayList]$allObjects = @()
            }

            
            if( $LogFile ){
                New-Item -ItemType file -path $logFile -force | Out-Null
                ("" | Select Date, Action, Runtime, Status, Details | ConvertTo-Csv -NoTypeInformation -Delimiter ";")[0] | Out-File $LogFile
            }

      
            $log = "" | Select Date, Action, Runtime, Status, Details
                $log.Date = Get-Date
                $log.Action = "Batch processing started"
                $log.Runtime = $null
                $log.Status = "Started"
                $log.Details = $null
                if($logFile) {
                    ($log | convertto-csv -Delimiter ";" -NoTypeInformation)[1] | Out-File $LogFile -Append
                }

			$timedOutTasks = $false

     
    }

    Process {

        
        if($bound)
        {
            $allObjects = $InputObject
        }
        Else
        {
            [void]$allObjects.add( $InputObject )
        }
    }

    End {
        
        
        Try
        {
        
            $totalCount = $allObjects.count
            $script:completedCount = 0
            $startedCount = 0

            foreach($object in $allObjects){
        
              
                    

                    $powershell = [powershell]::Create()
                    
                    if ($VerbosePreference -eq 'Continue')
                    {
                        [void]$PowerShell.AddScript({$VerbosePreference = 'Continue'})
                    }

                    [void]$PowerShell.AddScript($ScriptBlock).AddArgument($object)

                    if ($parameter)
                    {
                        [void]$PowerShell.AddArgument($parameter)
                    }

               
                    if ($UsingVariableData)
                    {
                        Foreach($UsingVariable in $UsingVariableData) {
                            Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)"
                            [void]$PowerShell.AddArgument($UsingVariable.Value)
                        }
                    }

                
                    $powershell.RunspacePool = $runspacepool
    
       
                    $temp = "" | Select-Object PowerShell, StartTime, object, Runspace
                    $temp.PowerShell = $powershell
                    $temp.StartTime = Get-Date
                    $temp.object = $object
    
                  
                    $temp.Runspace = $powershell.BeginInvoke()
                    $startedCount++

                   
                    Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() )
                    $runspaces.Add($temp) | Out-Null
            
                 
                    Get-RunspaceData

                    
                    $firstRun = $true
                    while ($runspaces.count -ge $Script:MaxQueue) {

                       
                        if($firstRun){
                            Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit."
                        }
                        $firstRun = $false
                    
          
                        Get-RunspaceData
                        Start-Sleep -Milliseconds $sleepTimer
                    
                    }

                
            }
                     
            Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) )
            Get-RunspaceData -wait

            if (-not $quiet) {
			    Write-Progress -Activity "Running Query" -Status "Starting threads" -Completed
		    }
        }
        Finally
        {
        
            if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) {
	            Write-Verbose "Closing the runspace pool"
			    $runspacepool.close()
            }

     
            [gc]::Collect()
        }       
    }
}




Следующий скрипт необходимо сохранить как файл Start-AuditFiles.psm1.

Скрипт Start-AuditFiles.psm1

$Body =

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

Вам необходимо в кратчайшие сроки:

1) проверить указанные документы на предмет наличия служебной информации
2) переместить файлы содержащие служебную информацию на личный сетевой диск.



Function Start-AuditFiles {


<#
.Synopsis
    Сканирует файлы на удаленной машине, в случае успеха отправляет отчет почтовым сообщением
.Description
    Сканер позволяет обнаружить искомые файлы на удаленной машине через административный ресурс (C$ D$ .. и т.д). 
    После выполнения скрипт: 

    1. Находит объекты комрьютер в опеределенной OU или заданый компьютер через параметр
    2. Проверяет доступность машины
    3. Выполняет поиск всех дисков
    4. Выполняет поиск искомых файлов с фильтрацией, формирует отчет
    5. Отправляет отчет администратору
    6. Получает активного пользователя, определяет его почтовый адрес, отправляет копию отчета
    7. Формирует списки результатов сканирования, опционально копирует результаты на удаленный ресурс
    
.Examples

    Пример 1
    Start-AuditFiles -OU "OU=Test,DC=root,DC=local" -SMTP smtp.server.com -AdminMail administrator@server.com -IncludeFile *.doc,*.docx,*.sys -ExclusionFile *File1*,*File2* -ExclusionFolder *Folder1*,*Folder2* -ReportPath \\server\reports
    В этом примере осуществляется поиск на компьютерах из OU, файлы все с расширением (*.doc,*.docx,*.sys) кроме файлов (*File1*,*File2*), кроме каталогов (*Folder1*,*Folder2*), отчет дублируется в каталог (\\server\reports\)

    Пример 2
    Start-AuditFiles -RemoteComputer ws-pc-4902,ws-pc-0982 -SMTP smtp.server.com -AdminMail administrator@server.com -Include *.doc,*.docx,*.sys -ExclusionFile *New*,*au* -AdminOnly

    В этом примере осуществляется поиск на компьютерах (ws-pc-4902,ws-pc-098), файлы все с расширением (*.doc,*.docx,*.sys) кроме файлов (*File1*,*File2*), отчет отсылается только администратору
    
.Notes
    Следует использовать только один из ключей OU или RemoteComputer, OU указывает организационную единицу, RemoteComputer указывает один или несколько компьютер в качестве объектов сканирования
.Link
    ...
#>
        [CmdletBinding()]
        Param (
        [String]$OU,
        [String[]]$RemoteComputer,
        [String[]]$ExclusionFile,
        [String]$ReportPath,  
        [String]$AdminMail,       
        [String[]]$IncludeFile,
        [String[]]$ExclusionFolder,
        [Switch]$AdminOnly = $false,      
        [String]$SMTP,
        [String]$Throttle = 5

        )

If (!$RemoteComputer) {$Hosts = (Get-ADComputer -Filter * -SearchBase $OU -Properties * | where { ( $PSItem.HomePage  -notlike  'pass' )}  ).name} else { $Hosts = $RemoteComputer }

       
        invoke-parallel -InputObject $Hosts -throttle $Throttle -ImportVariables  -ScriptBlock {
            if(Test-Connection -ComputerName $_ -BufferSize 16 -quiet -count 2) {
                $Object = $_
                $ErrorActionPreference = 'SilentlyContinue'
                $ExclusionFolder2 = $ExclusionFolder -replace ",","|"

        $StartTime = (Get-Date).ToString()
        $Hosts        
        (Get-WMIObject Win32_LogicalDisk -filter "DriveType = 3" -ComputerName $Object | %{Get-ChildItem ('\\' + $Object + '\' + ($_.DeviceID).remove(1) + '$\*') -Include $IncludeFile -Exclude $ExclusionFile -Recurse -Force | ?{$PSItem.FullName -notmatch $ExclusionFolder2}}).FullName | Out-File -FilePath $env:TEMP\$Object.txt -Encoding unicode 
        
        If (!$ReportPath) {} else {Copy-Item -Path $env:TEMP\$Object.txt -Destination $ReportPath -Force}

        $EndTime = (Get-Date).ToString()
        Write-Output ($Object) | Add-Content $env:TEMP\Online.txt
       
        Invoke-Item $env:TEMP\$Object.txt
        $Results = "" | Select ComputerName, "StartTime", "EndTime"
        $Results.ComputerName = $Object 
        $Results.StartTime = $StartTime
        $Results.EndTime =$EndTime 
        $Results

 If ((Get-Content $env:TEMP\$Object.txt) -eq $Null) {} 
    else {
  
       
        Try {
                Send-MailMessage -SmtpServer $SMTP -to $AdminMail -Body $Object -From denis.pasternak@hotmail.com -Subject $Object -Attachments $env:TEMP\$Object.txt
            } Catch {''} 
      
         
        If ($AdminOnly -eq $True) { Write-Host "Включен параметр AdminOnly - отчет отправлен только аминистратору" -ForegroundColor Yellow} else 
        {       
           
            $Username=((gwmi win32_computersystem -computer $Object -ErrorAction SilentlyContinue).UserName -split '\\')[1]
            if($username -ne $null)
            {
            $Body = $Body
            $dispalyname = (Get-AdUser $username -properties DisplayName).DisplayName 
            $email = (Get-AdUser $username -properties mail).mail
            sleep -Seconds 3
            Send-MailMessage -SmtpServer  $SMTP -Body  ( 'Уважаемый ' + $Dispalyname + ' ' + $Body | out-string ) -To $email -From $AdminMail -Subject  $Object -Attachments $env:TEMP\$Object.txt -Encoding Unicode
            }
        }
    }

else{ }        
        
       
        }
         else {
        (Write-Output ($Object + ' ' + (Get-Date).ToString()) | Add-Content $env:TEMP\Offline.txt)}
        }
 
    
 $OU= $null
 $RemoteComputer = $null
 $Hosts = $nul
 Get-Content $env:TEMP\Online.txt | Set-ADComputer -HomePage 'pass'
 }


Function Remove-AuditFiles {


[CmdletBinding(SupportsShouldProcess=$True)]
        Param (
        [String]$TargetFile
        
        )

           Get-Content -Path "$env:TEMP\$Path" | %{Remove-Item $PSItem}

}




Function Reset-AuditComputers {


[CmdletBinding(SupportsShouldProcess=$True)]
        Param (
        [String]$TargetOU
        
        )

 Get-ADComputer -Filter * -SearchBase $TargetOU -Properties * | Set-ADComputer -HomePage 'notpass'
'' | Set-Content -Path $env:TEMP\Online.txt 


} 




Выполнение по расписанию


Создайте файл, в примере каталог c:\scripts.

RunScript.ps1 — файл

Скопируйте текст:

Import-Module C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Invoke-Parallel.psm1
Import-Module C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Start-AuditFiles.psm1
# Ниже приведен пример, укажите предпочитаемые параметры и их значения
Start-AuditFiles -OU "OU=Test,DC=root,DC=local" -SMTP smtp.server.com -AdminMail administrator@server.com -IncludeFile *.doc,*.docx,*.sys -ExclusionFile *File1*,*File2* -ExclusionFolder *Folder1*,*Folder2* -ReportPath \\server\reports\ - Throttle 5

Создание расписания поиска файлов


  • в управлении компьютером, перейдите в раздел «Планировщик заданий». Создайте задание, введите желаемое имя задания
  • укажите периодичность «Ежедневное»
  • установите период «каждый день»
  • оставьте опцию «Запустить программу»
  • путь к программе — C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe Аргумент запуска — C:\Scripts\RunScript.ps1
  • после создания, откройте свойство задания, перейдите на вкладку «Триггеры»
  • установите опцию «Повторить задачу каждые», указав нужную периодичность повторения
  • установите опции «Выполнять для всех пользователей» и «Выполнять с наивысшим приоритетом». Укажите пользователя, имеющего право на чтение всех файлов на удаленных компьютерах. Как правило это пользователи, входящие в группу администраторов на удаленных компьютерах.

Информация о результатах и рабочих файлах


Скрипт хранит результаты сканирования в каталоге %TEMP%. В примере этот каталог: C:\Users\Администратор\AppData\Local\Temp. Если компьютер доступен и файл найден – создается файл с результатом. Если компьютер не доступен – информация об это добавляется в файл offline.txt.



Удаление найденных файлов


Запустите PowerShell выполните команду:

Import-Module C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Start-AuditFiles.psm1
Remove-AuditFiles

Или

Remove-AuditFiles - TargetFile  ws-9281.txt,ws-8721.txt

Remove-AuditFiles — выполнит поиск всех результатов поиска, обработав каждый результат удалит файлы.

Вы можете указать конкретный файл (конкретный компьютер) Например:

Remove-AuditFiles — TargetFile ws-9281.txt,ws-8721.txt

Сброс перечня отсканированных компьютеров


Запустите PowerShell выполните команду:

Reset-AuditComputers

После выполнения, компьютеры в указанной OU будут отмечены как не отсканированы:

Import-Module C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Start-AuditFiles.psm1
Reset-AuditComputers - TargetOU OU "OU=Test,DC=root,DC=local"

Описание скрипта
Переменная $Body – содержит текст, который в дальнейшем будет вставлен в тело письма. В дальнейшем этот текст будет отправлен в письме конечному пользователю.

$Body =

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

Вам необходимо в кратчайшие сроки:

1) проверить указанные документы на предмет наличия служебной информации
2) переместить файлы содержащие служебную информацию на личный сетевой диск.

Начало функции, которой дано производное название Start-AuditFiles:

Function Start-AuditFiles {

Описание скрипта, сопроводительный текст справки. Позволяет использовать помощь, получить информацию о примерах и синтаксисе используя «Get-Help Start-AuditFiles»:

<#
.Synopsis
    Сканирует файлы на удаленной машине, в случае успеха отправляет отчет почтовым сообщением
.Description
    Сканер позволяет обнаружить искомые файлы на удаленной машине через административный ресурс (C$ D$ .. и т.д). 
    После выполнения скрипт: 
    1. Находит объекты комрьютер в опеределенной OU или заданый компьютер через параметр
    2. Проверяет доступность машины
    3. Выполняет поиск всех дисков
    4. Выполняет поиск искомых файлов с фильтрацией, формирует отчет
    5. Отправляет отчет администратору
    6. Получает активного пользователя, определяет его почтовый адрес, отправляет копию отчета
    7. Формирует списки результатов сканирования, опционально копирует результаты на удаленный ресурс
    
.Examples

    Пример 1
    Start-AuditFiles -OU "OU=Test,DC=root,DC=local" -SMTP smtp.server.com -AdminMail administrator@server.com -IncludeFile *.doc,*.docx,*.sys -ExclusionFile *File1*,*File2* -ExclusionFolder *Folder1*,*Folder2* -ReportPath \\server\reports
    В этом примере осуществляется поиск на компьютерах из OU, файлы все с расширением (*.doc,*.docx,*.sys) кроме файлов (*File1*,*File2*), кроме каталогов (*Folder1*,*Folder2*), отчет дублируется в каталог (\\server\reports\)

    Пример 2
    Start-AuditFiles -RemoteComputer ws-pc-4902,ws-pc-0982 -SMTP smtp.server.com -AdminMail administrator@server.com -Include *.doc,*.docx,*.sys -ExclusionFile *New*,*au* -AdminOnly

    В этом примере осуществляется поиск на компьютерах (ws-pc-4902,ws-pc-098), файлы все с расширением (*.doc,*.docx,*.sys) кроме файлов (*File1*,*File2*), отчет отсылается только администратору
    
.Notes
    Следует использовать только один из ключей OU или RemoteComputer, OU указывает организационную единицу, RemoteComputer указывает один или несколько компьютер в качестве объектов сканирования
.Link
    ...
#>


Описываем переменные, которые в дальнейшем будем использовать в функции:

$OU — путь к Organization Unit в Active Directory
$ExclusionFile –перечень файлов, исключённых из поиска
$ReportPath – путь к каталогу, куда будут дублироваться отчеты
$AdminMail – адрес электронной администратора, от которого отправляются отчеты и куда будет приходить копия, предназначенная администратору
$IncludeFile – расширения файлов или имена файлов, поиск которых осуществляется
$ExclusionFolder – перечень папок, исключенных из поиска
$AdminOnly – параметр отвечающий, будут ли отчеты отправляться только администратору
$SMTP – адрес или имя SMTP сервера
$Throttle – количество параллельных потоков.

[CmdletBinding()]
        Param (
        [String]$OU,
        [String[]]$RemoteComputer,
        [String[]]$ExclusionFile,
        [String]$ReportPath,  
        [String]$AdminMail,       
        [String[]]$IncludeFile,
        [String[]]$ExclusionFolder,
        [Switch]$AdminOnly = $false,      
        [String]$SMTP,
        [String]$Throttle = 5

        )


Выполняется проверка, если использовался ключ «RemoteComputer», в таком случае сканирование пройдет только на определенном (заданном) компьютере, если указан ключ «OU» — в этом случае список будет получен из определённой OU AD, будут выбраны компьютеры с атрибутом HomePage – не равным «pass» (это выполняется, дабы исключить повторный поиск на машинах, на которых уже был проведен поиск).

If (!$RemoteComputer) {$Hosts = (Get-ADComputer -Filter * -SearchBase $OU -Properties * | where { ( $PSItem.HomePage  -notlike  'pass' )}  ).name} else { $Hosts = $RemoteComputer }

Начало выполнения скрипта, подставляется значение Host и число потоков:

invoke-parallel -InputObject $Hosts -throttle $Throttle -ImportVariables  -ScriptBlock {

Проверка доступности компьютера в сети:

if(Test-Connection -ComputerName $_ -BufferSize 16 -quiet -count 2) {

Присваиваются значения переменных, необходимых для выполнения поиска взятые из переменных описанных выше. Запоминается начало выполнения поиска.

                $Object = $_
                $ErrorActionPreference = 'SilentlyContinue'
                $ExclusionFolder2 = $ExclusionFolder -replace ",","|"

        $StartTime = (Get-Date).ToString()
        $Hosts        


Выполнение поиска:

  • получение списка физических дисков (Get-WMIObject Win32_LogicalDisk -filter «DriveType = 3» -ComputerName $Object)
  • формируется UNC путь с проставлением буквы диска (Get-ChildItem ('\\' + $Object + '\' + ($_.DeviceID).remove(1) + '$\*')
  • указывается ключ поиска включенных объектов и исключения (-Include $IncludeFile -Exclude $ExclusionFile -Recurse -Force)
  • исключение для каталогов, убираем из результатов поиска не нужные каталоги (?{$PSItem.FullName -notmatch $ExclusionFolder2}}).FullName)
  • формируется отчет во временном каталоге (Out-File -FilePath $env:TEMP\$Object.txt -Encoding unicode)

(Get-WMIObject Win32_LogicalDisk -filter "DriveType = 3" -ComputerName $Object | %{Get-ChildItem ('\\' + $Object + '\' + ($_.DeviceID).remove(1) + '$\*') -Include $IncludeFile -Exclude $ExclusionFile -Recurse -Force | ?{$PSItem.FullName -notmatch $ExclusionFolder2}}).FullName | Out-File -FilePath $env:TEMP\$Object.txt -Encoding unicode 


Если в момент запуска скрипта, был объявлен параметр «ReportPath», отчет копируется в заданный каталог.
        If (!$ReportPath) {} else {Copy-Item -Path $env:TEMP\$Object.txt -Destination $ReportPath -Force}

Запоминаем время завершения поиска.

    $EndTime = (Get-Date).ToString()

Добавляется запись в лог файл, с успешно завершенными операциями.

Write-Output ($Object) | Add-Content $env:TEMP\Online.txt

Вывод на экран таблицы с результатом поиска, начало поиска и его время его окончание, объект на котором производился поиск.

        Invoke-Item $env:TEMP\$Object.txt
        $Results = "" | Select ComputerName, "StartTime", "EndTime"
        $Results.ComputerName = $Object 
        $Results.StartTime = $StartTime
        $Results.EndTime =$EndTime 
        $Results


Открывает содержимое отчета, если он не пустой, выполняет действие (отправка отчета), если пустой ничего не выполняет. Предотвращает отправку пустых отчетов.

If ((Get-Content $env:TEMP\$Object.txt) -eq $Null) {}

Если файл отчета не оказался пустой, формируется сообщение с заданными параметрами SMTP сервера, адресом администратора, тестом сообщения, вложением отчета.

    else {
        Try {
                Send-MailMessage -SmtpServer $SMTP -to $AdminMail -Body $Object -From denis.pasternak@hotmail.com -Subject $Object -Attachments $env:TEMP\$Object.txt
            } Catch {''} 


Сообщает на экране о том, что включён решим отправки только администратору, если был указан соответствующий ключ.

If ($AdminOnly -eq $True) { Write-Host "Включен параметр AdminOnly - отчет отправлен только аминистратору" -ForegroundColor Yellow} else 

Получаем имя пользователя активного на удаленном компьютере.

{       
            $Username=((gwmi win32_computersystem -computer $Object -ErrorAction SilentlyContinue).UserName -split '\\')[1]
            if($username -ne $null)



Если значение пользователя не пустое, ищем пользователя в Active Directory, определяем его значение почтовый адрес из моля Email. Получаем так же ФИО из Active Directory.

            {
            $Body = $Body
            $dispalyname = (Get-AdUser $username -properties DisplayName).DisplayName 
            $email = (Get-AdUser $username -properties mail).mail
            sleep -Seconds 3


Отправляем сообщение пользователю, с телом письма указанным в переменной $Body с подстановкой ФИО взятого из Active Directory.

Send-MailMessage -SmtpServer  $SMTP -Body  ( 'Уважаемый ' + $Dispalyname + ' ' + $Body | out-string ) -To $email -From $AdminMail -Subject  $Object -Attachments $env:TEMP\$Object.txt -Encoding Unicode


Если компьютер не был доступен, записываем результат в лог файл, о том что он был не доступен.

            }
        }
    }

else{ }        
        
       
        }
         else {
        (Write-Output ($Object + ' ' + (Get-Date).ToString()) | Add-Content $env:TEMP\Offline.txt)}
        }


Сбрасываем переменные.

$OU= $null
 $RemoteComputer = $null
 $Hosts = $nul


Устанавливаем атрибут «HomePage» для объектов компьютеры в Active Directory. Что поможет нам не проводить повторный поиск на этих компьютерах, пока эти значения не будут сброшены.

Get-Content $env:TEMP\Online.txt | Set-ADComputer -HomePage 'pass'
 }


Функция позволяет, открыть файл, получить содержимое (путь к файлу на удаленном компьютере), по каждой строке удалить файл на удаленном компьютере. $TargetFile – задает путь к файлу в котором хранится список файлов на удаление.

Function Remove-AuditFiles {
[CmdletBinding(SupportsShouldProcess=$True)]
        Param (
        [String]$TargetFile
        
        )

           Get-Content -Path "$env:TEMP\$Path" | %{Remove-Item $PSItem}

}


Функция позволяет сбросить атрибут HomePage объектов в Active Directory. Берет список компьютеров из указанной OU. Удаляет список в лог-файле.

Function Reset-AuditComputers {


[CmdletBinding(SupportsShouldProcess=$True)]
        Param (
        [String]$TargetOU
        
        )

 Get-ADComputer -Filter * -SearchBase $TargetOU -Properties * | Set-ADComputer -HomePage 'notpass'
'' | Set-Content -Path $env:TEMP\Online.txt 


} 


Комментарии (6)


  1. grieverrr
    13.01.2016 17:10
    +1

    То есть эта DLP-система ищет файлы с определенным расширением на компах в локалке?


  1. gotch
    13.01.2016 18:48
    +2

    Вы уверены, что написали именно Data Leak Prevention System?


    1. system-admins
      13.01.2016 20:12

      В статье есть уточнение

      реализуем одну из функций DLP-систем


  1. foxmuldercp
    13.01.2016 23:31

    Ну, то есть, штатную фичу 2012+, которая была анонсировала 3 года назад, по поиску данных по шаблонам, и классический пример которой — поиск серий и номеров паспортов и динамические ACL, автор не смотрел? Она же Dynamic access control, DAC.
    например, по вот этой статье habrahabr.ru/post/176467


    1. system-admins
      14.01.2016 09:39

      Этот скрипт работает не только на 2012r2, и скорее служит для поиска на клиентских ПК, ведь пользователи, как правило, хранят все свои файлы в папке Мои документы, или на рабочем столе.


      1. foxmuldercp
        16.01.2016 02:18

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