Pular para o conteúdo principal

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:

  1. Commits em repositórios Git

  2. Execução de builds no Azure Pipelines

  3. Atividades nos Boards (work items modificados)

Se o usuário não tiver usado nenhum desses recursos nos últimos 30 dias, o script sugere o rebaixamento para a licença gratuita Stakeholder.

Pré-requisitos

  • Permissões adequadas para gerar Personal Access Token (PAT) com escopo de leitura em User Entitlements

  • PowerShell instalado na máquina

  • Módulo ImportExcel instalado (para exportação em Excel)

1. Gerar o Personal Access Token (PAT)

Para que o script funcione, você precisa de um Personal Access Token (PAT) com permissão de leitura para acessar as informações dos usuários da sua organização no Azure DevOps.

Acesse o Azure DevOps e faça login:
No canto superior direito, clique na sua foto de perfil e depois em “Security” (Segurança).
Em “Personal Access Tokens”, clique em “+ New Token” (Novo Token).

Preencha as informações:
Name: Auditoria DevOps
Organization: Selecione a organização desejada
Expiration: Escolha um prazo seguro (ex: 30 ou 90 dias)
Scopes (Escopos):
Marque apenas: Read & execute em "User Entitlements"
(Dentro da opção "Organization")

Clique em “Create”.
⚠️ Copie o PAT imediatamente e armazene em local seguro. Você não poderá visualizá-lo novamente.

Recomendações de segurança:

  • Use tokens com o menor escopo necessário.

  • Nunca compartilhe seu PAT.

  • Se possível, crie um token apenas para auditorias, com expiração curta.

  • Você pode gerar um token para cada organização se não tiver acesso global com o mesmo PAT.

2. Script PowerShell para auditoria

⚠️ Importante: O script não altera nada no ambiente — ele apenas lê e gera um relatório informativo.

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

# CONFIGURAÇÕES

$organizations = @("contoso", "outraOrg")  # substitua pelos nomes de suas organizações

$pat = "SEU_PAT_AQUI"                      # Token com acesso necessário


$authHeader = @{

    Authorization = ("Basic {0}" -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat")))

}


# Define a partir de quando considerar atividade (últimos 30 dias)

$cutoff = (Get-Date).AddDays(-30)


function Invoke-AzDo {

    param($Uri)

    try {

        return Invoke-RestMethod -Uri $Uri -Headers $authHeader -ErrorAction Stop

    } catch {

        return $null

    }

}


$report = @()


foreach ($org in $organizations) {

    Write-Host "n Checando organização: $org" -ForegroundColor Cyan


    # Obtem usuários Basic

    $uriUsers = "https://vsaex.dev.azure.com/$org/_apis/userentitlements?top=1000&api-version=7.1-preview.1"

    $resUsers = Invoke-AzDo $uriUsers

    if (-not $resUsers) { Write-Warning "Erro ao obter usuários de $org"; continue }


    $basicUsers = $resUsers.value | Where-Object { $_.accessLevel.licenseDisplayName -eq "Basic" }


    foreach ($u in $basicUsers) {

        $email = $u.user.mailAddress

        $name = $u.user.displayName


        Write-Host "→ Usuário: $name"


        $active = $false


        # 1. Verifica commits

        $uriRepos = "https://dev.azure.com/$org/_apis/projects?api-version=7.1"

        $projects = Invoke-AzDo $uriRepos

        foreach ($p in $projects.value) {

            $uriGit = "https://dev.azure.com/$org/$($p.name)/_apis/git/repositories?api-version=7.1"

            $repos = Invoke-AzDo $uriGit

            foreach ($r in $repos.value) {

                $uriCommits = "https://dev.azure.com/$org/$($p.name)/_apis/git/repositories/$($r.id)/commits?searchCriteria.author=$email&$top=1&api-version=7.1"

                $comm = Invoke-AzDo $uriCommits

                if ($comm.value -and [datetime]$comm.value[0].author.date -ge $cutoff) { $active = $true }

                if ($active) { break }

            }

            if ($active) { break }

        }


        # 2. Verifica builds

        if (-not $active) {

            foreach ($p in $projects.value) {

                $uriBuilds = "https://dev.azure.com/$org/$($p.name)/_apis/build/builds?requestedFor=$email&$top=1&api-version=7.1"

                $b = Invoke-AzDo $uriBuilds

                if ($b.value -and [datetime]$b.value[0].queueTime -ge $cutoff) {

                    $active = $true; break

                }

            }

        }


        # 3. Verifica boards (work items modifications)

        if (-not $active) {

            foreach ($p in $projects.value) {

                $uriWork = "https://dev.azure.com/$org/$($p.name)/_apis/wit/activitylogs?user=$email&$top=1&startDateTime=$($cutoff.ToString("o"))&api-version=7.1"

                $w = Invoke-AzDo $uriWork

                if ($w.value) {

                    $active = $true; break

                }

            }

        }


        # Sugestão de ação

        $action = if ($active) { "Manter Basic" } else { "Rebaixar para Stakeholder" }


        $report += [PSCustomObject]@{

            Organizacao       = $org

            Nome              = $name

            Email             = $email

            Licenca           = "Basic"

            AtivoUltimos30d   = $active

            AcaoSugerida      = $action

        }

    }

}


# Exporta resultado

$out = Join-Path (Get-Location).Path "Auditoria_Basic_Atividade.csv"

$report | Sort-Object Organizacao, Nome | Export-Csv -Path $out -NoTypeInformation -Encoding UTF8


Write-Host "n Relatório gerado: $out" -ForegroundColor Green

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

Exemplo de Resultado

Abaixo, um exemplo real do relatório gerado pelo script:


Neste cenário, enquanto João segue ativo e justifica sua licença Basic, a usuária Ana não apresentou nenhuma atividade relevante nos últimos 30 dias — o que sugere a possibilidade de rebaixamento para Stakeholder, liberando a licença para outro colaborador ou reduzindo custos.

Por que isso importa?

Em grandes organizações, é comum que licenças fiquem esquecidas — principalmente quando há rotatividade, mudanças de time ou paralisação de projetos.

Licenças Basic são pagas. Já a Stakeholder é 100% gratuita. Ou seja, identificar esses usuários inativos permite economizar de forma inteligente, sem prejudicar a produtividade.

Ao repetir essa auditoria periodicamente, você terá controle total e proativo sobre o uso real das licenças no Azure DevOps.






 

Comentários

Postagens mais visitadas deste blog

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...

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: Identi...