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

[玩转系统] 使用 PowerShell 全面开放

作者:精品下载站 日期:2024-12-14 20:38:27 浏览:12 分类:玩电脑

使用 PowerShell 全面开放


几周前,发布了 Iron Scripter PowerShell 脚本挑战赛。挑战涉及广泛的目录列表。这总是让我想到“敞开心扉”,这让我想到了蔡斯的“敞开心扉”。 (我以前吹小号,Chase 是当时的乐队)。无论如何,解决这个挑战很可能涉及 Get-ChildItem 和 Format-Wide 的组合。这就是我的想法。

独立功能

我的第一个解决方案采用独立函数的形式。该函数的大部分参数与 Get-ChildItem 相同。但我还提供了一个参数,让用户指定在显示的 [] 部分中显示哪些数据。这是完整的功能,然后我会指出一些事情。

Function Show-DirectoryInfo {
    #this version writes formatted data to the pipeline
    [cmdletbinding()]
    [alias("sw")]
    Param(
        [Parameter(Position = 0,HelpMessage = "Enter a file system path")]
        [ValidateScript({( Test-Path $_ ) -AND ((Get-Item $_).psprovider.name -eq "FileSystem")})]
        [string]$Path = ".",
        [Parameter(Position = 1,HelpMessage = "What detail do you want to see? Size or Count of files?")]
        [ValidateSet("Size", "Count")]
        [string]$Detail = "Count",
        [switch]$Recurse,
        [Int32]$Depth
    )
    DynamicParam {
        if ($Detail -eq "Size") {
            #define a parameter attribute object
            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.HelpMessage = "Enter a unit of measurement: KB, MB, GB Bytes."

            #define a collection for attributes
            $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributeCollection.Add($attributes)

            #define an alias
            $alias = New-Object System.Management.Automation.AliasAttribute -ArgumentList "as"
            $attributeCollection.Add($alias)

            #add a validateion set
            $set = New-Object -type System.Management.Automation.ValidateSetAttribute -ArgumentList ("bytes", "KB", "MB", "GB")
            $attributeCollection.add($set)

            #define the dynamic param
            $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Unit", [string], $attributeCollection)
            $dynParam1.Value = "Bytes"

            #create array of dynamic parameters
            $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add("Unit", $dynParam1)
            #use the array
            return $paramDictionary

        } #if
    } #dynamic parameter

    Begin {
        Write-Verbose "Starting $($myinvocation.MyCommand)"

        #set a default size unit
        if ($Detail -eq 'Size' -AND (-not $PSBoundParameters.ContainsKey("unit"))) {
            $PSBoundParameters.Add("Unit", "bytes")
        }
        #these internal helper functions could be combined but I'll keep them
        #separate for the sake of clarity.
        function _getCount {
            [cmdletbinding()]
            Param([string]$Path)
            (Get-ChildItem -Path $path -File).count
        }

        function _getSize {
            [cmdletbinding()]
            Param([string]$Path, [string]$Unit)
            $sum = (Get-ChildItem $path -File | Measure-Object -Sum -Property length).sum
            #write-verbose "detected $unit"
            Switch ($Unit) {
                "KB" { "$([math]::round($sum/1KB,2))KB" }
                "MB" { "$([math]::round($sum/1MB,2))MB" }
                "GB" { "$([math]::round($sum/1GB,2))GB" }
                Default { $sum }
            }
        }
        Write-Verbose "PSBoundParameters"
        Write-Verbose ($PSBoundParameters | Out-String)

        #build a hashtable of parameters to splat to Get-ChildItem
        $gciParams = @{
            Path = $Path
            Directory = $True
        }
        if ($PSBoundParameters["Depth"]) {
            $gciParams.Add("Depth", $PSBoundParameters["Depth"])
        }
        if ($PSBoundParameters["Recurse"]) {
            $gciParams.Add("Recurse", $PSBoundParameters["Recurse"])
        }
    } #begin
    Process {
        Write-Verbose "Processing $(Convert-Path $Path)"

        $directories = Get-ChildItem @gciParams

        #this code could be consolidated using techniques like splatting. This version emphasizes clarity.
        if ($Detail -eq "count") {
            Write-Verbose "Getting file count"
            if ($Recurse) {
                $directories | Get-ChildItem -Recurse -Directory | Format-Wide -Property { "$($_.name) [$( _getCount $_.fullname)]" } -AutoSize -GroupBy @{Name = "Path"; Expression = { $_.Parent }}
            }
            else {
                $directories | Format-Wide -Property { "$($_.name) [$( _getCount $_.fullname)]" } -AutoSize -GroupBy @{Name="Path";Expression={$_.Parent}}
            }
        }
        else {
            Write-Verbose "Getting file size in $($PSBoundParameters['Unit']) units"
            if ($Recurse) {
                $directories | Get-ChildItem -Recurse -Directory | Format-Wide -Property { "$($_.name) [$( _getsize -Path $_.fullname -unit $($PSBoundParameters['Unit']))]" } -AutoSize -GroupBy @{Name = "Path"; Expression = { $_.Parent }}
            }
            else {
                $directories | Format-Wide -Property { "$($_.name) [$( _getsize -path $_.fullname -unit $($PSBoundParameters['Unit']))]" } -AutoSize -GroupBy @{Name = "Path"; Expression = { $_.Parent }}
            }
        }
    } #Process

    End {
        Write-Verbose "Ending $($myinvocation.MyCommand)"
    }
}

我的函数包含解决一些挑战奖励元素的代码。首先,我使用参数验证来确保路径存在并且是文件系统路径。

[Parameter(Position = 0,HelpMessage = "Enter a file system path")]
[ValidateScript({( Test-Path $_ ) -AND ((Get-Item $_).psprovider.name -eq "FileSystem")})]
[string]$Path = ".",

我一开始有 2 个 ValidateScript 设置,但决定将它们组合起来。 Detail 参数是使用 ValidateSet 属性定义的。

[Parameter(Position = 1,HelpMessage = "What detail do you want to see? Size or Count of files?")]
[ValidateSet("Size", "Count")]
[string]$Detail = "Count",

现在是有趣的部分。我想要一种方法让用户格式化总文件大小,即以 KB 为单位(如果他们愿意的话)。但只有当用户选择使用“详细信息”参数的“大小”值时,才需要执行此操作。我选择使用动态参数。

DynamicParam {
    if ($Detail -eq "Size") {
        #define a parameter attribute object
        $attributes = New-Object System.Management.Automation.ParameterAttribute
        $attributes.HelpMessage = "Enter a unit of measurement: KB, MB, GB Bytes."

        #define a collection for attributes
        $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $attributeCollection.Add($attributes)

        #define an alias
        $alias = New-Object System.Management.Automation.AliasAttribute -ArgumentList "as"
        $attributeCollection.Add($alias)

        #add a validateion set
        $set = New-Object -type System.Management.Automation.ValidateSetAttribute -ArgumentList ("bytes", "KB", "MB", "GB")
        $attributeCollection.add($set)

        #define the dynamic param
        $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Unit", [string], $attributeCollection)
        $dynParam1.Value = "Bytes"

        #create array of dynamic parameters
        $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
        $paramDictionary.Add("Unit", $dynParam1)
        #use the array
        return $paramDictionary

    } #if
} #dynamic parameter

仅当详细参数为“Size”时,此参数才存在。该代码创建一个动态参数作为 Unit ,别名为 As。它还定义了一个验证集。

该函数的主要部分使用适当的参数调用 Get-ChildItem,然后使用 Format-Wide 格式化结果,并在父路径上自动调整大小和分组。

这是默认行为。

[玩转系统] 使用 PowerShell 全面开放

或者使用动态参数。

[玩转系统] 使用 PowerShell 全面开放

这可行,但存在许多潜在的缺点。

首先,动态参数有问题。默认情况下,它不会显示在命令帮助中。

[玩转系统] 使用 PowerShell 全面开放

尽管 PSReadline 和制表符完成应该检测到它。我不太喜欢动态参数,尤其是当有其他选项时。在这个函数中,我可以定义多个参数集。一种用于计数,另一种用于尺寸。如果我要继续使用这个功能,我就会做出改变。我在这个例子中保留了动态参数,因为我经常被问到这个问题,这里有一个很好的工作演示。

这种方法的另一个问题是函数中包含格式化。这意味着该函数的输出是格式范围指令,而不是“真实”对象。我唯一能做的就是查看输出或将其通过管道传输到输出打印机或输出文件。您几乎不想在函数中包含 Format 命令。这就是我使用 Show 作为命令动词的原因之一。我没有得到什么东西,我只是展示它。

使用 PowerShell 类

由于我正在编写一个函数,因此它需要将一个对象写入管道。我可以将函数输出传输到 Format-Wide 以获得所需的结果。我本可以在函数中使用 Select-Object 或 [pscustomobject] 类型,但我选择了 PowerShell 类定义。

Class DirectoryStat {
    [string]$Name
    [string]$Path
    [int64]$FileCount
    [int64]$FileSize
    [string]$Parent
    [string]$Computername = [System.Environment]::MachineName
} #close class definition

该类没有任何方法或特殊的构造函数。然后我编写了一个函数来使用这个类。

Function Get-DirectoryInfo {
    [cmdletbinding()]
    [alias("dw")]
    [OutputType("DirectoryStat")]
    Param(
        [Parameter(Position = 0)]
        [ValidateScript( { (Test-Path $_ ) -AND ((Get-Item $_).psprovider.name -eq "FileSystem") })]
        [string]$Path = ".",
        [switch]$Recurse,
        [int32]$Depth
    )

    Begin {
        Write-Verbose "Starting $($myinvocation.MyCommand)"

        #initialize a collection to hold the results
        $data = [System.Collections.Generic.list[object]]::new()

        function _newDirectoryStat {
            [CmdletBinding()]
            Param(
                [Parameter(ValueFromPipelineByPropertyName, Mandatory)]
                [string]$PSPath
            )

            Begin {}
            Process {
                $path = Convert-Path $PSPath
                $name = Split-Path -Path $Path -Leaf
                $stat = Get-ChildItem -Path $path -File | Measure-Object -Property Length -Sum

                $ds = [DirectoryStat]::New()
                $ds.Name = $name
                $ds.Path = $Path
                $ds.FileCount = $stat.Count
                $ds.FileSize = $stat.Sum
                $ds.Parent = (Get-Item -Path $path).Parent.FullName
                $ds
            }
            end {}
        }

        Write-Verbose "PSBoundParameters"
        Write-Verbose ($PSBoundParameters | Out-String)
        #build a hashtable of parameters to splat to Get-ChildItem
        $gciParams = @{
            Path      = $Path
            Directory = $True
        }
        if ($PSBoundParameters["Depth"]) {
            $gciParams.Add("Depth", $PSBoundParameters["Depth"])
        }
        if ($PSBoundParameters["Recurse"]) {
            $gciParams.Add("Recurse", $PSBoundParameters["Recurse"])
        }

    } #begin
    Process {
        Write-Verbose "Processing $(Convert-Path $Path)"

        $data.Add((Get-ChildItem @gciParams | _newDirectoryStat))

    } #Process

    End {
        #pre-sort the data
        $data | Sort-Object -Property Parent, Name
        Write-Verbose "Ending $($myinvocation.MyCommand)"
    }
}

正如您所看到的,该函数将一个对象写入管道,我可以根据需要对其进行格式化。

[玩转系统] 使用 PowerShell 全面开放

[玩转系统] 使用 PowerShell 全面开放

挑战在于以宽格式显示结果。正如您所看到的,这当然是可能的。但为了让这更容易,因为我使用的是自定义对象,所以我可以创建一个自定义格式文件。我使用 PSScriptTools 模块中的 New-PSFormatXML 创建一个默认视图,然后创建第二个命名视图。

dw | New-PSFormatXML -Path .\directorystat.format.ps1xml -Properties Name -GroupBy Parent -FormatType Wide
dw | New-PSFormatXML -Path .\directorystat.format.ps1xml -Properties Name -GroupBy Parent -FormatType Wide -append -ViewName sizekb

我的函数有一个别名 dw。我使用脚本块修改了该文件,以便默认视图是一个宽条目,并带有自定义显示,在括号中显示目录名称和文件计数。我还更新了 sizeKB 视图,以在方括号内显示以 KB 为单位的文件大小。我只需复制、粘贴和编辑即可获得更多视图。

我需要做的就是加载文件。

Update-FormatData .\directorystat.format.ps1xml

[玩转系统] 使用 PowerShell 全面开放

Get-FormatView 也是 PSScriptTools 模块的一部分。有了这个格式文件,我现在有了一个带有文件计数值的默认视图。

[玩转系统] 使用 PowerShell 全面开放

或者我可以说出其他宽广的观点。

[玩转系统] 使用 PowerShell 全面开放

如您所见,我在 ps1xml 文件中添加了 ANSI 转义序列。这应该适用于 Windows PowerShell 和 PowerShell 7。

<?xml version="1.0" encoding="UTF-8"?>
<!--
format type data generated 10/07/2020 12:20:03 by PROSPERO\Jeff
File created with New-PSFormatXML from the PSScriptTools module
which can be installed from the PowerShell Gallery
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 10/07/2020 12:20:03 by PROSPERO\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>DirectoryStat</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <ScriptBlock>"$([char]0x1b)[1;93m$($_.Parent)$([char]0x1b)[0m"</ScriptBlock>
        <Label>Path</Label>
      </GroupBy>
      <WideControl>
      <AutoSize />
        <WideEntries>
          <WideEntry>
            <WideItem>
              <ScriptBlock>"$([char]0x1b)[92m{0}$([char]0x1b)[0m [{1}]" -f $_.Name,$_.filecount</ScriptBlock>
            </WideItem>
          </WideEntry>
        </WideEntries>
      </WideControl>
    </View>
    <View>
      <!--Created 10/07/2020 12:23:20 by PROSPERO\Jeff-->
      <Name>size</Name>
      <ViewSelectedBy>
        <TypeName>DirectoryStat</TypeName>
      </ViewSelectedBy>
      <GroupBy>
       <ScriptBlock>"$([char]0x1b)[1;93m$($_.Parent)$([char]0x1b)[0m"</ScriptBlock>
        <Label>Path</Label>
      </GroupBy>
      <WideControl>
      <AutoSize />
        <WideEntries>
          <WideEntry>
            <WideItem>
              <ScriptBlock>"$([char]0x1b)[92m{0}$([char]0x1b)[0m [$([char]0x1b)[38;5;190m{1}$([char]0x1b)[0m]" -f $_.Name,$_.filesize</ScriptBlock>
            </WideItem>
          </WideEntry>
        </WideEntries>
      </WideControl>
    </View>
    <View>
      <!--Created 10/07/2020 12:23:20 by PROSPERO\Jeff-->
      <Name>sizekb</Name>
      <ViewSelectedBy>
        <TypeName>DirectoryStat</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <ScriptBlock>"$([char]0x1b)[1;93m$($_.Parent)$([char]0x1b)[0m"</ScriptBlock>
        <Label>Path</Label>
      </GroupBy>
      <WideControl>
        <WideEntries>
          <WideEntry>
            <WideItem>
              <ScriptBlock>"$([char]0x1b)[92m{0}$([char]0x1b)[0m [$([char]0x1b)[38;5;164m{1}KB$([char]0x1b)[0m]" -f $_.Name,([math]::Round($_.filesize/1KB,2))</ScriptBlock>
            </WideItem>
          </WideEntry>
        </WideEntries>
      </WideControl>
    </View>
    <View>
      <!--Created 10/07/2020 12:23:20 by PROSPERO\Jeff-->
      <Name>sizemb</Name>
      <ViewSelectedBy>
        <TypeName>DirectoryStat</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <ScriptBlock>"$([char]0x1b)[1;93m$($_.Parent)$([char]0x1b)[0m"</ScriptBlock>
        <Label>Path</Label>
      </GroupBy>
      <WideControl>
        <WideEntries>
          <WideEntry>
            <WideItem>
              <ScriptBlock>"$([char]0x1b)[92m{0}$([char]0x1b)[0m [$([char]0x1b)[38;5;147m{1}MB$([char]0x1b)[0m]" -f $_.Name,([math]::Round($_.filesize/1mb,2))</ScriptBlock>
            </WideItem>
          </WideEntry>
        </WideEntries>
      </WideControl>
    </View>
  </ViewDefinitions>
</Configuration>

这是我在使用 PowerShell 编写脚本时推荐的方法。让您的函数将对象写入管道。这是您的原始输出。然后根据需要使用 PowerShell 命令来操作和格式化数据。如果您知道需要某种默认格式,请创建一个 format.ps1xml 文件。

我意识到你们中的一些人仍在开始学习 PowerShell,这没关系。这就是 Iron Scripter 挑战背后的目的——测试和扩展你的技能。我希望您能尝试一下其他一些挑战。

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

取消回复欢迎 发表评论:

关灯