#Requires -Version 5.1 <# .SYNOPSIS Configuración inicial de Windows 10. .DESCRIPTION Configura una cuenta técnica llamada "tecnica" y habilita Escritorio Remoto. Características: - Se autoeleva a Administrador. - Crea "tecnica" si no existe. - Solicita y confirma la contraseña al crearla. - NO cambia la contraseña si "tecnica" ya existe. - Añade "tecnica" al grupo Administradores. - Activa Escritorio Remoto. - Configura el Firewall para RDP. - Añade "tecnica" al grupo Usuarios de escritorio remoto. - Oculta "tecnica" de la lista de usuarios del login. - Verifica el estado real al finalizar. - Registra todos los pasos y errores en %TEMP%\crb-setup.log. - Devuelve 0 si todo está correcto. - Devuelve 1 si hay algún error. .NOTES Compatible con Windows 10 / PowerShell 5.1. #> # ============================================================ # CONFIGURACIÓN # ============================================================ $Username = "tecnica" # Ruta del log de diagnóstico. $LogPath = Join-Path $env:TEMP "crb-setup.log" # ============================================================ # CONFIGURACIÓN DE ERRORES # ============================================================ $ErrorActionPreference = "Stop" # ============================================================ # FUNCIONES DE SALIDA # ============================================================ function Write-Step { param ( [string]$Message ) Write-Host "" Write-Host "==================================================" -ForegroundColor DarkGray Write-Host $Message -ForegroundColor Cyan Write-Host "==================================================" -ForegroundColor DarkGray Write-Log "[PASO] $Message" } function Write-Ok { param ( [string]$Message ) Write-Host "[OK] $Message" -ForegroundColor Green Write-Log "[OK] $Message" } function Write-WarningMessage { param ( [string]$Message ) Write-Host "[AVISO] $Message" -ForegroundColor Yellow Write-Log "[AVISO] $Message" } function Write-ErrorMessage { param ( [string]$Message ) Write-Host "[ERROR] $Message" -ForegroundColor Red Write-Log "[ERROR] $Message" } # ============================================================ # ESCRIBIR EN LOG # ============================================================ function Write-Log { param ( [string]$Message ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" try { Add-Content ` -LiteralPath $LogPath ` -Value "$timestamp $Message" ` -Encoding UTF8 ` -ErrorAction SilentlyContinue } catch { # El log nunca debe interrumpir la ejecución del script. } } # ============================================================ # OBTENER MENSAJE COMPLETO DE UNA EXCEPCIÓN # ============================================================ function Get-ExceptionMessage { param ( [Parameter(Mandatory)] [System.Exception]$Exception ) $parts = New-Object System.Collections.Generic.List[string] $current = $Exception while ($null -ne $current) { $parts.Add($current.Message) $current = $current.InnerException } return ($parts -join " << ") } # ============================================================ # COMPROBAR ADMINISTRADOR # ============================================================ function Test-IsAdministrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) return $principal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ) } # ============================================================ # AUTOELEVAR # ============================================================ function Ensure-Administrator { if (Test-IsAdministrator) { return } Write-Host "" Write-WarningMessage "Se necesitan permisos de Administrador." Write-Host "Solicitando elevación mediante UAC..." -ForegroundColor Yellow Write-Host "" if (-not $PSCommandPath) { Write-ErrorMessage "No se puede determinar la ruta del script." exit 1 } # -------------------------------------------------------- # ELEVACIÓN DESDE RUTA DE RED (UNC) # -------------------------------------------------------- # El proceso elevado puede perder el acceso a la carpeta de # red (\\servidor\...). Para evitarlo, se copia el script # a %TEMP% y se eleva ejecutando la copia local. $scriptPath = $PSCommandPath $tempCopy = $null if ($scriptPath -like "\\*") { Write-WarningMessage "El script está en una ruta de red (UNC)." Write-Host "Copiando a la carpeta temporal para poder elevarse..." ` -ForegroundColor Yellow $tempCopy = Join-Path $env:TEMP ( "crb-setup-" + [guid]::NewGuid().ToString("N") + ".ps1" ) try { Copy-Item ` -LiteralPath $PSCommandPath ` -Destination $tempCopy ` -Force ` -ErrorAction Stop } catch { Write-ErrorMessage "No se pudo copiar el script a la carpeta temporal." Write-ErrorMessage $_.Exception.Message exit 1 } $scriptPath = $tempCopy } $quotedScript = '"' + $scriptPath + '"' try { $process = Start-Process ` -FilePath "powershell.exe" ` -ArgumentList @( "-NoProfile" "-ExecutionPolicy" "Bypass" "-File" $quotedScript ) ` -Verb RunAs ` -Wait ` -PassThru ` -ErrorAction Stop } catch { if ($tempCopy) { Remove-Item ` -LiteralPath $tempCopy ` -Force ` -ErrorAction SilentlyContinue } Write-ErrorMessage "No se pudo obtener permisos de Administrador." Write-ErrorMessage $_.Exception.Message exit 1 } if ($tempCopy) { Remove-Item ` -LiteralPath $tempCopy ` -Force ` -ErrorAction SilentlyContinue } exit $process.ExitCode } # ============================================================ # OBTENER USUARIO LOCAL # ============================================================ function Get-LocalUserSafe { param ( [Parameter(Mandatory)] [string]$Name ) try { return Get-LocalUser -Name $Name -ErrorAction Stop } catch { return $null } } # ============================================================ # OBTENER GRUPO LOCAL MEDIANTE SID # ============================================================ function Get-LocalGroupBySid { param ( [Parameter(Mandatory)] [string]$Sid ) $group = Get-LocalGroup -ErrorAction Stop | Where-Object { $_.SID.Value -eq $Sid } | Select-Object -First 1 if ($null -eq $group) { throw "No se encontró el grupo con SID $Sid." } return $group } # ============================================================ # COMPROBAR USUARIO EN GRUPO # ============================================================ function Test-UserInGroup { param ( [Parameter(Mandatory)] [string]$Username, [Parameter(Mandatory)] $Group ) try { $members = Get-LocalGroupMember ` -Group $Group.Name ` -ErrorAction Stop foreach ($member in $members) { $memberName = $member.Name if ($memberName -match "\\") { $memberName = $memberName.Split("\")[-1] } if ($memberName -ieq $Username) { return $true } } return $false } catch { return $false } } # ============================================================ # AÑADIR USUARIO A GRUPO # ============================================================ function Ensure-UserInGroup { param ( [Parameter(Mandatory)] [string]$Username, [Parameter(Mandatory)] $Group ) if (Test-UserInGroup -Username $Username -Group $Group) { Write-Ok "'$Username' ya pertenece a '$($Group.Name)'." return } Write-Host "" Write-Host "Añadiendo '$Username' a '$($Group.Name)'..." ` -ForegroundColor Cyan Add-LocalGroupMember ` -Group $Group.Name ` -Member $Username ` -ErrorAction Stop # Verificación inmediata. if (-not (Test-UserInGroup -Username $Username -Group $Group)) { throw "El usuario '$Username' no aparece en '$($Group.Name)' después de añadirlo." } Write-Ok "'$Username' pertenece ahora a '$($Group.Name)'." } # ============================================================ # PEDIR Y CONFIRMAR CONTRASEÑA # ============================================================ function Read-ConfirmedPassword { while ($true) { Write-Host "" Write-Host "Introduce la contraseña para '$Username'." ` -ForegroundColor Cyan $password1 = Read-Host "Contraseña" -AsSecureString $password2 = Read-Host "Repite la contraseña" -AsSecureString $ptr1 = [IntPtr]::Zero $ptr2 = [IntPtr]::Zero $plain1 = $null $plain2 = $null try { $ptr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR( $password1 ) $ptr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR( $password2 ) $plain1 = [Runtime.InteropServices.Marshal]::PtrToStringBSTR( $ptr1 ) $plain2 = [Runtime.InteropServices.Marshal]::PtrToStringBSTR( $ptr2 ) if ([string]::IsNullOrWhiteSpace($plain1)) { Write-ErrorMessage "La contraseña no puede estar vacía." continue } if ($plain1 -cne $plain2) { Write-ErrorMessage "Las contraseñas no coinciden." continue } return $password1 } finally { if ($ptr1 -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr1) } if ($ptr2 -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr2) } $plain1 = $null $plain2 = $null } } } # ============================================================ # CREAR / COMPROBAR USUARIO TÉCNICO # ============================================================ function Ensure-TechnicalUser { param ( [Parameter(Mandatory)] [string]$Name ) $existingUser = Get-LocalUserSafe -Name $Name # -------------------------------------------------------- # EL USUARIO YA EXISTE # -------------------------------------------------------- if ($null -ne $existingUser) { Write-Ok "El usuario '$Name' ya existe." return $existingUser } # -------------------------------------------------------- # CREAR USUARIO # -------------------------------------------------------- Write-Host "" Write-Host "El usuario '$Name' no existe." ` -ForegroundColor Yellow $password = Read-ConfirmedPassword Write-Host "" Write-Host "Creando usuario '$Name'..." ` -ForegroundColor Cyan try { New-LocalUser ` -Name $Name ` -Password $password ` -Description "Cuenta técnica de administración" ` -AccountNeverExpires ` -PasswordNeverExpires ` -ErrorAction Stop | Out-Null # Verificación real. $verifiedUser = Get-LocalUserSafe -Name $Name if ($null -eq $verifiedUser) { throw "New-LocalUser terminó pero el usuario '$Name' no existe." } Write-Ok "Usuario '$Name' creado correctamente." return $verifiedUser } catch { throw "No se pudo crear el usuario '$Name': $($_.Exception.Message)" } } # ============================================================ # OBTENER REGLAS DE FIREWALL DE ESCRITORIO REMOTO # ============================================================ function Get-RdpFirewallRules { $rdpGroupNames = @("Remote Desktop", "Escritorio remoto") $rules = @( Get-NetFirewallRule ` -ErrorAction Stop | Where-Object { $rdpGroupNames -contains $_.DisplayGroup } ) return $rules } # ============================================================ # ACTIVAR ESCRITORIO REMOTO # ============================================================ function Enable-RemoteDesktop { Write-Host "" Write-Host "Activando Escritorio remoto..." ` -ForegroundColor Cyan # -------------------------------------------------------- # REGISTRO # -------------------------------------------------------- $rdpRegistryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server" Set-ItemProperty ` -Path $rdpRegistryPath ` -Name "fDenyTSConnections" ` -Type DWord ` -Value 0 ` -ErrorAction Stop # Verificación. $rdpValue = ( Get-ItemProperty ` -Path $rdpRegistryPath ` -Name "fDenyTSConnections" ` -ErrorAction Stop ).fDenyTSConnections if ($rdpValue -ne 0) { throw "Windows sigue teniendo RDP desactivado." } Write-Ok "RDP está activado en el registro de Windows." # -------------------------------------------------------- # SERVICIO # -------------------------------------------------------- $service = Get-Service ` -Name "TermService" ` -ErrorAction Stop if ($service.StartType -ne "Manual") { Set-Service ` -Name "TermService" ` -StartupType Manual ` -ErrorAction Stop } $service = Get-Service ` -Name "TermService" ` -ErrorAction Stop if ($service.Status -ne "Running") { Start-Service ` -Name "TermService" ` -ErrorAction Stop } # Verificación. $service = Get-Service ` -Name "TermService" ` -ErrorAction Stop if ($service.Status -ne "Running") { throw "El servicio TermService no está ejecutándose." } Write-Ok "Servicio TermService funcionando." # -------------------------------------------------------- # SERVICIO DE FIREWALL # -------------------------------------------------------- $firewallService = Get-Service ` -Name "mpssvc" ` -ErrorAction Stop if ($firewallService.Status -ne "Running") { Write-Host "El servicio de Firewall no está activo. Iniciándolo..." ` -ForegroundColor Yellow if ($firewallService.StartType -eq "Disabled") { Set-Service ` -Name "mpssvc" ` -StartupType Automatic ` -ErrorAction Stop } Start-Service ` -Name "mpssvc" ` -ErrorAction Stop $firewallService = Get-Service ` -Name "mpssvc" ` -ErrorAction Stop if ($firewallService.Status -ne "Running") { throw "No se pudo iniciar el servicio de Firewall (mpssvc)." } } Write-Ok "Servicio de Firewall de Windows activo." # -------------------------------------------------------- # FIREWALL # -------------------------------------------------------- $rules = Get-RdpFirewallRules if ($rules.Count -eq 0) { Write-Host "No se hallaron reglas RDP por grupo. Probando con netsh..." ` -ForegroundColor Yellow & netsh.exe advfirewall firewall set rule group="Remote Desktop" new enable=yes | Out-Null & netsh.exe advfirewall firewall set rule group="Escritorio remoto" new enable=yes | Out-Null $rules = Get-RdpFirewallRules } if ($rules.Count -eq 0) { throw "No se encontraron reglas de Firewall de Escritorio remoto (grupo 'Remote Desktop'/'Escritorio remoto')." } $rules | Enable-NetFirewallRule ` -ErrorAction Stop # Verificación REAL de las reglas. $enabledRules = @( Get-RdpFirewallRules | Where-Object { $_.Enabled -eq "True" } ) if ($enabledRules.Count -eq 0) { throw "No hay ninguna regla de Firewall de RDP habilitada." } Write-Ok "Firewall de Escritorio remoto habilitado." } # ============================================================ # OCULTAR USUARIO DE LA PANTALLA DE LOGIN # ============================================================ function Hide-UserFromLogin { param ( [Parameter(Mandatory)] [string]$Username ) Write-Host "" Write-Host "Ocultando '$Username' de la pantalla de login..." ` -ForegroundColor Cyan $registryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" # Crear la clave si no existe. if (-not (Test-Path $registryPath)) { New-Item ` -Path $registryPath ` -Force ` -ErrorAction Stop | Out-Null } # 0 = ocultar usuario. New-ItemProperty ` -Path $registryPath ` -Name $Username ` -PropertyType DWord ` -Value 0 ` -Force ` -ErrorAction Stop | Out-Null # Verificación. $value = ( Get-ItemProperty ` -Path $registryPath ` -Name $Username ` -ErrorAction Stop ).$Username if ($value -ne 0) { throw "No se pudo ocultar '$Username' del login." } Write-Ok "'$Username' está oculto de la lista de usuarios." } # ============================================================ # VERIFICACIÓN FINAL # ============================================================ function Test-FinalConfiguration { Write-Step "VERIFICACIÓN FINAL DEL SISTEMA" $allOk = $true # -------------------------------------------------------- # USUARIO # -------------------------------------------------------- $user = Get-LocalUserSafe -Name $Username if ($null -ne $user) { Write-Ok "Usuario '$Username': EXISTE" } else { Write-ErrorMessage "Usuario '$Username': NO EXISTE" $allOk = $false } # -------------------------------------------------------- # ADMINISTRADORES # -------------------------------------------------------- try { $administratorsGroup = Get-LocalGroupBySid "S-1-5-32-544" if (Test-UserInGroup ` -Username $Username ` -Group $administratorsGroup) { Write-Ok "Administrador: SI" } else { Write-ErrorMessage "Administrador: NO" $allOk = $false } } catch { Write-ErrorMessage "No se pudo comprobar el grupo Administradores." $allOk = $false } # -------------------------------------------------------- # RDP REGISTRO # -------------------------------------------------------- try { $rdpRegistryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server" $rdpValue = ( Get-ItemProperty ` -Path $rdpRegistryPath ` -Name "fDenyTSConnections" ` -ErrorAction Stop ).fDenyTSConnections if ($rdpValue -eq 0) { Write-Ok "Escritorio remoto de Windows: ACTIVADO" } else { Write-ErrorMessage "Escritorio remoto de Windows: DESACTIVADO" $allOk = $false } } catch { Write-ErrorMessage "No se pudo comprobar la configuración RDP." $allOk = $false } # -------------------------------------------------------- # SERVICIO # -------------------------------------------------------- try { $service = Get-Service ` -Name "TermService" ` -ErrorAction Stop if ($service.Status -eq "Running") { Write-Ok "TermService: EJECUTÁNDOSE" } else { Write-ErrorMessage "TermService: $($service.Status)" $allOk = $false } } catch { Write-ErrorMessage "No se pudo comprobar TermService." $allOk = $false } # -------------------------------------------------------- # FIREWALL # -------------------------------------------------------- try { $enabledRules = @( Get-RdpFirewallRules | Where-Object { $_.Enabled -eq "True" } ) if ($enabledRules.Count -gt 0) { Write-Ok "Firewall RDP: HABILITADO" } else { $allRdpRules = @(Get-RdpFirewallRules) if ($allRdpRules.Count -eq 0) { Write-ErrorMessage "Firewall RDP: NO HAY REGLAS RDP EN EL SISTEMA" } else { Write-ErrorMessage "Firewall RDP: LAS REGLAS EXISTEN PERO ESTÁN DESHABILITADAS" } $allOk = $false } } catch { Write-ErrorMessage "No se pudo comprobar el Firewall de RDP: $($_.Exception.Message)" $allOk = $false } # -------------------------------------------------------- # GRUPO RDP # -------------------------------------------------------- try { $rdpGroup = Get-LocalGroupBySid "S-1-5-32-555" if (Test-UserInGroup ` -Username $Username ` -Group $rdpGroup) { Write-Ok "Usuario autorizado para RDP: SI" } else { Write-ErrorMessage "Usuario autorizado para RDP: NO" $allOk = $false } } catch { Write-ErrorMessage "No se pudo comprobar el grupo RDP." $allOk = $false } # -------------------------------------------------------- # USUARIO OCULTO # -------------------------------------------------------- try { $loginPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" $hiddenValue = ( Get-ItemProperty ` -Path $loginPath ` -Name $Username ` -ErrorAction Stop ).$Username if ($hiddenValue -eq 0) { Write-Ok "Usuario oculto del login: SI" } else { Write-ErrorMessage "Usuario oculto del login: NO" $allOk = $false } } catch { Write-ErrorMessage "No se pudo comprobar la ocultación del usuario." $allOk = $false } # -------------------------------------------------------- # RESULTADO # -------------------------------------------------------- Write-Host "" if ($allOk) { Write-Host "==================================================" -ForegroundColor Green Write-Host " TODO CORRECTO" -ForegroundColor Green Write-Host "==================================================" -ForegroundColor Green return $true } else { Write-Host "==================================================" -ForegroundColor Red Write-Host " HAY ERRORES" -ForegroundColor Red Write-Host "==================================================" -ForegroundColor Red return $false } } # ============================================================ # PROGRAMA PRINCIPAL # ============================================================ try { Clear-Host Write-Log "========== INICIO DE EJECUCIÓN ==========" Write-Log "Windows: $([Environment]::OSVersion.VersionString)" Write-Log "Usuario: $([Environment]::UserDomainName)\$([Environment]::UserName)" Write-Log "Elevado (Administrador): $(Test-IsAdministrator)" Write-Host "" Write-Host "==================================================" -ForegroundColor Cyan Write-Host " WINDOWS 10 - CONFIGURACIÓN INICIAL" -ForegroundColor Cyan Write-Host "==================================================" -ForegroundColor Cyan Write-Host "" Write-Host "Usuario técnico: $Username" Write-Host "" # -------------------------------------------------------- # 0. ADMINISTRADOR # -------------------------------------------------------- Ensure-Administrator Write-Ok "El script se está ejecutando como Administrador." # -------------------------------------------------------- # 1. COMPROBAR GRUPOS # -------------------------------------------------------- Write-Step "COMPROBANDO GRUPOS LOCALES" $administratorsGroup = Get-LocalGroupBySid "S-1-5-32-544" $rdpGroup = Get-LocalGroupBySid "S-1-5-32-555" Write-Ok "Grupo Administradores: $($administratorsGroup.Name)" Write-Ok "Grupo Escritorio remoto: $($rdpGroup.Name)" # -------------------------------------------------------- # 2. CREAR / COMPROBAR USUARIO # -------------------------------------------------------- Write-Step "CREANDO / COMPROBANDO USUARIO" $user = Ensure-TechnicalUser -Name $Username # -------------------------------------------------------- # 3. ASEGURAR USUARIO HABILITADO # -------------------------------------------------------- if (-not $user.Enabled) { Write-Host "La cuenta está deshabilitada. Habilitándola..." ` -ForegroundColor Yellow Enable-LocalUser ` -Name $Username ` -ErrorAction Stop $user = Get-LocalUserSafe -Name $Username if ($null -eq $user -or -not $user.Enabled) { throw "No se pudo habilitar el usuario '$Username'." } Write-Ok "Usuario '$Username' habilitado." } else { Write-Ok "Usuario '$Username' está habilitado." } # -------------------------------------------------------- # 4. ADMINISTRADORES # -------------------------------------------------------- Write-Step "CONFIGURANDO PRIVILEGIOS DE ADMINISTRADOR" Ensure-UserInGroup ` -Username $Username ` -Group $administratorsGroup # -------------------------------------------------------- # 5. ESCRITORIO REMOTO # -------------------------------------------------------- Write-Step "ACTIVANDO ESCRITORIO REMOTO" Enable-RemoteDesktop # -------------------------------------------------------- # 6. USUARIO AUTORIZADO PARA RDP # -------------------------------------------------------- Write-Step "AUTORIZANDO USUARIO PARA ESCRITORIO REMOTO" Ensure-UserInGroup ` -Username $Username ` -Group $rdpGroup # -------------------------------------------------------- # 7. OCULTAR USUARIO # -------------------------------------------------------- Write-Step "OCULTANDO USUARIO DE LA PANTALLA DE LOGIN" Hide-UserFromLogin ` -Username $Username # -------------------------------------------------------- # 8. VERIFICACIÓN FINAL # -------------------------------------------------------- $result = Test-FinalConfiguration # -------------------------------------------------------- # RESULTADO # -------------------------------------------------------- Write-Host "" if ($result) { Write-Host "La configuración se ha completado correctamente." ` -ForegroundColor Green Write-Host "" Write-Host "El script se puede ejecutar de nuevo sin cambiar" ` -ForegroundColor Green Write-Host "la contraseña del usuario existente." ` -ForegroundColor Green Write-Host "" exit 0 } else { Write-Host "La configuración NO se ha completado correctamente." ` -ForegroundColor Red Write-Host "" Write-Host "Revisa los errores mostrados anteriormente." ` -ForegroundColor Yellow Write-Host "" exit 1 } } catch { $fullMessage = Get-ExceptionMessage -Exception $_.Exception Write-Host "" Write-Host "==================================================" -ForegroundColor Red Write-Host " ERROR FATAL" -ForegroundColor Red Write-Host "==================================================" -ForegroundColor Red Write-Host "" Write-ErrorMessage $fullMessage Write-Host "" Write-Host "Trace del script:" -ForegroundColor Yellow Write-Host $_.ScriptStackTrace Write-Log "==========================================================================" Write-Log "ERROR FATAL: $fullMessage" Write-Log ("TRACE: " + ($_.ScriptStackTrace -replace "(`r)?`n", " | ")) Write-Log "==========================================================================" Write-Host "" Write-Host "El script se ha detenido para evitar continuar" ` -ForegroundColor Yellow Write-Host "con una configuración incompleta." ` -ForegroundColor Yellow Write-Host "" Write-Host "Detalles guardados en el log: $LogPath" ` -ForegroundColor Cyan Write-Host "" exit 1 }