πŸ“œ ⬆️ ⬇️

PowerShell, dump my experience

Introduction


This article is addressed to those who have already become familiar with the basics of PowerShell, run some scripts with stack exchange, and probably have their own text file with certain snippets that facilitate everyday work. The purpose of writing it is to reduce entropy, increase readability and maintainability of the PowerShell code used in your company and, as a result, increase the productivity of the administrator working with it.


kdpv


At my previous place of work, I, due to the specifics of the tasks and the imperfection of the world, have greatly pumped my skills with PowerShell. After the change of work, this kind of load has greatly decreased, and everything that was just about at the fingertips began to sink deeper under the experience of solving new problems on new technologies. From this article claims to be only what he declares himself, revealing a list of topics that in my opinion would be useful to me about 7 years ago, when my acquaintance with this tool was just beginning.


If you don’t understand why PowerShell is an object-oriented shell, what bonuses come from it and why it is necessary at all, despite the cries of haters, I will advise you a good book that quickly introduces this medium - Popov Andrei Vladimirovich, Introduction to Windows PowerShell . Yes, it is about the old version of PS, yes, the language has gained some extensions and improvements, but this book is good because describing the early stage of development of this environment involuntarily focuses only on fundamental things. The syntactic sugar, which overgrown with the environment, I think you quickly understand without understanding how the concept itself works. Reading this book will take you literally a couple of evenings, come back after reading.


popov


The book is also available on the author’s website, although I’m not sure how licensed the use is: https://andpop.ru/courses/winscript/books/posh_popov.pdf


Style Guides


Making the scripts according to the style guides is a good practice in all cases of its use, there can hardly be two opinions. Some ecosystems have taken care of this at the level of native tilling, from the obvious, pep8 comes to mind in the Python community and go fmt in Golang. These are invaluable time-saving tools that unfortunately are missing in the standard PowerShell delivery, and that doesn’t transfer the problem to your head. The only way to solve the problem of uniform code formatting at the moment is to generate reflexes by repeatedly writing code that satisfies the style guide (actually, no).


Due to the lack of officially approved and described in detail by Microsoft, the Stylguides were born in the community at the time of PowerShell v3 and have since developed in an open form on the github: PowerShell PracticeAndStyle. This is a worthwhile repository for anyone who has ever used the "Save" button in PowerShell ise.


If you try to make a squeeze, it will probably be reduced to the following points:



Comment Based Help


Below is an example of how to issue help script. The script frames the image by bringing it to the square and performs a resize, I think you have a task to make avatars for users (except for turning according to exif). In the .EXAMPLE section .EXAMPLE is an example of use, try it. Because PowerShell is executed by the common language runtime, the same as other dotnet languages, it has the ability to use the full power of the dotnet libraries:


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

The script above begins with a multi-line comment <# ... #> , in the event that this comment comes first and contains certain keywords, PowerShell will guess to automatically build help for the script. This kind of help is literally called - a reference based on a comment :


help


Moreover, when calling the script, hints on parameters will work, be it a PowerShell console, be it a code editor:


inline help


Once again I will draw attention to the fact that she should not be neglected. If you do not know what to write there, write something, go to the cooler and on return you will have an understanding of what needs to be changed in the written. It works. You shouldn't fanatically fill in all the keywords, PowerShell is designed to be self-documenting and if you gave meaningful and full names to the parameters, a short sentence in the .SYNOPIS section and one example will be enough.


Strict mode


PowerShell, like many other scripting languages, has dynamic typing. There are many supporters of this type of typing: writing simple but powerful high-level logic is a matter of a couple of minutes, but when your decision starts to creep up to a thousand lines, you will definitely encounter the fragility of this approach.


The auto-output of types at the testing stage, the formed array in the place where you always received a set of elements, will surely enclose a pig if it receives one element and now in the following condition, instead of checking the number of elements you will receive the number of characters or another attribute, depending on item type The script logic will break, while the execution environment will pretend that everything is fine.


Setting a strict mode helps to avoid some of this kind of problem, but it also requires a bit more code from you, like initializing variables and explicit casting.


This mode is enabled by the Set-StrictMode -Version Latest cmdlet, although there are other options for "strictness", my choice is to use the latter.


In the example below, strict mode catches a call to a non-existent property. Since there is only one element inside the folder, the type of $Input as a result of execution will be FileInfo , and not the expected array of corresponding elements:


strict


To avoid such a problem, the result of running the cmdlet should explicitly result in an array:


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

Make it a rule to always set strict mode, this will allow you to avoid unexpected results of executing your scripts.


Error processing


ErrorActionPreference


Looking through other people's scripts, for example on a githaba, I often see either a complete disregard for the error handling mechanism, or the explicit inclusion of the silent continuation mode in case of an error. The issue of error handling is certainly not the easiest to program in general and in scripts in particular, but it definitely does not deserve ignoring. By default, PowerShell in case of an error displays it and continues to work (I simplified the concept a bit, but below will be a link to a git book on this topic). This is convenient if you urgently need to extend the update of a program that is widely used in the domain to all machines, without waiting for it to spill over to all sccm deployment points or spread in another way used by you. It is unpleasant to interrupt and restart the process in case one of the machines is turned off, this is true.


On the other hand, if you are doing a complex backup of a system consisting of more than one data file of more than one part of an information system, you need to be sure that your backup is consistent and that all the necessary data sets have been copied without errors.


To change the behavior of cmdlets in the event of an error, there is a global variable $ErrorActionPreference , with the following list of possible values: Stop, Inquire, Continue, Suspend, SilentlyContinue .


I recommend always using the Stop value when the number of scripts or their complexity ceases to remain on the stack in the head, it is better to be sure that in any unforeseen situation the script will stop its work and not break the firewood "quietly", having completed the execution "successfully."


In addition to stopping the script, if something went wrong, there is another prerequisite for its use - the handling of exceptional situations. There is a try/catch construct for this, but it only works if the error causes the execution to stop. The stop must not be enabled at the level of the entire script, ErrorAction can be set at the cmdlet level by the parameter


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

Actually, this possibility defines two logical strategies: resolve all errors "by default" and set the ErrorAction only for critical places where they should be processed; or include at the level of the entire script by setting the value of a global variable and set -ErrorAction 'Continue' on non-critical operations. I always choose the second option, I do not rush to impose it on you, I recommend only once to sort out this issue and use this useful tool.


try / catch


In the error handler, you can match by the type of exception and operate with the flow of execution, or, for example, add a little more information. Although using the try/catch/throw/trap you can build the entire flow of script execution, you should categorically avoid this, since this way of operating with the execution is not only considered an extreme antipattern, from the category of "goto-noodles", It also drains performance a lot.


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

It is worth noting the operator trap - this is a global error trap. She catches everything that has not been processed at lower levels, or is thrown out of the exception handler due to the impossibility of correcting the situation on her own.


In addition to the object-oriented exception approach described above, PowerShell also provides more familiar concepts that are compatible with other "classic" shells, such as error streams, return codes, and variables accumulating errors. All this is certainly convenient, sometimes without alternative, but beyond the scope of this, generally overview, topic. Fortunately, there is a good open book on github on this topic.


The logger code that I use when there is no certainty that PowerShell 5 will be in the system (where you can describe the logger class more conveniently), try it, it can be useful to you because of its simplicity and brevity, you will surely add additional methods. :


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

I repeat the idea - do not ignore error handling. It will save your time and nerves in the long run.
Do not think that the execution of the script in spite of everything is good. Well - it's time to fall without breaking the wood.


Instruments


It’s definitely worth starting an improvement on tools for working with PowerShell from a console emulator. I have often heard from supporters of alternative OS that the console in windows is bad and that it is not a console at all, but dos and so on. Few people could adequately formulate their claims to this effect, but if someone succeeded, then in fact it turned out that all problems could be solved. More about the terminals and the new console in windows on the HabrΓ© already, everything is more than ok .


The first step is to install Conemu or its Cmder assembly, which is not particularly important, since in my opinion it’s worth running through the settings anyway. I usually choose the cmder in the minimum configuration, without the gita and other binaries that I set myself, although I have been configuring my own config for pure Conemu for several years. This is really the best terminal emulator for windows, allowing you to split the screen (for tmux / screen fans), create tabs and enable the quake-style console mode:


Conemu


cmder


The next step I recommend to put the modules: oh-my-posh , posh-git and PSReadLine . The first two will make promt more pleasant by adding information about the current session, the status of the last command executed, the privilege indicator and the status of the git repository in the current location. PSReadLine pumps up proms heavily by adding for example a search through the history of commands entered (CRTL + R) and handy hints for cmdlets on CRTL + Space:


readline


And yes, now the console can be cleared with CTRL + L, and forget about cls .


Visual studio code


Editor. All the worst that I can say about PowerShell belongs exclusively to PowerShell ISE, those who have seen the first version with three panels will hardly forget this experience. The different terminal coding, lack of basic features of the editor, such as automatic indent, auto-closing brackets, code formatting, and a whole set of antipatterns generated by it about which I will not tell you (just in case) are all about ISE.


Do not use it, use Visual Studio Code with the PowerShell extension - there’s everything you want (within reasonable limits, of course). And do not forget that in PoweShell up to the sixth version (PowerShell Core 6.0) the encoding for scripts is UTF8 BOM, otherwise the Russian language will break.


vscode


In addition to syntax highlighting, method hints, and script debugging capabilities, the plugin installs a linter that will also help you follow established practices in the community, for example, in one click (on a light bulb), expand the abbreviations. In fact, this is a normal module that can be installed independently, for example, adding it to your script signature pipe : PSScriptAnalyzer


PSScriptAnalyzer


You can set the code formatting options in the extension settings, for all settings (and the editor and extensions) there is a search: File - Preferences - Settings :


Otbs


In order to get a new conpty console, you should set the flag in the settings ; later, this advice will probably be irrelevant.


It is worth remembering that any action in VS Code can be performed from the control center called by the combination CTRL + Shift + P. Format the piece of code inserted from the chat , sort the lines alphabetically, change the index from spaces to tabs and so on, all this in the control center.


For example, enable the full screen and the editor layout in the center:


layout


Source Control; Git svn


Often, Windows system administrators have a conflict resolution phobia in version control systems, probably from the fact that if a representative of this set uses git, it is often one and does not encounter any problems of this kind. With vscode, conflict resolution is literally reduced to mouse clicks on those parts of the code that need to be left or replaced.


merge


Here these inscriptions between 303 and 304 lines are clickable, you should click on all such that appear in the document in case of conflict, commit commit changes and send the changes to the server. U - Convenience.


About how to work with version control systems is available and with pictures written in the vscode dock , run your eyes to the end there briefly and well.


Snippets


Snippets - a kind of macros / templates to speed up the writing of code. Definitely a must see.


Quick object creation:


customobject


Fish for comment-based help:


help


In the event that the cmdlet needs to pass a large number of parameters, it makes sense to use splatting .
Here is a snippet for it:


splatting


View all available snippets available by Ctrl + Alt + J:


snippets


If after this you have a desire to continue to improve your environment, but you haven’t heard about wasp-sheets yet, then here you are . Also, if you have your own set of extensions that you can use when writing PowerShell scripts, I will be glad to see their list in the comments.


Performance


The topic of performance is not as simple as it may seem at first glance. On the one hand, premature optimizations can greatly reduce readability and maintainability of the code, saving 300ms of script execution time, the usual time of which can be ten minutes, their use in this case is certainly destructive. On the other hand, there are several fairly simple techniques that increase both the readability of the code and the speed of its work, which are quite appropriate to use on an ongoing basis. Below I will talk about some of them, in case if the performance is all for you, and readability takes a back seat due to the severe time constraints of service downtime during the service, I recommend referring to the relevant literature.


Pipeline and foreach


The easiest and always working way to increase productivity is to avoid using the pipes. Because of type safety and convenience of operation, PowerShell skips elements through the pipe and wraps each of them into an object. In dotnet languages, this behavior is called boxing . Boxing is good, it guarantees security, but it has its own price, which sometimes makes no sense to pay.


The first step is to improve performance and, in my opinion, you can increase readability by removing all applications from the Foreach-Object and replacing it with the foreach operator. You can be embarrassed by the fact that they are actually two different entities, because foreach is an alias for Foreach-Object - in practice, the main difference is that foreach does not take values ​​from the pipeline, while it works by experience up to three times faster.


Imagine the task: we need to process a large log to form some of its derivative, for example, select and lead to a different format a number of records in it:


 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 ) 

Aftertaste


β€” , , ; , , , :



: - , , .


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


Instead of conclusion


PowerShell β€” Windows-. , , , .


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


β€” .


calm


PS Boomburum , 2019 powershell β€” .


')

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


All Articles