PowerShell、私の経験のダンプ

はじめに



この記事は、PowerShellの基本に既に精通し、stackexchangeでいくつかのスクリプトを実行し、おそらく日常の作業を容易にするためのさまざまなスニペットを含む独自のテキストファイルを持っている人を対象としています。 これを書く目的は、エントロピーを減らし、会社で使用されているPowerShellコードの可読性と保守性を高め、その結果、それを使用する管理者の生産性を高めることです。







kdpv







以前の仕事の場所では、タスクの詳細と世界の不完全さのために、PowerShellで作業するスキルを大いに活気づけました。 作業の変更後、この種の負荷は劇的に減少し、指の角を曲がったところにあるものはすべて、新しい技術で新しい問題を解決する経験の下でますます深く沈み始めました。 このことから、この記事はそれ自体が宣言していることだけを主張しており、私の意見では、このツールの知り合いが始まったばかりの約7年前に自分にとって役立つトピックのリストを明らかにしています。







PowerShellがオブジェクト指向シェルである理由、これから得られるボーナス、およびなぜそれが必要なのかを理解していない場合、嫌悪感の叫びにもかかわらず、この環境の本質をすばやく紹介する良い本をお勧めします-Andrey Vladimirovich Popov、Introduction to Windows PowerShell 。 はい、それは古いバージョンのPSについてです、はい、言語はいくつかの拡張と改善を獲得しましたが、この環境の開発の初期段階を説明するとき、それは基本的なことだけを無意識に強調するので、この本は良いです。 環境が大きくなりすぎた構文糖は、概念がどのように機能するかを理解することなく、すぐにそれを知覚します。 この本を読むと、文字通り数晩の夕べになり、読んだ後に戻ってきます。







ポポフ







本は著者のウェブサイトでも入手できますが、この使用がどのようにライセンスされているかはわかりません: https : //andpop.ru/courses/winscript/books/posh_popov.pdf







スタイルガイド



スタイルガイドに従ってスクリプトを設計することは、その適用のすべての場合において良い習慣であり、2つの意見はほとんどあり得ません。 いくつかのエコシステムはネイティブチューニングのレベルでこれを処理しており、Pythonコミュニティではpep8、Golangではgo fmtが明らかになります。 これらは非常に貴重な時間節約ツールであり、残念ながら、標準のPowerShellパッケージには存在しないため、問題を頭に移します。 現在、統一されたコードフォーマットの問題を解決する唯一の方法は、スタイルガイドを満たすコードを繰り返し記述することで反射神経を開発することです(実際、いいえ)。







Microsoftによって公式に承認され、詳細に説明されていないためのスタイルガイドは、PowerShell v3の時代にコミュニティで誕生し、それ以来githubのオープンフォームで開発されています: PowerShellPracticeAndStyle 。 これは、PowerShell iseで「保存」ボタンを使用したことがある人にとって注目すべきリポジトリです。







スクイーズを行おうとすると、おそらく次の点になります。









コメントベースのヘルプ



以下は、ヘルプスクリプトを取得する方法の例です。 スクリプトは、画像を四角にし、サイズを変更します。ユーザーのアバターを作成するタスクがあると思います(おそらくexifデータに従った回転を除く)。 .EXAMPLE



セクションに使用例があります。試してください。 PowerShellは、他のドットネット言語と同様に、共通言語ランタイムによって実行されるため、ドットネットライブラリのすべての機能を使用できます。







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





上記のスクリプトは複数行のコメント<# ... #>



で始まり、このコメントが最初に来て特定のキーワードが含まれている場合、PowerShellはスクリプトのヘルプを自動的に作成します。 この種のヘルプは文字通り呼ばれます- コメントに基づくヘルプ







助けて







さらに、スクリプトが呼び出されると、PowerShellコンソールであれコードエディターであれ、パラメーターのツールチップが機能します。







インンラインヘルプ







もう一度、私は彼女が無視されるべきではないという事実に注意を引きます。 あなたがそこに何を書くべきかわからない場合は、何かを書いて、クーラーに行き、戻ったときに、書かれたもので何を変更する必要があるかを確実に理解するでしょう。 動作します。 すべてのキーワードを熱狂的に入力しないでください.SYNOPIS



自己文書化するように設計されており、パラメーターに意味のあるフルネームを付けた場合、 .SYNOPIS



セクションの短い文と1つの例で十分です。







厳格モード



PowerShellは、他の多くのスクリプト言語と同様に、動的型付けを備えています。 このタイプのタイピングには多くの支持者がいます:シンプルだが強力な高レベルのロジックを書くことは数分で完了しますが、決定が千行に達すると、このアプローチの脆弱性に必ず遭遇します。







常に要素のセットを受け取る場所に配列を形成したテスト段階で型を自動的に出力すると、1つの要素を取得し、次の条件で要素の数をチェックする代わりに、文字の数または別の属性を取得しますアイテムのタイプ。 スクリプトのロジックが壊れ、ランタイムはすべてが正常であると見せかけます。







ストリクトモードを設定すると、この種の問題を回避できますが、変数の初期化や明示的なキャストなど、もう少しコードを追加する必要があります。







このモードはSet-StrictMode -Version Latest



コマンドレットによって有効になりますが、「厳格さ」には他のオプションがありますが、私の選択は後者を使用することです。







以下の例では、strictモードは存在しないプロパティへの呼び出しをキャッチします。 フォルダー内には要素が1つしかないため、実行の結果としての$Input



変数の型はFileInfo



になり、対応する要素の予想される配列ではありません。







厳しい







このような問題を回避するには、コマンドレットの結果を明示的に配列にキャストする必要があります。







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





常に厳格モードを設定することをルールにしてください。これにより、スクリプトの実行による予期しない結果を回避できます。







エラー処理



ErrorActionPreference



他の人のスクリプト、たとえばgithubを調べると、エラー処理メカニズムを完全に無視するか、エラーが発生した場合に継続操作のサイレントモードを明示的にアクティブにすることがよくあります。 エラー処理の問題は、一般に、特にスクリプトでプログラミングするのが最も簡単ではありませんが、間違いなく無視するに値するものではありません。 デフォルトでは、エラーが発生した場合のPowerShellはエラーを表示して動作し続けます(概念を少し簡略化しましたが、このトピックに関するgitブックへのリンクです)。 これは、ドメインで広く使用されているプログラムの更新プログラムをすべてのマシンに緊急に配布する必要がある場合に便利です。すべてのsccm展開ポイントに広がるか、ユーザーが使用する別の方法で広がるまで待つ必要はありません。 マシンの1つがオフになっている場合、プロセスを中断して再起動するのは不快です。これは事実です。







一方、情報システムの複数の部分の複数のデータファイルで構成されるシステムの複雑なバックアップを作成する場合、バックアップの一貫性と、必要なすべてのデータセットがエラーなしでコピーされたことを確認する必要があります。







エラーが発生した場合のコマンドレットの動作を変更するには、グローバル変数$ErrorActionPreference



があり、次の値のリストがあります: Stop、Inquire、Continue、Suspend、SilentContinue







スクリプトの数またはその複雑さが頭のスタックに保存されなくなった場合は、常にStop



値を使用することをお勧めします。予期せぬ状況では、スクリプトが動作を停止し、fireを「静かに」中断せず、実行が「正常に」終了することを確認することをお勧めします。







何か問題が発生した場合にスクリプトを停止することに加えて、その使用には別の前提条件があります-例外的な状況を処理します。 これにはtry/catch



コンストラクトがありますが、エラーにより実行が停止した場合にのみ機能します。 スクリプト全体のレベルで停止を有効にする必要はありませんErrorAction



は、パラメーターを使用してコマンドレットレベルで設定することErrorAction



できます。







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





実際、この可能性は2つの論理的な戦略を定義します。すべてのエラーを「デフォルトで」解決し、エラーを処理する重要な場所にのみErrorAction



を設定します。 グローバル変数の値を設定して、スクリプト全体のレベルで有効にするか、重要でない操作で-ErrorAction 'Continue'



を設定します。 私は常に2番目のオプションを選択します。急いであなたに課すことはありません。この問題を理解し、この便利なツールを使用するのは1回だけにすることをお勧めします







試し/キャッチ



エラーハンドラーでは、例外の種類ごとに一致させ、実行のスレッドで操作したり、たとえば、もう少し情報を追加したりできます。 try/catch/throw/trap



演算子を使用すると、スクリプト実行のフロー全体を構築できますが、実行を伴うこのような方法は「goto-noodle」カテゴリの極端なアンチパターンと見なされるだけではないため、これを明確に回避する必要があります。また、パフォーマンスが大幅に低下します。







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





trap



演算子はグローバルエラートラップであることに注意してください。 下位レベルで処理されていないすべてのものをキャッチするか、状況を個別に修正できないために例外ハンドラーからスローされます。







上記の例外のオブジェクト指向のアプローチに加えて、PowerShellは、エラーフロー、リターンコード、エラー累積変数など、他の「クラシック」シェルと互換性のあるより馴染みのある概念も提供します。 これは確かに便利で、時には争われないこともありますが、全体としてこのトピックの範囲を超えています。 幸いなことに、このテーマに関するgithubの良い本があります。







ロガーのコードは、システムにPowerShell 5(ロガークラスをより便利に記述することができる)があるという確信がない場合に使用します。試してみてください。そのシンプルさと簡潔さから、役に立つことがあります。追加のメソッドを簡単に追加できます。 :







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





私はこの考えを繰り返します-エラー処理を無視しないでください。 これは長期的にあなたの時間と神経を節約します。

何があってもスクリプトを実行するとは思わないでください。 良い-itを壊さずに落ちる時です。







ツール



PowerShellを使用するためのツールの改善を開始するには、間違いなくコンソールエミュレーターを使用する必要があります。 代わりのスズメバチの支持者から、Windowsのコンソールが悪いこと、これはまったくコンソールではなく、dosなどであるとよく耳にしました。 この主題に関する主張を適切に定式化することはできませんでしたが、誰かが成功した場合、実際にはすべての問題を解決できることが判明しました。 端末とハブのウィンドウには新しいコンソールに関する情報がすでにありましたが、 すべてがそこにあります。







最初のステップはConemuまたはそのCmderアセンブリをインストールすることです。これは特に重要ではありません。とにかく設定を検討する価値があるからです。 私は通常、gitaや他のバイナリを使用せずに、最小限の構成でcmderを選択します。数年の間、純粋なConemuの構成を調整しました。 これは本当にウィンドウ用の最高のターミナルエミュレータで、画面を分割(tmux /スクリーンが好きな人向け)、タブを作成、地震スタイルのコンソールモードを有効にできます。







コネム



cmder







モジュールを配置することをお勧めする次のステップ: oh-my-poshposh-git 、およびPSReadLine 。 最初の2つは、現在のセッション、最後に実行されたコマンドのステータス、特権インジケーター、および現在の場所のgitリポジトリのステータスに関する情報を追加することにより、promをより快適にします。 PSReadLineは、入力されたコマンドの履歴(CRTL + R)の検索や、CRTL + Spaceのコマンドレットの便利なヒントなどを追加して、プロンプトを大幅に向上させます。







readline







そして、はい、CTRL + Lでコンソールをクリアでき、 cls



を忘れることができます。







ビジュアルスタジオコード



エディター。 PowerShellについて私が言える最悪のことは、純粋にPowerShell ISEです。3つのパネルを持つ最初のバージョンを見た人は、この経験を忘れることはほとんどありません。 ターミナルの異なるエンコーディング、自動インデント、自動クローズブラケット、コードの書式設定、生成されたアンチパターンのセットなど、基本的なエディター機能の欠如(これはISEについてです)。







使用しないでください。PowerShell拡張機能を備えたVisual Studio Codeを使用してください-必要のないものはすべてあります(もちろん、合理的な制限内です)。 また、6番目のバージョン(PowerShell Core 6.0)より前のPoweShellでは、スクリプトのエンコードがUTF8 BOMであることを忘れないでください。そうしないとロシア語が壊れます。







vscode







プラグインは、構文の強調表示、ツールのヒント、およびスクリプトのデバッグ機能に加えて、コミュニティで確立された慣習に従うのに役立つリンターをインストールします。たとえば、ワンクリックで(電球上で)ショートカットを展開します。 実際、これは独立してインストールできる通常のモジュールです。たとえば、パイプライン署名スクリプトに追加できます: PSScriptAnalyzer







PSScriptAnalyzer







拡張機能の設定でコードの書式設定パラメーターを設定できます。すべての設定(エディターと拡張機能の両方)に対して、検索があります: File - Preferences - Settings









Otbs







新しいconptyコンソールを取得するには、設定フラグを設定する必要があります 。おそらく、このアドバイスは関係ないでしょう。







VS Codeのすべてのアクションは、Ctrl + Shift + Pで呼び出されるコントロールセンターから実行できることを覚えておく価値があります。 チャットから挿入されたコードをフォーマット 、行をアルファベット順に並べ替え、インデントをスペースからタブに変更するなど、これはすべてコントロールセンターにあります。







たとえば、全画面表示をオンにしてエディターを中央に配置します。







レイアウト







ソース管理 Git、SVN



多くの場合、Windowsシステム管理者はバージョン管理システムの競合解決に恐怖心を抱いています。これはおそらく、このセットの代表者がgitを使用する場合、この種の問題が発生しないことが多いためです。 vscodeを使用すると、競合の解決は、コードの残りの部分または置き換える必要のある部分での文字通りのマウスクリックに限定されます。







併合する







303行と304行の間のこれらの碑文はクリック可能です。競合が発生した場合にドキュメントに表示されるすべてをクリックし、変更をコミットしてサーバーに変更を送信する価値があります。 U-利便性。







バージョン管理システムの操作方法と、vscode dock書かれた写真を使用する方法については、そこを簡単に確認してください。







スニペット



スニペットは、コードの記述を高速化できる一種のマクロ/テンプレートです。 間違いなく必見です。







オブジェクトの迅速な作成:







カスタムオブジェクト







コメントベースのヘルプの魚:







助けて







コマンドレットが多数のパラメーターを渡す必要がある場合は、 splatting使用するのが理にかなっています

これが彼のスニペットです。







飛び散る







Ctrl + Alt + Jで使用可能なすべてのスニペットを表示します。







スニペット







その後、周囲の環境を改善し続けたいと思っていたが、 wasp-lists nirazuについて聞いたことがない場合は、ここにいます 。 また、PowerShellスクリプトを作成するときに便利な独自の拡張機能セットがある場合は、コメントにそのリストが表示されてうれしいです。







性能



パフォーマンスのトピックは、一見すると思えるほど単純ではありません。 一方で、時期尚早な最適化により、コードの可読性と保守性が大幅に低下し、スクリプト実行時間が300ミリ秒節約されます。通常の実行時間は10分であり、この場合の使用は間違いなく破壊的です。 一方、コードの可読性と操作の速度の両方を向上させるいくつかの非常に単純なトリックがあり、これらは継続的に使用するのに非常に適切です。 以下にそれらのいくつかについて説明しますが、パフォーマンスがあなたにとってすべてであり、メンテナンス中のサービスのダウンタイムの厳しい時間制限のために読みやすさが途中である場合は、 専門の文献を参照することをお勧めします。







パイプラインとforeach



生産性を高める最も簡単で常に機能する方法は、パイプの使用を避けることです。 型の安全性とPowerShellのための作業の利便性により、要素をパイプに渡すと、各要素がオブジェクトにラップされます。 ドットネット言語では、この動作はボクシングと呼ばれます。 ボクシングは良いです、それはセキュリティを保証しますが、それはそれ自身の価格を持っています、それは時々支払う意味がありません。







最初のステップは、 Foreach-Object



すべてのアプリケーションを削除し、foreachステートメントに置き換えることにより、生産性を向上させ、読みやすさを向上させることです。 foreach



Foreach-Object



エイリアスであるため、これらは実際には2つの異なるエンティティであるという事実に混乱する可能性があります-実際には、主な違いはforeach



はパイプラインから値を受け入れず、同時に最大3倍の速度で動作することです。







問題を想像してください。たとえば、大きなログを処理してその派生物を作成する必要があります。たとえば、その中の一連のエントリを選択して別の形式にする必要があります。







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





上記の例では、タスクの最初の段階-必要なレコードの選択が最もわかりやすい方法で合格しました。これは認知の容易さと表現力に優れています。同時に、生産性を大幅に低下させる場所、つまりパイプラインが含まれています。または、パイプライン自体が原因ではなく、コマンドレットの動作であると言われますGet-Content



パイプラインでデータを転送するには、ファイルを1行ずつ読み取り、ログの各行をベースタイプから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'
      
      





:







リードカウント







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







ストリームリーダー







— "", — 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 :







ダンプ付きストリームリーダー







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







— , , ; , , , :









: - , .







Jobs



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







ssd

これは、新しくインストールされたHyper-VのWindows Server 2019から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
      
      





, — , . .







, :







仕事

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







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







仕事が死ぬ







. , — , . — , .







, , , - .







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:







ビンビン







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







カクサム







結論の代わりに



PowerShell — Windows-. , , , .







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







— .







落ち着いた







PS Boomburumは、2019年のPowerShell構文の強調表示をサポートしていません-ダッシュ文字列。








All Articles