Mostrando entradas con la etiqueta PS. Mostrar todas las entradas
Mostrando entradas con la etiqueta PS. Mostrar todas las entradas

viernes, 17 de abril de 2026

WINDOWS. Admincount. Analisis

 

Propósito:

El objetivo es garantizar que no existan cuentas de administrador no autorizadas en Active Directory.

Se realiza una comprobación en las cuentas que no son de administrador para identificar si tienen el atributo `admincount` activado. Si lo tienen, significa que esta cuenta, que no debería ser de administrador, ha recibido derechos de administrador en el pasado. Esto suele ocurrir cuando un administrador otorga derechos temporales a una cuenta normal fuera de proceso.

Se deben revisar estas cuentas, especialmente en lo que respecta a su actividad anterior, y eliminar el atributo `admincount`. Para identificar las cuentas detectadas por esta regla, recomendamos ejecutar el siguiente comando de PowerShell: `get-adobject -ldapfilter "(admincount=1)"`.

Pasos

 PASO 0: Listar quien cumple esta condición

COMMANDO:  get-adobject -ldapfilter "(admincount=1)"


 PASO 1 — Confirmar que el usuario YA NO es admin

COMMANDO:  Get-ADUser usuarioX -Properties MemberOf | Select -ExpandProperty MemberOf


 Verifica que NO esté en:
  • Domain Admins
  • Enterprise Admins
  • Schema Admins
  • Account Operators
  • Backup Operators
  • Administrators (builtin)
  • Print Operators
  • Server Operators
PASO 2 — Quitar el atributo adminCount

COMMANDO: Set-ADUser usuarioX -Clear adminCount



by GoN | Published: April 2026 | Last Updated:

lunes, 13 de abril de 2026

WINDOWS. LAPS. Check PCs


Propósito

A veces, viendo que en nuestro AD nuestros PCs tienen asignada una contraseña de LAPS creemos erróneamente que todo está funcionando bien. Hay que verificar alguna cosas más. que aquí explicaré, para tener más control
 
Vamos a planificar un script que no solo compruebe si un PC tiene contraseña de LAPS en el AD, sino que esta contraseña esté rotando cada 30 días como marca la GPO.

Pasos

La ejecución será sencilla, el resultado lo enviará por email.

 

Nos llegará un email con este formato:

 

No solo pone en el cuerpo del email los PCs encontrados, sino que los envía en un fichero adjunto para poder manipularlo. Es recomendable ejecutarlo entre 15 días y un mes, para ver si salen algún caso, tal como muestro en los pantallazos. Es evidente que si el PC no se enciende o no contacta con el AD, no se le puede cambiar la contraseña.

 

El código: 

<#
.DESCRIPCION
Script de auditoria LAPS en Active Directory.
Detecta equipos con LAPS activo cuya contrasena local no se ha rotado
en mas de 30 dias. El resultado se muestra en el cuerpo del email en
formato HTML  y se adjunta en CSV.

.DISENO
Entorno enterprise, solo lectura, sin impacto.
#>

Write-Host "=== AUDITORIA LAPS ===" -ForegroundColor Cyan

# ==========================
#  VARIABLES DEL USUARIO
# ==========================
$SMTPServer = "smtp.midominio.com"
$SMTPPort   = 25
$From       = "HD@midominio.com"
$To         = "Infra@midominio.com"
$Subject    = ""

# ==========================
#  RUTAS Y FECHAS
# ==========================
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$timestamp  = Get-Date -Format "yyyyMMdd_HHmm"
$csvFile    = "$scriptPath\LAPS_Auditoria_$timestamp.csv"

# ==========================
#  OBTENER EQUIPOS AD
# ==========================
$computers = Get-ADComputer -Filter * -Properties `
    msLAPS-PasswordExpirationTime,
    LastLogonDate,
    DistinguishedName,
    Enabled

$results = @()

foreach ($pc in $computers) {

    if (-not $pc."msLAPS-PasswordExpirationTime") { continue }

    $exp = [DateTime]::FromFileTimeUtc($pc."msLAPS-PasswordExpirationTime")
    $dias = (New-TimeSpan -Start $exp -End (Get-Date)).Days
    if ($dias -le 30) { continue }

    $ou = ($pc.DistinguishedName -split ",",2)[1]
    $estado = if ($pc.Enabled) { "Habilitado" } else { "Deshabilitado" }

    $results += [PSCustomObject]@{
        Equipo             = $pc.Name
        OU                 = $ou
        Equipo_Habilitado  = $estado
        LAPS_Activo        = "Si"
        Ultima_Rotacion    = $exp
        Dias_Sin_Cambiar   = $dias
        Ultima_Conexion    = $pc.LastLogonDate
    }
}

$resultsSorted = $results | Sort-Object Dias_Sin_Cambiar -Descending
$TotalEquipos = $resultsSorted.Count

# ==========================
#  EXPORTAR CSV
# ==========================
$resultsSorted | Export-Csv -Path $csvFile -NoTypeInformation -Encoding UTF8

# ==========================
#  GENERAR TABLA HTML
# ==========================
$HtmlRows = ""

foreach ($r in $resultsSorted) {

    $FechaRotacionHtml = if ($r.Ultima_Rotacion) {
        $r.Ultima_Rotacion.ToString("dd/MM/yyyy")
    } else {
        "N/D"
    }

    $FechaConexionHtml = if ($r.Ultima_Conexion) {
        $r.Ultima_Conexion.ToString("dd/MM/yyyy")
    } else {
        "Nunca"
    }

    $HtmlRows += "<tr>
        <td>$($r.Equipo)</td>
        <td>$($r.Equipo_Habilitado)</td>
        <td>$($r.Dias_Sin_Cambiar)</td>
        <td>$FechaRotacionHtml</td>
        <td>$FechaConexionHtml</td>
        <td>$($r.OU)</td>
    </tr>"
}

# ==========================
#  EMAIL HTML
# ==========================
$Subject = "$(Get-Date -Format 'yyyy-MM-dd - HHmm') - Listado equipos LAPS sin rotacion ($TotalEquipos)"

$Body = @"
<html>
<head>
<style>
body { font-family: Arial; font-size: 12px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #999; padding: 5px; }
th { background-color: #eeeeee; }
</style>
</head>
<body>

<h2>Auditoria LAPS Active Directory</h2>

<p>
Equipos con LAPS activo y mas de 30 dias sin cambio de contrasena local.
</p>

<ul>
<li>Total equipos listados: <b>$TotalEquipos</b></li>
<li>Fecha ejecucion: <b>$(Get-Date -Format "dd/MM/yyyy")</b></li>
</ul>

<table>
<tr>
<th>Equipo</th>
<th>Estado</th>
<th>Dias sin cambiar</th>
<th>Ultima rotacion</th>
<th>Ultima conexion</th>
<th>OU</th>
</tr>
$HtmlRows
</table>

<p>
Se adjunta el fichero CSV con el detalle completo.
</p>

</body>
</html>
"@

Send-MailMessage `
    -From $From `
    -To $To `
    -Subject $Subject `
    -Body $Body `
    -BodyAsHtml `
    -SmtpServer $SMTPServer `
    -Port $SMTPPort `
    -Attachments $csvFile

Write-Host "Email enviado correctamente" -ForegroundColor Green
Write-Host "=== FIN DE AUDITORIA ===" -ForegroundColor Cyan 
 
 
by GoN | Published: April 2026 | Last Updated:
 

sábado, 14 de marzo de 2026

WINDOWS. PS. Hosts sin conectar al dominio.

 Propósito

Una opción más para mantener nuestro Active Directory al día es asegurarnos de que no existan equipos que ya no estén en uso. Deberíamos procurar que los PCs estén lo más actualizados posible: últimas actualizaciones, parches, configuraciones de seguridad (GPOs), patrones de antivirus, etc. Además, un equipo que no se conecta o que ya no está activo sigue consumiendo licencias innecesariamente.

Para tener un control mínimo, he preparado este script que se ejecuta una vez por semana y informa de los equipos que llevan más de 30 y 60 días sin contactar con nuestro AD.

Pasos

# ==========================

#  VARIABLES DEL USUARIO

# ==========================

$SMTPServer = "smtp.com"

$SMTPPort   = 25

$From       = "HelpDesk@Miempresa.com"

$To         = "alertas@Miempresa.com"

$Subject    = "OU Computers que llevan tiempo sin conectar"

 # ==========================

#  CONFIGURACIÓN

# ==========================

Import-Module ActiveDirectory

 

$Hoy = (Get-Date)

$Dias30 = $Hoy.AddDays(-30)

$Dias60 = $Hoy.AddDays(-60)

 # ==========================

#  OBTENER EQUIPOS AD

# ==========================

$Equipos = Get-ADComputer -Filter * -Properties Description, LastLogonTimestamp, CanonicalName |

    Select-Object Name,

                  Description,

                  @{Name="OU";Expression={($_.CanonicalName -replace "^[^/]+/","")}},

                  @{Name="LastLogon";Expression={[DateTime]::FromFileTime($_.LastLogonTimestamp)}}

 # ==========================

#  FILTRADOS

# ==========================

$Entre30y60 = $Equipos | Where-Object { $_.LastLogon -le $Dias30 -and $_.LastLogon -gt $Dias60 }

$Mas60      = $Equipos | Where-Object { $_.LastLogon -le $Dias60 }

 # ==========================

#  GENERAR TABLAS HTML

# ==========================

function Convert-ToHtmlTable {

    param($Data, $Titulo)

 

    if ($Data.Count -eq 0) {

        return "<h3>$Titulo</h3><p>No hay equipos en este rango.</p>"

    }

 

    $Data | Select-Object Name, Description, OU, LastLogon |

        ConvertTo-Html -Fragment -PreContent "<h3>$Titulo</h3>"

}

 $Html30_60 = Convert-ToHtmlTable -Data $Entre30y60 -Titulo "Equipos sin conectar entre 30 y 60 dias"

$Html60    = Convert-ToHtmlTable -Data $Mas60 -Titulo "Equipos sin conectar mas de 60 dias"

 # ==========================

#  EMAIL HTML

# ==========================

$Body = @"

<html>

<head>

<style>

table { border-collapse: collapse; width: 100%; }

th, td { border: 1px solid #999; padding: 6px; text-align: left; }

th { background-color: #f2f2f2; }

h3 { font-family: Arial; }

</style>

</head>

<body>

<h2>Informe de equipos que llevan tiempo sin conectar</h2>

<p>Generado desde SRVDC04 $(Get-Date -Format "dd/MM/yyyy HH:mm")</p>

 

$Html30_60

<br>

$Html60

 

</body>

</html>

"@

 # ==========================

#  ENVÍO DEL EMAIL

# ==========================

Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -BodyAsHtml `

    -SmtpServer $SMTPServer -Port $SMTPPort


El resultado vendría a ser un email así: 



by GoN | Published: Mar 2026 | Last Updated:

 

martes, 10 de febrero de 2026

WINDOWS. PS. Computers - Pcs dias sin conectar al dominio

 Propósito

Localizar los PCs/host que lleven cierto tiempo sin contactar contra nuestro AD, ya sea porque se han perdido, los han robado, se han roto, etc... el tema es que hay que sacarlos porque podrían estar consumiendo licencias, rompiendo nuestras estadísticas de parcheo, los antivirus sin actualizar, etc..

Pasos

Para localizarlos he creado 2 scripts

Script 1:

Descripcion: PCs sin conectar en 90 dias (se puede modificar fácilmente la cantidad de días). La idea sería ejecutarlo una vez al día para estar al corriente de nuestro inventario. Se podría añadir a una tarea programada.

# Parámetros
$DiasSinConectar = 90   # 3 meses aproximados
$FechaLimite = (Get-Date).AddDays(-$DiasSinConectar)

# Nombre del fichero con fecha y hora
$TimeStamp = Get-Date -Format "yyyyMMdd_HHmmss"
$FicheroSalida = "Equipos_NoConectan_Desde_3Meses_$TimeStamp.csv"

# Consulta a AD
Import-Module ActiveDirectory

Get-ADComputer -Filter * -Properties lastLogonTimestamp, Description |
    Select-Object Name,
                  Description,
                  @{Name='UltimoLogon'; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}} |
    Where-Object { $_.UltimoLogon -lt $FechaLimite } |
    Export-Csv -Path $FicheroSalida -NoTypeInformation -Encoding UTF8

Write-Host "Proceso finalizado. Archivo generado: $FicheroSalida"

Su salida sería algo así:


Script 2:

Descripcion. PC búsqueda manual, pide en pantalla cuantos días quiere que retroceda para comprobar.


# Cargar módulo de Active Directory
Import-Module ActiveDirectory

# Pedir días por pantalla
$DiasSinConectar = Read-Host "Introduce el número de días que llevan los PCs sin conectar"
$FechaLimite = (Get-Date).AddDays(-[int]$DiasSinConectar)

# Convertir timestamp y filtrar
$Equipos = Get-ADComputer -Filter * -Properties lastLogonTimestamp, Description |
    Select-Object Name,
                  Description,
                  @{Name='UltimoLogon'; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp)}} |
    Where-Object { $_.UltimoLogon -lt $FechaLimite }

# Mostrar por pantalla
Write-Host ""
Write-Host "Equipos encontrados que llevan más de $DiasSinConectar días sin conectar:" -ForegroundColor Cyan
$Equipos | Format-Table -AutoSize
Write-Host ""

# Número de equipos
$Total = $Equipos.Count

# Crear nombre del fichero con fecha/hora y número de PCs
$TimeStamp = Get-Date -Format "yyyyMMdd_HHmmss"
$NombreFichero = "PCs_SinConectar_${DiasSinConectar}dias_${Total}equipos_$TimeStamp.csv"

# Exportar CSV
$Equipos | Export-Csv -Path $NombreFichero -NoTypeInformation -Encoding UTF8

Write-Host "Exportación completada. Archivo generado: $NombreFichero" -ForegroundColor Green

La salida sería algo así


Dejo en tu mano borrarlos!
by GoN | Published: Feb 2026 | Last Updated:

lunes, 9 de febrero de 2026

WINDOWS. PS. PCs en la OU de computers

Propósito

Cuando añadimos un PC al dominio se va al contendor "Computers" que no es una OU a la que se puedan asignar GPOs.

Esto implica el riesgo que no se les apliquen ninguna GPO, con lo que hay esta atentos para moverlos a su OU correspondiente.

El script que aquí pondré lo ejecuto una vez al día (Tarea programada) y le envío copia al equipo de Help Desk. Básicamente mira si hay algún hosts en el contenedor de Computers y si lo hay envía un email informando.

No modifica nada, solo es informativa.

Pasos

Básicamente mira si hay algún hosts en el contenedor de Computers y si lo hay envía un email informando

 <#
   Busca equipos en el contenedor CN=Computers.
   Si hay equipos → envía correo con la tabla HTML en el CUERPO.
   Si no hay equipos → no envía correo.
#>

# ==========================
#  VARIABLES DEL USUARIO
# ==========================
$SMTPServer = "miservidor.midominio.com"
$SMTPPort   = 25
$From       = "Servidor1@midominio.com"
$To         = "itsecurity@midominio.com" , "HelpDesk@midominio.com",
$Subject    = "PCs en la Ou de Computers"

# Contenedor donde buscar (CORRECTO)
$SearchBase = "CN=Computers,DC=midominio,DC=com"

# ==========================
#  CARGAR MÓDULO AD
# ==========================
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
    Write-Error "El módulo ActiveDirectory no está disponible. Instala RSAT."
    exit 1
}
Import-Module ActiveDirectory

# ==========================
#  VALIDAR CONTENEDOR
# ==========================
try {
    $null = Get-ADObject -Identity $SearchBase -ErrorAction Stop
}
catch {
    Write-Error "ERROR: No se encontró el contenedor/OU: $SearchBase"
    exit 1
}

# ==========================
#  OBTENER EQUIPOS
# ==========================
$Computers = Get-ADComputer -SearchBase $SearchBase `
    -SearchScope OneLevel `
    -Filter * `
    -Properties Name, DNSHostName, Description, OperatingSystem, Enabled, LastLogonDate, whenCreated, whenChanged

$Count = $Computers.Count
Write-Host "Equipos encontrados: $Count"

if ($Count -eq 0) {
    Write-Host "Contenedor vacío. No se envía correo."
    exit 0
}

# ==========================
#  GENERAR HTML
# ==========================
$Sorted = $Computers | Sort-Object Name

$Table = $Sorted | Select-Object `
    @{n="Nombre (sAM)"; e={$_.Name}},
    @{n="Hostname (DNS)"; e={$_.DNSHostName}},
    @{n="Descripción"; e={ if ($_.Description) { $_.Description } else { "N/D" } }},
    @{n="SO"; e={$_.OperatingSystem}},
    @{n="Habilitado"; e={$_.Enabled}},
    @{n="Último logon"; e={ if ($_.LastLogonDate) { $_.LastLogonDate } else { "N/D" } }},
    @{n="Creado"; e={$_.whenCreated}},
    @{n="Modificado"; e={$_.whenChanged}} |
    ConvertTo-Html -As Table -Fragment

$Style = @"
<style>
body { font-family: Segoe UI, Arial; color:#222; }
table { border-collapse: collapse; width:100%; }
th, td { border: 1px solid #ccc; padding:6px; }
th { background:#f0f0f0; }
tr:nth-child(even) { background:#fafafa; }
.notice { font-weight:bold; color:#b00020; }
</style>
"@

$IntroText = "<p class='notice'>Estos PC deben moverse a la OU correspondiente</p>"
$Header    = "<h2>Equipos encontrados en: $SearchBase</h2>"
$BodyHtml  = ConvertTo-Html -Head $Style -Body "$IntroText $Header $Table"

# ==========================
#  ENVIAR CORREO
# ==========================
try {
    $Mail = New-Object System.Net.Mail.MailMessage
    $Mail.From = $From

    foreach ($recipient in $To) {
        if ($recipient.Trim()) { $Mail.To.Add($recipient.Trim()) }
    }

    $Mail.Subject   = $Subject + " (Total: $Count)"
    $Mail.IsBodyHtml = $true
    $Mail.Body      = $BodyHtml

    $SMTP = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort)
    $SMTP.Send($Mail)

    Write-Host "Correo enviado correctamente."
}
catch {
    Write-Error "Error enviando el correo: $($_.Exception.Message)"
    exit 1

}

El resultado vendría ser algo parecido a lo siguiente


Dificultad: Baja
Utilidad: Alta
by GoN | Published: Feb 2026 | Last Updated: