PowerShell-скрипты для автоматизации Active Directory

Задача клиента: автоматизировать рутинные операции в пяти доменах

MSP-компания «АдминПро» обратилась к специалистам itfresh.ru с типичной для провайдера управляемых услуг проблемой. Их инженеры обслуживали пять доменов Active Directory общей численностью 2000 пользователей. Ежедневные рутинные операции — создание учётных записей, сброс паролей, аудит групп, очистка неактивных аккаунтов — занимали до 4 часов рабочего времени администратора.

Все операции выполнялись вручную через оснастку Active Directory Users and Computers (ADUC). При приёме нового сотрудника администратор тратил 15-20 минут: создание учётки, добавление в группы, настройка почтового ящика Exchange, создание домашней папки, назначение прав. При увольнении — столько же на отключение всех сервисов.

Мы разработали набор из 10 PowerShell-скриптов, которые сократили время выполнения рутинных операций с 4 часов до 20 минут в день. Каждый скрипт полностью автономен и запускается по расписанию через Task Scheduler.

Массовое создание пользователей из CSV

Первый и самый востребованный скрипт — создание пользователей из CSV-файла. HR-отдел заполняет Excel-таблицу с данными новых сотрудников, экспортирует в CSV, и скрипт автоматически создаёт учётные записи со всеми необходимыми атрибутами.

# bulk_create_users.ps1
# CSV-файл: LastName,FirstName,Department,Title,Office,Phone,Manager
param(
    [Parameter(Mandatory)]
    [string]$CsvPath,
    [string]$Domain = "company.local",
    [string]$DefaultPassword = "Welcome2024!",
    [switch]$WhatIf
)

Import-Module ActiveDirectory

$users = Import-Csv -Path $CsvPath -Encoding UTF8
$logFile = "C:\Logs\AD\user_creation_$(Get-Date -Format 'yyyyMMdd_HHmm').log"
$created = 0; $errors = 0

foreach ($user in $users) {
    $firstName = $user.FirstName.Trim()
    $lastName = $user.LastName.Trim()
    
    # Транслитерация для SamAccountName
    $sam = (ConvertTo-Translit "$firstName.$lastName").ToLower()
    $upn = "$sam@$Domain"
    $displayName = "$lastName $firstName"
    
    # Определяем OU по отделу
    $ouMap = @{
        "IT"        = "OU=IT,OU=Users,OU=Company,DC=company,DC=local"
        "Sales"     = "OU=Sales,OU=Users,OU=Company,DC=company,DC=local"
        "HR"        = "OU=HR,OU=Users,OU=Company,DC=company,DC=local"
        "Finance"   = "OU=Finance,OU=Users,OU=Company,DC=company,DC=local"
    }
    $targetOU = $ouMap[$user.Department]
    if (-not $targetOU) { $targetOU = "OU=Users,OU=Company,DC=company,DC=local" }
    
    try {
        if ($WhatIf) {
            Write-Host "[DRY RUN] Would create: $displayName ($sam)" -ForegroundColor Yellow
            continue
        }
        
        New-ADUser -Name $displayName `
            -GivenName $firstName `
            -Surname $lastName `
            -SamAccountName $sam `
            -UserPrincipalName $upn `
            -DisplayName $displayName `
            -Department $user.Department `
            -Title $user.Title `
            -Office $user.Office `
            -OfficePhone $user.Phone `
            -Manager (Get-ADUser -Filter "DisplayName -eq '$($user.Manager)'").DistinguishedName `
            -Path $targetOU `
            -AccountPassword (ConvertTo-SecureString $DefaultPassword -AsPlainText -Force) `
            -ChangePasswordAtLogon $true `
            -Enabled $true `
            -ErrorAction Stop
        
        # Добавляем в группы по отделу
        $groupMap = @{
            "IT"      = @("SG-VPN-Users","SG-IT-Staff","SG-RDP-Users")
            "Sales"   = @("SG-CRM-Users","SG-Sales-Team")
            "HR"      = @("SG-HR-Staff","SG-PersonalData-Access")
            "Finance" = @("SG-1C-Users","SG-Finance-Team")
        }
        foreach ($group in $groupMap[$user.Department]) {
            Add-ADGroupMember -Identity $group -Members $sam
        }
        
        $created++
        "$(Get-Date) [OK] Created: $displayName ($sam)" | Add-Content $logFile
        Write-Host "[OK] $displayName" -ForegroundColor Green
    }
    catch {
        $errors++
        "$(Get-Date) [ERROR] $displayName : $_" | Add-Content $logFile
        Write-Warning "[ERROR] $displayName : $_"
    }
}

Write-Host "`nResults: Created=$created, Errors=$errors" -ForegroundColor Cyan
Write-Host "Log: $logFile"

Скрипт поддерживает режим WhatIf для предварительной проверки без реального создания учёток. Функция ConvertTo-Translit переводит кириллические имена в латиницу по стандарту ГОСТ для формирования логинов.

Отчёт об истечении паролей и очистка неактивных учёток

Скрипт 2: Отчёт об истечении паролей — еженедельный отчёт для администраторов и уведомления пользователям.

# password_expiration_report.ps1
param(
    [int]$WarningDays = 14,
    [string]$SmtpServer = "mail.company.local",
    [string]$AdminEmail = "admin@company.local"
)

Import-Module ActiveDirectory

$maxAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
$today = Get-Date
$report = @()

Get-ADUser -Filter {Enabled -eq $true -and PasswordNeverExpires -eq $false} `
    -Properties PasswordLastSet,Mail,Department,DisplayName |
ForEach-Object {
    $expiryDate = $_.PasswordLastSet.AddDays($maxAge)
    $daysLeft = ($expiryDate - $today).Days
    
    if ($daysLeft -le $WarningDays -and $daysLeft -ge 0) {
        $report += [PSCustomObject]@{
            User       = $_.DisplayName
            Login      = $_.SamAccountName
            Email      = $_.Mail
            Department = $_.Department
            ExpiryDate = $expiryDate.ToString("dd.MM.yyyy")
            DaysLeft   = $daysLeft
        }
        
        # Уведомление пользователю
        if ($_.Mail -and $daysLeft -le 7) {
            $body = @"
Здравствуйте, $($_.DisplayName)!

Ваш пароль истекает через $daysLeft дн. ($($expiryDate.ToString('dd.MM.yyyy'))).
Пожалуйста, смените пароль: Ctrl+Alt+Del → Сменить пароль.

Требования: минимум 12 символов, заглавные и строчные буквы, цифры, спецсимволы.
"@
            Send-MailMessage -From "noreply@company.local" `
                -To $_.Mail -Subject "Пароль истекает через $daysLeft дн." `
                -Body $body -SmtpServer $SmtpServer -Encoding UTF8
        }
    }
}

# HTML-отчёт для администратора
$html = $report | Sort-Object DaysLeft |
    ConvertTo-Html -Title "Password Expiration Report" `
    -PreContent "<h2>Пароли истекают в ближайшие $WarningDays дней</h2>" |
    Out-String

Send-MailMessage -From "noreply@company.local" -To $AdminEmail `
    -Subject "[AD] Отчёт: $($report.Count) паролей истекают" `
    -Body $html -BodyAsHtml -SmtpServer $SmtpServer -Encoding UTF8

Скрипт 3: Очистка неактивных учётных записей — отключение аккаунтов, не входивших в систему более 90 дней.

# cleanup_inactive_accounts.ps1
param(
    [int]$InactiveDays = 90,
    [switch]$DisableOnly,
    [switch]$WhatIf
)

Import-Module ActiveDirectory
$cutoffDate = (Get-Date).AddDays(-$InactiveDays)
$logFile = "C:\Logs\AD\inactive_cleanup_$(Get-Date -Format 'yyyyMMdd').log"

# Находим неактивных пользователей
$inactive = Search-ADAccount -AccountInactive -DateTime $cutoffDate -UsersOnly |
    Where-Object {
        $_.Enabled -eq $true -and
        $_.DistinguishedName -notlike "*ServiceAccounts*" -and  # Исключаем сервисные
        $_.DistinguishedName -notlike "*OU=Admins*"             # Исключаем админов
    }

Write-Host "Found $($inactive.Count) inactive accounts (> $InactiveDays days)" -ForegroundColor Yellow

foreach ($user in $inactive) {
    $lastLogon = [DateTime]::FromFileTime($user.LastLogonTimestamp)
    $info = "$($user.SamAccountName) | Last logon: $($lastLogon.ToString('dd.MM.yyyy')) | $($user.DistinguishedName)"
    
    if ($WhatIf) {
        Write-Host "[DRY RUN] Would disable: $info" -ForegroundColor Yellow
        continue
    }
    
    # Отключаем учётку
    Disable-ADAccount -Identity $user.SamAccountName
    
    # Перемещаем в OU "Disabled"
    Move-ADObject -Identity $user.DistinguishedName `
        -TargetPath "OU=Disabled,O

Аудит членства в группах и управление OU

Скрипт 5: Аудит членства в группах — ежемесячный отчёт о том, кто входит в привилегированные группы.

# group_membership_audit.ps1
param(
    [string[]]$CriticalGroups = @(
        "Domain Admins",
        "Enterprise Admins",
        "Schema Admins",
        "Administrators",
        "Account Operators",
        "Backup Operators"
    ),
    [string]$ReportPath = "C:\Reports\AD"
)

Import-Module ActiveDirectory
$date = Get-Date -Format "yyyyMMdd"
$allData = @()

foreach ($group in $CriticalGroups) {
    $members = Get-ADGroupMember -Identity $group -Recursive | ForEach-Object {
        $user = Get-ADUser -Identity $_ -Properties DisplayName,Department,
            LastLogonDate,WhenCreated,WhenChanged,MemberOf
        [PSCustomObject]@{
            Group        = $group
            User         = $user.DisplayName
            Login        = $user.SamAccountName
            Department   = $user.Department
            LastLogon    = $user.LastLogonDate
            Created      = $user.WhenCreated
            Changed      = $user.WhenChanged
            TotalGroups  = ($user.MemberOf).Count
        }
    }
    $allData += $members
    Write-Host "$group : $($members.Count) members" -ForegroundColor Cyan
}

# Экспорт в CSV
$csvPath = "$ReportPath\group_audit_$date.csv"
$allData | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8

# Сравнение с предыдущим отчётом для обнаружения изменений
$prevFile = Get-ChildItem "$ReportPath\group_audit_*.csv" |
    Sort-Object LastWriteTime -Descending | Select-Object -Skip 1 -First 1

if ($prevFile) {
    $prev = Import-Csv $prevFile.FullName
    $current = Import-Csv $csvPath
    
    $added = Compare-Object $prev $current -Property Login,Group |
        Where-Object { $_.SideIndicator -eq "=>" }
    $removed = Compare-Object $prev $current -Property Login,Group |
        Where-Object { $_.SideIndicator -eq "<=" }
    
    if ($added -or $removed) {
        Write-Host "`n=== CHANGES DETECTED ==="  -ForegroundColor Red
        $added | ForEach-Object { Write-Host "[+] $($_.Login) added to $($_.Group)" -ForegroundColor Green }
        $removed | ForEach-Object { Write-Host "[-] $($_.Login) removed from $($_.Group)" -ForegroundColor Red }
    }
}

Write-Host "`nReport saved: $csvPath"

Скрипт 6: Управление структурой OU — экспорт и импорт организационных единиц между доменами. Незаменим для MSP, управляющих несколькими клиентами с одинаковой структурой.

# ou_management.ps1

# Экспорт структуры OU в JSON
function Export-OUStructure {
    param([string]$SearchBase, [string]$OutputFile)
    
    $ous = Get-ADOrganizationalUnit -Filter * -SearchBase $SearchBase `
        -Properties Description,ManagedBy | ForEach-Object {
        [PSCustomObject]@{
            Name            = $_.Name
            DistinguishedName = $_.DistinguishedName
            Description     = $_.Description
            ManagedBy       = $_.ManagedBy
            Depth           = ($_.DistinguishedName -split ',OU=').Count - 1
        }
    } | Sort-Object Depth,Name
    
    $ous | ConvertTo-Json -Depth 5 | Out-File $OutputFile -Encoding UTF8
    Write-Host "Exported $($ous.Count) OUs to $OutputFile"
}

# Импорт структуры OU из JSON
function Import-OUStructure {
    param([string]$InputFile, [string]$TargetDomain)
    
    $ous = Get-Content $InputFile | ConvertFrom-Json
    foreach ($ou in ($ous | Sort-Object Depth)) {
        $path = ($ou.Disting

Бэкап GPO и provisioning Exchange-ящиков

Скрипт 7: Резервное копирование и восстановление GPO — автоматический бэкап всех групповых политик с историей изменений.

# gpo_backup_restore.ps1
param(
    [string]$BackupPath = "\\FS01\Backups\GPO",
    [int]$RetentionDays = 90,
    [switch]$Restore,
    [string]$RestoreGpoName
)

Import-Module GroupPolicy
$date = Get-Date -Format "yyyyMMdd_HHmm"
$backupDir = "$BackupPath\$date"

if (-not $Restore) {
    # === BACKUP ===
    New-Item -Path $backupDir -ItemType Directory -Force | Out-Null
    
    $gpos = Get-GPO -All
    $manifest = @()
    
    foreach ($gpo in $gpos) {
        $result = Backup-GPO -Guid $gpo.Id -Path $backupDir
        $manifest += [PSCustomObject]@{
            Name         = $gpo.DisplayName
            Guid         = $gpo.Id
            BackupId     = $result.Id
            ModifiedTime = $gpo.ModificationTime
            Status       = "OK"
        }
        Write-Host "[BACKUP] $($gpo.DisplayName)" -ForegroundColor Green
    }
    
    # Сохраняем манифест
    $manifest | Export-Csv "$backupDir\_manifest.csv" -NoTypeInformation -Encoding UTF8
    
    # Очистка старых бэкапов
    Get-ChildItem $BackupPath -Directory |
        Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$RetentionDays) } |
        Remove-Item -Recurse -Force
    
    Write-Host "`nBackup complete: $($gpos.Count) GPOs → $backupDir"
}
else {
    # === RESTORE ===
    # Находим последний бэкап с нужной GPO
    $latestBackup = Get-ChildItem $BackupPath -Directory | Sort-Object Name -Descending | Select-Object -First 1
    $manifest = Import-Csv "$($latestBackup.FullName)\_manifest.csv"
    $target = $manifest | Where-Object { $_.Name -eq $RestoreGpoName }
    
    if ($target) {
        Restore-GPO -BackupId $target.BackupId -Path $latestBackup.FullName
        Write-Host "[RESTORED] $RestoreGpoName from $($latestBackup.Name)" -ForegroundColor Green
    } else {
        Write-Warning "GPO '$RestoreGpoName' not found in backup"
    }
}

Скрипт 8: Provisioning Exchange-ящиков — создание почтовых ящиков для новых пользователей.

# exchange_provisioning.ps1
param(
    [string]$SamAccountName,
    [string]$MailboxDatabase = "DB01",
    [string]$ExchangeServer = "EX01.company.local"
)

# Подключение к Exchange Management Shell
$session = New-PSSession -ConfigurationName Microsoft.Exchange `
    -ConnectionUri "http://$ExchangeServer/PowerShell/" `
    -Authentication Kerberos
Import-PSSession $session -AllowClobber

# Создание ящика для существующего AD-пользователя
$user = Get-ADUser $SamAccountName -Properties Department,DisplayName

Enable-Mailbox -Identity $SamAccountName `
    -Database $MailboxDatabase `
    -PrimarySmtpAddress "$SamAccountName@company.ru" `
    -Alias $SamAccountName

# Настройка квоты по отделу
$quotaMap = @{
    "IT"      = @{ Issue = "5GB"; Prohibit = "6GB"; Send = "5.5GB" }
    "Sales"   = @{ Issue = "2GB"; Prohibit = "3GB"; Send = "2.5GB" }
    "Default" = @{ Issue = "1GB"; Prohibit = "2GB"; Send = "1.5GB" }
}
$quota = $quotaMap[$user.Department]
if (-not $quota) { $quota = $quotaMap["Default"] }

Set-Mailbox -Identity $SamAccountName `
    -IssueWarningQuota $quota.Issue `
    -ProhibitSendQuota $quota.Send `
    -ProhibitSendReceiveQuota $quota.Prohibit `
    -UseDatabaseQuotaDefaults $false

Write-Host "[OK] Mailbox created: $SamAccountName@company.ru (quota: $($quota.Prohibit))"
Remove-PSSession $session

Автоматический onboarding и offboarding сотрудников

Скрипт 9: Onboarding — полный цикл создания рабочего места нового сотрудника за одну команду.

# onboarding.ps1 — единая точка входа для создания сотрудника
param(
    [Parameter(Mandatory)][string]$FirstName,
    [Parameter(Mandatory)][string]$LastName,
    [Parameter(Mandatory)][string]$Department,
    [Parameter(Mandatory)][string]$Title,
    [string]$Manager,
    [string]$Office = "HQ"
)

$ErrorActionPreference = "Stop"
$logFile = "C:\Logs\AD\onboarding_$(Get-Date -Format 'yyyyMMdd_HHmm').log"

function Log($msg) {
    $entry = "$(Get-Date -Format 'HH:mm:ss') $msg"
    Write-Host $entry
    $entry | Add-Content $logFile
}

try {
    $sam = (ConvertTo-Translit "$FirstName.$LastName").ToLower()
    Log "=== Onboarding: $FirstName $LastName ($sam) ==="
    
    # 1. Создание AD-учётки
    Log "[1/6] Creating AD account..."
    .\bulk_create_users.ps1 -CsvPath (New-TemporaryFile | ForEach-Object {
        "LastName,FirstName,Department,Title,Office,Phone,Manager" | Out-File $_.FullName
        "$LastName,$FirstName,$Department,$Title,$Office,,$Manager" | Add-Content $_.FullName
        $_.FullName
    })
    
    # 2. Создание Exchange-ящика
    Log "[2/6] Creating Exchange mailbox..."
    .\exchange_provisioning.ps1 -SamAccountName $sam
    
    # 3. Создание домашней папки
    Log "[3/6] Creating home folder..."
    $homePath = "\\FS01\UserFolders$\$sam"
    New-Item -Path $homePath -ItemType Directory -Force
    $acl = Get-Acl $homePath
    $rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
        "COMPANY\$sam","FullControl","ContainerInherit,ObjectInherit","None","Allow")
    $acl.AddAccessRule($rule)
    Set-Acl -Path $homePath -AclObject $acl
    Set-ADUser -Identity $sam -HomeDirectory $homePath -HomeDrive "H:"
    
    # 4. Добавление в группы Teams/SharePoint
    Log "[4/6] Adding to Teams groups..."
    $teamGroups = @{
        "IT"      = "Team-IT"
        "Sales"   = "Team-Sales"
        "HR"      = "Team-HR"
        "Finance" = "Team-Finance"
    }
    if ($teamGroups[$Department]) {
        Add-ADGroupMember -Identity $teamGroups[$Department] -Members $sam
    }
    
    # 5. Генерация приветственного письма
    Log "[5/6] Sending welcome email..."
    $password = "Welcome2024!"  # Временный пароль
    $body = @"

Добро пожаловать в компанию!

Логин: $sam

Email: $sam@company.ru

Временный пароль: $password

При первом входе система попросит сменить пароль.

Инструкции по настройке рабочего места: https://wiki.company.local/onboarding

"@ Send-MailMessage -From "hr@company.ru" -To "$Manager@company.ru" ` -Subject "Новый сотрудник: $FirstName $LastName" ` -Body $body -BodyAsHtml -SmtpServer mail.company.local -Encoding UTF8 # 6. Запись в журнал аудита Log "[6/6] Audit log entry..." $audit = [PSCustomObject]@{ Date = Get-Date -Format "yyyy-MM-dd HH:mm" Action = "ONBOARDING" User = $sam Department = $Department CreatedBy = $env:USERNAME } $audit | Export-Csv "C:\Logs\AD\audit_trail.csv" -Append -NoTypeInformation Log "=== Onboarding complete! ===" } catch { Log "[ERROR] $_" throw }

Скрипт 10: Offboarding — отключение всех сервисов при увольнении:

# offboarding.ps1
param(
    [Parameter(Mandatory)][string]$SamAcco

Запуск по расписанию и итоги автоматизации

Все скрипты зарегистрированы в Task Scheduler для автоматического выполнения:

# Регистрация задач в Task Scheduler

# Отчёт о паролях — каждый понедельник в 08:00
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -File C:\Scripts\AD\password_expiration_report.ps1"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At 8am
$principal = New-ScheduledTaskPrincipal -UserId "COMPANY\svc-scripts" `
    -LogonType Password -RunLevel Highest
Register-ScheduledTask -TaskName "AD-PasswordReport" `
    -Action $action -Trigger $trigger -Principal $principal

# Очистка неактивных — первое число каждого месяца в 06:00
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -File C:\Scripts\AD\cleanup_inactive_accounts.ps1 -InactiveDays 90"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -WeeksInterval 4 -At 6am
Register-ScheduledTask -TaskName "AD-InactiveCleanup" `
    -Action $action -Trigger $trigger -Principal $principal

# Аудит групп — каждую пятницу в 18:00
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -File C:\Scripts\AD\group_membership_audit.ps1"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Friday -At 6pm
Register-ScheduledTask -TaskName "AD-GroupAudit" `
    -Action $action -Trigger $trigger -Principal $principal

# Бэкап GPO — ежедневно в 02:00
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -File C:\Scripts\AD\gpo_backup_restore.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 2am
Register-ScheduledTask -TaskName "AD-GPOBackup" `
    -Action $action -Trigger $trigger -Principal $principal

Результаты внедрения автоматизации в «АдминПро»:

ОперацияДо (вручную)После (скрипт)
Onboarding сотрудника15-20 мин45 сек
Offboarding сотрудника15-20 мин30 сек
Аудит групп (все домены)2-3 часа5 мин (автоматически)
Отчёт о пароляхНе делалосьАвтоматически еженедельно
Бэкап GPOРаз в месяц вручнуюЕжедневно автоматически
Очистка неактивныхРаз в кварталЕжемесячно автоматически

Экономия времени — более 80 часов в месяц на пять доменов. Скрипты работают одинаково во всех доменах клиентов «АдминПро», легко адаптируются под конкретные требования. Если вашей компании нужна автоматизация Active Directory — обращайтесь к специалистам itfresh.ru.

Часто задаваемые вопросы

Модуль ActiveDirectory из состава RSAT (Remote Server Administration Tools). На Windows Server устанавливается командой Install-WindowsFeature RSAT-AD-PowerShell. На Windows 10/11 — через Settings → Apps → Optional Features → RSAT: Active Directory Domain Services. Модуль предоставляет более 140 командлетов для управления AD.
Используйте параметр -WhatIf, поддерживаемый большинством командлетов AD. Он показывает, что произойдёт при выполнении, без реального изменения. Также рекомендуется тестировать на отдельном контроллере домена в изолированной тестовой среде, которую можно развернуть через Hyper-V.
Создайте выделенную сервисную учётную запись (например, svc-scripts) с минимально необходимыми правами. Не используйте Domain Admin для автоматических задач. Для каждого скрипта определите конкретные права: для создания пользователей — делегируйте права на OU, для бэкапа GPO — Group Policy Creator Owners.
Напишите функцию транслитерации по стандарту ГОСТ 7.79-2000 или используйте готовый модуль из PowerShell Gallery. Функция должна заменять кириллические символы на латинские эквиваленты: А→A, Б→B, В→V и т.д. Обязательно проверяйте уникальность полученного SamAccountName в домене.
Да. Используйте параметр -Server для указания контроллера домена: Get-ADUser -Filter * -Server dc01.domain2.local. Для аутентификации в другом домене добавьте -Credential (Get-Credential). Для MSP удобно хранить список доменов в конфигурационном файле и обрабатывать их в цикле.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#powershell active directory#автоматизация ad#скрипты powershell#bulk user creation#password expiration report#group membership audit#gpo backup#onboarding offboarding
Комментарии 0

Оставить комментарий

загрузка...