1. Introdução
Este artigo é direcionado àqueles que já se familiarizaram com os conceitos básicos do PowerShell, executam alguns scripts com stackexchange e provavelmente possuem seu próprio arquivo de texto com vários trechos para facilitar o trabalho diário. O objetivo de redigir é reduzir a entropia, aumentar a legibilidade e a capacidade de manutenção do código do PowerShell usado em sua empresa e, como resultado, aumentar a produtividade do administrador que trabalha com ela.

No meu local de trabalho anterior, devido às especificidades das tarefas e à imperfeição do mundo, desenvolvi bastante a habilidade de trabalhar com o PowerShell. Após uma mudança de trabalho, cargas desse tipo diminuíram drasticamente e tudo o que estava ao redor do canto dos dedos começou a afundar cada vez mais com a experiência de resolver novos problemas com as novas tecnologias. A partir disso, este artigo afirma ser apenas o que se declara, revelando uma lista de tópicos que, na minha opinião, seriam úteis para mim cerca de 7 anos atrás, quando meu conhecimento dessa ferramenta estava apenas começando.
Se você não entende por que o PowerShell é um shell orientado a objetos, que tipo de bônus vem e por que é necessário, aconselhamo-lo, apesar dos inimigos, um bom livro que introduz rapidamente a essência desse ambiente - Andrey Vladimirovich Popov, Introdução ao Windows PowerShell . Sim, é sobre a versão antiga do PS, sim, a linguagem ganhou algumas extensões e melhorias, mas este livro é bom porque, ao descrever o estágio inicial de desenvolvimento desse ambiente, enfatiza, sem querer, apenas coisas fundamentais. Acho que o açúcar sintático com o qual o ambiente cresceu demais, você percebe rapidamente, sem entender como o conceito funciona. A leitura deste livro levará apenas algumas noites e volte depois da leitura.

O livro também está disponível no site do autor, embora não tenha certeza de quão licenciado é esse uso: https://andpop.ru/courses/winscript/books/posh_popov.pdf
Guias de estilo
Projetar scripts de acordo com os guias de estilo é uma boa prática em todos os casos de sua aplicação; dificilmente pode haver duas opiniões. Alguns ecossistemas cuidaram disso no nível do ajuste nativo, o pep8 na comunidade Python e o fmt em Golang chega ao óbvio. Essas são ferramentas valiosas de economia de tempo que, infelizmente, estão ausentes no pacote padrão do PowerShell e, portanto, transferem o problema para nossa cabeça. Atualmente, a única maneira de resolver o problema da formatação uniforme de código é desenvolver reflexos escrevendo repetidamente o código que satisfaz os guias de estilo (na verdade, não).
Os guias de estilo devido à falta de oficialmente aprovados e descritos em detalhes pela Microsoft nasceram na comunidade durante o PowerShell v3 e, desde então, vêm sendo desenvolvidos de forma aberta no github: PowerShellPracticeAndStyle . Este é um repositório digno de nota para quem já usou o botão "Salvar" no PowerShell ise.
Se você tentar fazer um aperto, ele provavelmente se resumirá aos seguintes pontos:
- O PowerShell usa o PascalCase para nomear variáveis, cmdlets, nomes de módulos e praticamente tudo, exceto operadores;
- Operadores de idioma como
if
, switch
, break
, process
, -match
são escritos em letras muito pequenas; - Os colchetes são colocados da única maneira verdadeira , também chamada de estilo de Kernigan e Richie, levando sua história do livro The C Programming Language ;
- Não use aliases em nenhum lugar que não seja uma sessão do console interativo, não escreva nenhum
ps | ? processname -eq firefox | %{$ws=0}{$ws+=$_.workingset}{$ws/1MB}
arquivo de script 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}
; - Indique nomes explícitos de parâmetros, o comportamento dos cmdlets e suas assinaturas podem mudar , além de adicionar um contexto a uma pessoa não familiarizada com um cmdlet específico;
- Projete os parâmetros para chamar scripts e não escreva uma função dentro do script e a última linha chama essa função com a necessidade de alterar os valores das variáveis globais em vez de especificar parâmetros;
- Especifique [CmdletBinding ()] - isso dará ao seu cmdlet
-Verbose
e -Debug
e muitos outros recursos úteis . Apesar da posição firme de alguns puristas na comunidade, não sou favorável a indicar esse atributo em funções e filtros inline simples que consistem em várias linhas literais; - Escreva uma ajuda baseada em comentários: uma frase, link de ticket, exemplo de chamada;
- Especifique a versão necessária do PowerShell na seção
#requires
; - Use
Set-StrictMode -Version Latest
, isso ajudará a evitar os problemas descritos abaixo ; - Lidar com erros;
- Não se apresse em reescrever tudo no PowerShell. O PowerShell é principalmente um shell e chamar binários é sua tarefa direta. Não há nada errado em usar robocopy em um script, é claro que não é rsync, mas também é muito bom.
Abaixo está um exemplo de como obter o script de ajuda. O script enquadra a imagem, trazendo-a para um quadrado e redimensionando, acho que você tem a tarefa de criar avatares para os usuários (exceto, talvez, a rotação de acordo com os dados exif). Há um exemplo de uso na seção .EXAMPLE
, tente. Devido ao fato de o PowerShell ser executado pelo Common Language Runtime, o mesmo que outros idiomas dotnet, ele tem a capacidade de usar todo o poder das bibliotecas 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 )
O script acima começa com um comentário de várias linhas <# ... #>
; se esse comentário ocorrer primeiro e contiver determinadas palavras-chave, o PowerShell criará automaticamente ajuda para o script. Esse tipo de ajuda é literalmente chamado - ajuda com base no comentário :

Além disso, quando o script é chamado, as dicas de ferramenta para os parâmetros funcionarão, seja no console do PowerShell ou no editor de código:

Mais uma vez, chamo a atenção para o fato de que ela não deve ser negligenciada. Se você não sabe o que escrever lá, escreva alguma coisa, vá para o refrigerador e, ao retornar, você definitivamente entenderá o que precisa ser alterado na escrita. Isso funciona. Não vale a pena fanaticamente preencher todas as palavras-chave, o PowerShell foi projetado para ser auto-documentado e, se você deu nomes significativos e completos aos parâmetros, uma breve frase na seção .SYNOPIS
e um exemplo são suficientes.
Modo estrito
O PowerShell, como muitas outras linguagens de script, possui digitação dinâmica. Esse tipo de digitação tem muitos apoiadores: escrever uma lógica de alto nível simples, porém poderosa, é questão de alguns minutos, mas quando sua decisão começa a chegar a mil linhas, você certamente encontrará a fragilidade dessa abordagem.
A saída automática de tipos invariavelmente no estágio de teste, que formava uma matriz no local em que você sempre recebe um conjunto de elementos, definitivamente colocará um porco no caso de você obter um elemento e na seguinte condição, em vez de verificar o número de elementos, o número de caracteres ou outro atributo, dependendo de tipo de item. A lógica do script será interrompida e o tempo de execução fingirá que está tudo bem.
Definir o modo estrito ajuda a evitar alguns desses tipos de problemas, mas também requer um pouco mais de seu código, como inicializar variáveis e converter explicitamente.
Esse modo é ativado pelo cmdlet Set-StrictMode -Version Latest
, embora existam outras opções para "rigor", minha escolha é usar o último.
No exemplo abaixo, o modo estrito captura uma chamada para uma propriedade inexistente. Como existe apenas um elemento dentro da pasta, o tipo da variável $Input
como resultado da execução será FileInfo
, e não a matriz esperada dos elementos correspondentes:

Para evitar esse problema, você deve converter explicitamente o resultado do cmdlet em uma matriz:
$Items = @( Get-ChildItem C:\Users\snd3r\Nextcloud )
Crie uma regra para sempre definir o modo estrito, para evitar resultados inesperados ao executar seus scripts.
Tratamento de erros
ErrorActionPreference
Ao examinar os scripts de outras pessoas, por exemplo, em um github, geralmente vejo ignorar completamente o mecanismo de tratamento de erros ou ativar explicitamente o modo de silêncio silencioso em caso de erro. O problema de manipulação de erros certamente não é o mais fácil de programar em geral e em scripts em particular, mas definitivamente não merece ser ignorado. Por padrão, o PowerShell em caso de erro o exibe e continua funcionando (simplifiquei um pouco o conceito, mas abaixo há um link para um livro git sobre este tópico). Isso é conveniente se você precisar distribuir com urgência a atualização de um programa amplamente usado no domínio para todas as máquinas, sem esperar até que ele se espalhe para todos os pontos da implantação do sccm ou se espalhe de outra maneira usada por você. É desagradável interromper e reiniciar o processo se uma das máquinas estiver desligada, isso é verdade.
Por outro lado, se você fizer um backup complexo de um sistema que consiste em mais de um arquivo de dados de mais de uma parte do sistema de informações, você precisa ter certeza de que seu backup é consistente e que todos os conjuntos de dados necessários foram copiados sem erros.
Para alterar o comportamento dos cmdlets no caso de um erro, existe uma variável global $ErrorActionPreference
, com a seguinte lista de valores possíveis: Parar, Consultar, Continuar, Suspender, Continuar silenciosamente .
Eu recomendo que você sempre use o valor Stop
quando o número de scripts ou sua complexidade deixar de ser armazenado na pilha em sua cabeça, é melhor garantir que, em qualquer situação imprevista, o script interrompa seu trabalho e não quebre a lenha "silenciosamente", concluindo a execução "com êxito".
Além de interromper o script, caso algo dê errado, há outro pré-requisito para seu uso - lidar com situações excepcionais. Existe uma construção try/catch
para isso, mas só funciona se o erro fizer com que a execução seja interrompida. Não necessariamente a parada deve ser ativada no nível de todo o script, ErrorAction
pode ser definido no nível do cmdlet pelo parâmetro:
Get-ChildItem 'C:\System Volume Information\' -ErrorAction 'Stop'
Na verdade, essa possibilidade define duas estratégias lógicas: resolva todos os erros "por padrão" e defina ErrorAction
apenas para locais críticos onde lidar com eles; habilite no nível de todo o script definindo o valor da variável global e definindo -ErrorAction 'Continue'
em operações não críticas. Eu sempre escolho a segunda opção, não tenho pressa de impor a você, recomendo apenas uma vez para entender esse problema e usar essa ferramenta útil.
tentar / pegar
No manipulador de erros, você pode corresponder pelo tipo de exceção e operar com um encadeamento de execução ou, por exemplo, adicionar um pouco mais de informação. Apesar de usar os operadores try/catch/throw/trap
, você pode construir todo o fluxo de execução de script, evite isso categoricamente, pois esse método de operação com execução não é considerado apenas um antipater extremo da categoria "goto-noodle", portanto também drena o desempenho drasticamente.
#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 }
Vale ressaltar que o operador de trap
é uma interceptação de erro global. Ele captura tudo o que não foi processado em níveis mais baixos ou é descartado do manipulador de exceções devido à impossibilidade de corrigir a situação independentemente.
Além da abordagem orientada a objeto das exceções descritas acima, o PowerShell também fornece conceitos mais familiares compatíveis com outros shells "clássicos", como fluxos de erros, códigos de retorno e variáveis de acumulação de erros. Tudo isso é certamente conveniente, às vezes sem alternativa, mas vai além do escopo deste tópico geral da visão geral. Felizmente, há um bom livro aberto no github sobre esse assunto.
O código do criador de logs que eu uso quando não há certeza de que o sistema terá o PowerShell 5 (onde você pode descrever a classe do criador de logs de maneira mais conveniente), experimente, pode ser útil devido à sua simplicidade e brevidade, você certamente adicionará métodos adicionais sem dificuldade. :
# " ", 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 }
Repito a ideia - não ignore o tratamento de erros. Isso economizará tempo e nervos a longo prazo.
Não pense que a execução do script não importa o que seja bom. Bom - é hora de cair sem quebrar a lenha.
As ferramentas
Vale a pena começar a melhorar as ferramentas para trabalhar com o PowerShell a partir do emulador de console. Ouvi muitas vezes de apoiadores de vespas alternativas que o console do Windows é ruim e que esse não é um console, mas dos e assim por diante. Poucos poderiam formular adequadamente suas reivindicações sobre esse assunto, mas se alguém conseguisse, na realidade, todos os problemas poderiam ser resolvidos. Já havia mais informações sobre os terminais e o novo console no Windows no hub; tudo está mais do que ok por lá .
O primeiro passo é instalar o Conemu ou seu assembly Cmder, o que não é particularmente importante, pois, na minha opinião, vale a pena percorrer as configurações de qualquer maneira. Normalmente escolho o cmder na configuração mínima, sem um gita e outros binários, os quais me propus, embora por vários anos tenha sintonizado minha configuração para o Conemu puro. Este é realmente o melhor emulador de terminal para Windows, permitindo que você divida a tela (para os amantes do tmux / screen), crie abas e ative o modo de console no estilo quake:
Conemu

A próxima etapa eu recomendo colocar os módulos: oh-my-posh , posh-git e PSReadLine . Os dois primeiros tornarão o baile mais agradável, adicionando informações sobre a sessão atual, o status do último comando executado, o indicador de privilégio e o status do repositório git no local atual. O PSReadLine aumenta bastante o prompt, adicionando, por exemplo, uma pesquisa no histórico de comandos inseridos (CRTL + R) e dicas convenientes para cmdlets no CRTL + Space:

E sim, agora o console pode ser limpo com CTRL + L e esquecer os cls
.
Código do Visual Studio
O editor. O pior que posso dizer sobre o PowerShell é puramente o PowerShell ISE; é improvável que aqueles que viram a primeira versão com três painéis esqueçam essa experiência. A codificação diferente do terminal, a falta de recursos básicos do editor, como recuo automático, colchetes de fechamento automático, formatação de código e todo um conjunto de antipadrões gerados por ele sobre os quais não vou contar (apenas no caso) - isso é tudo sobre o ISE.
Não use, use o Visual Studio Code com a extensão do PowerShell - há tudo o que você não gostaria (dentro de limites razoáveis, é claro). E não esqueça que no PoweShell até a sexta versão (PowerShell Core 6.0) a codificação para scripts é UTF8 BOM, caso contrário, o idioma russo será interrompido.

Além do realce da sintaxe, dicas de ferramentas e recursos de depuração de scripts, o plug-in instala um linter que também o ajudará a seguir as práticas estabelecidas na comunidade, por exemplo, expandindo atalhos com um clique (na lâmpada). Na verdade, este é um módulo regular que pode ser instalado independentemente, por exemplo, adicione-o aos scripts de assinatura de pipeline: PSScriptAnalyzer

Você pode definir os parâmetros de formatação do código nas configurações de extensão. Para todas as configurações (tanto o editor quanto as extensões), há uma pesquisa: File - Preferences - Settings
:

Para obter um novo console conpty, você deve definir o sinalizador nas configurações ; mais tarde, provavelmente, este conselho será irrelevante.
Vale lembrar que qualquer ação no VS Code pode ser executada no centro de controle, chamado por CTRL + Shift + P. Formate um trecho de código inserido no bate-papo , classifique alfabeticamente, altere o recuo de espaços para guias e assim por diante, tudo no centro de controle.
Por exemplo, ative a tela cheia e centralize o editor:

Controle de origem Git, SVN
Freqüentemente, os administradores de sistema do Windows têm uma fobia de resolução de conflitos nos sistemas de controle de versão, provavelmente porque se um representante desse conjunto usa o git, geralmente não há problemas desse tipo. Com o vscode, a resolução de conflitos é reduzida a literalmente cliques do mouse nas partes do código que precisam ser deixadas ou substituídas.

Esses rótulos entre as linhas 303 e 304 são clicáveis. Vale a pena clicar em todos os que aparecem no documento em caso de conflito, confirmar a confirmação e enviar as alterações ao servidor. U - Conveniência.
Sobre como trabalhar com sistemas de controle de versão está disponível e, com as imagens gravadas na estação de codificação vscode , passe por cima de seus olhos brevemente e bem.
Snippets
Snippets são um tipo de macro / modelo que permite acelerar a escrita de código. Definitivamente imperdível.
Criação rápida de objetos:

Procure ajuda com base em comentários:

Se o cmdlet precisar passar um grande número de parâmetros, faz sentido usar splatting .
Aqui está um trecho para ele:

Veja todos os trechos disponíveis disponíveis por Ctrl + Alt + J:

Se depois disso você desejou continuar melhorando o ambiente, mas ainda não tinha ouvido falar em folhas de vespa, aqui está você . Além disso, se você tiver seu próprio conjunto de extensões úteis ao escrever scripts do PowerShell, ficarei feliz em ver a lista deles nos comentários.
Desempenho
O tópico desempenho não é tão simples quanto parece à primeira vista. Por um lado, otimizações prematuras podem reduzir bastante a legibilidade e a manutenção do código, economizando 300ms de tempo de execução de script, cujo tempo de execução usual pode ser de dez minutos, e seu uso nesse caso é definitivamente destrutivo. Por outro lado, existem vários truques bastante simples que aumentam a legibilidade do código e sua velocidade, que são bastante apropriados para uso contínuo. A seguir, falarei sobre alguns deles, se o desempenho é tudo para você e a legibilidade diminui devido às rígidas limitações de tempo de inatividade do serviço durante a manutenção, recomendo consultar a literatura especializada .
Pipeline e foreach
A maneira mais fácil e sempre funcional de aumentar a produtividade é evitar o uso de tubos. Devido à segurança e conveniência do tipo por questão de energia, a passagem dos elementos do PowerShell por um tubo envolve cada um deles em um objeto. Nas linguagens dotnet, esse comportamento é chamado de boxe . O boxe é bom, garante segurança, mas tem seu próprio preço, que às vezes não faz sentido pagar.
A primeira etapa é aumentar o desempenho e, na minha opinião, melhorar a legibilidade removendo todos os aplicativos do Foreach-Object
e substituindo-o pela instrução foreach. Você pode ficar constrangido pelo fato de que na verdade são duas entidades diferentes, porque foreach
é um alias para Foreach-Object
- na prática, a principal diferença é que o foreach
não aceita valores de um pipeline, mas funciona por experiência até três vezes mais rápido.
: - , , :
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
;
, - ;
( "" );
: - , .
Jobs
, , , , , , . . , IO, .
ssdÉ assim que ocorre a primeira inicialização do Windows Server 2019 recém-instalado no Hyper-V para ssd (foi decidido pela migração da máquina virtual para o 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 ):

. , — , . — , .
, , , - .
Runspaces
— 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

Em vez de uma conclusão
PowerShell — Windows-. , , , .
, , "PowerShell, ", . , — , . . , - , - .
— .

PS Boomburum , 2019 powershell — .