[PowerShell] Optimize-Script - Script, um den eigenen PowerShell Code zu optimieren

DPXone

Lieutenant
Registriert
Mai 2009
Beiträge
552
[PowerShell] Optimize-Script - Script, um den eigenen Code in der ISE zu optimieren

Hi All,

wollte hier mal mein (bereits in die Monate gekommenes) PowerShell-Code Optimizer/Beautifier Script vorstellen.

Ich hab mir damals zur Aufgabe gemacht, mir die PowerShell-Code-Optmierung zu erleichtern, ohne eine PowerShell IDE von Drittherstellern zu nutzen.
Eine gefiel mir damals zwar echt gut und bot ziemlich viel was ich wollte, aber für den echt hohen Preis (für die damals hauptsächlich private Nutzung) war die IDE es mir dann doch nicht Wert und ich suchte nach Alternativen.

Naja, Resultat war eine Eigenentwicklung in PowerShell, was sich für mich damals aber echt herausfordernd heraus stellte (war da auch noch in meine PowerShell-Einstiegsphase)
Mittlerweile könnte ich es eigentlich alles überholen, aber es lief und lief und hat alle PowerShell-Versionen mitgemacht, deshalb gab es nie wirklich anpassungsbedarf.
Will es aber mal für alle bereitstellen, die ähnlichen Bedarf haben.

An alle PS-Experten: Verbesserungsvorschläge sind immer erwünscht.


Was macht mein Script generell und wie kann man es simple nutzen?:
  • Das Script optimiert den aktuell selektierten PowerShell ISE Tab
  • Man kann es einfach als Tastatur-Kürzel (Keyboard-Shortcut) nutzen (bei mir als Beispiel "F6")
Was macht es?:
Nun, damit lässt sich jedes Script vom Aussehen her optimieren.​
Jeder kennt es:​
  • hier und da Leerzeichen und Tabs vergessen
  • ungleichmäßige Formatierungen
  • Leerzeichen statt Tabs
  • Einrückungen fehlen
  • ...


Wie bereite ich die Nutzung des Moduls vor?:
Dazu einfach PowerShell ISE oder alternativ einfach ein Editor wie z.b. Notepad, Notepad++ öffnen und den Code für das Modul (siehe unten) einfügen.​
Danach das ganze als Optimize-Script.psm1 (Wichtig: Als PSM1 speichern!) unter %UserProfile%\Documents\WindowsPowerShell\Modules\ speichern.​


Wie binde ich das Script in jede PowerShell ISE Session ein?
Füge folgende zwei Zeilen in deine persönliche PowerShell ISE Profldatei ein (bitte nichts überschreiben!).​
Code:
import-module "$Env:USERPROFILE\Documents\WindowsPowerShell\Modules\Optimize-Script.psm1" -WarningAction Ignore
$psise.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Optimize-PScript' , { $psISE.CurrentFile.Editor.Text = (Optimize-PScript $psISE.CurrentFile.Editor.Text) } , 'F6') | out-null
Diese sollte standardmäßig folgenden Pfad aufweisen (wenn nicht, einfach nach dem rot markierten Dateinamen suchen):​
%UserProfile%\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1



Wie nutze ich die Optimierung?
Schreib ein Script in der PowerShell ISE und drücke die Taste F6. Dein Script wird automatisch gemäß den aktuellen (von mir definierten) Vorgaben angepasst.​
Zudem werden sofort Fehler angemerkt, sollte das Script irgendwo Syntaxfehler oder sonstige Fehler aufweisen.​


Wie kann ich das geladende Modul / die geladene Funktion nutzen, ohne es über den Keyboard-Shorcut / das Addon-Menü zu verwenden?
Dazu einfach folgenden Funktionsaufruf verwenden (Vorausgesetzt, das Modul oder die Funktion ist geladen):​
Code:
$psISE.CurrentFile.Editor.Text = (Optimize-PScript $psISE.CurrentFile.Editor.Text)


Wie kann ich die Formatierungen meinen Wünschen anpassen?
Dazu bedarf es schon sehr fortgeschrittener Erfahrung in der PowerShell-Welt und gutem logischen Verständnisses.​
Als einzige Hilfe aktuell kann ich nur folgenden Funktions-Aufruf, den ich in die Funktion eingebaut hab, geben:​
Code:
Optimize-PScript $psISE.CurrentFile.Editor.Text -GetTokens
Dadurch öffnet sich ein GridView, in welchem man alle erkannten PowerShell-Tokens dargestellt bekommt und sieht, um welche Token-Art es sich handelt und welche Flags es besitzt.​


Warum gibt es keine Hilfe?
Nun, bisher war es einfach noch nicht notwendig, da es nur von mir genutzt wurde. Zudem war ich zu faul ;P​
Kommt aber bei Bedarf gerne noch.​


Was klappt noch nicht?
  • Die Tab-Einrückung von Kommentar-Bereichen (<# und #>; sieht man hier im Forum im [ CODE ]-BBCode-Tag besonders), da der komplette Block als ein einziger Token angesehen wird -> somit keine Token-basierte Einrückung möglich)
Das Script (bitte als Modul speichern *.psm1):
PowerShell:
Function Optimize-PScript { 
	[Cmdletbinding()] 
	<#
    .Synopsis
   
    .DESCRIPTION
   
    .EXAMPLE
   
    .EXAMPLE
   
    .NOTES
    #> 
	Param (
		[Parameter(Mandatory = $true)]                   
		[String] $ScriptText = $psISE.CurrentFile.Editor.Text , 
		[Alias('GT')] 
		[Switch] $GetTokens = $false 
	) 
	
	Begin { 
		$CurrentLevel = 0 
		$ParseError = $null 
		$Tokens = $null 
		$stringBuilder = New-Object System.Text.StringBuilder 
		$TokenCollection = @() 
	} 
	
	Process { 
		$AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptText ,[ref] $Tokens ,[ref] $ParseError) 
		If ($ParseError) { 
			$ParseError | Write-Error 
			Throw "The parser will not work properly with errors in the script, please modify based on the above errors and retry." 
		} 
		
		For ($i = 0 ; $i -le ($Tokens.Count - 2) ; $i++) { 
			$addSpace = $true # Default                                                                         
			
			<#
            =====================================================
            Token-Details
            =====================================================
            #> 
			# Details about current token                                                                         
			$T = $Tokens[$i] 
			$TKind = $T.Kind 
			$TFlags = ($T.TokenFlags -split ', ') 
			$TokenCollection+= (new-object PSOBject -Property @{ Index = $i ; Token = $T ; Kind = $TKind ; Flags = $TFlags }) 
			
			# Details about next token                                                                          
			$NextT = $Tokens[$i + 1] 
			$NextTKind = $NextT.Kind 
			$NextTFlags = ($NextT.TokenFlags -split ', ') 
			
			# Details about last token                                                                           
			$LastT = $Tokens[$i - 1] 
			$LastTKind = $LastT.Kind 
			$LastTFlags = ($LastT.TokenFlags -split ', ') 
			
			
			
			<#
            =====================================================
            TAB-Levels
            =====================================================
            #> 
			If ($TKind -match '^(L|At|Dollar)(Curly|Paren)$') { 
				$CurrentLevel++ 
			} 
			If ($NextTKind -match '^R(Curly|Paren)$') { 
				$CurrentLevel-- 
			} 
			
			
			
			<#
            =====================================================
            Generic Syntax Definitions | Order is important!
            =====================================================
            #> 
			#------------------------------------                                                                        
			# Kind: Identifier Exceptions | AddSpace = $False                                                                  
			#------------------------------------                                                                   
			$TException = 'Identifier' 
			$NextTException = 'Lparen' , 'LBracket' 
			If (($TKind -in $TException) -and ($NextTKind -in $NextTException)) { 
				$addSpace = $false 
				write-verbose 'Identifier Space Exception' 
			} 
			
			#------------------------------------                                                                        
			# Kind: ColonColon Exceptions | AddSpace = $False                                                                  
			#------------------------------------                                 
			$TException = 'ColonColon' 
			$NextTException = 'ColonColon' 
			If (($TKind -eq $TException) -or ($NextTKind -in $NextTException)) { 
				$addSpace = $false 
			} 
			
			#------------------------------------                                                                        
			# Kind: Variable Exceptions | AddSpace = $False                                                                
			#------------------------------------                                                                   
			$TException = 'Variable' 
			$NextTException = 'PlusPlus' , 'PlusEquals' , 'MinusMinus' , 'MinusEquals' , 'LBracket' 
			If (($TKind -in $TException) -and ($NextTKind -in $NextTException)) { 
				$addSpace = $false 
			} 
			
			#------------------------------------                                                                        
			# Kind: Paren Exceptions | AddSpace = $False                                                                    
			#------------------------------------                                                                    
			$TException = 'LParen' , 'AtParen' , 'DollarParen' 
			$NextTException = 'LParen' , 'RParen' 
			If (($TKind -in $TException) -or ($NextTKind -in $NextTException)) { 
				$addSpace = $false 
			} 
			
			#------------------------------------                                                                        
			# Kind: Bracket Exceptions | AddSpace = $False                                                                      
			#------------------------------------                                                                   
			$TException = 'LBracket' 
			$NextTException = 'RBracket' , 'LBracket' 
			If (($TKind -in $TException) -or ($NextTKind -in $NextTException)) { 
				$addSpace = $false 
			} 
			
			#------------------------------------                                                                        
			# Flag: Command Name| AddSpace = $True                                                                      
			#------------------------------------                     
			If ($TFlags -contains 'CommandName') { 
				$addSpace = $true 
			} 
			
			
			
			#=====================================================                                                                    
			# Special Definitions | Order is important!                                                                       
			#=====================================================                                                                       
			
			# MATCH-Comp - TKind -> addSpace = FALSE                                                                        
			If ($TKind -match 'NewLine') { 
				$addSpace = $false 
			} 
			
			# OR-Comp -> addSpace = TRUE                                                                        
			$comp = 'BinaryOperator' 
			If (($TFlags -contains $comp) -or ($NextTFlags -contains $comp)) { 
				$addSpace = $true 
			} 
			$comp = 'AssignmentOperator' 
			If ($TFlags -contains $comp) { 
				$addSpace = $true 
			} 
			
			
			# OR-Comp -> addSpace = FALSE                                                                        
			$comp = 'Dot' 
			If (($TKind -contains $comp) -or ($NextTKind -contains $comp)) { 
				$addSpace = $false 
			} 
			
			# Keywords with capital letter                                                                        
			If ($TFlags -contains 'Keyword') { 
				$T = "$([string]$TKind)" 
				$addSpace = $true 
			} 
			
			
			<#
            =====================================================
            Add token to stringbuilder and add spaces if needed
            =====================================================
            #> 
			Switch ($addSpace) { 
				$false { $Null = $stringBuilder.Append("$T") } 
				$true { $Null = $stringBuilder.Append("$T ") } 
			} 
			
			
			<#
            =====================================================
            Add TAB if token -eq NewLine OR LineContinuation
            =====================================================
            #> 
			If ($TKind -eq 'NewLine') { 
				$Null = $stringBuilder.Append("`t" * $CurrentLevel) 
			} ElseIf ($TKind -eq 'LineContinuation') { 
				$Null = $stringBuilder.Append("`t" * ($CurrentLevel + 1)) # additional tab indentation   
			} 
		} 
	} 
	End { 
		$stringBuilder.ToString() 
		If ($GetTokens) { 
			$Null = $TokenCollection | sort Index | select Index , Token , Kind , Flags | ogv -Title 'Tokens' 
		} 
	} 
}
 
Zuletzt bearbeitet:
Zurück
Oben