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:
- Entra admin center → Identity → Applications → App registrations → New registration.
- Имя:
ITFresh-Automation-Graph. Supported account types: Single tenant. Без Redirect URI. - После регистрации — раздел API permissions → Add a permission → Microsoft Graph → Application permissions. Выбираете нужные (User.Read.All, Group.ReadWrite.All, Mail.Send, и т.д.). После добавления — кнопка Grant admin consent.
- Certificates & secrets → Certificates → Upload certificate (PFX без приватного ключа — .cer). Запоминаете Thumbprint.
- 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, но всё равно стоит:
- Использовать
-Allи-PageSize 500вместо перебора — пакетный вывод быстрее. - Для массовых операций использовать
$batchзапросы через Invoke-MgGraphRequest. - Не вызывать Get-MgUser в цикле для 5000 пользователей — заберите всех разом и фильтруйте в памяти.
- Для долгих экспортов — использовать Graph Data Connect или Microsoft 365 Reports.
- Логировать всё в файл — если скрипт упадёт на 3000-м пользователе, чтобы понять, с какого места продолжать.
Мониторинг действий приложения
Когда скрипт работает от имени приложения, все действия логируются в Entra ID Audit Logs под именем этого App. Это и плюс, и ответственность: любая ошибка — ваша. Я всегда настраиваю:
- Алерт в Entra: «App ITFresh-Automation-Graph выполнил Delete user» → email админу.
- Отдельный лог скрипта в Azure Monitor / Log Analytics через Data Collector API.
- Ежемесячный отчёт: сколько создано/удалено/обновлено, кем запущено.
- Ротация сертификата за 60 дней до истечения — автоматическая через тот же Graph.
Автоматизируем 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 действует от имени залогиненного пользователя, требует его сессии.