samedi 22 juin 2024

MessageBox TopMost (Csharp)

Aujourd'hui je vous propose de résoudre le problème suivant : "MessageBox" ne pop pas au premier plan !
Pour résoudre ce problème, je propose d'utiliser un script en Csharp inclu dans le code Powershell.

Dans Powershell : Je définit un type personnalisé (Add-Type) pour y inclure mon code Csharp.

Dans CSharp : La fonction "MessageBox" permet de déclarer le handle de la fenêtre parent.
Je vais donc attacher mon "MessageBox" à une nouvelle fenêtre que je vais forcer au premier plan.

A noter :
Les MessagesBox utilisent la Dll "SystemWindowsForms.dll".
Il faut penser à la déclarer le chemin de la Dll avec l'option "-ReferencedAssemblies".

 
#######################################################################
#                                                                     #
# MessageBox TopMost - V1.0.0                                         #
#                                                                     #
# Par Olivier DELORME                                                 #
# Site https://powershell.sekki.fr                                    #
#                                                                     #
#######################################################################


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


#Csharp
try  { [void][Sekki.Csharp] }
catch{
       Add-Type -TypeDefinition @"
       using System;
       using System.Windows;
       using System.Windows.Forms;
       namespace Sekki
        {
          public class Csharp
           {
             public static void MessageBoxA(string message, string title, MessageBoxIcon iconbox)
              {
                MessageBox.Show(new Form(){TopMost=true, TopLevel=true}, message, title, MessageBoxButtons.OK, iconbox);
              }
             public static string MessageBoxB(string message, string title, MessageBoxButtons buttonbox, MessageBoxIcon iconbox)
              {
                DialogResult result;
                result = MessageBox.Show(new Form(){TopMost=true, TopLevel=true}, message, title, buttonbox, iconbox);
                if (result == System.Windows.Forms.DialogResult.Abort)    { return "Abort";   }
                if (result == System.Windows.Forms.DialogResult.Cancel)   { return "Cancel";  }
                if (result == System.Windows.Forms.DialogResult.Ignore)   { return "Ignore";  }
                if (result == System.Windows.Forms.DialogResult.No)       { return "No";      }
                if (result == System.Windows.Forms.DialogResult.OK)       { return "OK";      }
                if (result == System.Windows.Forms.DialogResult.Retry)    { return "Retry";   }
                if (result == System.Windows.Forms.DialogResult.Yes)      { return "Yes";     }
                return "None";
              }
           }
        }
"@ -Language CSharp -ReferencedAssemblies System.Windows.Forms
     }


#Exemple 1 :
$text = "Voulez-vous adopter un chat ?"
$BoutonBox = [Windows.Forms.MessageBoxButtons]::YesNo
$titre = "Sekki Powershell - Choix"
$IconBox = [windows.forms.MessageBoxIcon]::Question
[Sekki.Csharp]::MessageBoxB($text,$titre,$BoutonBox,$IconBox)


#Exemple 2 :
$text = "Attention chat adorable !"
$titre = "Sekki Powershell - Alerte"
$IconBox = [windows.forms.MessageBoxIcon]::Warning
[Sekki.Csharp]::MessageBoxA($text,$titre,$IconBox)

jeudi 30 mai 2024

Watermark

Ce script permet d'inserer un watermark sur l'ensemble des images "jpg" et "png" d'un dossier.
1 - Copier le script dans le dossier d'image à traiter (ex: WaterMark.ps1).
2 - Créer le fichier "Watermark.png" à incruster.
3 - Dans le script, configurer l'option de position du watermark.
4 - Executer le script Powershell.

 
#######################################################################
#                                                                     #
# Watermark - V1.0.0                                                  #
#                                                                     #
# Par Olivier DELORME                                                 #
# Site https://powershell.sekki.fr                                    #
#                                                                     #
#######################################################################


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


#Choisir la position :
#"Remplir", "En haut a gauche", "En haut a droite",
#"En bas a gauche", "En bas a droite", "Au centre".
$wmOption = "Remplir"


#Chemin courant.
$global:curPath = ""
if ($psISE) { $global:curPath = Split-Path -parent $psISE.CurrentFile.Fullpath }
else        { $global:curPath = Split-Path $MyInvocation.MyCommand.Path }


#Image model :
#1 - Créer une image avec un fond transparant.
#2 - Pour rendre l'image transluside, règler le paramêtre "Opacité" dans votre éditeur d'image.
#3 - Sauvegarder l'image sous le nom "watermark.png" (au format PNG).
$Watermark = $global:curpath + "\watermark.png"

#Verifier si le fichier "watermark.png" est présent dans le dossier.
if (!(Test-Path -path ($global:curpath + "\watermark.png"))) { Write-Host "Le fichier ""watermark.png"" est absent !!!"; return }

#Créer le dossier de destination pour les images marquées.
if (!(Test-Path -path ($global:curpath + "\Marked"))) { $rtn = New-Item -Path ($global:curpath + "\Marked") -ItemType directory -Force }


#Initialisation graphique.
$waterMk = New-Object System.Drawing.Bitmap($Watermark)
$srcRect = New-Object System.Drawing.Rectangle(0, 0, $waterMk.Width, $waterMk.Height)


#Traitement des fichiers dans le dossier du script.
$files = Get-ChildItem -Path $global:curpath | where {$_ -match "((.jpg)|(.png))$"}

#Verifier si il y a des fichiers à traiter.
if ($files.count -le 1)
  {
    Write-Host "Le script doit être dans le même dossier que les images à traiter."
    return
  }

#Début du traitement.
foreach($file in $files)
 {
   if ($file -notmatch "WaterMark")
     {
       $image = New-Object System.Drawing.Bitmap($file.fullname)
       $graph = [System.Drawing.Graphics]::FromImage($image)
       Switch ($wmOption)
        {
                   "Remplir" {
                               $dstRect = New-Object System.Drawing.Rectangle(0, 0, $image.Width, $image.Height)
                               $graph.DrawImage($waterMk,$dstRect,$srcRect,2)
                             }
          "En haut a gauche" { $graph.DrawImage($waterMk,0,0,$srcRect,2) }
          "En haut a droite" {
                               $dstRect = New-Object System.Drawing.Rectangle(($image.Width-$waterMk.Width), 0, $waterMk.Width, $waterMk.Height)
                               $graph.DrawImage($waterMk,$dstRect,$srcRect,2)
                             }
          "En bas a gauche" {
                               $dstRect = New-Object System.Drawing.Rectangle(0, ($image.Height-$waterMk.Height), $waterMk.Width, $waterMk.Height)
                               $graph.DrawImage($waterMk,$dstRect,$srcRect,2)
                             }
          "En bas a droite" {
                               $dstRect = New-Object System.Drawing.Rectangle(($image.Width-$waterMk.Width), ($image.Height-$waterMk.Height), $waterMk.Width, $waterMk.Height)
                               $graph.DrawImage($waterMk,$dstRect,$srcRect,2)
                             }
                "Au centre" {
                               $cx = [int](($image.Width-$waterMk.Width)/2)
                               $cy = [int](($image.Height-$waterMk.Height)/2)
                               $dstRect = New-Object System.Drawing.Rectangle($cx, $cy, $waterMk.Width, $waterMk.Height)
                                $graph.DrawImage($waterMk,$dstRect,$srcRect,2)
                            }
        }
       $image.Save($global:curpath + "\Marked\" + $file)
       Write-Host "Le fichier ""$file"" a été sauvegardé dans le dossier ""Marked""."
       $graph.Dispose()
       $image.Dispose()
     }
 }

#Fin.
$waterMk.Dispose()


Exemple :


jeudi 26 août 2021

Généralités VmWare

Nous allons voir ici les bases du module VmWare pour PowerShell (VMware.VimAutomation.Core). Cet article montre comment se connecter sur un serveur VmWare, obtenir des informations (comme la liste des VMs), et fermer la connexion.

 

#Variables PowerShell.
$ErrorActionPreference = "SilentlyContinue"   #Defaut = Continue
$ConfirmPreference     = "None"               #Defaut = High


#Import du module VmWare.
Import-Module -Name VMware.VimAutomation.Core -Scope Global
if ((Get-Module -Name VMware.VimAutomation.Core) -eq $null) { Return }


#Configuration du module VmWare : Supprimer l'alerte du certificat.
#Options : Fail, Ignore, Warn.
$null = Set-PowerCliConfiguration -InvalidCertificateAction Ignore

#Configuration du module VmWare : Désactive le CEIP.
#Options : $True, $False.
$null = Set-PowerCliConfiguration -ParticipateInCeip $false

#Configuration du module VmWare : Web Timout (défaut = 300s).
$null = Set-PowerCliConfiguration -WebOperationTimeoutSeconds 300

#Configuration du module VmWare : Proxy.
$null = Set-PowerCliConfiguration -ProxyPolicy NoProxy


#Initialisation.
$vSrv   = "monserveur.sekki.fr"   #Nom ou Ip du vCenter.
$vConn  = $null                   #Information de connection.
$Cred   = $null                   #Login et mot de passe pour la connection.
$delais = 500
$ping   = New-Object System.Net.NetworkInformation.Ping


#MessageBox.
function Alerte([string]$TEXT)
  {
    $MessageBox = [Windows.Forms.MessageBox]
    $BoutonBox = [Windows.Forms.MessageBoxButtons]::OK
    $IconBox = [windows.forms.MessageBoxIcon]::Warning
    $null = $MessageBox::show($TEXT,"MessageBox",$BoutonBox,$IconBox)
  }


#Enregistrer le login et mot de passe (Credential).
While ($Cred -eq $null)
 {
   $Cred = Get-Credential -Message "Login to VmWare."
 }


#Verifie si le serveur répond au ping.
Try   { $reply = $ping.Send($vSrv,$delais) }
Catch { Alerte "Le serveur ne repond pas."; Return }
if ($reply.Status -ne "Success") { Alerte "Le serveur ne repond pas."; Return }


#Connection au vCenter.
$vConn = Connect-VIServer -Server $vSrv -Credential $Cred -Port 443 -Protocol https -EA SilentlyContinue
if ($vConn -eq $null)
  {
    Alerte "Echec de la connection à VmWare."
    Return #Si on est pas connecté on sort.
  }


###### Get-VM ######


#Liste les VMs.
$VMs = Get-VM
$VMs | Out-GridView

#Liste les VMs allumées.
$VMs = (Get-VM).where{$_.PowerState -eq 'PoweredOn'}
$VMs | Out-GridView

#Liste les VMs commencant par.
$VMs = Get-VM -Name V*
$VMs | Out-GridView

#Liste les VMs Windows.
$VMs = (Get-VM).where{$_.Guest.OSFullName -match 'Windows'}
$VMs | Out-GridView


###### Get-VApp ######

$VAs = Get-VApp
$VAs | Out-GridView


###### Get-Host ######


#Liste les Hotes.
$Hosts = Get-VMHost
$Hosts | Out-GridView


###### Get-DataStore ######


#Liste les Datastores.
$DSs = Get-Datastore
$DSs | Out-GridView


#Liste les Datastores d'un hote en particulier.
$DSs = Get-VMHost -Name Adam | Get-Datastore
$DSs | Out-GridView

#Info d'un Datastore.
$DSs = Get-Datastore -Name DS_SEKKI_01
$DSs | Out-GridView


###### Get-SnapShot ######


#Liste les SnapShots
$SSs = (Get-VM | Get-Snapshot)
$SSs | Out-GridView


###### Get-DataCenter ######


#Liste les DataCenters
$DCs = Get-DataCenter
$DCs | Out-GridView

#Liste les VMs d'un DataCenter
$DCs = Get-DataCenter -Name PARIS | Get-VM
$DCs | Out-GridView

#Liste les Datastores d'un DataCenter
$DCs = Get-DataCenter -Name PARIS | Get-Datastore
$DCs | Out-GridView


###### End ######


Disconnect-VIServer -Server $vConn

vendredi 9 juillet 2021

PsToHtml

Permet de convertir un script PowerShell en Hlml pour ce Blogger.
1 - Ajoute les balises pour le bouton "code".
2 - Converti les espaces et retour chariot pour HTML.
3 - Ajoute les balises de fermeture.

Usage :
1 - Créer un fichier "in.txt".
2 - Coller le code PowerShell dans le fichier "in.txt".
3 - Exécuter le script.
4 - Copier le code Html généré dans le fichier "out.txt".
5 - Coller le code généré dans la page "Html" lors de la création de l'article.
 
 

##################################################################################
#                                                                                #
# Prepare le code Ps pour la publication Html                                    #
#                                                                                #
# Note : Le script Ps ne doit pas depacer 83 caractères en largeur (si possible).#
#                                                                                #
##################################################################################


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

#Fichier d'entrée / sortie.
$script = $curpath+"\in.txt"
$html   = $curpath+"\out.txt"

#Code de zone pour la copie.
$cpcode = "copy" + (date -f "yyMMddHHmmss")

#Ajoute de l'entête.
$top  = "<div id=""code"">`r`n"
$top += "<div style=""text-align: right;""><button id=""copy"" "
$top += "onclick=""CopyCode('#$($cpcode)')"">Copy</button> </div>`r`n"
$top += "<div id=""$($cpcode)"">`r`n"
$top += "<br />`r`n"

#Traitement des lignes.
$txt   = ""
$lines = Get-Content $script
Foreach ($line in $lines)
 {
   #Convertir les espaces.
   $line = $line.replace("  ","&nbsp;&nbsp;")
   #Ajoute la balise Br en fin de ligne.
   $line += "<br />`r`n"
   #Ajout au texte modifié.
   $txt += $line
 }

#Fermeture des Divs.
$bottom = "</div></div>`r`n"

#Enregistre le fichier.
$txt = $top + $txt + $bottom
Set-Content -Path $html -Value $txt

#Fin.

vendredi 11 juin 2021

WaitAndClick

Wait&Click est une boucle qui envoie un clique de souris à l'endroit souhaité après un temps d'attente.


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

#Variables.
$global:X = 0
$global:Y = 0
$global:Decompte = 0
$global:Attente = 20

################################################
# Types.
################################################

#Ajoute du type : Send mouse click (Left 02 + 04 / Right 08 + 10 / Middle 20 + 40).
$signSendClick = @'
[DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
'@
$global:sendClick = Add-Type -memberDefinition $signSendClick -name "Win32MouseEventNew" -namespace Win32Functions -passThru

################################################
# Timer.
################################################

#Timer.
$timer1 = New-Object System.Windows.forms.timer
$timer1.Interval = (1000) #Millisecondes.
$timer1.Add_Tick({
                   $global:Decompte--
                   $global:labelA1.Text = [string]$global:Decompte

                   if ($global:Decompte -eq 0)
                     {
                       #Position courante.
                       $positon = [System.Windows.Forms.Cursor]::Position

                       #Enregistre la position si elle n'est pas initialisée.
                       if (($global:X -eq 0) -and ($global:Y -eq 0)) { $global:X = $positon.X; $global:Y = $positon.Y; $global:labelA1.BackColor = "White" }

                       #Déplace la souris.
                       [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point($global:X, $global:Y)

                       #Envoie un clique gauche.
                       $global:sendClick::mouse_event(0x00000002, 0, 0, 0, 0)
                       $global:sendClick::mouse_event(0x00000004, 0, 0, 0, 0)

                       #Envoie un clique droit.
                       #$global:sendClick::mouse_event(0x00000008, 0, 0, 0, 0)
                       #$global:sendClick::mouse_event(0x00000010, 0, 0, 0, 0)

                       #Envoie un clique milieu.
                       #$global:sendClick::mouse_event(0x00000020, 0, 0, 0, 0)
                       #$global:sendClick::mouse_event(0x00000040, 0, 0, 0, 0)

                       #Envoie la touche "Enter".
                       #[System.Windows.Forms.SendKeys]::SendWait("TEST")
                       #[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")

                       #Replace la souris.
                       [System.Windows.Forms.Cursor]::Position = $positon

                       #Relance de temps d'attente.
                       $global:Decompte = $global:Attente
                     }
                })

################################################
# Fonctions.
################################################

#Affiche une boite de dialogue.
function InputBox([string]$TEXT)
  {
    Try { $i = [int]([Microsoft.VisualBasic.Interaction]::InputBox($TEXT, "Notice",$global:Attente)) }
    Catch { $i = 20 }
    if ($i -eq 0) { Exit } #Bouton Annuler.
    if ($i -lt 5) { $i = 5 }
    return $i
  }

################################################
# Main.
################################################

#Démarrage du programme.
$global:Attente = InputBox "1 - Entrez le temps d'attente (en secondes).`r`n`r`n2 - Fermer la fenêtre en cliquant sur OK.`r`n`r`n3 - Positionnez la souris à l'emplacement à cliquer, et attendez la fin du premier décompte pour enregistre la position.`r`n`r`n"
$global:Decompte = $global:Attente

#Création d'une fenêtre.
$formA = New-Object Windows.Forms.Form
$formA.ClientSize = New-Object System.Drawing.Size(120,100)
$formA.MaximizeBox = 0
$formA.MinimizeBox = 0
$formA.ShowIcon = 0
$formA.FormBorderStyle = "Fixed3D"
$formA.text = "Wait&Click"
$formA.TopMost = 1 #Récupère le jeton TopMost.
$formA.ShowInTaskbar = 0
$formA.BackColor = "White"

#Création d'un label.
$global:labelA1 = New-Object Windows.Forms.Label
$global:labelA1.Location = New-Object Drawing.Point 0,0
$global:labelA1.Size = New-Object System.Drawing.Size 125,100
$global:labelA1.Font = New-Object System.Drawing.Font("Verdana","50",[System.Drawing.FontStyle]::Regular,3,0)
$global:labelA1.BackColor = [System.Drawing.Color]::FromArgb(255,248,203,47)
$global:labelA1.TextAlign = "MiddleCenter"
$global:labelA1.Text = "--"

#Attache le contrôle à la fenêtre.
$formA.controls.add($global:labelA1)

#Lance le timer.
$timer1.start()

#Affiche la fenêtre.
[void]$formA.ShowDialog()

#Fin.
$timer1.Stop()

vendredi 28 mai 2021

PowerShell et LiteDB v5

Nous allons voir dans cet article l'utilisation de la base de données LiteDB v5 dans un environnement Windows PowerShell. La nouvelle version LiteDB v5, présenté ici, tourne dans l'environnement FrameWork 4.5. 

Attention :
Les scripts PowerShell écrits pour la version LiteDB v3 ne sont pas compatible avec la version v5. Il n'est donc pas possible de simplement remplacer la Dll "LiteDB.Dll v3" par la Dll "LiteDB.Dll v5".

Référence


Toujours à la recherche d'une base de données légère pour mes scriptes PowerShell, j'ai découvert LiteDB. Cette base de données "sans serveur" se présente 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 4.5 (ou compatible) soit installé sur le système.

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

Notice :
- Ouvrir le site de Litedb.org.
- Cliquer sur "Download".
- Cliquer sur "Download package" (à droite).
- Ouvrir le dossier de téléchargement.
- Ouvrir le fichier "litedb.5.0.10.nupkg" avec WinZip (ou équivalent).
- Récupérer les fichiers "LiteDB.dll" et "LiteDB.xml" qui se trouvent dans le dossier "net45".

Exemple


Dans l'exemple ci-dessous, j'utilise la version LiteDB 5.0.10.

 
################################################################
#
#               Base de données : LiteBD 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 }

###Fonctions personnalisées.
#Renvoi un nombre entre 0 et 100.
function GR() { Get-Random -minimum 0 -maximum 100 }

###Fonctions personnalisées.
#Converti le résultat de la query pour l'afficher.
function BsonToObj($bsn)
 {
   #Conversion avec la version (Modifié pour LiteDB 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.
$coll = $db.GetCollection("Info")

#Créer un document Bson.
$bson = New-Object LiteDB.BsonDocument

#Créer 10 enregistrements.
for($i=0;$i -le 10;$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
   [void]$coll.Insert($bson)   #Insére le document dans la collection.
 }

#Afficher une requête.
$query = $coll.Find([LiteDB.Query]::all())
$query | %{BsonToObj $_} | Out-GridView

################################################################
### Modifier / Supprimer.
################################################################

#Modifier un enregistrement.
$query = $coll.Find([LiteDB.Query]::eq("Nom","Sekki"))
foreach ($q in $query)
 {
   $q["Cat"] = "A"
   [void]$coll.Update($q)
 }

#Supprimer un enregistrement : Méthode 1.
$query = $coll.Find([LiteDB.Query]::eq("Nom","Sekki"))
foreach ($q in $query)
 {
   $coll.Delete($q["_id"])
 }

#Supprimer un enregistrement : Méthode 2 (Modifié pour LiteDB v5).
$nb = $coll.DeleteMany([LiteDB.Query]::eq("Nom","Jumbor"))

################################################################
### Les requêtes.
################################################################

#Renvoi les articles dont le nom exact est :
$query = $coll.Find([LiteDB.Query]::eq("Nom","Sekki"))

#Renvoi les articles dont le prix est plus grand que :
$query = $coll.Find([LiteDB.Query]::gt("Prix",30))

#Renvoi les articles dont le prix est plus petit que :
$query = $coll.Find([LiteDB.Query]::lt("Prix",30))

#Renvoi les articles dont le nom commence par :
$query = $coll.Find([LiteDB.Query]::startswith("Nom","S"))

#Renvoi tout les enregistrements :
$query = $coll.Find([LiteDB.Query]::all())

#Afficher une requête.
$query = $coll.Find([LiteDB.Query]::all())
$query | %{BsonToObj $_} | Out-GridView

################################################################
### Les fichiers (Modifié pour LiteDB v5).
################################################################

###Charger des fichiers dans la base de données.
#Insérer/remplacer un fichier dans la base de données :
$db.FileStorage.Upload("$/Fichiers/file1.jpg", $curpath+"\file1.jpg")

#Lire un fichier dans la base de données et l'enregistrer sur le disque local :
$file = $db.FileStorage.FindById("$/Fichiers/file1.jpg")
$file.SaveAs($curpath+"\file2.jpg")

#Lire un fichier dans la base de données et le charger dans une variable :
$file = $db.FileStorage.FindById("$/Fichiers/file1.jpg")
$image = [System.Drawing.Image]::FromStream($file.OpenRead())

#Supprimer un fichier dans la base de données :
$db.FileStorage.Delete("$/Fichiers/file1.jpg")

################################################################

#Pause
if (!$psISE) { Write-Host -NoNewLine "Press any key to continue..."
               $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") }

#Compresser la base de données (Modifié pour LiteDB v5).
[void]$db.Rebuild()

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

#Fin.

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