Compartilhe que você está se especializando

Eu sempre precisei gerar organograma da empresa, mas cada vez mais o método que eu utilizava foi se tornando mais difícil (ou raro), pois dependia de uma licença funcional do Microsoft Visio — e, cada vez mais, o(s) computador(es) que ainda tinham alguma licença eram PC’s antigos, com uma versão bem desatualizada. Sendo assim, cada vez mais existir o risco de vulnerabilidades na rede (por ter o computador antigo) ou de não conseguir mais gerar o organograma.

Foi então que decidi criar um script em PowerShell, capaz de gerar o organograma diretamente a partir do Active Directory (AD). A ideia é simples:

  • Você informa o nome do colaborador que “está no topo” do nível hierárquico que deseja visualizar;
  • O script consulta o AD local, identifica os colaboradores subordinados (depois os subordinados de cada um deles, assim por diante), construindo a cadeia hierárquica completa em segundos.
  • Em seguida, utiliza um “template” (um html, que eu já configurei o layout da forma que eu quero) e insere esse “array” de todos os membros e vinculos entre eles.

Enquanto o Visio usava o Outlook como base para essa estrutura, o meu script dispensa licenças pagas e trabalha diretamente com as informações do AD corporativo, garantindo dados sempre atualizados e consistentes. Eliminando a dependência de software legado.

Apesar de termos optado por utilizar o Google chart como padrão para o layout de organograma, vamos demonstrar aqui o uso do template mais agradável que testamos, com o BALKAN OrgChart JS (mas atenção: É necessário comprar uma licença para usar este componente, em nosso Github existem mais templates, com opções gratuítas para vocês escolher):

Abaixo o script PowerShell (altere as partes em cinza, conforme sua realidade):

# CUSTOM (CHANGE) THIS PART OF THE SCRIPT
#Default information (If is blank in some user, CHANGE)
$DefaultTelephone = "+55 (61) 98505-1070"
$DefaultTitle = "------------------"
$DefaultEmail = "equipe@nvlan.com.br"
# Avatar image (UTR with a Avatar image/icon)
$avatarURL = "https://openclipart.org/image/800px/307452"
$htmlFolder =  "D:\Generate Organizational chart"
$templateFile = "template5.html" #(could be template1.html to template5.html)
#-------------------------------------------------------------------------------------------
$global:OrgArray = @()
$global:UserId = 1

# Get the avatar image
$webClient = New-Object System.Net.WebClient
$imageBytes = $webClient.DownloadData($avatarURL)
$contentType = $webClient.ResponseHeaders["Content-Type"]
if (-not $contentType) {
    switch -regex ($url.ToLower()) {
        '\.jpg$|\.jpeg$' { $contentType = "image/jpeg" }
        '\.png$'         { $contentType = "image/png" }
        '\.gif$'         { $contentType = "image/gif" }
        '\.bmp$'         { $contentType = "image/bmp" }
        '\.webp$'        { $contentType = "image/webp" }
        default           { $contentType = "application/octet-stream" }
    }
}
# Convert image to Base64
$base64String = [System.Convert]::ToBase64String($imageBytes)
$DefaultIconBase64 = "data:$contentType;base64,$base64String"

function Order-OrgArray {
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [array]$OrgArray
    )
    process {
        $OrgArray | 
        Select-Object *, @{Name="OrderTitle";Expression={
            switch -regex ($_.Title.ToLower()) {
                '^diretor'     { 1; break }
                '^secretar'    { 2; break }
                '^gerente'     { 3; break }
                '^coordenador' { 4; break }
                '^estagi'      { 6; break }
                'jovem'        { 7; break }
                default        { 5 }
            }
        }} |
        Sort-Object PID, OrderTitle, Name
    }
}

function Get-ADOrgTopology {  
    [CmdletBinding()]
    param(  
        [Parameter(Mandatory = $true)]
        [string]$Manager,
        [int]$Level,
        [int]$ParentId
    )    
    $UserData = Get-ADUser $Manager -Properties DisplayName, manager, mail, telephoneNumber, title, thumbnailPhoto
    If(!$UserData) {
        Write-Host "User $Manager not found!"
    }
    Else {
        if (!$Level) { $Level = 1 }
        $name = $UserData.DisplayName
        $fullname = $UserData.Name
        $title = $UserData.Title
        #Set "tag" depending of the user title (change if you want)
        switch -Wildcard ($title) {
            "diretor*"      { $tag = "ceo" }
            "secret*"       { $tag = "assistant" }
            "gerente*"      { $tag = "gerente" }
            "coordenador*"  { $tag = "coordenador" }
            "estag*"        { $tag = "estagiario" }
            default         { $tag = "0" }
        }
        #Setting default valeus, if the user value is blank
        if (!$title) { $title = $DefaultTitle }
        $mail = $UserData.mail
        if (!$mail) { $mail = $DefaultEmail }
        $telephoneNumber = $UserData.telephoneNumber
        if (!$telephoneNumber) { $telephoneNumber = $DefaultTelephone }
        $thumbnailPhoto = $UserData.thumbnailPhoto
        If (!$thumbnailPhoto)
        {
            $thumbnailPhoto = "data:image/png;base64,$DefaultIconBase64"
        }
        Else
        {
            $thumbnailPhoto = [System.Convert]::ToBase64String($thumbnailPhoto)
            $thumbnailPhoto = "data:image/png;base64,$thumbnailPhoto"
        }
        # Store the current ID
        $CurrentId = $global:UserId
        # Add to array with ParentId ($null if the first node)
        $global:OrgArray += [PSCustomObject]@{
            ID             = $CurrentId
            PID            = $ParentId
            Name           = $name
            FullName       = $fullname
            Title          = $title
            Tag            = $tag
            Email          = $mail
            Telephone       = $telephoneNumber
            thumbnailPhoto = $thumbnailPhoto
        }
        $global:UserId++
        # Search the Team
        $DirectReports = Get-ADUser -Filter { manager -eq $Manager }
        $NewLevel = $Level + 1
        $DirectReports | Where-Object { $_.Enabled -eq $true } | ForEach-Object {
            # Set the current ID as ParentId for the Team
            Get-ADOrgTopology -Manager $_.DistinguishedName -Level $NewLevel -ParentId $CurrentId
        }
    }  
}
Clear-Host
$ManagerName = Read-Host "Insert the username in the top of you Organogram"
Get-ADOrgTopology -Manager $ManagerName

#Show (for test only)
#$OrgArray | Format-Table -AutoSize

If ($OrgArray) {
    $OrgArray = Order-OrgArray -OrgArray $OrgArray
    $templateFile = $htmlFolder +"\"+ $templateFile
    If (Test-Path $templateFile) {
        $Template_DefaultSite = Get-Content $templateFile
        foreach ($line in $OrgArray) {
            $string = "      { id: `"" + $line.ID +"`","
            If ($line.PID -ne 0) { $string += "pid: `""+$line.PID+"`","}
            If ($line.Tag -ne "0") { $string += " tags: [`""+$line.Tag+"`"],"}
            $string += " name: `""+ $line.Name +"`", Title: `""+ $line.Title +"`", Phone: `""+ $line.Telephone +"`", Email: `""+ $line.Email +"`", img: `""+ $line.thumbnailPhoto +"`"},"
            $Template_DefaultSite = $Template_DefaultSite.replace("//NodeList",$string +"`n//NodeList")
        }
        $Template_DefaultSite | Out-File -Encoding utf8 -FilePath "$htmlFolder\index.html" -Force
        Write-Host "Done"
    }
    Else {Write-Host "$templateFile not found!"}
}

Aqui, o arquivo de template (lembrando neste exemplo está o BALKAN OrgChart JS, que é necessário comprar uma licença para usar este componente, em nosso Github existem mais templates, com opções gratuítas para vocês escolher), para que você altere o script PowerShell para usar esse arquivo como base:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>OrgChart Exemplo</title>
  <script src="https://balkan.app/js/OrgChart.js"></script>
  <style>
    html, body {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
    #tree {
      width: 100%;
      height: 100vh;
    }
		
	.node.ceo			rect	{ fill: #339933; }
	.node.assistant 	rect	{ fill: #4CAFAA; }
	.node.gerente		rect	{ fill: #D80073; }
	.node.coordenador	rect	{ fill: #FFBF00; }
	.node.estagiario	rect	{ fill: #A3D9F4; }
	.node.QA			rect	{ fill: #FFFFFF; }
    .node.QA			text	{ fill: #F57C00; }
  </style>
</head>
<body>

<div id="tree"></div>

<script>

OrgChart.MIXED_LAYOUT_IF_NUMBER_OF_CHILDREN_IS_MORE_THEN = 1;

//REMOVE AS OPCOES DE OCULTAR E/OU EXPANDIR
OrgChart.templates.ana.plus = "";
OrgChart.templates.ana.minus = "";
  
// Alinhando todos os campos à direita
OrgChart.templates.ana.field_0 = '<text width="230" style="font-size: 10px;" fill="#ffffff" text-anchor="end" x="240" y="25">{val}</text>';
OrgChart.templates.ana.field_1 = '<text width="230" style="font-size: 10px;" fill="#ffffff" text-anchor="end" x="240" y="45">{val}</text>';
OrgChart.templates.ana.field_2 = '<text width="230" style="font-size: 11px;" fill="#ffffff" text-anchor="start" x="10" y="85">{val}</text>';
OrgChart.templates.ana.field_3 = '<text width="230" style="font-size: 11px;" fill="#ffffff" text-anchor="start" x="10" y="100">{val}</text>';

//Padrão do Template
OrgChart.templates.ana.node =
  '<rect x="0" y="0" height="115" width="250" fill="#1BA1E2" stroke-width="1" stroke="#aeaeae"></rect>';

  // Criando o gráfico
  var chart = new OrgChart(document.getElementById("tree"), {
    
    mouseScrool: OrgChart.action.none,
	//AJUSTA A TELA
	scaleInitial: OrgChart.match.boundary,
	layout: OrgChart.treeRightOffset,
	assistantSeparation: 50,
    tags: {
      department: { min: true },
	  "hidden": { template: "hiddenTemplate" }
    },
	searchDisplayField: 'name',
    searchFieldsWeight: {
        "name": 100, //percent
        "Title": 20 //percent
    },
    nodeBinding: {
      field_0: "Phone",
      field_1: "Email",
      field_2: "name",
      field_3: "Title",
      img_0: "img"
    },
	
  });

  //RETIRA A OPCAO DE PODER CLICAR (E EXPANDIR O USUARIO)
  chart.on('click', function (sender, args) {
	if (args.node.min) {
		sender.maximize(args.node.id);
	}
	else {
		sender.minimize(args.node.id);
	}
	//return false;
  });

// Carrega dados
chart.load([
//NodeList
]);
</script>

</body>
</html>

Fontes/Referências

NVLAN – Github

https://balkan.app/OrgChartJS/Pricing

Mais Informações

Esperamos ter te ajudado e estaremos sempre a disposição para mais informações.

Se você tem interesse em algum assunto específico, tem alguma dúvida, precisa de ajuda, ou quer sugerir um post, entre em contato conosco pelo e-mail equipe@nvlan.com.br.

NVLAN - Consultoria