PowerShell İşlevlerini Dosyalara Aktarma
Bir PowerShell modülü yazdığımda genellikle birden fazla dışa aktarma işlevi içerir. Modül işlevlerinizi nerede saklayacağınız harika bir tartışma konusudur ve herkes için mutlaka tek bir en iyi uygulama olduğunu düşünmüyorum. Fonksiyonların sayısına ve karmaşıklığına bağlı olabileceğini düşünüyorum. Başkaları da modüle kod katkıda bulunuyor mu? Pester'ı nasıl kullanıyorsunuz? Bunlar dikkate alınması gereken sadece birkaç soru. İşimde bazen işlevler .psm1 dosyasında bulunur. Bazı projelerde çeşitli işlevlere sahip bir public.ps1 dosyası bulunur. PSScriptTools modülüm gibi daha karmaşık projelerde her işlev ayrı bir dosya alır.
Zorluk, tek bir dosyada tanımlanan tüm işlevleri ayırmaya karar verdiğimde ortaya çıkıyor. Her işlevin, dosya adı olarak işlev adını kullanarak kendi dosyasında bulunmasını istiyorum. Bunu manuel olarak yapmaktan yoruldum, bu yüzden işi benim için yapacak bir PowerShell işlevi yazdım.
AST'yi kullanma
PowerShell, "soyut sözdizimi ağacı" veya AST adı verilen bir kodlama konsepti kullanır. AST, bir PowerShell ifadesini ve hatta bir dosyayı ayrıştırmayı mümkün kılar. AST'yi kullanmak kesinlikle gelişmiş PowerShell komut dosyası oluşturma juju'dur.
New-Variable astTokens -Force
New-Variable astErr -Force
$AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)Neyi soyutladığınıza bağlı olarak, anında ayrıştırmadan tam olarak istediğinizi elde edebilirsiniz. Benim durumumda, belirli bir tür komut öğesi için AST sonucu içinde arama yapmam gerekiyordu.
$functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)
Örneğimde bu işlevleri dosyada buldum.
Fiil-İsim adlandırma kuralına uymayan işlev adlarının sınıf oluşturucuları olduğunu belirteceğim. Bununla birlikte, hiçbir zaman kamuya açıklanmadıkları için genellikle standart olmayan adlar kullanan özel yardımcı işlevler de olabilirler. Bunu nasıl halledeceğime birazdan değineceğim.
Her işlev öğesi kendi nesnesidir.
Gördüğünüz gibi fonksiyon metnini elde etmek oldukça kolaydır. ToString() yöntemini kullanırsam daha da kolaylaşır.
Set-Content -path "d:\temp\$($functions[-1].name).ps1" -Value $functions[-1].ToString()Fonksiyon Adlarının Test Edilmesi
Dışa aktarma aracımı oluştururken muhtemelen hiçbir zaman kamuya açıklanmayacak işlevleri atlamanın bir yoluna ihtiyacım olduğunu fark ettim. Bu yüzden bir işlev adını test etmek için basit bir işlev yazdım.
Function Test-FunctionName {
[CmdletBinding()]
[OutputType("boolean")]
Param(
[Parameter(Position = 0,Mandatory,HelpMessage = "Specify a function name.")]
[ValidateNotNullOrEmpty()]
[string]$Name
)
Write-Verbose "Validating function name $Name"
#Function name must first follow Verb-Noun pattern
if ($Name -match "^\w+-\w+$") {
#validate the standard verb
$verb = ($Name -split "-")[0]
Write-Verbose "Validating detected verb $verb"
if ((Get-Verb).verb -contains $verb ) {
$True
}
else {
Write-Verbose "$($Verb.ToUpper()) is not an approved verb."
$False
}
}
else {
Write-Verbose "$Name does not match the regex pattern ^\w+-\w+$"
$False
}
}Bunun tek satırlık bir komut olabileceğini biliyorum, ancak bir işlev adının neden başarısız olacağına dair bir açıklama eklemek istedim. Fonksiyon bir isim alır ve öncelikle “Kelime-Kelime” kalıbıyla eşleşip eşleşmediğini test eder. Eğer bu başarılı olursa, işlev "fiil"in standart bir fiil olup olmadığını test eder. Eğer öyleyse, isim testi geçer.
Dışa Aktarma İşlevleri
Bunların hepsini bir araya getirmenin zamanı geldi.
Function Export-FunctionFromFile {
[cmdletbinding(SupportsShouldProcess)]
[alias("eff")]
[OutputType("None", "System.IO.FileInfo")]
Param(
[Parameter(Position = 0, Mandatory, HelpMessage = "Specify the .ps1 or .psm1 file with defined functions.")]
[ValidateScript({
If (Test-Path $_ ) {
$True
}
Else {
Throw "Can't validate that $_ exists. Please verify and try again."
$False
}
})]
[ValidateScript({
If ($_ -match "\.ps(m)?1$") {
$True
}
Else {
Throw "The path must be to a .ps1 or .psm1 file."
$False
}
})]
[string]$Path,
[Parameter(HelpMessage = "Specify the output path. The default is the same directory as the .ps1 file.")]
[ValidateScript({ Test-Path $_ })]
[string]$OutputPath,
[Parameter(HelpMessage = "Export all detected functions.")]
[switch]$All,
[Parameter(HelpMessage = "Pass the output file to the pipeline.")]
[switch]$Passthru
)
Write-Verbose "Starting $($MyInvocation.MyCommand)"
#always create these variables
New-Variable astTokens -Force -WhatIf:$False
New-Variable astErr -Force -WhatIf:$False
if (-Not $OutputPath) {
#use the parent path of the file unless the user specifies a different path
$OutputPath = Split-Path -Path $Path -Parent
}
Write-Verbose "Processing $path for functions"
#the file will always be parsed regardless of WhatIfPreference
$AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)
#parse out functions using the AST
$functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)
if ($functions.count -gt 0) {
Write-Verbose "Found $($functions.count) functions"
Write-Verbose "Creating files in $outputpath"
Foreach ($item in $functions) {
Write-Verbose "Detected function $($item.name)"
#only export functions with standard namees or if -All is detected.
if ($All -OR (Test-FunctionName -name $item.name)) {
$newfile = Join-Path -Path $OutputPath -ChildPath "$($item.name).ps1"
Write-Verbose "Creating new file $newFile"
Set-Content -Path $newFile -Value $item.ToString() -Force
if ($Passthru -AND (-Not $WhatIfPreference)) {
Get-Item -Path $newfile
}
}
else {
Write-Verbose "Skipping $($item.name)"
}
} #foreach item
}
else {
Write-Warning "No functions detected in $Path."
}
Write-Verbose "Ending $($MyInvocation.MyCommand)"
} #end function
Varsayılan olarak işlev yalnızca standart adlara sahip işlevleri dışa aktarır.
Verbose çıktısına yakından bakarsanız, tespit edilen standart dışı işlevlerin atlandığını göreceksiniz. Ancak gerekirse tespit edilen tüm işlevleri dahil edebilirim.
Varsayılan kaynak dosyanın üst dizini olmasına rağmen, işlev yeni dosyalar için alternatif bir konum belirlememe olanak tanıyor.
Kaynak dosya etkilenmedi, ancak sanırım dosyadan kodu silmek için işlevi değiştirebilirim. Şimdilik kaynak dosyayı ve diğer modül dosyalarını yeni işlev dosyalarını yansıtacak şekilde manuel olarak güncelleyeceğim.
Dene
Kaynak noktam olan aynı dosyada her iki fonksiyona da sahibim. Ancak gelecekte bir noktada bu işlevleri PSScriptTools modülüne ekleyebilirim. Dışa aktarma işlevi, dosyanın bir .ps1 veya .psm1 dosyası olduğunu varsayar. Kullandığım parametre doğrulamasını kodda görebilirsiniz. Bir deneyin ve ne düşündüğünüzü bana bildirin. Kod, Windows PowerShell ve PowerShell 7.x'te çalışmalıdır.






