jeudi 18 décembre 2014

Console avec minuteur (timer)

Nous allons voir dans cet article deux méthodes pour créer un timer et afficher le résultat d'une commande à intervalle régulier. Dans les exemples suivants, je vais afficher le résultat de la commande "Get-Process" toutes les 5 secondes. Mon premier script montre comment créer un timer dans une console PowerShell.

Exemple 1


 
#Création d'un timer.
$timer = new-object timers.timer
$timer.Interval = 1000 #Démarrage.
$nom = "T1"            #Identifiant de mon timer.
$boucle = 5            #Arrête le timer après 5 exécutions.

#Action.
$action = {
            #Initialisation.
            $timer.Interval = 5000  #En milliseconde.
            #Action.
            Write-Host (Get-Process | Out-String)
            #Supprimer le timer.
            if (--$boucle -eq 0)
              {
                $timer.stop()
                Unregister-Event $nom
                Write-host "Timer" $Nom "supprimé."
              }
          }

#Lancement du timer.
Register-ObjectEvent -InputObject $timer -EventName elapsed –SourceIdentifier $nom -Action $action
$timer.start()

#Fin.


Exemple 2


Le script suivant est une solution alternatif. Il  utilise le timer des fenêtres (Forms) pour afficher toutes les 5 secondes le résultat de la commande "Get-Process".

 
######################
# Console avec timer
######################

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

#Plugin.
Get-Command -PSSnapin VirtualMachineManagerSnapin

#Paramêtres.
$global:consw = 600      #Largeur de la console au démarrage.
$global:consh = 300      #Hauteur de la console au démarrage.
$global:intvl = 5        #Intervale d'execution du timer (en seconde).
$global:titre = "Console avec timer"  #Titre de la fenêtre.

###########
# Timers
###########

#Timer.
$timer1 = new-object System.Windows.forms.timer
$timer1.Interval = 1000      #Démarrage rapide.

#Action.
$timer1.Add_Tick({
                   #Initialisation.
                   $global:labelA2.text = "Scan en cours ..."
                   #Intervalle après démarrage.
                   $timer1.Interval = ($global:intvl * 1000)
                   #Commande.
                   $global:labelA1.text = (Get-Process | Out-String)
                   #Fin.
                   $global:labelA2.text = "Dernier scan le " + (date -Format "dd-MM-yyyy à HH:mm:ss")
                })

#########################
# Fenêtre style console
#########################

#Ouvre une nouvelle fenêtre.
$formA = New-Object Windows.Forms.Form
$formA.text = $global:titre          
$formA.Size = New-Object System.Drawing.Size($global:consw,$global:consh)
$formA.BackColor = [System.Drawing.Color]::FromArgb(255,1,36,86)

#Création d'un label (Label).
$global:labelA1 = New-Object Windows.Forms.Label
$global:labelA1.Location = New-Object Drawing.Point 9,0
$global:labelA1.Size = New-Object System.Drawing.Size($formA.Size.Width,$formA.Size.Height)
$global:labelA1.ForeColor = [System.Drawing.Color]::FromArgb(255,255,255,255)
$global:labelA1.Font = New-Object System.Drawing.Font("Consolas","9",[System.Drawing.FontStyle]::Regular,3,0)
$global:labelA1.text = ""

#Création d'un label (Label).
$global:labelA2 = New-Object Windows.Forms.Label
$global:labelA2.Location = New-Object Drawing.Point 0,(($formA.Size.Height) - 56)
$global:labelA2.Size = New-Object System.Drawing.Size(($formA.Size.Width),20)
$global:labelA2.ForeColor = [System.Drawing.Color]::FromArgb(255,0,0,0)
$global:labelA2.BackColor = [System.Drawing.Color]::FromArgb(255,222,222,222)
$global:labelA2.Font = New-Object System.Drawing.Font("Consolas","9",[System.Drawing.FontStyle]::Regular,3,0)
$global:labelA2.TextAlign = "MiddleLeft"
$global:labelA2.text = "Initialisation ..."

#Events.
$formA.Add_Resize({
                    $global:labelA1.Size = New-Object System.Drawing.Size($formA.Size.Width,$formA.Size.Height)
                    $global:labelA2.Location = New-Object Drawing.Point 0,(($formA.Size.Height) - 56)
                    $global:labelA2.Size = New-Object System.Drawing.Size(($formA.Size.Width),20)
                 })

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

#Lance le timer.
$timer1.Start()

#Affiche le tout.
$formA.ShowDialog()

#Libère le timer.
$timer1.Stop()

#Fin.

jeudi 30 octobre 2014

Liste des groupes d'un utilisateur (AD).

(NDA : Malheureusement cet article est un peu obsolète aujourd'hui. Je vais préparer un nouvel article avec le résumé des commandes AD actuelles.)

Il existe différentes façons d'interroger l'Active Directory en PowerShell. Dans cet article, j'utilise l'extension de commande de Quest pour manipuler l'AD.

Edit : A l’époque ou j'ai rédigé cet article, Quest était une très bonne alternative aux commandes natives disponibles sous PowerShell. Evidement, aujourd'hui en 2018 les systèmes Windows Serveurs AD intègre cela très bien sans avoir à passer par cet extension.

Installation


1) Allez sur le site de Quest "www.quest.com/powershell/activeroles-server.aspx"
2) Cliquez sur la case à cocher "I have read and accept the agreement".
3) Téléchargez la dernière version de "ActiveRoles Management Shell for AD".
4) Et installez l'extension.
(La documentation pdf est plutôt bien fait. N'hésitez pas à la consulter.)

Liste des groupes d'un utilisateur (méthode alternatif)


Normalement Quest propose la fonction "Get-QADMemberOf" pour réaliser cette action. Malheureusement lorsque cette commande rencontre un groupe d'un domaine qui ne vous est pas accessible, elle s'arrête avec une erreur et ne retourne rien. Si vous rencontrez ce problème, je vous propose de passer par le script suivant :

#Chargement des commandes Quest.
Add-psSnapin quest.activeroles.admanagement

#Compte de connexion à l'AD.
$username = "DOMAINE\login.admin"

#Invite de saisi du mot de passe.
$password = read-host ("Password pour "+$username) -assecurestring

#Credential.
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password

#Connexion à l'AD
$AD1 = Connect-QADService -service "domain.sekki.fr" -Credential $cred

#Affiche les groupes de l'utilisateur au format "Domain\Groupe".
$user = "DOMAINE\login.user"
Get-QADUser $user | foreach {
            foreach ($mof in $_.MemberOf)
             {
               #Initialise.
               $grp = "";$dom = "";
               #Converti les données séparées par une virgule en tableau.
               $tmof = $mof.split(",")
               #Recupère le nom du groupe.
               foreach($e in $tmof) { if ($e -like "CN=*") {$grp = $e;break} }
               #Récupère le nom du domaine du groupe.
               foreach($e in $tmof) { if ($e -like "DC=*") {$dom = $e;break} }
               #Supprime les infos initules.
               $grp = $grp.Replace("CN=","")
               $dom = $dom.Replace("DC=","")
               #Recompose le nom du groupe au format "Domain\Groupe".
               $groupe = $dom+"\"+$grp
               #Affiche le nom du groupe.
               write-host $groupe
             }
                           }

#Déconnexion de l'AD.
Disconnect-QADService -connection $AD1

#Fin.

dimanche 12 octobre 2014

Annonce - 10000 Hits !

Bonjour,

Aujourd'hui j'ai atteint les 10000 visites sur mon blog. Les articles les plus visités sont les fichiers, les listes déroulantes, les partages et les listes d'accès. Et les visiteurs viennent principalement de France bien sur, mais aussi des Etats-Unis, Belgique, Allemagne, Suisse, Canada et Royaume-Unis. Merci à vous tous, et j'espère que ce blog vous a aidé dans votre travail.

A bientôt !
———————————————————————————————————————————

Photo - 10000 Hits !
———————————————————————————————————————————
Hi,

Today
I reached 10000 visite on my blog. The most visited post are files, combobox, shares and accès list. And most of the visitors come from France of course, but also United-States, Belgium, Germany, Switzerland, Canada, United-Kingdom. Thank you very much to all visitors. I hope this blog has helped you in your work.

See you soon !


mardi 12 août 2014

Canaux nommés (Named Pipe).

Le canal nommé (Named Pipe) permet de créer un canal de communication entre deux applications. La première application, de type "serveur", va crée le "Pipe". Ceci va permettre à un ou plusieurs clients de se connecter à l'application serveur. La seconde application va récupérer les informations de la première application via le Pipe.

Dans l'exemple suivant, je vais exploiter l'un des avantages du canal nommé en mode duplex. La propriété duplex permet au client et au serveur d'envoyer et de recevoir des informations, permettant ainsi aux deux applications de dialoguer entre elles.

Application serveur.


Le premier script fera office de serveur et il tournera dans son propre environnement PowerShell. Son rôle sera de créer le canal nommé, d'attendre les informations du client et de retourner une réponse. Bien entendu, ce script devra être exécuté en premier.


 
[Reflection.Assembly]::LoadWithPartialName("system.core")

#Ouverture du pipe en mode duplex.
Write-Host "Démarrage du serveur."
$pipeDir = [System.IO.Pipes.PipeDirection]::InOut
$pipe = new-object System.IO.Pipes.NamedPipeServerStream("\\.\pipe\jumbor12",$pipeDir)
$pipe.WaitForConnection()

#Déclaration des objets pour la lecture/écriture dans le pipe.
$sr = new-object System.IO.StreamReader($pipe)
$sw = new-object System.IO.StreamWriter($pipe)
$sw.AutoFlush = $true

#Ecoute du client.
while (($line = $sr.ReadLine()) -ne "Exit")
 {
   #Réponses.
   Write-Host $line
   if ($line -eq "Hello !") { $sw.WriteLine("Bonjour !") }
   if ($line -eq "Bye !")   { $sw.WriteLine("Bonsoir !") }
 }

#Dispose.
Write-Host "Arrêt du serveur."
$sw.Dispose()
$pipe.Dispose()

#Fin.


Application client.


Le second script sera le client. Il va se connecter au canal nommé du serveur et l'interroger. Il attendre la réponse du serveur, et l'affichera dans la console. Ce script doit être lancé depuis un nouvel environnement PowerShell. Et bien entendu devra être exécuté en même temps que le premier script. Sinon ça ne marchera pas ^^.


 
[Reflection.Assembly]::LoadWithPartialName("system.core")

#Connexion au pipe du serveur.
Write-host "Démarrage du client."
$timeout = 5000 #5 secondes
$pipe = new-object System.IO.Pipes.NamedPipeClientStream("\\.\pipe\jumbor12")
$pipe.Connect($timeout)
if (!$pipe.IsConnected)
  {
    Write-Host "Merci de vérifier si le serveur est lancé."
    Return
  }
 
#Déclaration des objets pour la lecture/écriture dans le pipe.
$sr = new-object System.IO.StreamReader($pipe)
$sw = new-object System.IO.StreamWriter($pipe)
$sw.AutoFlush = $true

#Interroger le serveur.
$sw.WriteLine("Hello !")

#Attendre la réponse.
Write-Host $sr.ReadLine()

#Interroger le serveur.
$sw.WriteLine("Bye !")

#Attendre la réponse.
Write-Host $sr.ReadLine()

#Arrêter le serveur.
$sw.WriteLine("Exit")

#Dispose.
Write-Host "Arrêt du client."
$sw.Dispose()
$pipe.Dispose()

#Fin.

jeudi 3 juillet 2014

Lire un fichier ouvert

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

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

Exemple


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

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

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

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

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

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

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

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

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

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

#Affiche le tout.
$form.ShowDialog()

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

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

#Fin.

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

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

mercredi 18 décembre 2013

PowerShell et MsSQL

Dans cet article, nous allons voir l’accès aux bases MsSQL avec Windows PowerShell. J'ai choisi de vous présenter ici une fonction qui permet d’exécuter des requêtes sur une base MsSQL. L'exemple suivant montre comment se connecter sur une base MsSQL, et utilisation des instructions Create Table, Insert, Query, Update et Drop Table.

Exemple


Avant d’exécuter le script, notez que j'ai créé au préalable une base de données "Base1".  Les tables et les données peuvent être créé ensuite avec les exemples de requêtes SQL présent dans le script.


 
#Les requêtes SQL en PowerShell.
Function Sql_Query([string] $sqServer, [string] $sqBase, [string] $sqQuery)
 {
   #Etablit la connexion avec la base SQL.
   $Connection = New-Object System.Data.SqlClient.SqlConnection
   $Connection.ConnectionString = "Server = $sqServer; Database = $sqBase; Integrated Security = True"

   #Crée une commande SQL.
   $sqlCmd = New-Object System.Data.SqlClient.SqlCommand
   $sqlCmd.CommandTimeout = 30
   $sqlCmd.CommandText = $sqQuery
   $sqlCmd.Connection = $Connection

   #Exécute et récupère le résultat.
   $DataSet = New-Object System.Data.DataSet
   $Adapter = New-Object System.Data.SqlClient.SqlDataAdapter
   $Adapter.SelectCommand = $sqlCmd
   $Adapter.Fill($DataSet)

   #Ferme la connexion avec la base SQL.
   $Connection.Close()

   #Renvoie le résultat.
   return $DataSet.Tables[0]
 }

#Paramètres de connexion.
$SQLSERVER = "."                #Le "." représente le poste local.
$SQLBASE = "Base1"              #Nom de la base SQL.
$sqlQuery = "SELECT @@VERSION"  #Requête par défaut.

#-------------------------------------------------------------------------#
#Voici la liste de différentes requêtes SQL que vous pouvez essayer.
#Supprimez le # devant la requête SQL que vous souhaitez tester.
#-------------------------------------------------------------------------#
#Créer une table.
#$sqlQuery = "CREATE TABLE [dbo].[Table1]([Colonne1] [nchar](20) NULL, [Colonne2] [nchar](20) NULL) ON [PRIMARY]"
#Ajouter une ligne.
#$SqlQuery = "INSERT INTO [Base1].[dbo].[Table1]([Colonne1],[Colonne2]) VALUES ('Jumbor12','PowerShell')"
#Lire une table.
#$SqlQuery = "select * from [Table1]"
#Lire une ligne.
#$SqlQuery = "select * from [Table1] WHERE [Colonne1] = 'Jumbor12'"
#Modifier une valeur.
#$SqlQuery = "UPDATE [Base1].[dbo].[Table1] SET [Colonne2] = 'PowerShell 2.0' WHERE [Colonne2] = 'PowerShell'"
#Supprimer une ligne.
#$SqlQuery = "DELETE FROM [Base1].[dbo].[Table1] WHERE [Colonne1] = 'Jumbor12'"
#Supprimer une table.
#$sqlQuery = "DROP TABLE [dbo].[Table1]"
#-------------------------------------------------------------------------#

#Affiche la requête exécutée.
Write-Host $sqlQuery

#Exécute la requête.
$rtn = Sql_Query $SQLSERVER $SQLBASE $sqlQuery
Foreach ($raw in $rtn)
 {
   #Ici j'affiche le retour pour les requêtes de lecture de données.
   Try { Write-Host "name :" $raw[0] $raw[1] } catch {}
 }
 Write-Host "Exécution terminé."

#Fin.