GitHub Icon Image
GitHub

Export Microsoft 365 Active Users to CSV Using Microsoft Graph (Cross-Platform)

Summary

This PowerShell script exports Microsoft 365 users — including properties such as Display Name, UPN, Department, Job Title, Account Status, and Manager Email — into a CSV file using the Microsoft Graph API.

It automatically ensures that the Microsoft.Graph module is installed, connects securely using interactive or device-code authentication, and retrieves enabled users by default (with optional switches for guests, disabled accounts, and manager lookups).

The script is fully optimized for macOS and Linux, performing a safe write-access check on the Graph cache directory (~/.mg) to prevent permission issues caused by previous sudo runs. If you are running on Windows, that permission check can be safely skipped or removed since Windows manages Graph credentials differently.

  • Microsoft Graph PowerShell

<#
.SYNOPSIS
Export active Microsoft 365 users to CSV using Microsoft Graph.

.DESCRIPTION
Wraps the script in an advanced function that ensures the Microsoft.Graph module is available,
connects using device code (with a macOS-friendly context scope), queries enabled users by default,
optionally looks up manager email, and writes a CSV. Includes a fallback to interactive login if
device code fails on macOS environments.

.EXAMPLE
Export-M365ActiveUsers
Exports enabled Member users to Desktop/M365_ActiveUsers.csv using device-code authentication.

.EXAMPLE
Export-M365ActiveUsers -IncludeGuests -Path "/tmp/users.csv"
Includes Guests in addition to Members and writes to /tmp/users.csv
#>
function Export-M365ActiveUsers {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter()]
        [string]$Path =  'M365_ActiveUsers.csv',

        [Parameter()]
        [string]$TenantId = $(Read-Host "Enter your Azure AD Tenant ID"),

        [Parameter()]
        [string[]]$Scopes = @('User.Read.All','Directory.Read.All'),

        [Parameter()]
        [switch]$IncludeGuests,

        [Parameter()]
        [switch]$IncludeDisabled,

        [Parameter()]
        [switch]$IncludeManager = $true,

        [Parameter()]
        [switch]$DeviceCode = $true,

        [Parameter()]
        [switch]$Force
    )

    # 1) Ensure Graph module exists
    $moduleName = 'Microsoft.Graph'
    if (-not (Get-Module -ListAvailable -Name $moduleName)) {
        Write-Host "Microsoft Graph module not found. Installing to CurrentUser scope..." -ForegroundColor Yellow
        try {
            Install-Module -Name $moduleName -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop | Out-Null
        } catch {
            throw "Failed to install module '$moduleName': $($_.Exception.Message)"
        }
    }
    Import-Module -Name $moduleName -ErrorAction Stop

    # Preflight check: warn if ~/.mg is owned by root (from a previous sudo run)
   $platform = [System.Environment]::OSVersion.Platform
if ($platform -eq 'Unix' -or $platform -eq 'MacOSX') {
    $mgDir = Join-Path $HOME '.mg'
    if (Test-Path $mgDir) {
        try {
            $testFile = Join-Path $mgDir "test_write_$(Get-Random).tmp"
            [void][System.IO.File]::WriteAllText($testFile, 'test')
            Remove-Item $testFile -Force -ErrorAction SilentlyContinue
        } catch {
            Write-Warning "Cannot write to $mgDir — likely owned by root from a previous sudo run."
            Write-Host "Fix with: sudo chown -R `$USER:staff ~/.mg && sudo chmod 700 ~/.mg" -ForegroundColor Yellow
            Write-Host "Or remove it: sudo rm -rf ~/.mg`n" -ForegroundColor Yellow
            throw "Access denied to Microsoft Graph cache directory. See instructions above."
        }
    }
} else {
    Write-Verbose "Windows detected — skipping ~/.mg permission check."
}

    # 2) Connect to Graph
    Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null

    Write-Host "`n🔐 Connecting to Microsoft Graph..." -ForegroundColor Cyan
    Write-Host "A browser window will open for authentication.`n" -ForegroundColor Yellow

    try {
        # Use interactive browser login - more reliable on macOS than device code
        # ContextScope Process keeps tokens in memory only (not persisted to disk)
        Connect-MgGraph -Scopes $Scopes -TenantId $TenantId -ContextScope Process -ErrorAction Stop
    } catch {
        $msg = $_.Exception.Message
        Write-Error "Failed to connect to Microsoft Graph: $msg"
        Write-Host "`n💡 Troubleshooting:" -ForegroundColor Yellow
        Write-Host "   1. Make sure you complete the browser sign-in" -ForegroundColor Yellow
        Write-Host "   2. If the browser doesn't open, check for popup blockers" -ForegroundColor Yellow
        Write-Host "   3. Close all browser windows and try again`n" -ForegroundColor Yellow
        throw
    }

    $context = Get-MgContext
    if (-not $context) { throw "Authentication context not available after login." }
    Write-Host "✅ Connected as: $($context.Account)" -ForegroundColor Green

    # 3) Query users
    $filters = @()
    if (-not $IncludeDisabled) { $filters += "accountEnabled eq true" }
    if (-not $IncludeGuests) { $filters += "userType eq 'Member'" }
    $filterStr = $filters -join ' and '

    $props = @('Id','DisplayName','UserPrincipalName','Department','JobTitle','AccountEnabled')
    if ([string]::IsNullOrWhiteSpace($filterStr)) {
        $users = Get-MgUser -All -Property $props
    } else {
        $users = Get-MgUser -All -Filter $filterStr -Property $props
    }

    # 4) Project output
    $export = @()
    foreach ($u in $users) {
        $mgr = $null
        if ($IncludeManager) {
            try {
                $mgrObj = Get-MgUserManager -UserId $u.Id -ErrorAction SilentlyContinue
                if ($mgrObj) { $mgr = $mgrObj.AdditionalProperties.mail }
            } catch {}
        }
        $export += [PSCustomObject]@{
            EmployeeId     = $u.Id
            EmployeeName   = $u.DisplayName
            Email          = $u.UserPrincipalName
            Department     = $u.Department
            JobTitle       = $u.JobTitle
            ManagerEmail   = $mgr
            AccountEnabled = $u.AccountEnabled
        }
    }

    # 5) Export
    if ($PSCmdlet.ShouldProcess($Path, 'Export users to CSV')) {
        $export | Export-Csv -Path $Path -NoTypeInformation -Encoding UTF8 -Force:$Force
        Write-Host "📄 Export complete. File saved to $Path" -ForegroundColor Cyan
    }

    return $export
}

# Run with defaults if executed directly
Export-M365ActiveUsers | Out-Null

Check out the Microsoft Graph PowerShell SDK to learn more at: https://learn.microsoft.com/graph/powershell/get-started

Contributors

Author(s)
Divya Akula

Disclaimer

THESE SAMPLES ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

Back to top Script Samples
Generated by DocFX with Material UI