PowerShell, vidage de mon expérience

Présentation


Cet article s'adresse à ceux qui se sont déjà familiarisés avec les bases de PowerShell, exécutent certains scripts avec stackexchange et ont probablement leur propre fichier texte avec divers extraits pour faciliter le travail quotidien. Le but de l'écrire est de réduire l'entropie, d'augmenter la lisibilité et la maintenabilité du code PowerShell utilisé dans votre entreprise et, par conséquent, d'augmenter la productivité de l'administrateur travaillant avec lui.


kdpv


Dans mon ancien lieu de travail, en raison des spécificités des tâches et de l'imperfection du monde, j'ai considérablement amélioré les compétences de travail avec PowerShell. Après un changement de travail, les charges de ce genre ont diminué de façon spectaculaire et tout ce qui était juste au coin des doigts a commencé à couler de plus en plus profondément sous l'expérience de la résolution de nouveaux problèmes avec les nouvelles technologies. À partir de là, cet article prétend être seulement ce qu'il se déclare, révélant une liste de sujets qui, à mon avis, me seraient utiles il y a environ 7 ans, lorsque ma connaissance de cet outil ne faisait que commencer.


Si vous ne comprenez pas pourquoi PowerShell est un shell orienté objet, quels bonus en découlent et pourquoi il est nécessaire du tout, je vous conseillerai un bon livre qui présente rapidement l'essence de cet environnement malgré les ennemis haineux - Andrey Vladimirovich Popov, Introduction à Windows PowerShell . Oui, il s'agit de l'ancienne version de PS, oui, le langage a gagné quelques extensions et améliorations, mais ce livre est bon car en décrivant les premiers stades de développement de cet environnement, il met involontairement l'accent uniquement sur les choses fondamentales. Le sucre syntaxique avec lequel l'environnement a envahi, je pense, vous le percevez rapidement sans comprendre comment fonctionne le concept. La lecture de ce livre ne vous prendra que quelques soirées, revenez après la lecture.


popov


Le livre est également disponible sur le site Web de l'auteur, bien que je ne sais pas dans quelle mesure cette utilisation est autorisée: https://andpop.ru/courses/winscript/books/posh_popov.pdf


Guides de style


Concevoir des scripts selon des guides de style est une bonne pratique dans tous les cas d'application, il ne peut guère y avoir deux avis. Certains écosystèmes ont pris soin de cela au niveau du réglage natif, pep8 dans la communauté Python et aller fmt à Golang vient à l'évidence. Ce sont des outils de gain de temps inestimables, qui, malheureusement, sont absents du package PowerShell standard, et transfèrent donc le problème dans notre tête. Actuellement, la seule façon de résoudre le problème de la mise en forme uniforme du code est de développer des réflexes en écrivant à plusieurs reprises du code qui satisfait les guides de style (en fait, non).


Les guides de style en raison de l'absence de documents officiellement approuvés et décrits en détail par Microsoft sont nés dans la communauté à l'époque de PowerShell v3 et depuis lors, ils se développent sous forme ouverte sur le github: PowerShellPracticeAndStyle . Il s'agit d'un référentiel remarquable pour tous ceux qui ont déjà utilisé le bouton "Enregistrer" dans PowerShell ise.


Si vous essayez de faire une compression, cela se résumera probablement aux points suivants:


  • PowerShell utilise PascalCase pour nommer les variables, les applets de commande, les noms de module et à peu près tout sauf les opérateurs;
  • Les opérateurs de langage tels que if , switch , break , process , -match sont écrits en très petites lettres;
  • Les accolades sont placées de la seule vraie manière , autrement appelée aussi le style de Kernigan et Richie, menant son histoire du livre The C Programming Language ;
  • N'utilisez pas d'alias ailleurs qu'une session de console interactive, n'écrivez aucun ps | ? processname -eq firefox | %{$ws=0}{$ws+=$_.workingset}{$ws/1MB} fichier de script ps | ? processname -eq firefox | %{$ws=0}{$ws+=$_.workingset}{$ws/1MB} ps | ? processname -eq firefox | %{$ws=0}{$ws+=$_.workingset}{$ws/1MB} ps | ? processname -eq firefox | %{$ws=0}{$ws+=$_.workingset}{$ws/1MB} ;
  • Indiquez des noms de paramètres explicites, le comportement des applets de commande et leur signature peuvent changer , plus cela ajoute un contexte à une personne qui ne connaît pas une applet de commande particulière;
  • Concevez les paramètres pour appeler des scripts et n'écrivez pas de fonction à l'intérieur du script et la dernière ligne appelle cette fonction avec la nécessité de changer les valeurs des variables globales au lieu de spécifier des paramètres;
  • Spécifiez [CmdletBinding ()] - cela donnera à vos -Verbose -Debug -Verbose et -Debug indicateurs et de nombreuses autres fonctionnalités utiles . Malgré la position ferme de certains puristes dans la communauté, je ne suis pas partisan d'indiquer cet attribut dans des fonctions en ligne simples et des filtres composés de plusieurs lignes littérales;
  • Écrivez une aide basée sur des commentaires: une phrase, lien de ticket, exemple d'appel;
  • Spécifiez la version requise de PowerShell dans la section #requires ;
  • Utilisez Set-StrictMode -Version Latest , cela vous aidera à éviter les problèmes décrits ci-dessous ;
  • Gérer les erreurs;
  • Ne vous précipitez pas pour tout réécrire dans PowerShell. PowerShell est principalement un shell et appeler des binaires est sa tâche directe. Il n'y a rien de mal à utiliser robocopy dans un script, bien sûr ce n'est pas rsync, mais c'est aussi très bon.

Aide basée sur les commentaires


Voici un exemple de la façon d'obtenir un script d'aide. Le script encadre l'image, l'amenant à un carré et redimensionnant, je pense que vous avez la tâche de faire des avatars pour les utilisateurs (sauf peut-être la rotation en fonction des données exif). Il existe un exemple d'utilisation dans la section .EXAMPLE , essayez-le. Du fait que PowerShell est exécuté par le Common Language Runtime, le même que les autres langages dotnet, il a la capacité d'utiliser toute la puissance des bibliothèques dotnet:


 <# .SYNOPSIS Resize-Image resizes an image file .DESCRIPTION This function uses the native .NET API to crop a square and resize an image file .PARAMETER InputFile Specify the path to the image .PARAMETER OutputFile Specify the path to the resized image .PARAMETER SquareHeight Define the size of the side of the square of the cropped image. .PARAMETER Quality Jpeg compression ratio .EXAMPLE Resize the image to a specific size: .\Resize-Image.ps1 -InputFile "C:\userpic.jpg" -OutputFile "C:\userpic-400.jpg"-SquareHeight 400 #> # requires -version 3 [CmdletBinding()] Param( [Parameter( Mandatory )] [string]$InputFile, [Parameter( Mandatory )] [string]$OutputFile, [Parameter( Mandatory )] [int32]$SquareHeight, [ValidateRange( 1, 100 )] [int]$Quality = 85 ) # Add System.Drawing assembly Add-Type -AssemblyName System.Drawing # Open image file $Image = [System.Drawing.Image]::FromFile( $InputFile ) # Calculate the offset for centering the image $SquareSide = if ( $Image.Height -lt $Image.Width ) { $Image.Height $Offset = 0 } else { $Image.Width $Offset = ( $Image.Height - $Image.Width ) / 2 } # Create empty square canvas for the new image $SquareImage = New-Object System.Drawing.Bitmap( $SquareSide, $SquareSide ) $SquareImage.SetResolution( $Image.HorizontalResolution, $Image.VerticalResolution ) # Draw new image on the empty canvas $Canvas = [System.Drawing.Graphics]::FromImage( $SquareImage ) $Canvas.DrawImage( $Image, 0, -$Offset ) # Resize image $ResultImage = New-Object System.Drawing.Bitmap( $SquareHeight, $SquareHeight ) $Canvas = [System.Drawing.Graphics]::FromImage( $ResultImage ) $Canvas.DrawImage( $SquareImage, 0, 0, $SquareHeight, $SquareHeight ) $ImageCodecInfo = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object MimeType -eq 'image/jpeg' # https://msdn.microsoft.com/ru-ru/library/hwkztaft(v=vs.110).aspx $EncoderQuality = [System.Drawing.Imaging.Encoder]::Quality $EncoderParameters = New-Object System.Drawing.Imaging.EncoderParameters( 1 ) $EncoderParameters.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter( $EncoderQuality, $Quality ) # Save the image $ResultImage.Save( $OutputFile, $ImageCodecInfo, $EncoderParameters ) 

Le script ci-dessus commence par un commentaire sur plusieurs lignes <# ... #> , si ce commentaire vient en premier et contient certains mots clés, PowerShell créera automatiquement de l'aide pour le script. Ce type d'aide est littéralement appelé - aide basée sur le commentaire :


aider


De plus, lorsque le script est appelé, les info-bulles des paramètres fonctionnent, que ce soit la console PowerShell ou l'éditeur de code:


aide en ligne


Encore une fois, j'attire l'attention sur le fait qu'elle ne doit pas être négligée. Si vous ne savez pas quoi écrire là-bas, écrivez quelque chose, allez à la glacière et à votre retour, vous aurez certainement une compréhension de ce qui doit être changé par écrit. Ça marche. Ne remplissez pas tous les mots-clés de manière fanatique, PowerShell est conçu pour être auto-documenté et si vous avez donné des noms significatifs et complets aux paramètres, une courte phrase dans la section .SYNOPIS et un exemple suffisent.


Mode strict


PowerShell, comme beaucoup d'autres langages de script, a un typage dynamique. Ce type de frappe a de nombreux partisans: écrire une logique de haut niveau simple mais puissante est une question de quelques minutes, mais lorsque votre décision commencera à atteindre mille lignes, vous rencontrerez certainement la fragilité de cette approche.


L'autotype des types invariablement au stade du test, formant un tableau à l'endroit où vous avez toujours reçu un ensemble d'éléments, mettra certainement un cochon au cas où vous obtenez un élément et dans la condition suivante, au lieu de vérifier le nombre d'éléments, vous obtiendrez le nombre de caractères ou un autre attribut, selon type d'article. La logique du script se brisera et le runtime prétendra que tout va bien.


La définition du mode strict permet d'éviter certains de ce type de problèmes, mais cela nécessite également un peu plus de code de votre part, comme l'initialisation des variables et la conversion explicite.


Ce mode est activé par l'applet de commande Set-StrictMode -Version Latest , bien qu'il existe d'autres options de "rigueur", mon choix est d'utiliser cette dernière.


Dans l'exemple ci-dessous, le mode strict intercepte un appel vers une propriété inexistante. Puisqu'il n'y a qu'un seul élément à l'intérieur du dossier, le type de la variable $Input résultant de l'exécution sera FileInfo , et non le tableau attendu des éléments correspondants:


strict


Pour éviter un tel problème, vous devez explicitement convertir le résultat de l'applet de commande dans un tableau:


 $Items = @( Get-ChildItem C:\Users\snd3r\Nextcloud ) 

Faites-en une règle pour toujours définir un mode strict, cela vous permettra d'éviter des résultats inattendus lors de l'exécution de vos scripts.


Gestion des erreurs


ErrorActionPreference


Lorsque je regarde les scripts d'autres personnes, par exemple, sur un github, je vois souvent soit ignorer complètement le mécanisme de gestion des erreurs, soit activer explicitement le mode continu silencieux en cas d'erreur. Le problème de gestion des erreurs n'est certainement pas le plus facile à programmer en général et dans les scripts en particulier, mais il ne mérite certainement pas d'être ignoré. Par défaut, PowerShell en cas d'erreur l'affiche et continue de fonctionner (j'ai un peu simplifié le concept, mais ci-dessous se trouve un lien vers un git book sur ce sujet). Ceci est pratique si vous avez un besoin urgent de distribuer la mise à jour d'un programme largement utilisé dans le domaine sur toutes les machines, sans attendre qu'il se propage à tous les points de déploiement sccm ou se propage d'une autre manière que vous utilisez. Il est désagréable d'interrompre et de redémarrer le processus si l'une des machines est éteinte, c'est vrai.


D'un autre côté, si vous effectuez une sauvegarde complexe d'un système composé de plusieurs fichiers de données de plusieurs parties du système d'information, vous devez vous assurer que votre sauvegarde est cohérente et que tous les ensembles de données nécessaires ont été copiés sans erreur.


Pour modifier le comportement des applets de commande en cas d'erreur, il existe une variable globale $ErrorActionPreference , avec la liste suivante de valeurs possibles: Stop, Inquire, Continue, Suspend, SilentlyContinue .


Je recommande que vous utilisiez toujours la valeur Stop lorsque le nombre de scripts ou leur complexité cesse d'être stocké dans la pile dans votre tête, il est préférable de vous assurer que dans toute situation imprévue, le script arrêtera son travail et ne cassera pas le bois de chauffage «tranquillement», complétant l'exécution «avec succès».


En plus d'arrêter le script en cas de problème, il existe une autre condition préalable à son utilisation: la gestion des situations exceptionnelles. Il existe une construction try/catch pour cela, mais cela ne fonctionne que si l'erreur provoque l'arrêt de l'exécution. Il n'est pas nécessaire que l'arrêt soit activé au niveau de l'ensemble du script, ErrorAction peut ErrorAction être défini au niveau de l'applet de commande avec le paramètre:


 Get-ChildItem 'C:\System Volume Information\' -ErrorAction 'Stop' 

En fait, cette possibilité définit deux stratégies logiques: résoudre toutes les erreurs "par défaut" et définir ErrorAction uniquement pour les endroits critiques où les gérer; soit activer au niveau de l'ensemble du script en définissant la valeur de la variable globale et en définissant -ErrorAction 'Continue' sur les opérations non critiques. Je choisis toujours la deuxième option, je ne suis pas pressé de vous l'imposer, je ne recommande qu'une seule fois pour comprendre ce problème et utiliser cet outil utile.


essayer / attraper


Dans le gestionnaire d'erreurs, vous pouvez faire correspondre le type d'exception et opérer avec un thread d'exécution ou, par exemple, ajouter un peu plus d'informations. Malgré le fait qu'en utilisant les opérateurs try/catch/throw/trap , vous pouvez créer l'intégralité du flux d'exécution de script, vous devez catégoriquement éviter cela, car une telle méthode de fonctionnement avec exécution n'est pas seulement considérée comme un antipater extrême de la catégorie "goto-noodle", donc réduit également considérablement les performances.


 #requires -version 3 $ErrorActionPreference = 'Stop' #   ,    , #          $Logger = Get-Logger "$PSScriptRoot\Log.txt" #    trap { $Logger.AddErrorRecord( $_ ) exit 1 } #    $count = 1; while ( $true ) { try { #   $StorageServers = @( Get-ADGroupMember -Identity StorageServers | Select-Object -Expand Name ) } catch [System.Management.Automation.CommandNotFoundException] { #      ,         throw " Get-ADGroupMember ,    Active Directory module for PowerShell; $( $_.Exception.Message )" } catch [System.TimeoutException] { #             if ( $count -le 3 ) { $count++; Start-Sleep -S 10; continue } #             throw "     -   ,   $count ; $( $_.Exception.Message )" } #         break } 

Il convient de noter que l'opérateur de trap est un déroutement d'erreur global. Il intercepte tout ce qui n'a pas été traité à des niveaux inférieurs ou est rejeté du gestionnaire d'exceptions en raison de l'impossibilité de corriger indépendamment la situation.


En plus de l'approche orientée objet des exceptions décrite ci-dessus, PowerShell fournit également des concepts plus familiers compatibles avec d'autres shells "classiques", par exemple, les flux d'erreurs, les codes de retour et les variables d'accumulation d'erreurs. Tout cela est certainement pratique, parfois sans alternative, mais dépasse le cadre de ce sujet, dans l'ensemble, un aperçu. Heureusement, il existe un bon livre ouvert sur github à ce sujet.


Le code de l'enregistreur que j'utilise lorsqu'il n'y a aucune certitude que le système aura PowerShell 5 (où vous pouvez décrire la classe de l'enregistreur plus facilement), essayez-le, il peut vous être utile en raison de sa simplicité et de sa brièveté, vous êtes sûr d'ajouter des méthodes supplémentaires sans difficulté. :


 #   " ",   PowerShell v3 function Get-Logger { [CmdletBinding()] param ( [Parameter( Mandatory = $true )] [string] $LogPath, [string] $TimeFormat = 'yyyy-MM-dd HH:mm:ss' ) $LogsDir = [System.IO.Path]::GetDirectoryName( $LogPath ) New-Item $LogsDir -ItemType Directory -Force | Out-Null New-Item $LogPath -ItemType File -Force | Out-Null $Logger = [PSCustomObject]@{ LogPath = $LogPath TimeFormat = $TimeFormat } Add-Member -InputObject $Logger -MemberType ScriptMethod AddErrorRecord -Value { param( [Parameter( Mandatory = $true )] [string]$String ) "$( Get-Date -Format 'yyyy-MM-dd HH:mm:ss' ) [Error] $String" | Out-File $this.LogPath -Append } return $Logger } 

Je répète l'idée - n'ignorez pas la gestion des erreurs. Cela vous fera économiser du temps et des nerfs à long terme.
Ne pensez pas que l'exécution du script, peu importe ce qui est bon. Bon - il est temps de tomber sans casser du bois de chauffage.


Les outils


Il vaut la peine de commencer à améliorer les outils pour travailler avec PowerShell à partir de l'émulateur de console. J'ai souvent entendu des partisans de guêpes alternatives que la console dans Windows est mauvaise et que ce n'est pas du tout une console, mais des dos et ainsi de suite. Rares sont ceux qui peuvent formuler correctement leurs affirmations à ce sujet, mais si quelqu'un réussit, il s'avère en réalité que tous les problèmes peuvent être résolus. Il y avait déjà plus d'informations sur les terminaux et la nouvelle console dans les fenêtres du hub; tout y est plus que correct .


La première étape consiste à installer Conemu ou son assemblage Cmder, ce qui n'est pas particulièrement important, car à mon avis, cela vaut la peine de parcourir les paramètres de toute façon. Je choisis généralement cmder dans la configuration minimale, sans gita et autres binaires, que je me suis fixé, même si pendant plusieurs années j'ai réglé ma config pour du Conemu pur. C'est vraiment le meilleur émulateur de terminal pour Windows, vous permettant de diviser l'écran (pour les amateurs de tmux / écran), de créer des onglets et d'activer le mode console de style tremblement de terre:


Conemu


cmder


La prochaine étape, je recommande de mettre les modules: oh-my-posh , posh-git et PSReadLine . Les deux premiers rendront le bal plus agréable en ajoutant des informations sur la session en cours, l'état de la dernière commande exécutée, l'indicateur de privilège et l'état du référentiel git à l'emplacement actuel. PSReadLine stimule considérablement promt, en ajoutant par exemple une recherche sur l'historique des commandes entrées (CRTL + R) et des conseils pratiques pour les applets de commande sur CRTL + Space:


readline


Et oui, maintenant la console peut être effacée avec CTRL + L, et oublier les cls .


Code Visual Studio


L'éditeur. Tout ce que je peux dire de pire sur PowerShell est purement PowerShell ISE, ceux qui ont vu la première version avec trois panneaux ont peu de chances d'oublier cette expérience. L'encodage différent du terminal, le manque de fonctionnalités de base de l'éditeur, telles que l'indentation automatique, les crochets à fermeture automatique, le formatage du code et tout un ensemble d'antipatterns générés par celui-ci dont je ne vous dirai pas (juste au cas où) - tout cela concerne ISE.


Ne l'utilisez pas, utilisez Visual Studio Code avec l'extension PowerShell - il y a tout ce que vous ne voudriez pas (dans des limites raisonnables, bien sûr). Et n'oubliez pas que dans PoweShell jusqu'à la sixième version (PowerShell Core 6.0), l'encodage des scripts est UTF8 BOM, sinon la langue russe se brisera.


vscode


En plus de la mise en évidence de la syntaxe, des info-bulles et des capacités de débogage de script, le plugin installe un linter qui vous aidera également à suivre les pratiques établies dans la communauté, par exemple, en développant les raccourcis en un seul clic (sur l'ampoule). En fait, il s'agit d'un module standard qui peut être installé indépendamment, par exemple, ajoutez-le à vos scripts de signature de pipeline: PSScriptAnalyzer


PSScriptAnalyzer


Vous pouvez définir les paramètres de formatage du code dans les paramètres d'extension, pour tous les paramètres (à la fois l'éditeur et les extensions), il y a une recherche: File - Preferences - Settings :


Otbs


Afin d'obtenir une nouvelle console vide, vous devez définir le drapeau dans les paramètres , plus tard, probablement, ce conseil ne sera pas pertinent.


Il convient de se rappeler que toute action dans VS Code peut être effectuée à partir du centre de contrôle, appelé par CTRL + Maj + P. Formatez un morceau de code inséré à partir du chat , triez l'ordre alphabétique, changez le retrait des espaces en onglets, etc., tous dans le centre de contrôle.


Par exemple, activez le plein écran et centrez l'éditeur:


mise en page


Contrôle de source Git, SVN


Souvent, les administrateurs système Windows ont une phobie de résolution de conflits dans les systèmes de contrôle de version, probablement parce que si un représentant de cet ensemble utilise git, alors souvent on ne rencontre aucun problème de ce type. Avec vscode, la résolution des conflits se réduit littéralement à des clics de souris sur les parties du code qui doivent être laissées ou remplacées.


fusionner


Ces étiquettes entre les lignes 303 et 304 sont cliquables, cela vaut la peine de cliquer sur toutes celles qui apparaissent dans le document en cas de conflit, de valider les modifications et de les envoyer au serveur. U - Commodité.


Sur la façon de travailler avec les systèmes de contrôle de version est disponible et avec des images, il est écrit dans le dock vscode , passez-y brièvement les yeux.


Extraits


Les extraits de code sont une sorte de macro / modèle qui vous permet d'accélérer l'écriture de code. Certainement un must.


Création rapide d'objets:


objet personnalisé


Pêcher pour une aide basée sur des commentaires:


aider


Si l'applet de commande doit passer un grand nombre de paramètres, il est judicieux d' utiliser l'éclaboussure .
Voici un extrait pour lui:


éclaboussures


Afficher tous les extraits disponibles disponibles par Ctrl + Alt + J:


extraits


Si après cela, vous aviez envie de continuer à améliorer votre environnement, mais que vous n'aviez pas encore entendu parler des feuilles de guêpe, alors vous y êtes . De plus, si vous avez votre propre ensemble d'extensions utiles pour l'écriture de scripts PowerShell, je serai heureux de voir leur liste dans les commentaires.


Performances


Le sujet de la performance n'est pas aussi simple qu'il y paraît à première vue. D'une part, les optimisations prématurées peuvent réduire considérablement la lisibilité et la maintenabilité du code, économisant 300 ms de temps d'exécution de script, dont le temps d'exécution habituel peut être de dix minutes, leur utilisation dans ce cas est définitivement destructrice. D'un autre côté, il existe plusieurs astuces assez simples qui augmentent à la fois la lisibilité du code et sa vitesse, qui sont tout à fait appropriées à utiliser de manière continue. Ci-dessous, je parlerai de certains d'entre eux, si les performances sont tout pour vous et que la lisibilité passe à côté en raison des limites de temps strictes des temps d'arrêt du service pendant la maintenance, je vous recommande de vous référer à la littérature pertinente .


Pipeline et foreach


La façon la plus simple et toujours efficace d'augmenter la productivité est d'éviter d'utiliser des tuyaux. En raison de la sécurité et de la commodité du type pour des raisons de puissance, les éléments PowerShell passant à travers un tuyau enveloppent chacun d'eux dans un objet. Dans les langages dotnet, ce comportement est appelé boxe . La boxe est bonne, elle garantit la sécurité, mais elle a son propre prix, ce qui n'a parfois aucun sens à payer.


La première étape consiste à augmenter les performances et, à mon avis, à améliorer la lisibilité en supprimant toutes les applications de l' Foreach-Object et en la remplaçant par l'instruction foreach. Vous pouvez être confondu par le fait qu'il s'agit en fait de deux entités différentes, car foreach est un alias de Foreach-Object - en pratique, la principale différence est que foreach n'accepte pas les valeurs d'un pipeline, et en même temps, il fonctionne par expérience jusqu'à trois fois plus rapidement.


: - , , :


 Get-Content D:\temp\SomeHeavy.log | Select-String '328117' 

— , . — , , Get-Content . string , , . — , :


When reading from and writing to binary files, use the AsByteStream parameter and a value of 0 for the ReadCount parameter. A ReadCount value of 0 reads the entire file in a single read operation. The default ReadCount value, 1, reads one byte in each read operation and converts each byte into a separate object, which causes errors when you use the Set-Content cmdlet to write the bytes to a file unless you use AsByteStream

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content

 Get-Content D:\temp\SomeHeavy.log -ReadCount 0 | Select-String '328117' 

:


recomptage


, . Select-String — . , Select-String . , Select-String , , :


 foreach ( $line in ( Get-Content D:\temp\SomeHeavy.log -ReadCount 0 )) { if ( $line -match '328117' ) { $line } } 

30 , - 30%, , , , - , ( ;-). , . , -match ; — . , — — - , .


— - , " " :


 foreach ( $line in ( Get-Content D:\temp\SomeHeavy.log -ReadCount 0 )) { if ( $line -match '328117' ) { "$( Get-Date -UFormat '%d.%m.%Y %H:%M:%S') $line" | Out-File D:\temp\Result.log -Append } } 

Measure-Command :


 Hours : 2 Minutes : 20 Seconds : 9 Milliseconds : 101 

. , , , . , PowerShell , , — . , , — StringBuilder . , , . , .


 $StringBuilder = New-Object System.Text.StringBuilder foreach ( $line in ( Get-Content D:\temp\SomeHeavy.log -ReadCount 0 )) { if ( $line -match '328117' ) { $null = $StringBuilder.AppendLine( "$( Get-Date -UFormat '%d.%m.%Y %H:%M:%S') $line" ) } } Out-File -InputObject $StringBuilder.ToString() -FilePath D:\temp\Result.log -Append -Encoding UTF8 

5 , :


 Hours : 0 Minutes : 5 Seconds : 37 Milliseconds : 150 

Out-File -InputObject , , . — . — Get-Help -Full , Accept pipeline input? true (ByValue) :


 -InputObject <psobject> Required? false Position? Named Accept pipeline input? true (ByValue) Parameter set name (All) Aliases None Dynamic? false 

PowerShell :


taskmgr


StringBuilder :


attribut de constructeur de cordes


, , 3 3. dotnet- — StreamReader .


 $StringBuilder = New-Object System.Text.StringBuilder $StreamReader = New-Object System.IO.StreamReader 'D:\temp\SomeHeavy.log' while ( $line = $StreamReader.ReadLine()) { if ( $line -match '328117' ) { $null = $StringBuilder.AppendLine( "$( Get-Date -UFormat '%d.%m.%Y %H:%M:%S') $line" ) } } $StreamReader.Dispose() Out-File -InputObject $StringBuilder.ToString() -FilePath C:\temp\Result.log -Append -Encoding UTF8 

 Hours : 0 Minutes : 5 Seconds : 33 Milliseconds : 657 

, . , , , , 2. , :


streamreader


— "", — StringBuilder — "" . , ( 100) . — 90% ( , ):


 $BufferSize = 104857600 $StringBuilder = New-Object System.Text.StringBuilder $BufferSize $StreamReader = New-Object System.IO.StreamReader 'C:\temp\SomeHeavy.log' while ( $line = $StreamReader.ReadLine()) { if ( $line -match '1443' ) { #      if ( $StringBuilder.Length -gt ( $BufferSize - ( $BufferSize * 0.1 ))) { Out-File -InputObject $StringBuilder.ToString() -FilePath C:\temp\Result.log -Append -Encoding UTF8 $StringBuilder.Clear() } $null = $StringBuilder.AppendLine( "$( Get-Date -UFormat '%d.%m.%Y %H:%M:%S') $line" ) } } Out-File -InputObject $StringBuilder.ToString() -FilePath C:\temp\Result.log -Append -Encoding UTF8 $StreamReader.Dispose() 

 Hours : 0 Minutes : 5 Seconds : 53 Milliseconds : 417 

1 :


streamreader with dump


, . , , StreamWriter , , ;-) , , .


- — , . , — . Select-String Out-File , OutOfMemoryException , — .



, PowerShell , — , : PowerShell — , .


, StringBuilder dir — ( ). :


 $CurrentPath = ( Get-Location ).Path + '\' $StringBuilder = New-Object System.Text.StringBuilder foreach ( $Line in ( &cmd /c dir /b /s /ad )) { $null = $StringBuilder.AppendLine( $Line.Replace( $CurrentPath, '.' )) } $StringBuilder.ToString() 

 Hours : 0 Minutes : 0 Seconds : 3 Milliseconds : 9 

 $StringBuilder = New-Object System.Text.StringBuilder foreach ( $Line in ( Get-ChildItem -File -Recurse | Resolve-Path -Relative )) { $null = $StringBuilder.AppendLine( $Line ) } $StringBuilder.ToString() 

 Hours : 0 Minutes : 0 Seconds : 16 Milliseconds : 337 

$null — . , — Out-Null ; , ( $null ) , .


 # : $null = $StringBuilder.AppendLine( $Line ) # : $StringBuilder.AppendLine( $Line ) | Out-Null 

, , . Compare-Object , , , . robocopy.exe, ( PowerShell 5), :


 class Robocopy { [String]$RobocopyPath Robocopy () { $this.RobocopyPath = Join-Path $env:SystemRoot 'System32\Robocopy.exe' if ( -not ( Test-Path $this.RobocopyPath -PathType Leaf )) { throw '    ' } } [void]CopyFile ( [String]$SourceFile, [String]$DestinationFolder ) { $this.CopyFile( $SourceFile, $DestinationFolder, $false ) } [void]CopyFile ( [String]$SourceFile, [String]$DestinationFolder, [bool]$Archive ) { $FileName = [IO.Path]::GetFileName( $SourceFile ) $FolderName = [IO.Path]::GetDirectoryName( $SourceFile ) $Arguments = @( '/R:0', '/NP', '/NC', '/NS', '/NJH', '/NJS', '/NDL' ) if ( $Archive ) { $Arguments += $( '/A+:a' ) } $ErrorFlag = $false &$this.RobocopyPath $FolderName $DestinationFolder $FileName $Arguments | Foreach-Object { if ( $ErrorFlag ) { $ErrorFlag = $false throw "$_ $ErrorString" } else { if ( $_ -match '(?<=\(0x[\da-f]{8}\))(?<text>(.+$))' ) { $ErrorFlag = $true $ErrorString = $matches.text } else { $Logger.AddRecord( $_.Trim()) } } } if ( $LASTEXITCODE -eq 8 ) { throw 'Some files or directories could not be copied' } if ( $LASTEXITCODE -eq 16 ) { throw 'Robocopy did not copy any files. Check the command line parameters and verify that Robocopy has enough rights to write to the destination folder.' } } [void]SyncFolders ( [String]$SourceFolder, [String]$DestinationFolder ) { $this.SyncFolders( $SourceFolder, $DestinationFolder, '*.*', '', $false ) } [void]SyncFolders ( [String]$SourceFolder, [String]$DestinationFolder, [Bool]$Archive ) { $this.SyncFolders( $SourceFolder, $DestinationFolder, '*.*', '', $Archive ) } [void]SyncFolders ( [String]$SourceFolder, [String]$DestinationFolder, [String]$Include ) { $this.SyncFolders( $SourceFolder, $DestinationFolder, $Include, '', $false ) } [void]SyncFolders ( [String]$SourceFolder, [String]$DestinationFolder, [String]$Include, [Bool]$Archive ) { $this.SyncFolders( $SourceFolder, $DestinationFolder, $Include, '', $Archive ) } [void]SyncFolders ( [String]$SourceFolder, [String]$DestinationFolder, [String]$Include, [String]$Exclude ) { $this.SyncFolders( $SourceFolder, $DestinationFolder, $Include, $Exclude, $false ) } [void]SyncFolders ( [String]$SourceFolder, [String]$DestinationFolder, [String]$Include, [String]$Exclude, [Bool]$Archive ) { $Arguments = @( '/MIR', '/R:0', '/NP', '/NC', '/NS', '/NJH', '/NJS', '/NDL' ) if ( $Exclude ) { $Arguments += $( '/XF' ) $Arguments += $Exclude.Split(' ') } if ( $Archive ) { $Arguments += $( '/A+:a' ) } $ErrorFlag = $false &$this.RobocopyPath $SourceFolder $DestinationFolder $Include $Arguments | Foreach-Object { if ( $ErrorFlag ) { $ErrorFlag = $false throw "$_ $ErrorString" } else { if ( $_ -match '(?<=\(0x[\da-f]{8}\))(?<text>(.+$))' ) { $ErrorFlag = $true $ErrorString = $matches.text } else { $Logger.AddRecord( $_.Trim()) } } } if ( $LASTEXITCODE -eq 8 ) { throw 'Some files or directories could not be copied' } if ( $LASTEXITCODE -eq 16 ) { throw 'Robocopy did not copy any files. Check the command line parameters and verify that Robocopy has enough rights to write to the destination folder.' } } } 

, ( ), — .


, : Foreach-Object !? , : foreach , Foreach-Object — , , , , . .


, :


 $Robocopy = New-Object Robocopy #    $Robocopy.CopyFile( $Source, $Dest ) #   $Robocopy.SyncFolders( $SourceDir, $DestDir ) #    .xml     $Robocopy.SyncFolders( $SourceDir, $DestDir , '*.xml', $true ) #     *.zip *.tmp *.log     $Robocopy.SyncFolders( $SourceDir, $DestDir, '*.*', '*.zip *.tmp *.log', $true ) 


— , , ; , , , :


  • foreach Foreach-Object ;


  • ;


  • / , ;


  • StringBuilder ;


  • , - ;


  • ( "" );



: - , .


Emplois


, , , , , , . . , IO, .


ssd

Voici comment se déroule le premier démarrage de Windows Server 2019 fraîchement installé dans Hyper-V vers ssd (il a été décidé par la migration de la machine virtuelle vers hdd):


2019ssd


PowerShell ( Get-Command *-Job ), .


, , , :


 $Job = Start-Job -ScriptBlock { Write-Output 'Good night' Start-Sleep -S 10 Write-Output 'Good morning' } $Job | Wait-Job | Receive-Job Remove-Job $Job 

, — , . .


, :


emplois
https://xaegr.wordpress.com/2011/07/12/threadping/


, , — , . , , (50 — 50 ):


le travail meurt


. , — , . — , .


, , , - .


Runspaces


Beginning Use of PowerShell Runspaces: Part 1 . , — PowerShell , . (, PowerShell ), : ( ) . , .


WPF , PowerShell, . — , . — , "" . .


, .


wpf


 #     $GUISyncHash = [hashtable]::Synchronized(@{}) <# WPF  #> $GUISyncHash.FormXAML = [xml](@" <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Sample WPF Form" Height="510" Width="410" ResizeMode="NoResize"> <Grid> <Label Content=" " HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="37" Width="374" FontSize="18"/> <Label Content="" HorizontalAlignment="Left" Margin="16,64,0,0" VerticalAlignment="Top" Height="26" Width="48"/> <TextBox x:Name="BackupPath" HorizontalAlignment="Left" Height="23" Margin="69,68,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="300"/> <Label Content="" HorizontalAlignment="Left" Margin="16,103,0,0" VerticalAlignment="Top" Height="26" Width="35"/> <TextBox x:Name="RestorePath" HorizontalAlignment="Left" Height="23" Margin="69,107,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="300"/> <Button x:Name="FirstButton" Content="√" HorizontalAlignment="Left" Margin="357,68,0,0" VerticalAlignment="Top" Width="23" Height="23"/> <Button x:Name="SecondButton" Content="√" HorizontalAlignment="Left" Margin="357,107,0,0" VerticalAlignment="Top" Width="23" Height="23"/> <CheckBox x:Name="Check" Content="  " HorizontalAlignment="Left" Margin="16,146,0,0" VerticalAlignment="Top" RenderTransformOrigin="-0.113,-0.267" Width="172"/> <Button x:Name="Go" Content="Go" HorizontalAlignment="Left" Margin="298,173,0,0" VerticalAlignment="Top" Width="82" Height="26"/> <ComboBox x:Name="Droplist" HorizontalAlignment="Left" Margin="16,173,0,0" VerticalAlignment="Top" Width="172" Height="26"/> <ListBox x:Name="ListBox" HorizontalAlignment="Left" Height="250" Margin="16,210,0,0" VerticalAlignment="Top" Width="364"/> </Grid> </Window> "@) <#   #> $GUISyncHash.GUIThread = { $GUISyncHash.Window = [Windows.Markup.XamlReader]::Load(( New-Object System.Xml.XmlNodeReader $GUISyncHash.FormXAML )) $GUISyncHash.Check = $GUISyncHash.Window.FindName( "Check" ) $GUISyncHash.GO = $GUISyncHash.Window.FindName( "Go" ) $GUISyncHash.ListBox = $GUISyncHash.Window.FindName( "ListBox" ) $GUISyncHash.BackupPath = $GUISyncHash.Window.FindName( "BackupPath" ) $GUISyncHash.RestorePath = $GUISyncHash.Window.FindName( "RestorePath" ) $GUISyncHash.FirstButton = $GUISyncHash.Window.FindName( "FirstButton" ) $GUISyncHash.SecondButton = $GUISyncHash.Window.FindName( "SecondButton" ) $GUISyncHash.Droplist = $GUISyncHash.Window.FindName( "Droplist" ) $GUISyncHash.Window.Add_SourceInitialized({ $GUISyncHash.GO.IsEnabled = $true }) $GUISyncHash.FirstButton.Add_Click( { $GUISyncHash.ListBox.Items.Add( 'Click FirstButton' ) }) $GUISyncHash.SecondButton.Add_Click( { $GUISyncHash.ListBox.Items.Add( 'Click SecondButton' ) }) $GUISyncHash.GO.Add_Click( { $GUISyncHash.ListBox.Items.Add( 'Click GO' ) }) $GUISyncHash.Window.Add_Closed( { Stop-Process -Id $PID -Force }) $null = $GUISyncHash.Window.ShowDialog() } $Runspace = @{} $Runspace.Runspace = [RunspaceFactory]::CreateRunspace() $Runspace.Runspace.ApartmentState = "STA" $Runspace.Runspace.ThreadOptions = "ReuseThread" $Runspace.Runspace.Open() $Runspace.psCmd = { Add-Type -AssemblyName PresentationCore, PresentationFramework, WindowsBase }.GetPowerShell() $Runspace.Runspace.SessionStateProxy.SetVariable( 'GUISyncHash', $GUISyncHash ) $Runspace.psCmd.Runspace = $Runspace.Runspace $Runspace.Handle = $Runspace.psCmd.AddScript( $GUISyncHash.GUIThread ).BeginInvoke() Start-Sleep -S 1 $GUISyncHash.ListBox.Dispatcher.Invoke( "Normal", [action] { $GUISyncHash.ListBox.Items.Add( '' ) }) $GUISyncHash.ListBox.Dispatcher.Invoke( "Normal", [action] { $GUISyncHash.ListBox.Items.Add( '  ' ) }) foreach ( $item in 1..5 ) { $GUISyncHash.Droplist.Dispatcher.Invoke( "Normal", [action] { $GUISyncHash.Droplist.Items.Add( $item ) $GUISyncHash.Droplist.SelectedIndex = 0 }) } $GUISyncHash.ListBox.Dispatcher.Invoke( "Normal", [action] { $GUISyncHash.ListBox.Items.Add( 'While ( $true ) { Start-Sleep -S 10 }' ) }) while ( $true ) { Start-Sleep -S 10 } 

WPF github, , smart : https://github.com/snd3r/GetDiskSmart/ . , MVVM:


cinglant


Visual Studio, Community Edition , xaml- wpf — https://github.com/punker76/kaxaml


kaxaml


Au lieu d'une conclusion


PowerShell — Windows-. , , , .


, , "PowerShell, ", . , — , . . , - , - .


— .


calme


PS Boomburum , 2019 powershell — .

Source: https://habr.com/ru/post/fr443256/


All Articles