{"id":51,"date":"2024-04-20T12:08:44","date_gmt":"2024-04-20T10:08:44","guid":{"rendered":"https:\/\/kev-it.fr\/?p=51"},"modified":"2024-04-21T20:11:07","modified_gmt":"2024-04-21T18:11:07","slug":"powershell-et-le-multi-threading","status":"publish","type":"post","link":"https:\/\/kev-it.fr\/index.php\/2024\/04\/20\/powershell-et-le-multi-threading\/","title":{"rendered":"PowerShell et le Multi-Threading"},"content":{"rendered":"\n<p>Comme tout bon scripteur fou qui se respecte, vous avez cr\u00e9\u00e9 de belles usines \u00e0 gaz qui manipulent de gros volumes de donn\u00e9es. Mais quand le traitement se compte en heures ou en jours, \u00e7a devient vraiment gal\u00e8re ! Et si on parall\u00e9lisait les traitements ?<\/p>\n\n\n\n<p>L&rsquo;id\u00e9e derri\u00e8re le multi-threading est de d\u00e9couper le traitement en lots ind\u00e9pendants que l&rsquo;on pourra ex\u00e9cuter en parall\u00e8le. A la fin, on consolide les r\u00e9sultats et c&rsquo;est gagn\u00e9 \ud83d\ude42<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Un cas d&rsquo;usage<\/h2>\n\n\n\n<p>Vous l&rsquo;aurez compris, en pr\u00e9requis il faut pouvoir d\u00e9composer les actions en groupes ind\u00e9pendants. Pour cela il faut que le traitement \u00e0 parall\u00e9liser puisse \u00eatre d\u00e9plac\u00e9 dans une fonction ou un script d\u00e9di\u00e9. Prenons un exemple : vous extrayez la liste de vos site SharePoint et pour chaque site, vous voulez auditer les permissions. L&rsquo;audit des permissions de chaque site est bien une actions ind\u00e9pendante pour chaque site. On a alors deux scripts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>l&rsquo;un qui va auditer les permissions d&rsquo;un site SharePoint et fournir un r\u00e9sultat au format CSV<\/li>\n\n\n\n<li>l&rsquo;autre qui servira de script chapeau pour \n<ul class=\"wp-block-list\">\n<li>r\u00e9cup\u00e9rer la liste des sites SharePoint<\/li>\n\n\n\n<li>lancer les processus enfants pour l&rsquo;analyse de chaque script (script pr\u00e9c\u00e9dent)<\/li>\n\n\n\n<li>suivre le r\u00e9sultat d&rsquo;ex\u00e9cution des processus enfants<\/li>\n\n\n\n<li>consolider les r\u00e9sultats<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Et en pratique \u00e7a donne quoi<\/h2>\n\n\n\n<p>Techniquement, il existe plusieurs m\u00e9thode pour faire du multi-threading en PowerShell mais nous nous concentrerons sur la m\u00e9thode la plus simple et la plus int\u00e9gr\u00e9e : Les <strong>PSJob<\/strong>. Ils sont int\u00e9gr\u00e9s directement depuis PowerShell version 3. L&rsquo;autre m\u00e9thode, les <strong>Runspaces<\/strong> sont plus efficace mais un peu plus complexes \u00e0 mettre en \u0153uvre et \u00e0 g\u00e9rer car il faut appeler les classes .NET en direct.<br>La derni\u00e8re approche consiste \u00e0 cr\u00e9er des scripts de type Workflow. Ils int\u00e8grent nativement des fonctions de traitement parall\u00e8le, mais cela n\u00e9cessite de revoir un peu sa mani\u00e8re coder.<\/p>\n\n\n\n<p>Premi\u00e8re \u00e9tape : je d\u00e9marre les processus enfants<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Je cr\u00e9\u00e9 la liste des arguments \u00e0 passer (pour la lisibilit\u00e9)\n$myargs = @($param1Value, $param2Value)\n# Je cr\u00e9\u00e9 un nouveau job en appelant mon script avec ses param\u00e8tres\n$newJob = start-job -filepath \"$($currdir)\\myChildScript.ps1\" -argumentlist $myargs\n# Je consolide les jobs dans une liste\n$jobList += $newJob<\/code><\/pre>\n\n\n\n<p>Deuxi\u00e8me \u00e9tape : on attend la fin de l&rsquo;ex\u00e9cution des processus<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Wait for remaining jobs to finish\nwhile (@($jobList |? {$_.state -like 'Running'}).count -gt 0) {\n    $progress = @($jobList |? {$_.state -notlike 'Running'}).count\n    Write-progress -activity \"Processing Sites (with $maxThreadCount threads)\" -status \"Finishing threads\" -PercentComplete ($progress*100\/$urlList.count) -id 1\n    start-sleep -seconds 10\n}<\/code><\/pre>\n\n\n\n<p>Et pour finir, on regarde des processus qui on eut des erreurs<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Collect Errors logs\n$errors = @($jobList |? {$_.state -like 'Failed'} |Receive-Job)\nif ($errors.count -gt 0) {\n    Write-log (\"{0} thread failed: {1]\" -f $errors.count, ($errors.id -join ',')) \"ERROR\"\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Et pour aller plus loin<\/h2>\n\n\n\n<p>Si vous voulez limiter le nombre de processus qui s&rsquo;ex\u00e9cuteront en parall\u00e8le, il faut se donner une limite. Gr\u00e2ce a elle on ajoute une condition afin de ne pas d\u00e9marrer plus de processus que la limite tant que d&rsquo;autres processus ne se sont pas termin\u00e9s<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (@($jobList |? {$_.state -like 'Running'}).count -le $maxThreadCount) {\n    # Je d\u00e9marre mes processus enfants\n} else {\n    # MaxThreadCount reached\n    start-sleep -seconds 1\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">R\u00e9f\u00e9rences<\/h2>\n\n\n\n<p>Cmd-let PowerShell :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/powershell\/module\/microsoft.powershell.core\/start-job\">Start-Job<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/powershell\/module\/microsoft.powershell.core\/remove-job\">Remove-Job<\/a><\/li>\n<\/ul>\n\n\n\n<p>Comparaison des m\u00e9thodes de multi-threading PSJobs vs. Runspaces. <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Voici un <a href=\"https:\/\/adamtheautomator.com\/powershell-multithreading\">article super int\u00e9ressant et d\u00e9taill\u00e9 sur le sujet (en anglais)<\/a> <\/li>\n<\/ul>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Comme tout bon scripteur fou qui se respecte, vous avez cr\u00e9\u00e9 de belles usines \u00e0 gaz qui manipulent de gros volumes de donn\u00e9es. Mais quand le traitement se compte en heures ou en jours, \u00e7a devient vraiment gal\u00e8re ! Et si on parall\u00e9lisait les traitements ?<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[9,8,10],"class_list":["post-51","post","type-post","status-publish","format-standard","hentry","category-dev","tag-multi-threading","tag-powershell","tag-tips"],"_links":{"self":[{"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/posts\/51","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/comments?post=51"}],"version-history":[{"count":4,"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/posts\/51\/revisions"}],"predecessor-version":[{"id":58,"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/posts\/51\/revisions\/58"}],"wp:attachment":[{"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/media?parent=51"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/categories?post=51"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kev-it.fr\/index.php\/wp-json\/wp\/v2\/tags?post=51"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}