การส่งออกฟังก์ชัน PowerShell ไปยังไฟล์
เมื่อฉันเขียนโมดูล PowerShell โดยทั่วไปจะมีฟังก์ชันการส่งออกมากกว่าหนึ่งฟังก์ชัน ตำแหน่งที่คุณจัดเก็บฟังก์ชันโมดูลของคุณเป็นหัวข้อสนทนาที่ยอดเยี่ยม และฉันไม่คิดว่าจะต้องมีแนวทางปฏิบัติที่ดีที่สุดข้อใดข้อหนึ่งสำหรับทุกคน ฉันคิดว่ามันอาจจะขึ้นอยู่กับจำนวนและความซับซ้อนของฟังก์ชัน มีคนอื่นร่วมเขียนโค้ดให้กับโมดูลหรือไม่? คุณเพลิดเพลินกับ Pester แค่ไหน? นี่เป็นเพียงคำถามสองสามข้อที่ต้องพิจารณา ในงานของฉัน บางครั้งฟังก์ชันต่างๆ จะอยู่ในไฟล์ .psm1 บางโครงการมีไฟล์ public.ps1 พร้อมฟังก์ชันหลายอย่าง ในโปรเจ็กต์ที่ซับซ้อนมากขึ้น เช่น โมดูล PSScriptTools ของฉัน แต่ละฟังก์ชันจะได้รับไฟล์แยกกัน
ความท้าทายของฉันเกิดขึ้นเมื่อฉันตัดสินใจแยกฟังก์ชันทั้งหมดที่กำหนดไว้ในไฟล์เดียว ฉันต้องการให้แต่ละฟังก์ชันอยู่ในไฟล์ของตัวเอง โดยใช้ชื่อฟังก์ชันเป็นชื่อไฟล์ ฉันเบื่อที่จะทำสิ่งนี้ด้วยตนเอง ดังนั้นฉันจึงเขียนฟังก์ชัน PowerShell เพื่อทำงานให้ฉัน
การใช้ AST
PowerShell ใช้แนวคิดการเขียนโค้ดที่เรียกว่า "แผนผังไวยากรณ์นามธรรม" หรือ AST AST ทำให้สามารถแยกวิเคราะห์นิพจน์ PowerShell หรือแม้แต่ไฟล์ได้ การใช้ AST ถือเป็นการเขียนสคริปต์ PowerShell ขั้นสูงอย่างแน่นอน
New-Variable astTokens -Force
New-Variable astErr -Force
$AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)ขึ้นอยู่กับสิ่งที่คุณกำลังสรุป คุณอาจได้สิ่งที่คุณต้องการอย่างแน่นอนจากการแยกวิเคราะห์ทันที ในกรณีของฉัน ฉันจำเป็นต้องค้นหาองค์ประกอบคำสั่งประเภทใดประเภทหนึ่งภายในผลลัพธ์ AST
$functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)
ในตัวอย่างของฉัน ฉันพบฟังก์ชันเหล่านี้ในไฟล์
ฉันจะชี้ให้เห็นว่าชื่อฟังก์ชันที่ไม่เป็นไปตามหลักการตั้งชื่อคำกริยาและคำนามคือตัวสร้างคลาส แม้ว่าพวกมันอาจเป็นฟังก์ชันผู้ช่วยส่วนตัวที่โดยทั่วไปจะใช้ชื่อที่ไม่เป็นมาตรฐานเนื่องจากไม่เคยเปิดเผยต่อสาธารณะ ข้อมูลเพิ่มเติมเกี่ยวกับวิธีจัดการเรื่องนี้ในอีกสักครู่
แต่ละองค์ประกอบฟังก์ชั่นเป็นวัตถุของตัวเอง
อย่างที่คุณเห็น มันค่อนข้างง่ายในการรับข้อความฟังก์ชัน มันจะง่ายยิ่งขึ้นถ้าฉันใช้วิธี ToString()
Set-Content -path "d:\temp\$($functions[-1].name).ps1" -Value $functions[-1].ToString()การทดสอบชื่อฟังก์ชัน
ขณะที่ฉันกำลังสร้างเครื่องมือส่งออก ฉันรู้ว่าฉันต้องการวิธีข้ามฟังก์ชันที่อาจไม่มีวันเปิดเผยต่อสาธารณะ ดังนั้นฉันจึงเขียนฟังก์ชันง่ายๆ เพื่อทดสอบชื่อฟังก์ชัน
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
}
}ฉันรู้ว่านี่อาจเป็นคำสั่งบรรทัดเดียว แต่ฉันต้องการเพิ่มคำอธิบายว่าเหตุใดชื่อฟังก์ชันจึงล้มเหลว ฟังก์ชันจะใช้ชื่อและทดสอบก่อนว่าตรงกับรูปแบบ "Word-Word" หรือไม่ หากผ่าน ฟังก์ชันจะทดสอบว่า “กริยา” เป็นกริยามาตรฐานหรือไม่ ถ้าเป็นเช่นนั้นชื่อจะผ่านการทดสอบ
ฟังก์ชันการส่งออก
ถึงเวลาที่จะรวบรวมทั้งหมดนี้เข้าด้วยกัน
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
ตามค่าเริ่มต้น ฟังก์ชันจะส่งออกเฉพาะฟังก์ชันที่มีชื่อมาตรฐานเท่านั้น
หากคุณดูเอาต์พุตแบบ Verbose อย่างใกล้ชิด คุณจะเห็นว่าฟังก์ชันที่ไม่ได้มาตรฐานที่ตรวจพบถูกข้ามไป แม้ว่าฉันจะสามารถรวมฟังก์ชันที่ตรวจพบทั้งหมดได้ถ้าจำเป็น
ฟังก์ชันนี้ช่วยให้ฉันระบุตำแหน่งอื่นสำหรับไฟล์ใหม่ได้ แม้ว่าค่าเริ่มต้นจะเป็นไดเร็กทอรีหลักของไฟล์ต้นฉบับก็ตาม
ไฟล์ต้นฉบับไม่ได้รับผลกระทบ แม้ว่าฉันคิดว่าฉันสามารถแก้ไขฟังก์ชันเพื่อลบโค้ดออกจากไฟล์ได้ สำหรับตอนนี้ ฉันจะอัปเดตไฟล์ต้นฉบับและไฟล์โมดูลอื่นๆ ด้วยตนเองเพื่อแสดงไฟล์ฟังก์ชันใหม่
ลองมัน
ฉันมีทั้งสองฟังก์ชั่นในไฟล์เดียวกันซึ่งฉันดอทซอร์ส แม้ว่าฉันอาจจะเพิ่มฟังก์ชันเหล่านี้ลงในโมดูล PSScriptTools ในอนาคต ฟังก์ชันส่งออกจะถือว่าไฟล์นั้นเป็นไฟล์ .ps1 หรือ .psm1 คุณสามารถดูการตรวจสอบพารามิเตอร์ที่ฉันใช้ในโค้ดได้ ลองดูสิและแจ้งให้เราทราบว่าคุณคิดอย่างไร รหัสควรทำงานใน Windows PowerShell และ PowerShell 7.x






