ค้นหาเว็บไซต์

มีไฟล์อยู่ใน 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 ของฉันได้

เช่นเดียวกับโพสต์อื่นๆ ของฉัน ผลลัพธ์ที่ได้ไม่สำคัญเท่ากับเทคนิคที่ฉันเคยไปถึงจุดนั้น ความคิดเห็นและคำถามยินดีต้อนรับ

บทความที่เกี่ยวข้อง