มีไฟล์อยู่ใน My PowerShell Bucket
หากมีงานหนึ่งที่ฉันไม่เคยหยุดทำ นั่นก็คือการค้นหาไฟล์ ฉันกำลังสร้างวิธีใหม่ๆ อยู่เสมอในการจัดระเบียบไฟล์และแสดงไฟล์ในรูปแบบที่มีความหมาย โดยปกติแล้ว PowerShell เป็นเครื่องมือที่ยอดเยี่ยมสำหรับงานนี้ Get-ChildItem เป็นจุดเริ่มต้นที่เหมาะสมอย่างเห็นได้ชัด cmdlet ทำงานได้ดีในการรับเฉพาะไฟล์จากโฟลเดอร์ และฉันก็สามารถกรองเบื้องต้นตามชื่อและนามสกุลด้วยไวด์การ์ดได้ แต่งานล่าสุดของฉันคือการจัดระเบียบไฟล์ตามวันที่ และเนื่องจากวิธีที่ Get-ChildItem ทำงานภายใต้การควบคุม ฉันจึงต้องหันไปใช้การกรองล่าช้าด้วย Where-Object ไม่มีอะไรผิดปกติกับสิ่งนั้น แต่ถ้านี่เป็นงานที่ฉันมักจะทำซ้ำ แสดงว่าฟังก์ชัน PowerShell อยู่บนกระดานวาดภาพ เป้าหมายของฉันคือการสร้างฟังก์ชันที่จะแสดงไฟล์ที่จัดกลุ่มตามอายุ เช่น 1 สัปดาห์หรือ 6 เดือน แม้ว่าฉันจะคำนึงถึงอายุตามเวลาที่เขียนครั้งล่าสุดเป็นหลัก แต่ฉัน (หรือคุณ) อาจจำเป็นต้องยึดอายุตามเวลาที่สร้าง มาเขียนโค้ดกันเถอะ
การสร้างรายการ
ฟังก์ชั่นของฉันจะรับอินพุตไปป์ไลน์จาก Get-Childitem อย่างไรก็ตาม เนื่องจากฉันกำลังจะจัดเรียงและจัดกลุ่มไฟล์ ฉันจึงไม่สามารถทำได้จนกว่าไฟล์ทั้งหมดจาก Get-ChildItem จะได้รับการประมวลผลโดยฟังก์ชันของฉัน ฉันต้องเก็บไว้ชั่วคราวเมื่อผ่านท่อ ฉันสามารถใช้วัตถุอาร์เรย์และเพิ่มแต่ละวัตถุไปป์ไลน์ได้ รหัสของฉันอาจมีลักษณะเช่นนี้
Begin {
$files = @()
}
Process {
$files += $inputobject
}
End {
$files | Sort-Object -property LastWriteTime ...
}ในทางเทคนิคแล้วไม่มีอะไรผิดกับแนวทางนี้ และฉันใช้มันมาหลายปีแล้ว แต่เมื่อเร็ว ๆ นี้ ฉันหันไปใช้รายการทั่วไป โดยเฉพาะวัตถุ [System.Collections.Generic.list[]] เหตุผลก็คือเมื่อคุณเพิ่มเข้าไปในอาร์เรย์ แต่ละครั้งที่คุณเพิ่มองค์ประกอบ อาร์เรย์จะถูกล้างและสร้างใหม่ สำหรับสินค้าจำนวนน้อยก็ไม่ใช่ปัญหา แต่ฉันสามารถประมวลผลไฟล์ได้หลายพันไฟล์ จะมีการประนีประนอมประสิทธิภาพเล็กน้อยเพื่อความสะดวกในการใช้งาน ฉันเพิ่มไฟล์เกือบ 9,000 ไฟล์ลงในอาเรย์ในเวลาเพียง 3 วินาที การใช้รายการทั่วไปใช้เวลาไม่ถึงหนึ่งวินาที
ในบล็อก Begin ของฟังก์ชันของฉัน ฉันสามารถกำหนดวัตถุคอลเลกชันได้ดังนี้:
$list = [system.Collections.Generic.list[system.io.fileinfo]]::new()
แต่ฉันจะใช้เทคนิคการเขียนสคริปต์เพื่อลดความซับซ้อนของไวยากรณ์นั้น ในไฟล์ .ps1 ที่กำหนดฟังก์ชันของฉัน ก่อนฟังก์ชันนี้ ฉันจะแทรกคำสั่ง การใช้
Using namespace System.Collections.Genericตอนนี้ในบล็อก Begin คำจำกัดความของฉันง่ายกว่ามาก:
$list = [List[System.IO.FileInfo]]::new()โดยทั่วไป คุณจะบอก PowerShell ว่าวัตถุประเภทใดที่จะอยู่ในรายการของคุณ ในกรณีของฉัน ฉันรู้ว่ามันจะเป็นอ็อบเจ็กต์ System.IO.FileInfo หากคุณไม่แน่ใจหรือจะมีวัตถุผสมกัน คุณสามารถใช้ 'วัตถุ' ได้
รายการวัตถุมีหลายวิธีที่ฉันจะไม่เข้าไปที่นี่ ในบล็อกกระบวนการ ฉันจะเพิ่มแต่ละไฟล์ลงในรายการ
$list.Add($FilePath)เมื่อฉันไปถึง End block $list จะมีไฟล์ทั้งหมดที่ไปป์ในฟังก์ชันของฉัน ตอนนี้ฉันสามารถทำเวทย์มนตร์ถังได้แล้ว
การจัดกลุ่มอายุ
I had to decide what kind of aging buckets I wanted. ฉันตัดสินใจตามปีและจำนวนวัน ฉันจะควบคุมสิ่งนี้ด้วยพารามิเตอร์
[Parameter(HelpMessage = "How do you want the files organized? The default is Month.")]
[ValidateNotNullOrEmpty()]
[ValidateSet("Month","Year","Days")]
[string]$GroupBy = "Month"ฉันยังต้องควบคุมสิ่งที่ฉันกำลังตัดสินใจอายุมาก
[Parameter(HelpMessage = "Specify if grouping by the file's CreationTime or LastWriteTime property. The default is LastWriteTime.")]
[ValidateSet("CreationTime","LastWritetime")]
[ValidateNotNullOrEmpty()]
[string]$Property = "LastWriteTime",ฉันต้องเพิ่มคุณสมบัติที่ระบุกลุ่มอายุสำหรับแต่ละไฟล์ในรายการของฉัน ฉันรู้ว่าฉันกำลังจะเพิ่มคุณสมบัติให้กับวัตถุไฟล์
$file | Add-Member -MemberType NoteProperty -Name AgeGroup -Value $valueค่าจะถูกคำนวณตามถังการจัดกลุ่ม ถ้าฉันต้องการจัดกลุ่มในปีนี้สำหรับแต่ละไฟล์ฉันจะเรียกใช้รหัสนี้:
$value = $file.$property.year
$sort = "AgeGroup"ตัวแปร $sort จะถูกใช้ในตอนท้ายของฟังก์ชั่นเพื่อช่วยจัดรูปแบบผลลัพธ์ สำหรับการจัดกลุ่มเดือนฉันต้องการเดือนและปี ฉันยังต้องเปลี่ยนค่าให้เป็นวัตถุ DateTime เพื่อให้มันเรียงลำดับอย่างถูกต้อง $Property จะเป็นทั้ง CreationTime หรือ LastWriteTime
$value = "$($file.$property.month)/$($file.$property.year)"
$sort = {$_.AgeGroup -as [datetime]}คุณสมบัติที่กำหนดเอง ค่าที่กำหนดเอง
การจัดกลุ่มตามจำนวนวันเป็นขั้นตอนที่ยุ่งยากที่สุด ฉันตัดสินใจกำหนดชุดชื่อบัคเก็ต เช่น OneWeek และ SixMonth อย่างไรก็ตาม นี่หมายความว่าการเรียงลำดับจะใช้ค่าสตริงซึ่งไม่ถูกต้อง วิธีแก้ปัญหาของฉันคือกำหนด Enum ในไฟล์ฟังก์ชันของฉัน
Enum FileAge {
YearPlus
Year
NineMonth
SixMonth
ThreeMonth
OneMonth
OneWeek
OneDay
}ทำไม เนื่องจากใน Enum แต่ละค่าจะมีค่าตัวเลขเริ่มต้นที่ 0 จริงๆ แม้ว่าฉันจะใช้สตริงเพื่อระบุกลุ่มอายุ แต่เมื่อฉันเรียงลำดับ มันก็จะเป็นค่าตัวเลขโดยนัย ด้วยแจงนับนี้ ฉันสามารถกำหนดค่าตามจำนวนวันทั้งหมดได้
$age = New-TimeSpan -start $file.$property -end $now
$value = switch ($age.totaldays -as [int]) {
{$_ -ge 365} {[FileAge]::YearPlus ; break}
{$_ -gt 270 -AND $_ -lt 365 }{[FileAge]::Year;break}
{$_ -gt 180 -AND $_ -le 270 }{[FileAge]::NineMonth;break}
{$_ -gt 90 -AND $_ -le 180} {[FileAge]::SixMonth;break}
{$_ -gt 30 -AND $_ -le 90} {[FileAge]::ThreeMonth;break}
{$_ -gt 7 -AND $_ -le 30} {[FileAge]::OneMonth;break}
{$_ -gt 1 -And $_ -le 7} {[FileAge]::OneWeek;break}
{$_ -le 1} {[FileAge]::OneDay;break}
}
$sort = "AgeGroup"ฉันสามารถสร้างออบเจ็กต์แบบกำหนดเองได้แม้จะใช้คลาส PowerShell ก็ตาม แต่ฉันดีใจที่ได้ใช้ประเภท System.IO.FileInfo อย่างไรก็ตาม การจัดรูปแบบเริ่มต้นไม่ทราบอะไรเกี่ยวกับคุณสมบัติที่กำหนดเองของฉัน ฉันรู้ว่าฉันต้องการสร้างการจัดรูปแบบของตัวเอง ดังนั้นฉันจึงต้องใส่ชื่อประเภทใหม่
$file.psobject.TypeNames.insert(0,"FileAgingInfo")เมื่อวางแผนล่วงหน้า ฉันรู้ว่าจะใช้การจัดกลุ่มในการจัดรูปแบบที่กำหนดเอง ซึ่งหมายความว่าต้องจัดเรียงออบเจ็กต์ โดยปกติแล้ว ฉันจะปล่อยให้การเรียงลำดับออกจากฟังก์ชัน แต่ในกรณีนี้ ฉันต้องการมัน
$list | Sort-Object -Property $sort,DirectoryName,$property,Nameฉันกำลังเรียงลำดับตัวแปร $sort ของฉันก่อน จากนั้นตามด้วยชื่อไดเร็กทอรี จากนั้นคุณสมบัติ CreationTime หรือ LastWriteTime และสุดท้ายคือชื่อไฟล์
นี่คือฟังก์ชั่นที่สมบูรณ์:
#requires -version 5.1
#declaring a namespace here makes the code simpler later on
Using namespace System.Collections.Generic
#define an enumeration that will be used in a new custom property
Enum FileAge {
YearPlus
Year
NineMonth
SixMonth
ThreeMonth
OneMonth
OneWeek
OneDay
}
Function Get-FileAgeGroup {
[cmdletbinding()]
[alias("gfa")]
[OutputType("FileAgingInfo")]
Param(
[Parameter(Mandatory,Position=0,ValueFromPipeline,HelpMessage = "The path to a file. If you pipe from a Get-ChildItem command, be sure to use the -File parameter.")]
[ValidateNotNullOrEmpty()]
[System.IO.FileInfo]$FilePath,
[Parameter(HelpMessage = "Specify if grouping by the file's CreationTime or LastWriteTime property. The default is LastWriteTime.")]
[ValidateSet("CreationTime","LastWritetime")]
[ValidateNotNullOrEmpty()]
[string]$Property = "LastWriteTime",
[Parameter(HelpMessage = "How do you want the files organized? The default is Month.")]
[ValidateNotNullOrEmpty()]
[ValidateSet("Month","Year","Days")]
[string]$GroupBy = "Month"
)
Begin {
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)"
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Grouping by $GroupBy on the $Property property"
#initialize a list to contain all piped in files
#this code is shorter because of the Using statement at the beginning of this file
$list = [List[System.IO.FileInfo]]::new()
#get a static value for now
$now = Get-Date
} #begin
Process {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Adding $FilePath"
#add each file to the list
$list.Add($FilePath)
} #process
End {
#now process all the files and add aging properties
Write-Verbose "[$((Get-Date).TimeofDay) END ] Sorting $($list.count) files"
#add custom properties based on the age grouping
foreach ($file in $list) {
switch ($GroupBy) {
"Month" {
$value = "$($file.$property.month)/$($file.$property.year)"
$sort = {$_.AgeGroup -as [datetime]}
}
"Year" {
$value = $file.$property.year
$sort = "AgeGroup"
}
"Days" {
$age = New-TimeSpan -start $file.$property -end $now
$value = switch ($age.totaldays -as [int]) {
{$_ -ge 365} {[FileAge]::YearPlus ; break}
{$_ -gt 270 -AND $_ -lt 365 }{[FileAge]::Year;break}
{$_ -gt 180 -AND $_ -le 270 }{[FileAge]::NineMonth;break}
{$_ -gt 90 -AND $_ -le 180} {[FileAge]::SixMonth;break}
{$_ -gt 30 -AND $_ -le 90} {[FileAge]::ThreeMonth;break}
{$_ -gt 7 -AND $_ -le 30} {[FileAge]::OneMonth;break}
{$_ -gt 1 -And $_ -le 7} {[FileAge]::OneWeek;break}
{$_ -le 1} {[FileAge]::OneDay;break}
}
$sort = "AgeGroup"
}
} #switch
#add a custom property to each file object
$file | Add-Member -MemberType NoteProperty -Name AgeGroup -Value $value
#insert a custom type name which will be used by the custom format file
$file.psobject.TypeNames.insert(0,"FileAgingInfo")
} #foreach file
#write the results to the pipeline. Sorting results so that the default
#formatting will be displayed properly
$list | Sort-Object -Property $sort,DirectoryName,$property,Name
Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"
} #end
} #close Get-FileAgeGroupเอาต์พุตที่จัดรูปแบบแล้ว
จุดประสงค์ทั้งหมดของงานนี้คือเพื่อให้ฉันดูไฟล์ที่จัดกลุ่มตามอายุได้ง่ายสำหรับฉันดังนี้:
นี่คือเหตุผลที่ฉันต้องการชื่อประเภทที่กำหนดเองและเอาต์พุตที่เรียงลำดับไว้ล่วงหน้า ฉันใช้ New-PSFormatXML เพื่อสร้างไฟล์ ps1xml รูปแบบที่กำหนดเอง
<!--
Format type data generated 09/17/2021 11:54:13 by PROSPERO\Jeff
This file was created using the New-PSFormatXML command that is part
of the PSScriptTools module.
https://github.com/jdhitsolutions/PSScriptTools
-->
<Configuration>
<ViewDefinitions>
<View>
<!--Created 09/17/2021 11:54:13 by PROSPERO\Jeff-->
<Name>default</Name>
<ViewSelectedBy>
<TypeName>FileAgingInfo</TypeName>
</ViewSelectedBy>
<GroupBy>
<!--
You can also use a scriptblock to define a custom property name.
You must have a Label tag.
<ScriptBlock>$_.machinename.toUpper()</ScriptBlock>
<Label>Computername</Label>
Use <Label> to set the displayed value.
-->
<PropertyName>AgeGroup</PropertyName>
<Label>AgeGroup</Label>
</GroupBy>
<TableControl>
<!--Delete the AutoSize node if you want to use the defined widths.-->
<AutoSize />
<TableHeaders>
<TableColumnHeader>
<Label>Directory</Label>
<Width>16</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Created</Label>
<Width>24</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Modified</Label>
<Width>23</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Length</Label>
<Width>11</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Name</Label>
<Width>10</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<!--
By default the entries use property names, but you can replace them with scriptblocks.
<ScriptBlock>$_.foo /1mb -as [int]</ScriptBlock>
-->
<TableColumnItem>
<PropertyName>DirectoryName</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>CreationTime</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>LastWriteTime</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Length</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>ที่ส่วนท้ายของไฟล์ ps1 ฉันนำเข้าไฟล์รูปแบบไปยังเซสชัน PowerShell ของฉัน
Update-FormatData $psscriptroot\FileAge.format.ps1xml
ตอนนี้ผมมีมุมมองเดียวเท่านั้น
เนื่องจากฉันใช้ enum ไฟล์ของฉันจึงได้รับการจัดเรียงอย่างเหมาะสมและได้ผลลัพธ์ที่มีความหมาย ฉันอาจต้องปรับแต่งไฟล์รูปแบบเล็กน้อย
หรือฉันสามารถปรับนิพจน์ PowerShell ของฉันได้
เช่นเดียวกับโพสต์อื่นๆ ของฉัน ผลลัพธ์ที่ได้ไม่สำคัญเท่ากับเทคนิคที่ฉันเคยไปถึงจุดนั้น ความคิดเห็นและคำถามยินดีต้อนรับ




