jeudi 3 juillet 2014

Lire un fichier ouvert

Cet exemple permet d'ouvrir un fichier de logs en cours d'utilisation par une autre application, et d'en extraire des informations. Ceci peut-être pratique par exemple pour suivre l'évolution d'un traitement particulièrement long qui génère des fichiers de log volumineux.

Pour ouvrir un fichier log en cours de création, je vais utiliser un objet StreamReader. Cet objet me permettra non seulement de lire le contenu existant, mais également de lire les lignes nouvellement ajoutées sans qu'il soit nécessaire de relire le fichier depuis le début. Pour m'assurer de ne pas verrouiller le fichier lors de sa lecture, je vais utiliser [System.IO.File]::Open() pour contrôler la façon dont je souhaite accéder au fichier.

Exemple


 
#Assembly.
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

#Déclarations des variables.
$global:totale = 0

#Emplacement du fichier de log à ouvrir.
$fichier = "C:\log\tuto.txt"

#J'ouvre mon fichier de log en lecture seul de façon à ce que celui-ci
#reste disponible en lecture/écriture pour une autre application.
#[System.IO.File]::OPEN -> Nom et emplacement du fichier à ouvrir,
#             -> Opération (Open = ouverture du fichier),
#             -> Mode (Read = lecture seul),
#             -> Partage (ReadWrite = autorise l'accès en lecture/écriture
#                pour les autres applications).
$hfichier = [System.IO.File]::Open($fichier,"Open","Read","ReadWrite")

#Puis, je récupère un objet stream pour lire mon fichier.
$global:stream = new-object System.IO.StreamReader($hfichier)

#Le timer suivant va me permettre de lire régulièrement le fichier de log.
#Si de nouvelles lignes sont inscrites, le timer permettra de les traiter au
#fur et à mesure qu'elles sont ajoutées.
$global:timer1 = new-object System.Windows.forms.timer
$global:timer1.Interval = 2000 #millisecondes.
$global:timer1.Add_Tick({
     #Pour accélérer la lecture, je vais lire le fichier par lot
     #de 1000 lignes à chaque exécution du timer.
     for($i = 0;$i -le 1000;$i++)
      {
        #Ici, je lis une ligne du fichier de log.
        $ligne = $global:stream.ReadLine()
        #Si je suis à la fin du fichier, $ligne sera égale à null.
        if ($ligne -ne $null)
          {
            #J'ai récupéré une nouvelle ligne !
            #Pour récupérer mes informations plus vite j’accélère le timer.
            $global:timer1.Interval = 1
            #Puis je vérifie si ma ligne contient des données utiles.
            $lmaj = $ligne.ToUpper()
            $lspl = $lmaj.Split(" ")
            if ($lmaj.contains('UNIT'))
              {
                #Si ma ligne contient le texte "UNIT", je la traite.
                $sun = $lspl[6]
                Try{ $iun = $sun -as [int] } catch{ $iun = 0 }
                $global:totale = $global:totale + $iun
                #Puis j'affiche la valeur dans ma fenêtre.
                $global:label1.text = $global:totale
                $global:label1.Refresh()
              }
          }
        else
          {
            #Ici, je n'ai pas de nouvelles lignes à analyser.
            #Je reprogramme l’exécution du timer à 2 secondes
            #pour éviter de charger inutilement la machine.
            $global:timer1.Interval = 2000
            #Il n'y a plus rien à lire dans le fichier (arrêt de la boucle).
            Break
          }
      }
                   })
#Lancement du timer.
$global:timer1.Start()

#Ouvre une fenêtre.
$form = New-Object Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(160,90)
$form.text = "Streamlog"
$form.TopMost = 0
$form.ControlBox = 0
$form.FormBorderStyle = "FixedSingle"
$form.KeyPreview = $True
$form.Add_KeyDown({if ($_.KeyCode -eq "Escape") {$form.Close()}})

#Création d'un label (Label).
$global:label1 = New-Object Windows.Forms.Label
$global:label1.Location = New-Object Drawing.Point 15,14
$global:label1.Size = New-Object System.Drawing.Size(40,16)
$global:label1.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
$global:label1.text = "------"

#Création d'un label (Label).
$label2 = New-Object Windows.Forms.Label
$label2.Location = New-Object Drawing.Point 60,15
$label2.Size = New-Object System.Drawing.Size(35,16)
$label2.text = "Unité"

#Création d'un bouton (Button).
$bouton1 = New-Object Windows.Forms.Button
$bouton1.Location = New-Object Drawing.Point 100,12
$bouton1.Size = New-Object System.Drawing.Size(45,20)
$bouton1.text = "Reset"
$bouton1.add_click({
                     $global:totale = 0
                     #Affiche la valeur.
                     $global:label1.text = $global:totale
                     $global:label1.refresh() #Actualise le contrôle.
                  })
                               
#Création d'un bouton (Button).
$bouton2 = New-Object Windows.Forms.Button
$bouton2.Location = New-Object Drawing.Point 100,32
$bouton2.Size = New-Object System.Drawing.Size(45,20)
$bouton2.text = "Close"
$bouton2.add_click({ $form.Close() })
                               
#Attache les contrôles à la fenêtre.
$form.controls.add($global:label1)
$form.controls.add($label2)
$form.controls.add($bouton1)
$form.controls.add($bouton2)

#Affiche le tout.
$form.ShowDialog()

#J'arrête le timer.
$global:timer1.Stop()

#Puis je libère mon flux.
$global:stream.Close()

#Fin.

Pour tester ce programme, créez un fichier texte contenant les lignes suivantes :
date heure truc bidule a généré 1400 Unité.
date heure blabla blabla blabla blabla
date heure blablablabla blabla blabla blabla
date heure truc bidule a généré 200 Unité.
date heure truc bidule a généré 1200 Unité.

Enregistrez une premier fois le texte, et lancez le programme. Il fera la somme des unitées affichées dans le fichier. Ajoutez à nouveau les lignes ci-dessus au fichier texte, et enregistrez le de nouveau. Le programme (qui tourne toujours) prend en compte les nouvelles lignes.