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.
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
etget-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
L'équivalent de
/
sous Linux estC:\
sous Windows.
Déplaçons-nous :
Set-Location C:\
Ou si vous êtes sous Linux avec Powershell :
Set-Location /
Lancer
Set-Location
sans arguments est comme lancercd
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
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
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
Donnera :
Directory: /home/chris/Documents/blog
Mode LastWriteTime Length Name
---- ------------- ------ ----
---------- 27/01/2021 16:07 0 pouet
Et pour un dossier :
New-item -ItemType directory -Name dirdir
Résultat :
Directory: /home/chris/Documents/blog
Mode LastWriteTime Length Name
--------- ------------- ------ ----
d---- 27/01/2021 16:07 dirdir
Déplaçons le fichier pouet
dans dirdir
:
# Windows
Move-Item -Path pouet -Destination dirdir\pouet
# Linux
Move-Item -Path pouet -Destination dirdir/pouet
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
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"
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 "
Pour compter le nombre d'occurrences :
Select-String -Path texte_complet.txt -Pattern " love " | Measure-Object -Word
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
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
Et créons un fichier d'un coup
Get-Content liste_mdp.txt | Sort-Object -Unique > nouvelle_liste.txt
Et voilà le travail !
Fonctions plus avancées
Passons maintenant au second article, et réessayons de refaire tout cela en Powershell !
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
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]}
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"
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
Et la réassignation fonctionne aussi de la même manière :
$nombre_deux=2
echo $nombre_deux
Résultat : 2
$nombre_deux=3
echo $nombre_deux
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}
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
Essayons de le faire en Powershell !
$global:somme=0; ForEach ($i in 0..100) {$somme = $somme + $i}; echo $somme
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
}
Exemple 2 :
if condition {
bloc
} else if condition 2 {
bloc 2
} else {
bloc 3
}
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 !
Invoke-WebRequest -Uri "https://en.wikipedia.org/wiki/Hedy_Lamarr" -OutFile page.html
Ensuite :
Select-String -Path page.html -Pattern 'href=\"/wiki/'
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+'
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>
Nous allons maintenant itérer sur chaque objet de notre recherche
Select-String -Path page.html -Pattern 'href=\"/wiki/\S+' | ForEach-Object {$_.Matches}
Résultat :
(...)
Groups : {0}
Success : True
Name : 0
Captures : {0}
Index : 38
Length : 41
Value : href="/wiki/Wikipedia:General_disclaimer"
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}
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"
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
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
Avant de tout télécharger, vérifions quel est le format de nos liens :
Get-Content -Head 5 -Path liens.txt
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"
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}
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("/", "_") }
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é pargci
-
Set-Location
peut être remplacé parcd
-
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)