Задача клиента: один администратор, 200 сотрудников и рутина
К нам обратилась страховая компания из Екатеринбурга с типичной для среднего бизнеса проблемой: единственный IT-администратор тонул в рутинных задачах. 200 сотрудников, 8 серверов, Active Directory, файловые шары, почтовый сервер — и на всё это один человек, который каждое утро вручную проверял диски, просматривал журналы событий, разблокировал учётки и генерировал отчёты для руководства.
По нашей оценке, на рутинные проверки и обслуживание администратор тратил до 3 часов в день — 15 часов в неделю, которые можно было направить на развитие инфраструктуры. Мы предложили комплексное решение: набор из 20 PowerShell-скриптов, покрывающих все ежедневные задачи, с автоматическим запуском по расписанию и email-уведомлениями.
Каждый скрипт мы протестировали в инфраструктуре клиента, снабдили подробными комментариями и настроили для автономной работы. Все скрипты совместимы с PowerShell 5.1 (Windows Server 2019 клиента) и PowerShell 7.
Подготовка: настройка рабочего окружения
Первым делом наши инженеры подготовили среду выполнения скриптов на серверах клиента.
Что мы настроили перед внедрением
На основном сервере управления мы выполнили подготовку:
# Проверка версии PowerShell
$PSVersionTable.PSVersion
# Настройка политики выполнения скриптов
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Установка необходимых модулей
Install-Module -Name ActiveDirectory -Force
Install-Module -Name ImportExcel -Force # Для экспорта в Excel без Office
Install-Module -Name PSWriteHTML -Force # Для HTML-отчётов
# Создание структуры каталогов для скриптов
New-Item -ItemType Directory -Path C:\Scripts\Reports -Force
New-Item -ItemType Directory -Path C:\Scripts\Logs -Force
New-Item -ItemType Directory -Path C:\Scripts\Backup -Force
Все скрипты мы разместили в C:\Scripts и настроили их запуск через Task Scheduler.
Принципы, которых мы придерживались
При разработке скриптов для клиента мы следовали правилам, которые делают скрипты пригодными для продуктивной среды:
- Обработка ошибок: везде используем
try/catch и $ErrorActionPreference = 'Stop' - Логирование: каждый скрипт записывает действия в лог-файл с датой и временем
- Параметризация: все переменные (серверы, пути, email) вынесены в начало скрипта
- Идемпотентность: повторный запуск скрипта не создаёт проблем
- Комментарии: каждый скрипт содержит описание, автора и дату — для IT-специалиста клиента
Блок 1: скрипты мониторинга дисков и ресурсов
Мониторинг дискового пространства и системных ресурсов — первое, что мы автоматизировали. Нехватка места на диске — причина номер один неожиданных сбоев, и у страховой компании такое случалось дважды за последний квартал.
Скрипт 1: Мониторинг свободного места на дисках
Этот скрипт мы настроили на запуск каждый час — он проверяет все 8 серверов клиента и отправляет алерт при заполнении диска:
# Скрипт 1: Мониторинг дискового пространства
# Разработан инженерами АйТи Фреш для страховой компании
param(
[int]$ThresholdPercent = 15,
[string]$SmtpServer = 'mail.company.ru',
[string]$AdminEmail = 'admin@company.ru'
)
$servers = @('SRV-DC01', 'SRV-FILE01', 'SRV-SQL01', 'SRV-APP01')
$alerts = @()
foreach ($server in $servers) {
try {
$disks = Get-WmiObject -Class Win32_LogicalDisk `
-ComputerName $server -Filter "DriveType=3" -ErrorAction Stop
foreach ($disk in $disks) {
$freePercent = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 1)
$freeGB = [math]::Round($disk.FreeSpace / 1GB, 1)
$totalGB = [math]::Round($disk.Size / 1GB, 1)
if ($freePercent -lt $ThresholdPercent) {
$alerts += [PSCustomObject]@{
Server = $server
Drive = $disk.DeviceID
TotalGB = $totalGB
FreeGB = $freeGB
FreePercent = $freePercent
Status = if ($freePercent -lt 5) { 'CRITICAL' } else { 'WARNING' }
}
}
}
} catch {
$alerts += [PSCustomObject]@{
Server = $server; Drive = 'N/A'; TotalGB = 0
FreeGB = 0; FreePercent = 0; Status = "UNREACHABLE: $_"
}
}
}
if ($alerts.Count -gt 0) {
$html = $alerts | ConvertTo-Html -Title 'Disk Space Alert' `
-PreContent "<h2>Disk Space Alert — $(Get-Date)</h2>" | Out-String
Send-MailMessage -From 'monitor@company.ru' -To $AdminEmail `
-Subject "[DISK ALERT] $($alerts.Count) warnings" `
-Body $html -BodyAsHtml -SmtpServer $SmtpServer -Encoding UTF8
}
Скрипт 2: Мониторинг CPU и RAM в реальном времени
Скрипт, который мы настроили для записи истории нагрузки серверов — позволяет находить закономерности и планировать апгрейды:
# Скрипт 2: Мониторинг CPU и RAM на серверах клиента
$servers = @('SRV-DC01', 'SRV-FILE01', 'SRV-SQL01')
$logFile = "C:\Scripts\Logs\ResourceUsage_$(Get-Date -Format 'yyyyMMdd').csv"
$results = @()
foreach ($server in $servers) {
try {
$cpu = Get-WmiObject -Class Win32_Processor -ComputerName $server |
Measure-Object -Property LoadPercentage -Average |
Select-Object -ExpandProperty Average
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $server
$totalRAM = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1)
$freeRAM = [math]::Round($os.FreePhysicalMemory / 1MB, 1)
$usedRAMPercent = [math]::Round((1 - $os.FreePhysicalMemory / $os.TotalVisibleMemorySize) * 100, 1)
$results += [PSCustomObject]@{
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
Server = $server
CPU_Percent = $cpu
RAM_TotalGB = $totalRAM
RAM_FreeGB = $freeRAM
RAM_UsedPct = $usedRAMPercent
}
} catch {
Write-Warning "Не удалось подключиться к $server : $_"
}
}
$results | Format-Table -AutoSize
$results | Export-Csv -Path $logFile -Append -NoTypeInformation -Encoding UTF8
Скрипт 3: Топ процессов по потреблению памяти
Администратор клиента регулярно жаловался на «медленные серверы». Этот скрипт мы написали для быстрой диагностики утечек памяти:
# Скрипт 3: Топ-10 процессов по потреблению RAM
param(
[string]$ComputerName = $env:COMPUTERNAME,
[int]$TopN = 10
)
$processes = Get-Process -ComputerName $ComputerName |
Sort-Object -Property WorkingSet64 -Descending |
Select-Object -First $TopN -Property @(
'Name',
'Id',
@{Name='RAM_MB'; Expression={[math]::Round($_.WorkingSet64 / 1MB, 0)}},
@{Name='CPU_Sec'; Expression={[math]::Round($_.CPU, 1)}},
@{Name='Handles'; Expression={$_.HandleCount}},
@{Name='Threads'; Expression={$_.Threads.Count}},
@{Name='StartTime'; Expression={$_.StartTime}}
)
$processes | Format-Table -AutoSize
$html = $processes | ConvertTo-Html -Title "Top $TopN Processes — $ComputerName" `
-PreContent "<h2>Top $TopN процессов по RAM на $ComputerName</h2><p>$(Get-Date)</p>"
$html | Out-File -FilePath "C:\Scripts\Reports\TopProcesses_$ComputerName.html"
Скрипт 4: Мониторинг сетевых подключений
После инцидента с подозрительными соединениями с одного из ПК мы добавили скрипт анализа сетевой активности:
# Скрипт 4: Анализ сетевых подключений
Get-NetTCPConnection -State Established, TimeWait, CloseWait |
Group-Object -Property State |
Select-Object Count, Name |
Sort-Object Count -Descending |
Format-Table -AutoSize
Write-Host "`n--- Топ-10 удалённых адресов по количеству соединений ---" -ForegroundColor Cyan
Get-NetTCPConnection -State Established |
Group-Object -Property RemoteAddress |
Sort-Object Count -Descending |
Select-Object -First 10 Count, Name |
Format-Table -AutoSize
Write-Host "`n--- Прослушиваемые порты ---" -ForegroundColor Cyan
Get-NetTCPConnection -State Listen |
Select-Object LocalPort, @{
Name='Process'; Expression={
(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName
}
} | Sort-Object LocalPort | Format-Table -AutoSize
Блок 2: скрипты Active Directory
Active Directory в страховой компании — центральный компонент инфраструктуры. 200 пользователей, частая ротация кадров, строгие требования регулятора к безопасности. Мы автоматизировали отчёты и аудит AD, что сэкономило клиенту десятки часов ежемесячно.
Скрипт 5: Отчёт по неактивным пользователям AD
В страховой компании текучка кадров выше среднего — забытые учётки представляли риск безопасности. Мы внедрили еженедельную проверку:
# Скрипт 5: Поиск неактивных пользователей AD
param(
[int]$InactiveDays = 90,
[string]$ExportPath = 'C:\Scripts\Reports\InactiveUsers.csv'
)
Import-Module ActiveDirectory
$threshold = (Get-Date).AddDays(-$InactiveDays)
$inactiveUsers = Get-ADUser -Filter {
LastLogonDate -lt $threshold -and Enabled -eq $true
} -Properties LastLogonDate, Created, Department, Title, Manager,
PasswordLastSet, PasswordNeverExpires -SearchBase 'DC=company,DC=ru' |
Select-Object @(
'SamAccountName',
'Name',
'Department',
'Title',
@{Name='Manager'; Expression={
if ($_.Manager) { (Get-ADUser $_.Manager).Name } else { 'Не назначен' }
}},
'LastLogonDate',
'PasswordLastSet',
'PasswordNeverExpires',
'Created',
@{Name='InactiveDays'; Expression={
if ($_.LastLogonDate) {
(New-TimeSpan -Start $_.LastLogonDate -End (Get-Date)).Days
} else { 'Никогда не входил' }
}}
) | Sort-Object LastLogonDate
Write-Host "Найдено неактивных пользователей: $($inactiveUsers.Count)" -ForegroundColor Yellow
$inactiveUsers | Export-Csv -Path $ExportPath -NoTypeInformation -Encoding UTF8
Скрипт 6: Аудит членства в привилегированных группах
Требование регулятора — контроль доступа привилегированных учёток. Мы настроили ежедневный аудит:
# Скрипт 6: Аудит привилегированных групп AD
$privilegedGroups = @(
'Domain Admins',
'Enterprise Admins',
'Schema Admins',
'Account Operators',
'Server Operators',
'Backup Operators',
'Administrators'
)
$report = @()
foreach ($group in $privilegedGroups) {
try {
$members = Get-ADGroupMember -Identity $group -Recursive -ErrorAction Stop
foreach ($member in $members) {
$user = Get-ADUser -Identity $member.SamAccountName `
-Properties LastLogonDate, Enabled, PasswordLastSet
$report += [PSCustomObject]@{
Group = $group
UserName = $user.SamAccountName
DisplayName = $user.Name
Enabled = $user.Enabled
LastLogon = $user.LastLogonDate
PasswordLastSet = $user.PasswordLastSet
}
}
} catch {
Write-Warning "Ошибка при обработке группы $group : $_"
}
}
$report | Sort-Object Group, UserName | Format-Table -AutoSize
$report | Export-Csv 'C:\Scripts\Reports\PrivilegedGroups.csv' `
-NoTypeInformation -Encoding UTF8
Write-Host "`n--- Сводка ---" -ForegroundColor Green
$report | Group-Object Group | Select-Object Count, Name | Format-Table
Скрипт 7: Массовое создание пользователей из CSV
При открытии нового филиала клиенту потребовалось создать 30 учёток за день. Мы подготовили скрипт массового создания с транслитерацией:
# Скрипт 7: Массовое создание пользователей AD из CSV
param(
[string]$CsvPath = 'C:\Scripts\Data\NewUsers.csv',
[string]$OUPath = 'OU=Users,OU=Company,DC=company,DC=ru',
[string]$Domain = 'company.ru'
)
Import-Module ActiveDirectory
$users = Import-Csv -Path $CsvPath -Encoding UTF8
$created = 0; $errors = 0
foreach ($user in $users) {
$login = ($user.FirstName.Substring(0,1) + $user.LastName).ToLower()
$translitMap = @{
'а'='a';'б'='b';'в'='v';'г'='g';'д'='d';'е'='e';'ё'='yo';
'ж'='zh';'з'='z';'и'='i';'й'='y';'к'='k';'л'='l';'м'='m';
'н'='n';'о'='o';'п'='p';'р'='r';'с'='s';'т'='t';'у'='u';
'ф'='f';'х'='kh';'ц'='ts';'ч'='ch';'ш'='sh';'щ'='shch';
'ы'='y';'э'='e';'ю'='yu';'я'='ya';'ъ'='';'ь'=''
}
$loginTranslit = ''
foreach ($char in $login.ToCharArray()) {
$loginTranslit += if ($translitMap.ContainsKey([string]$char)) {
$translitMap[[string]$char]
} else { $char }
}
try {
New-ADUser -SamAccountName $loginTranslit `
-UserPrincipalName "$loginTranslit@$Domain" `
-Name "$($user.LastName) $($user.FirstName)" `
-GivenName $user.FirstName `
-Surname $user.LastName `
-Department $user.Department `
-Title $user.Title `
-Path $OUPath `
-AccountPassword (ConvertTo-SecureString $user.Password -AsPlainText -Force) `
-Enabled $true `
-ChangePasswordAtLogon $true `
-ErrorAction Stop
Write-Host "[OK] Создан: $loginTranslit" -ForegroundColor Green
$created++
} catch {
Write-Warning "[FAIL] $loginTranslit : $_"
$errors++
}
}
Write-Host "`nИтого: создано $created, ошибок $errors" -ForegroundColor Cyan
Скрипты 8-9: Поиск заблокированных учёток и контроль паролей
В страховой компании блокировки учёток происходили ежедневно — сотрудники забывали пароли после отпуска. Мы автоматизировали поиск и уведомления:
# Скрипт 8: Поиск заблокированных учётных записей и источника блокировки
Import-Module ActiveDirectory
$locked = Search-ADAccount -LockedOut | Get-ADUser -Properties LockedOut,
LastBadPasswordAttempt, BadLogonCount, LockoutTime, Department
$locked | Select-Object Name, SamAccountName, Department,
BadLogonCount, LastBadPasswordAttempt,
@{Name='LockoutTime'; Expression={
[DateTime]::FromFileTime($_.LockoutTime)
}} | Format-Table -AutoSize
$pdc = (Get-ADDomainController -Discover -Service PrimaryDC).HostName[0]
foreach ($user in $locked) {
Write-Host "`nИсточник блокировки для $($user.SamAccountName):" -ForegroundColor Yellow
Get-WinEvent -ComputerName $pdc -FilterHashtable @{
LogName = 'Security'
Id = 4740
} -MaxEvents 5 | Where-Object {
$_.Properties[0].Value -eq $user.SamAccountName
} | Select-Object TimeCreated,
@{Name='Source'; Expression={$_.Properties[1].Value}} |
Format-Table -AutoSize
}
# Скрипт 9: Отчёт об истекающих паролях
# Предупреждаем сотрудников заранее — меньше звонков в техподдержку
param([int]$DaysAhead = 14)
$maxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
$warningDate = (Get-Date).AddDays($DaysAhead)
$expiringUsers = Get-ADUser -Filter {
Enabled -eq $true -and PasswordNeverExpires -eq $false
} -Properties PasswordLastSet, Mail, Department |
Where-Object {
$_.PasswordLastSet -and
($_.PasswordLastSet.AddDays($maxPasswordAge)) -lt $warningDate -and
($_.PasswordLastSet.AddDays($maxPasswordAge)) -gt (Get-Date)
} | Select-Object Name, SamAccountName, Mail, Department,
@{Name='PasswordExpires'; Expression={
$_.PasswordLastSet.AddDays($maxPasswordAge)
}},
@{Name='DaysLeft'; Expression={
($_.PasswordLastSet.AddDays($maxPasswordAge) - (Get-Date)).Days
}} | Sort-Object DaysLeft
$expiringUsers | Format-Table -AutoSize
Write-Host "Пользователей с истекающим паролем (< $DaysAhead дней): $($expiringUsers.Count)"
Блок 3: скрипты управления службами и журналами
Управление службами Windows и анализ журналов событий — ежедневные задачи, на которые администратор клиента тратил по часу каждое утро. Мы автоматизировали всё.
Скрипт 10: Автоматический перезапуск критичных служб
Этот скрипт мы настроили на запуск каждые 15 минут — он контролирует ключевые службы и перезапускает их при остановке:
# Скрипт 10: Мониторинг и автоперезапуск критичных служб
$criticalServices = @(
@{Name='Spooler'; DisplayName='Служба печати'},
@{Name='MSSQLSERVER'; DisplayName='SQL Server'},
@{Name='W3SVC'; DisplayName='IIS Web Server'},
@{Name='WinRM'; DisplayName='Windows Remote Management'},
@{Name='DNS'; DisplayName='DNS Server'}
)
$logFile = "C:\Scripts\Logs\ServiceRestart_$(Get-Date -Format 'yyyyMMdd').log"
function Write-Log($message) {
$entry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | $message"
Add-Content -Path $logFile -Value $entry
Write-Host $entry
}
foreach ($svc in $criticalServices) {
$service = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
if (-not $service) {
Write-Log "[SKIP] $($svc.DisplayName) — служба не найдена"
continue
}
if ($service.Status -ne 'Running') {
Write-Log "[WARN] $($svc.DisplayName) не запущена (статус: $($service.Status))"
try {
Start-Service -Name $svc.Name -ErrorAction Stop
Start-Sleep -Seconds 5
$service.Refresh()
if ($service.Status -eq 'Running') {
Write-Log "[OK] $($svc.DisplayName) успешно перезапущена"
} else {
Write-Log "[FAIL] $($svc.DisplayName) не удалось запустить"
}
} catch {
Write-Log "[ERROR] $($svc.DisplayName): $_"
}
} else {
Write-Log "[OK] $($svc.DisplayName) работает нормально"
}
}
Скрипт 11: Анализ журналов событий Windows
Вместо ручного просмотра Event Viewer мы настроили автоматический сбор критичных событий:
# Скрипт 11: Анализ критичных событий за последние 24 часа
param(
[int]$HoursBack = 24,
[string[]]$LogNames = @('System', 'Application'),
[string]$ExportPath = 'C:\Scripts\Reports'
)
$startTime = (Get-Date).AddHours(-$HoursBack)
$allEvents = @()
foreach ($logName in $LogNames) {
Write-Host "Анализ журнала: $logName" -ForegroundColor Cyan
$events = Get-WinEvent -FilterHashtable @{
LogName = $logName
Level = 1,2,3
StartTime = $startTime
} -ErrorAction SilentlyContinue
if ($events) {
$allEvents += $events | Select-Object @(
'TimeCreated',
@{Name='Log'; Expression={$logName}},
@{Name='Level'; Expression={
switch ($_.Level) {
1 {'CRITICAL'}
2 {'ERROR'}
3 {'WARNING'}
}
}},
'Id',
'ProviderName',
'Message'
)
}
}
Write-Host "`n--- Топ-10 источников ошибок ---" -ForegroundColor Yellow
$allEvents | Group-Object ProviderName |
Sort-Object Count -Descending |
Select-Object -First 10 Count, Name |
Format-Table -AutoSize
$reportFile = "$ExportPath\EventLog_$(Get-Date -Format 'yyyyMMdd_HHmm').csv"
$allEvents | Export-Csv -Path $reportFile -NoTypeInformation -Encoding UTF8
Write-Host "`nВсего событий: $($allEvents.Count). Отчёт: $reportFile"
Скрипты 12-13: Аудит запланированных задач и автозагрузки
После обнаружения несанкционированной задачи в планировщике на одном из ПК мы добавили скрипты аудита:
# Скрипт 12: Аудит запланированных задач на сервере
$tasks = Get-ScheduledTask | Where-Object {
$_.State -ne 'Disabled' -and $_.TaskPath -notlike '\Microsoft\*'
} | ForEach-Object {
$info = $_ | Get-ScheduledTaskInfo -ErrorAction SilentlyContinue
[PSCustomObject]@{
TaskName = $_.TaskName
TaskPath = $_.TaskPath
State = $_.State
RunAs = $_.Principal.UserId
LastRunTime = $info.LastRunTime
LastResult = $info.LastTaskResult
NextRunTime = $info.NextRunTime
Actions = ($_.Actions | ForEach-Object {
"$($_.Execute) $($_.Arguments)"
}) -join '; '
}
}
$tasks | Sort-Object TaskPath | Format-Table -AutoSize -Wrap
$failed = $tasks | Where-Object { $_.LastResult -ne 0 -and $_.LastResult -ne $null }
if ($failed) {
Write-Host "`n[!] Задачи с ошибками:" -ForegroundColor Red
$failed | Format-Table TaskName, LastRunTime, LastResult -AutoSize
}
# Скрипт 13: Аудит программ в автозагрузке
$regPaths = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run'
)
$autostart = @()
foreach ($path in $regPaths) {
if (Test-Path $path) {
$items = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue
$items.PSObject.Properties | Where-Object {
$_.Name -notin @('PSPath','PSParentPath','PSChildName','PSProvider','PSDrive')
} | ForEach-Object {
$autostart += [PSCustomObject]@{
RegistryPath = $path
Name = $_.Name
Command = $_.Value
}
}
}
}
$autostart | Format-Table -AutoSize -Wrap
Write-Host "Всего записей автозагрузки: $($autostart.Count)"
Блок 4: скрипты резервного копирования
Резервное копирование в страховой компании — требование регулятора. Мы автоматизировали бэкапы файлов, сетевого оборудования и очистку старых данных.
Скрипт 14: Инкрементальный бэкап с ротацией
Для файлового сервера клиента мы настроили инкрементальные бэкапы с автоматической ротацией:
# Скрипт 14: Инкрементальный бэкап каталогов с ротацией
param(
[string]$Source = 'D:\SharedData',
[string]$Destination = 'E:\Backups\SharedData',
[int]$RetainDays = 30
)
$timestamp = Get-Date -Format 'yyyyMMdd_HHmm'
$backupDir = Join-Path $Destination "Backup_$timestamp"
$logFile = Join-Path $Destination "backup_log.txt"
$lastBackup = Get-ChildItem -Path $Destination -Directory |
Sort-Object CreationTime -Descending | Select-Object -First 1
$sinceDate = if ($lastBackup) { $lastBackup.CreationTime } else { [DateTime]::MinValue }
Write-Host "Бэкап: $Source -> $backupDir"
Write-Host "Файлы изменённые после: $sinceDate"
$files = Get-ChildItem -Path $Source -Recurse -File |
Where-Object { $_.LastWriteTime -gt $sinceDate }
$copied = 0
foreach ($file in $files) {
$relativePath = $file.FullName.Substring($Source.Length)
$destFile = Join-Path $backupDir $relativePath
$destDir = Split-Path $destFile -Parent
if (-not (Test-Path $destDir)) {
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
}
Copy-Item -Path $file.FullName -Destination $destFile -Force
$copied++
}
$summary = "$(Get-Date) | Скопировано файлов: $copied | Размер: $(
[math]::Round(($files | Measure-Object Length -Sum).Sum / 1MB, 1)
) МБ"
Add-Content -Path $logFile -Value $summary
Write-Host $summary -ForegroundColor Green
$old = Get-ChildItem -Path $Destination -Directory |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$RetainDays) }
if ($old) {
$old | Remove-Item -Recurse -Force
Write-Host "Удалено старых бэкапов: $($old.Count)" -ForegroundColor Yellow
}
Скрипт 15: Бэкап конфигурации сетевого оборудования
У клиента были Cisco-коммутаторы и FortiGate-файрвол. Мы настроили автоматический бэкап конфигурации по SSH:
# Скрипт 15: Бэкап конфигурации сетевого оборудования по SSH
param(
[string]$BackupDir = 'C:\Scripts\Backup\NetworkConfig'
)
Import-Module Posh-SSH
$devices = @(
@{Name='SW-CORE-01'; IP='10.0.0.1'; Type='Cisco'; Cmd='show running-config'},
@{Name='SW-ACCESS-01'; IP='10.0.0.2'; Type='Cisco'; Cmd='show running-config'},
@{Name='FW-01'; IP='10.0.0.254'; Type='FortiGate'; Cmd='show full-configuration'}
)
$credential = Get-Credential -Message 'Учётные данные для сетевого оборудования'
$date = Get-Date -Format 'yyyyMMdd'
foreach ($device in $devices) {
try {
$session = New-SSHSession -ComputerName $device.IP `
-Credential $credential -AcceptKey -ConnectionTimeout 10
$stream = New-SSHShellStream -SessionId $session.SessionId
Start-Sleep -Seconds 2
if ($device.Type -eq 'Cisco') {
$stream.WriteLine('terminal length 0')
Start-Sleep -Seconds 1
}
$stream.WriteLine($device.Cmd)
Start-Sleep -Seconds 5
$config = $stream.Read()
$filePath = "$BackupDir\$($device.Name)_$date.txt"
$config | Out-File -FilePath $filePath -Encoding UTF8
Write-Host "[OK] $($device.Name) — сохранено в $filePath" -ForegroundColor Green
Remove-SSHSession -SessionId $session.SessionId | Out-Null
} catch {
Write-Warning "[FAIL] $($device.Name): $_"
}
}
Скрипт 16: Очистка временных файлов и старых логов
На файловом сервере клиента скопилось 40 ГБ временных файлов. Мы внедрили автоматическую очистку:
# Скрипт 16: Очистка временных файлов и устаревших данных
param([int]$RetainDays = 30)
$pathsToClean = @(
@{Path='C:\Windows\Temp'; Pattern='*'},
@{Path='C:\Windows\Logs'; Pattern='*.log'},
@{Path='C:\inetpub\logs\LogFiles'; Pattern='*.log'},
@{Path="$env:TEMP"; Pattern='*'},
@{Path='C:\Windows\SoftwareDistribution\Download'; Pattern='*'}
)
$totalFreed = 0
foreach ($item in $pathsToClean) {
if (-not (Test-Path $item.Path)) { continue }
$oldFiles = Get-ChildItem -Path $item.Path -Filter $item.Pattern `
-Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$RetainDays) }
if ($oldFiles) {
$size = ($oldFiles | Measure-Object Length -Sum).Sum
$totalFreed += $size
$oldFiles | Remove-Item -Force -ErrorAction SilentlyContinue
Write-Host "$($item.Path): удалено $($oldFiles.Count) файлов " +
"($([math]::Round($size / 1MB, 1)) МБ)" -ForegroundColor Green
}
}
Clear-RecycleBin -Force -ErrorAction SilentlyContinue
Write-Host "`nВсего освобождено: $([math]::Round($totalFreed / 1MB, 1)) МБ" -ForegroundColor Cyan
Блок 5: скрипты безопасности и сертификатов
Страховая компания обрабатывает персональные данные клиентов, поэтому мониторинг безопасности — не опция, а обязательное требование. Мы внедрили автоматический контроль сертификатов и аудит попыток доступа.
Скрипт 17: Проверка сроков SSL-сертификатов
После инцидента, когда истёкший сертификат на портале клиентов привёл к 4 часам простоя, мы настроили упреждающий мониторинг:
# Скрипт 17: Мониторинг сроков действия SSL/TLS сертификатов
param(
[int]$WarningDays = 30,
[string]$SmtpServer = 'mail.company.ru',
[string]$AdminEmail = 'admin@company.ru'
)
Write-Host "--- Локальные сертификаты ---" -ForegroundColor Cyan
$localCerts = Get-ChildItem -Path Cert:\LocalMachine\My -Recurse |
Where-Object { $_.NotAfter -lt (Get-Date).AddDays($WarningDays) -and
$_.NotAfter -gt (Get-Date) } |
Select-Object Subject, Thumbprint, NotAfter,
@{Name='DaysLeft'; Expression={($_.NotAfter - (Get-Date)).Days}}
$localCerts | Format-Table -AutoSize
$webHosts = @('portal.company.ru', 'mail.company.ru', 'vpn.company.ru')
$remoteCerts = @()
foreach ($host_ in $webHosts) {
try {
$tcpClient = New-Object System.Net.Sockets.TcpClient($host_, 443)
$sslStream = New-Object System.Net.Security.SslStream(
$tcpClient.GetStream(), $false,
{ param($s,$c,$ch,$e) $true }
)
$sslStream.AuthenticateAsClient($host_)
$cert = $sslStream.RemoteCertificate
$x509 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($cert)
$remoteCerts += [PSCustomObject]@{
Host = $host_
Subject = $x509.Subject
Issuer = $x509.Issuer
NotAfter = $x509.NotAfter
DaysLeft = ($x509.NotAfter - (Get-Date)).Days
}
$sslStream.Close()
$tcpClient.Close()
} catch {
Write-Warning "Не удалось проверить $host_ : $_"
}
}
Write-Host "`n--- Удалённые сертификаты ---" -ForegroundColor Cyan
$remoteCerts | Format-Table -AutoSize
$expiring = @($localCerts) + @($remoteCerts | Where-Object { $_.DaysLeft -lt $WarningDays })
if ($expiring.Count -gt 0) {
$body = $expiring | ConvertTo-Html -Title 'Certificate Expiry Alert' | Out-String
Send-MailMessage -From 'certs@company.ru' -To $AdminEmail `
-Subject "[CERT ALERT] $($expiring.Count) certificates expiring" `
-Body $body -BodyAsHtml -SmtpServer $SmtpServer
}
Скрипт 18: Аудит безопасности — попытки несанкционированного доступа
Для выполнения требований регулятора мы настроили ежедневный аудит неудачных попыток входа:
# Скрипт 18: Аудит неудачных попыток входа
param([int]$HoursBack = 24)
$startTime = (Get-Date).AddHours(-$HoursBack)
$failedLogins = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = $startTime
} -ErrorAction SilentlyContinue | ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeCreated
Account = $_.Properties[5].Value
Domain = $_.Properties[6].Value
SourceIP = $_.Properties[19].Value
LogonType = switch ($_.Properties[10].Value) {
2 {'Interactive (console)'}
3 {'Network (SMB/mapped)'}
7 {'Unlock'}
8 {'NetworkCleartext'}
10 {'RemoteDesktop (RDP)'}
default {$_.Properties[10].Value}
}
FailReason = switch ($_.Properties[7].Value) {
'0xC000006D' {'Bad username or password'}
'0xC000006A' {'Wrong password'}
'0xC0000234' {'Account locked'}
'0xC0000072' {'Account disabled'}
'0xC000015B' {'Logon type not allowed'}
default {$_.Properties[7].Value}
}
}
}
Write-Host "Неудачных попыток входа за $HoursBack ч: $($failedLogins.Count)" `
-ForegroundColor Yellow
Write-Host "`n--- Топ IP-адресов с неудачными попытками ---" -ForegroundColor Red
$failedLogins | Group-Object SourceIP |
Sort-Object Count -Descending |
Select-Object -First 10 Count, Name |
Format-Table -AutoSize
Write-Host "--- Топ атакуемых учётных записей ---" -ForegroundColor Red
$failedLogins | Group-Object Account |
Sort-Object Count -Descending |
Select-Object -First 10 Count, Name |
Format-Table -AutoSize
$failedLogins | Export-Csv 'C:\Scripts\Reports\FailedLogins.csv' `
-NoTypeInformation -Encoding UTF8
Блок 6: сетевая диагностика и ежедневный отчёт
Завершающие скрипты — для сетевой диагностики и автоматического ежедневного отчёта руководству. Именно ежедневный отчёт стал визитной карточкой проекта — директор по ИТ каждое утро получает красивый HTML-дайджест о состоянии инфраструктуры.
Скрипт 19: Комплексная сетевая диагностика
Администратор клиента использует этот скрипт для быстрой диагностики при жалобах пользователей на «не работает интернет»:
# Скрипт 19: Комплексная сетевая диагностика хоста
param(
[string]$TargetHost = '8.8.8.8',
[string]$DNSName = 'google.com'
)
Write-Host "=== Сетевая диагностика ===" -ForegroundColor Cyan
Write-Host "Цель: $TargetHost / $DNSName" -ForegroundColor Cyan
Write-Host "Время: $(Get-Date)" -ForegroundColor Cyan
Write-Host "`n--- 1. Сетевые адаптеры ---" -ForegroundColor Yellow
Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway } |
Select-Object InterfaceAlias, IPv4Address, IPv4DefaultGateway,
@{Name='DNS'; Expression={$_.DNSServer.ServerAddresses -join ', '}} |
Format-Table -AutoSize
Write-Host "--- 2. Ping $TargetHost ---" -ForegroundColor Yellow
$ping = Test-Connection -TargetName $TargetHost -Count 5 -ErrorAction SilentlyContinue
if ($ping) {
$ping | Select-Object Address, Latency, Status | Format-Table -AutoSize
$avg = ($ping | Measure-Object Latency -Average).Average
Write-Host "Средняя задержка: $([math]::Round($avg, 1)) мс"
} else {
Write-Host "Хост недоступен!" -ForegroundColor Red
}
Write-Host "`n--- 3. Traceroute до $TargetHost ---" -ForegroundColor Yellow
Test-Connection -TargetName $TargetHost -Traceroute -ErrorAction SilentlyContinue |
Select-Object Hop, Address, Latency | Format-Table -AutoSize
Write-Host "--- 4. DNS-разрешение $DNSName ---" -ForegroundColor Yellow
Resolve-DnsName -Name $DNSName -Type A -ErrorAction SilentlyContinue | Format-Table -AutoSize
Resolve-DnsName -Name $DNSName -Type MX -ErrorAction SilentlyContinue | Format-Table -AutoSize
Write-Host "--- 5. Проверка портов $TargetHost ---" -ForegroundColor Yellow
$ports = @(80, 443, 22, 3389, 445, 53)
foreach ($port in $ports) {
$result = Test-NetConnection -ComputerName $TargetHost -Port $port `
-WarningAction SilentlyContinue
$status = if ($result.TcpTestSucceeded) {'OPEN'} else {'CLOSED'}
$color = if ($result.TcpTestSucceeded) {'Green'} else {'Red'}
Write-Host " Порт ${port}: $status" -ForegroundColor $color
}
Скрипт 20: Ежедневный сводный HTML-отчёт руководству
Гордость проекта — красивый HTML-отчёт, который отправляется каждое утро в 7:00 директору по ИТ и генеральному директору:
# Скрипт 20: Генерация и отправка сводного HTML-отчёта
# Разработан инженерами АйТи Фреш
param(
[string]$SmtpServer = 'mail.company.ru',
[string]$AdminEmail = 'admin@company.ru',
[string]$ReportPath = 'C:\Scripts\Reports'
)
$serverName = $env:COMPUTERNAME
$reportDate = Get-Date -Format 'dd.MM.yyyy HH:mm'
$uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
$uptimeSpan = New-TimeSpan -Start $uptime -End (Get-Date)
$disks = Get-WmiObject Win32_LogicalDisk -Filter 'DriveType=3' |
Select-Object DeviceID,
@{Name='SizeGB'; Expression={[math]::Round($_.Size/1GB,1)}},
@{Name='FreeGB'; Expression={[math]::Round($_.FreeSpace/1GB,1)}},
@{Name='FreePct'; Expression={[math]::Round($_.FreeSpace/$_.Size*100,1)}}
$topProc = Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 Name, Id,
@{Name='RAM_MB'; Expression={[math]::Round($_.WorkingSet64/1MB)}}
$stoppedSvc = Get-Service | Where-Object {
$_.StartType -eq 'Automatic' -and $_.Status -ne 'Running'
} | Select-Object Name, DisplayName, Status
$failedEvents = Get-WinEvent -FilterHashtable @{
LogName='System'; Level=1,2; StartTime=(Get-Date).AddHours(-24)
} -MaxEvents 10 -ErrorAction SilentlyContinue |
Select-Object TimeCreated, Id, ProviderName,
@{Name='Msg'; Expression={$_.Message.Substring(0, [Math]::Min(100, $_.Message.Length))}}
$css = @'
'@
$html = @"
$css
Ежедневный отчёт: $serverName
Дата: $reportDate | Uptime: $($uptimeSpan.Days)д $($uptimeSpan.Hours)ч
Дисковое пространство
$($disks | ConvertTo-Html -Fragment | Out-String)
Топ-5 процессов по RAM
$($topProc | ConvertTo-Html -Fragment | Out-String)
Остановленные автозапускаемые службы
$(if ($stoppedSvc) { $stoppedSvc | ConvertTo-Html -Fragment | Out-String }
else { 'Все автозапускаемые службы работают
' })
Критичные события за 24 часа
$(if ($failedEvents) { $failedEvents | ConvertTo-Html -Fragment | Out-String }
else { 'Критичных событий нет
' })
Автоматический отчёт — система мониторинга АйТи Фреш
"@
$filePath = "$ReportPath\DailyReport_$(Get-Date -Format 'yyyyMMdd').html"
$html | Out-File -FilePath $filePath -Encoding UTF8
Send-MailMessage -From "report@$serverName" -To $AdminEmail `
-Subject "Ежедневный отчёт: $serverName — $reportDate" `
-Body $html -BodyAsHtml -SmtpServer $SmtpServer -Encoding UTF8
Write-Host "Отчёт отправлен на $AdminEmail" -ForegroundColor Green
Результаты внедрения
Проект автоматизации IT-рутины для страховой компании был завершён за 2 недели. Вот что было достигнуто:
- 20 PowerShell-скриптов внедрены и настроены на автоматический запуск через Task Scheduler
- Экономия 15 часов в неделю — время администратора, ранее потраченное на рутинные проверки
- Email-уведомления о всех критичных событиях в течение 5 минут — вместо обнаружения проблем «на следующий день»
- Ежедневный HTML-отчёт для руководства — полная прозрачность состояния IT-инфраструктуры
- Аудит безопасности AD — выявлено 47 неактивных учёток (некоторые с привилегированным доступом), все деактивированы
- Автоматический бэкап файлов и конфигурации сетевого оборудования — с ротацией и уведомлениями
- Документация — каждый скрипт снабжён комментариями, администратор клиента может самостоятельно модифицировать параметры
Бизнес-результат: за первый месяц после внедрения администратор перенаправил высвободившееся время на миграцию почтового сервера и внедрение VPN — проекты, которые откладывались полгода. Руководство получило инструмент контроля за IT-инфраструктурой, а компания успешно прошла аудит безопасности регулятора.