PowerShell avanzato — Automatizzare in sicurezza l'analisi dei log, l'archiviazione e la reportistica

· · PowerShell, Windows, Automazione, Analisi dei log, Miglioramento operativo, Riutilizzo di asset esistenti

1. Cosa sapere prima di iniziare

Nell’articolo precedente abbiamo riassunto i comandi di base di PowerShell, la pipeline, l’esportazione in CSV, il JSON, gli script .ps1 e le verifiche di sicurezza con -WhatIf. In questo articolo, come seguito, costruiremo uno script un po’ più grande e utile nel lavoro quotidiano. L’argomento è un’attività operativa come questa:

  1. Analizzare i log
  2. Raccogliere le righe di errore in un CSV
  3. Elencare i log obsoleti come candidati per l’archiviazione
  4. Spostare i log obsoleti, se necessario
  5. Conservare il risultato dell’esecuzione come traccia di audit

Ciò che conta nell’uso avanzato di PowerShell non è allungare i comandi, ma costruire il seguente flusso:

  1. Separare la configurazione
  2. Costruire l’elaborazione di sola lettura
  3. Conservare l’output
  4. Suddividere le operazioni di modifica in funzioni
  5. Fare una prova con -WhatIf
  6. Valutare l’esecuzione automatica per ultima

Automatizzare non significa «eliminare la verifica umana». Significa fissare il punto in cui va effettuata la verifica e garantire che venga prodotta la stessa traccia di audit ogni volta.

Il codice presentato in questo articolo è pubblicato su GitHub come set di esempi pronti all’uso (lo script completo, il file di configurazione, uno script per creare un ambiente di log fittizio e i test Pester che verificano l’analisi, la simulazione e lo spostamento).

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

2. Cosa costruiremo

In questo articolo costruiremo uno script chiamato Invoke-LogMaintenance.ps1.

Le funzionalità principali sono le seguenti.

Funzionalità Descrizione
Ricerca dei log Cerca i file .log sotto la cartella specificata
Intervallo temporale Analizza solo i log modificati negli ultimi N giorni
Estrazione errori Estrae le righe contenenti ERROR, WARN, FATAL, ecc.
Esportazione CSV Salva i risultati della ricerca in log-hits.csv
Elenco log obsoleti Salva i log più vecchi di N giorni in archive-targets.csv
Archiviazione Sposta i log obsoleti in una cartella separata
Esecuzione di prova Con -Preview non sposta nulla, mostra solo il piano previsto
Registrazione dell’esecuzione Salva transcript, JSON di riepilogo e i CSV dei risultati

Da notare che non viene effettuata alcuna eliminazione. In questo primo capitolo avanzato ci limitiamo allo «spostamento», più sicuro della cancellazione.

3. Struttura delle cartelle

Come esempio, adottiamo la seguente struttura.

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

Tenere separati il corpo dello script e il file di configurazione rende più semplice adattarli a ciascun ambiente. È possibile impostare un’operatività in cui cambia solo il percorso, ad esempio C:\Test\Logs in ambiente di sviluppo e D:\App\Logs in produzione.

4. Creare il file di configurazione

Per prima cosa creiamo log-maintenance.json.

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

I significati sono i seguenti.

Voce Significato
LogPath La cartella dei log da analizzare
OutputPath La destinazione dei report
Days Quanti giorni recenti di log analizzare
Patterns Le stringhe/pattern da cercare
ArchiveDays Da quanti giorni un log deve essere obsoleto per diventare candidato all’archiviazione
ArchivePath La cartella di destinazione dell’archivio

Usare il JSON permette di modificare le condizioni senza toccare il corpo dello script.

In PowerShell, ConvertFrom-Json permette di trattare il JSON come un oggetto. Viceversa, per salvare il risultato di un’elaborazione come JSON si usa ConvertTo-Json. ConvertTo-Json è il cmdlet che converte un oggetto in una stringa JSON; quando si ha a che fare con strutture annidate in profondità, specificare -Depth diventa importante.

5. Costruire prima solo la lettura

Senza scrivere subito l’elaborazione di spostamento, all’inizio ci limitiamo a cercare i log e a trasformarli in 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

Successivamente, cerchiamo all’interno del contenuto dei log.

$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

Fino a questo punto si tratta solo di lettura. Anche quando si prova sulla cartella di produzione, ci si ferma prima a questa fase.

6. Elencare i log obsoleti

Ora elenchiamo i candidati all’archiviazione.

$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

Anche a questo punto non spostiamo ancora nulla. Osservando archive-targets.csv verifichiamo che i candidati non siano troppi e che la cartella non sia sbagliata.

7. Separare le operazioni di modifica in funzioni

Le operazioni di modifica, come lo spostamento, vanno separate dall’elaborazione di sola lettura.

In PowerShell, aggiungendo SupportsShouldProcess a una funzione, questa diventa in grado di gestire -WhatIf e -Confirm. -WhatIf mostra «cosa si intende modificare» senza eseguire l’operazione, mentre -Confirm è il meccanismo che chiede conferma prima dell’esecuzione. Per maggiori dettagli si può consultare about_Functions_CmdletBindingAttribute su 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     = ""
      }
    }
  }
}

Il punto chiave è che $PSCmdlet.ShouldProcess() viene collocato subito prima di Move-Item. La decisione se procedere con la modifica non viene presa fuori dalla funzione, ma proprio a ridosso della modifica stessa.

8. Lo script completo

Ecco la versione completa che riunisce tutto quanto visto finora. Il nome del file è 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. Esempi di esecuzione

Per prima cosa eseguiamo solo l’analisi dei log, senza effettuare l’archiviazione.

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

Successivamente verifichiamo il piano di archiviazione.

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

Aggiungendo -Preview, l’elaborazione di spostamento dei log obsoleti viene trattata come -WhatIf.

I tre file da controllare a questo punto sono:

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

Se non ci sono problemi, si esegue rimuovendo -Preview.

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

Per vedere i dettagli, si aggiunge -Verbose.

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

10. I file generati in output

All’esecuzione, sotto OutputPath viene creata una cartella con timestamp come C:\App\Reports\20260602-030000.

Al suo interno vengono generati i seguenti file.

File Contenuto
log-hits.csv Le righe rilevate come errori o avvisi
archive-targets.csv I log obsoleti diventati candidati all’archiviazione
archive-result.csv Il risultato dello spostamento, oppure il risultato in modalità Preview
summary.json Un riepilogo dei conteggi e delle condizioni di esecuzione
transcript.txt La registrazione della sessione PowerShell
error.txt I dettagli in caso di errore

Start-Transcript è il cmdlet che registra in un file di testo i comandi e l’output della console di una sessione PowerShell. Negli script operativi, questo rende molto più facile verificare in seguito «quando, con quali condizioni e cosa è stato prodotto».

11. Eseguire periodicamente con l’Utilità di pianificazione

Se l’esecuzione manuale non presenta problemi, è possibile eseguire lo script periodicamente tramite l’Utilità di pianificazione.

Ecco un esempio iniziale che esegue lo script ogni giorno alle 3 del mattino.

$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 oggetto che rappresenta il comando eseguito dall’attività, mentre New-ScheduledTaskTrigger crea le condizioni di avvio: giornaliera, settimanale, all’accesso e così via. Infine, Register-ScheduledTask registra l’attività sul computer locale.

Nell’operatività di produzione, verificare anche i seguenti punti.

  • L’utente di esecuzione ha i permessi di lettura sulla cartella dei log
  • Sono presenti i permessi di scrittura sulla destinazione dell’archivio
  • Il percorso di pwsh.exe è raggiungibile
  • Lo script rispetta la execution policy e le regole di firma
  • L’esecuzione manuale e quella pianificata producono lo stesso risultato
  • In caso di fallimento è possibile controllare error.txt e la cronologia dell’attività

12. Problemi comuni

Sintomo Causa Rimedio
I log non vengono trovati LogPath è errato Verificare con Test-Path e Get-ChildItem
Il CSV risulta vuoto Non ci sono log nel periodo considerato Ampliare Days e verificare
Il testo giapponese appare corrotto La codifica dei log è diversa da quella prevista Verificare le codifiche di input/output
Non funziona come attività pianificata L’utente di esecuzione o la cartella di lavoro sono diversi Verificare WorkingDirectory e i permessi
Troppi candidati all’archiviazione ArchiveDays è troppo breve Osservare archive-targets.csv e regolare il valore
La destinazione dello spostamento non è quella prevista Non si è compresa la regola di mantenimento del percorso relativo Verificare Destination con -Preview
Fallisce solo in produzione Differenze di permessi, policy o file bloccati Verificare error.txt e transcript.txt

In particolare, quando si esegue tramite l’Utilità di pianificazione, «l’utente con cui si esegue manualmente» e «l’utente di esecuzione dell’attività» possono essere diversi. Se lo script funziona manualmente ma fallisce come attività pianificata, il primo sospetto deve ricadere sui permessi e sulla cartella di lavoro.

13. Come pensare alle personalizzazioni

Questo script è pensato più per essere adattato gradualmente al contesto operativo che per essere usato così com’è. Ecco alcuni esempi comuni di personalizzazione.

Cosa si vuole fare Dove intervenire
Includere anche i file .txt Modificare Get-ChildItem -Filter *.log
Vedere anche alcune righe prima e dopo ERROR Usare i risultati di Select-String per recuperare le righe circostanti con Get-Content
Comprimere prima di archiviare Aggiungere Compress-Archive prima di Move-OldLogFile
Inviare notifiche via email Aggiungere un’elaborazione di notifica basata su summary.json
Separare la configurazione per applicazione Predisporre più file JSON e suddividere le attività
Automatizzare anche l’eliminazione Prima verificare per un certo periodo l’operatività basata sullo spostamento, poi valutare

Tuttavia, è più sicuro non aggiungere tutto fin dall’inizio. Per uno script operativo, conta più la possibilità di risalire alla causa in caso di fallimento che il numero di funzionalità.

14. Checklist operativa sul campo

Prima di eseguire periodicamente uno script PowerShell, verificare i seguenti punti.

  • Si è eseguita prima solo l’elaborazione di lettura con -SkipArchive
  • Si è poi verificato il piano di spostamento con -Preview
  • I candidati in archive-targets.csv erano coerenti con le attese
  • Il valore Destination in archive-result.csv corrispondeva alle attese
  • Nella cartella di output è rimasta una traccia di audit con timestamp
  • Si è verificato che error.txt venga prodotto in caso di errore
  • Si sono verificati i permessi dell’utente di esecuzione dell’attività
  • Si sono verificate la execution policy, la firma e le regole aziendali interne
  • Si opera prima con lo spostamento, non con l’eliminazione immediata
  • È stata stabilita la destinazione di ripristino in caso di recupero

15. Conclusione

L’uso avanzato di PowerShell non significa dover ricorrere a una sintassi complessa. Ciò che dà davvero risultati nel lavoro quotidiano è aver costruito uno schema come questo:

  • Separare la configurazione in JSON
  • Costruire prima l’elaborazione di sola lettura
  • Conservare le tracce di audit in CSV e JSON
  • Suddividere le operazioni di modifica in funzioni
  • Predisporre una prova equivalente a -WhatIf
  • Rendere tutto tracciabile tramite il transcript ed error.txt
  • Verificare con l’esecuzione manuale prima di passare a quella pianificata

Lo script presentato in questo articolo prende come esempio l’analisi dei log e l’archiviazione, ma l’approccio si applica anche ad altri ambiti di lavoro.

  • Organizzazione dei file
  • Generazione di report
  • Aggregazione di dati CSV
  • Sostituzione di elaborazioni batch
  • Inventario di asset obsoleti
  • Verifiche operative giornaliere e mensili

PowerShell è comodo anche come singolo comando, ma per un uso professionale è più sicuro rispettare il seguente ordine:

Osservare → Registrare → Simulare → Eseguire → Conservare le tracce

Adottando questa forma, PowerShell smette di essere un semplice strumento per abbreviare il lavoro e diventa una piccola applicazione gestionale in grado di stabilizzare l’operatività.

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

Quando si automatizza l'analisi dei log con PowerShell, da dove conviene iniziare?
Non bisogna scrivere subito operazioni di modifica come lo spostamento o l'eliminazione: prima si costruisce solo la parte di lettura. Si completa per prima cosa la ricerca dei log target con Get-ChildItem e l'estrazione delle righe di errore con Select-String, esportandole in CSV. Anche quando si prova sulla cartella di produzione, conviene fermarsi a questa fase di sola lettura e verificare i risultati. In seguito, è più sicuro procedere gradualmente elencando i target da archiviare e trasformando lo spostamento in una funzione dedicata.
Come si integra -WhatIf in uno script PowerShell?
Aggiungendo SupportsShouldProcess all'attributo CmdletBinding di una funzione, quella funzione può usare -WhatIf e -Confirm. Il punto chiave è collocare $PSCmdlet.ShouldProcess() subito prima dell'operazione che modifica effettivamente qualcosa (ad esempio Move-Item), in modo che la decisione se procedere venga presa proprio a ridosso della modifica. Nello script di questo articolo, lo switch -Preview viene passato come -WhatIf:$Preview, così è possibile verificare solo il piano previsto senza spostare realmente nulla.
Perché uno script PowerShell funziona se eseguito manualmente ma fallisce nell'Utilità di pianificazione?
La causa tipica è che l'utente con cui viene eseguita l'attività pianificata è diverso da quello con cui si è eseguito lo script manualmente. Bisogna verificare che l'utente di esecuzione abbia i permessi di lettura sulla cartella dei log e di scrittura sulla destinazione dell'archivio, che la cartella di lavoro (WorkingDirectory) sia corretta, che il percorso di pwsh.exe sia raggiungibile e che tutto rispetti la execution policy e le regole di firma. È importante predisporre in anticipo error.txt, transcript.txt e la cronologia dell'attività, così da poter risalire alla causa in caso di fallimento.
È opportuno automatizzare anche l'eliminazione dei log obsoleti?
Automatizzare l'eliminazione fin dall'inizio non è consigliabile. Anche lo script presentato in questo articolo si ferma allo spostamento, un'operazione più sicura rispetto alla cancellazione. Conviene prima gestire l'operazione come spostamento per un certo periodo, verificare che non emergano problemi osservando le tracce di audit come archive-targets.csv e archive-result.csv, e solo a quel punto valutare l'automazione dell'eliminazione. È importante anche stabilire in anticipo dove ripristinare i file in caso sia necessario un recupero.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog