mardi 2 mars 2021

Grille de données et LiteDB

Dans cet article, nous allons voir comment afficher une collection LiteDB dans un DataGridView. Pour cela, je vais créer une base LiteDB avec une collection. Puis je vais créer un formulaire de type DataGridView pour afficher les données de la collection. J'ajouterons à ce formulaire des fonctions d'ajout, modification et suppression de données.

LiteDB


LiteDB est une base de données "sans serveur" de type NoSQL se présentant sous la forme d'une simple Dll de moins de 500Ko. Elle ne nécessite ni installation, ni droit administrateur. LiteDB est donc facilement portable pour peu que le FrameWork soit installé sur le système.

Site de référence : http://www.litedb.org

Exemple


Dans cette exemple j'utilise LiteDB v3.1.4 (compatible v5.0.10). Pour fonctionner, il faut créer un dossier ldb au même niveau d'arborescence que le script, puis copier dans dossier lbd le fichier LiteDB.dll ainsi que son fichier xml.

 
####################################################################
#
#               Base de données : LiteBD v3.1.4 (compatible v5.0.10)
#                     Powershell.sekki.fr
#
# 1 - Créer un dossier ldb à côté du script.
# 2 - Télécharger et copier LiteDB.dll+xml dans le dossier ldb.
#
####################################################################

#Chemin courant.
$curpath = ""
if ($psISE) { $curpath = split-path -parent $psISE.CurrentFile.Fullpath }
else        { $curpath = Split-Path $MyInvocation.MyCommand.Path  }

#Charge les fonctions LiteDB.
Try   { [void][Reflection.Assembly]::LoadFile($curpath+"\ldb\LiteDB.dll") }
Catch { Write-Host "LiteDB.dll manquant.";Return 2 }

#Version LiteDB.
$global:VersionLDB = ([System.Diagnostics.FileVersionInfo]::GetVersionInfo(($curpath+"\ldb\LiteDB.dll")).FileVersion)[0]
Write-host "Vous utilisez LiteDB v$global:VersionLDB"

#Fonctions personnalisées : Converti le résultat de la query pour l'afficher.
function BsonToObj($bsn)
 {
   #Conversion (compatible LiteDB v3 et v5).
   $obj = New-Object System.Object
   $obj | Add-Member -type NoteProperty -name _id   -Value $bsn["_id"].AsObjectID
   $obj | Add-Member -type NoteProperty -name date  -Value $bsn["date"].AsString
   $obj | Add-Member -type NoteProperty -name info1 -Value $bsn["nom"].AsString
   $obj | Add-Member -type NoteProperty -name info2 -Value $bsn["prix"].AsInt32
   $obj | Add-Member -type NoteProperty -name info3 -Value $bsn["cat"].AsString
   $obj
 }

#Ouvrir/Créer une Base de Données.
$db = New-Object LiteDB.LiteDatabase(($curpath+"\ldb\LiteDB.db"),$null)

#Ouvrir/Créer une Collection.
$global:coll = $db.GetCollection("Info")

#Nouveau document Bson.
$bson = New-Object LiteDB.BsonDocument

#Fonction : Renvoi un nombre entre 0 et 100.
function GR() { Get-Random -minimum 0 -maximum 100 }

#Créer 5 enregistrements si la base est vide.
if (($global:coll.Count([LiteDB.Query]::all())) -eq 0)
  {
    for($i=0;$i -le 4;$i++)
     {
       $x = GR   #Nombre aléatoire entre 0 et 100.
       if ($x -le 50) { $nom = "Sekki";   $prix = $x; $cat = "Z" }
       else           { $nom = "Jumbor";  $prix = $x; $cat = "B" }

       #Ajouter un enregistrement dans la base de données.
       $bson["_id"]  = [LiteDB.ObjectId]::NewObjectId()
       $bson["date"] = date -format "dd/MM/yyyy"
       $bson["nom"]  = [string]$nom
       $bson["prix"] = [int]$prix
       $bson["cat"]  = [string]$cat
       write-host $nom $prix $cat
       [void]$global:coll.Insert($bson)   #Insère le document dans la collection.
     }
 }

#Fonction : Met à jour ou enregistre un objet dans la base (Écriture).
function SaveToBase($obj)
 {
   #Vérification.
   if ([string]::IsNullOrEmpty($obj._id)) { Return }
   
   #Vérifier si c'est un nouvel enregistrement ou une modification.
   if ($obj._id -eq "New") { $modifier = $false } else { $modifier = $true }

   #Création du Bson.
   if ($modifier)
     { #Modifier une ligne existante.
       $bsonid = New-Object LiteDB.ObjectId ([string]($obj._id))
       $bson   = $global:coll.FindOne([LiteDB.Query]::Eq("_id",$bsonid))
     }
   else
     { #Ajouter une nouvelle ligne.
       $bson = New-Object LiteDB.BsonDocument
       $bson["_id"] = [LiteDB.ObjectId]::NewObjectId()
     }
     
   #Tronc commun.   
   $bson["date"] = ([string]$obj.date)
   $bson["nom"]  = ([string]$obj.info1)
   $bson["prix"] = ([int]$obj.info2)
   $bson["cat"]  = ([string]$obj.info3)

   #Enregistrer.
   if ($modifier) { [void]$global:coll.Update($bson) }
   else           { [void]$global:coll.Insert($bson) }
 }

#Initialisation.
$global:delid = @()   #Tableau des lignes à supprimer.

#Formulaire.
$formA = New-Object Windows.Forms.Form
$formA.Text = "DataGridView-LiteDB."
$formA.BackColor = [System.Drawing.Color]::FromArgb(255,240,240,240)
    
#Taille de la fenêtre.
$formA.Size = New-Object Drawing.Point 400,300
$largeurA = $formA.width
$hauteurA = $formA.height
       
#DataGrid.
$DataGridA1 = New-Object System.Windows.Forms.DataGridView
$DataGridA1.Location = New-Object Drawing.Point 6,6
$DataGridA1.Size = New-Object Drawing.Point ($largeurA-28),($hauteurA-78)
$DataGridA1.ReadOnly = $false
$DataGridA1.AutoSize = $false
#$DataGridA1.EditMode = "EditOnEnter"
$DataGridA1.GridColor = "Black"
$DataGridA1.ScrollBars = "Vertical"
$DataGridA1.AllowUserToResizeRows = $true
$DataGridA1.BorderStyle = [System.Windows.Forms.BorderStyle]::None
$DataGridA1.BackgroundColor = [System.Drawing.Color]::FromArgb(255,240,240,240)
$DataGridA1.DefaultCellStyle.SelectionBackColor = [System.Drawing.Color]::FromArgb(255,70,149,218)
 
#Titre/Entête.
$DataGridA1.RowHeadersVisible    = $false
$DataGridA1.ColumnHeadersVisible = $true
$DataGridA1.ColumnHeadersHeightSizeMode = "DisableResizing"
$DataGridA1.EnableHeadersVisualStyles = $false
$DataGridA1.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(255,70,149,218)
$DataGridA1.ColumnHeadersDefaultCellStyle.ForeColor = "White"
$DataGridA1.ColumnHeadersBorderStyle = "Single"
 
#Colonnes.
[void]$DataGridA1.Columns.Add((New-Object System.Windows.Forms.DataGridViewTextBoxColumn))
[void]$DataGridA1.Columns.Add((New-Object System.Windows.Forms.DataGridViewTextBoxColumn))
[void]$DataGridA1.Columns.Add((New-Object System.Windows.Forms.DataGridViewTextBoxColumn))
[void]$DataGridA1.Columns.Add((New-Object System.Windows.Forms.DataGridViewTextBoxColumn))
[void]$DataGridA1.Columns.Add((New-Object System.Windows.Forms.DataGridViewTextBoxColumn))
$DataGridA1.Columns[0].Name = "Id"
$DataGridA1.Columns[0].Visible = $false   #Masque la colonne Id.
$DataGridA1.Columns[1].Name = "Date"
$DataGridA1.Columns[2].Name = "Nom"
$DataGridA1.Columns[3].Name = "Prix"
$DataGridA1.Columns[4].Name = "Cat"
 
#Colonnes : Défini la tailles des colonnes.
foreach($column in $DataGridA1.Columns)
 {
   $column.SortMode     = "NotSortable"
   $column.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill
 }
$DataGridA1.Columns[3].Width = 50   #Fixe la taille de la colonne.
$DataGridA1.Columns[4].Width = 50   #Fixe la taille de la colonne.
 
#Events.
$DataGridA1.Add_CellValueChanged({
  $r = $DataGridA1.SelectedCells[0].RowIndex
  $c = $DataGridA1.SelectedCells[0].ColumnIndex
  if ($DataGridA1.Rows[$r].Cells[0].Value -eq $null)
    {
      #Nouvelle ligne : Met la valeur "New" dans la colonne Id.
      #L'Id Bson sera généré plus tard lors de l'enregistrement.
      $DataGridA1.Rows[$DataGridA1.SelectedCells[0].RowIndex].Cells[0].Value="New"
    }
                                })

#Lier.
$formA.Controls.Add($DataGridA1)

#Bouton - Annuler.
$buttonA1 = New-Object Windows.Forms.Button
$buttonA1.Size = New-Object Drawing.Point 80,23
$buttonA1.Location = New-Object Drawing.Point ($largeurA-102),($hauteurA-67)
$buttonA1.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$buttonA1.BackColor = [System.Drawing.Color]::FromArgb(255,225,225,225)
$buttonA1.Text = "Annuler"
$buttonA1.Add_Click({ $formA.Close() })   #Ferme la fenêtre sans enregistrer.
$formA.Controls.Add($buttonA1)
   
#Bouton - Supprimer.
$buttonA2 = New-Object Windows.Forms.Button
$buttonA2.Size = New-Object Drawing.Point 80,23
$buttonA2.Location = New-Object Drawing.Point ($largeurA-188),($hauteurA-67)
$buttonA2.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$buttonA2.BackColor = [System.Drawing.Color]::FromArgb(255,225,225,225)
$buttonA2.Text = "Supprimer"
$buttonA2.Add_Click({
                      #Supprime les lignes sélectionnées.
                      foreach($data in $DataGridA1.SelectedCells)
                       {
                         $r = $data.RowIndex
                         if ($DataGridA1.Rows[$r])
                           {
                             $rowid  = $DataGridA1.Rows[$r].Cells[0].Value
                             #Ajoute l'Id au tableau des lignes à supprimer.
                             $global:delid += $rowid
                             #Supprime la ligne du DataGrid.
                             $DataGridA1.Rows.Remove($DataGridA1.Rows[$r])
                           }
                       }
                   })
$formA.Controls.Add($buttonA2)
 
#Bouton - OK.
$buttonA3 = New-Object Windows.Forms.Button
$buttonA3.Size = New-Object Drawing.Point 80,23
$buttonA3.Location = New-Object Drawing.Point ($largeurA-274),($hauteurA-67)
$buttonA3.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$buttonA3.BackColor = [System.Drawing.Color]::FromArgb(255,225,225,225)
$buttonA3.Text = "OK"
$buttonA3.Add_Click({
                      #Supprimer les données.
                      foreach($rowid in $global:delid)
                       {
                        #Les nouvelles lignes ne sont pas traitées.
                         if ($rowid -ne "New")
                           {
                             #Convertir le String "Id" en Bson "ObjectId".
                             $bsonid = New-Object LiteDB.ObjectId $rowid
                             #Supprime la ligne de la table "Info".
                             switch ($global:VersionLDB)
                              {
                                "3" { $null = $global:coll.Delete([LiteDB.Query]::Eq("_id",$bsonid)) }
                                "5" { $null = $global:coll.DeleteMany([LiteDB.Query]::Eq("_id",$bsonid)) }
                                Default { Write-Host "Version LiteDB non prise en charge !" }
                              }
                           }
                       }

                      #Enregistrer les données.
                      foreach($data in $DataGridA1.Rows)
                       {
                         #Créer un nouvel objet avec les données de la ligne.
                         $savinfo = New-Object PSObject -Property @{ _id = ""; date  = ""; info1 = ""; info2 = 0; info3 = "" }
                         $savinfo._id   = $data.Cells[0].Value
                         $savinfo.date  = $data.Cells[1].Value
                         $savinfo.info1 = $data.Cells[2].Value
                         $savinfo.info2 = $data.Cells[3].Value
                         $savinfo.info3 = $data.Cells[4].Value
                         #Sauvegarder l'objet dans la collection "Info".
                         $null = SaveToBase $savinfo
                       }
                       
                      #Fermer la fenêtre.
                      $formA.Close()
                   })
$formA.Controls.Add($buttonA3)
     
#Event.
$formA.Add_Load({   $DataGridA1.ClearSelection() })
$formA.Add_Resize({
     #Ajuster la position des contrôles lorsque la fenêtre change de taille.
     $largeurA          = $formA.width; $hauteurA = $formA.height
     $DataGridA1.Size   = New-Object Drawing.Point ($largeurA-28),($hauteurA-78)
     $buttonA1.Location = New-Object Drawing.Point ($largeurA-102) ,($hauteurA-67)
     $buttonA2.Location = New-Object Drawing.Point ($largeurA-188),($hauteurA-67)
     $buttonA3.Location = New-Object Drawing.Point ($largeurA-274),($hauteurA-67)
                 })

#Chargement des données.
$base = $global:coll.Find([LiteDB.Query]::all())
foreach($bson in $base)  
 {
   #Ajoute la ligne dans le DataGridView.
   $line = BsonToObj $bson
   [void]$DataGridA1.Rows.Add($line._id, $line.date, $line.info1, $line.info2, $line.info3 )
 }
     
#Afficher.
[void]$formA.ShowDialog()

#Fermer la base de données.
$db.Dispose()

#Fin.

mercredi 10 février 2021

Exécuter un script Powershell à distance

Dans cet article je vais vous présenter la fonction PSSession qui nous permet d’exécuter des commandes Powershell sur des ordinateurs distants. PSSession se base sur le service "Windows Remote Management (WS-Management)". Ce service doit donc être démarré et configurer pour accepter les connections.

Premièrement nous allons voir le mode interactif. Pour cela, nous allons utiliser la commande "Enter-PSSession" qui va nous permettre de taper nos commandes comme si nous étions sur le poste distant.

Enter-PSSession "mycomputer.sekki.fr"

IpConfig

Exit

Ce mode est intéressant mais un peu limité. Pour automatiser mes commandes, je vais avoir besoin d'executer des scripts complet. Pour cela je vais m’intéresser à la commande "New-PSSession" qui me permettra d’exécuter des blocs de commande complet.

 
#Initialisation.
$serveur = "mycomputer.sekki.fr"

#Ouvre une session permanente sur le poste distant.
$mySession = New-PSSession $serveur

if ($mySession)
  {
    #Exécute une commande sur le poste distant.
    Invoke-Command -Session $mySession { Ipconfig }
  }


La commande "Get-PSSession" permet de voir les sessions ouvertes.

Get-PSSession

Au besoin, les sessions peuvent être déconnecte et reconnecté avec les commandes suivantes.

 
#Deconnecter une session.
Disconnect-PSSession $mySession
Get-PSSession


 
#Reconnecter une session.
Connect-PSSession $mysession
Get-PSSession


Pour fermer une session permanente, nous utilisons la commande "Remove-PSSession".

 
#Ferme une session ouverte avec "New-PSSession".
Remove-PSSession $mysession
Get-PSSession



Exemple 1 : Exécuter un script à distance.

 
#Initialisation.
$serveur = "mycomputer.sekki.fr"
#Script à exécuter.
$script = {
            #Script à exécuter sur la session distante.
            Ipconfig
          }

#Ouvre une session sur le poste distant.
$mySession = New-PSSession $serveur
if ($mySession)
  {
    #Exécute le script sur le poste distant.
    Invoke-Command -Session $mySession $script

    #Ferme la session.
    Remove-PSSession $mySession
  }



Exemple 2 : Installer un Msi avec PSSession.

 
#Initialisation.
$serveur = "mycomputer.sekki.fr"
$remoteTemp = "\\"+$serveur+"\C$\temp"

#Copier la source sur le poste distant.
Copy-Item -Path "D:\Sources\Agent.msi" -Destination $remoteTemp

#Ouvre une session sur le poste distant.
$mySession = New-PSSession $serveur
if ($mySession)
  {
    #Exécute le script sur le poste distant.
    Invoke-Command -Session $mySession { Start-Process msiexec -argumentlist "/i C:\temp\agent.msi /qn" }

    #Ferme la session.
    Remove-PSSession $mySession
  }


mercredi 21 octobre 2020

100000 Hits

Le site vient d'atteindre les 100000 hits. Cette année 2020 à connu une hausse significatif du nombre de visite. Les articles les plus visités sont les expressions régulières, les fichiers, les listes déroulantes. Les visiteurs viennent principalement cette année de France, Hong Kong, Etats-Unis. Merci à tous !



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 27 février 2020

Annonce - Eco7 v1.5.0

Eco7 version 1.5.0 avec Snmp est enfin sortie. Ca fait un bon moment que j'ai cette version dans mes cartons mais je n'ai pas eu le temps de finaliser totalement sa sortie. C'est chose fait ! Dans cette version j'ai intégré "SharpSnmpLib" ce qui lui permet de récupèrer les compteurs Snmp d'un serveur ou d'un équipement. Eco7 se limite néanmoins à l’interrogation des compteurs Snmp via les OID.

J'ai également apporter quelques amélioration plus discret ici et là. Notamment j'ai réorganisé les champs dans les fenêtres. Plus quelques améliorations de codes.

N'hésitez pas à vous rendre sur la page d'Eco7 pour avoir plus d'informations.

mardi 11 février 2020

Ecriture de l'AD

Nous allons voir dans cet article les commandes pour modifier l'Active Directory. Les commandes de bases sont New-ADUser, Set-ADUser, Remove-ADUser et New-ADGroup, Set-ADGroup, Add-ADGroupMember, Remove-ADGroupMember, Remove-ADGroup. Ces commandes vont nous permettre de modifier les informations sur les utilisateurs et les groupes enregistrés dans l'Active Directory.

Il faut tout d'abord importer les commandes dans notre environnement PowerShell :
 
Import-Module ActiveDirectory

Pour commencer nous allons créer un utilisateur dans l'Active-Directory dans l'OU "Test\Users" :
 
$login    = "Jumbor"   #SamAccountName
$domain   = "sky.fr"
$sbase    = "OU=Users,OU=Test,DC=sky,DC=fr"
$password = ConvertTo-secureString "Password123" -AsPlainText -Force

New-ADUser -Name "Jumbor" -DisplayName "Baru Crow" -UserPrincipalName ($login+"@"+$domain) -SamAccountName $login -AccountPassword $password -ChangePasswordAtLogon $true -Enabled $true -Path $sbase

Pour modifier une propriété du compte, nous allons utiliser la commande suivante :
 
$user = "Jumbor"   #SamAccountName
Set-ADUser -Identity $user -EmailAddress "jumbor@sekki.fr"
Le paramètre "-Identity" accepte plusieurs types d'entrés pour identifier le compte à modifier. Les types acceptés sont "Distinguished Name","SID/GUID","SamAccountName", ou un objet utilisateur.

De nombreuses propriétés peuvent être modifiées de cette façon. Par exemple  : -AccountExpirationDate, -CannotChangePassword, -ChangePasswordAtLogon, -PasswordNeverExpires,-Description, -GivenName, -HomeDirectory, -LogonWorkstations, -Organization, -PostalCode, -ScriptPath, -StreetAddress, -City ...

Mais parfois, nous devons modifier un attribue qui ne figure pas dans la liste des paramètres. Dans ce cas nous utilisons les commandes "-Replace", "-Clear", "-Add", "-Remove" pour modifier un ou plusieurs attribues :
 
$user = "Jumbor"   #SamAccountName
Set-ADUser -Identity $user -Replace @{loginShell="/ksh/bash"}

Pour supprimer un utilisateur :
 
$user = "Jumbor"   #SamAccountName
Remove-ADUser -Identity $user -Confirm:$false

Nous allons maintenant créer un groupe dans l'Active-Directory dans l'OU "Test\Groups" :
 
$group = "One_Shot"   #SamAccountName
$sbase    = "OU=Groups,OU=Test,DC=sky,DC=fr"

New-ADGroup -Name "One Shot" -SamAccountName $group -GroupScope "Global" -GroupCategory "Security" -Path $sbase
GroupScope prend les valeurs suivantes : DomainLocal, Global, Universal.
GroupCategory prend les valeurs suivantes : Distribution, Security.

Pour modifier une propriété du groupe, nous allons utiliser la commande suivante :
 
$group = "One_Shot"   #SamAccountName
Set-ADGroup -Identity $group -Description "One Shot Group !"

Pour modifier un attribue :
 
$group = "One_Shot"   #SamAccountName
Set-ADGroup -Identity $group -Replace @{mail="Jumbor@sekki.fr"}

Pour ajouter des membres dans le groupe :
 
$group = "One_Shot"   #SamAccountName
Add-ADGroupMember -Identity $group -Members "Jumbor","Arkel"

Pour supprimer des membres du groupe :
 
$group = "One_Shot"   #SamAccountName
Remove-ADGroupMember -Identity $group -Members "Jumbor","Arkel" -Confirm:$false

Pour supprimer un groupe :
 
$group = "One_Shot"   #SamAccountName
Remove-ADGroup -Identity $group -Confirm:$false

mercredi 22 janvier 2020

Lecture de l'AD

Nous allons voir dans cet article les commandes pour interroger l'Active Directory. Les trois commandes de bases sont Get-ADUser, Get-ADGroup et Get-ADComputer. Ces commandes vont nous permettre de récupérer des informations sur les utilisateurs, les groupes et les ordinateurs enregistré dans l'Active Directory.

Pour commencer il faut tout d'abord importer les commandes dans notre environnement PowerShell :
 
Import-Module ActiveDirectory

Les commandes suivantes nous permettent de récupérer l'ensemble des objets de l'AD :
 
Get-ADUser -Filter * | Out-GridView
Get-ADGroup -Filter * | Out-GridView
Get-ADComputer -Filter * | Out-GridView

Le paramètre -Filter * indique à la commande que nous voulons récupérer tout les objets. Pour voir un objet en particulier nous allons utiliser un filtre plus détaillé :
 
Get-ADUser -Filter {SamAccountName -like 'Sekki'}
Get-ADGroup -Filter {SamAccountName -like 'Tribuns'}
Get-ADComputer -Filter {Name -like 'Yukine'}

Par défaut, nous ne voyons qu'une petite partie des propriétés des objets. Pour afficher la totalité des paramètres nous allons utiliser les commandes suivantes :
 
Get-ADUser -Filter {SamAccountName -like 'Sekki'} -Properties *
Get-ADGroup -Filter {SamAccountName -like 'Tribuns'} -Properties *
Get-ADComputer -Filter {Name -like 'Yukine'} -Properties *

Cette fois nous pouvons voir tout les propriétés des objets, et il y en a beaucoup. Nous allons donc faire maintenant une sélection :
 
Get-ADUser -Filter * -Properties SamAccountName,Enabled,LockedOut,LastLogondate | Out-GridView Get-ADGroup -Filter * -Properties SamAccountName,GroupCategory,GroupScope | Out-GridView Get-ADComputer -Filter * -Properties Name,Enabled,Created,LastLogonDate,Location | Out-GridView

Si le domain à interroger n'est pas le domain courant, on utilise l'option -Server pour spécifier le nom du domain :
 
Get-ADUser -Filter * -Server "Sekki.fr" -Properties SamAccountName,Enabled,LockedOut | Out-GridView

Nous allons voir maintenant quelques cas classique. En premier comment récupérer les groupes d'un utilisateur :
 
$groups = Get-ADUser -Filter {Name -like 'yukine'} -Properties *
foreach($group in $groups)
 {
   $group = (($group.split(","))[0]) -replace("CN=","")   #Nettoyage.
   Write-Host $group
 }

Les commandes qui permettent le lister les membres d'un groupe :
 
$members = (Get-ADGroup -Filter {Name -like 'Tribuns'} -Properties *).Members
foreach($member in $members)
 {
   $member = (($member.split(","))[0]) -replace("CN=","")   #Nettoyage.
   Write-Host $member
 }
ou
 
(Get-ADGroupMember -Identity "Tribuns").SamAccountName

Pour finir cette commande permet de récupérer les comptes inutilisés depuis 6 mois.
 
$date = (Get-Date).AddDays(-180)
Get-ADUser -Filter {LastLogonDate -lt $date} -Properties Enabled,LockedOut,DisplayName,SamAccountName,LastLogonDate | Out-GridView

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()
  

vendredi 19 octobre 2018

Réaliser une image ronde.

Je vais revenir un peu sur les graphismes dans cet article. Nous allons voir comment créer une image d'avatar tout ronde avec PowerShell. Dans l'exemple suivant, je crée une fenêtre transparente dans laquelle j'affiche une image toute ronde à partir d'un fichier contenant une image classique. Pour des raisons pratiques, j'ai ajouté une fonction "Escape" pour pouvoir sortir facilement du programme.


 
#Assembly.
[void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
[void][reflection.assembly]::LoadWithPartialName("System.Drawing")

#Defini le chemin courant.
$curpath = ""
if ($psISE) { $curpath = Split-Path -parent $psISE.CurrentFile.Fullpath }
else        { $curpath = Split-Path $MyInvocation.MyCommand.Path }
 
#Ouvre une fenêtre transparente sans bord ni fond.
$formA = New-Object Windows.Forms.Form
$formA.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
$formA.Size = New-Object System.Drawing.Size(100,100)
$formA.FormBorderStyle = "None"
$formA.TopMost = 1
$formA.BackColor = [System.Drawing.Color]::FromArgb(255,1,2,3)
$formA.TransparencyKey = [System.Drawing.Color]::FromArgb(255,1,2,3)

#Crée un contrôle image.
$imageA1 = New-Object System.Windows.Forms.pictureBox
$imageA1.Location = New-Object Drawing.Point 0,0
$imageA1.Size = New-Object System.Drawing.Size(60,60)

#Charge le fichier image à traiter.
#Il doit être au même endroit que le script.
#Notre fichier contient une image classique rectangulaire.
Try   { $file = New-Object System.Drawing.Bitmap($curpath+"\av.png") }
Catch { Exit }

#Créer un Bitmap.
#Correspond ici à une feuille vierge (transparente).
$cgdata = New-Object System.Drawing.Bitmap(60,60)

#Créer un objet Graphics.
#Cet objet nous permettra de dessiner sur notre feuille.
$graphic = [System.Drawing.Graphics]::FromImage($cgdata)

#Nous allons redimensionné l'image.
#Pour obtenir une meilleur qualité, nous allons utiliser l'option suivante.
$graphic.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic

#Nous allons maintenant définir une zone de dessin sur notre feuille.
#Ici, ma zone est un cercle de 60px sur 60px. Une fois appliqué, seule
#la zones définit sera dessinable. Rien ne sera modifié en dehors de la zone.
$grpath = New-Object System.Drawing.Drawing2D.GraphicsPath(0)
$grpath.AddEllipse(0, 0, 60, 60)
$graphic.SetClip($grpath)

#Copie l'image ac.png dans la zone dessinable sur la feuille.
#J'utilise ici DrawImage pour forcer le redimensionnement
#de l'image à la taille de ma feuille.
$graphic.DrawImage($file, 0, 0, 60, 60)
        
#Puis j'applique le dessin obtenu au contrôle image.
$imageA1.Image = $cgdata
$graphic.Dispose()

#Fermer la fenêtre lorsque l'on appuie sur la touche "Escape".
$formA.add_KeyDown({ If ($_.KeyCode -eq "Escape") { $formA.Close() } })

#Attache les contrôles à la fenêtre.
$formA.controls.add($labelA1)
$formA.controls.add($imageA1)

#Affiche le tout.
[void]$formA.ShowDialog()

mardi 15 mai 2018

Dism - Install roles and features

DISM (Deployment Image Servicing and Management) est un outil en ligne de commande permettant de modifier l'image Windows. Dans cet article, nous allons voir comment activer et désactiver des rôles et des fonctionnalités Windows en ligne de commande avec la commande DISM.

Installer un rôle ou une fonctionnalité


Installation simple sur l'OS en cours :
DISM /Online /Enable-Feature /All /FeatureName:TFTP

Installation en spécifiant la source :
DISM /Online /Enable-Feature /FeatureName:TFTP /Source:Z:\sources\SxS /LimitAccess

/Online : Indique que l'opération se déroule sur un Système en cours d’exécution.
/Enable-Feature : Installe le rôle ou la fonctionnalité indiqué avec /FeatureName.
/FeatureName : Nom du rôle ou de la fonctionnalité.
/LimitAccess : Empêche Dism d'utiliser Windows Update comme source.
/Source : Indique le chemin de la source à utiliser pour l'installation.
/All : Indique qu'il faut installer toutes les dépendances et les fonctionnalités liées.

Désinstaller un rôle ou une fonctionnalité


Désinstallation simple sur l'OS en cours :
DISM /Online /Disable-Feature /FeatureName:TFTP

/Online : Indique que l'opération se déroule sur un Système en cours d’exécution.
/Disable-Feature : Désinstalle le rôle ou la fonctionnalité indiqué avec /FeatureName.
/FeatureName : Nom du rôle ou de la fonctionnalité.

Lister tout les rôles et fonctionnalités disponible


Liste simple sur l'OS en cours :
Dism /Online /Get-Features

/Online : Indique que l'opération se déroule sur un Système en cours d’exécution.
/Get-Feature : Liste les rôles et les fonctionnalités, et affiche si ils sont activé ou non.

Liste des noms à utiliser avec FeatureName


Get-WindowsFeature

Rôle (FeatureName) Nom affiché
RemoteAccess
NetworkController
ServerEssentialsRole
Hyper-V
MultiPointServerRole
Fax
DHCP
DNS
Web-Server
Web-WebServer
Web-Static-Content
Web-Default-Doc
Web-Http-Errors
Web-Dir-Browsing
Web-Http-Redirect
Web-Http-Logging
Web-Request-Monitor
Web-Log-Libraries
Web-Http-Tracing
Web-Stat-Compression
Web-Dyn-Compression
Web-Filtering
Web-Basic-Auth
Web-Digest-Auth
Web-Client-Auth
Web-Windows-Auth
Web-CertProvider
Web-Asp-Net45
Web-CGI
Web-Net-Ext45
Web-ISAPI-Ext
Web-ISAPI-Filter
Web-Mgmt-Console
Web-Metabase
Web-Scripting-Tools
Web-Ftp-Service
HostGuardianServiceRole
AD-Domain-Services
ADLDS
ADRMS
Remote-Desktop-Services
VolumeActivation
Print-Services
AD-Certificate
WDS
ADFS-Federation
FS-FileServer
FS-Data-Deduplication
Storage-Services
NPAS
UpdateServices
...
Accès à distance
Contrôleur de réseau
Expérience Windows Server Essentials
Hyper-V
MultiPoint Services
Serveur de télécopie
Serveur DHCP
Serveur DNS
Serveur Web (IIS)
Serveur Web
Contenu statique
Document par défaut
Erreurs HTTP
Exploration de répertoire
Redirection HTTP
Journalisation HTTP
Observateur de demandes
Outils de journalisation
Suivi de traces
Compression du contenu statique
Compression de contenu dynamique
Filtrage des demandes
Authentification de base
Authentification Digest
Authentification par mappage de certificat
Authentification Windows
Prise en charge centralisée des certificat
ASP.NET 4.6
CGI
Extensibilité .NET 4.6
Extensions ISAPI
Filtres ISAPI
Console de gestion IIS
Compatibilité de métadonnées IIS 6
Scripts et outils de gestion IIS
Service FTP
Service Guardian hôte
Services AD DS
Services AD LDS
Services AD RMS
Services Bureau à distance
Services d’activation en volume
Services d’impression et de numérisation
Services de certificats Active Directory
Services de déploiement Windows
Services de fédération Active Directory
Serveur de fichiers
Déduplication des données
Services de stockage
Services de stratégie et d’accès réseau
Services WSUS
...

vendredi 23 mars 2018

Annonce - Eco7 v1.4.0

Avec Eco7 version 1.4.0, j'ai pratiquement réécris l'ensemble de l'application. Si le concept reste inchangé, il n'y a pas une seul fonction qui n'a pas du être remanié ou optimisé. C'est un gros travail qui m'a longtemps refroidi. Mais j'ai finalement sauté le pas ^^.

Les principales modifications sont l’intégration de Lite-DB dans Eco7 pour stocker les données et les journaux. L'apport de la base de données permet de travailler directement sur la base de données au lieu de charger un fichier en mémoire et de sauvegarder à la fermeture du programme. Cela apporte plus de fiabilité mais aussi plus de rapidité dans le traitement des informations.

J'ai ajouté une nouvelle fonctionnalités afin d'afficher un rapport de surveillance pour chaque icône. Ceci permet d'avoir un tableau plus simple à lire et avec des alarmes plus détaillé.

J'ai ajouté la possibilité de surveiller de décalage des horloges des serveurs Windows par rapport à un serveur NTP de référence lorsque l'option Wmi est activé. Actuellement, j'utilise les seuils suivant : Inférieur à 1s = vert, de 1-5s = jaune, 5-60s = orange, plus de 60s = rouge.

N'hésitez pas à vous rendre sur la page d'Eco7 pour avoir plus d'information.

Spoiler : Pour la prochaine version, je travaille sur l'intégration de SharpSnmpLib dans Eco7.

mercredi 21 mars 2018

Powershell et SNMP

Pour récupérer des informations SNMP avec Windows Powershell, j'utilise dans cet article SharpSnmpLib.dll. L'exemple proposé ici montre en particulier comment utiliser les commandes SnmpWalker et SnmpGet avec Windows Powershell.

Principe général du protocole SNMP


Le SNMP est un protocole client serveur utilisé pour transmettre l'état des services et applications d'un serveurs vers une console de surveillance. Le protocole SNMP se divise en deux principes de communication. Le mode Pull à l'initiative du client et le mode Push, à l'initiative du serveur. Dans le mode Pull le client va lire les informations mis à disposition par le serveur (snmpwalk, snmpget). Dans le mode Push, le client va écouté les informations envoyé par le serveur (snmptrap, snmptrapd).

Référence


J'ai testé un article très intéressant sur la façon de récupérer des informations SNMP en PowerShell. Si vous maitrisez un minimum l'anglais, je vous invite à le lire l'article en référence. Vous trouverez le lien du site ci-dessous. Par contre j'ai eu un peu de mal à récupérer le fichier "SharpSnmpLib.dll". Je vais donc ajouter un lien avec la source. L'exemple que je vous propose aujourd'hui est une donc une version légèrement remanier en français.

Site de référence : https://vwiki.co.uk/SNMP_and_PowerShell
Télécharger la Dll : SharpSnmpLib.dll

Exemple


Dans cet exemple j'utilise le mode SNMP Pull pour lire les information mis à disposition sur un système distant. Pour cet exemple, il faut créer un dossier "lib" au même niveau que le script, et y décompresser l'archive téléchargée ci-dessus.


 
############################################################
# Snmp : SharpSnmpLib v7.6
# Powershell : Version 2.0
############################################################

#Chemin courant.
$curpath = ""
if ($psISE) { $curpath = split-path -parent $psISE.CurrentFile.Fullpath }
else        { $curpath = Split-Path $MyInvocation.MyCommand.Path  }

#Chargement des librairies.
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
Try   { [void][Reflection.Assembly]::LoadFile($curpath+"\lib\SharpSnmpLib.dll") }
Catch { write-host "SharpSnmpLib.dll manquant.";Return }

############################################################
# Fonctions SNMP
############################################################

#Fonction - Créer un type générique.
function Snmp-GenericObject ([string]$typeName,[string[]]$typeParams,[object[]]$constructorParams)
 {
   #Créer le nom du type générique.
   $genericTypeName = $typeName + "``" + $typeParams.Count
   $genericType = [Type] $genericTypeName
   if (!$genericType) { throw "Impossible de trouver le type générique $genericTypeName" }

   #Lier les parametres de type à celui-ci.
   [type[]] $typedParams = $typeParams
   $closedType = $genericType.MakeGenericType($typedParams)
   if (!$closedType) { throw "Impossible de finaliser le type $genericType" }

   #Créer la version final du type générique.
   ,[Activator]::CreateInstance($closedType, $constructorParams)
}

#Fonction - SnmpWalker.
function Snmp-Walker ([string]$sIP, $sOIDstart, [string]$Community = "public", [int]$UDPport = 161, [int]$TimeOut=3000)
 {
   #$TimeOut est en msec, 0 ou -1 pour infini.
   #Créer un objet OID.
   $oid = New-Object Lextm.SharpSnmpLib.ObjectIdentifier ($sOIDstart)
   
   #Créer la liste pour les résultats (compatible powershell v2).
   $results = Snmp-GenericObject System.Collections.Generic.List Lextm.SharpSnmpLib.Variable
   
   #Créer un point de terminaison pour le serveur SNMP.
   $ip = [System.Net.IPAddress]::Parse($sIP)
   $svr = New-Object System.Net.IpEndPoint ($ip, 161)

   #Utiliser SNMP v2 avec le mode "WithinSubTree".
   $ver = [Lextm.SharpSnmpLib.VersionCode]::V2
   $walkMode = [Lextm.SharpSnmpLib.Messaging.WalkMode]::WithinSubtree

   #Lancer le requête SNMP.
   Try   { $null = [Lextm.SharpSnmpLib.Messaging.Messenger]::Walk($ver, $svr, $Community, $oid, $results, $TimeOut, $walkMode) }
   Catch { Write-Host "Erreur SNMP : $_"; Return }

   #Dans cet exemple j'ai changé le format de sortie pour un tableau d'objet.
   $rtn = @()
   foreach ($var in $results)
    {
      $obj = New-Object System.Object
      $obj | Add-Member -type NoteProperty -name OID  -Value ([string]$var.Id)
      $obj | Add-Member -type NoteProperty -name Data -Value ([string]$var.Data)
      $rtn += $obj
    }
   #Résultats.
   $rtn
 }

#Fonction - GetSnmp.
function Snmp-Get ([string]$sIP, $sOIDs, [string]$Community = "public", [int]$UDPport = 161, [int]$TimeOut=3000)
 {
   #$TimeOut est en msec, 0 ou -1 pour infini.
   #Créer une liste de variables OID (compatible powershell v2).
   $vList = Snmp-GenericObject System.Collections.Generic.List Lextm.SharpSnmpLib.Variable
   foreach ($sOID in $sOIDs)
    {
      $oid = New-Object Lextm.SharpSnmpLib.ObjectIdentifier ($sOID)
      $vList.Add($oid)
    }
   
   #Créer un point de terminaison pour le serveur SNMP.
   $ip = [System.Net.IPAddress]::Parse($sIP)
   $svr = New-Object System.Net.IpEndPoint ($ip, 161)
   
   #Utiliser SNMP v2.<
   $ver = [Lextm.SharpSnmpLib.VersionCode]::V2
   
   #Lancer le requête SNMP.
   Try   { $msg = [Lextm.SharpSnmpLib.Messaging.Messenger]::Get($ver, $svr, $Community, $vList, $TimeOut) }
   Catch { Write-Host "SNMP Get error: $_"; Return }

   #Dans cet exemple j'ai changé le format de sortie pour un tableau d'objet.   
   $rtn = @()
   foreach ($var in $msg)
    {
      $obj = New-Object System.Object
      $obj | Add-Member -type NoteProperty -name OID  -Value ([string]$var.Id)
      $obj | Add-Member -type NoteProperty -name Data -Value ([string]$var.Data)
      $rtn += $obj
    }
   #Résultats.
   $rtn
 }

############################################################
# Zone de test.
#
# Usage :
# Snmp-Walker IP OIDSTART [Community] [Port] [Timeout]
# Snmp-Get    IP OID      [Community] [Port] [Timeout]
############################################################

#Snmp Walker.
#Remplacer l'adresse ip par une adresse valide.
Snmp-Walker "127.0.0.1" ".1.3.6.1.2.1" | Out-String
Snmp-Walker "127.0.0.1" ".1.3.6.1.2.1" | Out-GridView

#Snmp Get.
#Remplacer l'adresse ip par une adresse valide.
Snmp-Get "127.0.0.1" ".1.3.6.1.2.1.1.5.0" | Out-String
Snmp-Get "127.0.0.1" ".1.3.6.1.2.1.1.5.0" | Out-GridView

#Fin.

mardi 20 mars 2018

Expressions régulières (RegEx)

Windows PowerShell utilise les expressions régulières (RegEx) pour déterminer si une chaine de caractères est conforme au format définit par l'expression régulière. Pour comparer une chaine de caractères avec une RegEx, Windows PowerShell utilise -match -notmatch, -cmatch, -notcmatch.

Principe du RegEx.


Une expression régulière est une chaîne de caractères représentant un ensemble de chaîne de caractères possibles. Dit comme ça ce ne parle pas forcement à tout le monde. Je vais essayer d'expliquer ça plus simplement. Imaginez que vous voulez rechercher le fichier "toto.txt"sur votre disque. Dans le champ de recherche vous utilisez le caractères génériques * pour dire au système de trouver tout les fichiers commençant par "toto" (ex : toto*). Le système va alors afficher des fichiers commençant par "toto" comme "toto1.txt", "toto.xml", etc. Le RegEx c'est pareil, mais avec des caractères génériques infiniment plus élaborés.

Note : Oui je sais, le caractère générique * n'est pas interprété pareil dans la recherche de fichier que dans le RegEx, mais c'est pour l'explication du principe de base ... "Il écoute vraiment rien Hervé" ^^.

Les bases de l'expression régulière.


^ = En début de chaine signifie "commence par".
^ = En milieu de chaine ce symbole représente la négation.
$ = En fin de chaine signifie "se termine par".
\w = [a-zA-Z0-9_]
\d = Contient au moins 1 chiffre.
\s = Contient au moins 1 espace.
\D = Ne contient aucun chiffre.
\S = Ne contient aucun espace.
[] = Liste ou range de caractère ex: [a-z] tout les caractères de "a" à "z".
{} = Longueur de la chaine à valider ex: {1,5} = 1 à 5 caractères.
{5} = Longueur exacte de la chaine (ici 5 caractère).
{5,} = Longueur au moins de 5 caractères (de 5 à n).
{5,9} = Longueur de 5 à 9 caractères.
* = N'importe quel longueur de 0 à n caractères.
+ = N'importe quel longueur de 1 à n caractères.
? = Indique que la condition précédente est optionnel.
| = Indique le choix entre deux conditions (Ou).

Exemples :
[a-z]* = Chaine alphabétique de longueur de 0 à n caractères.
[a-z]+ = Chaine alphabétique de longueur de 1 à n caractères.
[a-z]{1,12} = Chaine alphabétique de longueur de 1 à 12 caractères.
\d{1,6} = Nombre de 0 à 999999.
[:;,.] = Liste de caractères.
([a-z]+\d{1,4})+ = Le (...)+ indique une structure répétable de 1 à n fois.

Les comparaisons :
-match = Correspond à ... (insensible à la casse).
-notmatch = Ne correspond pas à ... (insensible à la casse).
-cmatch = Correspond à ... (sensible à la casse).
-cnotmatch = Ne correspond pas à ... (sensible à la casse).

Caractère d'échappement :
En dehors des cas vu précédemment, la barre oblique inverse "\" est utilisée dans une expression régulière pour indiquer que le caractère suivant doit être interprété littéralement. Par exemple :
\\ = \
\. = .
\+ = +
\* = *

Exemples classiques.


Texte avec caractères alphabétiques :
$texte = "Jumbor"
$texte -match  "^[a-z]*$"
$texte -cmatch "^[a-zA-Z]*$"

Texte en minuscule uniquement :
$texte = "jumbor"
$texte -cmatch "^[a-z]*$"


Texte en majuscule uniquement :
$texte = "JUMBOR"
$texte -cmatch "^[A-Z]*$"


Texte de 1 à 12 caractères alphabétiques :
$texte = "Jumbor"
$texte -match "^[a-z]{1,12}$"


Texte commençant par une majuscule, et de 1 à 12 caractères alphabétiques :
$texte = "Jumbor"
$texte -cmatch "^[A-Z]{1}[a-zA-Z]{0,11}$"


Texte de 1 à 12 caractères sans espace et sans chiffre :
$texte = "Jumbor"
$texte -match "^\S\D{1,12}$"


Chiffre de 0 à 99999 :
$texte = "12"
$texte -match "^\d{1,5}$"


Ne contient pas ces caractères :
$texte = "Jumbor"
$texte -notmatch "[:;,.]"


Contient que des chiffres et lettres :
$texte = "Jumbor12"
$texte -match "^[0-9a-z]*$"
$texte -cmatch "^[0-9a-zA-Z]*$"

Exemples avancés.


Vérifier le format "Prénom NOM" avec des prénoms simples :
$texte = "Barus CROW"
$texte -cmatch "^[A-Z]{1}[a-zA-Z]* [A-Z]*$"


Idem en ajoutant des prénoms composés en "Prénom-Prénom" :
$texte = "Jean-Pierre CROW"
$texte -cmatch "^(([A-Z]{1}[a-z]*)|([A-Z]{1}[a-z]*-[A-Z]{1}[a-z]*)) [A-Z]*$"


Idem en ajoutant des caractères accentué :
$texte = "Loïc CROW"
$texte -cmatch "^(([A-Z]{1}[éïëèa-z]*)|([A-Z]{1}[éïëèa-z]*-[A-Z]{1}[éïëèa-z]*)) [A-Z]*$"


Vérifier une adresse ip4 :
$ip = "192.168.1.1"
$ip -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"


Vérifier une adresse ip4 plus précisément :
$ip = "192.168.1.1"
$ip -match "((\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)"


Vérifier une adresse email en particulier :
$email = "Jumbor@sekki.fr"
$email -match "^(([a-z]+)|([a-z]+\.[a-z]+))@sekki.fr$"


Vérifier une adresse mail générique :
$email = "Jumbor@sekki.fr"
$email -match "^(([a-z]+)|([a-z]+\.[a-z]+))@[a-z]+\.[a-z]+$"


Vérifier une adresse DNS simple :
$dns = "powershell.sekki.fr"
$site -match $dns -match "^([a-z]+\.)*([a-z]+)$"


Vérifier un site web :
$site = "http://powershell.sekki.fr"
$site -match "^(http://|https://|ftp://)([a-z]+\.)*([a-z]+)$"

Extraire une chaine en RegEx.


Exemple 1 :
$text = "Mon numéro de téléphone n'est pas le 06-11-22-33-44 !!!"
$phone = [regex]::match($text,'((\d{2}(-|\.){0,1}){5})').Value

Remplacer une chaine en RegEx.


Remplace une Ip dans le texte :
$text = "Serveur;Site;10.112.120.6;Type"
$rtn = [regex]::replace($text,'((\d{1,3}(\.){0,1}){4})','xxx.xxx.xxx.xxx')


#Remplace le début d'une Ip dans le texte :
$text = "Serveur;Site;10.112.120.6;Type"
$ip1 = [regex]::match($text,'((\d{1,3}(\.){0,1}){4})').Value
$ip2 = [regex]::replace($ip1,'^(\d{1,3}(\.))','xxx.')
$rtn = [regex]::replace($text,'((\d{1,3}(\.){0,1}){4})',$($ip2))


lundi 5 mars 2018

Bouton Parcourir (OpenFileDialog)


Dans cet article, nous allons utiliser Windows PowerShell pour ouvrir une fenêtre (Form) et y ajouter le bouton de sélection de fichier "Parcourir" (OpenFileDialog). L'objet OpenFileDialog gère à la fois la création de la fenêtre de sélection, l'exploration des fichiers, et la sélection du fichier. Il ne nous reste plus qu'à configurer quelques options, lancer l'ouverture de la fenêtre, et récupérer le nom du fichier choisi.


 
#Menu contextuel sur un contrôle.
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

#Ouvre une fenêtre.
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$form = New-Object Windows.Forms.Form
$form.text = "Ouvrir un fichier"            
$form.Size = New-Object System.Drawing.Size(250,150)

#Création d'un label (Label).
$label1 = New-Object Windows.Forms.Label
$label1.Location = New-Object Drawing.Point 20,30
$label1.Size = New-Object Drawing.Point 150,16
$label1.text = "Sélectionner un fichier."

#Création d'une zone texte (TextBox).
$texte1 = New-Object Windows.Forms.TextBox
$texte1.Location = New-Object Drawing.Point 20,50
$texte1.Size = New-Object Drawing.Point 140,30
$texte1.Text = ""

#Création d'un bouton parcourir (Button + OpenFileDialog).
$bouton1 = New-Object Windows.Forms.Button
$bouton1.Location = New-Object Drawing.Point 160,49
$bouton1.Size = New-Object Drawing.Point 65,21
$bouton1.text = "Parcourir"
$bouton1.add_click({
                     #Création d'un objet "ouverture de fichier".
                     $ouvrir1 = New-Object System.Windows.Forms.OpenFileDialog

                     #Initialisation du chemin par défaut.
                     $ouvrir1.initialDirectory = "C:\"

                     #Ici on va afficher que les fichiers en ".txt".
                     $ouvrir1.filter = "TXT Files (*.txt)| *.txt"

                     #Affiche la fenêtre d'ouverture de fichier.
                     $retour1 = $ouvrir1.ShowDialog()

                     #Traitement du retour.
                     #Si "OK" on affiche le fichier sélectionné dans la TextBox.
                     #Sinon on afficher un fichier par défaut.
                     if ($retour1 -eq "OK") { $texte1.Text = $ouvrir1.filename }
                     else { $texte1.Text = "C:\jumbor12.txt" }
                  })

#Attache le contrôle à la fenêtre.
$form.controls.add($label1)
$form.controls.add($texte1)
$form.controls.add($bouton1)#Affiche le tout.
$form.ShowDialog()

#Fin.



Dans notre exemple, nous n'avons pas intégré la vérification de l'existence du fichier en cas de saisi manuel dans la TextBox. Pour faire cette vérification, nous pouvons utiliser la commande "Test-Path" :


$fichier1 = "C:\jumpor12.jpg"
$test1 = Test-Path $fichier1
if ($test1 -ne "True") { Write-Host "Fichier absent."  }
else                   { Write-Host "Fichier présent." }