当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 我的 PowerShell 存储桶中有一个文件

作者:精品下载站 日期:2024-12-14 08:04:19 浏览:12 分类:玩电脑

我的 PowerShell 存储桶中有一个文件


[玩转系统] 我的 PowerShell 存储桶中有一个文件

如果有一项任务是我从未停止过的,那就是查找文件。我不断创造新的方法来组织文件并以有意义的格式显示它们。当然,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[]] 对象。原因是,当您添加到数组时,每次添加元素时,数组都会被擦除并重建。对于少量物品来说,这没有问题。但我可能会处理数千个文件。为了易用性,将会有轻微的性能权衡。我在 3 秒多一点的时间里向数组添加了近 9000 个文件。使用通用列表只需要不到一秒钟的时间。

在函数的 Begin 块中,我可以像这样定义集合对象:

$list = [system.Collections.Generic.list[system.io.fileinfo]]::new()

但是,我将使用脚本技术来简化该语法。在定义函数的 .ps1 文件中,我将在函数之前插入一条 using 语句。

Using namespace System.Collections.Generic

现在在 Begin 块中,我的定义要简单得多:

 $list = [List[System.IO.FileInfo]]::new()

您通常会告诉 PowerShell 什么类型的对象将进入您的列表。就我而言,我知道它将是 System.IO.FileInfo 对象。如果您不确定或想要混合对象,您可以简单地使用“对象”。

列表对象有许多方法,我不会在这里介绍。在“进程”块中,我将每个文件添加到列表中。

 $list.Add($FilePath)

当我到达 End 块时, $list 将包含通过管道传输到我的函数中的所有文件。现在我可以施展我的水桶魔法了。

年龄分组

我必须决定我想要什么样的陈年桶。我是按照年、月、天数来决定的。我将用一个参数来控制它。

[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 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

格式化输出

这项工作的重点是让我可以轻松地查看按年龄分组的文件,如下所示:

[玩转系统] 我的 PowerShell 存储桶中有一个文件

这就是为什么我需要自定义类型名称和预排序输出。我使用 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

我现在只有一个视图。

[玩转系统] 我的 PowerShell 存储桶中有一个文件

因为我使用枚举,所以我的文件已正确排序,并且我得到了有意义的输出。我可能需要稍微调整一下格式文件。

[玩转系统] 我的 PowerShell 存储桶中有一个文件

或者我可以调整我的 PowerShell 表达式。

[玩转系统] 我的 PowerShell 存储桶中有一个文件

与我的许多帖子一样,最终结果并不像我用来实现目标的技术那么重要。欢迎提出意见和问题。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯