PowerShell, volcado de mi experiencia

Introduccion


Este artículo está dirigido a aquellos que ya se han familiarizado con los conceptos básicos de PowerShell, ejecutan algunos scripts con stackexchange y probablemente tienen su propio archivo de texto con varios fragmentos para facilitar el trabajo diario. El propósito de escribirlo es reducir la entropía, aumentar la legibilidad y la facilidad de mantenimiento del código de PowerShell utilizado en su empresa y, como resultado, aumentar la productividad del administrador que trabaja con él.


kdpv


En mi lugar de trabajo anterior, debido a las características específicas de las tareas y la imperfección del mundo, aproveché enormemente la habilidad de trabajar con PowerShell. Después del cambio de trabajo, cargas de este tipo disminuyeron significativamente y todo lo que estaba a la vuelta de la esquina de los dedos comenzó a hundirse más y más bajo la experiencia de resolver nuevos problemas con nuevas tecnologías. A partir de esto, este artículo afirma ser solo lo que se declara a sí mismo, revelando una lista de temas que, en mi opinión, me serían útiles hace unos 7 años, cuando recién comencé a conocer esta herramienta.


Si no comprende por qué PowerShell es un shell orientado a objetos, qué bonificaciones provienen de esto y por qué es necesario en absoluto, le aconsejaré un buen libro que presente rápidamente la esencia de este entorno a pesar de los que odian: Andrey Vladimirovich Popov, Introducción a Windows PowerShell . Sí, se trata de la versión anterior de PS, sí, el lenguaje ha ganado algunas extensiones y mejoras, pero este libro es bueno porque al describir la primera etapa de desarrollo de este entorno, sin darse cuenta, enfatiza solo cosas fundamentales. El azúcar sintáctica con la que el medio ambiente se ha desbordado, creo, se percibe rápidamente sin comprender cómo funciona el concepto. Leer este libro te llevará solo un par de noches, vuelve después de leer.


popov


El libro también está disponible en el sitio web del autor, aunque no estoy seguro de qué licencia tiene este uso: https://andpop.ru/courses/winscript/books/posh_popov.pdf


Guías de estilo


Diseñar guiones de acuerdo con guías de estilo es una buena práctica en todos los casos de su aplicación, apenas puede haber dos opiniones. Algunos ecosistemas se han ocupado de esto en el nivel de ajuste nativo, pep8 en la comunidad de Python e ir a Golang en Golang se vuelve obvio. Estas son herramientas invaluables para ahorrar tiempo, que, lamentablemente, están ausentes en el paquete estándar de PowerShell y, por lo tanto, transfieren el problema a nuestra cabeza. Actualmente, la única forma de resolver el problema del formato de código uniforme es desarrollar reflejos escribiendo código repetidamente que satisfaga las guías de estilo (de hecho, no).


Las guías de estilo debido a la falta de aprobación oficial y descritas en detalle por Microsoft nacieron en la comunidad durante el tiempo de PowerShell v3 y desde entonces se han desarrollado en forma abierta en el github: PowerShellPracticeAndStyle . Este es un repositorio digno de mención para cualquiera que haya usado el botón "Guardar" en PowerShell ise.


Si intenta hacer un apretón, probablemente se reducirá a los siguientes puntos:


  • PowerShell usa PascalCase para nombrar variables, cmdlets, nombres de módulos y casi todo excepto los operadores;
  • Los operadores de lenguaje como if , switch , break , process , -match están escritos en letras muy pequeñas;
  • Los corchetes se colocan de la única manera verdadera , de lo contrario también se llama el estilo de Kernigan y Richie, liderando su historia del libro The C Programming Language ;
  • No use alias en ningún lugar que no sea una sesión de consola interactiva, no escriba ningún ps | ? processname -eq firefox | %{$ws=0}{$ws+=$_.workingset}{$ws/1MB} archivo 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 nombres de parámetros explícitos, el comportamiento de los cmdlets y su firma pueden cambiar , además esto agrega un contexto a una persona que no está familiarizada con un cmdlet en particular;
  • Diseñe los parámetros para llamar scripts, y no escriba una función dentro del script y la última línea llama a esta función con la necesidad de cambiar los valores de las variables globales en lugar de especificar parámetros;
  • Especifique [CmdletBinding ()]: esto le dará a su cmdlet los -Verbose y -Debug y muchas otras características útiles . A pesar de la posición firme de algunos puristas en la comunidad, no soy partidario de indicar este atributo en funciones simples en línea y filtros que consisten en varias líneas literales;
  • Escriba una ayuda basada en comentarios: una oración, enlace de ticket, ejemplo de llamada;
  • Especifique la versión requerida de PowerShell en la sección #requires ;
  • Use Set-StrictMode -Version Latest , esto lo ayudará a evitar los problemas que se describen a continuación ;
  • Manejar errores;
  • No se apresure a reescribir todo en PowerShell. PowerShell es principalmente un shell y llamar a los binarios es su tarea directa. No hay nada de malo en usar robocopy en un script, por supuesto, no es rsync, pero también es muy bueno.

Ayuda basada en comentarios


A continuación se muestra un ejemplo de cómo obtener la secuencia de comandos de ayuda. El script enmarca la imagen llevándola a un cuadrado y realiza un cambio de tamaño, creo que tiene la tarea de hacer avatares para los usuarios (excepto quizás la rotación de acuerdo con los datos exif). Hay un ejemplo de uso en la sección .EXAMPLE , pruébalo. Debido al hecho de que PowerShell se ejecuta mediante Common Language Runtime, al igual que otros lenguajes dotnet, tiene la capacidad de utilizar todo el poder de las 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 ) 

El script anterior comienza con un comentario de varias líneas <# ... #> , si este comentario es el primero y contiene ciertas palabras clave, PowerShell generará automáticamente ayuda para el script. Este tipo de ayuda se llama literalmente: ayuda basada en el comentario :


ayuda


Además, cuando se llama al script, la información sobre herramientas para los parámetros funcionará, ya sea la consola PowerShell o el editor de código:


ayuda en línea


Una vez más, llamo la atención sobre el hecho de que no debe descuidarse. Si no sabe qué escribir allí, escriba algo, vaya al refrigerador y al regresar definitivamente comprenderá lo que debe cambiarse por escrito. Funciona No complete fanáticamente todas las palabras clave, PowerShell está diseñado para autodocumentarse y, si proporcionó nombres significativos y completos a los parámetros, una breve oración en la sección .SYNOPIS y un ejemplo son suficientes.


Modo estricto


PowerShell, como muchos otros lenguajes de secuencias de comandos, tiene una tipificación dinámica. Este tipo de mecanografía tiene muchos partidarios: escribir una lógica de alto nivel simple pero poderosa es cuestión de unos minutos, pero cuando su decisión comience a llegar a mil líneas, seguramente encontrará la fragilidad de este enfoque.


La salida automática de tipos invariablemente en la etapa de prueba, que formó una matriz en el lugar donde siempre recibió un conjunto de elementos, definitivamente pondrá un cerdo en caso de que obtenga un elemento y en la siguiente condición, en lugar de verificar el número de elementos, obtendrá el número de caracteres u otro atributo, dependiendo de tipo de artículo La lógica del script se romperá y el tiempo de ejecución fingirá que todo está bien.


Establecer el modo estricto ayuda a evitar algunos de estos problemas, pero también requiere un poco más de código, como la inicialización de variables y la conversión explícita.


Este modo está habilitado por el cmdlet Set-StrictMode -Version Latest , aunque hay otras opciones para el "rigor", mi elección es usar este último.


En el ejemplo a continuación, el modo estricto captura una llamada a una propiedad inexistente. Dado que solo hay un elemento dentro de la carpeta, el tipo de la variable $Input como resultado de la ejecución será FileInfo y no la matriz esperada de los elementos correspondientes:


estricto


Para evitar este problema, debe convertir explícitamente el resultado del cmdlet a una matriz:


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

Establezca una regla para establecer siempre el modo estricto, esto le permitirá evitar resultados inesperados al ejecutar sus scripts.


Manejo de errores


ErrorActionPreference


Cuando miro los scripts de otras personas, por ejemplo, en un github, a menudo veo ignorar por completo el mecanismo de manejo de errores o activar explícitamente el modo de continuación silenciosa en caso de error. El problema de manejo de errores ciertamente no es el más fácil de programar en general y en los scripts en particular, pero definitivamente no merece ser ignorado. De forma predeterminada, PowerShell en caso de error lo muestra y continúa funcionando (simplifiqué un poco el concepto, pero a continuación hay un enlace a un libro git sobre este tema). Esto es conveniente si necesita distribuir con urgencia la actualización de un programa que se usa ampliamente en el dominio a todas las máquinas, sin esperar hasta que se extienda a todos los puntos de implementación de sccm o se difunda de otra manera utilizada por usted. Es desagradable interrumpir y reiniciar el proceso si una de las máquinas está apagada, esto es cierto.


Por otro lado, si realiza una copia de seguridad compleja de un sistema que consta de más de un archivo de datos de más de una parte del sistema de información, debe asegurarse de que su copia de seguridad sea coherente y que todos los conjuntos de datos necesarios se hayan copiado sin errores.


Para cambiar el comportamiento de los cmdlets en caso de error, hay una variable global $ErrorActionPreference , con la siguiente lista de valores posibles: Stop, Inquire, Continue, Suspend, SilentlyContinue .


Le recomiendo que siempre use el valor Stop cuando el número de scripts o su complejidad deje de almacenarse en la pila en su cabeza, es mejor asegurarse de que en cualquier situación imprevista el script detendrá su trabajo y no romperá la leña "en silencio", completando la ejecución "con éxito".


Además de detener el script en caso de que algo salga mal, hay otro requisito previo para su uso: manejar situaciones excepcionales. Hay una construcción try/catch para esto, pero solo funciona si el error hace que la ejecución se detenga. No es necesario que la parada deba habilitarse en el nivel de todo el script, ErrorAction puede establecer en el nivel de cmdlet con el parámetro:


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

En realidad, esta posibilidad define dos estrategias lógicas: resolver todos los errores "por defecto" y establecer ErrorAction solo para lugares críticos donde manejarlos; habilite a nivel de todo el script configurando el valor de la variable global y configurando -ErrorAction 'Continue' en operaciones no críticas. Siempre elijo la segunda opción, no tengo prisa por imponerle, solo recomiendo una vez que comprenda este problema y use esta herramienta útil.


intentar / atrapar


En el controlador de errores, puede hacer coincidir el tipo de excepción y operar con un hilo de ejecución o, por ejemplo, agregar un poco más de información. A pesar de que al usar los operadores try/catch/throw/trap puede construir todo el flujo de ejecución del script, debe evitarlo categóricamente, ya que dicho método de operar con ejecución no solo se considera un antipater extremo de la categoría "goto-noodle", por lo que También reduce drásticamente el rendimiento.


 #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 la pena señalar que el operador de trap es una trampa de error global. Captura todo lo que no se ha procesado en niveles inferiores, o se elimina del controlador de excepciones debido a la imposibilidad de corregir de forma independiente la situación.


Además del enfoque orientado a objetos de las excepciones descritas anteriormente, PowerShell también proporciona conceptos más familiares compatibles con otros shells "clásicos", como flujos de error, códigos de retorno y variables de acumulación de errores. Todo esto es ciertamente conveniente, a veces sin alternativa, pero va más allá del alcance de este tema general. Afortunadamente hay un buen libro abierto sobre github sobre este tema.


El código del registrador que uso cuando no hay certeza de que el sistema tendrá PowerShell 5 (donde puede describir la clase de registrador más convenientemente), pruébelo, puede serle útil debido a su simplicidad y brevedad, seguramente agregará métodos adicionales sin dificultad. :


 #   " ",   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 la idea: no ignore el manejo de errores. Esto le ahorrará tiempo y nervios a largo plazo.
No piense que ejecutar el script no importa lo que sea bueno. Bien, es hora de caer sin romper la leña.


Las herramientas


Las herramientas de PowerShell ciertamente deberían iniciarse con un emulador de consola. A menudo escuché de partidarios de avispas alternativas que la consola en Windows es mala y que esta no es una consola, sino dos, etc. Pocos podrían formular adecuadamente sus afirmaciones sobre este tema, pero si alguien tuvo éxito, en realidad resultó que todos los problemas podrían resolverse. Ya había más información sobre los terminales y la nueva consola en Windows en el hub; todo está más que bien allí .


El primer paso es instalar Conemu o su conjunto Cmder, lo cual no es particularmente importante, ya que en mi opinión vale la pena revisar la configuración de todos modos. Por lo general, elijo cmder en la configuración mínima, sin un gita y otros binarios, que configuré yo mismo, aunque durante varios años ajusté mi configuración para Conemu puro. Este es realmente el mejor emulador de terminal para Windows, ya que le permite dividir la pantalla (para tmux / amantes de la pantalla), crear pestañas y habilitar el modo de consola de estilo sismo:


Conemu


cmder


El siguiente paso que recomiendo es poner los módulos: oh-my-posh , posh-git y PSReadLine . Los dos primeros harán que el baile sea más agradable al agregar información sobre la sesión actual, el estado del último comando ejecutado, el indicador de privilegio y el estado del repositorio git en la ubicación actual. PSReadLine aumenta enormemente el indicador, agregando, por ejemplo, una búsqueda en el historial de comandos ingresados ​​(CRTL + R) y consejos convenientes para cmdlets en CRTL + Space:


readline


Y sí, ahora la consola se puede borrar con CTRL + L y olvidarse de los cls .


Código de estudio visual


El editor Lo peor que puedo decir sobre PowerShell es puramente PowerShell ISE, aquellos que vieron la primera versión con tres paneles probablemente no olviden esta experiencia. La diferente codificación del terminal, la falta de funciones básicas del editor, como la sangría automática, los corchetes de cierre automático, el formato de código y un conjunto completo de antipatrones generados por él sobre lo que no le diré (por si acaso), esto se trata de ISE.


No lo use, use Visual Studio Code con la extensión PowerShell: hay todo lo que no desea (dentro de límites razonables, por supuesto). Y no olvide que en PoweShell hasta la sexta versión (PowerShell Core 6.0) la codificación de los scripts es UTF8 BOM, de lo contrario el idioma ruso se romperá.


vscode


Además del resaltado de sintaxis, la información sobre herramientas y las capacidades de depuración de scripts, el complemento instala un linter que también lo ayudará a seguir las prácticas establecidas en la comunidad, por ejemplo, expandiendo los accesos directos con un solo clic (en la bombilla). De hecho, este es un módulo normal que se puede instalar de forma independiente, por ejemplo, agréguelo a sus scripts de firma de canalización: PSScriptAnalyzer


PSScriptAnalyzer


Puede establecer los parámetros de formato de código en la configuración de la extensión, para todas las configuraciones (tanto el editor como las extensiones) hay una búsqueda: File - Preferences - Settings :


Otbs


Para obtener una nueva consola conpty, debe establecer la bandera en la configuración , más tarde, probablemente, este consejo será irrelevante.


Vale la pena recordar que cualquier acción en VS Code se puede realizar desde el centro de control, llamado por CTRL + Shift + P. Formatee un código insertado desde el chat , ordene alfabéticamente, cambie la sangría de espacios a pestañas, y así sucesivamente, todo en el centro de control.


Por ejemplo, encienda la pantalla completa y centre el editor:


maquetacion


Control de fuente Git, SVN


A menudo, los administradores de sistemas de Windows tienen fobia a la resolución de conflictos en los sistemas de control de versiones, probablemente porque si un representante de este conjunto usa git, a menudo uno no encuentra ningún problema de este tipo. Con vscode, la resolución de conflictos se reduce literalmente a clics del mouse en aquellas partes del código que deben dejarse o reemplazarse.


fusionar


Se puede hacer clic en estas etiquetas entre las líneas 303 y 304, vale la pena hacer clic en todas las que aparecen en el documento en caso de conflicto, comprometerse a confirmar los cambios y enviar los cambios al servidor. U - Conveniencia.


Sobre cómo trabajar con los sistemas de control de versiones está disponible y con las imágenes escritas en el dock vscode , revise sus ojos allí brevemente y bien.


Fragmentos


Los fragmentos son un tipo de macro / plantilla que le permite acelerar la escritura de código. Definitivamente una visita obligada.


Creación rápida de objetos:


objeto personalizado


Busque ayuda basada en comentarios:


ayuda


Si el cmdlet necesita pasar una gran cantidad de parámetros, tiene sentido usar salpicaduras .
Aquí hay un fragmento para él:


salpicaduras


Ver todos los fragmentos disponibles disponibles por Ctrl + Alt + J:


fragmentos


Si después de eso deseaba continuar mejorando su entorno, pero aún no había escuchado sobre las hojas de avispa, entonces aquí está . Además, si tiene su propio conjunto de extensiones útiles para escribir scripts de PowerShell, me complacerá ver su lista en los comentarios.


Rendimiento


El tema del rendimiento no es tan simple como podría parecer a primera vista. Por un lado, las optimizaciones prematuras pueden reducir en gran medida la legibilidad y la facilidad de mantenimiento del código, ahorrando 300 ms de tiempo de ejecución del script, cuyo tiempo de ejecución habitual puede ser de diez minutos, su uso en este caso es definitivamente destructivo. Por otro lado, hay varios trucos bastante simples que aumentan tanto la legibilidad del código como su velocidad, que son bastante apropiados para usar de forma continua. A continuación, hablaré sobre algunos de ellos, si el rendimiento lo es todo para usted y la legibilidad se va por el camino debido a los estrictos límites de tiempo de inactividad del servicio durante el mantenimiento, le recomiendo que consulte la literatura relevante .


Tubería y foreach


La forma más fácil y siempre funcional de aumentar la productividad es evitar el uso de tuberías. Debido a la seguridad y conveniencia del tipo por el bien de la energía, los elementos de paso de PowerShell a través de una tubería envuelven cada uno de ellos en un objeto. En los lenguajes dotnet, este comportamiento se llama boxeo . El boxeo es bueno, garantiza la seguridad, pero tiene su propio precio, que a veces no tiene sentido pagar.


El primer paso es aumentar el rendimiento y, en mi opinión, mejorar la legibilidad al eliminar todas las aplicaciones del Foreach-Object y reemplazarlo con la instrucción foreach. Puede avergonzarse por el hecho de que en realidad son dos entidades diferentes, porque foreach es un alias para Foreach-Object ; en la práctica, la principal diferencia es que foreach no acepta valores de una tubería, pero funciona por experiencia hasta tres veces más 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' 

:


recuento


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


cadena de construcción


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


lector de corriente


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


lector de corriente con volcado


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


  • , - ;


  • ( "" );



: - , .


Trabajos


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


ssd

Así es como se lleva a cabo el primer arranque del recién instalado Windows Server 2019 en Hyper-V a ssd (fue decidido por la migración de la máquina virtual a 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 

, — , . .


, :


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


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


el trabajo muere


. , — , . — , .


, , , - .


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:


atracones


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


kaxaml


En lugar de una conclusión


PowerShell — Windows-. , , , .


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


— .


calma


PS Boomburum , 2019 powershell — .

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


All Articles