implementaçao dos scripts para mudar senha e foto, assim como o mockup da tela de mudança
This commit is contained in:
parent
5ccc8653f6
commit
d984a5311f
|
|
@ -0,0 +1,218 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MVP - Minha Conta</title>
|
||||
<style>
|
||||
body { font-family: Arial, Helvetica, sans-serif; background-color: #f4f4f9; color: #333; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; flex-direction: column; }
|
||||
.container { background-color: #ffffff; padding: 30px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); max-width: 500px; width: 100%; margin-top: 20px;}
|
||||
h1 { color: #2c3e50; text-align: center; border-bottom: 2px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 30px; }
|
||||
.form-group { margin-bottom: 20px; }
|
||||
label { display: block; margin-bottom: 8px; font-weight: bold; color: #555; }
|
||||
input[type="email"], input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #3498db; /* Cor principal azul */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
transition: background-color 0.3s;
|
||||
overflow: hidden; /* Garante que os elementos internos não ultrapassem as bordas arredondadas */
|
||||
}
|
||||
button:hover:not(:disabled) { background-color: #2980b9; }
|
||||
button:disabled { cursor: wait; }
|
||||
|
||||
/* Estilos para o preview da imagem */
|
||||
.photo-upload-container { text-align: center; margin-bottom: 30px; }
|
||||
#image_preview {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 3px solid #ecf0f1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
#image_preview:hover { transform: scale(1.05); }
|
||||
#profile_image_input { display: none; }
|
||||
|
||||
/* Estilos para os estados do botão */
|
||||
.button-content { display: flex; justify-content: center; align-items: center; }
|
||||
.button-text, .button-loader, .button-success-icon { transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; }
|
||||
|
||||
.button-loader {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
border-top-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.button-success-icon {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
button.sending { background-color: #95a5a6; }
|
||||
button.sending .button-text { opacity: 0; transform: translateY(-10px); }
|
||||
button.sending .button-loader { opacity: 1; }
|
||||
|
||||
button.success { background-color: #27ae60; }
|
||||
button.success .button-loader { opacity: 0; }
|
||||
button.success .button-success-icon { opacity: 1; transform: scale(1.2); }
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<form id="profile-form">
|
||||
<h1>Minha Conta</h1>
|
||||
|
||||
<div class="form-group photo-upload-container">
|
||||
<img id="image_preview" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150' viewBox='0 0 24 24' fill='%23f0f0f1' stroke='%23ccc' stroke-width='1' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E" alt="Preview da foto de perfil" title="Clique para selecionar uma nova foto de perfil">
|
||||
<input type="file" id="profile_image_input" name="ImagePath" accept="image/png, image/jpeg">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user_email">E-mail</label>
|
||||
<input type="email" id="user_email" name="UserEmail" placeholder="seu.usuario@dominio.com" required title="Seu e-mail de login. Não pode ser alterado.">
|
||||
</div>
|
||||
|
||||
<hr style="border: 1px solid #f0f0f0; margin: 30px 0;">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="old_password">Senha Atual</label>
|
||||
<input type="password" id="old_password" name="OldPassword" placeholder="Necessária para salvar" required title="Sua senha atual é necessária para confirmar qualquer alteração.">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new_password">Nova Senha</label>
|
||||
<input type="password" id="new_password" name="NewPassword" placeholder="Deixe em branco para não alterar" title="Insira sua nova senha. Mínimo de 8 caracteres.">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Confirmar Nova Senha</label>
|
||||
<input type="password" id="confirm_password" name="ConfirmNewPassword" placeholder="Repita a nova senha" title="As senhas devem ser idênticas.">
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submit-button">
|
||||
<span class="button-content">
|
||||
<span class="button-text">Salvar Alterações</span>
|
||||
<span class="button-loader"></span>
|
||||
<span class="button-success-icon">✓</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const profileForm = document.getElementById('profile-form');
|
||||
const imagePreview = document.getElementById('image_preview');
|
||||
const imageInput = document.getElementById('profile_image_input');
|
||||
const submitButton = document.getElementById('submit-button');
|
||||
// A constante agora guarda o SVG embutido para poder restaurá-lo após o refresh.
|
||||
const placeholderImage = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150' viewBox='0 0 24 24' fill='%23f0f0f1' stroke='%23ccc' stroke-width='1' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E";
|
||||
|
||||
// --- Lógica para o Preview da Imagem ---
|
||||
imagePreview.addEventListener('click', () => imageInput.click());
|
||||
|
||||
imageInput.addEventListener('change', (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => { imagePreview.src = e.target.result; };
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Lógica Unificada de Submissão do Formulário ---
|
||||
profileForm.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
// Coleta de dados
|
||||
const email = document.getElementById('user_email').value;
|
||||
const oldPassword = document.getElementById('old_password').value;
|
||||
const newPassword = document.getElementById('new_password').value;
|
||||
const confirmPassword = document.getElementById('confirm_password').value;
|
||||
const fileSelected = imageInput.files.length > 0;
|
||||
|
||||
// Validações
|
||||
if (!email || !oldPassword) {
|
||||
console.warn('VALIDAÇÃO FALHOU: E-mail e Senha Atual são obrigatórios.');
|
||||
return;
|
||||
}
|
||||
if (newPassword && newPassword !== confirmPassword) {
|
||||
console.warn('VALIDAÇÃO FALHOU: A nova senha e a confirmação não são idênticas.');
|
||||
return;
|
||||
}
|
||||
if (!fileSelected && !newPassword) {
|
||||
console.info('INFO: Nenhuma alteração detectada (foto ou senha). O envio foi cancelado.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Inicia a animação de envio
|
||||
submitButton.classList.add('sending');
|
||||
submitButton.disabled = true;
|
||||
console.log(`%c[${new Date().toLocaleTimeString()}] INICIANDO ENVIO...`, 'color: #3498db; font-weight: bold;');
|
||||
|
||||
// 2. Simula um delay de rede de 2 segundos
|
||||
setTimeout(() => {
|
||||
// Log de simulação no console
|
||||
const samAccountName = email.split('@')[0];
|
||||
let actions = [];
|
||||
if (fileSelected) actions.push({ action: "Atualização de Foto de Perfil", file: imageInput.files[0].name, script: "muda-imagem-perfil.ps1" });
|
||||
if (newPassword) actions.push({ action: "Alteração de Senha", validation: "SUCESSO (simulado)", script: "muda-senha-usuario.ps1" });
|
||||
|
||||
console.group(`%c[${new Date().toLocaleTimeString()}] RESPOSTA DA SIMULAÇÃO DE BACKEND`, 'color: #27ae60; font-weight: bold;');
|
||||
console.log("DESTINO: API de Atualização de Perfil");
|
||||
console.log("USUÁRIO:", samAccountName);
|
||||
console.log("AÇÕES EXECUTADAS:");
|
||||
console.table(actions);
|
||||
console.groupEnd();
|
||||
|
||||
// 3. Transição para o estado de "sucesso"
|
||||
submitButton.classList.remove('sending');
|
||||
submitButton.classList.add('success');
|
||||
|
||||
// 4. Aguarda 1.5s no estado de "sucesso" e depois faz o refresh
|
||||
setTimeout(() => {
|
||||
submitButton.classList.remove('success');
|
||||
submitButton.disabled = false;
|
||||
|
||||
// Limpa os campos do formulário (refresh)
|
||||
document.getElementById('new_password').value = '';
|
||||
document.getElementById('confirm_password').value = '';
|
||||
document.getElementById('old_password').value = '';
|
||||
imageInput.value = ''; // Limpa a seleção do arquivo
|
||||
imagePreview.src = placeholderImage; // Restaura a imagem padrão
|
||||
|
||||
console.log(`%c[${new Date().toLocaleTimeString()}] INTERFACE ATUALIZADA E PRONTA.`, 'color: #9b59b6;');
|
||||
}, 1500);
|
||||
|
||||
}, 2000);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
# 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Atualiza a foto de perfil (thumbnailPhoto) de um usuário no Active Directory.
|
||||
|
||||
.DESCRIPTION
|
||||
Versão 1.0. Componente da "Plataforma Unificada de Trabalho Digital", este script é responsável por
|
||||
processar uma imagem, validá-la, redimensioná-la para um formato adequado e, em seguida,
|
||||
atualizar o atributo 'thumbnailPhoto' do usuário especificado no Active Directory.
|
||||
O script inclui validações de tamanho de arquivo para garantir conformidade com os limites do AD.
|
||||
|
||||
.NOTES
|
||||
Autor: Gemini (Especialista NGINX & Automação)
|
||||
Data: 15 de outubro de 2025
|
||||
Versão: 1.0
|
||||
Contexto: Módulo "Atualização de Foto de Perfil" do Portal de Autoatendimento.
|
||||
#>
|
||||
|
||||
function Set-UnifiedADUserPhoto {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# --- Parâmetros de Conexão e Autenticação (Obrigatórios) ---
|
||||
[Parameter(Mandatory = $true, HelpMessage = "IP ou FQDN do Domain Controller principal (cliente).")]
|
||||
[string]$ClientADServer,
|
||||
|
||||
[Parameter(Mandatory = $true, HelpMessage = "Credencial (usuário e senha) da conta de serviço para autenticar no AD do cliente.")]
|
||||
[pscredential]$ClientADCredential,
|
||||
|
||||
# --- Parâmetros do Usuário (Obrigatórios) ---
|
||||
[Parameter(Mandatory = $true, HelpMessage = "O nome de logon (SamAccountName) do usuário cuja foto será alterada.")]
|
||||
[string]$SamAccountName,
|
||||
|
||||
[Parameter(Mandatory = $true, HelpMessage = "O caminho completo para o arquivo de imagem (JPG/PNG) a ser carregado.")]
|
||||
[string]$ImagePath,
|
||||
|
||||
# --- Parâmetros de Logging ---
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$LogFilePath = ".\Set-UnifiedADUserPhoto-Log-$(Get-Date -Format 'yyyy-MM-dd').txt"
|
||||
)
|
||||
|
||||
# --- LÓGICA PRINCIPAL ---
|
||||
|
||||
# Validação da existência da função de log, para consistência com outros scripts
|
||||
if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "[ERRO FATAL] A função auxiliar 'Write-Log' não foi encontrada. O script não pode continuar." -ForegroundColor Red
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
Import-Module ActiveDirectory -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level 'ERROR' -Message "Falha ao importar o módulo ActiveDirectory. Verifique se as Ferramientas de Administração de Servidor Remoto (RSAT) estão instaladas."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Log -Level 'INFO' -Message "Iniciando tentativa de atualização de foto para '$SamAccountName'."
|
||||
|
||||
# --- Bloco 1: Validação e Processamento da Imagem ---
|
||||
if (-not (Test-Path -Path $ImagePath -PathType Leaf)) {
|
||||
Write-Log -Level 'ERROR' -Message "FALHA: O arquivo de imagem especificado não foi encontrado em '$ImagePath'."
|
||||
Write-Host " [ERRO] O arquivo de imagem não foi encontrado." -ForegroundColor Red
|
||||
return
|
||||
}
|
||||
|
||||
$imageBytes = $null
|
||||
try {
|
||||
Write-Host " Processando imagem de '$ImagePath'..."
|
||||
# Carrega a biblioteca .NET para manipulação de imagens
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
# Carrega a imagem do arquivo
|
||||
$image = [System.Drawing.Image]::FromFile($ImagePath)
|
||||
|
||||
# Define o tamanho máximo em bytes (95 KB para segurança)
|
||||
$maxSizeInBytes = 97280
|
||||
|
||||
# Redimensiona a imagem para um padrão (ex: 240x240 pixels)
|
||||
$newBitmap = New-Object System.Drawing.Bitmap(240, 240)
|
||||
$graphics = [System.Drawing.Graphics]::FromImage($newBitmap)
|
||||
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
||||
$graphics.DrawImage($image, 0, 0, 240, 240)
|
||||
|
||||
# Salva a imagem processada em um fluxo de memória para obter os bytes
|
||||
$memoryStream = New-Object System.IO.MemoryStream
|
||||
# Salva como JPEG para melhor compressão
|
||||
$newBitmap.Save($memoryStream, [System.Drawing.Imaging.ImageFormat]::Jpeg)
|
||||
|
||||
$imageBytes = $memoryStream.ToArray()
|
||||
|
||||
# Limpeza de recursos para evitar memory leaks
|
||||
$graphics.Dispose()
|
||||
$image.Dispose()
|
||||
$newBitmap.Dispose()
|
||||
$memoryStream.Dispose()
|
||||
|
||||
# Validação final do tamanho
|
||||
if ($imageBytes.Length -gt $maxSizeInBytes) {
|
||||
throw "A imagem processada excede o limite de tamanho do AD ($($maxSizeInBytes / 1KB) KB). Tamanho final: $($imageBytes.Length / 1KB) KB."
|
||||
}
|
||||
|
||||
Write-Host " [OK] Imagem processada e validada com sucesso. Tamanho: $($imageBytes.Length / 1KB) KB." -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
$errorMessage = "FALHA no processamento da imagem para '$SamAccountName'. Erro: $($_.Exception.Message -replace "`r`n"," ")"
|
||||
Write-Log -Level 'ERROR' -Message $errorMessage
|
||||
Write-Host " [ERRO] Falha ao processar a imagem." -ForegroundColor Red
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
# --- Bloco 2: Atualização no Active Directory ---
|
||||
if ($imageBytes) {
|
||||
try {
|
||||
Write-Host " Tentando atualizar o atributo 'thumbnailPhoto' no AD do Cliente..."
|
||||
Set-ADUser -Identity $SamAccountName -Replace @{thumbnailPhoto = $imageBytes} -Server $ClientADServer -Credential $ClientADCredential -ErrorAction Stop
|
||||
|
||||
Write-Log -Level 'INFO' -Message "SUCESSO: Foto de perfil para '$SamAccountName' atualizada no AD do Cliente." -TargetADServer $ClientADServer
|
||||
Write-Host " [OK] Foto de perfil atualizada com sucesso no Active Directory." -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
$errorMessage = "FALHA ao atualizar a foto de '$SamAccountName' no AD do Cliente. Erro: $($_.Exception.Message -replace "`r`n"," ")"
|
||||
Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ClientADServer
|
||||
Write-Host " [ERRO] Falha ao gravar a imagem no Active Directory." -ForegroundColor Red
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Processo de atualização de foto concluído."
|
||||
}
|
||||
|
||||
|
||||
# --- FUNÇÃO AUXILIAR DE LOG (Exemplo, assumindo que já existe no seu ambiente) ---
|
||||
# Mantenha sua função de log centralizada. Esta é uma versão mínima para permitir testes autônomos.
|
||||
function Write-Log {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)] [string]$Message,
|
||||
[Parameter(Mandatory=$true)] [ValidateSet('INFO', 'WARN', 'ERROR')] [string]$Level,
|
||||
[Parameter(Mandatory=$false)] [string]$TargetADServer
|
||||
)
|
||||
$LogFilePath = ".\Set-UnifiedADUserPhoto-Log-$(Get-Date -Format 'yyyy-MM-dd').txt"
|
||||
$logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [$Level] - $Message"
|
||||
$logEntry | Out-File -FilePath $LogFilePath -Append
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Altera a senha de um usuário de forma sincronizada em dois ambientes Active Directory (Cliente e Exchange).
|
||||
|
||||
.DESCRIPTION
|
||||
Versão 1.0. Este script é um componente crítico da "Plataforma Unificada de Trabalho Digital".
|
||||
Ele primeiro valida a senha antiga do usuário e, se bem-sucedido, aplica a nova senha em ambos os Domain Controllers.
|
||||
O script foi projetado para tratar falhas parciais (ex: sucesso no primeiro AD, falha no segundo)
|
||||
gerando logs específicos para alertar sobre a dessincronização.
|
||||
|
||||
.NOTES
|
||||
Autor: Gemini (Especialista NGINX & Automação)
|
||||
Data: 15 de outubro de 2025
|
||||
Versão: 1.0
|
||||
Contexto: Módulo "Alteração de Senha (Self-Service)" do Portal de Autoatendimento.
|
||||
#>
|
||||
|
||||
function Set-UnifiedADUserPassword {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# --- Parâmetros de Conexão e Autenticação (Obrigatórios) ---
|
||||
[Parameter(Mandatory = $true, HelpMessage = "IP ou FQDN do Domain Controller principal (cliente).")]
|
||||
[string]$ClientADServer,
|
||||
|
||||
[Parameter(Mandatory = $true, HelpMessage = "Credencial (usuário e senha) da conta de serviço para autenticar no AD do cliente.")]
|
||||
[pscredential]$ClientADCredential,
|
||||
|
||||
[Parameter(Mandatory = $true, HelpMessage = "IP ou FQDN do Domain Controller do ambiente Exchange.")]
|
||||
[string]$ExchangeADServer,
|
||||
|
||||
[Parameter(Mandatory = $true, HelpMessage = "Credencial (usuário e senha) da conta de serviço para autenticar no AD do Exchange.")]
|
||||
[pscredential]$ExchangeADCredential,
|
||||
|
||||
# --- Parâmetros do Usuário (Obrigatórios) ---
|
||||
[Parameter(Mandatory = $true, HelpMessage = "O nome de logon do usuário (SamAccountName) cuja senha será alterada.")]
|
||||
[string]$SamAccountName,
|
||||
|
||||
[Parameter(Mandatory = $true, HelpMessage = "A senha ANTIGA do usuário. Deve ser um objeto SecureString.")]
|
||||
[System.Security.SecureString]$OldPassword,
|
||||
|
||||
[Parameter(Mandatory = $true, HelpMessage = "A NOVA senha do usuário. Deve ser um objeto SecureString.")]
|
||||
[System.Security.SecureString]$NewPassword,
|
||||
|
||||
# --- Parâmetros de Logging ---
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$LogFilePath = ".\Set-UnifiedADUserPassword-Log-$(Get-Date -Format 'yyyy-MM-dd').txt"
|
||||
)
|
||||
|
||||
# --- LÓGICA PRINCIPAL ---
|
||||
|
||||
# Dependência da função Write-Log (deve estar no mesmo escopo ou módulo)
|
||||
if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "[ERRO FATAL] A função auxiliar 'Write-Log' não foi encontrada. O script não pode continuar." -ForegroundColor Red
|
||||
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-Log -Level 'INFO' -Message "Iniciando tentativa de alteração de senha para '$SamAccountName'."
|
||||
|
||||
$clientADSuccess = $false
|
||||
|
||||
# --- Bloco 1: Alteração no Active Directory do Cliente ---
|
||||
# O cmdlet Set-ADAccountPassword valida a senha antiga e define a nova em uma única operação.
|
||||
try {
|
||||
Write-Host " Tentando alterar a senha no AD do Cliente..."
|
||||
Set-ADAccountPassword -Identity $SamAccountName -OldPassword $OldPassword -NewPassword $NewPassword -Server $ClientADServer -Credential $ClientADCredential -ErrorAction Stop
|
||||
|
||||
$clientADSuccess = $true
|
||||
Write-Log -Level 'INFO' -Message "SUCESSO: Senha para '$SamAccountName' alterada no AD do Cliente." -TargetADServer $ClientADServer
|
||||
Write-Host " [OK] Senha alterada com sucesso no AD do Cliente." -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
# A falha aqui geralmente significa "senha antiga incorreta" ou "política de complexidade não atendida".
|
||||
$errorMessage = "FALHA ao alterar a senha de '$SamAccountName' no AD do Cliente. Erro: $($_.Exception.Message -replace "`r`n"," ")"
|
||||
Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ClientADServer
|
||||
Write-Host " [ERRO] Falha ao alterar senha no AD do Cliente. Verifique a senha antiga e as políticas de complexidade." -ForegroundColor Red
|
||||
# Não continuamos se o primeiro passo falhar.
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
# --- Bloco 2: Alteração no Active Directory do Exchange ---
|
||||
# Este bloco só executa se o primeiro foi bem-sucedido.
|
||||
if ($clientADSuccess) {
|
||||
try {
|
||||
Write-Host " Tentando alterar a senha no AD do Exchange para manter a sincronia..."
|
||||
# Repetimos a operação no segundo AD
|
||||
Set-ADAccountPassword -Identity $SamAccountName -OldPassword $OldPassword -NewPassword $NewPassword -Server $ExchangeADServer -Credential $ExchangeADCredential -ErrorAction Stop
|
||||
|
||||
Write-Log -Level 'INFO' -Message "SUCESSO: Senha para '$SamAccountName' alterada no AD do Exchange." -TargetADServer $ExchangeADServer
|
||||
Write-Host " [OK] Senha alterada com sucesso no AD do Exchange." -ForegroundColor Green
|
||||
Write-Log -Level 'INFO' -Message "SINCRONIZAÇÃO COMPLETA: A senha de '$SamAccountName' foi alterada com sucesso em ambos os ambientes."
|
||||
}
|
||||
catch {
|
||||
# ESTE É O CENÁRIO CRÍTICO DE DESSINCRONIZAÇÃO
|
||||
$errorMessage = "FALHA DE SINCRONIZAÇÃO: A senha de '$SamAccountName' FOI ALTERADA no AD do Cliente, mas FALHOU no AD do Exchange. AÇÃO MANUAL PODE SER NECESSÁRIA. Erro: $($_.Exception.Message -replace "`r`n"," ")"
|
||||
Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ExchangeADServer
|
||||
Write-Host " [ERRO CRÍTICO] Falha ao sincronizar a senha no AD do Exchange. A senha foi alterada apenas no primeiro ambiente!" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Processo de alteração de senha concluído."
|
||||
}
|
||||
|
||||
# --- FUNÇÃO AUXILIAR DE LOG (Exemplo, assumindo que já existe no seu ambiente) ---
|
||||
# Coloque aqui a sua função Write-Log ou carregue-a de outro arquivo .ps1
|
||||
# Exemplo mínimo para o script funcionar de forma autônoma:
|
||||
function Write-Log {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)] [string]$Message,
|
||||
[Parameter(Mandatory=$true)] [ValidateSet('INFO', 'WARN', 'ERROR')] [string]$Level,
|
||||
[Parameter(Mandatory=$false)] [string]$TargetADServer
|
||||
)
|
||||
$LogFilePath = ".\Set-UnifiedADUserPassword-Log-$(Get-Date -Format 'yyyy-MM-dd').txt"
|
||||
$logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [$Level] - $Message"
|
||||
$logEntry | Out-File -FilePath $LogFilePath -Append
|
||||
}
|
||||
Loading…
Reference in New Issue