PowerShell avancé — automatiser en toute sécurité l'investigation, l'archivage et le reporting des journaux

· · PowerShell, Windows, Automatisation, Investigation des journaux, Amélioration opérationnelle, Réutilisation des actifs existants

1. Ce qu’il faut retenir avant de commencer

La dernière fois, nous avons passé en revue les commandes PowerShell de base, le pipeline, l’export CSV, le JSON, les scripts .ps1 et la vérification de sécurité avec -WhatIf. Dans la suite de cet article, nous allons construire un script un peu plus conséquent, facile à utiliser dans un contexte professionnel. Le sujet est le type de tâche opérationnelle suivant :

  1. Investiguer les journaux
  2. Regrouper les lignes d’erreur dans un CSV
  3. Lister les anciens journaux comme candidats à l’archivage
  4. Déplacer les anciens journaux si nécessaire
  5. Conserver le résultat de l’exécution comme preuve

Ce qui compte dans l’usage avancé de PowerShell, ce n’est pas de rallonger les commandes, mais de mettre en place le flux suivant :

  1. Séparer la configuration
  2. Construire le traitement en lecture seule
  3. Conserver la sortie
  4. Séparer le traitement de modification dans des fonctions
  5. Faire une répétition avec -WhatIf
  6. Envisager l’exécution automatique en dernier

L’automatisation ne consiste pas à « supprimer la vérification humaine ». Elle consiste à fixer l’endroit où cette vérification doit avoir lieu, de sorte que la même preuve d’exécution soit conservée à chaque fois.

Par ailleurs, le code présenté dans cet article est publié sur GitHub sous la forme d’un ensemble d’exemples directement exécutables (le script final, le fichier de configuration, un script de création d’un environnement de journaux factices, ainsi que des tests Pester qui vérifient l’investigation, la répétition et le déplacement).

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

2. Ce que nous allons construire

Nous allons construire cette fois un script nommé Invoke-LogMaintenance.ps1.

Voici ses principales fonctionnalités.

Fonctionnalité Description
Recherche des journaux Recherche les fichiers .log sous le dossier indiqué
Fenêtre temporelle N’examine que les journaux modifiés au cours des N derniers jours
Extraction des erreurs Extrait les lignes contenant ERROR, WARN, FATAL, etc.
Export CSV Enregistre les résultats de recherche dans log-hits.csv
Liste des anciens journaux Enregistre dans archive-targets.csv les journaux plus anciens que N jours
Archivage Déplace les anciens journaux vers un autre dossier
Exécution en aperçu Avec -Preview, affiche uniquement le plan sans déplacer
Journal d’exécution Enregistre un transcript, un résumé JSON et les CSV de résultats

Notez que nous ne supprimons rien. Dans ce premier volet avancé, nous nous limitons au « déplacement », plus sûr que la suppression.

3. Organisation des dossiers

Prenons, à titre d’exemple, l’organisation suivante.

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

Séparer le corps du script du fichier de configuration facilite le remplacement selon l’environnement. Vous pouvez ainsi ne faire varier que le chemin, par exemple C:\Test\Logs en environnement de développement et D:\App\Logs en production.

4. Créer le fichier de configuration

Commençons par créer log-maintenance.json.

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

Voici la signification de chaque champ.

Champ Signification
LogPath Le dossier de journaux à examiner
OutputPath La destination des rapports
Days Le nombre de jours récents de journaux à examiner
Patterns Les chaînes/motifs à rechercher
ArchiveDays À partir de combien de jours d’ancienneté un journal devient une cible d’archivage
ArchivePath Le dossier de destination de l’archivage

En utilisant le JSON, vous pouvez modifier les conditions sans toucher au corps du script.

Dans PowerShell, ConvertFrom-Json permet de manipuler du JSON sous forme d’objets. Inversement, pour conserver un résultat de traitement en JSON, on utilise ConvertTo-Json. ConvertTo-Json est la cmdlet qui convertit un objet en chaîne JSON ; lorsqu’on manipule une hiérarchie profonde, préciser -Depth devient important.

5. Commencer par construire uniquement la lecture

Sans écrire tout de suite le traitement de déplacement, contentons-nous d’abord de rechercher les journaux et de les exporter en 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

Recherchons ensuite le contenu des journaux.

$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

Jusqu’ici, tout est en lecture seule. Même en testant sur un dossier de production, arrêtez-vous d’abord à ce stade.

6. Dresser la liste des anciens journaux

Dressons maintenant la liste des cibles d’archivage.

$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

À ce stade non plus, on ne déplace encore rien. Consultez archive-targets.csv pour vérifier que le nombre de cibles n’est pas excessif et que le dossier n’est pas erroné.

7. Séparer le traitement de modification dans une fonction

Un traitement de modification comme le déplacement doit être séparé du traitement en lecture seule.

Dans PowerShell, ajouter SupportsShouldProcess à une fonction lui permet de gérer -WhatIf et -Confirm. -WhatIf affiche « ce qui serait modifié » sans exécuter réellement l’action, tandis que -Confirm est le mécanisme qui demande une confirmation avant l’exécution. Vous trouverez plus de détails dans about_Functions_CmdletBindingAttribute sur 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     = ""
      }
    }
  }
}

Le point clé est que $PSCmdlet.ShouldProcess() est placé juste avant Move-Item. La décision de modifier ou non se prend non pas à l’extérieur de la fonction, mais juste avant la modification elle-même.

8. Le script final

Voici la version finale qui rassemble tout ce qui précède. Le nom du fichier est 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. Exemples d’exécution

Commençons par n’effectuer que l’investigation des journaux, sans exécuter l’archivage.

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

Vérifions ensuite le plan d’archivage.

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

Avec -Preview, le traitement de déplacement des anciens journaux est traité comme un -WhatIf.

Les trois fichiers à vérifier à ce stade sont les suivants :

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

Si tout est correct, exécutez sans -Preview.

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

Pour voir plus de détails, ajoutez -Verbose.

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

10. Les fichiers générés en sortie

Lors de l’exécution, un dossier horodaté tel que C:\App\Reports\20260602-030000 est créé sous OutputPath.

Il contient les fichiers suivants.

Fichier Contenu
log-hits.csv Les lignes détectées comme erreurs ou avertissements
archive-targets.csv Les anciens journaux devenus cibles d’archivage
archive-result.csv Le résultat du déplacement, ou le résultat en mode Preview
summary.json Un résumé des nombres et des conditions d’exécution
transcript.txt L’enregistrement de la session PowerShell
error.txt Les détails en cas d’erreur

Start-Transcript est la cmdlet qui enregistre dans un fichier texte les commandes et la sortie console d’une session PowerShell. Dans un script opérationnel, cela facilite grandement la vérification a posteriori de « quand, dans quelles conditions, et avec quel résultat » le script s’est exécuté.

11. Exécuter périodiquement avec le Planificateur de tâches

Si l’exécution manuelle ne pose pas de problème, vous pouvez passer à une exécution périodique via le Planificateur de tâches.

Voici, pour commencer, un exemple d’exécution quotidienne à 3 heures du matin.

$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 crée l’objet représentant la commande que la tâche exécutera, et New-ScheduledTaskTrigger définit les conditions de déclenchement : quotidien, hebdomadaire, à l’ouverture de session, etc. Enfin, Register-ScheduledTask enregistre la tâche sur l’ordinateur local.

En exploitation en production, vérifiez également les points suivants.

  • L’utilisateur d’exécution dispose des droits de lecture sur le dossier de journaux
  • Il dispose des droits d’écriture sur la destination d’archivage
  • Le chemin de pwsh.exe est bien résolu
  • Le script est conforme à la politique d’exécution et aux règles de signature
  • L’exécution manuelle et l’exécution via la tâche produisent le même résultat
  • En cas d’échec, error.txt et l’historique de la tâche peuvent être consultés

12. Difficultés fréquentes

Symptôme Cause Solution
Les journaux ne sont pas trouvés LogPath est incorrect Vérifier avec Test-Path et Get-ChildItem
Le CSV est vide Aucun journal ne correspond à la période cible Élargir Days et vérifier
Le texte est mal encodé L’encodage des journaux diffère de ce qui était prévu Vérifier les encodages d’entrée et de sortie
Ne fonctionne pas en tant que tâche L’utilisateur d’exécution ou le dossier de travail diffère Vérifier WorkingDirectory et les droits
Trop de cibles d’archivage ArchiveDays est trop court Consulter archive-targets.csv et ajuster
La destination diffère de ce qui était prévu La règle de conservation du chemin relatif n’est pas comprise Vérifier Destination avec -Preview
Échoue uniquement en production Différences de droits, de politique ou de fichiers verrouillés Vérifier error.txt et transcript.txt

En particulier, lorsqu’on exécute le script via le Planificateur de tâches, « l’utilisateur avec lequel on l’a exécuté soi-même » et « l’utilisateur d’exécution de la tâche » peuvent être différents. Si le script fonctionne manuellement mais échoue en tant que tâche, suspectez d’abord les droits et le dossier de travail.

13. Comment aborder les personnalisations

Ce script est conçu pour être adapté progressivement au terrain plutôt qu’utilisé tel quel. Voici quelques exemples courants de personnalisation.

Ce que l’on veut faire Ce qu’il faut modifier
Inclure aussi les .txt Modifier Get-ChildItem -Filter *.log
Voir aussi quelques lignes autour de chaque ERROR Récupérer les lignes voisines avec Get-Content à partir des résultats de Select-String
Compresser avant d’archiver Ajouter Compress-Archive avant Move-OldLogFile
Envoyer une notification par e-mail Ajouter un traitement de notification basé sur summary.json
Séparer la configuration par application Préparer plusieurs fichiers JSON et diviser les tâches
Automatiser jusqu’à la suppression D’abord vérifier pendant une certaine période avec le déplacement, puis envisager

Cependant, il est plus sûr de ne pas tout intégrer dès le départ. Pour un script opérationnel, il est plus important de pouvoir retracer ce qui s’est passé en cas d’échec que d’avoir de nombreuses fonctionnalités.

14. Liste de contrôle opérationnelle sur le terrain

Avant de planifier l’exécution périodique d’un script PowerShell, vérifiez les points suivants.

  • D’abord exécuté uniquement le traitement en lecture seule avec -SkipArchive
  • Ensuite vérifié le plan de déplacement avec -Preview
  • Les cibles de archive-targets.csv étaient pertinentes
  • Le champ Destination de archive-result.csv correspondait aux attentes
  • Une preuve horodatée a bien été conservée dans le dossier de sortie
  • Vérifié que error.txt est bien généré en cas d’erreur
  • Vérifié les droits de l’utilisateur d’exécution de la tâche
  • Vérifié la politique d’exécution, la signature et les règles internes
  • Exploiter d’abord avec le déplacement, pas la suppression immédiate
  • Défini à l’avance la destination de restauration en cas de récupération

15. Conclusion

Même quand on parle d’usage avancé de PowerShell, il n’est pas nécessaire d’utiliser une syntaxe compliquée en abondance. Ce qui porte vraiment ses fruits dans le travail quotidien, c’est de mettre en place ce type de schéma :

  • Séparer la configuration dans un JSON
  • Construire d’abord le traitement en lecture seule
  • Conserver des preuves en CSV et en JSON
  • Séparer le traitement de modification dans des fonctions
  • Prévoir une répétition équivalente à -WhatIf
  • Permettre le suivi grâce au transcript et à error.txt
  • Vérifier par une exécution manuelle avant de planifier l’exécution périodique

Le script de cet article prend l’investigation et l’archivage des journaux comme sujet, mais cette approche s’applique aussi à d’autres tâches.

  • Organisation de fichiers
  • Génération de rapports
  • Agrégation de CSV
  • Remplacement de traitements par lots
  • Inventaire des anciens actifs
  • Vérifications opérationnelles quotidiennes et mensuelles

PowerShell est pratique même sous forme de commandes d’une ligne, mais pour un usage professionnel, il est plus sûr de respecter l’ordre suivant.

Observer → Enregistrer → Répéter → Exécuter → Conserver les preuves

En adoptant cette structure, PowerShell cesse d’être un simple outil pour raccourcir les tâches et devient une petite application métier capable de stabiliser l’exploitation.

Liens de référence

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Quand on automatise l'investigation de journaux avec PowerShell, par quoi faut-il commencer ?
Ne commencez pas directement par écrire du code de modification (déplacement, suppression, etc.) : construisez d'abord uniquement le traitement en lecture seule. Terminez d'abord la partie qui recherche les journaux concernés avec Get-ChildItem, extrait les lignes d'erreur avec Select-String et les exporte en CSV. Même lorsque vous testez sur un dossier de production, arrêtez-vous à ce stade de lecture pour vérifier le résultat. Il est plus sûr de progresser ensuite par étapes : dresser la liste des cibles d'archivage, puis transformer le traitement de déplacement en fonction.
Comment intègre-t-on -WhatIf dans un script PowerShell ?
En ajoutant SupportsShouldProcess à l'attribut CmdletBinding d'une fonction, celle-ci devient capable de gérer -WhatIf et -Confirm. Le point clé est de placer $PSCmdlet.ShouldProcess() juste avant le traitement qui modifie réellement quelque chose (par exemple Move-Item), afin que la décision de modifier ou non se prenne au tout dernier moment, juste avant la modification. Dans le script de cet article, le switch -Preview est transmis sous la forme -WhatIf:$Preview, ce qui permet de vérifier uniquement le plan d'action sans déplacer réellement les fichiers.
Pourquoi un script PowerShell fonctionne-t-il manuellement mais échoue-t-il dans le Planificateur de tâches ?
La cause typique est que l'utilisateur d'exécution de la tâche diffère de celui utilisé lors de l'exécution manuelle. Vérifiez que l'utilisateur d'exécution dispose des droits de lecture sur le dossier de journaux et des droits d'écriture sur la destination d'archivage, que le dossier de travail (WorkingDirectory) est correct, que le chemin de pwsh.exe est bien résolu, et que la politique d'exécution ainsi que les règles de signature sont respectées. Il est important de pouvoir retracer les échecs grâce à error.txt, transcript.txt et à l'historique de la tâche.
Peut-on aller jusqu'à automatiser la suppression des anciens journaux ?
Il n'est pas recommandé d'automatiser la suppression dès le départ. Le script de cet article lui-même s'arrête au « déplacement », qui est plus sûr que la suppression. Faites d'abord fonctionner l'opération de déplacement pendant une certaine période, examinez les preuves d'exécution telles que archive-targets.csv et archive-result.csv, et ce n'est qu'après avoir confirmé l'absence de problème que vous pourrez envisager d'automatiser la suppression. Il est également important de définir à l'avance où restaurer les fichiers en cas de besoin de récupération.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog