PowerShell, Dump meiner Erfahrung

Einführung


Dieser Artikel richtet sich an diejenigen, die sich bereits mit den Grundlagen von PowerShell vertraut gemacht haben, einige Skripte mit Stackexchange ausführen und wahrscheinlich über eine eigene Textdatei mit verschiedenen Snippets verfügen, um die tägliche Arbeit zu erleichtern. Der Zweck des Schreibens besteht darin, die Entropie zu verringern, die Lesbarkeit und Wartbarkeit des in Ihrem Unternehmen verwendeten PowerShell-Codes zu verbessern und infolgedessen die Produktivität des Administrators zu steigern, der damit arbeitet.


kdpv


An meinem früheren Arbeitsplatz habe ich aufgrund der Besonderheiten der Aufgaben und der Unvollkommenheit der Welt die Fähigkeit, mit PowerShell zu arbeiten, erheblich gesteigert. Nach dem Wechsel der Arbeit nahmen die Belastungen dieser Art erheblich ab und alles, was sich um die Fingerspitzen befand, begann unter der Erfahrung, neue Probleme mit neuen Technologien zu lösen, immer tiefer zu sinken. Aus diesem Grund behauptet dieser Artikel, nur das zu sein, was er selbst deklariert, und enthüllt eine Liste von Themen, die meiner Meinung nach vor etwa 7 Jahren, als meine Bekanntschaft mit diesem Tool gerade erst begann, für mich selbst nützlich sein würden.


Wenn Sie nicht verstehen, warum PowerShell eine objektorientierte Shell ist, aus welchen Boni sie stammen und warum sie überhaupt erforderlich sind, empfehle ich Ihnen trotz der Hasser ein gutes Buch, das die Essenz dieser Umgebung schnell einführt - Andrey Vladimirovich Popov, Einführung in Windows PowerShell . Ja, es geht um die alte Version von PS, ja, die Sprache hat einige Erweiterungen und Verbesserungen erhalten, aber dieses Buch ist gut, weil es bei der Beschreibung des frühen Entwicklungsstadiums dieser Umgebung unwissentlich nur grundlegende Dinge hervorhebt. Ich denke, der syntaktische Zucker, mit dem die Umwelt überwachsen ist, wird schnell wahrgenommen, ohne zu verstehen, wie das Konzept funktioniert. Das Lesen dieses Buches dauert nur ein paar Abende. Kommen Sie nach dem Lesen zurück.


popov


Das Buch ist auch auf der Website des Autors verfügbar, obwohl ich nicht sicher bin, wie lizenziert diese Verwendung ist: https://andpop.ru/courses/winscript/books/posh_popov.pdf


Styleguides


Das Entwerfen von Skripten nach Styleguides ist in allen Fällen eine gute Praxis. Es kann kaum zwei Meinungen geben. Einige Ökosysteme haben sich auf der Ebene der nativen Abstimmung darum gekümmert, pep8 in der Python-Community und go fmt in Golang sind offensichtlich. Dies sind unschätzbare zeitsparende Tools, die im Standard-PowerShell-Paket leider nicht vorhanden sind und daher das Problem auf unseren Kopf übertragen. Derzeit besteht die einzige Möglichkeit, das Problem der einheitlichen Code-Formatierung zu lösen, darin, Reflexe zu entwickeln, indem wiederholt Code geschrieben wird, der den Stilrichtlinien entspricht (tatsächlich nein).


Styleguides wurden aufgrund der fehlenden offiziellen Genehmigung und detaillierten Beschreibung durch Microsoft in der Community während der Zeit von PowerShell v3 geboren und haben sich seitdem in offener Form auf dem Github entwickelt: PowerShellPracticeAndStyle . Dies ist ein bemerkenswertes Repository für alle, die jemals die Schaltfläche "Speichern" in PowerShell ise verwendet haben.


Wenn Sie versuchen, einen Druck auszuüben, wird dies wahrscheinlich auf die folgenden Punkte zurückzuführen sein:


  • PowerShell verwendet PascalCase, um Variablen, Cmdlets, Modulnamen und so ziemlich alles außer Operatoren zu benennen.
  • -match wie if , switch , break , process , -match werden in sehr kleinen Buchstaben geschrieben.
  • Geschweifte Klammern werden auf die einzig wahre Weise platziert , die auch als Stil von Kernigan und Richie bezeichnet wird und seine Geschichte aus dem Buch The C Programming Language leitet.
  • Verwenden Sie keine anderen Aliase als eine interaktive Konsolensitzung. Schreiben Sie keine ps | ? processname -eq firefox | %{$ws=0}{$ws+=$_.workingset}{$ws/1MB} Skriptdatei 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} ;
  • Geben Sie explizite Parameternamen an, das Verhalten der Cmdlets und ihre Signatur können sich ändern. Außerdem wird einer Person, die mit einem bestimmten Cmdlet nicht vertraut ist, ein Kontext hinzugefügt.
  • Entwerfen Sie die Parameter zum Aufrufen von Skripten und schreiben Sie keine Funktion in das Skript. In der letzten Zeile wird diese Funktion aufgerufen, wobei die Werte globaler Variablen geändert werden müssen, anstatt Parameter anzugeben.
  • Geben Sie [CmdletBinding ()] an - dies gibt Ihrem Cmdlet -Verbose und -Debug Flags und vielen anderen nützlichen Funktionen . Trotz der festen Position einiger Puristen in der Community bin ich kein Befürworter der Angabe dieses Attributs in einfachen Inline-Funktionen und Filtern, die aus mehreren Zeilen bestehen.
  • Schreiben Sie eine kommentarbasierte Hilfe: ein Satz, Ticketlink, Anrufbeispiel;
  • Geben Sie die erforderliche Version von PowerShell im Abschnitt #requires .
  • Verwenden Sie Set-StrictMode -Version Latest , um die unten beschriebenen Probleme zu vermeiden .
  • Fehler behandeln;
  • Beeilen Sie sich nicht, alles in PowerShell neu zu schreiben. PowerShell ist in erster Linie eine Shell und das Aufrufen von Binärdateien ist seine direkte Aufgabe. Es ist nichts Falsches daran, Robocopy in einem Skript zu verwenden. Natürlich ist es nicht rsync, aber es ist auch sehr gut.

Kommentarbasierte Hilfe


Unten finden Sie ein Beispiel, wie Sie ein Hilfeskript erhalten. Das Skript rahmt das Bild ein, bringt es auf ein Quadrat und führt eine Größenänderung durch. Ich denke, Sie haben die Aufgabe, Avatare für Benutzer zu erstellen (außer vielleicht die Drehung gemäß Exif-Daten). .EXAMPLE Abschnitt .EXAMPLE finden Sie ein Verwendungsbeispiel. .EXAMPLE es aus. Aufgrund der Tatsache, dass PowerShell wie andere Dotnet-Sprachen von der Common Language Runtime ausgeführt wird, kann es die volle Leistung von Dotnet-Bibliotheken nutzen:


 <# .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 ) 

Das obige Skript beginnt mit einem mehrzeiligen Kommentar <# ... #> . Wenn dieser Kommentar an erster Stelle steht und bestimmte Schlüsselwörter enthält, erstellt PowerShell automatisch eine Hilfe für das Skript. Diese Art von Hilfe wird wörtlich genannt - Hilfe basierend auf dem Kommentar :


Hilfe


Wenn das Skript aufgerufen wird, funktionieren außerdem die QuickInfos für die Parameter, unabhängig davon, ob es sich um die PowerShell-Konsole oder den Code-Editor handelt:


Inline-Hilfe


Ich mache noch einmal darauf aufmerksam, dass sie nicht zu vernachlässigen ist. Wenn Sie nicht wissen, was Sie dort schreiben sollen, schreiben Sie etwas, gehen Sie zum Kühler und nach Ihrer Rückkehr haben Sie definitiv ein Verständnis dafür, was in der Schrift geändert werden muss. Es funktioniert. Es lohnt sich nicht, alle Schlüsselwörter fanatisch auszufüllen. PowerShell ist so konzipiert, dass es sich selbst dokumentiert. Wenn Sie den Parametern aussagekräftige und vollständige Namen gegeben haben, reicht ein kurzer Satz im Abschnitt .SYNOPIS und ein Beispiel völlig aus.


Strenger Modus


PowerShell verfügt wie viele andere Skriptsprachen über eine dynamische Typisierung. Diese Art des Schreibens hat viele Befürworter: Das Schreiben einer einfachen, aber leistungsstarken Logik auf hoher Ebene dauert nur wenige Minuten. Wenn Ihre Entscheidung jedoch tausend Zeilen umfasst, werden Sie mit Sicherheit auf die Fragilität dieses Ansatzes stoßen.


Die automatische Ausgabe von Typen in der Testphase, die an der Stelle, an der Sie immer eine Reihe von Elementen erhalten haben, ein Array bildete, wird definitiv ein Schwein setzen, falls Sie ein Element erhalten, und in der folgenden Bedingung erhalten Sie, anstatt die Anzahl der Elemente zu überprüfen, die Anzahl der Zeichen oder ein anderes Attribut, abhängig von Art des Artikels. Die Logik des Skripts bricht ab und die Laufzeit gibt vor, dass alles in Ordnung ist.


Das Festlegen des strengen Modus hilft, einige dieser Probleme zu vermeiden, erfordert jedoch auch etwas mehr Code von Ihnen, z. B. das Initialisieren von Variablen und das explizite Casting.


Dieser Modus wird durch das Set-StrictMode -Version Latest aktiviert. Obwohl es andere Optionen für "Rigor" gibt, habe ich die Wahl, letzteres zu verwenden.


Im folgenden Beispiel ruft der strikte Modus eine nicht vorhandene Eigenschaft auf. Da sich nur ein Element im Ordner befindet, lautet der Typ der Variablen $Input als Ergebnis der Ausführung FileInfo und nicht das erwartete Array der entsprechenden Elemente:


streng


Um ein solches Problem zu vermeiden, sollten Sie das Ergebnis des Cmdlets explizit in ein Array umwandeln:


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

Machen Sie es sich zur Regel, immer den strengen Modus festzulegen. Auf diese Weise können Sie unerwartete Ergebnisse beim Ausführen Ihrer Skripte vermeiden.


Fehlerbehandlung


ErrorActionPreference


Wenn ich mir die Skripte anderer Leute anschaue, zum Beispiel auf einem Github, sehe ich oft, dass entweder der Fehlerbehandlungsmechanismus vollständig ignoriert wird oder der stille Fortsetzungsmodus im Fehlerfall explizit aktiviert wird. Das Problem der Fehlerbehandlung ist sicherlich nicht das am einfachsten zu programmierende im Allgemeinen und in Skripten im Besonderen, aber es verdient definitiv nicht, ignoriert zu werden. Standardmäßig zeigt PowerShell im Fehlerfall diesen an und funktioniert weiter (ich habe das Konzept etwas vereinfacht, aber unten finden Sie einen Link zu einem Git-Buch zu diesem Thema). Dies ist praktisch, wenn Sie das Update eines in der Domäne weit verbreiteten Programms dringend an alle Computer verteilen müssen, ohne zu warten, bis es sich auf alle sccm-Bereitstellungspunkte oder auf eine andere von Ihnen verwendete Weise ausbreitet. Es ist unangenehm, den Prozess zu unterbrechen und neu zu starten, wenn eine der Maschinen ausgeschaltet ist. Dies ist wahr.


Wenn Sie andererseits eine komplexe Sicherung eines Systems erstellen, das aus mehr als einer Datendatei von mehr als einem Teil des Informationssystems besteht, müssen Sie sicherstellen, dass Ihre Sicherung konsistent ist und dass alle erforderlichen Datensätze fehlerfrei kopiert wurden.


Um das Verhalten von Cmdlets im Fehlerfall zu ändern, gibt es eine globale Variable $ErrorActionPreference mit der folgenden Liste möglicher Werte: Stop, Inquire, Continue, Suspend, SilentlyContinue .


Ich empfehle, dass Sie immer den Stop Wert verwenden, wenn die Anzahl der Skripte oder deren Komplexität nicht mehr auf dem Stapel in Ihrem Kopf gespeichert ist. Es ist besser sicherzustellen, dass das Skript in einer unvorhergesehenen Situation seine Arbeit stoppt und das Brennholz nicht „leise“ bricht und die Ausführung „erfolgreich“ abschließt.


Neben dem Stoppen des Skripts für den Fall, dass etwas schief geht, gibt es eine weitere Voraussetzung für seine Verwendung - den Umgang mit Ausnahmesituationen. Hierfür gibt es ein try/catch Konstrukt, das jedoch nur funktioniert, wenn der Fehler dazu führt, dass die Ausführung gestoppt wird. Es ist nicht erforderlich, dass der Stopp auf der Ebene des gesamten Skripts ErrorAction ist. ErrorAction kann ErrorAction auf der Cmdlet-Ebene mit dem folgenden Parameter festgelegt werden:


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

Tatsächlich definiert diese Möglichkeit zwei logische Strategien: ErrorAction alle Fehler "standardmäßig" und legen Sie ErrorAction nur für kritische Stellen fest, an denen sie behandelt werden sollen. Aktivieren Sie entweder auf der Ebene des gesamten Skripts, indem Sie den Wert der globalen Variablen -ErrorAction 'Continue' und -ErrorAction 'Continue' für nicht kritische Vorgänge -ErrorAction 'Continue' . Ich wähle immer die zweite Option. Ich habe es nicht eilig, sie Ihnen aufzuzwingen. Ich empfehle nur einmal, dieses Problem zu verstehen und dieses nützliche Tool zu verwenden.


versuchen / fangen


In der Fehlerbehandlungsroutine können Sie nach Art der Ausnahme übereinstimmen und mit einem Ausführungsthread arbeiten oder beispielsweise ein wenig mehr Informationen hinzufügen. Trotz der Tatsache, dass Sie mit den Operatoren try/catch/throw/trap den gesamten Ablauf der Skriptausführung erstellen können, sollten Sie dies kategorisch vermeiden, da eine solche Methode des Betriebs mit Ausführung nicht nur als extremer Antipater aus der Kategorie "goto-noodle" angesehen wird verringert auch die Leistung drastisch.


 #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 } 

Es ist erwähnenswert, dass der trap Operator eine globale Fehlerfalle ist. Es fängt alles ab, was nicht auf niedrigeren Ebenen verarbeitet wurde oder aus dem Ausnahmebehandler geworfen wird, da es unmöglich ist, die Situation unabhängig zu korrigieren.


Neben dem oben beschriebenen objektorientierten Ansatz von Ausnahmen bietet PowerShell auch bekanntere Konzepte, die mit anderen "klassischen" Shells kompatibel sind, z. B. Fehlerströme, Rückkehrcodes und Variablen zur Fehlerakkumulation. All dies ist sicherlich praktisch, manchmal ohne Alternative, geht aber über den Rahmen dieses Gesamtübersichtsthemas hinaus. Zum Glück gibt es ein gutes offenes Buch über Github zu diesem Thema.


Der Code des Loggers, den ich verwende, wenn es keine Gewissheit gibt, dass das System über PowerShell 5 verfügt (wo Sie die Logger-Klasse bequemer beschreiben können). Probieren Sie ihn aus. Er kann Ihnen aufgrund seiner Einfachheit und Kürze nützlich sein. Sie werden sicher ohne Schwierigkeiten zusätzliche Methoden hinzufügen. ::


 #   " ",   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 } 

Ich wiederhole die Idee - ignoriere die Fehlerbehandlung nicht. Dies spart Ihnen auf lange Sicht Zeit und Nerven.
Denken Sie nicht, dass das Ausführen des Skripts, egal was gut ist. Gut - es ist Zeit zu fallen, ohne Brennholz zu zerbrechen.


Die Werkzeuge


Es lohnt sich, die Tools für die Arbeit mit PowerShell über den Konsolenemulator zu verbessern. Ich habe oft von Anhängern alternativer Wespen gehört, dass die Konsole in Windows schlecht ist und dass dies überhaupt keine Konsole ist, sondern dos und so weiter. Nur wenige konnten ihre Behauptungen zu diesem Thema angemessen formulieren, aber wenn es jemandem gelang, stellte sich in Wirklichkeit heraus, dass alle Probleme gelöst werden konnten. Es gab bereits mehr Informationen über die Terminals und die neue Konsole in Windows am Hub, dort ist alles mehr als in Ordnung .


Der erste Schritt ist die Installation von Conemu oder seiner Cmder-Baugruppe, was nicht besonders wichtig ist, da es meiner Meinung nach ohnehin einen Durchlauf der Einstellungen wert ist. Normalerweise wähle ich cmder in der Minimalkonfiguration ohne Gita und andere Binärdateien, die ich selbst festgelegt habe, obwohl ich meine Konfiguration mehrere Jahre lang auf reines Conemu abgestimmt habe. Dies ist wirklich der beste Terminalemulator für Windows, mit dem Sie den Bildschirm (für tmux- / Bildschirmliebhaber) teilen, Registerkarten erstellen und den Konsolenmodus im Bebenstil aktivieren können:


Conemu


cmder


Im nächsten Schritt empfehle ich, die Module zu platzieren: oh-my-posh , posh-git und PSReadLine . Die ersten beiden machen den Abschlussball angenehmer, indem sie Informationen über die aktuelle Sitzung, den Status des zuletzt ausgeführten Befehls, die Berechtigungsanzeige und den Status des Git-Repositorys am aktuellen Speicherort hinzufügen. PSReadLine steigert die Promt erheblich und fügt beispielsweise eine Suche nach dem Verlauf eingegebener Befehle (CRTL + R) und praktische Tipps für Cmdlets in CRTL + Space hinzu:


readline


Und ja, jetzt kann die Konsole mit STRG + L gelöscht werden und cls vergessen.


Visual Studio Code


Der Herausgeber. Das Schlimmste, was ich über PowerShell sagen kann, ist reine PowerShell ISE. Diejenigen, die die erste Version mit drei Panels gesehen haben, werden diese Erfahrung wahrscheinlich nicht vergessen. Die unterschiedliche Codierung des Terminals, das Fehlen grundlegender Editorfunktionen wie automatisches Einrücken, automatisch schließende Klammern, Codeformatierung und eine ganze Reihe von Antimustern, über die ich Ihnen (nur für den Fall) nichts erzählen werde - hier geht es nur um ISE.


Verwenden Sie es nicht, verwenden Sie Visual Studio Code mit der PowerShell-Erweiterung - es gibt alles, was Sie nicht möchten (natürlich in angemessenen Grenzen). Und vergessen Sie nicht, dass in PoweShell bis zur sechsten Version (PowerShell Core 6.0) die Codierung für Skripte UTF8-Stückliste ist, da sonst die russische Sprache beschädigt wird.


vscode


Zusätzlich zur Hervorhebung der Syntax, QuickInfos und Funktionen zum Debuggen von Skripten installiert das Plugin einen Linter, mit dem Sie die in der Community etablierten Praktiken befolgen können, z. B. Verknüpfungen mit einem Klick erweitern (auf die Glühbirne). Tatsächlich ist dies ein reguläres Modul, das unabhängig installiert werden kann. Fügen Sie es beispielsweise Ihren Pipeline-Signaturskripten hinzu: PSScriptAnalyzer


PSScriptAnalyzer


Sie können die Code-Formatierungsparameter in den Erweiterungseinstellungen festlegen. Für alle Einstellungen (sowohl den Editor als auch die Erweiterungen) wird gesucht: File - Preferences - Settings :


Otbs


Um eine neue leere Konsole zu erhalten, sollten Sie das Flag in den Einstellungen setzen . Später ist dieser Rat wahrscheinlich irrelevant.


Es ist zu beachten, dass jede Aktion in VS Code vom Kontrollzentrum aus ausgeführt werden kann, das mit STRG + Umschalt + P aufgerufen wird. Formatieren Sie einen aus dem Chat eingefügten Code, sortieren Sie ihn alphabetisch, ändern Sie den Einzug von Leerzeichen in Tabulatoren usw. im Kontrollzentrum.


Schalten Sie beispielsweise den Vollbildmodus ein und zentrieren Sie den Editor:


Layout


Quellcodeverwaltung Git, SVN


Windows-Systemadministratoren haben häufig eine Phobie der Konfliktlösung in Versionskontrollsystemen, wahrscheinlich weil, wenn ein Vertreter dieses Satzes Git verwendet, häufig keine derartigen Probleme auftreten. Mit vscode wird die Konfliktlösung auf einen Mausklick auf die Teile des Codes reduziert, die verlassen oder ersetzt werden müssen.


zusammenführen


Diese Beschriftungen zwischen den Zeilen 303 und 304 können angeklickt werden. Es lohnt sich, im Konfliktfall auf alle im Dokument angezeigten Beschriftungen zu klicken, die Änderungen festzuschreiben und die Änderungen an den Server zu senden. U - Bequemlichkeit.


Informationen zur Arbeit mit Versionskontrollsystemen finden Sie unter und mit Bildern , die im vscode-Dock geschrieben sind , gehen Sie dort kurz und gut über Ihre Augen.


Schnipsel


Snippets sind eine Art Makro / Vorlage, mit der Sie das Schreiben von Code beschleunigen können. Auf jeden Fall ein Muss.


Schnelle Objekterstellung:


benutzerdefiniertes Objekt


Fisch für kommentarbasierte Hilfe:


Hilfe


Wenn das Cmdlet eine große Anzahl von Parametern übergeben muss, ist es sinnvoll, Splatting zu verwenden .
Hier ist ein Ausschnitt für ihn:


splatting


Alle verfügbaren Schnipsel anzeigen, die mit Strg + Alt + J verfügbar sind:


Schnipsel


Wenn Sie danach den Wunsch hatten, Ihre Umgebung weiter zu verbessern, aber noch nichts von Wespenblättern gehört haben, dann sind Sie hier . Wenn Sie beim Schreiben von PowerShell-Skripten über eigene Erweiterungen verfügen, die für Sie nützlich sind, freue ich mich über deren Liste in den Kommentaren.


Leistung


Das Thema Performance ist nicht so einfach, wie es auf den ersten Blick erscheinen mag. Einerseits können vorzeitige Optimierungen die Lesbarkeit und Wartbarkeit des Codes erheblich beeinträchtigen und 300 ms Skriptausführungszeit einsparen, deren übliche Laufzeit zehn Minuten betragen kann. Ihre Verwendung ist in diesem Fall definitiv destruktiv. Auf der anderen Seite gibt es einige ziemlich einfache Tricks, die sowohl die Lesbarkeit des Codes als auch seine Geschwindigkeit verbessern und für die fortlaufende Verwendung durchaus geeignet sind. Im Folgenden werde ich auf einige davon eingehen. Wenn die Leistung alles für Sie ist und die Lesbarkeit aufgrund der strengen Fristen für Ausfallzeiten während der Wartung auf der Strecke bleibt, empfehle ich Ihnen, sich auf die entsprechende Literatur zu beziehen.


Pipeline und foreach


Der einfachste und stets funktionierende Weg zur Steigerung der Produktivität besteht darin, die Verwendung von Rohren zu vermeiden. Aus Gründen der Sicherheit und Bequemlichkeit des Typs umhüllt PowerShell Elemente, die durch ein Rohr geführt werden, jedes Element in ein Objekt. In Dotnet-Sprachen wird dieses Verhalten als Boxen bezeichnet . Boxen ist gut, es garantiert Sicherheit, aber es hat seinen eigenen Preis, was manchmal keinen Sinn macht, zu zahlen.


Der erste Schritt besteht darin, die Leistung zu steigern und meiner Meinung nach die Lesbarkeit zu verbessern, indem alle Anwendungen des Foreach-Object und durch die foreach-Anweisung ersetzt werden. Es mag Ihnen peinlich sein, dass dies tatsächlich zwei verschiedene Entitäten sind, da foreach ein Alias ​​für Foreach-Object In der Praxis besteht der Hauptunterschied darin, dass foreach keine Werte aus einer Pipeline akzeptiert, aber erfahrungsgemäß bis zu dreimal schneller arbeitet.


Stellen Sie sich das Problem vor: Wir müssen ein großes Protokoll verarbeiten, um eine Ableitung davon zu bilden. Wählen Sie beispielsweise eine Reihe von Einträgen aus und bringen Sie sie in ein anderes Format:


 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' 

:


readcount


, . 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 :


stringbuilder alloc


, , 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 ) 

Nachgeschmack


— , , ; , , , :


  • foreach Foreach-Object ;


  • ;


  • / , ;


  • StringBuilder ;


  • , - ;


  • ( "" );



: - , .


Jobs


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


ssd

Windows Server 2019 Hyper-V ssd ( 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 

, — , . .


, :


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


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


job dies


. , — , . — , .


, , , - .


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:


binging


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


kaxaml


Anstelle einer Schlussfolgerung


PowerShell — Windows-. , , , .


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


— .


calm


PS Boomburum , 2019 powershell — .

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


All Articles