DLP система своими руками

от автора

Помимо основной задачи предотвращения утечек конфиденциальной информации у 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    }   

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