Aplicación de scripts PowerShell — automatizar de forma segura la investigación de logs, el archivado y la generación de informes
· Go Komura · 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.
- Investigar los logs
- Recopilar las líneas de error en un CSV
- Listar los logs antiguos como candidatos a archivado
- Mover los logs antiguos si es necesario
- 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.
- Separar la configuración
- Construir el procesamiento de lectura
- Dejar constancia de la salida
- Separar el procesamiento de cambios en funciones
- Ensayar con
-WhatIf - 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.csvarchive-targets.csvarchive-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.exeestá 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.txty 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.csveran razonables - El valor
Destinationdearchive-result.csvcoincidía con lo esperado - Quedó en la carpeta de salida una evidencia con marca de fecha y hora
- Se confirmó que
error.txtse 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
- El conjunto completo de código de muestra de este artículo (script final, archivo de configuración, pruebas Pester) - komurasoft-blog-samples (GitHub)
- PowerShell Documentation - Microsoft Learn
- ConvertTo-Json - Microsoft Learn
- about_Functions_CmdletBindingAttribute - Microsoft Learn
- Start-Transcript - Microsoft Learn
- New-ScheduledTaskAction - Microsoft Learn
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Externalización y desarrollo por encargo de una app Windows: qué aclarar antes de solicitarlo
Antes de encargar la externalización o el desarrollo por encargo de una app Windows, estos son los puntos a aclarar: revisión de software...
El extraño amor de un desarrollador, o: cómo aprendí a dejar de preocuparme y amar Windows
Windows es incómodo. Pero esa incomodidad es también la de un sistema operativo que lleva décadas cargando sobre sus espaldas el trabajo ...
Guía práctica de Process Monitor (ProcMon): identifica en 10 minutos por qué «no se lee la configuración» o aparece «ACCESS DENIED»
«Modifiqué el archivo de configuración pero no se refleja», «funcionaba hasta ayer, pero hoy no arranca»: antes de tocar el código fuente...
Cuándo no conviene migrar una aplicación de Windows a la Web: una tabla de decisión y la solución realista de la "división"
Cada vez son más las solicitudes para migrar aplicaciones Windows de uso interno a la Web, pero en aplicaciones con integración de dispos...
Internacionalización de aplicaciones WinForms/WPF: la práctica de resx, ensamblados satélite y cambio de cultura
Organizamos desde una perspectiva práctica la internacionalización de aplicaciones de escritorio Windows: la diferencia entre CurrentCult...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
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.
Enlaces públicos