Pular para o conteúdo principal

Auditoria no Azure AD: o risco oculto das caixas compartilhadas

Durante o planejamento de uma auditoria de contas no Azure AD, me deparei com um desafio importante: ao listar usuários Cloud-Only, percebi que caixas compartilhadas do Exchange Online também são exibidas como contas de usuário comuns no diretório.

O problema: Shared Mailboxes que "parecem" usuários

Quando criamos uma caixa compartilhada (Shared Mailbox) no Microsoft 365, o sistema cria automaticamente uma conta de usuário associada, com UPN e tudo. Mesmo após a conversão da caixa para o tipo "compartilhada", essa conta permanece visível no Azure AD e continua se comportando como uma conta Cloud-Only.

Ou seja, se você estiver rodando um script ou fazendo uma auditoria manual para identificar contas inativas ou que devem ser excluídas, corre o risco de excluir acidentalmente uma caixa de e-mail ativa e compartilhada com outros times.

A solução: auditoria com checagem de tipo de caixa

Para evitar esse tipo de erro, desenvolvi um script em PowerShell que:

  • Identifica apenas contas Cloud-Only no Azure AD;

  • Coleta dados como nome, UPN, cargo, departamento e último login;

  • Verifica no Exchange Online se a conta é uma Shared Mailbox;

  • Exporta todas essas informações para um arquivo .CSV completo.

Com isso, conseguimos diferenciar claramente:

  • Contas de usuário reais e inativas, que podem ser removidas com segurança;

  • Contas associadas a caixas compartilhadas, que devem ser mantidas.

O Script

Aqui está o script criado para essa auditoria (com uso dos módulos Microsoft.Graph.Users e ExchangeOnlineManagement):

==================================================================

#Requires -Modules Microsoft.Graph.Users, ExchangeOnlineManagement


<#

.SYNOPSIS

    Audits Azure AD users, focusing on Cloud-Only accounts, retrieves specific properties including last sign-in time,

    identifies shared mailboxes via Exchange Online, and exports the results to a CSV file.


.DESCRIPTION

    This script connects to Microsoft Graph and Exchange Online to gather user information.

    It filters for users created directly in Azure AD (Cloud-Only) by checking the 'onPremisesSyncEnabled' property.

    For each Cloud-Only user, it retrieves DisplayName, UserPrincipalName, Department, JobTitle, and LastSignInDateTime.

    It then checks Exchange Online to determine if the UserPrincipalName corresponds to a Shared Mailbox.

    Finally, it compiles this information and exports it to a CSV file named 'AzureAD_Cloud_User_Audit.csv' in the script's execution directory.


.NOTES

    Author: Lucas Silva

    Date: 2025-05-27


    Prerequisites:

    - PowerShell 5.1 or later.

    - Microsoft Graph PowerShell SDK (Modules: Microsoft.Graph.Users).

      Install using: Install-Module Microsoft.Graph.Users -Scope CurrentUser

    - Exchange Online Management module (V3 or later recommended).

      Install using: Install-Module ExchangeOnlineManagement -Scope CurrentUser


    Permissions Required:

    - Microsoft Graph: User.Read.All (at least) for reading user properties and sign-in activity.

    - Exchange Online: View-Only Recipients or higher role for Get-EXORecipient.


    Execution:

    1. Save the script as a .ps1 file (e.g., Audit-CloudUsers.ps1).

    2. Open PowerShell as an administrator (recommended for first-time module installation if needed).

    3. Navigate to the directory where you saved the script.

    4. Run the script: .\Audit-CloudUsers.ps1

    5. You will be prompted to authenticate to Microsoft Graph and Exchange Online.


.EXAMPLE

    .\Audit-CloudUsers.ps1

    Runs the script, prompts for authentication, performs the audit, and creates 'AzureAD_Cloud_User_Audit.csv'.

#>


param (

    [Parameter(Mandatory=$false)]

    [string]$OutputPath = ".\AzureAD_Cloud_User_Audit.csv"

)


# --- Script Start --- 


Write-Host "Iniciando auditoria de usuários Cloud-Only no Azure AD..." -ForegroundColor Cyan


#region Connect to Services


Write-Host "Verificando e conectando aos serviços necessários (Microsoft Graph e Exchange Online)..." -ForegroundColor Yellow


# Check and Import Modules

try {

    Import-Module Microsoft.Graph.Users -ErrorAction Stop

    Import-Module ExchangeOnlineManagement -ErrorAction Stop

    Write-Host "Módulos necessários carregados com sucesso." -ForegroundColor Green

} catch {

    Write-Error "Erro ao carregar módulos necessários (Microsoft.Graph.Users, ExchangeOnlineManagement). Certifique-se de que estão instalados (Install-Module Microsoft.Graph.Users; Install-Module ExchangeOnlineManagement). Detalhes: $($_.Exception.Message)"

    # Exit if modules can't be loaded

    return 

}


# Connect to Microsoft Graph

try {

    # Check existing connection

    $graphConnection = Get-MgContext -ErrorAction SilentlyContinue

    if (-not $graphConnection) {

        Write-Host "Conectando ao Microsoft Graph..." 

        # Define required scopes

        $scopes = @("User.Read.All", "AuditLog.Read.All") # AuditLog.Read.All needed for signInActivity

        Connect-MgGraph -Scopes $scopes

        Write-Host "Conectado ao Microsoft Graph com sucesso." -ForegroundColor Green

    } else {

        Write-Host "Já conectado ao Microsoft Graph." -ForegroundColor Green

    }

} catch {

    Write-Error "Falha ao conectar ao Microsoft Graph. Verifique permissões e conectividade. Detalhes: $($_.Exception.Message)"

    return

}


# Connect to Exchange Online

try {

    # Check existing connection

    $exoConnection = Get-ConnectionInformation -ErrorAction SilentlyContinue

    if (-not ($exoConnection | Where-Object { $_.AppName -eq 'ExchangeOnline' -and $_.ConnectionState -eq 'Connected' })) {

        Write-Host "Conectando ao Exchange Online..."

        Connect-ExchangeOnline -ShowBanner:$false

        Write-Host "Conectado ao Exchange Online com sucesso." -ForegroundColor Green

    } else {

        Write-Host "Já conectado ao Exchange Online." -ForegroundColor Green

    }

} catch {

    Write-Error "Falha ao conectar ao Exchange Online. Verifique permissões e conectividade. Detalhes: $($_.Exception.Message)"

    return

}


#endregion


#region Fetch and Process Users


Write-Host "Buscando usuários Cloud-Only no Azure AD..." -ForegroundColor Yellow


$allUsers = @()

try {

    # Get users - Filter for members, exclude guests, and check onPremisesSyncEnabled

    # Select necessary properties including signInActivity for LastSignInDateTime

    # Note: Filtering directly on onPremisesSyncEnabled can be inconsistent. We filter later.

    # Note: signInActivity requires Azure AD Premium P1 or P2 license.

    $users = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, Department, JobTitle, UserType, onPremisesSyncEnabled, signInActivity 

        -Filter "UserType eq 'Member'"


    Write-Host "Encontrados $($users.Count) usuários do tipo 'Member'. Filtrando por Cloud-Only e processando..."


    $ProgressPreference = 'SilentlyContinue' # Suppress progress bar for Get-EXORecipient inside loop

    $i = 0

    foreach ($user in $users) {

        $i++

        Write-Progress -Activity "Processando Usuários" -Status "Verificando Usuário $i de $($users.Count): $($user.UserPrincipalName)" -PercentComplete (($i / $users.Count) * 100)


        # Filter for Cloud-Only: onPremisesSyncEnabled is null or explicitly false

        if ($null -eq $user.onPremisesSyncEnabled -or $user.onPremisesSyncEnabled -eq $false) {

            

            # Check if it's a Shared Mailbox in Exchange Online

            $isSharedMailbox = "Não"

            try {

                $recipient = Get-EXORecipient -Identity $user.UserPrincipalName -ErrorAction SilentlyContinue

                if ($recipient -ne $null -and $recipient.RecipientTypeDetails -eq "SharedMailbox") {

                    $isSharedMailbox = "Sim"

                }

            } catch {

                # Handle potential errors if user not found in EXO or other issues

                Write-Warning "Não foi possível verificar o status da caixa de correio para $($user.UserPrincipalName) no Exchange Online. Detalhes: $($_.Exception.Message)"

            }


            # Get Last Sign-In DateTime (Handle potential null value)

            $lastSignIn = if ($user.SignInActivity -ne $null) { $user.SignInActivity.LastSignInDateTime } else { $null }


            # Create custom object with desired properties

            $userData = [PSCustomObject]@{ 

                Nome                = $user.DisplayName

                EnderecoDeEmail     = $user.UserPrincipalName

                Departamento        = $user.Department

                Cargo               = $user.JobTitle

                UltimoLogin         = $lastSignIn

                EhCaixaCompartilhada = $isSharedMailbox

            }

            $allUsers += $userData

        }

    }

    $ProgressPreference = 'Continue' # Restore progress bar preference


} catch {

    Write-Error "Erro ao buscar ou processar usuários do Azure AD. Detalhes: $($_.Exception.Message)"

    # Attempt to disconnect before exiting

    Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue

    Disconnect-MgGraph -ErrorAction SilentlyContinue

    return

}


Write-Host "Processamento de usuários concluído. Encontrados $($allUsers.Count) usuários Cloud-Only." -ForegroundColor Green


#endregion


#region Export to CSV


if ($allUsers.Count -gt 0) {

    Write-Host "Exportando resultados para CSV: $OutputPath" -ForegroundColor Yellow

    try {

        $allUsers | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -Delimiter ';'

        Write-Host "Exportação para CSV concluída com sucesso: $OutputPath" -ForegroundColor Green

    } catch {

        Write-Error "Falha ao exportar dados para CSV. Verifique o caminho e permissões. Detalhes: $($_.Exception.Message)"

    }

} else {

    Write-Host "Nenhum usuário Cloud-Only encontrado para exportar." -ForegroundColor Yellow

}


#endregion


#region Disconnect Services


Write-Host "Desconectando dos serviços..." -ForegroundColor Yellow

try {

    Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue

    # Disconnect-MgGraph # Optional: Keep Graph connection if running multiple scripts

    Write-Host "Desconexão concluída (Exchange Online)." -ForegroundColor Green

} catch {

    Write-Warning "Ocorreu um problema ao desconectar dos serviços. Detalhes: $($_.Exception.Message)"

}


#endregion


Write-Host "Auditoria concluída." -ForegroundColor Cyan


# --- Script End ---

==================================================================

Resultado

Ao final da execução, obtemos um relatório claro e seguro com os seguintes campos:


Esse nível de detalhe garantiu uma auditoria segura, sem riscos de exclusão de caixas essenciais para a operação da empresa.

Conclusão

Automatizar a auditoria de contas no Azure AD é fundamental — mas é preciso fazer isso com inteligência. O uso combinado de Microsoft Graph e Exchange Online PowerShell permite análises mais seguras, eficazes e alinhadas com a realidade do ambiente Microsoft 365.

Fica aqui o alerta: caixas compartilhadas criadas a partir de contas Cloud-Only não devem ser tratadas como contas inativas comuns. Identifique, classifique e só então decida se deve excluir ou manter.


Comentários

Postagens mais visitadas deste blog

Otimize Custos e Segurança: Auditoria Fácil de Usuários e Licenças no Azure DevOps

Por que criei este script de auditoria para o Azure DevOps? Em meu trabalho diário com equipes que utilizam o Azure DevOps, percebi que muitos gestores e administradores enfrentam um desafio comum: controlar quem realmente está usando o ambiente, qual tipo de licença está ativa e, principalmente, quando foi a última vez que cada usuário acessou a plataforma. Em ambientes com múltiplas organizações e centenas de usuários, controlar quem realmente utiliza repositórios, pipelines e boards pode gerar uma economia significativa. Tudo isso de forma segura, sem modificar nada no ambiente, garantindo que a auditoria seja apenas informativa. Essa auditoria é essencial para manter a segurança, controlar custos e garantir conformidade. Pensando nisso, criei um script de auditoria que: O que o script verifica? Para cada usuário com licença Basic , o script checa: Commits em repositórios Git Execução de builds no Azure Pipelines Atividades nos Boards (work items modificados) Se o...

Auditoria de Usuários Ativos no Azure AD com Último Login e Tipo de Conta

Durante a gestão de ambientes em nuvem, é comum nos depararmos com contas de usuários que permanecem ativas, mesmo sem uso recente. Isso impacta diretamente no consumo de licenças e na postura de segurança da organização. Pensando nisso, desenvolvi um script em PowerShell utilizando o Microsoft Graph para facilitar auditorias de contas ativas no Azure AD , trazendo informações importantes como: Nome do usuário E-mail (UPN) Departamento e cargo Data de criação da conta Último login registrado Tipo de conta (nuvem ou sincronizada com AD local) O que o script faz? - Conecta-se ao Microsoft Graph com as permissões necessárias - Busca todos os usuários internos ativos (tipo "Member") - Verifica a última data de login registrada - Informa se a conta é nuvem (Cloud Only) ou sincronizada (On-Premises) - Exporta os dados para um relatório CSV ================================================================ # Script para auditoria de usuários ativos no Azure...