<# .SYNOPSIS Cria um novo usuário no Active Directory com um conjunto completo de atributos e habilita sua caixa de correio no Exchange. .DESCRIPTION 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") em ambos os ambientes (AD do Cliente e AD do Exchange) antes de qualquer operação de escrita, garantindo que OUs existam e que usuários não sejam duplicados ou sobrescritos. .NOTES Autor: Gemini (Especialista NGINX & Automação) Data: 15 de outubro de 2025 Versão: 2.4 - Corrigido erro de sintaxe (chave extra no final do arquivo). Contexto: Projeto Plataforma Unificada de Trabalho Digital #> function New-UnifiedADUser { [CmdletBinding(DefaultParameterSetName = 'SingleUser')] param( # --- Parâmetros de Conexão e Autenticação (Obrigatórios para todas as operações) --- [Parameter(Mandatory = $true, Position = 0, HelpMessage = "IP ou FQDN do Domain Controller principal (cliente).")] [string]$ClientADServer, [Parameter(Mandatory = $true, Position = 1, HelpMessage = "Credencial (usuário e senha) para autenticar no AD do cliente.")] [pscredential]$ClientADCredential, [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" ) # --- LÓGICA PRINCIPAL --- # --- 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 { 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." 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)" "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [ERROR] - $dbErrorMessage" | Out-File -FilePath $LogFilePath -Append } $logEntry | Out-File -FilePath $LogFilePath -Append if (-not [string]::IsNullOrEmpty($TargetADServer)) { try { $eventMessage = "Script 'New-UnifiedADUser.ps1' executado por '$scriptUser'.`nDetalhes: $Message" $eventLevel = switch ($Level) { 'INFO' {'Information'} 'WARN' {'Warning'} 'ERROR' {'Error'} } Invoke-Command -ComputerName $TargetADServer -ScriptBlock { param($eventMessage, $eventLevel) $logSource = "Provisionamento de Usuários AD" if (-not ([System.Diagnostics.EventLog]::SourceExists($logSource))) { New-EventLog -LogName 'Application' -Source $logSource } Write-EventLog -LogName 'Application' -Source $logSource -EventId 1001 -EntryType $eventLevel -Message $eventMessage } -ArgumentList $eventMessage, $eventLevel Write-Verbose "Log de evento escrito em '$TargetADServer'." } catch { $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 } } } function Show-Usage { Write-Host @" 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) ... "@ }