Tenant Radar

Collecteur local

Un script lisible, exécuté chez l'admin.

Le SaaS ne conserve pas de token. L'admin exécute le collecteur localement, ouvre le JSON s'il veut, puis décide de l'importer.

0 token

Mode idéal pour PME prudentes et MSP.

Télécharger le collecteur PowerShell

Lecture seule, aucune modification du tenant, JSON vérifiable avant import. Le script installe/import les modules Microsoft Graph nécessaires.

Télécharger
param(
    [string]$OutputPath = (Join-Path ([Environment]::GetFolderPath("Desktop")) "tenantradar-export.json"),
    [switch]$SkipModuleInstall
)

$ErrorActionPreference = "Stop"

if ($PSVersionTable.PSEdition -eq "Desktop") {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}

Write-Host "Tenant Radar Collector" -ForegroundColor Cyan
Write-Host "Lecture seule. Aucune modification ne sera effectuée." -ForegroundColor Yellow
Write-Host "Export cible : $OutputPath" -ForegroundColor DarkGray

$requiredModules = @(
    "Microsoft.Graph.Authentication",
    "Microsoft.Graph.Users",
    "Microsoft.Graph.Identity.DirectoryManagement"
)

$missingModules = @()
foreach ($moduleName in $requiredModules) {
    if (-not (Get-Module -ListAvailable -Name $moduleName)) {
        $missingModules += $moduleName
    }
}

if ($missingModules.Count -gt 0) {
    Write-Host "Modules Microsoft Graph manquants : $($missingModules -join ', ')" -ForegroundColor Yellow

    if ($SkipModuleInstall) {
        throw "Modules manquants. Relancez sans -SkipModuleInstall ou installez-les avec Install-Module."
    }

    $install = Read-Host "Installer les modules manquants pour l'utilisateur courant ? (O/N)"
    if ($install -notin @("O", "o", "Oui", "oui", "Y", "y", "Yes", "yes")) {
        throw "Installation annulée. Le collecteur ne peut pas continuer sans ces modules."
    }

    if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
        Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force
    }

    foreach ($moduleName in $missingModules) {
        Write-Host "Installation de $moduleName..." -ForegroundColor Cyan
        Install-Module -Name $moduleName -Scope CurrentUser -Repository PSGallery -Force -AllowClobber
    }
}

foreach ($moduleName in $requiredModules) {
    Import-Module $moduleName -ErrorAction Stop
}

$requiredCommands = @(
    "Connect-MgGraph",
    "Get-MgContext",
    "Get-MgOrganization",
    "Get-MgDomain",
    "Get-MgUser",
    "Get-MgSubscribedSku",
    "Get-MgDirectoryRole",
    "Get-MgDirectoryRoleMember",
    "Invoke-MgGraphRequest"
)

$missingCommands = @()
foreach ($commandName in $requiredCommands) {
    if (-not (Get-Command $commandName -ErrorAction SilentlyContinue)) {
        $missingCommands += $commandName
    }
}

if ($missingCommands.Count -gt 0) {
    throw "Commandes Graph indisponibles après import : $($missingCommands -join ', '). Vérifiez l'installation du SDK Microsoft Graph PowerShell."
}

$scopes = @(
    "User.Read.All",
    "Directory.Read.All",
    "Group.Read.All",
    "Domain.Read.All",
    "Organization.Read.All",
    "LicenseAssignment.Read.All",
    "RoleManagement.Read.Directory",
    "AuditLog.Read.All",
    "UserAuthenticationMethod.Read.All"
)

try {
    Connect-MgGraph -Scopes $scopes -NoWelcome
} catch {
    Connect-MgGraph -Scopes $scopes
}

$context = Get-MgContext
if (-not $context) {
    throw "Connexion Microsoft Graph impossible."
}

Write-Host "Connecté au tenant : $($context.TenantId)" -ForegroundColor Green

$organization = Get-MgOrganization
$domains = Get-MgDomain -All

$users = Get-MgUser -All -Property "id,displayName,userPrincipalName,mail,accountEnabled,userType,createdDateTime,assignedLicenses,signInActivity,department,jobTitle,companyName"

$skus = Get-MgSubscribedSku -All
$roles = Get-MgDirectoryRole -All
$roleMembers = @()

foreach ($role in $roles) {
    try {
        $members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id -All
        foreach ($member in $members) {
            $roleMembers += [PSCustomObject]@{
                roleId = $role.Id
                roleName = $role.DisplayName
                memberId = $member.Id
                memberType = $member.AdditionalProperties.'@odata.type'
                displayName = $member.AdditionalProperties.displayName
                userPrincipalName = $member.AdditionalProperties.userPrincipalName
            }
        }
    } catch {
        Write-Warning "Impossible de lire les membres du rôle $($role.DisplayName)"
    }
}

$guests = $users | Where-Object { $_.UserType -eq "Guest" }

$mfaRegistration = @()
try {
    $mfaRegistration = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/reports/authenticationMethods/userRegistrationDetails"
} catch {
    Write-Warning "Impossible de récupérer les détails MFA. Vérifiez les permissions/licences. Le reste de l'export continue."
}

$export = [PSCustomObject]@{
    generator = "Tenant Radar Collector"
    generatorVersion = "0.2.0"
    generatedAt = (Get-Date).ToUniversalTime().ToString("o")
    tenantId = $context.TenantId
    account = $context.Account
    organization = $organization
    domains = $domains
    users = $users
    subscribedSkus = $skus
    directoryRoles = $roles
    roleMembers = $roleMembers
    guests = $guests
    mfaRegistration = $mfaRegistration
}

$folder = Split-Path -Parent $OutputPath
if ($folder -and -not (Test-Path $folder)) {
    New-Item -ItemType Directory -Path $folder -Force | Out-Null
}

$export | ConvertTo-Json -Depth 10 | Out-File -FilePath $OutputPath -Encoding utf8

Write-Host "Export terminé : $OutputPath" -ForegroundColor Green
Write-Host "Vous pouvez ouvrir le fichier avant import dans Tenant Radar." -ForegroundColor Green