Aplicación de scripts PowerShell — automatizar de forma segura la investigación de logs, el archivado y la generación de informes

· · PowerShell, Windows, Automatización, Investigación de logs, Mejora operativa, Aprovechamiento de activos existentes

1. Lo primero que hay que tener claro

La vez anterior repasamos los comandos básicos de PowerShell, el pipeline, la salida a CSV, JSON, los scripts .ps1 y la verificación segura con -WhatIf. Esta vez, como continuación, vamos a construir un script un poco más grande y útil en el trabajo real. El tema son tareas operativas como las siguientes.

  1. Investigar los logs
  2. Recopilar las líneas de error en un CSV
  3. Listar los logs antiguos como candidatos a archivado
  4. Mover los logs antiguos si es necesario
  5. Dejar el resultado de la ejecución como evidencia

Lo importante en la aplicación práctica de PowerShell no es alargar los comandos, sino establecer el siguiente flujo.

  1. Separar la configuración
  2. Construir el procesamiento de lectura
  3. Dejar constancia de la salida
  4. Separar el procesamiento de cambios en funciones
  5. Ensayar con -WhatIf
  6. Considerar la ejecución automática al final

La automatización no consiste en «omitir la verificación humana». Consiste en fijar dónde debe ocurrir la verificación y en asegurar que siempre queden las mismas evidencias.

El código que aparece en este artículo se publica en GitHub como un conjunto de muestras listo para ejecutar (el script final, el archivo de configuración, un script para crear un entorno de logs de prueba y pruebas Pester que verifican la investigación, el ensayo y el movimiento).

powershell-script-log-maintenance-automation - komurasoft-blog-samples (GitHub)

2. Lo que vamos a construir esta vez

Esta vez construiremos un script llamado Invoke-LogMaintenance.ps1.

Sus principales funciones son las siguientes.

Función Contenido
Búsqueda de logs Buscar archivos .log bajo la carpeta indicada
Ventana de tiempo Investigar solo los logs modificados en los últimos N días
Extracción de errores Extraer líneas que contengan ERROR, WARN, FATAL, etc.
Salida a CSV Guardar los resultados de la búsqueda en log-hits.csv
Listado de logs antiguos Guardar en archive-targets.csv los logs más antiguos que N días
Archivado Mover los logs antiguos a otra carpeta
Ejecución de ensayo Con -Preview, mostrar el plan sin mover nada
Registro de ejecución Guardar el transcript, el JSON de resumen y los CSV de resultado

Cabe destacar que no se elimina nada. En esta primera entrega de aplicación práctica, nos limitamos al «movimiento», que es más seguro que la eliminación.

3. Estructura de carpetas

Como ejemplo, usamos la siguiente estructura.

C:\Ops
  Invoke-LogMaintenance.ps1
  log-maintenance.json

C:\App\Logs
  app.log
  batch.log
  old
    app-202401.log

C:\App\Reports
  20260602-030000
    log-hits.csv
    archive-targets.csv
    archive-result.csv
    summary.json
    transcript.txt

C:\App\Archive
  20260602-030000
    old
      app-202401.log

Mantener separados el cuerpo del script y el archivo de configuración facilita el reemplazo según el entorno. Se puede operar cambiando solo las rutas, por ejemplo C:\Test\Logs en desarrollo y D:\App\Logs en producción.

4. Crear el archivo de configuración

Primero creamos log-maintenance.json.

{
  "LogPath": "C:\\App\\Logs",
  "OutputPath": "C:\\App\\Reports",
  "Days": 7,
  "Patterns": [
    "ERROR",
    "WARN",
    "FATAL"
  ],
  "ArchiveDays": 90,
  "ArchivePath": "C:\\App\\Archive"
}

El significado de cada campo es el siguiente.

Elemento Significado
LogPath Carpeta de logs a investigar
OutputPath Destino de los informes
Days Cuántos días recientes de logs investigar
Patterns Cadenas o patrones a buscar
ArchiveDays A partir de cuántos días de antigüedad un log pasa a ser candidato a archivado
ArchivePath Carpeta de destino del archivado

Usar JSON permite cambiar las condiciones sin editar el cuerpo del script.

En PowerShell, ConvertFrom-Json permite tratar el JSON como objetos. A la inversa, para dejar el resultado del procesamiento en JSON se usa ConvertTo-Json. ConvertTo-Json es el cmdlet que convierte objetos en cadenas JSON; al tratar con jerarquías profundas, es importante especificar -Depth.

5. Construir primero solo la lectura

En lugar de escribir de inmediato el procesamiento de movimiento, al principio nos limitamos a buscar los logs y convertirlos en un CSV.

$config = Get-Content .\log-maintenance.json -Raw -Encoding UTF8 | ConvertFrom-Json

$since = (Get-Date).AddDays(-[int]$config.Days)

$files = Get-ChildItem -LiteralPath $config.LogPath -Filter *.log -File -Recurse |
  Where-Object { $_.LastWriteTime -ge $since }

$files |
  Select-Object FullName, Length, LastWriteTime

A continuación, buscamos dentro del contenido de los logs.

$patterns = [string[]]$config.Patterns

Select-String -LiteralPath ($files | Select-Object -ExpandProperty FullName) -Pattern $patterns |
  Select-Object Path, LineNumber, Pattern, Line |
  Export-Csv .\log-hits.csv -NoTypeInformation -Encoding UTF8

Hasta aquí todo es solo lectura. Incluso al probarlo en la carpeta de producción, conviene detenerse primero en esta etapa.

6. Listar los logs antiguos

A continuación, listamos los objetos a archivar.

$limit = (Get-Date).AddDays(-[int]$config.ArchiveDays)

$targets = Get-ChildItem -LiteralPath $config.LogPath -Filter *.log -File -Recurse |
  Where-Object { $_.LastWriteTime -lt $limit } |
  Sort-Object LastWriteTime

$targets |
  Select-Object FullName, Length, LastWriteTime |
  Export-Csv .\archive-targets.csv -NoTypeInformation -Encoding UTF8

En esta etapa tampoco movemos nada todavía. Revise archive-targets.csv para confirmar que los objetos no son demasiados y que la carpeta no está equivocada.

7. Separar el procesamiento de cambios en una función

El procesamiento de cambios, como el movimiento de archivos, se separa del procesamiento de lectura.

En PowerShell, al agregar SupportsShouldProcess a una función, esta puede manejar -WhatIf y -Confirm. -WhatIf muestra «qué se cambiaría» sin ejecutarlo, y -Confirm es el mecanismo para pedir confirmación antes de ejecutar. Puede consultar los detalles en about_Functions_CmdletBindingAttribute de Microsoft Learn.

function Move-OldLogFile {
  [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
  param(
    [Parameter(Mandatory)]
    [System.IO.FileInfo[]]$File,

    [Parameter(Mandatory)]
    [string]$SourceRoot,

    [Parameter(Mandatory)]
    [string]$ArchiveRoot
  )

  foreach ($item in $File) {
    $relativePath = [System.IO.Path]::GetRelativePath($SourceRoot, $item.FullName)
    $destination = Join-Path $ArchiveRoot $relativePath
    $destinationDirectory = Split-Path -Path $destination -Parent

    if ($PSCmdlet.ShouldProcess($item.FullName, "Move to $destination")) {
      if (-not [System.IO.Directory]::Exists($destinationDirectory)) {
        [System.IO.Directory]::CreateDirectory($destinationDirectory) | Out-Null
      }

      Move-Item -LiteralPath $item.FullName -Destination $destination -ErrorAction Stop

      [pscustomobject]@{
        Source      = $item.FullName
        Destination = $destination
        Status      = "Moved"
        Message     = ""
      }
    }
    else {
      [pscustomobject]@{
        Source      = $item.FullName
        Destination = $destination
        Status      = "Preview"
        Message     = ""
      }
    }
  }
}

El punto clave es que $PSCmdlet.ShouldProcess() se coloca justo antes de Move-Item. La decisión de si se cambia o no se coloca justo antes del cambio, no fuera de la función.

8. Script completo

Aquí tiene la versión final que reúne todo lo anterior. El nombre del archivo es Invoke-LogMaintenance.ps1.

# Invoke-LogMaintenance.ps1
#Requires -Version 7.0

[CmdletBinding()]
param(
  [ValidateNotNullOrEmpty()]
  [string]$ConfigPath = ".\log-maintenance.json",

  [switch]$Preview,

  [switch]$SkipArchive,

  [switch]$SkipTranscript
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Ensure-Directory {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [string]$Path
  )

  if (-not [System.IO.Directory]::Exists($Path)) {
    [System.IO.Directory]::CreateDirectory($Path) | Out-Null
  }
}

function Import-LogMaintenanceConfig {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [string]$Path
  )

  if (-not (Test-Path -LiteralPath $Path)) {
    throw "Config file not found: $Path"
  }

  $config = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json

  foreach ($name in @("LogPath", "OutputPath", "Days", "Patterns", "ArchiveDays", "ArchivePath")) {
    if (-not ($config.PSObject.Properties.Name -contains $name)) {
      throw "Config value missing: $name"
    }
  }

  if ([string]::IsNullOrWhiteSpace([string]$config.LogPath)) {
    throw "LogPath is empty."
  }

  if (-not (Test-Path -LiteralPath $config.LogPath)) {
    throw "LogPath not found: $($config.LogPath)"
  }

  if ([string]::IsNullOrWhiteSpace([string]$config.OutputPath)) {
    throw "OutputPath is empty."
  }

  if ([string]::IsNullOrWhiteSpace([string]$config.ArchivePath)) {
    throw "ArchivePath is empty."
  }

  if (@($config.Patterns).Count -eq 0) {
    throw "Patterns is empty."
  }

  if ([int]$config.Days -lt 1) {
    throw "Days must be 1 or greater."
  }

  if ([int]$config.ArchiveDays -lt 1) {
    throw "ArchiveDays must be 1 or greater."
  }

  return $config
}

function Export-CsvWithHeader {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [object[]]$InputObject,

    [Parameter(Mandatory)]
    [string]$Path,

    [Parameter(Mandatory)]
    [string[]]$Header
  )

  if ($InputObject.Count -gt 0) {
    $InputObject |
      Export-Csv -LiteralPath $Path -NoTypeInformation -Encoding UTF8
  }
  else {
    ($Header -join ",") |
      Set-Content -LiteralPath $Path -Encoding UTF8
  }
}

function Get-LogHit {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [string]$LogPath,

    [Parameter(Mandatory)]
    [ValidateRange(1, 3650)]
    [int]$Days,

    [Parameter(Mandatory)]
    [string[]]$Pattern
  )

  $since = (Get-Date).AddDays(-$Days)

  $files = @(
    Get-ChildItem -LiteralPath $LogPath -Filter *.log -File -Recurse -ErrorAction Stop |
      Where-Object { $_.LastWriteTime -ge $since }
  )

  Write-Verbose "Recent log files: $($files.Count)"

  if ($files.Count -eq 0) {
    return @()
  }

  $paths = $files | Select-Object -ExpandProperty FullName

  Select-String -LiteralPath $paths -Pattern $Pattern -ErrorAction Stop |
    ForEach-Object {
      [pscustomobject]@{
        Path       = $_.Path
        LineNumber = $_.LineNumber
        Pattern    = $_.Pattern
        Line       = $_.Line.Trim()
      }
    }
}

function Get-OldLogFile {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [string]$LogPath,

    [Parameter(Mandatory)]
    [ValidateRange(1, 3650)]
    [int]$ArchiveDays
  )

  $limit = (Get-Date).AddDays(-$ArchiveDays)

  Get-ChildItem -LiteralPath $LogPath -Filter *.log -File -Recurse -ErrorAction Stop |
    Where-Object { $_.LastWriteTime -lt $limit } |
    Sort-Object LastWriteTime
}

function Move-OldLogFile {
  [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
  param(
    [Parameter(Mandatory)]
    [System.IO.FileInfo[]]$File,

    [Parameter(Mandatory)]
    [string]$SourceRoot,

    [Parameter(Mandatory)]
    [string]$ArchiveRoot
  )

  foreach ($item in $File) {
    $relativePath = [System.IO.Path]::GetRelativePath($SourceRoot, $item.FullName)
    $destination = Join-Path $ArchiveRoot $relativePath
    $destinationDirectory = Split-Path -Path $destination -Parent

    if (Test-Path -LiteralPath $destination) {
      $name = [System.IO.Path]::GetFileNameWithoutExtension($item.Name)
      $ext = $item.Extension
      $destination = Join-Path $destinationDirectory ("{0}_{1:yyyyMMddHHmmss}{2}" -f $name, $item.LastWriteTime, $ext)
    }

    if ($PSCmdlet.ShouldProcess($item.FullName, "Move to $destination")) {
      Ensure-Directory -Path $destinationDirectory

      Move-Item -LiteralPath $item.FullName -Destination $destination -ErrorAction Stop

      [pscustomobject]@{
        Source      = $item.FullName
        Destination = $destination
        Status      = "Moved"
        Message     = ""
      }
    }
    else {
      [pscustomobject]@{
        Source      = $item.FullName
        Destination = $destination
        Status      = "Preview"
        Message     = ""
      }
    }
  }
}

$config = Import-LogMaintenanceConfig -Path $ConfigPath

$runStamp = Get-Date -Format "yyyyMMdd-HHmmss"
$reportDir = Join-Path ([string]$config.OutputPath) $runStamp
Ensure-Directory -Path $reportDir

$transcriptStarted = $false
$transcriptPath = Join-Path $reportDir "transcript.txt"

try {
  if (-not $SkipTranscript) {
    Start-Transcript -Path $transcriptPath -Force | Out-Null
    $transcriptStarted = $true
  }

  Write-Host "Report directory: $reportDir"

  $hits = @(
    Get-LogHit `
      -LogPath ([string]$config.LogPath) `
      -Days ([int]$config.Days) `
      -Pattern ([string[]]$config.Patterns)
  )

  $hitCsv = Join-Path $reportDir "log-hits.csv"
  Export-CsvWithHeader `
    -InputObject $hits `
    -Path $hitCsv `
    -Header @("Path", "LineNumber", "Pattern", "Line")

  $oldFiles = @(
    Get-OldLogFile `
      -LogPath ([string]$config.LogPath) `
      -ArchiveDays ([int]$config.ArchiveDays)
  )

  $archiveTargets = @(
    $oldFiles |
      Select-Object FullName, Length, LastWriteTime
  )

  $archiveTargetCsv = Join-Path $reportDir "archive-targets.csv"
  Export-CsvWithHeader `
    -InputObject $archiveTargets `
    -Path $archiveTargetCsv `
    -Header @("FullName", "Length", "LastWriteTime")

  $moveResults = @()

  if ($SkipArchive) {
    Write-Host "Archive skipped."
  }
  elseif ($oldFiles.Count -eq 0) {
    Write-Host "No archive targets."
  }
  else {
    $archiveRunRoot = Join-Path ([string]$config.ArchivePath) $runStamp

    $moveResults = @(
      Move-OldLogFile `
        -File $oldFiles `
        -SourceRoot ([string]$config.LogPath) `
        -ArchiveRoot $archiveRunRoot `
        -WhatIf:$Preview
    )
  }

  $archiveResultCsv = Join-Path $reportDir "archive-result.csv"
  Export-CsvWithHeader `
    -InputObject $moveResults `
    -Path $archiveResultCsv `
    -Header @("Source", "Destination", "Status", "Message")

  $summary = [pscustomobject]@{
    CheckedAt          = (Get-Date).ToString("s")
    ComputerName       = $env:COMPUTERNAME
    LogPath            = [string]$config.LogPath
    ReportDirectory    = $reportDir
    HitCount           = $hits.Count
    ArchiveTargetCount = $oldFiles.Count
    ArchiveResultCount = $moveResults.Count
    Preview            = [bool]$Preview
    SkipArchive        = [bool]$SkipArchive
  }

  $summaryPath = Join-Path $reportDir "summary.json"
  $summary |
    ConvertTo-Json -Depth 5 |
    Set-Content -LiteralPath $summaryPath -Encoding UTF8

  Write-Host "Finished."
  Write-Host "Hits: $($hits.Count)"
  Write-Host "Archive targets: $($oldFiles.Count)"
}
catch {
  $errorPath = Join-Path $reportDir "error.txt"
  $_ | Out-String | Set-Content -LiteralPath $errorPath -Encoding UTF8
  Write-Error "Failed: $($_.Exception.Message)"
  exit 1
}
finally {
  if ($transcriptStarted) {
    Stop-Transcript | Out-Null
  }
}

9. Ejemplos de ejecución

Primero, ejecutamos solo la investigación de logs sin realizar el archivado.

.\Invoke-LogMaintenance.ps1 -ConfigPath .\log-maintenance.json -SkipArchive

A continuación, comprobamos el plan de archivado.

.\Invoke-LogMaintenance.ps1 -ConfigPath .\log-maintenance.json -Preview

Al indicar -Preview, el procesamiento de movimiento de logs antiguos se trata como -WhatIf.

En este punto, los tres archivos que hay que revisar son:

  • log-hits.csv
  • archive-targets.csv
  • archive-result.csv

Si no hay problemas, se ejecuta sin -Preview.

.\Invoke-LogMaintenance.ps1 -ConfigPath .\log-maintenance.json

Si desea ver más detalle, agregue -Verbose.

.\Invoke-LogMaintenance.ps1 -ConfigPath .\log-maintenance.json -Preview -Verbose

10. Archivos generados

Al ejecutarse, se crea bajo OutputPath una carpeta con marca de fecha y hora, como C:\App\Reports\20260602-030000.

Dentro se generan los siguientes archivos.

Archivo Contenido
log-hits.csv Líneas detectadas como errores o advertencias
archive-targets.csv Logs antiguos que pasaron a ser objeto de archivado
archive-result.csv Resultado del movimiento, o resultado del Preview
summary.json Resumen de cantidades y condiciones de ejecución
transcript.txt Registro de la sesión de PowerShell
error.txt Detalle cuando ocurre un error

Start-Transcript es el cmdlet para registrar en un archivo de texto los comandos y la salida de consola de una sesión de PowerShell. En scripts operativos, esto facilita comprobar más adelante «cuándo, bajo qué condiciones y qué se produjo».

11. Ejecutar periódicamente con el Programador de tareas

Si la ejecución manual no presenta problemas, se puede ejecutar de forma periódica con el Programador de tareas.

Este es un ejemplo que, al principio, se ejecuta todos los días a las 3 de la madrugada.

$scriptPath = "C:\Ops\Invoke-LogMaintenance.ps1"
$configPath = "C:\Ops\log-maintenance.json"

$action = New-ScheduledTaskAction `
  -Execute "pwsh.exe" `
  -Argument "-NoProfile -File `"$scriptPath`" -ConfigPath `"$configPath`"" `
  -WorkingDirectory "C:\Ops"

$trigger = New-ScheduledTaskTrigger -Daily -At 3:00

Register-ScheduledTask `
  -TaskName "AppLogMaintenance" `
  -Action $action `
  -Trigger $trigger `
  -Description "Collect app log errors and archive old logs"

New-ScheduledTaskAction crea un objeto que representa el comando que ejecutará la tarea, y New-ScheduledTaskTrigger crea las condiciones de inicio: diario, semanal, al iniciar sesión, etc. Finalmente, Register-ScheduledTask registra la tarea en el equipo local.

En un entorno de producción, conviene comprobar también lo siguiente.

  • El usuario de ejecución tiene permiso de lectura sobre la carpeta de logs
  • Hay permiso de escritura sobre el destino de archivado
  • La ruta a pwsh.exe está resuelta
  • Se ajusta a la política de ejecución y a las reglas de firma del script
  • La ejecución manual y la ejecución mediante la tarea producen el mismo resultado
  • En caso de fallo, se puede revisar error.txt y el historial de la tarea

12. Problemas frecuentes

Síntoma Causa Solución
No se encuentran logs LogPath está mal configurado Verificar con Test-Path y Get-ChildItem
El CSV queda vacío No hay logs dentro del período indicado Ampliar Days y verificar
El texto aparece con caracteres corruptos La codificación de caracteres del log no es la esperada Verificar la codificación de entrada y salida
No funciona como tarea El usuario de ejecución o la carpeta de trabajo son distintos Verificar WorkingDirectory y los permisos
Hay demasiados objetos de archivado ArchiveDays es demasiado corto Revisar archive-targets.csv y ajustar
El destino no coincide con lo esperado No se entiende la regla de conservación de la ruta relativa Verificar Destination con -Preview
Solo falla en producción Diferencias de permisos, política o archivos bloqueados Verificar error.txt y transcript.txt

En particular, al ejecutarlo mediante el Programador de tareas, «el usuario con el que usted lo ejecuta» y «el usuario de ejecución de la tarea» pueden ser distintos. Si funciona manualmente pero falla como tarea, sospeche primero de los permisos y de la carpeta de trabajo.

13. Cómo pensar las modificaciones

Este script está pensado para adaptarse gradualmente al entorno de trabajo en lugar de usarse tal cual. A continuación, algunos ejemplos de modificaciones habituales.

Qué se quiere hacer Dónde modificar
Incluir también .txt Cambiar Get-ChildItem -Filter *.log
Ver también unas líneas antes y después de ERROR A partir del resultado de Select-String, obtener las líneas cercanas con Get-Content
Comprimir antes de archivar Agregar Compress-Archive antes de Move-OldLogFile
Enviar una notificación por correo Agregar procesamiento de notificación basado en summary.json
Separar la configuración por aplicación Preparar varios JSON y dividir las tareas
Automatizar también la eliminación Primero operar con el movimiento durante un tiempo y considerarlo después

Sin embargo, es más seguro no incluir todo desde el principio. En un script operativo, importa más poder rastrear lo ocurrido cuando falla que tener muchas funciones.

14. Lista de verificación operativa para el terreno

Antes de ejecutar un script de PowerShell de forma periódica, conviene comprobar lo siguiente.

  • Primero se ejecutó solo el procesamiento de lectura con -SkipArchive
  • Después se comprobó el plan de movimiento con -Preview
  • Los objetos de archive-targets.csv eran razonables
  • El valor Destination de archive-result.csv coincidía con lo esperado
  • Quedó en la carpeta de salida una evidencia con marca de fecha y hora
  • Se confirmó que error.txt se genera en caso de error
  • Se verificaron los permisos del usuario de ejecución de la tarea
  • Se verificaron la política de ejecución, la firma y las normas internas
  • Se opera primero con el movimiento, no con la eliminación directa
  • Se ha decidido de antemano el destino de restauración en caso de recuperación

15. Conclusión

Aplicar PowerShell no significa usar mucha sintaxis complicada. Lo que realmente rinde en el trabajo real es contar con un patrón como este.

  • Separar la configuración en JSON
  • Construir primero el procesamiento de lectura
  • Dejar evidencias en CSV y JSON
  • Separar el procesamiento de cambios en funciones
  • Preparar un ensayo equivalente a -WhatIf
  • Poder rastrear lo ocurrido mediante el transcript y error.txt
  • Verificar con ejecución manual antes de programar la ejecución periódica

El script de este artículo toma como tema la investigación y el archivado de logs, pero el enfoque también sirve para otras tareas.

  • Organización de archivos
  • Generación de informes
  • Agregación de datos en CSV
  • Sustitución de procesos por lotes
  • Inventario de activos antiguos
  • Verificaciones operativas diarias o mensuales

PowerShell es útil incluso como comandos de una sola línea, pero para uso en el trabajo real es más seguro respetar este orden:

Observar → Registrar → Ensayar → Ejecutar → Dejar evidencia

Si se mantiene esta forma de trabajar, PowerShell deja de ser solo una herramienta para ahorrar tiempo y pasa a funcionar como una pequeña aplicación de negocio que ayuda a estabilizar la operación.

Enlaces de referencia

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

Al automatizar la investigación de logs con PowerShell, ¿por dónde conviene empezar?
No conviene escribir directamente procesos de cambio como mover o eliminar archivos; primero se construye solo el procesamiento de lectura. Complete primero la parte que busca los logs objetivo con Get-ChildItem, extrae las líneas de error con Select-String y las exporta a un CSV. Incluso al probarlo contra la carpeta de producción, deténgase en esta etapa de lectura y revise los resultados. Después, avance de forma gradual hacia la elaboración del listado de objetos a archivar y, finalmente, hacia la creación de una función para el procesamiento de movimiento.
¿Cómo se incorpora -WhatIf de PowerShell en un script?
Al agregar SupportsShouldProcess al atributo CmdletBinding de una función, esa función puede usar -WhatIf y -Confirm. El punto clave es colocar $PSCmdlet.ShouldProcess() justo antes del procesamiento que realiza el cambio real (como Move-Item), de modo que la decisión de si se cambia o no se tome justo antes del cambio. En el script de este artículo, el switch -Preview se pasa como -WhatIf:$Preview, lo que permite comprobar solo el plan sin mover realmente los archivos.
¿Por qué un script de PowerShell funciona al ejecutarlo manualmente pero falla en el Programador de tareas?
La causa típica es que el usuario con el que se ejecuta la tarea es distinto del usuario con el que usted la probó manualmente. Compruebe si ese usuario tiene permiso de lectura sobre la carpeta de logs y de escritura sobre el destino de archivado, si la carpeta de trabajo (WorkingDirectory) es correcta, si la ruta a pwsh.exe está resuelta y si el script cumple con la política de ejecución y las reglas de firma. Es importante que, en caso de fallo, se pueda investigar mediante error.txt, transcript.txt y el historial de la tarea.
¿Es recomendable automatizar también la eliminación de logs antiguos?
No se recomienda automatizar la eliminación desde el principio. El script de este artículo también se limita al «movimiento», que es más seguro que la eliminación. Primero, opere con el movimiento durante un período determinado, revise evidencias como archive-targets.csv y archive-result.csv, y solo después de confirmar que no hay problemas, considere automatizar la eliminación. También es importante decidir de antemano el destino de restauración por si hace falta recuperar algo.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog