· 17 мин чтения

Microsoft Graph API через PowerShell: автоматизация Microsoft 365 с нуля до продакшена

Я Семёнов Евгений Сергеевич, директор АйТи Фреш. За 15+ лет сопровождения корпоративных облачных сервисов видел все этапы эволюции API Microsoft — от MSOnline через AzureAD к текущему Graph. И сегодня правильный инструмент для автоматизации Microsoft 365 из PowerShell — только Microsoft.Graph SDK. Старые модули Microsoft объявила устаревшими, так что пора переписывать. В этой статье — как зарегистрировать приложение в Entra ID, настроить сертификатную аутентификацию и написать рабочие скрипты для массового управления пользователями, лицензиями, Exchange Online, Teams и SharePoint.

Что такое Microsoft Graph

Graph — единый REST API для всех облачных сервисов Microsoft: Entra ID (бывший Azure AD), Exchange Online, SharePoint, Teams, OneDrive, Intune, Defender. Один набор эндпоинтов, одна модель аутентификации, одна документация. PowerShell-модуль Microsoft.Graph — обёртка вокруг этого API, сгенерированная автоматически из OpenAPI-спек.

Модуль делится на суб-модули по областям: Microsoft.Graph.Users, Microsoft.Graph.Groups, Microsoft.Graph.Identity.DirectoryManagement, Microsoft.Graph.Mail и т.д. Можно установить весь пакет или только нужное:

# Полностью (700+ МБ, долго)
Install-Module Microsoft.Graph -Scope CurrentUser

# Только нужные модули (быстрее)
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser
Install-Module Microsoft.Graph.Users -Scope CurrentUser
Install-Module Microsoft.Graph.Groups -Scope CurrentUser
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser

Регистрация приложения в Entra ID

Для production-автоматизации — всегда через отдельное приложение. Ручной вход с Connect-MgGraph -Scopes ... годится только для отладки. В Entra ID Portal:

  1. Entra admin center → Identity → Applications → App registrations → New registration.
  2. Имя: ITFresh-Automation-Graph. Supported account types: Single tenant. Без Redirect URI.
  3. После регистрации — раздел API permissions → Add a permission → Microsoft Graph → Application permissions. Выбираете нужные (User.Read.All, Group.ReadWrite.All, Mail.Send, и т.д.). После добавления — кнопка Grant admin consent.
  4. Certificates & secrets → Certificates → Upload certificate (PFX без приватного ключа — .cer). Запоминаете Thumbprint.
  5. Overview → запоминаете Application (client) ID и Directory (tenant) ID.

Генерация сертификата и подключение

# На сервере автоматизации
$cert = New-SelfSignedCertificate -Subject 'CN=ITFresh-Graph-Auth' `
    -CertStoreLocation 'Cert:\CurrentUser\My' `
    -KeyExportPolicy Exportable -KeySpec Signature `
    -KeyLength 2048 -HashAlgorithm SHA256 -NotAfter (Get-Date).AddYears(2)

# Экспортируем .cer для загрузки в Entra Portal
Export-Certificate -Cert $cert -FilePath 'C:\Install\graph-app.cer' -Type CERT

# Подключение к Graph сертификатом
Connect-MgGraph -ClientId 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' `
    -TenantId 'yyyyyyyy-xxxx-zzzz-wwww-vvvvvvvvvvvv' `
    -CertificateThumbprint $cert.Thumbprint -NoWelcome

# Проверка
Get-MgContext
Get-MgUser -Top 5

С этого момента вы можете из скриптов дёргать любую команду SDK под правами приложения. Я никогда не использую client secret в продакшене — только сертификаты. Причина проста: secret один раз попав в чей-то git или backup-файл становится бомбой замедленного действия.

Основные операции над пользователями

# Все активные пользователи
Get-MgUser -All -Filter "accountEnabled eq true" `
    -Property Id,UserPrincipalName,DisplayName,Department,JobTitle,UsageLocation |
    Select-Object UserPrincipalName, DisplayName, Department

# Создание
$params = @{
    AccountEnabled = $true
    DisplayName = 'Иван Петров'
    MailNickname = 'ipetrov'
    UserPrincipalName = 'ipetrov@itfresh.ru'
    UsageLocation = 'RU'
    PasswordProfile = @{
        Password = 'Rnd-Tmp-8f3A!'
        ForceChangePasswordNextSignIn = $true
    }
}
New-MgUser @params

# Обновление
Update-MgUser -UserId 'ipetrov@itfresh.ru' -Department 'IT' -JobTitle 'Сисадмин'

# Отключение
Update-MgUser -UserId 'ipetrov@itfresh.ru' -AccountEnabled:$false

# Удаление (перемещает в корзину на 30 дней)
Remove-MgUser -UserId 'ipetrov@itfresh.ru'

Массовый онбординг из CSV

Типичный сценарий: HR присылает список новых сотрудников Excel, нужно создать 15 учёток за 5 минут. Мой рабочий скрипт:

Import-Csv .\new-hires.csv -Encoding UTF8 | ForEach-Object {
    $tmpPwd = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 14 |
        ForEach-Object {[char]$_})
    try {
        $user = New-MgUser -AccountEnabled $true `
            -DisplayName "$($_.LastName) $($_.FirstName)" `
            -GivenName $_.FirstName -Surname $_.LastName `
            -MailNickname $_.Login -UserPrincipalName "$($_.Login)@itfresh.ru" `
            -UsageLocation 'RU' -Department $_.Department `
            -JobTitle $_.Position `
            -PasswordProfile @{ Password = $tmpPwd; ForceChangePasswordNextSignIn = $true }

        # Лицензия E3 через группу-based licensing
        New-MgGroupMember -GroupId $licenseGroupId -DirectoryObjectId $user.Id
        [PSCustomObject]@{ UPN = $user.UserPrincipalName; TempPassword = $tmpPwd; Status = 'OK' }
    } catch {
        [PSCustomObject]@{ UPN = "$($_.Login)@itfresh.ru"; Status = "ERR: $($_.Exception.Message)" }
    }
} | Export-Excel .\onboarding-result-$(Get-Date -f yyyyMMdd).xlsx -AutoSize

Exchange Online: почтовые ящики и правила

Для полного управления Exchange Online я обычно комбинирую Graph с модулем ExchangeOnlineManagement — Graph покрывает чтение, но многие операции над mailbox и правилами по-прежнему доступны только в классическом модуле.

Install-Module ExchangeOnlineManagement -Scope CurrentUser
Connect-ExchangeOnline -AppId 'aaaaaaaa-...' -CertificateThumbprint $thumb `
    -Organization 'itfresh.onmicrosoft.com' -ShowBanner:$false

# Размеры всех ящиков
Get-EXOMailbox -ResultSize Unlimited -PropertySets Minimum |
    ForEach-Object {
        $stat = Get-EXOMailboxStatistics $_.UserPrincipalName
        [PSCustomObject]@{
            UPN = $_.UserPrincipalName
            Size_GB = [math]::Round(($stat.TotalItemSize.Value.ToBytes() / 1GB), 2)
            Items = $stat.ItemCount
        }
    } | Sort-Object Size_GB -Descending |
    Export-Excel .\mbx-sizes.xlsx -AutoSize

# Автоответ при увольнении
Set-MailboxAutoReplyConfiguration -Identity user@itfresh.ru `
    -AutoReplyState Enabled -ExternalAudience All `
    -InternalMessage 'Сотрудник уволен, обращайтесь в info@itfresh.ru' `
    -ExternalMessage 'Сотрудник уволен, обращайтесь в info@itfresh.ru'

Мини-кейс: автоматизация онбординга на 120 человек

В ноябре 2025 пришёл клиент — сеть образовательных центров, 120 новых преподавателей к 1 сентября. Раньше HR-админ делал это вручную неделю: создание учёток, лицензирование, настройка автоответа, добавление в distribution-группы, создание папок в SharePoint. Я написал пайплайн на PowerShell Graph + SharePoint PnP: HR выгружает CSV из 1С:ЗУП, скрипт создаёт учётки, добавляет в группу с лицензией Business Standard, создаёт персональную папку в SharePoint и шлёт письмо с временным паролем менеджеру.

Серверная часть — наша виртуалка в дата-центре МТС на Dell с Xeon Platinum 8280, 64 ГБ RAM и 40G Mellanox до хранилища. Запускается по событию в SharePoint (Power Automate дёргает webhook на скрипт). Результат: 120 учёток развёрнуто за 8 минут, ошибок — 2 (некорректные символы в имени, исправили руками). Общая экономия времени HR-админа — около 40 часов. Стоимость проекта 75 тыс. руб.

Teams: массовое управление командами

# Создание команды для нового проекта
$team = New-MgTeam -DisplayName 'Project Delta' -Description 'Секретный проект' `
    -Visibility 'Private' -MemberSettings @{ AllowCreateUpdateChannels = $true }

# Добавление участников
@('ipetrov@itfresh.ru','asidorov@itfresh.ru') | ForEach-Object {
    $user = Get-MgUser -UserId $_
    New-MgTeamMember -TeamId $team.Id `
        -Roles @('member') -AdditionalProperties @{
            '@odata.type' = '#microsoft.graph.aadUserConversationMember'
            'user@odata.bind' = "https://graph.microsoft.com/v1.0/users('$($user.Id)')"
        }
}

# Канал
New-MgTeamChannel -TeamId $team.Id -DisplayName 'Инженерия' -MembershipType 'standard'

Throttling и best practices

Graph API ограничивает количество запросов в секунду на приложение. Если превысите — получите HTTP 429 с заголовком Retry-After. Модуль Microsoft.Graph сам ретраит при 429, но всё равно стоит:

Мониторинг действий приложения

Когда скрипт работает от имени приложения, все действия логируются в Entra ID Audit Logs под именем этого App. Это и плюс, и ответственность: любая ошибка — ваша. Я всегда настраиваю:

Автоматизируем Microsoft 365 под вас

Напишу PowerShell-пайплайны на Graph API для онбординга/оффбординга, управления лицензиями, отчётности, миграции с локального Exchange или Яндекс.Почты для бизнеса. 15+ лет опыта с корпоративным Microsoft 365, от тенантов на 10 лицензий до 800+. Хостинг автоматизации — виртуалки Dell с Xeon Platinum 8280 в дата-центре МТС с 40G аплинком.

Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш

FAQ — частые вопросы по Graph API

Чем Graph API лучше старых модулей MSOnline и AzureAD?
MSOnline и AzureAD Microsoft объявила устаревшими. Graph API — единая точка входа во все сервисы Microsoft 365, постоянно обновляется. Со временем останется только он.
Нужно ли регистрировать приложение?
Да, для продакшен-сценариев обязательно: даёт контроль над scope-правами, работу без интерактивного входа, сертификатную аутентификацию. Connect-MgGraph без -ClientId работает, но требует админ-пароль каждый раз.
Сертификат или client secret?
Сертификат безопаснее. Его нельзя случайно закоммитить в репо — в коде только thumbprint, сам PFX в защищённом хранилище. Secret имеет срок жизни максимум 24 месяца и часто истекает в неподходящий момент.
Как управлять лицензиями массово?
Set-MgUserLicense с параметрами AddLicenses и RemoveLicenses. Группа-based licensing через Azure AD Group — ещё надёжнее: добавил пользователя в группу, лицензия применилась автоматически.
Какие delegated vs application permissions?
Application permissions — App действует от своего имени, не требует присутствия пользователя. Подходит для ночных скриптов. Delegated permissions — App действует от имени залогиненного пользователя, требует его сессии.

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.