mercredi 23 octobre 2019

Annonce - Copy Code

J'ai trouvé un petit script Html/Java pour copier le code affiché dans le presse papier. J'ai testé ce script sur FireFox 60 et IE 11 sans problème. Comme vous pouvez déjà le voir, un bouton est apparu sur la plupart des scripts. 

Exemple :
Write-Host "Hello World !"

A noter : Suivant le niveau de sécurité du navigateur un popup peut apparaitre pour demander l'autorisation de copier le code dans le presse papier. Bien entendu si le Java Script n'est pas activé il ne se passera rien.

jeudi 10 octobre 2019

Canaux Nommes & Form


Dans cet exemple je vais m’intéresser aux problèmes d'attentes rencontré lors de l'utilisation des canaux nommées dans le cadre de communication entre deux processus affichant des formulaires.

Si j’intègre tel quel mon pipe pour communiquer entre 2 Thread contenant chacun leur propre fenêtre indépendante, les fenêtres vont se figer sans arrêt. Ceci tiens à plusieurs problème.

WriteLine() fige la fenêtre :
Ce premier cas est simple à résoudre. Pour éviter que la fenêtre se fige nous allons ajouter un buffer.

$pipeDir = [System.IO.Pipes.PipeDirection]::InOut
$pipeTra = [System.IO.Pipes.PipeTransmissionMode]::Message
$pipeOpt = [System.IO.Pipes.PipeOptions]::Asynchronous
$pipeBuf = 4096   #Buffer Entrée+Sortie.
 $pipe = New-Object System.IO.Pipes.NamedPipeServerStream("\\.\pipe\J12Pipe1",$pipeDir,1,$pipeTra,$pipeOpt,$pipeBuf,$pipeBuf)

Dans ce cas WriteLine() va écrire sans attendre dans le buffer et passer à la ligne suivante. Les données seront ensuite récupérées par ReadLine() dans le seconde processus.

ReadLine() fige la fenêtre :
Le meilleur moyen de ne pas se retrouver avec une fenêtre figé est de faire en sorte de ne pas appeler la commande ReadLine() lorsqu'il n'y a plus de donnée à lire. Il existe bien des propriétés pour connaitre la taille des données en attente, mais elle ne sont pas implémenté dans le cadre des canaux nommées. Il faut donc ruser pour dire au second processus qu'il n'y a plus de donnée à lire.

Les sémaphores au secours des canaux nommées :
Il y a certainement plusieurs moyens pour dire au second processus de ne pas utiliser ReadLine() lorsqu'il n'y a plus de données à lire. Dans cet exemple j'ai choisie d'utiliser les sémaphores pour indiquer aux deux processus le nombre de ligne en attente dans le pipe.

New-Object System.Threading.Semaphore(0, 10000, "Global\J12Sema1")

Je vais déclaré un sémaphore pouvant stocker une valeur de 0 à 10000. Cette valeur correspondra dans mon script au nombre de ligne que j'aurai écrit dans le buffer du pipe. La méthode Release() à la caractéristique bien utile de me renvoyer le nombre de Release positionner dans le sémaphore. Release() va incrémenter ce nombre, tandis que la méthode Waitone() va le diminuer.

Waitone() fige la fenêtre :
La méthode Waitone() a malheureusement aussi la fâcheuse habitude de figer la fenêtre. Dans le cadre d'une utilisation normal du sémaphore c'est bien. Mais comme je détourne son utilisation, ça ne m'arrange pas. Je vais donc utiliser le timeout de la méthode Waitone() pour qu'elle réponde rapidement.

Pour tester ces scriptes, ouvrez deux fois le programme ISE. Copier le premier script dans la premier fenêtre ISE. Et le second script dans la seconde fenêtre ISE. Puis exécutez les deux scripts simultanément. A noter : Les fenêtres ne sont pas code dans cet exemple. La fonction While est utiliser pour simuler ici un Timer.

Premier script :
 
#Création d'un sémaphore.
#Je vais utiliser ici le sémaphore pour envoyer le nombre de ligne à lire.
$sema = New-Object System.Threading.Semaphore(0, 10000, "Global\J12Sema1")

#Création du canal nommé coté client.
$pipe = new-object System.IO.Pipes.NamedPipeClientStream("\\.\pipe\J12Pipe1")

#StreamWriter.
$sw = new-object System.IO.StreamWriter($pipe)

#Connexion avec le serveur.
$pipe.Connect()

#Initialisation.
$base = @("data1","data2","data3","data4")
$sw.AutoFlush = $true
$nb = 10000

While(1)  #Pour simuler un timer.
 {
   #Vérifier le nombre de ligne envoyé dans le pipe.
   try{
        #lit la valeur dans le sémaphore et l'incrémente.
        $nb = $sema.Release(1)     #Incrémente de 1.
       
        #Décrémente immédiatement le sémaphore pour corriger la valeur. 
        $null = $sema.Waitone(1)   #Le timout est de 1ms.
      }
   catch {
           #En cas d'erreur cela indique que le
           #nombre maximal de ligne est atteint.
           $nb = 10000
         }

   #Si tout les lignes sont lu, j’envoie les données.
   if ($nb -eq 0)
     {
       foreach ($ligne in $base)
        {
          #Ecrit dans le Pipe.
          $global:sw.WriteLine($ligne)
         
          #Ecrit dans la Console + pause d'une seconde.
          write-host "envoi de" $ligne; Sleep 1
         
          #Incrémente le nombre de ligne à lire dans le sémaphore.
          $null = $sema.Release(1)
        }
     }
 }

$pipe.Close()
$sema.Close()


Deuxième script :
 
#Création d'un sémaphore.
$sema = New-Object System.Threading.Semaphore(0, 10000, "Global\J12Sema1")

#Création du canal nommé coté serveur.
$pipeDir = [System.IO.Pipes.PipeDirection]::InOut
$pipe = new-object System.IO.Pipes.NamedPipeServerStream("\\.\pipe\J12Pipe1",$pipeDir,1,[System.IO.Pipes.PipeTransmissionMode]::Byte,[System.IO.Pipes.PipeOptions]::Asynchronous,51200,51200)

#StreamReader.
$sr = new-object System.IO.StreamReader($pipe)

#Connexion avec le serveur.
$pipe.WaitForConnection()

#Initialisation.
$nb = 10000

While(1)  #Pour simuler un timer.
 {
#Vérifier le nombre de ligne envoyé dans le pipe.
   try{
        #lit la valeur dans le sémaphore et l'incrémente.
        $nb = $sema.Release(1)     #Incrémente de 1.
       
        #Décrémente immédiatement le sémaphore pour corriger la valeur.
        $null=$sema.Waitone(1)   #Le timout est de 1ms.
       }
   catch
       {
         #En cas d'erreur cela indique que le
         #nombre maximal de ligne est atteint.
         $nb = 10000
       }
      
   #Si il y a des donnée à lire.
   if ($nb -gt 0)
     {
       while ($nb-- -ne 0)
        {
          #Lecture du Pipe.
          $text = $sr.ReadLine()
         
          #Ecrit dans la Console + pause d'une seconde.
          Write-host "Lecture" $text; Sleep 1
         
          #Décrémente le nombre de ligne à lire dans le sémaphore.
          $null = $sema.Waitone(1)
        }
     }
 }

$pipe.Close()
$sema.Close()

mercredi 25 septembre 2019

Text Overlay

Ce script permettant d'afficher un message en plein milieu de l'écran en mode "Overlay". La difficulté ici est de créer un texte avec un contour noir afin que le texte ne soit pas noyé dans l'image de fond. Cette méthode a l'avantage de pouvoir utiliser les fonts systèmes déjà présente sur le poste. D’ailleurs j'utilise dans cet exemple du Verdana.


 
[Reflection.Assembly]::LoadWithPartialName("System.Drawing")

#Fonctions Message.
function Message([string]$TEXT)
 {
   #Calculer la taille de la fenêtre.
   $font = New-Object System.Drawing.Font("Verdana", 21, [System.Drawing.FontStyle]::Bold, 2,0)
   $size = [System.Windows.Forms.TextRenderer]::MeasureText($TEXT, $font)
   $xm = $size.Width + [int]($TEXT.length/2)
   $ym = $size.Height - 1

   #Création de l'images vide.
   $Bitmap  = New-Object System.Drawing.Bitmap($xm,$ym) 
   $Graphic = [System.Drawing.Graphics]::FromImage($Bitmap)
   $Graphic.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::SingleBitPerPixelGridFit
   $Rect    = New-Object System.Drawing.Rectangle (0,0,$xm,$ym)

   #Pen (contour en noir) et Brush (remplissage en blanc).
   $Brush = New-Object Drawing.SolidBrush( [System.Drawing.Color]::FromArgb(255,250,250,250) )
   $Pen   = New-Object Drawing.Pen( [System.Drawing.Color]::FromArgb(255,30,30,30) )
   $Pen.width = 4   #Epaisseur du contour.

   #Format
   $Format               = [System.Drawing.StringFormat]::GenericDefault
   $Format.Alignment     = [System.Drawing.StringAlignment]::Center
   $Format.LineAlignment = [System.Drawing.StringAlignment]::Far

   #Créer un GraphicPath et ajouter le texte dedans.
   $cgPath = New-Object System.Drawing.Drawing2D.GraphicsPath(0)
   $cgPath.AddString($Text, "Verdana", [System.Drawing.FontStyle]::Bold, 21, $Rect, $Format )

   #Appliquer le graphicPath sur le bitmap.
   $Graphic.DrawPath($Pen,   $cgPath)   #Dessine le contour de la forme.
   $Graphic.FillPath($Brush, $cgPath)   #Remplit la forme.
   $Graphic.Dispose()

   #Timer pour fermeture automatique de la fenêtre.
   $timerM = New-Object System.Windows.forms.timer
   $timerM.Interval = (5000)   #Millisecondes.
   $timerM.Add_Tick({ $timerM.Stop(); $formM.Close() })
  
   #Ouvre une fenêtre.
   $formM = New-Object Windows.Forms.Form
   $formM.Size = New-Object System.Drawing.Size($xm,$ym)
   $formM.StartPosition = "CenterScreen"
   $formM.FormBorderStyle = "None"
   $formM.ShowInTaskbar = $false
   $formM.TopMost = $true
   $formM.text = "Message"
   $formM.BackColor = [System.Drawing.Color]::FromArgb(255,1,2,3)
   $formM.TransparencyKey = [System.Drawing.Color]::FromArgb(125,1,2,3)
  
   #Image.
   $pictureM = New-Object System.Windows.Forms.pictureBox
   $pictureM.Size = New-Object Drawing.Point $xm,$ym
   $pictureM.Location = New-Object Drawing.Point (0,0)
   $pictureM.SizeMode = "CenterImage"
   $pictureM.Image = $Bitmap
  
   #Attache les contrôles à la fenêtre.
   $formM.controls.add($pictureM)
  
   #Event.
   $formM.Add_Load({ $timerM.Start() })
  
   #Affiche le tout.
   $formM.ShowDialog()
 }

Message "Message Test"

mardi 17 septembre 2019

Scheduler

Voici un exemple original de script permettant de programmer l’exécution d'une commande dans une plage de 24h. Pour cela il suffit de lui indiquer une heure de début et de fin et le programme se charge du reste.


 
#Variables utilisateurs à modifier :
$debut = "21:00:00"
$fin   = "7:00:00"
$batch = "C:\test.bat"

#Initialisation.
$dstart = get-date([System.DateTime]$debut)
$dstop  = get-date([System.DateTime]$fin)
$cmd    = "cmd.exe"
$arg    = "/C ""$($batch)"""
cls

#Ajustement de la plage horaire.
$now = get-date
if ($dstart -ge $dstop) { $dstop = $dstop.AddDays(1) }
if ($dstart -le $now) { $dstop = $dstop.AddDays(1); $dstart = $dstart.AddDays(1); }

#Information de planification.
Write-Host "`r`n"
Write-Host "#-----------------------------------#"
Write-Host "# Planification #"
Write-Host "#-----------------------------------#"
Write-Host "# Lancement : $($dstart) #"
Write-Host "# Arrêt : $($dstop) #"
Write-Host "#-----------------------------------#"
Write-Host "`r`n"

#Attendre pour lancer la tâche.
While ($true)
 {
  $now = get-date
  if (($now -gt $dstart) -and ($now -lt $dstop)) { break }
  Sleep -s 1
 }

#Lancement de la tâche.
Write-Host "$(get-date) - Lancement du batch."
$id = start-process -FilePath $cmd -ArgumentList $arg -Passthru

#Attendre pour fermer la tâche.
While ($true)
 {
  $now = get-date
  if (($now -gt $dstop) -or ($id.HasExited)) { break }
  Sleep -s 1
 }

#Fermer la tâche.
if (!$id.HasExited) { Write-Host "$(get-date) - Kill du batch."; $id.Kill() }
else { Write-Host "$(get-date) - Fin du batch." }

jeudi 15 novembre 2018

50000 Hits !

Le site vient d'atteindre les 50000 hits. Cette dernière année fut très difficile pour mon petit blog. En juillet 2017 le site devient "powershell.sekki.fr". Cette manœuvre qui me semblait anodine était en réalité loin de l'être. Il m a fallu batailler ferme pour re-indexer et reconstruire le SEO du site. Cela m'a appris beaucoup de chose sur le fonctionnement du référencement de Google. J'ai également étendu la visibilité du site en l'enregistrant auprès de Symantec car sans cela le site n'était pas accessible depuis certaine société. La non plus cela n'a pas été une mince affaire. Mon ancien hébergeur publiait du contenu bloqué par Symantec sur mon nom de domain. J'ai dû me résoudre à changer d'hébergeur pour enfin me débarrasser des pages bloquées. Aujourd'hui tous ces problèmes semblent enfin réglés. Le mois d'octobre 2018 fait partie des 3 meilleurs mois depuis le lancement du site.



mercredi 24 octobre 2018

Grille de données

L'objet "DataGridView" est un contrôle à la fois très intéressant et beaucoup plus complexe que les autres contrôles que nous avons vu précédemment. J'ai donc réaliser un exemple avec un maximum de chose intéressante, tout en gardant un niveau que j’espère abordable pour un maximum de monde.

La façon simple et rapide de réaliser un DataGridView est d'utiliser la commande "Out-GridView". Par exemple : Get-ADComputer -Filter 'Name –Like "*"' | Out-GridView

C'est bien mais si je veux l’intégrer dans un formulaire ce n'est pas très beau, et le comportement n'est pas forcement adapté. Pour pouvoir intégrer correctement le contrôle dans un formulaire, j'utilise donc l'objet "DataGridView". Celui-ci me permet d'utiliser ses paramètres pour obtenir le résultat et la forme que je souhaite. Par exemple : $gridA1.ReadOnly permet d'autoriser (ou non) la modification des données.


 
#Fenêtre.
$formA = New-Object Windows.Forms.Form
$formA.Size = New-Object Drawing.Point 400,230
$formA.Text = "DataGridView"
$formA.BackColor = [System.Drawing.Color]::FromArgb(255,240,240,240)

#Taille de la fenêtre.
$largeurA = $formA.width
$hauteurA = $formA.height

#DataGrid - Options générales.
#SelectionMode -> CellSelect, FullRowSelect, FullColumnSelect,
#SelectionMode -> RowHeaderSelect, ColumnHeaderSelect.
#ScrollBars ----> None, Horizontal, Vertical, Both.
$gridA1 = New-Object System.Windows.Forms.DataGridView
$gridA1.Location = New-Object Drawing.Point -1,-1
$gridA1.Size = New-Object Drawing.Point ($largeurA-14),($hauteurA-72)
$gridA1.ScrollBars = "Vertical"  
$gridA1.ReadOnly = $false
$gridA1.AutoSize = $false
$gridA1.GridColor = "Black"
$gridA1.AllowUserToResizeRows = $true
$gridA1.BorderStyle = [System.Windows.Forms.BorderStyle]::None
$gridA1.BackgroundColor = [System.Drawing.Color]::FromArgb(255,240,240,240)
$gridA1.DefaultCellStyle.SelectionBackColor = [System.Drawing.Color]::FromArgb(255,94,191,212)
$gridA1.SelectionMode = "FullRowSelect"
$gridA1.MultiSelect = $false

#DataGrid - Style de l'entête.
#ColumnHeadersHeightSizeMode -> EnableResizing, DisableResizing, AutoSize.
#ColumnHeadersBorderStyle ----> Custom, Single, Raised, Sunken, None.
$gridA1.RowHeadersVisible    = $false
$gridA1.ColumnHeadersVisible = $true
$gridA1.ColumnHeadersHeightSizeMode = "DisableResizing"
$gridA1.EnableHeadersVisualStyles = $false
$gridA1.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255,44,40,109)
$gridA1.ColumnHeadersDefaultCellStyle.ForeColor = "White"
$gridA1.ColumnHeadersDefaultCellStyle.Alignment = "MiddleCenter"
$gridA1.ColumnHeadersBorderStyle = "Single"

#DataGrid - Ajouter les colonnes.
[void]$gridA1.Columns.Add((New-Object System.Windows.Forms.DataGridViewTextBoxColumn))
[void]$gridA1.Columns.Add((New-Object System.Windows.Forms.DataGridViewTextBoxColumn))
[void]$gridA1.Columns.Add((New-Object System.Windows.Forms.DataGridViewComboBoxColumn))

#DataGrid - Ajouter le texte des entêtes de colonnes.
$gridA1.Columns[0].Name = "Titre"
$gridA1.Columns[1].Name = "Editeur"
$gridA1.Columns[2].Name = "Nb Volume"

#DataGrid - Données pour la ComboBox.
$gridA1.Columns[2].DataSource = @("1","2","3","4","5","6","7","8","9","10","11","12")
$gridA1.Columns[2].FlatStyle  = [System.Windows.Forms.FlatStyle]::Flat

#DataGrid - Options des colonnes.
#SortMode -> NotSortable, Automatic, Programmatic.
foreach($column in $gridA1.Columns)
 {
   $column.SortMode     = "NotSortable"
   $column.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill
 }

#DataGrid - Alignement.
#Alignment -> NotSet, TopLeft, TopCenter, TopRight, MiddleLeft, MiddleCenter,
#Alignment -> MiddleRight, BottomLeft, BottomCenter, BottomRight.
$gridA1.Columns[2].DefaultCellStyle.Alignment = "MiddleCenter"

#DataGrid - Taille de colonne.
#En mode "Fill", il faut laisser une colonnes sans taille fixe.
$gridA1.Columns[2].Width = 80

#DataGrid - Ajouter les lignes de données.
[void]$gridA1.Rows.Add("Jumbor"      ,"Kana"      ,"2" )
[void]$gridA1.Rows.Add("Battle Game" ,"Doki-Doki" ,"2" )
[void]$gridA1.Rows.Add("Re:Teen"     ,"Doki-Doki" ,"3" )
[void]$gridA1.Rows.Add("Imotep"      ,"Kioon"     ,"10")
[void]$gridA1.Rows.Add("Pluto"       ,"Kana"      ,"8" )

#Bouton - Annuler.
$buttonA1 = New-Object Windows.Forms.Button
$buttonA1.Location = New-Object Drawing.Point ($largeurA-102),($hauteurA-67)
$buttonA1.Size = New-Object Drawing.Point 80,23
$buttonA1.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$buttonA1.BackColor = [System.Drawing.Color]::FromArgb(255,225,225,225)
$buttonA1.Text = "Quitter"
$buttonA1.Add_Click({ $formA.Close() })

#Bouton - Valider.
$buttonA2 = New-Object Windows.Forms.Button
$buttonA2.Location = New-Object Drawing.Point ($largeurA-187),($hauteurA-67)
$buttonA2.Size = New-Object Drawing.Point 80,23
$buttonA2.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$buttonA2.BackColor = [System.Drawing.Color]::FromArgb(255,225,225,225)
$buttonA2.Text = "OK"
$buttonA2.Add_Click({
                      #Index de la 1er cellule selectionné.
                      $r = $gridA1.SelectedCells[0].RowIndex
                      $c = $gridA1.SelectedCells[0].ColumnIndex
                     
                      #Nombre de ligne de données.
                      $n = ($gridA1.RowCount - 1)
                     
                      #Si la ligne sélectionné est valide.
                      if (($r -ne $n) -and (![string]::IsNullOrEmpty($r)))
                        {
                          #Afficher les données.
                          $titre   = $gridA1.SelectedCells[0].Value
                          $editeur = $gridA1.SelectedCells[1].Value
                          $volume  = $gridA1.SelectedCells[2].Value
                          Write-Host $titre "-" $editeur "-" $volume "volume(s)."
                        }
                      else
                        {
                          #Sinon afficher une erreur.
                          Write-Host "La valeur sélectionné n'est pas valide."
                        }
                   })

#Lier.
$formA.Controls.Add($gridA1)
$formA.Controls.Add($buttonA1)
$formA.Controls.Add($buttonA2)

#Events.
$formA.Add_Load({
                    $gridA1.ClearSelection()
                    $formA.Activate()
               })

$formA.Add_Resize({
                    #Ajuste la position des contrôles lorsque la fenêtre change de taille.
                    $gridA1.Size       = New-Object Drawing.Point ($formA.width-14) ,($formA.height-72)
                    $buttonA1.Location = New-Object Drawing.Point ($formA.width-102),($formA.height-67)
                    $buttonA2.Location = New-Object Drawing.Point ($formA.width-187),($formA.height-67)
                 })

#Afficher.
[void]$formA.ShowDialog()