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." }