Manuales+50 XP

GPO básicas para una organización pequeña: cómo lo montaría yo con dos directivas y PowerShell

Cómo montaría las GPO de una organización pequeña: unidades de red, impresoras, instalación de agentes y alta de equipos con dos directivas y PowerShell.

IvánPublicado el 9 minRead in English

Cuando una organización pasa de diez a unas decenas de equipos, configurar cada puesto a mano deja de ser viable: a uno le falta una unidad de red, otro no tiene la impresora y en el tercero nadie instaló el agente de inventario. Si me tocara montarlo desde cero, no haría veinte directivas: haría dos GPO y un par de scripts de PowerShell que cualquiera del equipo pueda leer. Aquí te cuento cómo, con el código y las trampas típicas.

Es una propuesta genérica: el dominio, las rutas, los grupos y los nombres son de ejemplo. Adáptalos a tu red.

1. El mapa: dos GPO y una estructura de OU pensada para crecer

GPO Qué hace Tipo
GPO-Usuarios-Base Unidades de red, impresoras y ajustes de escritorio Script de inicio de sesión (usuario)
GPO-Agentes Instala los agentes de seguridad e inventario Script de inicio (equipo)

Al principio las enlazaría a la raíz del dominio, para que lleguen a todo el mundo. Lo que cambia según la persona (qué carpeta compartida ve, por ejemplo) lo decidiría dentro del script por grupos de seguridad, no con una GPO por departamento.

Lo que sí haría desde el primer día es una estructura de OU preparada para afinar más adelante:

tu-dominio.local
├── Servidores
├── Bajas
├── Departamentos        (usuarios: una OU por departamento)
│   ├── Administracion
│   └── Tecnico
└── Equipos              (espejo de los departamentos)
    ├── Administracion
    ├── Tecnico
    └── Comunes          (salas, recepción...)

Separar usuarios y equipos en OU distintas es lo que más agradecerás después: el día que quieras una directiva de equipo solo para un departamento, la enlazas a su OU y listo, sin filtros WMI. Para los grupos usaría el mismo patrón que conté en altas y bajas en Active Directory con PowerShell: un GRP-Todos y un GRP-<Departamento>.

2. Unidades de red con un script de inicio de sesión

La forma «de manual» es usar las Preferencias de directiva (GPP) con filtrado a nivel de elemento. Funciona bien, pero con muchas unidades se convierte en un montón de clics escondidos en la consola. Yo prefiero un script de PowerShell guardado en NETLOGON y asignado en:

Configuración del usuario > Directivas > Configuración de Windows > Scripts (inicio o cierre de sesión) > Iniciar sesión, pestaña Scripts de PowerShell.

Un reparto típico:

Letra Destino Para quién
U: \\srv-ficheros\Personal$\<usuario> Todos (si su carpeta existe)
S: \\srv-ficheros\Comun Todos
T: Carpeta del departamento Según su grupo

La clave es que la tabla grupo → letra → ruta esté arriba del todo. Cambiar un destino es editar una línea, no tocar la lógica:

$Mapa = @(
    @{ Grupo = 'GRP-Todos';          Letra = 'S'; Ruta = '\\srv-ficheros\Comun' }
    @{ Grupo = 'GRP-Tecnico';        Letra = 'T'; Ruta = '\\srv-ficheros\Tecnico' }
    @{ Grupo = 'GRP-Administracion'; Letra = 'T'; Ruta = '\\srv-ficheros\Administracion' }
)
$LogFallos = "$env:TEMP\unidades-red.log"

# Grupos del usuario, sacados del token (no hace falta el módulo de AD en el puesto)
$misGrupos = [Security.Principal.WindowsIdentity]::GetCurrent().Groups | ForEach-Object {
    try { $_.Translate([Security.Principal.NTAccount]).Value -replace '^.*\\' } catch {}
}

function Add-Unidad($Letra, $Ruta) {
    if (Get-PSDrive -Name $Letra -ErrorAction SilentlyContinue) { return }   # ya está
    try {
        New-PSDrive -Name $Letra -PSProvider FileSystem -Root $Ruta -Persist -Scope Global -ErrorAction Stop | Out-Null
    } catch {
        net use "${Letra}:" $Ruta /persistent:yes | Out-Null
        if ($LASTEXITCODE -ne 0) { "$(Get-Date -Format s) ${Letra}: -> $Ruta fallo" | Add-Content $LogFallos }
    }
}

$personal = "\\srv-ficheros\Personal$\$env:USERNAME"
if (Test-Path $personal) { Add-Unidad 'U' $personal }

foreach ($m in $Mapa) {
    if ($misGrupos -contains $m.Grupo) { Add-Unidad $m.Letra $m.Ruta }
}

Cuatro detalles que importan:

  • Comprueba antes de actuar. El script corre en cada inicio de sesión; si la letra ya existe, no hace nada. Así puedes lanzarlo cien veces sin romper nada.
  • Los grupos salen del token, no de consultar AD. Es más rápido y no necesita RSAT en el puesto. A cambio, si metes a alguien en un grupo, tiene que cerrar sesión para que lo note.
  • Hay un plan B. Si New-PSDrive -Persist falla, net use casi siempre funciona.
  • Los fallos quedan apuntados. Un script de inicio de sesión que falla en silencio solo se descubre cuando alguien llama diciendo que no tiene la U:.

3. Ajustes de escritorio sin abrir la consola

La misma GPO puede llevar unos cuantos ajustes que se ponen desde PowerShell con Set-GPRegistryValue. Así quedan en un script y se pueden repetir en otro dominio o reconstruir si alguien borra la GPO:

$gpo = 'GPO-Usuarios-Base'

# Bloqueo de pantalla a los 5 minutos, con contraseña
$k = 'HKCU\Software\Policies\Microsoft\Windows\Control Panel\Desktop'
Set-GPRegistryValue -Name $gpo -Key $k -ValueName ScreenSaveActive    -Type String -Value '1'
Set-GPRegistryValue -Name $gpo -Key $k -ValueName ScreenSaverIsSecure -Type String -Value '1'
Set-GPRegistryValue -Name $gpo -Key $k -ValueName ScreenSaveTimeOut   -Type String -Value '300'

# Autorun desactivado en todas las unidades
Set-GPRegistryValue -Name $gpo -Key 'HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer' `
    -ValueName NoDriveTypeAutoRun -Type DWord -Value 255

Una confusión típica: buscar un parámetro -Context "Computer" para decirle si el ajuste es de equipo o de usuario. No existe. Lo decide la rama: HKLM es configuración de equipo y HKCU, de usuario.

4. Impresoras: también en el script

Las impresoras compartidas en un servidor de impresión se añaden en el mismo script, una línea por impresora:

Add-Printer -ConnectionName '\\srv-impresion\Multifuncion-Planta1'
Add-Printer -ConnectionName '\\srv-impresion\Multifuncion-Planta2'

Si solo hay un par de multifunción para toda la oficina, no merece la pena filtrar: que las vean todos. El puesto baja el driver del servidor al conectar. Con más impresoras por planta o por sede, ahí sí compensa meterlas en la tabla de grupos del punto 2 o usar la consola de Administración de impresión.

Lo que más guerra da con las impresoras no es la GPO, sino lo que se configura a mano en los puestos. Una impresora añadida con credenciales guardadas de una cuenta que ya no está activa reintenta sin parar, y cada reintento es un inicio de sesión fallido. Para un SIEM eso se parece mucho a un ataque de fuerza bruta, y si lo tienes conectado a los tickets, la avalancha está asegurada. Silenciar la regla no lo arregla: hay que ir al equipo y quitar esa impresora. Cómo domar ese tipo de alertas lo conté en Wazuh + GLPI: tickets solos y falsas alarmas.

5. Software: agentes con un script de inicio de equipo

Para cosas pequeñas y que tienen que estar en todos los equipos, como el agente de Wazuh y el de GLPI, no usaría la «Instalación de software» clásica de las GPO (asignar o publicar un MSI). Da poca información cuando falla y no deja pasar parámetros con comodidad. Prefiero un script de inicio que se ejecuta como SYSTEM al arrancar el equipo:

Configuración del equipo > Directivas > Configuración de Windows > Scripts (inicio o apagado) > Inicio

  • Programa: powershell.exe
  • Parámetros: -ExecutionPolicy Bypass -NonInteractive -WindowStyle Hidden -File \\tu-dominio.local\NETLOGON\Deploy\Deploy-Agents.ps1

Esto se hace a mano en la consola (GPMC): Set-GPRegistryValue no sirve para asignar scripts de inicio.

El script solo instala lo que falta y deja un log en cada equipo:

$Log = 'C:\Windows\Temp\despliegue-agentes.log'
function Write-Log($m) { "$(Get-Date -Format s) $m" | Add-Content $Log }

function Install-Msi($Msi, $Parametros, $Nombre) {
    for ($i = 1; $i -le 5; $i++) {
        $argumentos = "/i `"$Msi`" /qn /l*v `"C:\Windows\Temp\$Nombre-msi.log`" $Parametros"
        $p = Start-Process msiexec.exe -ArgumentList $argumentos -Wait -PassThru
        if ($p.ExitCode -in 0, 3010) { Write-Log "$Nombre instalado"; return }
        if ($p.ExitCode -eq 1618) { Write-Log "${Nombre}: otra instalacion en curso, reintento $i"; Start-Sleep 60; continue }
        Write-Log "${Nombre}: error $($p.ExitCode)"; return
    }
}

$origen = '\\tu-dominio.local\NETLOGON\Deploy'

$svc = Get-Service WazuhSvc -ErrorAction SilentlyContinue
if (-not $svc) {
    Install-Msi "$origen\wazuh-agent.msi" 'WAZUH_MANAGER="wazuh.tu-dominio.local" WAZUH_AGENT_GROUP="puestos"' 'Wazuh'
} elseif ($svc.Status -ne 'Running') {
    Start-Service WazuhSvc; Write-Log 'Wazuh estaba parado: arrancado'
}

if (-not (Get-Service glpi-agent -ErrorAction SilentlyContinue)) {
    Install-Msi "$origen\glpi-agent.msi" 'SERVER="https://glpi.tu-dominio.local/" RUNNOW=1' 'GLPI'
}

El bucle de reintento está ahí por el error 1618 (ERROR_INSTALL_ALREADY_RUNNING), el más típico en este tipo de despliegues: en un equipo recién instalado, nada más arrancar suele haber otra instalación MSI en marcha (actualizaciones, el propio Windows…). Esperar un minuto y reintentar lo arregla.

Comprobar si un programa está instalado con Win32_Product es tentador, pero esa clase lanza una comprobación de consistencia de todos los MSI del equipo cada vez que la consultas. Mirar el servicio o el registro (HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall) es mucho más limpio.

Si en algunos equipos el script no llega a ejecutarse, suele ser porque la red no está lista al arrancar. Para eso está la directiva Configuración del equipo > Directivas > Plantillas administrativas > Sistema > Inicio de sesión > Esperar siempre a la red al iniciar el equipo y la sesión.

6. El alta del equipo en un solo comando

Si no hay servidor de imágenes, lo habitual es unir el equipo desde sysdm.cpl y luego moverlo a su OU. Se puede hacer todo de una vez con Add-Computer: renombra, une al dominio y deja el equipo en su OU:

Add-Computer -DomainName tu-dominio.local -NewName EQ-1042 `
    -OUPath 'OU=Tecnico,OU=Equipos,DC=tu-dominio,DC=local' -Credential TU-DOMINIO\admin-alta -Restart

Así el equipo nunca pasa por el contenedor Computers, donde no le llega ninguna directiva de las OU. Si alguno se te cuela ahí, se mueve con:

Move-ADObject -Identity 'CN=EQ-1042,CN=Computers,DC=tu-dominio,DC=local' `
    -TargetPath 'OU=Tecnico,OU=Equipos,DC=tu-dominio,DC=local'

Para los nombres usaría un formato fijo como EQ-<número>, sin nada que dependa de quién usa el equipo: la persona se va y el equipo se queda.

7. Comprobar que todo se aplica

gpupdate /force     # y luego cerrar sesión (usuario) o reiniciar (equipo)
gpresult /r         # qué GPO se han aplicado y cuáles se han filtrado
net use             # unidades mapeadas
Get-Printer         # impresoras del usuario
Get-Service WazuhSvc, glpi-agent
Get-Content C:\Windows\Temp\despliegue-agentes.log -Tail 20

Ojo con gpupdate: actualiza la directiva, pero un script de inicio de sesión solo corre al iniciar sesión, y uno de inicio de equipo, al arrancar. Y si te dice que el comando no existe, mira bien lo que has escrito: gpudate me ha hecho perder más tiempo del que me gustaría admitir.

Lo que no hay que hacer

  • Escribir los scripts con tildes si van a correr en PowerShell 5.1. Un .ps1 en UTF-8 sin BOM se lee como ANSI y aparecen errores de sintaxis absurdos. ASCII puro, o UTF-8 con BOM.
  • Usar filtros de AD con llaves {} en PowerShell 5.1. Fallan en cuanto metes propiedades de variables ($u.Name); con cadena (-Filter "Name -eq '$x'") van fino.
  • Publicar recursos con un nombre alias sin preparar el servidor. SMB puede rechazar la conexión por la comprobación estricta de nombres, sobre todo cuando un script que corre en ese mismo servidor usa la ruta UNC. Registra el alias con netdom computername <servidor> /add:<alias> o, como apaño, pon DisableStrictNameChecking = 1 en HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters y reinicia el servicio LanmanServer.
  • Dejar equipos en Computers. Ese contenedor no es una OU y no se le pueden enlazar GPO.
  • Basar los nombres de equipo en la persona que lo usa.

Resumen

  1. Dos GPO enlazadas a la raíz: una de usuario (unidades, impresoras, ajustes) y otra de equipo (agentes).
  2. Scripts de PowerShell con una tabla de grupos arriba, idempotentes, con un plan B y un log de fallos.
  3. Ajustes de registro con Set-GPRegistryValue; los scripts de inicio, a mano en la consola.
  4. Agentes instalados como SYSTEM al arrancar, con reintento para el error 1618 y un log por equipo.
  5. Usuarios y equipos en OU separadas desde el principio, y altas de equipo con Add-Computer.

No hace falta una GPO para cada cosa. Con pocas directivas bien pensadas y scripts que puedas leer, cualquiera del equipo sabe qué recibe cada puesto y por qué.

Mini-quiz

¿Te has quedado con la idea?

Tres preguntas rápidas. Cada acierto suma 10 XP.

  1. Has cambiado un script de inicio de equipo y has lanzado gpupdate /force. ¿Por qué no ves ningún cambio todavía?
  2. msiexec devuelve el error 1618 al instalar un agente en un equipo recién arrancado. ¿Qué significa?
  3. ¿Por qué el script de unidades de red mira primero si la letra ya existe?
  • #gpo
  • #active-directory
  • #powershell
  • #windows-server
  • #sysadmin
Esc