Affichage des articles dont le libellé est Fichier. Afficher tous les articles
Affichage des articles dont le libellé est Fichier. Afficher tous les articles

lundi 10 août 2020

Alternate Data Stream

Introduction.

Alternate Data Stream (ADS) est un flux de données additionnels lié à un fichier. Habituellement nous utilisons le flux principale pour lire ou écrire un fichier. Avec l'ADS nous pouvons créer des flux de données alternatifs distincts au sein d'un même fichier.

Prenons par exemple, le fichier "test.txt". Ce fichier peut contenir plusieurs textes distincts :
[Test.txt] "Je suis dans le flux principale par défaut."
[Test.txt:Flux1] "Je suis dans le flux 'Flux1'."
[Test.txt:Flux2] "Je suis dans le flux 'Flux2'."

Un peu de pratique pour comprendre tout ça ne fera pas de mal :

  • Lancez la commande "Notepad C:\test.txt", et entrez le texte "Je suis dans le flux principale par défaut.". Si vous ouvrez le fichier depuis l'explorateur vous verrez le texte que vous avez entré.
  • Lancez la commande "Notepad C:\test.txt:Flux1", et entrez le texte "Je suis dans le flux 'Flux1'". Si vous ouvrez le fichier depuis l'explorateur vous ne verrez pas le texte que vous avez entré, mais celui du du flux par défaut. Pour voir le texte du 'Flux1' vous devrez taper de nouveau la commande "Notepad C:\test.txt:Flux1".
  • Lancez la commande "Notepad C:\test.txt:Flux2", et entrez le texte "Je suis dans le flux 'Flux2'". La encore si vous ouvrez le fichier depuis l'explorateur vous ne verrez pas le texte que vous avez entré, mais celui du flux par défaut. Les textes que vous avez tapé dans le Flux1 et dans le Flux2 sont donc "cachés".

Caché ? Peut-être pas tant que ça :

  • Sous DOS, lancez la commande "Dir". Seul le fichier "test.txt" apparait.
  • Sous DOS, lancez la commande "Dir /r". Les flux "teste.txt:Flux1" et "teste.txt:Flux2" liés au fichier "test.txt" apparaissent.

A noter :

  • Les flux alternatifs ne sont possible que sur des partitions formatées en NTFS.
  • La taille des flux ne sont pas comptabilisés dans la taille totale du fichier par défaut. Un fichier de 3Ko dans l'explorateur peut très bien contenir un flux de 10Mo.

Écrire dans un flux alternatif avec Powershell 2.0.


 
#Initialisation.
$fichier = "C:\test.txt"
$flux    = "Flux1"
$cmd     = "cmd.exe"
$Txt     = "Je suis dans le flux 'Flux1'"

#Writing stream.
$arg1 = "/C ""echo $($txt) > $($fichier):$($flux)"""
start-process -filepath $cmd -ArgumentList $arg1

Lire un flux alternatif avec Powershell 2.0.


 
#Initialisation.
$fichier = "C:\test.txt"
$flux    = "Flux1"
$cmd     = "cmd.exe"

#Reading stream.
$arg2 = "/C ""more < $($fichier):$($flux)"""
$info = New-Object System.Diagnostics.ProcessStartInfo
$info.FileName = $cmd
$info.RedirectStandardOutput = $true
$info.UseShellExecute = $false
$info.Arguments = $arg2
$prcs = New-Object System.Diagnostics.Process
$prcs.StartInfo = $info
$null = $prcs.Start()
Sleep -s 1
$stdout = $prcs.StandardOutput.ReadToEnd()
Write-Host $stdout

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.

vendredi 7 décembre 2012

Utiliser les fichiers textes.

Nous allons voir comment lire et écrire un fichier sur le disque. Dans l'exemple suivant, je vais créer un fichier texte, écrire quelques choses dedans, et lire son contenu.

 
#Chemin du fichier.
$fichier1 = "C:\Jumbor12.txt"

#Verifie si le fichier existe.
$test1 = Test-Path $fichier1

#Si le fichier n'existe pas, je crée un nouveau fichier.
if ($test1 -ne "True") { New-Item -ItemType file -Path $fichier1 -Force }

#Ajoute une ligne dans le fichier.
Add-Content -Path $fichier1 -Value "Nouvelle ligne !"

#Lit le contenu du fichier, et l'affiche dans la console.
get-content $fichier1 | foreach { write-host $_ }

#Fin.

Cette dernière ligne mérite quelques explications. Je vais la décomposer un peu. "get-content $fichier1" lit le fichier. Si on tape seulement cette partie, la commande va afficher le contenu du fichier dans la console. Le "|" permet de rediriger le résultat vers une autre commande. Ici j'envoie le résultat vers une boucle foreach. La redirection "|" n'envoie pas tout le résultat d'un coup vers la commande suivante, il procède ligne par ligne. Chaque ligne renvoyée est alors contenue dans la variable "$_". La boucle foreach sert à traiter chaque ligne du fichier.

Nous allons voir maintenant un NotePad maison. Dans cet exemple, j'ouvre une fenêtre qui va afficher le contenu d'un fichier dans une TextBox multiligne. A la fermeture de la fenêtre, je transfère le contenu de la TextBox dans le fichier.

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

#Chemin du fichier.
$fichier1 = "C:\Jumbor12.txt"

#Ouvre une fenêtre.
$form = New-Object Windows.Forms.Form
$form.text = "Notepad maison."            
$form.Size = New-Object System.Drawing.Size(250,250)

#Création d'une zone texte (TextBox).
$texte1 = New-Object Windows.Forms.TextBox
$texte1.Location = New-Object Drawing.Point 0,0
$texte1.Size = New-Object Drawing.Point 242,216
$texte1.Multiline = $True
$texte1.WordWrap = $False
$texte1.ReadOnly = $False
$texte1.backcolor = "white"
$texte1.ScrollBars = "Both"

#Lecture du fichier
#Verifie si le fichier existe.
$test1 = Test-Path $fichier1

#Si le fichier existe, je lis le fichier, sinon le contenu de la TextBox est vide.
if ($test1 -eq "True")
  {
    $t = ""
    get-content $fichier1 | foreach { $t = $t + $_ + "`r`n" }
    $texte1.Text = $t
  }
else { $texte1.Text = "" }

#Attache le contrôle à la fenêtre.
$form.controls.add($texte1)

#Affiche le tout.
$form.ShowDialog()

#Sauvegarder TextBox.
#Verifier si le fichier existe.
$test1 = Test-Path $fichier1

#Si le fichier existe, je le supprime.
if ($test1 -eq "True") { Remove-Item $fichier1 -ErrorAction SilentlyContinue }
New-Item -ItemType file -Path $fichier1 -Force

#Création d'un nouveau fichier avec écriture du contenu de la TextBox.
foreach ($l in $texte1.Text) { Add-Content -Path $fichier1 -Value $l }

#Fin.

Voici une petit exemple supplémentaire pour les besoins tout simple qui nécessite d'ouvrir un fichier, de faire un petit traitement et de la mettre à jour le fichier sans se compliquer la vie.

 
$fichier1 = "C:\Jumbor12.txt"
$texte1 = Get-Content $fichier1 | foreach {
                                            #Affiche la ligne courante.
                                            Write-Host $_
                                            #Ajoute un commentaire.
                                            $out = $_ + "OK"
                                            #Retourne la ligne modifiée.
                                            $out
                                          }
Set-Content -Path $fichier1 -Value $texte1