PowerShell, dump dari pengalaman saya

Pendahuluan


Artikel ini ditujukan kepada mereka yang sudah terbiasa dengan dasar-dasar PowerShell, menjalankan beberapa skrip dengan stackexchange dan mungkin memiliki file teks mereka sendiri dengan berbagai potongan untuk memudahkan pekerjaan sehari-hari. Tujuan penulisan ini adalah untuk mengurangi entropi, meningkatkan keterbacaan dan pemeliharaan kode PowerShell yang digunakan di perusahaan Anda, dan, sebagai akibatnya, meningkatkan produktivitas administrator yang bekerja dengannya.


kdpv


Di tempat kerja saya sebelumnya, karena spesifik tugas dan ketidaksempurnaan dunia, saya sangat memompa keterampilan bekerja dengan PowerShell. Setelah pergantian pekerjaan, banyak dari jenis ini menurun secara signifikan dan segala sesuatu yang berada di ujung jari mulai tenggelam lebih dalam dan lebih dalam di bawah pengalaman memecahkan masalah baru dengan teknologi baru. Dari sini, artikel ini mengklaim hanya apa yang dinyatakannya sendiri, mengungkapkan daftar topik yang, menurut pendapat saya, akan berguna bagi saya sendiri sekitar 7 tahun yang lalu, ketika kenalan saya dengan alat ini baru saja dimulai.


Jika Anda tidak mengerti mengapa PowerShell adalah shell berorientasi objek, bonus seperti apa yang datang dan mengapa itu diperlukan, saya akan memberi tahu Anda, terlepas dari para pembenci, sebuah buku bagus yang dengan cepat memperkenalkan inti dari lingkungan ini - Andrei Popov, Pengantar Windows PowerShell . Ya, ini tentang versi lama PS, ya, bahasanya telah mendapatkan beberapa ekstensi dan peningkatan, tetapi buku ini bagus karena ketika menggambarkan tahap awal pengembangan lingkungan ini, tanpa disadari hanya menekankan hal-hal mendasar. Gula sintaksis yang telah ditumbuhi oleh lingkungan, saya pikir, Anda dengan cepat merasakannya tanpa memahami bagaimana konsep itu bekerja. Membaca buku ini hanya akan membawa Anda beberapa malam, kembali setelah membaca.


popov


Buku ini juga tersedia di situs web penulis, meskipun saya tidak yakin bagaimana lisensi penggunaan ini: https://andpop.ru/courses/winscript/books/posh_popov.pdf


Panduan gaya


Merancang skrip menurut styleguides adalah praktik yang baik dalam semua kasus penerapannya, hampir tidak ada dua pendapat. Beberapa ekosistem telah mengatasi hal ini pada level penyetelan asli, pep8 di komunitas Python dan pergi di Golang menjadi jelas. Ini adalah alat penghemat waktu yang sangat berharga, yang, sayangnya, tidak ada dalam paket PowerShell standar, dan karenanya mentransfer masalah ke kepala kami. Saat ini, satu-satunya cara untuk memecahkan masalah pemformatan kode seragam adalah dengan mengembangkan refleks dengan berulang kali menulis kode yang memenuhi panduan gaya (pada kenyataannya, tidak).


Panduan gaya karena kurangnya resmi disetujui dan dijelaskan secara rinci oleh Microsoft lahir di komunitas selama masa PowerShell v3 dan sejak itu mereka telah mengembangkan dalam bentuk terbuka di github: PowerShellPracticeAndStyle . Ini adalah repositori penting bagi siapa saja yang pernah menggunakan tombol "Simpan" di PowerShell.


Jika Anda mencoba memeras, itu mungkin akan turun ke poin-poin berikut:


  • PowerShell menggunakan PascalCase untuk menamai variabel, cmdlet, nama modul, dan hampir semuanya kecuali operator;
  • Operator bahasa seperti if , switch , break , process , -match ditulis dalam huruf yang sangat kecil;
  • Kurung keriting ditempatkan dalam satu - satunya cara yang benar , sebaliknya juga disebut gaya Kernigan dan Richie, memimpin ceritanya dari buku The C Programming Language ;
  • Jangan gunakan alias di mana pun selain sesi konsol interaktif, jangan menulis ps | ? processname -eq firefox | %{$ws=0}{$ws+=$_.workingset}{$ws/1MB} file skrip 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} ;
  • Tunjukkan nama parameter eksplisit, perilaku cmdlet dan tanda tangannya dapat berubah , ditambah ini menambahkan konteks untuk orang yang tidak terbiasa dengan cmdlet tertentu;
  • Rancang parameter untuk skrip panggilan, dan jangan menulis fungsi di dalam skrip dan baris terakhir memanggil fungsi ini dengan kebutuhan untuk mengubah nilai variabel global alih-alih menentukan parameter;
  • Tentukan [CmdletBinding ()] - ini akan memberikan cmdlet Anda flag -Debug dan -Debug dan banyak fitur berguna lainnya . Terlepas dari posisi kuat beberapa purist dalam komunitas, saya bukan pendukung yang menunjukkan atribut ini dalam fungsi inline sederhana dan filter yang terdiri dari beberapa baris literal;
  • Tulis bantuan berbasis komentar: satu kalimat, tautan tiket, contoh panggilan;
  • Tentukan versi PowerShell yang diperlukan di bagian #requires ;
  • Gunakan Set-StrictMode -Version Latest , ini akan membantu Anda menghindari masalah yang dijelaskan di bawah ini ;
  • Menangani kesalahan;
  • Jangan buru-buru menulis ulang semuanya di PowerShell. PowerShell pada dasarnya adalah sebuah shell dan memanggil binari adalah tugas langsungnya. Tidak ada yang salah dengan menggunakan robocopy dalam sebuah skrip, tentu saja ini bukan rsync, tetapi juga sangat bagus.

Bantuan Berbasis Komentar


Di bawah ini adalah contoh cara mendapatkan skrip bantuan. Script membingkai gambar yang membawanya ke kotak dan melakukan perubahan ukuran, saya pikir Anda memiliki tugas membuat avatar untuk pengguna (kecuali mungkin rotasi menurut data exif). Ada contoh penggunaan di bagian .EXAMPLE , cobalah. Karena PowerShell dijalankan oleh runtime bahasa umum, sama seperti bahasa dotnet lainnya, PowerShell memiliki kemampuan untuk menggunakan kekuatan penuh dari perpustakaan 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 ) 

Skrip di atas dimulai dengan komentar multi-baris <# ... #> , jika komentar ini didahulukan dan berisi kata kunci tertentu, PowerShell akan secara otomatis membangun bantuan untuk skrip. Jenis bantuan ini secara harfiah disebut - bantuan berdasarkan komentar :


bantuan


Selain itu, ketika skrip dipanggil, tooltips untuk parameter akan berfungsi, apakah itu konsol PowerShell atau editor kode:


bantuan sebaris


Sekali lagi saya menarik perhatian pada fakta bahwa dia tidak boleh diabaikan. Jika Anda tidak tahu apa yang harus ditulis di sana, tulis sesuatu, pergi ke pendingin dan setelah kembali Anda pasti akan memiliki pemahaman tentang apa yang perlu diubah dalam tulisan. Itu bekerja. Tidak layak secara fanatik mengisi semua kata kunci, PowerShell dirancang untuk mendokumentasikan diri sendiri dan jika Anda memberi nama yang bermakna dan lengkap ke parameter, kalimat pendek di bagian .SYNOPIS dan satu contoh sudah cukup.


Mode yang ketat


PowerShell, seperti banyak bahasa skrip lainnya, memiliki pengetikan dinamis. Jenis pengetikan ini memiliki banyak pendukung: menulis logika tingkat tinggi yang sederhana namun kuat adalah masalah beberapa menit, tetapi ketika keputusan Anda mulai mencapai ribuan baris, Anda pasti akan menghadapi kerapuhan pendekatan ini.


Secara otomatis menghasilkan tipe yang selalu pada tahap pengujian, yang membentuk sebuah array di tempat Anda selalu menerima set elemen, pasti akan meletakkan babi jika Anda mendapatkan satu elemen dan dalam kondisi berikut, alih-alih memeriksa jumlah elemen, Anda akan mendapatkan jumlah karakter atau atribut lain, tergantung pada jenis barang. Logika skrip akan pecah, dan runtime akan berpura-pura bahwa semuanya baik-baik saja.


Menetapkan mode ketat membantu menghindari beberapa masalah seperti ini, tetapi juga memerlukan sedikit lebih banyak kode dari Anda, seperti menginisialisasi variabel dan melakukan casting secara eksplisit.


Mode ini diaktifkan oleh cmdlet Set-StrictMode -Version Latest , meskipun ada opsi lain untuk "rigor", pilihan saya adalah menggunakan yang terakhir.


Pada contoh di bawah ini, mode ketat menangkap panggilan ke properti yang tidak ada. Karena hanya ada satu elemen di dalam folder, jenis variabel $Input sebagai hasil eksekusi akan menjadi FileInfo , dan bukan array yang diharapkan dari elemen yang sesuai:


ketat


Untuk menghindari masalah seperti itu, Anda harus secara eksplisit membuang hasil cmdlet ke array:


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

Buat aturan untuk selalu mengatur mode ketat, ini akan memungkinkan Anda untuk menghindari hasil yang tidak terduga dari menjalankan skrip Anda.


Menangani kesalahan


ErrorActionPreference


Ketika melihat skrip orang lain, misalnya, di github, saya sering melihat mengabaikan mekanisme penanganan kesalahan, atau secara eksplisit mengaktifkan mode melanjutkan diam jika terjadi kesalahan. Masalah penanganan kesalahan tentu bukan yang paling mudah untuk diprogram pada umumnya dan dalam skrip pada khususnya, tetapi jelas tidak layak untuk diabaikan. Secara default, PowerShell jika ada kesalahan yang menampilkannya dan terus bekerja (saya sedikit menyederhanakan konsepnya, tetapi di bawah ini adalah tautan ke buku git tentang topik ini). Ini nyaman jika Anda sangat perlu mendistribusikan pembaruan program yang banyak digunakan di domain ke semua mesin, tanpa menunggu hingga menyebar ke semua titik penyebaran SCM atau menyebar dengan cara lain yang digunakan oleh Anda. Tidak menyenangkan mengganggu dan memulai ulang proses jika salah satu mesin dimatikan, ini benar.


Di sisi lain, jika Anda membuat cadangan kompleks dari suatu sistem yang terdiri dari lebih dari satu file data lebih dari satu bagian dari sistem informasi, Anda perlu memastikan bahwa cadangan Anda konsisten dan bahwa semua set data yang diperlukan disalin tanpa kesalahan.


Untuk mengubah perilaku cmdlet jika terjadi kesalahan, ada variabel global $ErrorActionPreference , dengan daftar nilai-nilai yang mungkin berikut: Stop, Enquire, Continue, Suspend, SilentlyContinue .


Saya sarankan Anda selalu menggunakan nilai Stop ketika jumlah skrip atau kerumitannya berhenti disimpan di tumpukan di kepala Anda, lebih baik untuk memastikan bahwa dalam situasi yang tidak terduga, skrip akan menghentikan pekerjaannya, dan tidak mematahkan kayu bakar β€œdengan tenang”, menyelesaikan eksekusi β€œdengan sukses”.


Selain menghentikan skrip jika terjadi kesalahan, ada prasyarat lain untuk penggunaannya - menangani situasi luar biasa. Ada konstruksi try/catch untuk ini, tetapi hanya berfungsi jika kesalahan menyebabkan eksekusi berhenti. Tidak perlu berhenti harus diaktifkan di tingkat seluruh skrip, ErrorAction dapat diatur pada tingkat cmdlet dengan parameter:


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

Sebenarnya, kemungkinan ini mendefinisikan dua strategi logis: menyelesaikan semua kesalahan "secara default" dan mengatur ErrorAction hanya untuk tempat-tempat kritis di mana untuk menanganinya; aktifkan di tingkat keseluruhan skrip dengan menetapkan nilai variabel global dan pengaturan -ErrorAction 'Continue' pada operasi yang tidak kritis. Saya selalu memilih opsi kedua, saya tidak terburu-buru untuk memaksakannya pada Anda, saya sarankan hanya sekali untuk memahami masalah ini dan menggunakan alat yang bermanfaat ini.


coba / tangkap


Di penangan kesalahan, Anda dapat mencocokkan dengan jenis pengecualian dan beroperasi dengan utas eksekusi atau, misalnya, menambahkan sedikit informasi lebih lanjut. Terlepas dari kenyataan bahwa dengan menggunakan operator try/catch/throw/trap Anda dapat membangun seluruh alur eksekusi skrip, Anda harus menghindari ini secara kategoris, karena metode operasi dengan eksekusi seperti itu tidak hanya dianggap sebagai antipater ekstrim dari kategori "goto-mie", jadi juga menguras kinerja secara drastis.


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

Perlu dicatat bahwa operator trap adalah perangkap kesalahan global. Ia menangkap semua yang belum diproses pada level yang lebih rendah, atau dikeluarkan dari handler pengecualian karena ketidakmungkinan untuk memperbaiki situasi secara independen.


Selain pendekatan berorientasi objek pengecualian yang dijelaskan di atas, PowerShell juga menyediakan konsep yang lebih akrab yang kompatibel dengan shell "klasik" lainnya, seperti stream kesalahan, kode kembali, dan variabel akumulasi kesalahan. Semua ini tentu saja nyaman, kadang-kadang tanpa alternatif, tetapi melampaui ruang lingkup ini, secara keseluruhan, topik ikhtisar. Untungnya ada buku terbuka yang bagus tentang github tentang hal ini.


Kode logger yang saya gunakan ketika tidak ada kepastian bahwa sistem akan memiliki PowerShell 5 (di mana Anda dapat menggambarkan kelas logger lebih mudah), cobalah, ini dapat berguna bagi Anda karena kesederhanaan dan singkatnya, Anda pasti akan menambahkan metode tambahan tanpa kesulitan. :


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

Saya ulangi gagasan itu - jangan abaikan penanganan kesalahan. Ini akan menghemat waktu dan saraf Anda dalam jangka panjang.
Jangan berpikir bahwa menjalankan skrip tidak peduli apa yang baik. Bagus - saatnya jatuh tanpa merusak kayu bakar.


Alat-alatnya


Penting untuk mulai meningkatkan alat untuk bekerja dengan PowerShell dari konsol emulator. Saya sering mendengar dari pendukung tawon alternatif bahwa konsol di windows buruk dan bahwa ini bukan konsol sama sekali, tetapi dos dan sebagainya. Hanya sedikit yang dapat merumuskan klaim mereka tentang masalah ini secara memadai, tetapi jika seseorang berhasil, maka pada kenyataannya ternyata semua masalah dapat diselesaikan. Sudah ada lebih banyak informasi tentang terminal dan konsol baru di windows pada hub, semuanya lebih dari ok di sana .


Langkah pertama adalah menginstal Conemu atau rakitan Cmdernya, yang tidak terlalu penting, karena menurut saya itu layak dijalankan melalui pengaturan. Saya biasanya memilih cmder dalam konfigurasi minimal, tanpa gita dan binari lain, yang saya atur sendiri, meskipun selama beberapa tahun saya menyetel konfigurasi saya untuk Conemu murni. Ini benar-benar emulator terminal terbaik untuk windows, memungkinkan Anda untuk membagi layar (untuk tmux / pecinta layar), membuat tab dan mengaktifkan mode konsol gaya gempa:


Conemu


cmder


Langkah selanjutnya saya sarankan untuk meletakkan modul: oh-my-posh , posh-git dan PSReadLine . Dua yang pertama akan membuat prom lebih menyenangkan dengan menambahkan informasi tentang sesi saat ini, status perintah yang terakhir dieksekusi, indikator hak istimewa dan status repositori git di lokasi saat ini. PSReadLine sangat meningkatkan promt, menambahkan misalnya pencarian pada sejarah perintah yang dimasukkan (CRTL + R) dan tips mudah untuk cmdlet pada CRTL + Space:


readline


Dan ya, sekarang konsol dapat dihapus dengan CTRL + L, dan lupakan cls .


Kode studio visual


Editor. Semua hal terburuk yang dapat saya katakan tentang PowerShell adalah murni PowerShell ISE, mereka yang melihat versi pertama dengan tiga panel tidak mungkin melupakan pengalaman ini. Pengkodean terminal yang berbeda, kurangnya fitur editor dasar, seperti lekukan otomatis, tanda kurung tutup otomatis, pemformatan kode, dan seluruh rangkaian antipattern yang dihasilkan olehnya tentang hal yang tidak akan saya beri tahu (hanya untuk berjaga-jaga) - ini semua tentang ISE.


Jangan menggunakannya, gunakan Visual Studio Code dengan ekstensi PowerShell - ada segala sesuatu yang tidak Anda inginkan (tentu saja dalam batas yang wajar). Dan jangan lupa bahwa dalam PoweShell hingga versi keenam (PowerShell Core 6.0) pengkodean untuk skrip adalah UTF8 BOM, jika tidak bahasa Rusia akan pecah.


vscode


Selain penyorotan sintaks, tip alat dan kemampuan debugging skrip, plugin memasang linter yang juga akan membantu Anda mengikuti praktik yang telah ada di komunitas, misalnya, memperluas pintasan dengan satu klik (pada bola lampu). Bahkan, ini adalah modul reguler yang dapat diinstal secara independen, misalnya, tambahkan ke skrip penandatanganan pipa Anda: PSScriptAnalyzer


PSScriptAnalyzer


Anda dapat mengatur parameter pemformatan kode dalam pengaturan ekstensi, untuk semua pengaturan (baik editor dan ekstensi) ada pencarian: File - Preferences - Settings :


Otb


Untuk mendapatkan konsol conpty baru Anda harus mengatur bendera di pengaturan , nanti, mungkin, saran ini tidak akan relevan.


Patut diingat bahwa tindakan apa pun dalam Kode VS dapat dilakukan dari pusat kontrol, yang disebut oleh CTRL + Shift + P. Memformat sepotong kode yang dimasukkan dari obrolan , mengurutkan menurut abjad, mengubah indentasi dari spasi ke tab, dan seterusnya, semua di pusat kontrol.


Misalnya, aktifkan layar penuh dan pusat editor:


tata letak


Kontrol sumber Git, SVN


Seringkali, administrator sistem Windows memiliki fobia resolusi konflik dalam sistem kontrol versi, mungkin karena jika seorang perwakilan dari himpunan ini menggunakan git, maka seringkali seseorang tidak mengalami masalah seperti ini. Dengan vscode, resolusi konflik berkurang menjadi klik mouse pada bagian-bagian kode yang perlu dibiarkan atau diganti.


bergabung


Label-label antara garis 303 dan 304 ini dapat diklik, perlu mengklik semua yang muncul dalam dokumen jika terjadi konflik, komit melakukan perubahan dan mengirimkan perubahan ke server. U - Kenyamanan.


Tentang cara bekerja dengan sistem kontrol versi tersedia dan dengan gambar-gambar yang ditulis di dok vscode , buka mata Anda di sana secara singkat dan baik.


Cuplikan


Cuplikan adalah sejenis makro / templat yang memungkinkan Anda mempercepat penulisan kode. Pasti harus melihat.


Pembuatan objek cepat:


objek khusus


Ikan untuk bantuan berbasis komentar:


bantuan


Jika cmdlet perlu melewati sejumlah besar parameter, masuk akal untuk menggunakan splatting .
Ini cuplikan untuknya:


menampar


Lihat semua cuplikan yang tersedia yang tersedia oleh Ctrl + Alt + J:


cuplikan


Jika setelah itu Anda memiliki keinginan untuk terus meningkatkan lingkungan Anda, tetapi Anda belum pernah mendengar tentang tab tawon, maka di sinilah Anda sekarang . Juga, jika Anda memiliki set ekstensi Anda sendiri yang berguna bagi Anda saat menulis skrip PowerShell, saya akan senang melihat daftar mereka di komentar.


Performa


Topik kinerja tidak sesederhana yang tampak pada pandangan pertama. Di satu sisi, optimasi prematur dapat sangat mengurangi keterbacaan dan pemeliharaan kode, menghemat 300ms waktu eksekusi skrip, waktu run yang biasa dapat sepuluh menit, penggunaannya dalam kasus ini jelas merusak. Di sisi lain, ada beberapa trik yang cukup sederhana yang meningkatkan keterbacaan kode dan kecepatannya, yang cukup tepat untuk digunakan secara berkelanjutan. Di bawah ini saya akan berbicara tentang beberapa dari mereka, jika kinerja adalah segalanya untuk Anda, dan keterbacaan berjalan di sisi karena batas waktu yang ketat dari downtime layanan selama pemeliharaan, saya sarankan Anda merujuk pada literatur yang relevan .


Pipeline dan foreach


Cara termudah dan selalu bekerja untuk meningkatkan produktivitas adalah menghindari penggunaan pipa. Karena jenis keamanan dan kenyamanan demi kekuatan, elemen melewati PowerShell melalui pipa membungkus mereka masing-masing dalam suatu objek. Dalam bahasa dotnet, perilaku ini disebut tinju . Boxing itu bagus, ia menjamin keamanan, tetapi memiliki harga sendiri, yang terkadang tidak masuk akal untuk dibayar.


Langkah pertama adalah meningkatkan kinerja dan, menurut saya, meningkatkan keterbacaan dengan menghapus semua aplikasi Foreach-Object dan menggantinya dengan pernyataan foreach. Anda mungkin malu dengan fakta bahwa ini sebenarnya adalah dua entitas yang berbeda, karena foreach adalah alias untuk Foreach-Object - dalam praktiknya, perbedaan utama adalah bahwa foreach tidak menerima nilai dari sebuah pipa, tetapi ia bekerja berdasarkan pengalaman hingga tiga kali lebih cepat.


: - , , :


 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 ) 


β€” , , ; , , , :


  • 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


Alih-alih sebuah kesimpulan


PowerShell β€” Windows-. , , , .


, , "PowerShell, ", . , β€” , . . , - , - .


β€” .


calm


PS Boomburum , 2019 powershell β€” .

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


All Articles