DEV Community

chris
chris

Posted on

Powershell (quand on ne connaît que Linux)

Powershell est une programme de ligne de commande qui marche sur différentes plateformes mais est essentiellement utilisée sur Windows. C'est codé en .NET (prononcé: dot net) un framework pour C# (prononcé: si charp) et donc répond a certains concepts de programmation orientée objet comme... les (surprise) objets.

En gros, chaque fonction que l'on verra reverra comme résultat une structure sur laquelle on pourra agir. Contrairement à Bash (voir articles précédents 1 et 2) qui nous retourne plutôt des listes ou des variables.

Si vous ne connaissez pas les concepts de programmation orientée objet, ce n'est pas grave. Dans cet article, nous allons surtout essayer de refaire les exercices des deux premiers articles en Powershell et nous n'aborderons pas des concepts trop avancés.

Powershell est bien calibré pour les environnements Windows, ce programme vous permet non seulement de travailler avec du texte et des fichiers mais aussi de gérer votre ordinateur... Scripter avec Powershell est un vrai atout lorsque vous travailler sur ce type de machines.

Si vous voulez plus d'informations, la documentation de Microsoft est plutôt extensive.

Bases

Si vous utilisez Windows 10 ou plus, Powershell devrait déjà être installé et vous pouvez trouver la ligne de commande en cherchant "powershell" dans vos programmes.

Alt Text

Si vous utilisez Linux et que faire des trucs comme ça n'hérisse pas votre poil de libriste, il y a moyen d'installer Powershell sur votre machine ! Même via Snap, si ce n'est pas beau tout ça !

Voyons quelques fonctions de base :

Linux (Bash) Powershell Options
cat Get-Content -Path, -Tail, -Head
pwd Get-Location
cd Set-Location (or cd)
ls Get-ChildItem (or dir) -Path, -File, -Directory, -Filter, -Recurse, -Hidden, -ErrorAction SilentlyContinue
cp Copy-Item -Path, -Destination
mv Move-Item -Path, -Destination
mkdir New-Item -ItemType, -Name
wc Measure-Object -word
man Get-Help
wget Invoke-WebRequest -Uri, -OutFile

Les majuscules ne sont pas obligatoire, Get-Content et get-content sont équivalents. Cependant, n'hésitez jamais à perdre un peu de rapidité à la frappe pour une meilleure lecture.

Ok, eh bien, réimplémentons les exercices des premiers articles en Powershell !

Mouvements

Trouvons où nous sommes dans l'arbre (pwd) :

Get-Location
Enter fullscreen mode Exit fullscreen mode

L'équivalent de / sous Linux est C:\ sous Windows.

Déplaçons-nous :

Set-Location C:\
Enter fullscreen mode Exit fullscreen mode

Ou si vous êtes sous Linux avec Powershell :

Set-Location /
Enter fullscreen mode Exit fullscreen mode

Lancer Set-Location sans arguments est comme lancer cd tout seul, il vous amènera au dossier de base de votre compte (C:\Users\chris ou /home/chris en fonction de votre système).

Lister le contenu d'un dossier :

dir
# ou bien
Get-ChildItem
Enter fullscreen mode Exit fullscreen mode

Jouez avec les options indiquées sur le table, par example que font les fonctions suivantes ?

Get-ChildItem -File
Get-ChildItem -Directory
Get-ChildItem -Directory -Recurse
Enter fullscreen mode Exit fullscreen mode

Création de fichiers et de dossiers

Pour la création de fichiers et de dossiers, c'est même plus simple qu'avec
Linux, puisqu'il nous suffira d'utiliser une seule fonction : New-Item (nouvel
object).

Pour un fichier :

New-Item -ItemType file -Name pouet
Enter fullscreen mode Exit fullscreen mode

Donnera :

    Directory: /home/chris/Documents/blog

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
----------          27/01/2021    16:07              0 pouet
Enter fullscreen mode Exit fullscreen mode

Et pour un dossier :

New-item -ItemType directory -Name dirdir
Enter fullscreen mode Exit fullscreen mode

Résultat :

    Directory: /home/chris/Documents/blog

Mode                 LastWriteTime         Length Name
---------                 -------------         ------ ----
d----          27/01/2021    16:07                dirdir
Enter fullscreen mode Exit fullscreen mode

Déplaçons le fichier pouet dans dirdir :

# Windows
Move-Item -Path pouet -Destination dirdir\pouet

# Linux
Move-Item -Path pouet -Destination dirdir/pouet
Enter fullscreen mode Exit fullscreen mode

Que les choses sérieuses commencent !

Téléchargeons notre premier fichier :

Invoke-Webrequest -Uri "https://archive.org/stream/Frankenstein1818Edition/frank-a5_djvu.txt" -OutFile texte_complet.txt
Enter fullscreen mode Exit fullscreen mode

Normalement, ici nous utiliserions grep. Sauf qu'en Powershell ça n'existe par vraiment. Il faudra utiliser Where-Object si vous recherchez des objets retournés par une autre fonction Powershell (comme Get-Process) ou bien Select-String quand vous travaillez avec des chaînes de caractères (des strings en anglais).

Donc :

Select-String -Path texte_complet.txt -Pattern "love"
Enter fullscreen mode Exit fullscreen mode

Vous remarquez que le résultat est plus précis par défaut que la version par défaut de grep. En effet, sans devoir le préciser, Select-String indique le numéro de la ligne ainsi que le fichier où la résultat a été trouvé.

Et la version plus précise :

Select-String -Path texte_complet.txt -Pattern " love "
Enter fullscreen mode Exit fullscreen mode

Pour compter le nombre d'occurrences :

Select-String -Path texte_complet.txt -Pattern " love " | Measure-Object -Word
Enter fullscreen mode Exit fullscreen mode

Ok, super. Passons à la liste de mots de passe :

Invoke-WebRequest -Uri "https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/10k-most-common.txt" -OutFile liste_mdp.txt
Enter fullscreen mode Exit fullscreen mode

Pour trier de façon unique, nous utiliserons la même logique sur Linux : afficher le texte et trier le résultat.

Get-Content liste_mdp.txt | Sort-Object -Unique
Enter fullscreen mode Exit fullscreen mode

Et créons un fichier d'un coup

Get-Content liste_mdp.txt | Sort-Object -Unique > nouvelle_liste.txt
Enter fullscreen mode Exit fullscreen mode

Et voilà le travail !

Fonctions plus avancées

Passons maintenant au second article, et réessayons de refaire tout cela en Powershell !

Alt Text

Cette instruction est parfaitement valide en Powershell :

echo 'Robert;Dupont;rue du Verger, 12;
"Michel";"Durand";" av. de la Ferme, 89 ";
"Michel ""Michele""";"Durand";" av. de la Ferme, 89";
"Michel;Michele";"Durand";"av. de la Ferme, 89";
' > exemple.csv
Enter fullscreen mode Exit fullscreen mode

Maintenant, les choses se compliquent. Powershell n'a pas vraiment d'équivalent 1 à 1 pour cut (voir cette réponse sur stackoverflow -- en anglais). Il y a plusieurs solutions sur Internet, nous allons prendre une qui parait un peu moins simple mais qui pourrait être utile dans le futur.

Get-Content exemple.csv | ForEach-Object {$_.Split(";")[1]}
Enter fullscreen mode Exit fullscreen mode

Que se passe-t-il ici ? La première partie avant le | récupère le contenu de exemple.csv et l'envoie via | à la fonction suivante. ForEach-Object est une fonction qui va nous permettre de travail sur un objet énumérable.

En programmation, un énumérable est une structure, ordonnée ou non qui contient multiples valeurs, dès fois de type différent mais pas nécessairement. Par exemple, un dictionnaire en Python : [1, "un", True]. Retenez que ce type de structure contient multiple valeurs et peut donc être passée dans une boucle qui va... (vous le devinez) l'énumérer.

Ensuite, $_ est une variable, ce type de variable et de notation est assez commun en Powershell lorsqu'on utilise des pipes (|). Donc ici $_ fait référence à exemple.csv.

La fonction split (assez commune, elle existe en Ruby et en Python entre autres), va couper la ligne à un délimiteur donné ici ;.

[1] va prendre la... seconde occurrence après le délimiteur !

Attention, les ordinateurs comptent à partir de 0. Donc pour prendre le premier objet après le split, ça sera [0] et non [1].

Donc, nous aurons comme résultat :

PS /home/chris/Documents/blog/dirdir> Get-Content exemple.csv | ForEach-Object {$_.Split(";")[1]}
Dupont
"Durand"
"Durand"
Michele"
Enter fullscreen mode Exit fullscreen mode

Essayez de jouer avec les délimiteurs et les index ([1], [0]). Par exemple que donne l'index [-2] (Get-Content exemple.csv | ForEach-Object {$_.split(";")[-2]}) ?

Variables

Pour déclarer une variable en Powershell, $ sera aussi nécessaire pour la
déclaration. Donc :

$nombre_deux=2
echo $nombre_deux
Enter fullscreen mode Exit fullscreen mode

Et la réassignation fonctionne aussi de la même manière :

$nombre_deux=2
echo $nombre_deux
Enter fullscreen mode Exit fullscreen mode

Résultat : 2

$nombre_deux=3
echo $nombre_deux
Enter fullscreen mode Exit fullscreen mode

Résultat : 3

boucles

En ce qui concerne les boucles, nous avons déjà vu la fonction ForEach-Object qui fonctionne avec des objets, donc des structures assez spécifiques. Il existe un autre manière de faire des boucles avec ForEach.

ForEach ($ligne in Get-Content exemple.csv) {echo $ligne}
Enter fullscreen mode Exit fullscreen mode

Pour plus d'information sur les boucles en Powershell, la documentation de Microsoft est assez bien écrite.

Vous vous souvenez de cette ligne cryptique ?

somme=0; for i in $(seq 0 100); do somme=$(expr $somme + $i); done; echo $somme
Enter fullscreen mode Exit fullscreen mode

Essayons de le faire en Powershell !

$global:somme=0; ForEach ($i in 0..100) {$somme = $somme + $i}; echo $somme
Enter fullscreen mode Exit fullscreen mode

Pourquoi global devant la déclaration de la variable somme ? Parce que les variables en Powershell on un scope.

Revenons à la notion de bloc mentionnée plus haut.

Exemple 1 :

function {
    bloc
}
Enter fullscreen mode Exit fullscreen mode

Exemple 2 :

if condition {
    bloc
} else if condition 2 {
    bloc 2
} else {
    bloc 3
}
Enter fullscreen mode Exit fullscreen mode

Un bloc est une instruction ou une suite d'instructions de code qui se trouvent à l'intérieur d'une fonction (que ça soit une fonction en tant que telle ou dans une branche d'une fonction conditionnelle comme un if). En fonction des langages de programmation, les variables peuvent voyager entre les blocs ou non.

Dans le cas ou ce n'est pas possible, on parle de variable "locale" (à savoir locales au bloc) ou globale (globales au programme entier).

Donc ici, Powershell a besoin qu'on définisse la variable $somme comme globale afin de réaliser la dernière instruction echo $somme qui se trouve à l'extérieur du bloc de la fonction ForEach.

Si nous ne le faisons pas, $somme à la fin de l'instruction sera égal à 0. En effet, $somme dans le bloc de la fonction ForEach est une variable locale et donc n'est pas la même que la variable $somme définie en-dehors de la fonction.

À part cela, seq 0 100 a son équivalent 0..100 et pas besoin d'utiliser expr pour réaliser une fonction arithmétique.

Cas pratique : Fouiller une page web

Allons-y, Alonso !

Alt Text

Invoke-WebRequest -Uri "https://en.wikipedia.org/wiki/Hedy_Lamarr" -OutFile page.html
Enter fullscreen mode Exit fullscreen mode

Ensuite :

Select-String -Path page.html -Pattern 'href=\"/wiki/'
Enter fullscreen mode Exit fullscreen mode

Ce coup-ci, on utilisera la pleine puissance de Select-String plutôt que de le faire passer dans différents processus. De plus, nous avons déjà récupéré la liste des liens internes de la page qui commencent avec /wiki/.

Note sur les regex : Select-String peut parfaitement utiliser des regex pour des recherches avancées. Par exemple : Select-String -Path page.html -Pattern '\w'. Voir l'article sur les regex pour plus d'informations sur ce type d'instructions.

Cependant, la liste n'est pas "propre", il est encore difficile de travailler avec car les objets retournés par la fonction Select-String ne nous permettent pas de travailler dessus directement. Du coup, nous allons préciser notre
recherche et puiser dans les ressources de Powershell.

Nous devons d'abord récupérer la liste des chaînes de caractères qui commencent par href="/wiki et qui se terminent par un espace. En regex, ça donne : href=\"/wiki/\S+ :

Select-String -Path page.html -Pattern 'href=\"/wiki/\S+'
Enter fullscreen mode Exit fullscreen mode

Rappel : \S tout caractère sauf espace (donc, alphanumérique et spéciaux) et + cherchera de une à infini nombre d'occurrences.

Résultat :

(...)
/home/chris/Documents/blog/page.html:1250:  <li id="footer-places-about"><a href="/wiki/Wikipedia:About"
title="Wikipedia:About">About Wikipedia</a></li>
/home/chris/Documents/blog/page.html:1251:  <li id="footer-places-disclaimer"><a href="/wiki/Wikipedia:General_disclaimer"
title="Wikipedia:General disclaimer">Disclaimers</a></li>
Enter fullscreen mode Exit fullscreen mode

Nous allons maintenant itérer sur chaque objet de notre recherche

Select-String -Path page.html -Pattern 'href=\"/wiki/\S+' | ForEach-Object {$_.Matches}
Enter fullscreen mode Exit fullscreen mode

Résultat :

(...)
Groups   : {0}
Success  : True
Name     : 0
Captures : {0}
Index    : 38
Length   : 41
Value    : href="/wiki/Wikipedia:General_disclaimer"
Enter fullscreen mode Exit fullscreen mode

Voilà quelque chose d'intéressant ! Ce que nous voyons, ce sont les objets résultat de la recherche. Donc en réitérant dessus, nous pourrons récupérer simplement la valeur de ces recherches avec les champs .Value pour l'objet .Match qui appartient à .Matches.

Select-String -Path page.html -Pattern 'href=\"/wiki/\S+' | ForEach-Object {$_.Matches} | ForEach-Object {$_.Value}
Enter fullscreen mode Exit fullscreen mode

Résulat :

(...)
href="/wiki/Main_Page"
href="/wiki/Main_Page"
href="/wiki/Help:Contents"
href="/wiki/Special:WhatLinksHere/Hedy_Lamarr"
href="/wiki/Wikipedia:About"
href="/wiki/Wikipedia:General_disclaimer"
Enter fullscreen mode Exit fullscreen mode

Il faut encore trier les résultats :

Select-String -Path page.html -Pattern 'href=\"/wiki/\S+' | ForEach-Object {$_.Matches} | ForEach-Object {$_.Value} | Sort-Object -Unique
Enter fullscreen mode Exit fullscreen mode

Maintenant rajouter les liens pour les télécharger :

Select-String -Path ../page.html -Pattern 'href=\"/wiki/\S+' | ForEach-Object {$_.Matches} | ForEach-Object {$_.Value} | Sort-Object -Unique | ForEach-Object {$_.replace('href="/wiki/', "https://fr.wikipedia.org/wiki/")} > liens.txt
Enter fullscreen mode Exit fullscreen mode

Avant de tout télécharger, vérifions quel est le format de nos liens :

Get-Content -Head 5 -Path liens.txt
Enter fullscreen mode Exit fullscreen mode

Résultat :

PS /home/chris/Documents/blog/dirdir> Get-Content -Head 5 -Path liens.txt
https://fr.wikipedia.org/wiki//Film"
https://fr.wikipedia.org/wiki/%C3%91uSat"
https://fr.wikipedia.org/wiki/A_Lady_Without_Passport"
https://fr.wikipedia.org/wiki/Alexis_Granowsky"
https://fr.wikipedia.org/wiki/Alfred_Abel"
Enter fullscreen mode Exit fullscreen mode

Nous risquons d'avoir un soucis ici, car le " à la fin des lignes risque de casser le lien. Donc, nettoyons cela rapidement :

ForEach ($ligne in Get-Content liens.txt) {$ligne.replace('"', '') >> liens_propres.txt}
Enter fullscreen mode Exit fullscreen mode

Et téléchargeons le tout !

Invoke-WebRequest retourne un objet avec les champs suivants. Les champs les plus intéressants sont :

  • Content (le contenu de la page)
  • Links (voir la dernière partie de cet article)
ForEach ($ligne in Get-Content liens_propres.txt) {Invoke-WebRequest -Uri $ligne -OutFile $ligne.replace("https://", "").replace("/", "_")  }
Enter fullscreen mode Exit fullscreen mode

Ici, nous nettoyons les liens des / qui peuvent casser le chemin du fichier (particulièrement sous Linux), c'est pour cela que l'on enchaîne deux méthodes replace lorsque l'on crée le fichier.

Go go powershell

Powershell a encore quelques astuces :

  • ForEach-Object peut être remplacé par le caractère % et donc les boucles ressemblent à % { bloc }
  • GetChild-Item peut être remplacé par gci
  • Set-Location peut être remplacé par cd
  • Invoke-WebRequest peut télécharger les liens d'une page web d'un coup : (Invoke-WebRequest -Uri "https://aka.ms/pscore6-docs").Links.Href.

Essayez de refaire l'exercice avec ces astuces.

Quelques liens pour aller plus loin :

Top comments (0)