This commit is contained in:
João Pedro Toledo Goncalves 2025-10-15 14:39:49 -03:00
parent 48b2c46063
commit 5ccc8653f6
6 changed files with 606 additions and 421 deletions

56
.gitignore vendored Normal file
View File

@ -0,0 +1,56 @@
# ===================================================================
# Arquivo .gitignore para Projeto de Automação PowerShell
# Data: 15 de outubro de 2025
# Contexto: Ignora arquivos temporários, logs, módulos e configurações locais.
# ===================================================================
# --- Arquivos de Log e Saída ---
# Ignora logs gerados pelos scripts e relatórios de testes
*.log
*.log.*
TestResults/
*.trx
*.xml
output/
out/
# --- Configurações de Editores ---
# Visual Studio Code
.vscode/*
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/settings.json
# --- Dependências e Módulos do PowerShell ---
# Ignora a pasta de módulos que pode ser instalada localmente
/modules/
/PSDepend/
/Pester/
# --- Arquivos do Sistema Operacional ---
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Linux
.directory
# --- Arquivos Sensíveis ---
# NUNCA comite credenciais, chaves ou arquivos de ambiente
*.env
*.pem
*.key
credentials.xml
*cred*
*secret*
# --- Outros ---
# Arquivos de histórico do PowerShell
*History.txt

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use o IntelliSense para saber mais sobre os atributos possíveis.
// Focalizar para exibir as descrições dos atributos existentes.
// Para obter mais informações, acesse: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "PowerShell: Launch Current File",
"type": "PowerShell",
"request": "launch",
"script": "${file}",
"args": []
}
]
}

175
README.md
View File

@ -1,3 +1,5 @@
Plaintext
# Plataforma Unificada de Trabalho Digital # Plataforma Unificada de Trabalho Digital
![Status](https://img.shields.io/badge/status-em%20desenvolvimento-yellowgreen) ![Status](https://img.shields.io/badge/status-em%20desenvolvimento-yellowgreen)
@ -24,121 +26,132 @@ Conjunto de scripts e aplicações para automação de tarefas de gestão de ide
## 🌟 Visão Geral do Projeto ## 🌟 Visão Geral do Projeto
Este projeto detalha a criação de uma **Plataforma Unificada de Trabalho Digital**, um investimento estratégico projetado para **reduzir custos operacionais**, **aumentar a segurança e a conformidade (LGPD)** e **melhorar a agilidade organizacional**. Este projeto detalha a criação de uma **Plataforma Unificada de Trabalho Digital**, um portal de autoatendimento (self-service) e automação projetado para modernizar e otimizar a gestão de identidades e acessos. A plataforma visa reduzir a carga de trabalho do time de TI, empoderar os usuários, aumentar a segurança e garantir a conformidade com a LGPD através de processos automatizados e auditáveis.
O objetivo é transformar processos manuais de gestão de identidades (criação de usuários, resets de senha, gestão de grupos, desligamentos) em um fluxo de trabalho automatizado, seguro e eficiente, orquestrado por um portal de autoatendimento para colaboradores, gestores e RH.
Os principais objetivos incluem:
- **Automatizar 80% das solicitações de TI rotineiras**.
- **Unificar o acesso a sistemas-chave** como Exchange, Nextcloud e Zammad.
- **Mitigar riscos de segurança** através da automação de processos de desligamento e higienização de contas inativas.
- **Empoderar colaboradores e gestores** com ferramentas de autoatendimento.
Para um guia completo sobre a arquitetura e o funcionamento de cada script, consulte a **[Wiki do Projeto](link-para-a-wiki-do-seu-gitea)**.
## 🚀 Módulos e Funcionalidades Principais ## 🚀 Módulos e Funcionalidades Principais
A plataforma é dividida nos seguintes módulos, conforme o dossiê do projeto: 1. **Portal de Autoatendimento do Usuário:**
* Login seguro (integração com AD).
* Atualização de dados cadastrais (telefone, endereço, etc.).
* Reset de senha de forma autônoma e segura.
* Visualização de organograma.
#### Módulo 1: Portal do Colaborador (Hub Digital) 2. **Portal de Gestão (RH e Gestores):**
- Autenticação Segura via LDAPS * Formulário inteligente para requisição de novos colaboradores.
- Dashboard Unificado com Widgets (AD, Exchange, Nextcloud, Zammad) * Fluxo de aprovação para criação de contas.
- Alteração de Senha (Self-Service) * Processo automatizado de desligamento (offboarding).
- Atualização de Foto de Perfil * Gestão de grupos de acesso e distribuição.
#### Módulo 2: Portal do Gestor 3. **Automação e Orquestração (Backend):**
- Criação de Novos Colaboradores a partir de templates * Scripts PowerShell robustos para provisionamento em Active Directory e Exchange.
- Criação e Gestão de Grupos de Distribuição * Higienização automática de dados do AD (contas inativas, dados incompletos).
* Módulo de auditoria e geração de relatórios de conformidade.
#### Módulo 3: Gestão de Colaboradores (RH) 4. **Integrações e Segurança:**
- Painel de Consulta e Edição de Colaboradores Ativos * Integração Multi-AD (múltiplas florestas/domínios).
- Processo de Desligamento Totalmente Automatizado * Conexão com sistemas de SIEM para monitoramento de eventos.
* Painel de gestão para administradores de TI.
#### Módulo 4: Automação e Governança (Backend)
- Higienização Agendada de Contas de Usuários e Computadores Inativos
- Mapeamento e Atualização Automática do Organograma no AD
#### Módulo 5: Painel de Auditoria e Relatórios
- Geração de Relatórios de Atividade de Login
- Exportação Segura de Logs de Auditoria da Plataforma
## 🛠️ Tecnologias Utilizadas ## 🛠️ Tecnologias Utilizadas
- **Backend (Automação):** PowerShell 5.1+ * **Backend/Automação:** PowerShell 5.1+
- **Sistemas Alvo:** Active Directory, Microsoft Exchange * **Framework de Testes:** Pester
- **Backend (API):** *(A definir, ex: Node.js, Python/Flask, C#/.NET)* * **Controle de Versão:** Git / Gitea
- **Frontend:** *(A definir, ex: React, Vue.js, Angular)* * **Frontend (Portal):** HTML5, CSS3, JavaScript (Vue.js ou React a ser definido)
- **Banco de Dados (Configurações):** *(A definir, ex: PostgreSQL, MariaDB)* * **Backend (Portal):** Node.js ou .NET Core (a ser definido)
* **Banco de Dados:** PostgreSQL ou MariaDB (a ser definido)
---
## 🏁 Começando (Getting Started) ## 🏁 Começando (Getting Started)
Para configurar um ambiente de desenvolvimento local, siga os passos abaixo. Esta seção descreve como configurar o ambiente para executar os scripts de automação.
### Pré-requisitos ### Pré-requisitos
- PowerShell 5.1 ou superior Para executar os scripts de automação e os testes contidos neste repositório, o ambiente precisa atender aos seguintes requisitos:
- Módulo `ActiveDirectory` (instalado via RSAT - Ferramentas de Administração de Servidor Remoto)
- Ferramentas de Gerenciamento do Exchange 1. **PowerShell 5.1** ou superior.
- Acesso a um ambiente de Active Directory e Exchange para testes 2. Módulo **ActiveDirectory** para PowerShell:
- *(Outros pré-requisitos para a stack web, como Node.js, Python, etc.)* * Pode ser instalado via "Recursos Opcionais" do Windows ou com o comando:
```powershell
Install-WindowsFeature RSAT-AD-PowerShell
```
3. Módulo **Pester** para execução dos testes:
* Normalmente já vem instalado nas versões mais recentes do Windows. Para instalar ou atualizar, use:
```powershell
Install-Module -Name Pester -Force -SkipPublisherCheck
```
### Instalação ### Instalação
1. **Clone o repositório:** 1. Clone o repositório do Gitea para sua máquina local:
```sh ```bash
git clone [URL_DO_SEU_REPOSITORIO_GITEA] git clone [http://10.10.253.128/seu-usuario/plataforma-unificada-ad.git](http://10.10.253.128/seu-usuario/plataforma-unificada-ad.git)
cd portal-gestao-automation
``` ```
2. **Configuração do Backend:** 2. Navegue até o diretório do projeto:
*(Instruções para configurar a API que irá executar os scripts)* ```bash
3. **Configuração do Frontend:** cd plataforma-unificada-ad
*(Instruções para instalar dependências e iniciar o servidor de desenvolvimento do frontend)* ```
4. **Configuração dos Scripts:** 3. Execute os testes para garantir que tudo está funcionando como esperado:
- Nenhuma instalação é necessária para os scripts, mas garanta que a política de execução do PowerShell permita a execução de scripts locais:
```powershell ```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser Invoke-Pester -Path .\tests\
``` ```
---
## 📖 Como Usar ## 📖 Como Usar
Os scripts de automação estão localizados na pasta `/scripts` e são projetados para serem chamados pela API do backend. Para testes manuais, podem ser executados diretamente via PowerShell. ### Exemplo de Execução do Script
O script `cria-usuario-unico.ps1` é o coração do provisionamento individual. Ele deve ser executado a partir de um terminal PowerShell com os parâmetros necessários.
**Exemplo de uso:**
**Exemplo (Criação de Usuário em Massa):**
```powershell ```powershell
# 1. Prepare as credenciais de forma segura # 1. Primeiro, armazene as credenciais de forma segura em variáveis
$clientCred = Get-Credential $clientCred = Get-Credential
$exchangeCred = Get-Credential $exchangeCred = Get-Credential
# 2. Execute o script apontando para o arquivo CSV # 2. Execute o script localizado na pasta 'src' com os parâmetros do usuário
.\scripts\identity\New-ADUserFromTemplate.ps1 -CsvPath "C:\temp\novos_usuarios.csv" -ClientADServer "dc01.local" -ClientADCredential $clientCred -ExchangeADServer "mail.local" -ExchangeADCredential $exchangeCred .\src\cria-usuario-unico.ps1 `
``` -ClientADServer "dc01.cliente.local" `
> Para detalhes completos sobre os parâmetros de cada script e a estrutura dos arquivos de entrada, consulte a **Wiki do Projeto**. -ClientADCredential $clientCred `
-ExchangeADServer "dc01.exchange.local" `
-ExchangeADCredential $exchangeCred `
-SamAccountName "joao.novo" `
-UserPrincipalName "joao.novo@cliente.com.br" `
-GivenName "Joao" `
-Surname "Novo" `
-Name "Joao Novo" `
-Path "OU=Usuarios,DC=cliente,DC=local"
🧪 Executando os Testes
Os testes automatizados garantem a qualidade e a estabilidade do código. Eles foram criados com o framework Pester. Para executá-los, navegue até a pasta raiz do projeto e use o seguinte comando:
## 🗺️ Fases do Projeto (Roadmap) PowerShell
A implementação seguirá as fases estratégicas definidas no dossiê do projeto para mitigar riscos e entregar valor de forma contínua. Invoke-Pester -Path .\tests\
🗺️ Fases do Projeto (Roadmap)
Fase Entrega Principal Estimativa Entregáveis e Valor Agregado
1 Discovery e Arquitetura 3-4 Semanas Blueprint técnico, DPIA/LGPD, planejamento de extensão AD
2 Prova de Conceito (PoC) de Riscos 2 Semanas Validação de integrações complexas (Multi-AD, SIEM)
3 MVP - Autoatendimento Essencial 6-8 Semanas Portal com login, troca de senha e atualização de perfil
4 Módulo de Gestão (Gestores e RH) 5-6 Semanas Criação de usuários, grupos, e módulo de desligamento
5 Módulos de Automação e Auditoria 5-7 Semanas Scripts de higienização, organograma e painel de auditoria
6 Integração com Sistemas Externos 4-6 Semanas Conectores (Exchange, Nextcloud, Zammad), dashboard unificado
7 Painel de Gestão e Configurações 3-4 Semanas Painel de admin para gerenciar variáveis e integrações
8 Lançamento e Melhoria Contínua Contínuo Plataforma em produção, monitoramento de KPIs e treinamento
| Fase | Título | Duração Estimada | Entregáveis Chave | Exportar para as Planilhas
| :--: | ------------------------------------------ | :--------------: | --------------------------------------------------------- | 🤝 Como Contribuir
| 1 | Análise e Arquitetura Estratégica | 3-4 Semanas | Blueprint técnico, DPIA/LGPD, planejamento de extensão AD | Contribuições são o que tornam a comunidade um lugar incrível para aprender, inspirar e criar. Qualquer contribuição que você fizer será muito apreciada.
| 2 | Prova de Conceito (PoC) de Riscos | 2 Semanas | Validação de integrações complexas (Multi-AD, SIEM) |
| 3 | MVP - Autoatendimento Essencial | 6-8 Semanas | Portal com login, troca de senha e atualização de perfil |
| 4 | Módulo de Gestão (Gestores e RH) | 5-6 Semanas | Criação de usuários, grupos, e módulo de desligamento |
| 5 | Módulos de Automação e Auditoria | 5-7 Semanas | Scripts de higienização, organograma e painel de auditoria|
| 6 | Integração com Sistemas Externos | 4-6 Semanas | Conectores (Exchange, Nextcloud, Zammad), dashboard unificado|
| 7 | Painel de Gestão e Configurações | 3-4 Semanas | Painel de admin para gerenciar variáveis e integrações |
| 8 | Lançamento e Melhoria Contínua | Contínuo | Plataforma em produção, monitoramento de KPIs e treinamento |
Faça um Fork do Projeto
Crie sua Feature Branch (git checkout -b feature/AmazingFeature)
## 🤝 Como Contribuir Faça o Commit de suas alterações (git commit -m 'Add some AmazingFeature')
Contribuições são o que tornam a comunidade um lugar incrível para aprender, inspirar e criar. Qualquer contribuição que você fizer será **muito apreciada**. Faça o Push para a Branch (git push origin feature/AmazingFeature)
1. Faça um Fork do Projeto Abra um Pull Request
2. Crie sua Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Faça o Commit de suas alterações (`git commit -m 'Add some AmazingFeature'`)
4. Faça o Push para a Branch (`git push origin feature/AmazingFeature`)
5. Abra um Pull Request
---

Binary file not shown.

View File

@ -0,0 +1,157 @@
# cria-usuario-unico.Tests.ps1
# Importa o script que será testado, carregando a função New-UnifiedADUser na memória
. "$PSScriptRoot\cria-usuario-unico.ps1"
# --- SOLUÇÃO DEFINITIVA PARA O ERRO 'CommandNotFoundException' ---
# Em vez de uma função dummy, criamos o comando dinamicamente.
# Isso garante que o comando é registrado oficialmente no PowerShell antes do Pester tentar mocká-lo.
New-Item -Path "function:Enable-Mailbox" -Value { "Comando falso para Pester" } -Force | Out-Null
# Inicia a suíte de testes
Describe 'Testes do Script de Provisionamento New-UnifiedADUser v2.2' {
# --- SETUP INICIAL ---
# Define variáveis e mocks globais para todos os testes
BeforeAll {
# Parâmetros de conexão falsos que serão usados em todos os testes
$script:dummyCred = New-Object System.Management.Automation.PSCredential ('dummyuser', (ConvertTo-SecureString 'dummypass' -AsPlainText -Force))
$script:clientDC = 'client.dc.local'
$script:exchangeDC = 'exchange.dc.local'
# Mocks padrão: por padrão, tudo existe e está disponível
Mock Get-ADUser { return $null }
Mock Get-ADOrganizationalUnit { return $true }
Mock New-ADUser { } -Verifiable
Mock Enable-Mailbox { } -Verifiable # Agora o Pester encontrará este comando para mockar.
Mock Get-ADForest {
return [PSCustomObject]@{
UPNSuffixes = @('cliente.com.br', 'legacy.local')
RootDomain = 'cliente.com.br'
}
}
Mock Write-Log { } -Verifiable
}
# Define os parâmetros de conexão que são obrigatórios para todas as chamadas da função
$connectionParams = @{
ClientADServer = $script:clientDC
ClientADCredential = $script:dummyCred
ExchangeADServer = $script:exchangeDC
ExchangeADCredential = $script:dummyCred
}
Context '1. Validação de Parâmetros Essenciais' {
It '1.1 Deve falhar se um campo crítico como SamAccountName estiver faltando' {
# Prepara os parâmetros do usuário, faltando um campo obrigatório
$userParams = @{
UserPrincipalName = 'teste@cliente.com.br'
GivenName = 'Teste'
Surname = 'Sobrenome'
Name = 'Nome Completo'
Path = 'OU=Teste,DC=local'
}
# Executa a função com os parâmetros de conexão e do usuário
New-UnifiedADUser @connectionParams @userParams
# A função New-ADUser NUNCA deve ser chamada
Assert-VerifiableMocks {
New-ADUser | Should -Not -BeCalled
}
# O log de erro deve ser chamado com a mensagem correta
Assert-VerifiableMocks {
Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: O campo crítico 'SamAccountName' está faltando para ''." | Should -BeCalled
}
}
}
Context '2. Validação de Pré-Voo (Pre-flight Checks)' {
It '2.1 Deve falhar se a OU não existir no AD do Cliente' {
# Simula que a OU não existe APENAS no AD do Cliente
Mock Get-ADOrganizationalUnit -ParameterFilter { $Server -eq $script:clientDC } { return $null }
Mock Get-ADOrganizationalUnit -ParameterFilter { $Server -eq $script:exchangeDC } { return $true }
$userParams = @{ SamAccountName = 'teste.ou1'; UserPrincipalName = 'teste.ou1@cliente.com.br'; GivenName = 'T'; Surname = 'O'; Name = 'TO'; Path = 'OU=Inexistente,DC=local' }
New-UnifiedADUser @connectionParams @userParams
Assert-VerifiableMocks {
New-ADUser | Should -Not -BeCalled
Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: A OU 'OU=Inexistente,DC=local' não foi encontrada no AD do Cliente." | Should -BeCalled
}
}
It '2.2 Deve falhar se a OU não existir no AD do Exchange' {
# Simula que a OU não existe APENAS no AD do Exchange
Mock Get-ADOrganizationalUnit -ParameterFilter { $Server -eq $script:clientDC } { return $true }
Mock Get-ADOrganizationalUnit -ParameterFilter { $Server -eq $script:exchangeDC } { return $null }
$userParams = @{ SamAccountName = 'teste.ou2'; UserPrincipalName = 'teste.ou2@cliente.com.br'; GivenName = 'T'; Surname = 'O'; Name = 'TO'; Path = 'OU=Inexistente,DC=local' }
New-UnifiedADUser @connectionParams @userParams
Assert-VerifiableMocks {
New-ADUser | Should -Not -BeCalled
Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: A OU 'OU=Inexistente,DC=local' não foi encontrada no AD do Exchange." | Should -BeCalled
}
}
It '2.3 Deve falhar se o SamAccountName já existir no AD do Cliente' {
# Simula que o usuário existe APENAS no AD do Cliente
Mock Get-ADUser -ParameterFilter { $Server -eq $script:clientDC -and $Filter -like "*SamAccountName -eq 'usuario.existente'*" } { return $true }
$userParams = @{ SamAccountName = 'usuario.existente'; UserPrincipalName = 'novo.upn@cliente.com.br'; GivenName = 'U'; Surname = 'E'; Name = 'UE'; Path = 'OU=Teste,DC=local' }
New-UnifiedADUser @connectionParams @userParams
Assert-VerifiableMocks {
New-ADUser | Should -Not -BeCalled
Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: O SamAccountName 'usuario.existente' já existe no AD do Cliente." | Should -BeCalled
}
}
It '2.4 Deve falhar se o UserPrincipalName já existir no AD do Exchange' {
# Simula que o UPN existe APENAS no AD do Exchange
Mock Get-ADUser -ParameterFilter { $Server -eq $script:exchangeDC -and $Filter -like "*UserPrincipalName -eq 'upn.existente@cliente.com.br'*" } { return $true }
$userParams = @{ SamAccountName = 'novo.sam'; UserPrincipalName = 'upn.existente@cliente.com.br'; GivenName = 'U'; Surname = 'E'; Name = 'UE'; Path = 'OU=Teste,DC=local' }
New-UnifiedADUser @connectionParams @userParams
Assert-VerifiableMocks {
New-ADUser | Should -Not -BeCalled
Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: O UserPrincipalName 'upn.existente@cliente.com.br' já existe no AD do Exchange." | Should -BeCalled
}
}
It '2.5 Deve falhar se um UPN com sufixo inválido for usado' {
$userParams = @{ SamAccountName = 'teste.upn'; UserPrincipalName = 'teste.upn@dominio.invalido'; GivenName = 'T'; Surname = 'U'; Name = 'TU'; Path = 'OU=Teste,DC=local' }
New-UnifiedADUser @connectionParams @userParams
Assert-VerifiableMocks {
New-ADUser | Should -Not -BeCalled
Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: O sufixo de UPN '@dominio.invalido' não é válido na floresta do AD do Cliente." | Should -BeCalled
}
}
}
Context '3. Sequência de Execução' {
It '3.1 NÃO deve tentar criar a Mailbox se a criação do usuário no AD do Cliente falhar' {
# Força o New-ADUser a lançar uma exceção
Mock New-ADUser { throw "Acesso negado" }
# Garante que todas as validações de pré-voo passem
Mock Get-ADUser { return $null }
Mock Get-ADOrganizationalUnit { return $true }
$userParams = @{ SamAccountName = 'teste.falha'; UserPrincipalName = 'teste.falha@cliente.com.br'; GivenName = 'T'; Surname = 'F'; Name = 'TF'; Path = 'OU=Teste,DC=local' }
New-UnifiedADUser @connectionParams @userParams
# New-ADUser deve ser chamada, mas Enable-Mailbox NUNCA
Assert-VerifiableMocks {
New-ADUser | Should -BeCalled -Exactly 1
Enable-Mailbox | Should -Not -BeCalled
}
}
}
}

View File

@ -3,129 +3,306 @@
Cria um novo usuário no Active Directory com um conjunto completo de atributos e habilita sua caixa de correio no Exchange. Cria um novo usuário no Active Directory com um conjunto completo de atributos e habilita sua caixa de correio no Exchange.
.DESCRIPTION .DESCRIPTION
Versão 2.0 do script de provisionamento. Desenvolvido para o projeto "Plataforma Unificada de Trabalho Digital", esta ferramenta robusta Versão 2.2 do script de provisionamento. Focado em segurança, esta versão realiza uma validação de pré-voo ("pre-flight check")
suporta a criação de usuários com mais de 25 atributos comuns do AD, com validações críticas para garantir a integridade dos dados. em ambos os ambientes (AD do Cliente e AD do Exchange) antes de qualquer operação de escrita, garantindo que OUs existam e que
O script é projetado para automação e não permite interação via console. usuários não sejam duplicados ou sobrescritos.
.NOTES .NOTES
Autor: Gemini (Especialista NGINX & Automação) Autor: Gemini (Especialista NGINX & Automação)
Data: 14 de outubro de 2025 Data: 15 de outubro de 2025
Versão: 2.0 - Expansão massiva de atributos, campos obrigatórios e validação dupla de unicidade (SamAccountName e UserPrincipalName). Versão: 2.4 - Corrigido erro de sintaxe (chave extra no final do arquivo).
Contexto: Projeto Plataforma Unificada de Trabalho Digital Contexto: Projeto Plataforma Unificada de Trabalho Digital
#> #>
[CmdletBinding(DefaultParameterSetName = 'SingleUser')]
param(
# --- Parâmetros de Conexão e Autenticação (Obrigatórios para todas as operações) ---
[Parameter(Mandatory = $true, Position = 0)]
[string]$ClientADServer,
[Parameter(Mandatory = $true, Position = 1)] function New-UnifiedADUser {
[pscredential]$ClientADCredential, [CmdletBinding(DefaultParameterSetName = 'SingleUser')]
[Parameter(Mandatory = $true, Position = 2)]
[string]$ExchangeADServer,
[Parameter(Mandatory = $true, Position = 3)]
[pscredential]$ExchangeADCredential,
# --- Parâmetros de Input (Escolha um método) ---
[Parameter(Mandatory = $true, ParameterSetName = 'CsvImport')]
[string]$CsvPath,
[Parameter(Mandatory = $true, ParameterSetName = 'DbImport')]
[switch]$FromDatabase,
# --- ATRIBUTOS CRÍTICOS (Obrigatórios para criação de usuário único) ---
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser')]
[string]$SamAccountName,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser')]
[string]$UserPrincipalName,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser')]
[string]$GivenName,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser')]
[string]$Surname,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser')]
[string]$Name,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser')]
[string]$Path,
# --- ATRIBUTOS OPCIONAIS ---
# -- Organização --
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Title,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Department,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Company,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Manager,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$EmployeeID,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$EmployeeNumber,
# -- Contato --
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$OfficePhone,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$MobilePhone,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$EmailAddress,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$StreetAddress,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$City,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$State,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$PostalCode,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Country,
# -- Perfil --
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Description,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Office,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$HomePage,
# -- Controle da Conta --
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [switch]$PasswordNeverExpires,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [switch]$CannotChangePassword,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [datetime]$AccountExpirationDate,
# -- Atributos Customizados --
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$extensionAttribute1,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$extensionAttribute2,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$ProxyAddresses,
# --- Parâmetros de Logging ---
[Parameter(Mandatory = $false)] [string]$DatabaseConnectionString,
[Parameter(Mandatory = $false)] [string]$LogFilePath = ".\New-UnifiedADUser-Log-$(Get-Date -Format 'yyyy-MM-dd').txt"
)
# --- FUNÇÕES AUXILIARES ---
function Write-Log {
param( param(
[Parameter(Mandatory = $true)] # --- Parâmetros de Conexão e Autenticação (Obrigatórios para todas as operações) ---
[string]$Message, [Parameter(Mandatory = $true, Position = 0, HelpMessage = "IP ou FQDN do Domain Controller principal (cliente).")]
[Parameter(Mandatory = $true)] [string]$ClientADServer,
[ValidateSet('INFO', 'WARN', 'ERROR')]
[string]$Level, [Parameter(Mandatory = $true, Position = 1, HelpMessage = "Credencial (usuário e senha) para autenticar no AD do cliente.")]
[Parameter(Mandatory = $false)] [pscredential]$ClientADCredential,
[string]$TargetADServer
[Parameter(Mandatory = $true, Position = 2, HelpMessage = "IP ou FQDN do Domain Controller do ambiente Exchange.")]
[string]$ExchangeADServer,
[Parameter(Mandatory = $true, Position = 3, HelpMessage = "Credencial (usuário e senha) para autenticar no AD do Exchange.")]
[pscredential]$ExchangeADCredential,
# --- Parâmetros de Input (Escolha um método) ---
[Parameter(Mandatory = $true, ParameterSetName = 'CsvImport', HelpMessage = "Caminho completo para o arquivo .CSV de importação. Ex: 'C:\temp\usuarios.csv'")]
[string]$CsvPath,
[Parameter(Mandatory = $true, ParameterSetName = 'DbImport', HelpMessage = "Switch para ativar o modo de importação via banco de dados (ainda não implementado).")]
[switch]$FromDatabase,
# --- ATRIBUTOS CRÍTICOS (Obrigatórios para criação de usuário único) ---
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser', HelpMessage = "O nome de logon do usuário (curto, pré-Windows 2000). Ex: 'joao.silva'")]
[string]$SamAccountName,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser', HelpMessage = "O logon principal do usuário, geralmente o e-mail. Ex: 'joao.silva@cliente.com.br'")]
[string]$UserPrincipalName,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser', HelpMessage = "O primeiro nome do usuário. Ex: 'João'")]
[string]$GivenName,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser', HelpMessage = "O sobrenome do usuário. Ex: 'Silva'")]
[string]$Surname,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser', HelpMessage = "O nome completo de exibição. Ex: 'João da Silva'")]
[string]$Name,
[Parameter(Mandatory = $true, ParameterSetName = 'SingleUser', HelpMessage = "O caminho completo da OU de destino. Ex: 'OU=Usuarios,DC=cliente,DC=local'")]
[string]$Path,
# --- ATRIBUTOS OPCIONAIS ---
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Title,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Department,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Company,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Manager,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$EmployeeID,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$EmployeeNumber,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$OfficePhone,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$MobilePhone,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$EmailAddress,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$StreetAddress,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$City,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$State,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$PostalCode,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Country,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Description,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$Office,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$HomePage,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [switch]$PasswordNeverExpires,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [switch]$CannotChangePassword,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [datetime]$AccountExpirationDate,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$extensionAttribute1,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$extensionAttribute2,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleUser')] [string]$ProxyAddresses,
# --- Parâmetros de Logging ---
[Parameter(Mandatory = $false)] [string]$DatabaseConnectionString,
[Parameter(Mandatory = $false)] [string]$LogFilePath = ".\New-UnifiedADUser-Log-$(Get-Date -Format 'yyyy-MM-dd').txt"
) )
$logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [$Level] - $Message" # --- LÓGICA PRINCIPAL ---
$scriptUser = $env:USERNAME
# --- VALIDAÇÃO SEQUENCIAL DE PARÂMETROS DE CONEXÃO ---
if ($PSBoundParameters.Count -eq 0) { Show-Usage; return }
if ([string]::IsNullOrWhiteSpace($ClientADServer)) { Write-Host "[ERRO] O parâmetro -ClientADServer é obrigatório e não pode ser vazio." -ForegroundColor Red; Show-Usage; return }
if ($ClientADCredential -eq $null) { Write-Host "[ERRO] O parâmetro -ClientADCredential é obrigatório." -ForegroundColor Red; Show-Usage; return }
if ([string]::IsNullOrWhiteSpace($ExchangeADServer)) { Write-Host "[ERRO] O parâmetro -ExchangeADServer é obrigatório e não pode ser vazio." -ForegroundColor Red; Show-Usage; return }
if ($ExchangeADCredential -eq $null) { Write-Host "[ERRO] O parâmetro -ExchangeADCredential é obrigatório." -ForegroundColor Red; Show-Usage; return }
try { try {
if (-not [string]::IsNullOrEmpty($DatabaseConnectionString)) { Import-Module ActiveDirectory -ErrorAction Stop
# [PLACEHOLDER] Lógica de log no banco de dados.
Write-Verbose "Log enviado para o banco de dados."
}
} }
catch { catch {
Write-Log -Level 'ERROR' -Message "Falha ao importar o módulo ActiveDirectory. Verifique se as Ferramentas de Administração de Servidor Remoto (RSAT) estão instaladas."
return
}
Write-Host "Coletando informações da floresta de AD para validação..."
try {
$adForest = Get-ADForest -Server $ClientADServer -Credential $ClientADCredential
[array]$validUPNSuffixes = $adForest.UPNSuffixes + $adForest.RootDomain
Write-Log -Level 'INFO' -Message "Sufixos de UPN válidos carregados: $($validUPNSuffixes -join ', ')"
}
catch {
Write-Log -Level 'ERROR' -Message "Não foi possível obter informações da floresta do AD do Cliente. Verifique a conexão e as credenciais. Erro: $($_.Exception.Message)"
return
}
$usersToProcess = @()
switch ($PSCmdlet.ParameterSetName) {
'SingleUser' {
$usersToProcess += [PSCustomObject]@{
SamAccountName = $SamAccountName; UserPrincipalName = $UserPrincipalName; GivenName = $GivenName; Surname = $Surname; Name = $Name; Path = $Path; Title = $Title; Department = $Department; Company = $Company; Manager = $Manager; EmployeeID = $EmployeeID; EmployeeNumber = $EmployeeNumber; OfficePhone = $OfficePhone; MobilePhone = $MobilePhone; EmailAddress = $EmailAddress; StreetAddress = $StreetAddress; City = $City; State = $State; PostalCode = $PostalCode; Country = $Country; Description = $Description; Office = $Office; HomePage = $HomePage; PasswordNeverExpires = $PasswordNeverExpires; CannotChangePassword = $CannotChangePassword; AccountExpirationDate = $AccountExpirationDate; extensionAttribute1 = $extensionAttribute1; extensionAttribute2 = $extensionAttribute2; ProxyAddresses = $ProxyAddresses
}
break
}
'CsvImport' {
try {
$usersToProcess = Import-Csv -Path $CsvPath -ErrorAction Stop
Write-Log -Level 'INFO' -Message "Arquivo CSV '$CsvPath' carregado com $($usersToProcess.Count) registros."
}
catch {
Write-Log -Level 'ERROR' -Message "Não foi possível ler o arquivo CSV em '$CsvPath'. Erro: $($_.Exception.Message)"
return
}
break
}
'DbImport' {
Write-Log -Level 'WARN' -Message "O método de importação via banco de dados (-FromDatabase) ainda não foi implementado."
return
break
}
default {
Show-Usage
return
}
}
foreach ($user in $usersToProcess) {
Write-Host "Iniciando pré-validação para o usuário: $($user.SamAccountName)"
Write-Log -Level 'INFO' -Message "Iniciando pré-validação para '$($user.SamAccountName)' em ambos os ADs."
$validationFailed = $false
$mandatoryFields = @('SamAccountName', 'UserPrincipalName', 'GivenName', 'Surname', 'Name', 'Path')
foreach ($field in $mandatoryFields) {
if ([string]::IsNullOrWhiteSpace($user.$field)) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O campo crítico '$field' está faltando para '$($user.SamAccountName)'."
$validationFailed = $true; break
}
}
if ($validationFailed) { Write-Host " [ERRO] Falha na validação de campos obrigatórios." -ForegroundColor Red; continue }
if (-not (Get-ADOrganizationalUnit -Filter "DistinguishedName -eq '$($user.Path)'" -Server $ClientADServer -Credential $ClientADCredential -ErrorAction SilentlyContinue)) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: A OU '$($user.Path)' não foi encontrada no AD do Cliente."
$validationFailed = $true
}
if (-not (Get-ADOrganizationalUnit -Filter "DistinguishedName -eq '$($user.Path)'" -Server $ExchangeADServer -Credential $ExchangeADCredential -ErrorAction SilentlyContinue)) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: A OU '$($user.Path)' não foi encontrada no AD do Exchange."
$validationFailed = $true
}
if (Get-ADUser -Filter "SamAccountName -eq '$($user.SamAccountName)'" -Server $ClientADServer -Credential $ClientADCredential -ErrorAction SilentlyContinue) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O SamAccountName '$($user.SamAccountName)' já existe no AD do Cliente."
$validationFailed = $true
}
if (Get-ADUser -Filter "SamAccountName -eq '$($user.SamAccountName)'" -Server $ExchangeADServer -Credential $ExchangeADCredential -ErrorAction SilentlyContinue) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O SamAccountName '$($user.SamAccountName)' já existe no AD do Exchange."
$validationFailed = $true
}
if (Get-ADUser -Filter "UserPrincipalName -eq '$($user.UserPrincipalName)'" -Server $ClientADServer -Credential $ClientADCredential -ErrorAction SilentlyContinue) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O UserPrincipalName '$($user.UserPrincipalName)' já existe no AD do Cliente."
$validationFailed = $true
}
if (Get-ADUser -Filter "UserPrincipalName -eq '$($user.UserPrincipalName)'" -Server $ExchangeADServer -Credential $ExchangeADCredential -ErrorAction SilentlyContinue) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O UserPrincipalName '$($user.UserPrincipalName)' já existe no AD do Exchange."
$validationFailed = $true
}
$upnSuffix = ($user.UserPrincipalName).Split('@')[1]
if ($validUPNSuffixes -notcontains $upnSuffix) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O sufixo de UPN '@$upnSuffix' não é válido na floresta do AD do Cliente."
$validationFailed = $true
}
if ($validationFailed) {
Write-Host " [ERRO] Falha na pré-validação. Verifique o log para detalhes." -ForegroundColor Red
continue
}
Write-Host " [OK] Pré-validação concluída com sucesso. Iniciando criação..." -ForegroundColor Green
Write-Log -Level 'INFO' -Message "Pré-validação para '$($user.SamAccountName)' concluída com sucesso. Iniciando criação."
$clientADSuccess = $false
try {
$tempPassword = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 16 | ForEach-Object { [char]$_ })
$securePassword = ConvertTo-SecureString $tempPassword -AsPlainText -Force
$userParams = @{
SamAccountName = $user.SamAccountName; UserPrincipalName = $user.UserPrincipalName; GivenName = $user.GivenName; Surname = $user.Surname; Name = $user.Name; DisplayName = $user.Name; Path = $user.Path; AccountPassword = $securePassword; Enabled = $true; ChangePasswordAtLogon = $true
}
if ($user.PSObject.Properties.Name -contains 'EmailAddress' -and -not [string]::IsNullOrWhiteSpace($user.EmailAddress)) { $userParams.Add('EmailAddress', $user.EmailAddress) }
if ($user.PSObject.Properties.Name -contains 'Description' -and -not [string]::IsNullOrWhiteSpace($user.Description)) { $userParams.Add('Description', $user.Description) }
if ($user.PSObject.Properties.Name -contains 'Title' -and -not [string]::IsNullOrWhiteSpace($user.Title)) { $userParams.Add('Title', $user.Title) }
if ($user.PSObject.Properties.Name -contains 'Department' -and -not [string]::IsNullOrWhiteSpace($user.Department)) { $userParams.Add('Department', $user.Department) }
if ($user.PSObject.Properties.Name -contains 'Company' -and -not [string]::IsNullOrWhiteSpace($user.Company)) { $userParams.Add('Company', $user.Company) }
if ($user.PSObject.Properties.Name -contains 'EmployeeID' -and -not [string]::IsNullOrWhiteSpace($user.EmployeeID)) { $userParams.Add('EmployeeID', $user.EmployeeID) }
if ($user.PSObject.Properties.Name -contains 'EmployeeNumber' -and -not [string]::IsNullOrWhiteSpace($user.EmployeeNumber)) { $userParams.Add('EmployeeNumber', $user.EmployeeNumber) }
if ($user.PSObject.Properties.Name -contains 'Office' -and -not [string]::IsNullOrWhiteSpace($user.Office)) { $userParams.Add('physicalDeliveryOfficeName', $user.Office) }
if ($user.PSObject.Properties.Name -contains 'OfficePhone' -and -not [string]::IsNullOrWhiteSpace($user.OfficePhone)) { $userParams.Add('OfficePhone', $user.OfficePhone) }
if ($user.PSObject.Properties.Name -contains 'MobilePhone' -and -not [string]::IsNullOrWhiteSpace($user.MobilePhone)) { $userParams.Add('Mobile', $user.MobilePhone) }
if ($user.PSObject.Properties.Name -contains 'StreetAddress' -and -not [string]::IsNullOrWhiteSpace($user.StreetAddress)) { $userParams.Add('StreetAddress', $user.StreetAddress) }
if ($user.PSObject.Properties.Name -contains 'City' -and -not [string]::IsNullOrWhiteSpace($user.City)) { $userParams.Add('City', $user.City) }
if ($user.PSObject.Properties.Name -contains 'State' -and -not [string]::IsNullOrWhiteSpace($user.State)) { $userParams.Add('State', $user.State) }
if ($user.PSObject.Properties.Name -contains 'PostalCode' -and -not [string]::IsNullOrWhiteSpace($user.PostalCode)) { $userParams.Add('PostalCode', $user.PostalCode) }
if ($user.PSObject.Properties.Name -contains 'Country' -and -not [string]::IsNullOrWhiteSpace($user.Country)) { $userParams.Add('Country', $user.Country) }
if ($user.PSObject.Properties.Name -contains 'HomePage' -and -not [string]::IsNullOrWhiteSpace($user.HomePage)) { $userParams.Add('HomePage', $user.HomePage) }
if ($user.PSObject.Properties.Name -contains 'AccountExpirationDate' -and $user.AccountExpirationDate) { $userParams.Add('AccountExpirationDate', ([datetime]$user.AccountExpirationDate)) }
if ($user.PSObject.Properties.Name -contains 'PasswordNeverExpires' -and ($user.PasswordNeverExpires -eq 'True' -or $user.PasswordNeverExpires -is [bool] -and $user.PasswordNeverExpires)) { $userParams.Add('PasswordNeverExpires', $true) }
if ($user.PSObject.Properties.Name -contains 'CannotChangePassword' -and ($user.CannotChangePassword -eq 'True' -or $user.CannotChangePassword -is [bool] -and $user.CannotChangePassword)) { $userParams.Add('CannotChangePassword', $true) }
if ($user.PSObject.Properties.Name -contains 'extensionAttribute1' -and -not [string]::IsNullOrWhiteSpace($user.extensionAttribute1)) { $userParams.Add('extensionAttribute1', $user.extensionAttribute1) }
if ($user.PSObject.Properties.Name -contains 'extensionAttribute2' -and -not [string]::IsNullOrWhiteSpace($user.extensionAttribute2)) { $userParams.Add('extensionAttribute2', $user.extensionAttribute2) }
if ($user.PSObject.Properties.Name -contains 'ProxyAddresses' -and -not [string]::IsNullOrWhiteSpace($user.ProxyAddresses)) {
$proxyList = @("SMTP:" + $user.UserPrincipalName)
$aliases = $user.ProxyAddresses -split ';'
foreach ($alias in $aliases) { if (-not [string]::IsNullOrWhiteSpace($alias)) { $proxyList += "smtp:" + $alias.Trim() } }
$userParams.Add('ProxyAddresses', $proxyList)
}
if ($user.PSObject.Properties.Name -contains 'Manager' -and -not [string]::IsNullOrWhiteSpace($user.Manager)) {
$managerUser = Get-ADUser -Filter "SamAccountName -eq '$($user.Manager)'" -Server $ClientADServer -Credential $ClientADCredential
if ($managerUser) {
$userParams.Add('Manager', $managerUser.DistinguishedName)
} else {
Write-Log -Level 'WARN' -Message "Gestor '$($user.Manager)' não encontrado para o usuário '$($user.SamAccountName)'. O campo não será preenchido."
}
}
New-ADUser @userParams -Server $ClientADServer -Credential $ClientADCredential -ErrorAction Stop
Write-Log -Level 'INFO' -Message "SUCESSO: Conta '$($user.SamAccountName)' criada no AD do Cliente. Senha temporária gerada." -TargetADServer $ClientADServer
Write-Host " [OK] Conta criada no AD do Cliente." -ForegroundColor Green
Write-Log -Level 'INFO' -Message "Senha temporária para '$($user.SamAccountName)': $tempPassword"
$clientADSuccess = $true
}
catch {
$errorMessage = "FALHA ao criar a conta '$($user.SamAccountName)' no AD do Cliente. Erro: $($_.Exception.Message -replace "`r`n"," ")"
Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ClientADServer
Write-Host " [ERRO] Falha ao criar no AD do Cliente." -ForegroundColor Red
continue
}
if ($clientADSuccess) {
try {
Start-Sleep -Seconds 5
Write-Log -Level 'INFO' -Message "Tentando habilitar a mailbox para '$($user.SamAccountName)' no AD do Exchange em '$ExchangeADServer'." -TargetADServer $ExchangeADServer
Enable-Mailbox -Identity $user.SamAccountName -DomainController $ExchangeADServer -Credential $ExchangeADCredential -ErrorAction Stop
Write-Log -Level 'INFO' -Message "SUCESSO: Mailbox para '$($user.SamAccountName)' habilitada no ambiente Exchange." -TargetADServer $ExchangeADServer
Write-Host " [OK] Mailbox habilitada no Exchange." -ForegroundColor Green
Write-Log -Level 'INFO' -Message "PROVISIONAMENTO COMPLETO para o usuário '$($user.SamAccountName)'."
}
catch {
$errorMessage = "FALHA PARCIAL: A conta '$($user.SamAccountName)' foi criada no AD do Cliente, mas falhou ao habilitar a mailbox no Exchange. Erro: $($_.Exception.Message -replace "`r`n"," ")"
Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ExchangeADServer
Write-Host " [ERRO] Falha ao habilitar a mailbox no Exchange." -ForegroundColor Red
}
}
}
Write-Host "Processamento concluído."
}
# --- FUNÇÕES AUXILIARES ---
# Estas funções são definidas fora da função principal para serem acessíveis por todo o script, se necessário.
function Write-Log {
param(
[Parameter(Mandatory = $true)] [string]$Message,
[Parameter(Mandatory = $true)] [ValidateSet('INFO', 'WARN', 'ERROR')] [string]$Level,
[Parameter(Mandatory = $false)] [string]$TargetADServer
)
$logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [$Level] - $Message"
$scriptUser = $env:USERNAME
try {
if (-not [string]::IsNullOrEmpty($DatabaseConnectionString)) { # Placeholder para log em DB
Write-Verbose "Log enviado para o banco de dados."
}
} catch {
$dbErrorMessage = "Falha ao gravar no banco de dados: $($_.Exception.Message)" $dbErrorMessage = "Falha ao gravar no banco de dados: $($_.Exception.Message)"
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [ERROR] - $dbErrorMessage" | Out-File -FilePath $LogFilePath -Append "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [ERROR] - $dbErrorMessage" | Out-File -FilePath $LogFilePath -Append
} }
$logEntry | Out-File -FilePath $LogFilePath -Append $logEntry | Out-File -FilePath $LogFilePath -Append
if (-not [string]::IsNullOrEmpty($TargetADServer)) { if (-not [string]::IsNullOrEmpty($TargetADServer)) {
try { try {
$eventMessage = "Script 'New-UnifiedADUser.ps1' executado por '$scriptUser'.`nDetalhes: $Message" $eventMessage = "Script 'New-UnifiedADUser.ps1' executado por '$scriptUser'.`nDetalhes: $Message"
$eventLevel = switch ($Level) { $eventLevel = switch ($Level) { 'INFO' {'Information'} 'WARN' {'Warning'} 'ERROR' {'Error'} }
'INFO' { 'Information' }
'WARN' { 'Warning' }
'ERROR' { 'Error' }
}
Invoke-Command -ComputerName $TargetADServer -ScriptBlock { Invoke-Command -ComputerName $TargetADServer -ScriptBlock {
param($eventMessage, $eventLevel) param($eventMessage, $eventLevel)
$logSource = "Provisionamento de Usuários AD" $logSource = "Provisionamento de Usuários AD"
@ -135,8 +312,7 @@ function Write-Log {
Write-EventLog -LogName 'Application' -Source $logSource -EventId 1001 -EntryType $eventLevel -Message $eventMessage Write-EventLog -LogName 'Application' -Source $logSource -EventId 1001 -EntryType $eventLevel -Message $eventMessage
} -ArgumentList $eventMessage, $eventLevel } -ArgumentList $eventMessage, $eventLevel
Write-Verbose "Log de evento escrito em '$TargetADServer'." Write-Verbose "Log de evento escrito em '$TargetADServer'."
} } catch {
catch {
$eventErrorMessage = "Falha ao escrever no Event Log de '$TargetADServer': $($_.Exception.Message)" $eventErrorMessage = "Falha ao escrever no Event Log de '$TargetADServer': $($_.Exception.Message)"
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [ERROR] - $eventErrorMessage" | Out-File -FilePath $LogFilePath -Append "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [ERROR] - $eventErrorMessage" | Out-File -FilePath $LogFilePath -Append
} }
@ -147,239 +323,7 @@ function Show-Usage {
Write-Host @" Write-Host @"
ERRO: O script foi chamado sem os parâmetros necessários ou com um método de entrada de dados inválido. ERRO: O script foi chamado sem os parâmetros necessários ou com um método de entrada de dados inválido.
... (o resto da sua mensagem de ajuda continua aqui) ...
Este script cria usuários no AD do cliente e no AD do Exchange e deve ser chamado com todos os parâmetros requeridos.
MÉTODOS DE USO:
1. Criação de Usuário Único (parâmetros diretos):
Forneça os dados do usuário via parâmetros na linha de comando.
Exemplo:
.\New-UnifiedADUser.ps1 -SamAccountName 'joao.silva' -GivenName 'João' -Surname 'Silva' -Name 'João da Silva' `
-UserPrincipalName 'joao.silva@cliente.com.br' -Path 'OU=Usuarios,DC=local' `
-Title 'Analista' -Department 'TI' `
-ClientADServer '...' -ClientADCredential (Get-Credential) -ExchangeADServer '...' -ExchangeADCredential (Get-Credential)
2. Criação em Massa via CSV (usando o parâmetro -CsvPath):
Forneça um arquivo CSV com os dados de todos os usuários.
Exemplo:
.\New-UnifiedADUser.ps1 -CsvPath 'C:\temp\usuarios.csv' -ClientADServer '...' -ClientADCredential (Get-Credential) -ExchangeADServer '...' -ExchangeADCredential (Get-Credential)
"@ "@
} }
# --- LÓGICA PRINCIPAL ---
if ($PSBoundParameters.Count -eq 0) {
Show-Usage
exit 1
}
try {
Import-Module ActiveDirectory -ErrorAction Stop
}
catch {
Write-Log -Level 'ERROR' -Message "Falha ao importar o módulo ActiveDirectory. Verifique se as Ferramentas de Administração de Servidor Remoto (RSAT) estão instaladas."
exit 1
}
Write-Host "Coletando informações da floresta de AD para validação..."
try {
$adForest = Get-ADForest -Server $ClientADServer -Credential $ClientADCredential
[array]$validUPNSuffixes = $adForest.UPNSuffixes + $adForest.RootDomain
Write-Log -Level 'INFO' -Message "Sufixos de UPN válidos carregados: $($validUPNSuffixes -join ', ')"
}
catch {
Write-Log -Level 'ERROR' -Message "Não foi possível obter informações da floresta do AD. Verifique a conexão e as credenciais. Erro: $($_.Exception.Message)"
exit 1
}
$usersToProcess = @()
switch ($PSCmdlet.ParameterSetName) {
'SingleUser' {
$usersToProcess += [PSCustomObject]@{
SamAccountName = $SamAccountName; UserPrincipalName = $UserPrincipalName; GivenName = $GivenName; Surname = $Surname; Name = $Name; Path = $Path; Title = $Title; Department = $Department; Company = $Company; Manager = $Manager; EmployeeID = $EmployeeID; EmployeeNumber = $EmployeeNumber; OfficePhone = $OfficePhone; MobilePhone = $MobilePhone; EmailAddress = $EmailAddress; StreetAddress = $StreetAddress; City = $City; State = $State; PostalCode = $PostalCode; Country = $Country; Description = $Description; Office = $Office; HomePage = $HomePage; PasswordNeverExpires = $PasswordNeverExpires; CannotChangePassword = $CannotChangePassword; AccountExpirationDate = $AccountExpirationDate; extensionAttribute1 = $extensionAttribute1; extensionAttribute2 = $extensionAttribute2; ProxyAddresses = $ProxyAddresses
}
break
}
'CsvImport' {
try {
$usersToProcess = Import-Csv -Path $CsvPath -ErrorAction Stop
Write-Log -Level 'INFO' -Message "Arquivo CSV '$CsvPath' carregado com $($usersToProcess.Count) registros."
}
catch {
Write-Log -Level 'ERROR' -Message "Não foi possível ler o arquivo CSV em '$CsvPath'. Erro: $($_.Exception.Message)"
exit 1
}
break
}
'DbImport' {
Write-Log -Level 'WARN' -Message "O método de importação via banco de dados (-FromDatabase) ainda não foi implementado."
exit 1
break
}
default {
Show-Usage
exit 1
}
}
foreach ($user in $usersToProcess) {
Write-Host "Iniciando processamento para o usuário: $($user.SamAccountName)"
Write-Log -Level 'INFO' -Message "Iniciando provisionamento para o usuário '$($user.SamAccountName)'."
# --- BLOCO DE VALIDAÇÕES APRIMORADO ---
$validationFailed = $false
# 1. Validação de Campos Críticos Obrigatórios
$mandatoryFields = @('SamAccountName', 'UserPrincipalName', 'GivenName', 'Surname', 'Name', 'Path')
foreach ($field in $mandatoryFields) {
if ([string]::IsNullOrWhiteSpace($user.$field)) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O campo crítico obrigatório '$field' está faltando para '$($user.SamAccountName)'."
$validationFailed = $true
break
}
}
if ($validationFailed) { Write-Host " [ERRO] Falha na validação de campos obrigatórios." -ForegroundColor Red; continue }
# 2. Validação de Unicidade (SamAccountName e UserPrincipalName)
if (Get-ADUser -Filter "SamAccountName -eq '$($user.SamAccountName)'" -Server $ClientADServer -Credential $ClientADCredential) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O SamAccountName '$($user.SamAccountName)' já existe."
$validationFailed = $true
}
if (Get-ADUser -Filter "UserPrincipalName -eq '$($user.UserPrincipalName)'" -Server $ClientADServer -Credential $ClientADCredential) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O UserPrincipalName '$($user.UserPrincipalName)' já existe."
$validationFailed = $true
}
# 3. Validação de OU
if (-not (Get-ADOrganizationalUnit -Filter "DistinguishedName -eq '$($user.Path)'" -Server $ClientADServer -Credential $ClientADCredential)) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: A OU '$($user.Path)' não foi encontrada no Active Directory."
$validationFailed = $true
}
# 4. Validação de UPN
$upnSuffix = ($user.UserPrincipalName).Split('@')[1]
if ($upnSuffix -eq 'exch.local') {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O UPN '$($user.UserPrincipalName)' usa o sufixo proibido 'exch.local'."
$validationFailed = $true
}
elseif ($validUPNSuffixes -notcontains $upnSuffix) {
Write-Log -Level 'ERROR' -Message "USUÁRIO IGNORADO: O sufixo de UPN '@$upnSuffix' não é válido para esta floresta."
$validationFailed = $true
}
if ($validationFailed) { Write-Host " [ERRO] Falha na validação. Verifique o log para detalhes." -ForegroundColor Red; continue }
# --- FIM DAS VALIDAÇÕES ---
$clientADSuccess = $false
try {
$tempPassword = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 16 | ForEach-Object { [char]$_ })
$securePassword = ConvertTo-SecureString $tempPassword -AsPlainText -Force
# Inicia a construção dos parâmetros para o New-ADUser
$userParams = @{
SamAccountName = $user.SamAccountName
UserPrincipalName = $user.UserPrincipalName
GivenName = $user.GivenName
Surname = $user.Surname
Name = $user.Name
DisplayName = $user.Name
Path = $user.Path
AccountPassword = $securePassword
Enabled = $true
ChangePasswordAtLogon = $true
}
# --- ADIÇÃO CONDICIONAL DE TODOS OS ATRIBUTOS OPCIONAIS ---
# A checagem '... -contains ...' garante que o script não falhe se a coluna não existir no CSV
if ($user.PSObject.Properties.Name -contains 'EmailAddress' -and -not [string]::IsNullOrWhiteSpace($user.EmailAddress)) { $userParams.Add('EmailAddress', $user.EmailAddress) }
if ($user.PSObject.Properties.Name -contains 'Description' -and -not [string]::IsNullOrWhiteSpace($user.Description)) { $userParams.Add('Description', $user.Description) }
# Organização
if ($user.PSObject.Properties.Name -contains 'Title' -and -not [string]::IsNullOrWhiteSpace($user.Title)) { $userParams.Add('Title', $user.Title) }
if ($user.PSObject.Properties.Name -contains 'Department' -and -not [string]::IsNullOrWhiteSpace($user.Department)) { $userParams.Add('Department', $user.Department) }
if ($user.PSObject.Properties.Name -contains 'Company' -and -not [string]::IsNullOrWhiteSpace($user.Company)) { $userParams.Add('Company', $user.Company) }
if ($user.PSObject.Properties.Name -contains 'EmployeeID' -and -not [string]::IsNullOrWhiteSpace($user.EmployeeID)) { $userParams.Add('EmployeeID', $user.EmployeeID) }
if ($user.PSObject.Properties.Name -contains 'EmployeeNumber' -and -not [string]::IsNullOrWhiteSpace($user.EmployeeNumber)) { $userParams.Add('EmployeeNumber', $user.EmployeeNumber) }
if ($user.PSObject.Properties.Name -contains 'Office' -and -not [string]::IsNullOrWhiteSpace($user.Office)) { $userParams.Add('physicalDeliveryOfficeName', $user.Office) }
# Contato
if ($user.PSObject.Properties.Name -contains 'OfficePhone' -and -not [string]::IsNullOrWhiteSpace($user.OfficePhone)) { $userParams.Add('OfficePhone', $user.OfficePhone) }
if ($user.PSObject.Properties.Name -contains 'MobilePhone' -and -not [string]::IsNullOrWhiteSpace($user.MobilePhone)) { $userParams.Add('Mobile', $user.MobilePhone) }
# Endereço
if ($user.PSObject.Properties.Name -contains 'StreetAddress' -and -not [string]::IsNullOrWhiteSpace($user.StreetAddress)) { $userParams.Add('StreetAddress', $user.StreetAddress) }
if ($user.PSObject.Properties.Name -contains 'City' -and -not [string]::IsNullOrWhiteSpace($user.City)) { $userParams.Add('City', $user.City) }
if ($user.PSObject.Properties.Name -contains 'State' -and -not [string]::IsNullOrWhiteSpace($user.State)) { $userParams.Add('State', $user.State) }
if ($user.PSObject.Properties.Name -contains 'PostalCode' -and -not [string]::IsNullOrWhiteSpace($user.PostalCode)) { $userParams.Add('PostalCode', $user.PostalCode) }
if ($user.PSObject.Properties.Name -contains 'Country' -and -not [string]::IsNullOrWhiteSpace($user.Country)) { $userParams.Add('Country', $user.Country) }
# Perfil Web
if ($user.PSObject.Properties.Name -contains 'HomePage' -and -not [string]::IsNullOrWhiteSpace($user.HomePage)) { $userParams.Add('HomePage', $user.HomePage) }
# Controle da Conta
if ($user.PSObject.Properties.Name -contains 'AccountExpirationDate' -and $user.AccountExpirationDate) { $userParams.Add('AccountExpirationDate', ([datetime]$user.AccountExpirationDate)) }
if ($user.PSObject.Properties.Name -contains 'PasswordNeverExpires' -and ($user.PasswordNeverExpires -eq 'True' -or $user.PasswordNeverExpires -is [bool] -and $user.PasswordNeverExpires)) { $userParams.Add('PasswordNeverExpires', $true) }
if ($user.PSObject.Properties.Name -contains 'CannotChangePassword' -and ($user.CannotChangePassword -eq 'True' -or $user.CannotChangePassword -is [bool] -and $user.CannotChangePassword)) { $userParams.Add('CannotChangePassword', $true) }
# Atributos Customizados
if ($user.PSObject.Properties.Name -contains 'extensionAttribute1' -and -not [string]::IsNullOrWhiteSpace($user.extensionAttribute1)) { $userParams.Add('extensionAttribute1', $user.extensionAttribute1) }
if ($user.PSObject.Properties.Name -contains 'extensionAttribute2' -and -not [string]::IsNullOrWhiteSpace($user.extensionAttribute2)) { $userParams.Add('extensionAttribute2', $user.extensionAttribute2) }
# Lógica para Múltiplos E-mails (ProxyAddresses)
if ($user.PSObject.Properties.Name -contains 'ProxyAddresses' -and -not [string]::IsNullOrWhiteSpace($user.ProxyAddresses)) {
$proxyList = @("SMTP:" + $user.UserPrincipalName)
$aliases = $user.ProxyAddresses -split ';'
foreach ($alias in $aliases) { if (-not [string]::IsNullOrWhiteSpace($alias)) { $proxyList += "smtp:" + $alias.Trim() } }
$userParams.Add('ProxyAddresses', $proxyList)
}
# Lógica para Gestor (Manager)
if ($user.PSObject.Properties.Name -contains 'Manager' -and -not [string]::IsNullOrWhiteSpace($user.Manager)) {
$managerUser = Get-ADUser -Filter "SamAccountName -eq '$($user.Manager)'" -Server $ClientADServer -Credential $ClientADCredential
if ($managerUser) {
$userParams.Add('Manager', $managerUser.DistinguishedName)
} else {
Write-Log -Level 'WARN' -Message "Gestor '$($user.Manager)' não encontrado para o usuário '$($user.SamAccountName)'. O campo não será preenchido."
}
}
# Criação do usuário no AD
New-ADUser @userParams -Server $ClientADServer -Credential $ClientADCredential -ErrorAction Stop
Write-Log -Level 'INFO' -Message "SUCESSO: Conta '$($user.SamAccountName)' criada no AD do Cliente. Senha temporária gerada." -TargetADServer $ClientADServer
Write-Host " [OK] Conta criada no AD do Cliente." -ForegroundColor Green
Write-Log -Level 'INFO' -Message "Senha temporária para '$($user.SamAccountName)': $tempPassword"
$clientADSuccess = $true
}
catch {
$errorMessage = "FALHA ao criar a conta '$($user.SamAccountName)' no AD do Cliente. Erro: $($_.Exception.Message -replace "`r`n"," ")"
Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ClientADServer
Write-Host " [ERRO] Falha ao criar no AD do Cliente." -ForegroundColor Red
continue
}
if ($clientADSuccess) {
try {
Start-Sleep -Seconds 5
Write-Log -Level 'INFO' -Message "Tentando habilitar a mailbox para '$($user.SamAccountName)' no AD do Exchange em '$ExchangeADServer'." -TargetADServer $ExchangeADServer
Enable-Mailbox -Identity $user.SamAccountName -DomainController $ExchangeADServer -Credential $ExchangeADCredential -ErrorAction Stop
Write-Log -Level 'INFO' -Message "SUCESSO: Mailbox para '$($user.SamAccountName)' habilitada no ambiente Exchange." -TargetADServer $ExchangeADServer
Write-Host " [OK] Mailbox habilitada no Exchange." -ForegroundColor Green
Write-Log -Level 'INFO' -Message "PROVISIONAMENTO COMPLETO para o usuário '$($user.SamAccountName)'."
}
catch {
$errorMessage = "FALHA PARCIAL: A conta '$($user.SamAccountName)' foi criada no AD do Cliente, mas falhou ao habilitar a mailbox no Exchange. Erro: $($_.Exception.Message -replace "`r`n"," ")"
Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ExchangeADServer
Write-Host " [ERRO] Falha ao habilitar a mailbox no Exchange." -ForegroundColor Red
}
}
}
Write-Host "Processamento concluído."