Microsoft 365 / 使用Microsoft Graph PowerShell查询用户并导出CSV

使用 Microsoft Graph PowerShell 可以查询 Microsoft 365 用户,并自动化帐户和许可证管理操作。首次使用时,请按照如下顺序操作:安装所需模块 → 使用读取权限连接 → 确认目标组织 → 将用户列表保存为 CSV。以下查询实操以及帐户创建和许可证更改示例由于权限和业务影响不同,需要区分执行。

Microsoft Graph PowerShell 是什么?

Microsoft Graph是处理 Microsoft Entra ID、Teams、SharePoint 等数据的 API(应用程序编程接口,程序间通信接口)。Graph PowerShell SDK(软件开发工具包)可以让你通过 PowerShell 命令使用该 API。在以前的文档中称为 Azure Active Directory 或 Azure AD 的目录服务,其当前名称是 Microsoft Entra ID。

安装 Graph 并不意味着可以使用所有 Microsoft 365 管理命令。有些操作,如仅针对 Exchange Online 的管理命令,需要单独的模块和连接。本文以全球云环境为例,使用Microsoft Graph v1.0模块进行 交互式委派认证。无人值守的计划任务应用专用认证需要单独设计应用权限和证书等,与用户登录示例直接用于计划任务的方式不同。由世纪互联运营的环境等国家/地区云使用不同的连接终结点,不能直接照搬本例的默认连接环境;请对照Microsoft Graph国家/地区云部署说明。

准备事项和模块安装

Microsoft建议使用PowerShell 7或更高版本。请在受支持的PowerShell 7环境中进行以下练习。Windows PowerShell 5.1有额外的.NET Framework、PowerShellGet等要求,并且在一个版本中安装的模块不会自动安装到其他版本中。请先确认SDK安装要求。

  1. 在组织允许的管理用PC上打开PowerShell。$PSVersionTable.PSVersion检查版本。
  2. 在Microsoft Entra管理中心的Entra ID → 概览中,确认操作目标租户ID。如果帐户可以访问其他组织,则尤为重要。
  3. 确认Graph权限的管理员同意及自己的管理角色。请使用组织的公司或学校帐户,而非普通Microsoft个人帐户。
  4. 创建用于保存结果文件的访问受限文件夹。由于用户列表包含姓名、帐户地址等信息,请勿使用公开存储库或公开网络文件夹。

要仅安装当前用户范围所需的模块,请按以下方式执行。如果出现安装确认消息,请检查存储库和软件包。也可以安装整个 Microsoft.Graph 模块,但本练习以下子模块已足够。

Install-Module -Name Microsoft.Graph.Authentication,Microsoft.Graph.Users,Microsoft.Graph.Identity.DirectoryManagement,Microsoft.Graph.Users.Actions -Scope CurrentUser -Repository PSGallery

安装后,可通过以下命令确认可用版本和命令名称。在此步骤中不会连接组织数据。

Get-Module -ListAvailable Microsoft.Graph.Authentication,Microsoft.Graph.Users,Microsoft.Graph.Identity.DirectoryManagement,Microsoft.Graph.Users.Actions | Select-Object Name,Version
Get-Command Connect-MgGraph,Get-MgUser,Get-MgSubscribedSku,Set-MgUserLicense

如果由于执行策略而模块或脚本被阻止,请先查看后面的执行策略部分。不要将策略错误当作 403 权限错误处理。

区分连接权限和管理员角色

委托认证是以登录用户身份执行操作的方式。-Scopes 是应用请求的 Graph 权限范围,并且还必须满足登录管理员的角色·目标范围以及管理员同意。在 -Scopes 中写下名称并不会自动授予该权限或生成管理员角色。

操作 示例中请求的 Graph 权限 附加确认
查询用户列表·指定属性 User.Read.All 管理员同意和要查询的属性限制
订阅 SKU·用户许可证查询 LicenseAssignment.Read.All Directory Readers 等该 API 支持的角色
云用户创建 User.Create 管理员同意与用户管理权限·对象
许可证分配·回收 LicenseAssignment.ReadWrite.All License Administrator 等支持角色

进行读取练习时,不需要同时请求组更改权限或目录整体写入权限。权限的委托·应用区分及管理员同意情况可在 Microsoft Graph 权限参考 中确认。

如要测试单独的查询命令,请按如下方式连接。输入的租户 ID 是由组织确认的实际值,登录后请确认 Account·TenantId·Scopes。登录方式可根据安装版本和环境使用浏览器或 Windows 认证窗口等。

$tenantId = Read-Host '操作目标租户 ID'
Connect-MgGraph -TenantId $tenantId -Scopes 'User.Read.All' -ContextScope Process
Get-MgContext | Select-Object Account,TenantId,AuthType,Scopes

-ContextScope Process 是用于将认证上下文用于当前 PowerShell 进程范围的选项。不要假设未显式指定请求范围时 Connect-MgGraph 的结果总是 User.Read,而应实际检查上下文。在切换到其他帐户或权限时,请 Disconnect-MgGraph 后重新连接到所需范围。连接和认证方式可以参考 Graph PowerShell 认证指南。

用户查询和 CSV 保存完整示例

Get-MgUser -All 会继续查询分页返回的用户列表。不过,这并不意味着会自动获取所有属性。Select-Object * 只是展开已接收对象的属性,对于基本返回中未包含的值,需要 通过-Property向服务器请求这些属性。这个区别可以在 Get-MgUser 说明 中看到。

将以下完整代码保存为 m365-users-report.ps1 UTF-8 文件。脚本会读取用户信息,在同一文件夹内生成带时间戳的 CSV,并断开连接。不会更改帐户或许可证。如果之前进行了连接实习,请先使用 Disconnect-MgGraph 结束后再运行。

param(
    [Parameter(Mandatory)]
    [Guid]$TenantId
)

$ErrorActionPreference = 'Stop'
$connected = $false
$file = Join-Path $PSScriptRoot ("m365-users-{0}.csv" -f (Get-Date -Format 'yyyyMMdd-HHmmss'))

try {
    Connect-MgGraph -TenantId $TenantId.ToString() -Scopes 'User.Read.All' -ContextScope Process -NoWelcome
    $connected = $true
    $context = Get-MgContext
    if ($context.TenantId -ne $TenantId.ToString()) {
        throw '连接的租户与指定的租户不一致。'
    }

    $properties = 'Id','DisplayName','UserPrincipalName','Mail','AccountEnabled','UsageLocation','AssignedLicenses'
    $users = @(Get-MgUser -All -Property $properties)
    if ($users.Count -eq 0) {
        throw '没有查询到用户。请检查连接和权限。'
    }
    $report = @($users | Select-Object Id,DisplayName,UserPrincipalName,Mail,AccountEnabled,UsageLocation,
        @{Name='AssignedSkuCount';Expression={ @($_.AssignedLicenses | Where-Object { $null -ne $_ }).Count }})

    $report | Export-Csv -Path $file -NoTypeInformation -Encoding utf8BOM -NoClobber
    Write-Host ("保存位置: {0} / 用户数: {1}" -f $file, $report.Count)
    $report | Select-Object -First 5 | Format-Table DisplayName,UserPrincipalName,AssignedSkuCount -AutoSize
}
finally {
    if ($connected) {
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
    }
}

在 PowerShell 中移动到包含文件的文件夹,然后按如下方式执行。文件路径需根据实际保存位置设置,在输入框中粘贴组织的租户 ID。

$targetTenant = Read-Host '操作目标租户 ID'
.\m365-users-report.ps1 -TenantId $targetTenant

成功后将显示 CSV 路径和用户数量,屏幕上将显示前五名用户。将文件的行数与屏幕上的用户数量进行对照,并将已知测试帐户的一名用户的显示名称、登录地址、使用位置和许可证与管理中心进行比较。AssignedSkuCount 是已分配的产品 SKU 数量,而不是单个服务数量或购买的许可证数。

即使 Mail 为空的用户也不会立即判定为查询失败。登录名 UserPrincipalName 与邮箱地址是不同的属性。即使 AccountEnabled 为 TRUE,由于许可证、策略或风险判断等原因,实际登录或服务使用可能会受限。不要仅凭此报表判断用户最近是否登录或正常执行工作。

若查询结果为空或 CSV 创建失败,则不计为完成。检查输出路径的权限、同名文件、文件锁定以及错误信息。报表反映的是执行时的查询结果,如果帐户有变更,请重新执行以进行对比,并根据组织的保存政策管理之前的文件。

许可证产品与用户分配查询

SKU(库存管理单元)是区分被授权产品的单位。SkuPartNumber 是产品识别字符串,SkuId 是用于 Graph 命令的产品GUID(全局唯一标识符)。更改用户分配时,请确保不要将订阅对象 ID 或服务计划 ID 替代 SKU ID。

由于整个 CSV 脚本在结束时已断开连接,执行以下单独命令时将重新连接。订阅 SKU 查询不仅需要读取权限,还需要任务支持的角色。请检查 订阅 SKU 查找 API 的角色和权限。

$tenantId = Read-Host '操作目标租户 ID'
Connect-MgGraph -TenantId $tenantId -Scopes 'User.Read.All','LicenseAssignment.Read.All' -ContextScope Process
Get-MgContext | Select-Object Account,TenantId,Scopes
Get-MgSubscribedSku -All | Select-Object SkuPartNumber,SkuId,CapabilityStatus,ConsumedUnits,@{Name='EnabledUnits';Expression={$_.PrepaidUnits.Enabled}}

$userId = Read-Host '要查询的用户 UPN 或对象 ID'
Get-MgUserLicenseDetail -UserId $userId -All | Select-Object SkuPartNumber,SkuId,ServicePlans

ConsumedUnits 指已消耗的许可数量,PrepaidUnits.Enabled 表示处于启用状态的许可单位数量。由于可能存在暂停或警告状态和产品状况,简单的差异不会立即被解读为所有产品的计费数量或剩余许可数量。实际购买与计费记录应按订阅、计费与许可证数量核对流程单独检查。

Get-MgUserLicenseDetail包括直接分配和从组继承的许可。仅凭这些结果无法判断分配原因。对于基于组的分配,请查看管理中心或额外的分配状态信息。你也可以在用户许可证的详细说明中查看委派权限和支持角色API。

用户·许可证更改操作需单独执行

以下示例是修改组织数据的操作。不要紧接只读报告一并执行这些变更,应在获得批准的测试用户和变更前记录准备好后,以所需权限重新连接。下面的 -WhatIf 是预览将要执行操作的选项,不是检查服务器所有有效性、权限和许可证条件的模拟实验。

创建云测试用户

仅对直接在云中管理的用户使用此方法。请先确认同步组织的帐户是否在源目录中创建。需要在租户中验证的域名,不重复的登录名,以及符合密码策略的临时密码。此操作与邀请外部协作来宾不同。根据 用户创建 API 的必填值和权限 ,检查最小创建权限 User.Create,并确认用户管理所需的角色和对象。

Disconnect-MgGraph
$tenantId = Read-Host '操作目标租户 ID'
Connect-MgGraph -TenantId $tenantId -Scopes 'User.Create','User.Read.All' -ContextScope Process
Get-MgContext | Select-Object Account,TenantId,Scopes

$upn = Read-Host '要创建的测试用户 UPN(已验证域)'
$nickname = Read-Host '邮件别名(例如 graph-test01)'
$securePassword = Read-Host '符合策略的临时密码' -AsSecureString
$params = @{
    AccountEnabled = $true
    DisplayName = 'Graph 测试用户'
    UserPrincipalName = $upn
    MailNickname = $nickname
    PasswordProfile = @{
        Password = [System.Net.NetworkCredential]::new('', $securePassword).Password
        ForceChangePasswordNextSignIn = $true
    }
}
try {
    New-MgUser -BodyParameter $params -WhatIf
}
finally {
    $params.PasswordProfile.Password = $null
    Remove-Variable securePassword
}

在确认输入和对象后,若要实际创建,请移除代码中的 -WhatIf 并重新执行。请不要将密码固定在命令行字符串或脚本文件中。SDK 请求需要明文密码,因此此操作不要使用调试输出或请求正文记录,临时密码也需通过组织批准的途径传递。

创建后,通过 Get-MgUser -UserId $upn -Property Id,DisplayName,UserPrincipalName,AccountEnabled 再次确认帐户。仅创建用户并不会完成应用许可、邮箱或加入组的全部操作。测试帐户的删除也需要像业务帐户删除一样进行数据审查。请在查看 离职员工帐户及数据交接流程 后,在管理中心审查并整理可处理及恢复的范围。

回收直接分配的许可证

回收前,请确认用户、产品、直接/组分配情况及数据保留条件。在此示例的 -RemoveLicenses 中,传递 SKU ID 字符串数组 ,不包括 @{SkuId=...} 格式的哈希表。当没有要添加的产品时,同时使用 -AddLicenses @()。请查看 Set-MgUserLicense 语法 。

Disconnect-MgGraph
$tenantId = Read-Host '操作目标租户 ID'
Connect-MgGraph -TenantId $tenantId -Scopes 'LicenseAssignment.ReadWrite.All' -ContextScope Process
Get-MgContext | Select-Object Account,TenantId,Scopes

$userId = Read-Host '要回收许可证的测试用户 UPN 或对象 ID'
$skuId = [Guid](Read-Host '已确认的待回收 SkuId')
Set-MgUserLicense -UserId $userId -AddLicenses @() -RemoveLicenses @($skuId.ToString()) -WhatIf

实际更改需在审批和确认对象后通过移除 -WhatIf 执行。对 LicenseAssignment.ReadWrite.All,需要同意以及 License Administrator 等支持角色。对于基于组的分配,不要仅通过用户直接回收命令处理,请检查原始的组分配。 许可分配 API 的权限、角色及请求格式 是标准。

更改后,请使用所需的读取权限重新连接,并对照许可证详情和管理中心。恢复操作需确认剩余许可数量、使用位置以及之前已停用的服务计划,并将其重新分配至原始配置。重新绑定许可证并不意味着已进行的数据删除会完全取消。回收前完成保存和交接非常重要。

检查 Windows 执行策略并进行恢复

执行策略是脚本和模块的执行条件。如果在 Windows 中发生阻止错误,请检查有效策略及按范围的值。

Get-ExecutionPolicy
Get-ExecutionPolicy -List

仅在组织策略允许且需要在当前用户范围内更改时,记录先前值之后再查看 RemoteSigned。如果已有允许设置,不要为了统一格式而更改。如组织组策略 MachinePolicy·UserPolicy 优先,请联系负责人,不要绕过。

$previousUserPolicy = Get-ExecutionPolicy -Scope CurrentUser
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Get-ExecutionPolicy -List

作业完成后,如需在同一会话中恢复原值,请按如下方式执行。如需在新窗口中恢复,请使用之前记录的实际值。RemoteSigned 并不表示信任所有从互联网获取的代码。请确认 执行策略的范围与优先级 。

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy $previousUserPolicy

常见错误与作业结束

症状 确认顺序
无法识别命令 确认正在运行的 PowerShell 版本和模块安装位置。Get-MgUser 位于 Microsoft.Graph.Users 中。使用 Get-Command 查找命令,并区分安装或导入错误。
403 或权限不足 确认连接帐户、租户、Scopes、管理员同意及该 API 的支持角色。不要随意添加完全写入权限。
找不到用户或对象 确认组织和 UPN、对象 ID、删除或同步状态。检查是否输入了其他租户的帐户名。
属性为空 区分是否通过 -Property 请求、该帐户是否有实际值、是否为需要额外权限的属性。
登录被阻止 记录错误代码·时间,并检查条件访问·多重身份验证·允许的认证方式以及登录日志。为了登录成功,不要关闭安全策略。

如果记录安装版本和命令的详细错误、请求ID·发生时间,将有助于重现问题和支持请求。在使用Graph PowerShell 错误诊断的方法时,也不要直接共享包含令牌·密码·个人信息的日志。

单个命令练习结束后,请断开连接并确认结果文件的存放位置。注销操作不同于撤销已执行帐户·许可变更的操作。

Disconnect-MgGraph