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.
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.
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
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:
if
, switch
, break
, process
, -match
are written in lowercase letters;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}
;-Verbose
and -Debug
flags and many other useful features . Despite the firmness of the position of some purists in the community, I am not in favor of indicating this attribute in simple inline functions and filters consisting of literal several lines;#requires
section;Set-StrictMode -Version Latest
to help you avoid the problems described below ;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 :
Moreover, when calling the script, hints on parameters will work, be it a PowerShell console, be it a code editor:
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.
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:
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.
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.
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.
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:
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:
And yes, now the console can be cleared with CTRL + L, and forget about cls
.
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.
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
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
:
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:
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.
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 - a kind of macros / templates to speed up the writing of code. Definitely a must see.
Quick object creation:
Fish for comment-based 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:
View all available snippets available by Ctrl + Alt + J:
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.
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.
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'
:
, . 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 :
StringBuilder
:
, , 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 )
β , , ; , , , :
foreach
Foreach-Object
;
;
/ , ;
StringBuilder
;
, - ;
( "" );
: - , , .
, , , , , , . . , IO, .
Windows Server 2019 Hyper-V ssd ( hdd):
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 ):
. , β , . β , .
, , , - .
β Beginning Use of PowerShell Runspaces: Part 1 . , β PowerShell , . (, PowerShell ), : ( ) . , .
WPF , PowerShell, . β , . β , "" . .
# $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 β .
Source: https://habr.com/ru/post/443256/
All Articles