สคริปต์ PowerShell จะส่งคืนเวอร์ชันใด ๆ ของ. NET Framework บนเครื่อง
การเดาครั้งแรกของฉันคือสิ่งที่เกี่ยวข้องกับ WMI มีอะไรที่ดีกว่านี้ไหม?
ควรเป็นหนึ่งซับเพื่อส่งคืนเฉพาะเวอร์ชันล่าสุดสำหรับการติดตั้ง. NET แต่ละครั้งในแต่ละบรรทัด
asp.net -v
สคริปต์ PowerShell จะส่งคืนเวอร์ชันใด ๆ ของ. NET Framework บนเครื่อง
การเดาครั้งแรกของฉันคือสิ่งที่เกี่ยวข้องกับ WMI มีอะไรที่ดีกว่านี้ไหม?
ควรเป็นหนึ่งซับเพื่อส่งคืนเฉพาะเวอร์ชันล่าสุดสำหรับการติดตั้ง. NET แต่ละครั้งในแต่ละบรรทัด
asp.net -v
คำตอบ:
หากคุณกำลังจะใช้รีจิสตรีคุณจะต้องรับเงินคืนเพื่อให้ได้เวอร์ชันเต็มสำหรับ 4.x Framework คำตอบก่อนหน้านี้ทั้งคืนหมายเลขรูทบนระบบของฉันสำหรับ. NET 3.0 (โดยที่หมายเลข WCF และ WPF ซึ่งซ้อนอยู่ภายใต้ 3.0 จะสูงกว่า - ฉันไม่สามารถอธิบายได้) และไม่สามารถส่งคืนสิ่งใดสำหรับ 4.0 .
แก้ไข: สำหรับ. Net 4.5 ขึ้นไปการเปลี่ยนแปลงนี้เล็กน้อยอีกครั้งดังนั้นขณะนี้มีบทความ MSDN ที่ดีที่นี่อธิบายวิธีการแปลงค่าReleaseเป็นหมายเลข. Net มันเป็นซากรถไฟทั้งหมด :-(
สิ่งนี้ดูถูกต้องสำหรับฉัน (โปรดทราบว่ามันจะส่งออกหมายเลขรุ่นแยกต่างหากสำหรับ WCF & WPF บน 3.0 ฉันไม่รู้ว่ามันเกี่ยวข้องกับอะไร) มันยังส่งผลทั้งลูกค้าและเต็มใน 4.0 (ถ้าคุณมีพวกเขาทั้งสองติดตั้ง):
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse |
Get-ItemProperty -name Version,Release -EA 0 |
Where { $_.PSChildName -match '^(?!S)\p{L}'} |
Select PSChildName, Version, Release
จากบทความ MSDN คุณสามารถสร้างตารางการค้นหาและส่งคืนหมายเลขรุ่นผลิตภัณฑ์การตลาดสำหรับรุ่นหลังจาก 4.5:
$Lookup = @{
378389 = [version]'4.5'
378675 = [version]'4.5.1'
378758 = [version]'4.5.1'
379893 = [version]'4.5.2'
393295 = [version]'4.6'
393297 = [version]'4.6'
394254 = [version]'4.6.1'
394271 = [version]'4.6.1'
394802 = [version]'4.6.2'
394806 = [version]'4.6.2'
460798 = [version]'4.7'
460805 = [version]'4.7'
461308 = [version]'4.7.1'
461310 = [version]'4.7.1'
461808 = [version]'4.7.2'
461814 = [version]'4.7.2'
528040 = [version]'4.8'
528049 = [version]'4.8'
}
# For One True framework (latest .NET 4x), change the Where-Object match
# to PSChildName -eq "Full":
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
Get-ItemProperty -name Version, Release -EA 0 |
Where-Object { $_.PSChildName -match '^(?!S)\p{L}'} |
Select-Object @{name = ".NET Framework"; expression = {$_.PSChildName}},
@{name = "Product"; expression = {$Lookup[$_.Release]}},
Version, Release
ในความเป็นจริงเนื่องจากฉันต้องอัปเดตคำตอบนี้ต่อไปนี้เป็นสคริปต์เพื่อสร้างสคริปต์ด้านบน (มีเพิ่มเล็กน้อย) จากแหล่งมาร์กอัปสำหรับหน้าเว็บนั้น นี่อาจจะแตกในบางจุดดังนั้นฉันเก็บสำเนาปัจจุบันข้างต้น
# Get the text from github
$url = "https://raw.githubusercontent.com/dotnet/docs/master/docs/framework/migration-guide/how-to-determine-which-versions-are-installed.md"
$md = Invoke-WebRequest $url -UseBasicParsing
$OFS = "`n"
# Replace the weird text in the tables, and the padding
# Then trim the | off the front and end of lines
$map = $md -split "`n" -replace " installed [^|]+" -replace "\s+\|" -replace "\|$" |
# Then we can build the table by looking for unique lines that start with ".NET Framework"
Select-String "^.NET" | Select-Object -Unique |
# And flip it so it's key = value
# And convert ".NET FRAMEWORK 4.5.2" to [version]4.5.2
ForEach-Object {
[version]$v, [int]$k = $_ -replace "\.NET Framework " -split "\|"
" $k = [version]'$v'"
}
# And output the whole script
@"
`$Lookup = @{
$map
}
# For extra effect we could get the Windows 10 OS version and build release id:
try {
`$WinRelease, `$WinVer = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" ReleaseId, CurrentMajorVersionNumber, CurrentMinorVersionNumber, CurrentBuildNumber, UBR
`$WindowsVersion = "`$(`$WinVer -join '.') (`$WinRelease)"
} catch {
`$WindowsVersion = [System.Environment]::OSVersion.Version
}
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
Get-ItemProperty -name Version, Release -EA 0 |
# For The One True framework (latest .NET 4x), change match to PSChildName -eq "Full":
Where-Object { `$_.PSChildName -match '^(?!S)\p{L}'} |
Select-Object @{name = ".NET Framework"; expression = {`$_.PSChildName}},
@{name = "Product"; expression = {`$Lookup[`$_.Release]}},
Version, Release,
# Some OPTIONAL extra output: PSComputerName and WindowsVersion
# The Computer name, so output from local machines will match remote machines:
@{ name = "PSComputerName"; expression = {`$Env:Computername}},
# The Windows Version (works on Windows 10, at least):
@{ name = "WindowsVersion"; expression = { `$WindowsVersion }}
"@
'^(?!S)\p{L}'
regex และรับข้อมูล Version และ Release นิพจน์ทั่วไปนั้นพยายามทำคุณสมบัติอะไร
PSChildName
เป็นชื่อลีฟของคีย์รีจิสตรี \p{L}
เป็นอักขระใด ๆ ในหมวดหมู่ "จดหมาย" ของ Unicode (?!S)
เป็นลบมองไปรอบ ๆ และ^
เป็นจุดเริ่มต้นของสตริง S
ดังนั้นจึงมีการเริ่มต้นด้วยตัวอักษรอื่นที่ไม่ใช่ ดังนั้นหากคุณพิจารณาเฉพาะ ASCII จะเป็นเช่นเดียวกับ$_.PSChildName -cmatch '^[A-RT-Za-z]'
(หมายเหตุ-cmatch
) S
ดังนั้นจึงพบว่ากุญแจที่ชื่อขึ้นต้นด้วยตัวอักษรอื่นที่ไม่ใช่ ฉันไม่รู้ว่าทำไมคุณถึงสนใจว่าไม่ใช่ ASCII หากคุณกำลังกรองชื่อที่ขึ้นต้นด้วยS
... แน่นอนว่าคุณกำลังสับสนอยู่
Get-ItemProperty -name Version,Release -EA 0
ทำ ฉันรู้ว่า-EA 0
เหมือนกัน-ErrorAction SilentlyContinue
แต่จะGet-ItemProperty -name Version,Release
มีผลกระทบอะไรบ้างเมื่อส่งผลลัพธ์ทั้งหมดให้กับมัน ดูเหมือนจะไม่ตัดตัวแปรใด ๆ ออกจากวัตถุเนื่องจากมีการใช้คำสั่งอื่น ๆ ในภายหลังในไปป์ไลน์ มันทำงานเกิดข้อผิดพลาดเมื่อไม่มีชื่อVersion
หรือRelease
หายไปจากคีย์แล้วส่งวัตถุที่สำเร็จไปยังคำสั่งถัดไปในไปป์ไลน์หรือไม่
(?!S)
ข้อเพื่อ(?![SW])
ยกเว้นรายการ "Windows *" เพิ่มเติม นอกจากนี้ยังสามารถทำได้(?=[vCF])
เนื่องจากมีเพียงคีย์เดียวที่เราสนใจคือรูทเวอร์ชันและคีย์ "เต็ม" และ "ไคลเอนต์" สำหรับ. NET 4.0+ ;)
gci 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' |
sort pschildname -des |
select -fi 1 -exp pschildname
คำตอบนี้จะไม่ส่งคืน 4.5 หากติดตั้งไว้ คำตอบด้านล่างจาก @Jaykul และการใช้การเรียกเก็บเงินคืนจะทำอย่างไร
เพิ่มการรองรับ v4.8 ในสคริปต์:
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse |
Get-ItemProperty -name Version,Release -EA 0 |
Where { $_.PSChildName -match '^(?![SW])\p{L}'} |
Select PSChildName, Version, Release, @{
name="Product"
expression={
switch -regex ($_.Release) {
"378389" { [Version]"4.5" }
"378675|378758" { [Version]"4.5.1" }
"379893" { [Version]"4.5.2" }
"393295|393297" { [Version]"4.6" }
"394254|394271" { [Version]"4.6.1" }
"394802|394806" { [Version]"4.6.2" }
"460798|460805" { [Version]"4.7" }
"461308|461310" { [Version]"4.7.1" }
"461808|461814" { [Version]"4.7.2" }
"528040|528049" { [Version]"4.8" }
{$_ -gt 528049} { [Version]"Undocumented version (> 4.8), please update script" }
}
}
}
[environment]::Version
ให้อินสแตนซ์Version
สำหรับ CLR ให้คุณใช้สำเนาปัจจุบันของ PSH (ตามที่ระบุไว้ที่นี่ )
$PSVersionTable
เพื่อค้นหารุ่นของ CLR PowerShell ที่กำลังทำงานอยู่
ไวยากรณ์ที่ถูกต้อง:
[System.Runtime.InteropServices.RuntimeEnvironment]::GetSystemVersion()
#or
$PSVersionTable.CLRVersion
GetSystemVersion
ฟังก์ชันส่งกลับสตริงเช่นนี้:
v2.0.50727 #PowerShell v2.0 in Win 7 SP1
หรือเช่นนี้
v4.0.30319 #PowerShell v3.0 (Windows Management Framework 3.0) in Win 7 SP1
$PSVersionTable
เป็นวัตถุแบบอ่านอย่างเดียว คุณสมบัติ CLRVersion เป็นหมายเลขเวอร์ชันที่มีโครงสร้างดังนี้:
Major Minor Build Revision
----- ----- ----- --------
4 0 30319 18444
ฉันพบสิ่งนี้ผ่านการทำให้แท็บเสร็จสมบูรณ์ใน powershell สำหรับ osx:
[System.Runtime.InteropServices.RuntimeInformation]::get_FrameworkDescription()
.NET Core 4.6.25009.03
[version]([Runtime.InteropServices.RuntimeInformation]::FrameworkDescription -replace '^.[^\d.]*','')
ไม่มีวิธีที่เชื่อถือได้สำหรับแพลตฟอร์มและสถาปัตยกรรมทั้งหมดโดยใช้สคริปต์อย่างง่าย ถ้าคุณต้องการเรียนรู้วิธีการที่เชื่อถือได้ให้เริ่มที่โพสต์บล็อกตัวอย่างรหัสตรวจจับ. NET Frameworkที่ทำการตรวจสอบในเชิงลึกมากขึ้น
อ้างถึงหน้าสคริปต์สำหรับการค้นหารุ่น NET ที่ติดตั้งบนเวิร์กสเตชันระยะไกลมีการติดตั้งบนเวิร์กสเตชันระยะไกล
สคริปต์อาจมีประโยชน์ในการค้นหาเวอร์ชัน. NET ของหลาย ๆ เครื่องบนเครือข่าย
ลองใช้โมดูลDotNetVersionLister ที่สามารถดาวน์โหลดได้ (ขึ้นอยู่กับข้อมูลรีจิสทรีและตารางค้นหารุ่นต่อรุ่นการตลาด)
ซึ่งจะใช้เช่นนี้:
PS> Get-DotNetVersion -LocalHost -nosummary
ComputerName : localhost
>=4.x : 4.5.2
v4\Client : Installed
v4\Full : Installed
v3.5 : Installed
v3.0 : Installed
v2.0.50727 : Installed
v1.1.4322 : Not installed (no key)
Ping : True
Error :
หรือเช่นนี้หากคุณต้องการทดสอบสำหรับ. NET Frameworkบางตัว> = 4 * :
PS> (Get-DotNetVersion -LocalHost -nosummary).">=4.x"
4.5.2
แต่มันจะไม่ทำงาน (ติดตั้ง / นำเข้า) เช่นกับPS v2.0 ( Win 7 , Win Server 2010มาตรฐาน) เนื่องจากความไม่ลงรอยกัน ...
(คุณสามารถข้ามการอ่านนี้และใช้รหัสด้านล่าง)
เราต้องทำงานกับPS 2.0บนเครื่องบางเครื่องและไม่สามารถติดตั้ง / นำเข้าDotNetVersionListerด้านบนได้
บนเครื่องอื่น ๆ ที่เราอยากจะอัปเดต (จากPS 2.0 ) เพื่อPS 5.1 (ซึ่งในทางกลับกันความต้องการ.NET Framework> = 4.5 ) ด้วยความช่วยเหลือของทั้งสอง บริษัท ที่กำหนดเองและInstall-DotnetLatestCompany
เพื่อให้คำแนะนำแก่ผู้ดูแลระบบผ่านกระบวนการติดตั้ง / อัปเดตเราจะต้องพิจารณารุ่น. NET ในฟังก์ชั่นเหล่านี้ในทุกเครื่องและรุ่น PS ที่มีอยู่
ดังนั้นเราจึงใช้ฟังก์ชั่นด้านล่างเพื่อกำหนดความปลอดภัยในทุกสภาพแวดล้อม ...Install-PSLatestCompany
ดังนั้นตัวอย่างการใช้งานโค้ดและด้านล่าง (แยกแล้ว) มีประโยชน์ที่นี่ (ตามคำตอบอื่น ๆ ที่นี่):
function Get-DotNetVersionByFs {
<#
.SYNOPSIS
NOT RECOMMENDED - try using instead:
Get-DotNetVersion
from DotNetVersionLister module (https://github.com/EliteLoser/DotNetVersionLister),
but it is not usable/importable in PowerShell 2.0
Get-DotNetVersionByReg
reg(istry) based: (available herin as well) but it may return some wrong version or may not work reliably for versions > 4.5
(works in PSv2.0)
Get-DotNetVersionByFs (this):
f(ile) s(ystem) based: determines the latest installed .NET version based on $Env:windir\Microsoft.NET\Framework content
this is unreliable, e.g. if 4.0* is already installed some 4.5 update will overwrite content there without
renaming the folder
(works in PSv2.0)
.EXAMPLE
PS> Get-DotnetVersionByFs
4.0.30319
.EXAMPLE
PS> Get-DotnetVersionByFs -All
1.0.3705
1.1.4322
2.0.50727
3.0
3.5
4.0.30319
.NOTES
from https://stackoverflow.com/a/52078523/1915920
#>
[cmdletbinding()]
param(
[Switch]$All ## do not return only latest, but all installed
)
$list = ls $Env:windir\Microsoft.NET\Framework |
?{ $_.PSIsContainer -and $_.Name -match '^v\d.[\d\.]+' } |
%{ $_.Name.TrimStart('v') }
if ($All) { $list } else { $list | select -last 1 }
}
function Get-DotNetVersionByReg {
<#
.SYNOPSIS
NOT RECOMMENDED - try using instead:
Get-DotNetVersion
From DotNetVersionLister module (https://github.com/EliteLoser/DotNetVersionLister),
but it is not usable/importable in PowerShell 2.0.
Determines the latest installed .NET version based on registry infos under 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP'
.EXAMPLE
PS> Get-DotnetVersionByReg
4.5.51209
.EXAMPLE
PS> Get-DotnetVersionByReg -AllDetailed
PSChildName Version Release
----------- ------- -------
v2.0.50727 2.0.50727.5420
v3.0 3.0.30729.5420
Windows Communication Foundation 3.0.4506.5420
Windows Presentation Foundation 3.0.6920.5011
v3.5 3.5.30729.5420
Client 4.0.0.0
Client 4.5.51209 379893
Full 4.5.51209 379893
.NOTES
from https://stackoverflow.com/a/52078523/1915920
#>
[cmdletbinding()]
param(
[Switch]$AllDetailed ## do not return only latest, but all installed with more details
)
$Lookup = @{
378389 = [version]'4.5'
378675 = [version]'4.5.1'
378758 = [version]'4.5.1'
379893 = [version]'4.5.2'
393295 = [version]'4.6'
393297 = [version]'4.6'
394254 = [version]'4.6.1'
394271 = [version]'4.6.1'
394802 = [version]'4.6.2'
394806 = [version]'4.6.2'
460798 = [version]'4.7'
460805 = [version]'4.7'
461308 = [version]'4.7.1'
461310 = [version]'4.7.1'
461808 = [version]'4.7.2'
461814 = [version]'4.7.2'
}
$list = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
Get-ItemProperty -name Version, Release -EA 0 |
# For One True framework (latest .NET 4x), change match to PSChildName -eq "Full":
Where-Object { $_.PSChildName -match '^(?!S)\p{L}'} |
Select-Object `
@{
name = ".NET Framework" ;
expression = {$_.PSChildName}},
@{ name = "Product" ;
expression = {$Lookup[$_.Release]}},
Version, Release
if ($AllDetailed) { $list | sort version } else { $list | sort version | select -last 1 | %{ $_.version } }
}
ตัวอย่างการใช้งาน:
PS> Get-DotNetVersionByFs
4.0.30319
PS> Get-DotNetVersionByFs -All
1.0.3705
1.1.4322
2.0.50727
3.0
3.5
4.0.30319
PS> Get-DotNetVersionByReg
4.5.51209
PS> Get-DotNetVersionByReg -AllDetailed
.NET Framework Product Version Release
-------------- ------- ------- -------
v2.0.50727 2.0.50727.5420
v3.0 3.0.30729.5420
Windows Communication Foundation 3.0.4506.5420
Windows Presentation Foundation 3.0.6920.5011
v3.5 3.5.30729.5420
Client 4.0.0.0
Client 4.5.2 4.5.51209 379893
Full 4.5.2 4.5.51209 379893
(Get-DotNetVersion -LocalHost -nosummary).">=4.x"
ไม่สวย. ไม่สวยแน่นอน :
ls $Env:windir\Microsoft.NET\Framework | ? { $_.PSIsContainer } | select -exp Name -l 1
สิ่งนี้อาจใช้งานได้หรือไม่ แต่สำหรับเวอร์ชั่นล่าสุดนั้นน่าจะน่าเชื่อถือเพราะมีโฟลเดอร์ว่างสำหรับเวอร์ชั่นเก่า (1.0, 1.1) แต่ไม่ใช่โฟลเดอร์ที่ใหม่กว่า - โฟลเดอร์เหล่านั้นจะปรากฏเมื่อติดตั้งเฟรมเวิร์กที่เหมาะสมเท่านั้น
ถึงกระนั้นฉันสงสัยว่าจะต้องมีวิธีที่ดีกว่า
ls $Env:windir\Microsoft.NET\Framework | ? { $_.PSIsContainer -and $_.Name -match '^v\d.[\d\.]+' } | % { $_.Name.TrimStart('v') }
หากคุณติดตั้ง Visual Studio บนเครื่องของคุณให้เปิดพรอมต์คำสั่งสำหรับนักพัฒนา Visual Studio และพิมพ์คำสั่งต่อไปนี้: clrver
มันจะแสดง. NET Framework รุ่นที่ติดตั้งไว้ทั้งหมดในเครื่องนั้น
ที่นี่ฉันจะใช้คำถามนี้ตามเอกสาร msft :
$gpParams = @{
Path = 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full'
ErrorAction = 'SilentlyContinue'
}
$release = Get-ItemProperty @gpParams | Select-Object -ExpandProperty Release
".NET Framework$(
switch ($release) {
({ $_ -ge 528040 }) { ' 4.8'; break }
({ $_ -ge 461808 }) { ' 4.7.2'; break }
({ $_ -ge 461308 }) { ' 4.7.1'; break }
({ $_ -ge 460798 }) { ' 4.7'; break }
({ $_ -ge 394802 }) { ' 4.6.2'; break }
({ $_ -ge 394254 }) { ' 4.6.1'; break }
({ $_ -ge 393295 }) { ' 4.6'; break }
({ $_ -ge 379893 }) { ' 4.5.2'; break }
({ $_ -ge 378675 }) { ' 4.5.1'; break }
({ $_ -ge 378389 }) { ' 4.5'; break }
default { ': 4.5+ not installed.' }
}
)"
ตัวอย่างนี้ใช้งานได้กับ PowerShell ทุกรุ่นและจะใช้งานได้ตลอดกาลเนื่องจาก 4.8 เป็นเวอร์ชันล่าสุดของ. NET Framework
นี่คือแนวคิดทั่วไป:
รับไอเท็มลูกในไดเร็กทอรี. NET Framework ที่เป็นคอนเทนเนอร์ที่มีชื่อตรงกับรูปแบบหมายเลข v จำนวนจุดวีจำนวนจำนวนจุดเรียงลำดับตามชื่อจากมากไปน้อยนำวัตถุแรกและส่งกลับคุณสมบัติของชื่อ
นี่คือสคริปต์:
(Get-ChildItem -Path $Env:windir\Microsoft.NET\Framework | Where-Object {$_.PSIsContainer -eq $true } | Where-Object {$_.Name -match 'v\d\.\d'} | Sort-Object -Property Name -Descending | Select-Object -First 1).Name
ฉันจะลองอันนี้ใน PowerShell: ใช้งานได้สำหรับฉัน!
(Get-ItemProperty "HKLM: Setup \ NDP \ v4 \ Full Framework Software \ Microsoft \ NET Framework)
ฉันไม่ได้อยู่ในไวยากรณ์ PowerShell ของฉัน แต่ฉันคิดว่าคุณสามารถเรียกSystem.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion ()ได้ นี่จะคืนค่าเวอร์ชันเป็นสตริง (เหมือนกับที่v2.0.50727
ฉันคิด)
[System.Runtime.InteropServices.RuntimeEnvironment]::GetSystemVersion()
, แต่มันจะคืนค่า v4.0.30319, แม้ว่า v4.6 จะถูกติดตั้งในเคสของฉัน
นี่เป็นผลสืบเนื่องของการโพสต์ก่อนหน้านี้ แต่ได้รับรุ่นล่าสุดของกรอบสุทธิ 4 ในการทดสอบของฉัน
get-itemproperty -name version,release "hklm:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\FULL"
ซึ่งจะช่วยให้คุณสามารถเรียกใช้คำสั่งไปยังเครื่องระยะไกล:
invoke-command -computername server01 -scriptblock {get-itemproperty -name version,release "hklm:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\FULL" | select pscomputername,version,release}
ซึ่งตั้งค่าความเป็นไปได้นี้ด้วยคำนำหน้า ADModule และการตั้งชื่อ:
get-adcomputer -Filter 'name -like "*prefix*"' | % {invoke-command -computername $_.name -scriptblock {get-itemproperty -name version,release "hklm:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\FULL" | select pscomputername,version,release} | ft