PowerShell Write-Log: Un tutorial sobre una función simple de registro

Si estás escribiendo scripts de PowerShell que hacen algo significativo, necesitas registro. Ya sea que estés desplegando software, gestionando servicios o automatizando tareas, tener un registro de lo que hizo tu script (o no hizo) es crucial. En este tutorial, aprenderás cómo crear una función de registro de PowerShell simple pero efectiva.

Requisitos previos

Si deseas seguir este tutorial, asegúrate de tener:

  • Windows 10 o Windows Server con PowerShell 5.1 o PowerShell 7+
  • Un editor de texto (se recomienda VSCode)
  • Comprensión básica de las funciones de PowerShell

El problema con el registro básico

Imaginemos que estás escribiendo un script para instalar silenciosamente algún software. El enfoque básico podría verse algo así:

Add-Content -Path "C:\\Scripts\\install.log" -Value "Starting install..."
Start-Process -FilePath 'installer.exe' -ArgumentList '/i /s' -Wait -NoNewWindow
Add-Content -Path "C:\\Scripts\\install.log" -Value "Finished install."

Esto funciona, pero tiene algunos problemas:

  • Sin marcas de tiempo
  • Código repetitivo
  • Formato de registro inconsistente
  • Ruta de registro codificada

Arreglemos estos problemas construyendo una función de registro adecuada.

Construyendo una Función Básica de Escribir-Registro

Primero, creemos una función simple que añada marcas de tiempo a nuestras entradas de registro:

function Write-Log {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message
    )

    $timeGenerated = Get-Date -Format HH:mm:ss
    Add-Content -Path "C:\\Scripts\\script.log" -Value "$timeGenerated - $Message"
}

Ahora puedes usarla así:

Write-Log -Message "Starting install..."
Start-Process -FilePath 'installer.exe' -ArgumentList '/i /s' -Wait -NoNewWindow
Write-Log -Message "Finished install."

El archivo de registro (C:\Scripts\script.log) contendrá entradas que se ven así:

09:42:15 - Starting install...
09:43:22 - Finished install.

¡Mucho más limpio! Pero podemos hacerlo mejor.

Agregando más funcionalidad

Mejoremos nuestra función de registro con algunas características útiles:

  • Rutas de registro personalizadas
  • Diferentes niveles de registro (Info, Advertencia, Error)
  • Fecha en el nombre del archivo
  • Manejo de errores

Aquí está la versión mejorada:

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$LogFilePath = "C:\\Scripts\\Logs",

        [Parameter()]
        [ValidateSet('Information','Warning','Error')]
        [string]$Level = "Information"
    )

    # Create the log directory if it doesn't exist
    if (!(Test-Path $LogFilePath)) {
        New-Item -Path $LogFilePath -ItemType Directory -Force | Out-Null
    }

    # Build the log file path with date
    $date = Get-Date -Format "MM-dd-yyyy"
    $logFile = Join-Path $LogFilePath "log-$date.txt"

    # Get the current timestamp
    $timeStamp = Get-Date -Format "HH:mm:ss"

    # Create the log entry
    $logEntry = "$timeStamp [$Level] - $Message"

    try {
        Add-Content -Path $logFile -Value $logEntry -ErrorAction Stop
    }
    catch {
        Write-Error "Failed to write to log file: $_"
    }
}

Esta versión mejorada te brinda mucha más flexibilidad. Aquí te explicamos cómo usarla:

# Basic information logging
Write-Log -Message "Starting software installation"

# Warning about a non-critical issue
Write-Log -Message "Config file not found, using defaults" -Level Warning

# Log an error
Write-Log -Message "Installation failed!" -Level Error

# Use a custom log path
Write-Log -Message "Custom path log" -LogFilePath "D:\\CustomLogs"

El archivo de registro resultante (log-03-12-2024.txt) se verá así:

10:15:22 [Information] - Starting software installation
10:15:23 [Warning] - Config file not found, using defaults
10:15:25 [Error] - Installation failed!

Y en D:\CustomLogs\log-03-12-2024.txt:

10:15:26 [Information] - Custom path log

Nota cómo cada entrada incluye la marca de tiempo, el nivel de registro entre corchetes y el mensaje. Este formato estructurado facilita el análisis de registros y la identificación rápida de problemas.

Ejemplo del mundo real: Script de instalación de software

Pongamos nuestra función de registro a trabajar en un script real que instala software en silencio:

# First, dot-source the logging function
. .\\Write-Log.ps1

# Script variables
$installer = "C:\\Installers\\software.exe"
$logPath = "C:\\Scripts\\InstallLogs"

# Start logging
Write-Log -Message "Beginning installation process" -LogFilePath $logPath

# Check if installer exists
if (Test-Path $installer) {
    Write-Log -Message "Found installer at: $installer"

    try {
        # Attempt installation
        Write-Log -Message "Starting installation..."
        $process = Start-Process -FilePath $installer -ArgumentList '/i /s' -Wait -NoNewWindow -PassThru

        # Check the exit code
        if ($process.ExitCode -eq 0) {
            Write-Log -Message "Installation completed successfully"
        }
        else {
            Write-Log -Message "Installation failed with exit code: $($process.ExitCode)" -Level Error
        }
    }
    catch {
        Write-Log -Message "Installation failed with error: $_" -Level Error
    }
}
else {
    Write-Log -Message "Installer not found at: $installer" -Level Error
}

Write-Log -Message "Installation script completed"

El archivo de registro resultante se verá algo así:

09:15:22 [Information] - Beginning installation process
09:15:22 [Information] - Found installer at: C:\\Installers\\software.exe
09:15:22 [Information] - Starting installation...
09:16:45 [Information] - Installation completed successfully
09:16:45 [Information] - Installation script completed

Consejos útiles

Aquí hay algunas mejores prácticas al usar esta función de registro:

  1. Siempre registra el inicio y el final de tu script – Esto ayuda a rastrear el tiempo de ejecución del script y el estado de finalización.

  2. Utiliza niveles de registro apropiados – No marques todo como un error; utiliza el nivel correcto para la situación:

    • Información: Operaciones normales
    • Advertencia: Problemas no críticos
    • Error: Problemas críticos que necesitan atención
  3. Incluye detalles relevantes – Registra suficiente información para entender qué sucedió:

    # Malo
    Write-Log "Falló la conexión"
    
    # Bueno
    Write-Log "Falló la conexión al servidor 'SQL01' - tiempo de espera después de 30 segundos" -Level Error
    
  4. Limpia los registros antiguos – Considera agregar rotación de registros para evitar llenar el espacio en disco:

    # Eliminar registros más antiguos de 30 días
    Get-ChildItem -Path $LogFilePath -Filter "*.txt" |
        Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
        Remove-Item
    

Conclusión

Una buena función de registro es esencial para cualquier script de PowerShell serio. Con la función Write-Log que hemos construido, ahora tienes una forma flexible y reutilizable de agregar un registro adecuado a todos tus scripts. Recuerda adaptar la función a tus necesidades específicas – podrías querer agregar características como:

Rotación de Registros

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [int]$MaxLogFiles = 30  # Keep last 30 days of logs
    )

    # Remove old log files
    Get-ChildItem -Path $LogFilePath -Filter "*.txt" |
        Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$MaxLogFiles) } |
        Remove-Item -Force

    # Continue with normal logging...
}

Diferentes Formatos de Salida (CSV, JSON)

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [ValidateSet('TXT','CSV','JSON')]
        [string]$Format = 'TXT'
    )

    $logEntry = [PSCustomObject]@{
        Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Level = $Level
        Message = $Message
    }

    switch ($Format) {
        'CSV'  { $logEntry | Export-Csv -Path "$LogFilePath\\log.csv" -Append -NoTypeInformation }
        'JSON' { $logEntry | ConvertTo-Json | Add-Content -Path "$LogFilePath\\log.json" }
        'TXT'  { "$($logEntry.Timestamp) [$($logEntry.Level)] - $($logEntry.Message)" |
                 Add-Content -Path "$LogFilePath\\log.txt" }
    }
}

Soporte de Rutas de Red

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [string]$NetworkPath = "\\\\server\\logs"
    )

    # Test network path connectivity
    if (!(Test-Path $NetworkPath)) {
        # Fallback to local logging if network is unavailable
        $NetworkPath = "C:\\Scripts\\Logs"
        Write-Warning "Network path unavailable. Using local path: $NetworkPath"
    }

    # Continue with normal logging...
}

Notificaciones por Correo Electrónico para Errores

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [string]$SmtpServer = "smtp.company.com",

        [Parameter()]
        [string[]]$NotifyOnError = "[email protected]"
    )

    # Normal logging first...

    # Send email if this is an error
    if ($Level -eq 'Error' -and $NotifyOnError) {
        $emailParams = @{
            From = "[email protected]"
            To = $NotifyOnError
            Subject = "PowerShell Script Error"
            Body = "Error occurred at $timeStamp`n`nMessage: $Message"
            SmtpServer = $SmtpServer
        }

        try {
            Send-MailMessage @emailParams
        }
        catch {
            Write-Warning "Failed to send error notification: $_"
        }
    }
}

La clave es comenzar con una base sólida y construir a partir de ahí según tus necesidades específicas. Estos ejemplos deberían darte un buen punto de partida para extender la función básica de registro con características más avanzadas.

Por ejemplo, podrías combinar varias de estas características en una solución de registro única y completa:

Write-Log -Message "Critical error in payment processing" `
          -Level Error `
          -Format CSV `
          -NetworkPath "\\\\server\\logs" `
          -NotifyOnError "[email protected]","[email protected]" `
          -MaxLogFiles 90

Esto permitiría:

  • Registrar el error en formato CSV
  • Almacenarlo en una carpeta compartida en red
  • Enviar por correo electrónico a varios destinatarios
  • Mantener un historial de registro de 90 días

Recuerda probar exhaustivamente, especialmente al implementar rutas de red o notificaciones por correo electrónico, ya que estas dependencias externas pueden afectar la confiabilidad de tu script. ¡Feliz programación!

Source:
https://adamtheautomator.com/powershell-write-log-tutorial/