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

[玩转系统] 解决 PowerShell 对象时代挑战 - 第 2 部分

作者:精品下载站 日期:2024-12-14 07:58:57 浏览:17 分类:玩电脑

解决 PowerShell 对象时代挑战 - 第 2 部分


有一天,我分享了 Iron Scripter 挑战的部分解决方案,编写一个通用函数来报告对象的年龄。这个想法是,您可以将任何类型的对象通过管道传递给函数并获得结果。因为我无法控制自己,所以我的解决方案更进一步。

关于我的原始函数,让我烦恼的事情之一是我需要指定创建时间和上次修改时间的属性名称。除此之外,我知道这些属性对于常见对象类型(例如文件和进程)来说是什么。那么为什么不让它变得更容易呢?

使用 JSON 配置文件

我使用常见对象类型和关联属性创建了一个 JSON 文件。

[
  {
    "TypeName": "Process",
    "Created": "StartTime",
    "Modified": "StartTime",
    "Properties": [
      "Name",
      "ID",
      "WS",
      "Handles"
    ]
  },
  {
    "TypeName": "File",
    "Created": "CreationTime",
    "Modified": "LastWriteTime",
    "Properties": [
      "Name",
      "FullName",
      "Length",
      {
        "Name" : "SizeKB",
        "Expression" : "[math]::Round($_.length/1KB,2)"
      },
       {
        "Name" : "Owner",
        "Expression" : "(Get-Acl $_.fullname).owner"
      }
    ]
  },
  {
    "TypeName": "ADUser",
    "Created": "Created",
    "Modified": "Modified",
    "Properties": [
      "DistinguishedName",
      "Name",
      "SAMAccountName"
    ]
  },
  {
    "TypeName": "ADGroup",
    "Created": "Created",
    "Modified": "Modified",
    "Properties": [
      "DistinguishedName",
      "Name",
      "GroupCategory",
      "GroupScope"
    ]
  },
    {
      "TypeName": "ADComputer",
      "Created": "Created",
      "Modified": "Modified",
      "Properties": [
        "DistinguishedName",
        "Name",
        "DNSHostName"
      ]
    }
]

该文件名为 objectage-types.json。 TypeName 是标识符。稍后我将向您展示这是如何发挥作用的。每个对象类型都有“创建”和“修改”属性的设置。流程对象没有修改的属性,因此我将仅使用 Created 属性。每个对象类型还具有一组默认属性。我什至可以使用自定义属性哈希表。

在 ps1 文件中,当您点源该文件时,我有这行代码要运行。

$global:ObjectAgeData = Get-Content $PSScriptRoot\objectage-types.json |
ConvertFrom-Json 

如果将其打包为模块,我会做一些不同的事情。

我的参数现在包括参数集。

Function Get-ObjectAge2 {
    [cmdletbinding(DefaultParameterSetName = "custom")]
    [alias('goa2')]

    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullorEmpty()]
        [object[]]$InputObject,

        #provider a helper
        [Parameter(Mandatory, ParameterSetName = "named")]
        [ValidateSet("File", "Process", "ADUser", "ADComputer", "ADGroup")]
        [string]$ObjectType,

        [Parameter(Mandatory, Position = 0, ParameterSetName = "custom")]
        [string]$CreationProperty,

        [Parameter(Position = 1, ParameterSetName = "custom")]
        [string]$ChangeProperty,

        [Parameter(ParameterSetName = "custom")]
        [object[]]$Properties = @("Name")
    )

默认值是我在这个函数的原始版本中使用的。另一个参数集使用名为 ObjectType 的参数。验证集中的每一项都对应于 JSON 文件中的一个对象。

在函数中,我从全局变量加载数据。

if ($pscmdlet.ParameterSetName -eq 'named') {
    Write-Verbose "[BEGIN  ] Using user specified object type of $ObjectType"

    #get values from the global data variable
    $data = $global:ObjectAgeData |
    Where-Object {$_.Typename -eq $ObjectType}

    $CreationProperty = $data.Created
    $ChangeProperty = $data.Modified
    $Properties = $data.Properties

    $tname = "objAge.$ObjectType"
}

$tname 变量将是自定义对象类型名称。您将在下面看到它是如何发挥作用的。

创建自定义对象

导入每个对象的过程保持不变。我还使用 ANSI 进度微调器。现在,对于每个项目,我正在创建一个自定义属性并插入类型名称。

foreach ($item in $all) {
    $Age = New-TimeSpan -end $Now -Start ($item.$creationProperty)
    $tmpHash = [ordered]@{
        PSTypeName = $tname
        Created    = $item.$CreationProperty
        Modified   = $item.$ChangeProperty
        Age        = $Age
        Average    = If ($Age -gt $Avg) {"Above"} else {"Below"}
        AverageAll = $avg
    }
            
    foreach ($prop in $Properties) {
        if ($prop.gettype().name -eq 'hashtable') {
                Write-Verbose "[END    ] Expanding hashtable"
                $custom = $item | Select-object -property $prop
                $prop = $custom.psobject.properties.Name
                $val = $custom.psobject.properties.Value
        }
        elseif ($prop.gettype().name -eq "PSCustomObject") {
                Write-Verbose "[END    ] Expanding custom object probably from a json file"
                #reconstruct the hashtable
                $tmpProp = @{Name = "$($prop.name)";Expression = $([scriptblock]::create($prop.Expression))}
                $custom = $item | Select-object -property $tmpProp
                $prop = $custom.psobject.properties.Name
                $val = $custom.psobject.properties.Value
        }
        else {                
            $val = $item.$prop
        }
            Write-Verbose "[END    ] Adding property $prop $val"
            $tmpHash.Add($prop, $val)
    } #foreach prop

    #create the object
    New-Object -TypeName PSObject -property $tmpHash

} #foreach item

我必须添加一些代码来从 JSON 文件重新创建属性哈希表,因为所有内容都被视为字符串。

我添加新的自定义类型名称的原因是这样我可以格式化结果。至少默认情况下是这样。我使用 PSScriptTools 模块中的 New-PSFormatXML 命令构建了一个 format.ps1xml 文件。

<?xml version="1.0" encoding="UTF-8"?>
<!--
format type data generated 05/26/2020 15:50:58 by BOVINE320\Jeff
This file is incomplete. It doesn't have all of the defined
object types in Get-ObjectAge2. Consider this file a proof of
concept or work-in-progress.
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 05/26/2020 15:50:58 by BOVINE320\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>objAge.Process</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.
        <AutoSize />-->
        <TableHeaders>
          <TableColumnHeader>
            <Label>Created</Label>
            <Width>21</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Modified</Label>
            <Width>21</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Age</Label>
            <Width>11</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Avg</Label>
            <Width>5</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Name</Label>
            <Width>23</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>ID</Label>
            <Width>7</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>WS</Label>
            <Width>11</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Handles</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>Created</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Modified</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                $_.Age.ToString("d'.'hh':'mm':'ss")
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>

                if ($_.Average -eq 'Above') {
                  "$([char]0x1b)[91m$($_.average)$([char]0x1b)[0m"
                }
                else {
                  $_.Average
                }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>ID</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>WS</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Handles</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <!--Created 05/26/2020 16:01:55 by BOVINE320\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>objAge.File</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <PropertyName>AverageAll</PropertyName>
        <Label>Overall Average Creation Age</Label>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.
        <AutoSize />-->
        <TableHeaders>
          <TableColumnHeader>
            <Label>Created</Label>
            <Width>24</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Modified</Label>
            <Width>23</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Age</Label>
            <Width>15</Width>
            <Alignment>Right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Average</Label>
            <Width>10</Width>
            <Alignment>center</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Name</Label>
            <Width>24</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>SizeKB</Label>
            <Width>9</Width>
            <Alignment>right</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>Created</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Modified</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                $_.Age.ToString("d'.'hh':'mm':'ss")
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                if ($_.Average -eq 'Above') {
                  "$([char]0x1b)[91m$($_.average)$([char]0x1b)[0m"
                }
                else {
                  $_.Average
                }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>SizeKB</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <!--Created 05/26/2020 16:09:07 by BOVINE320\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>objAge.ADUser</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <PropertyName>AverageAll</PropertyName>
        <Label>Overall Average Creation Age</Label>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.
        <AutoSize />-->
        <TableHeaders>
          <TableColumnHeader>
            <Label>Created</Label>
            <Width>23</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Modified</Label>
            <Width>22</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Age</Label>
            <Width>15</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Avg</Label>
            <Width>5</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>DistinguishedName</Label>
            <Width>46</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>Created</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Modified</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                $_.Age.ToString("d'.'hh':'mm':'ss")
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                if ($_.Average -eq 'Above') {
                  "$([char]0x1b)[91m$($_.average)$([char]0x1b)[0m"
                }
                else {
                  $_.Average
                }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>DistinguishedName</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

我将此文件加载到 .ps1 文件中。

Update-FormatData $PSScriptRoot\objage.format.ps1xml

现在我可以轻松获得常见对象类型的格式化结果。

[玩转系统] 解决 PowerShell 对象时代挑战 - 第 2 部分

查询 Active Directory 时,我必须记住包含必要的属性。

[玩转系统] 解决 PowerShell 对象时代挑战 - 第 2 部分

我仍然可以使用自定义方式来处理其他所有事情。

$cim = @{
Namespace  = "root/virtualization/v2"
ClassName  = "Msvm_ComputerSystem"
filter = "caption='Virtual Machine'"
}
$goa = @{
 CreationProperty = "InstallDate"
 ChangeProperty = "TimeOfLastStateChange"
 Properties = @{Name="VM";Expression={$_.ElementName}},
 @{Name="Uptime";Expression = {New-TimeSpan -Start (Get-Date).AddMilliseconds(-$_.ontimeInMilliseconds) -End (Get-Date)}}
}

Get-Ciminstance @cim | Get-ObjectAge2 @goa

[玩转系统] 解决 PowerShell 对象时代挑战 - 第 2 部分

代码

这是完整的功能和相关命令。

Function Get-ObjectAge2 {
    [cmdletbinding(DefaultParameterSetName = "custom")]
    [alias('goa2')]

    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullorEmpty()]
        [object[]]$InputObject,

        #provider a helper
        [Parameter(Mandatory, ParameterSetName = "named")]
        [ValidateSet("File", "Process", "ADUser", "ADComputer", "ADGroup")]
        [string]$ObjectType,

        [Parameter(Mandatory, Position = 0, ParameterSetName = "custom")]
        [string]$CreationProperty,

        [Parameter(Position = 1, ParameterSetName = "custom")]
        [string]$ChangeProperty,

        [Parameter(ParameterSetName = "custom")]
        [object[]]$Properties = @("Name")
    )

    Begin {
        Write-Verbose "[BEGIN  ] Starting: $($MyInvocation.Mycommand)"
        $all = [System.Collections.Generic.List[object]]::new()

        if ($pscmdlet.ParameterSetName -eq 'named') {
            Write-Verbose "[BEGIN  ] Using user specified object type of $ObjectType"

            #get values from the global data variable
            $data = $global:ObjectAgeData |
            Where-Object {$_.Typename -eq $ObjectType}

            $CreationProperty = $data.Created
            $ChangeProperty = $data.Modified
            $Properties = $data.Properties

            $tname = "objAge.$ObjectType"
        }
        else {
            #set a default value
            $tname = "objAge"
        }

        #use the same date time for all timespans
        $Now = Get-Date
        Write-Verbose "[BEGIN  ] Using $NOW for timespan calculations"
        Write-Verbose "[BEGIN  ] Using $CreationProperty for CreationProperty"
        #use CreationProperty parameter for Changed property if latter is not specified
        if (-Not $ChangeProperty) {
            Write-Verbose "[BEGIN  ] Using $CreationProperty for ChangeProperty"
            $changeProperty = $CreationProperty
        }
        else {
            Write-Verbose "[BEGIN  ] Using $ChangeProperty for ChangeProperty"
        }

        #initialize counters and an array of characters to be using in a progress wheel
        $i = 0
        $ch = @("|", "/", "-", "\", "|", "/", "-")
        $c = 0

    } #begin

    Process {
        foreach ($object in $InputObject) {
            $i++
            #display a progress symbol if NOT using -Verbose
            if (-Not ($VerbosePreference -eq "Continue")) {
                if ($c -ge $ch.count) {
                    $c = 0
                }
                Write-Host "$([char]0x1b)[1EProcessing $($ch[$c])$([char]0x1b)[0m" -ForegroundColor cyan -NoNewline
                $c++
                Start-Sleep -Milliseconds 10
            }
            $all.Add($object)
        }
    } #process

    End {
        Write-Verbose "[END    ] Calculating average creation age for $($all.count) objects"
        $allTimeSpans = $all | ForEach-Object {
            Try {
                $X = $_
                New-TimeSpan -start $_.$creationProperty -end $Now -ErrorAction Stop
            }
            Catch {
                Write-Warning "Failed to get $CreationProperty value. $($_.exception.message)"
                Write-Warning ($X | Out-String)
            }
        }  #foreach

        $avg = New-TimeSpan -seconds ($allTimeSpans | Measure-Object -Property TotalSeconds -average).Average

        $results = foreach ($item in $all) {
            $Age = New-TimeSpan -end $Now -Start ($item.$creationProperty)
            $tmpHash = [ordered]@{
                PSTypeName = $tname
                Created    = $item.$CreationProperty
                Modified   = $item.$ChangeProperty
                Age        = $Age
                Average    = If ($Age -gt $Avg) {"Above"} else {"Below"}
                AverageAll = $avg
            }
            
            foreach ($prop in $Properties) {
                if ($prop.gettype().name -eq 'hashtable') {
                        Write-Verbose "[END    ] Expanding hashtable"
                        $custom = $item | Select-object -property $prop
                        $prop = $custom.psobject.properties.Name
                        $val = $custom.psobject.properties.Value
                }
                elseif ($prop.gettype().name -eq "PSCustomObject") {
                     Write-Verbose "[END    ] Expanding custom object probably from a json file"
                     #reconstruct the hashtable
                     $tmpProp = @{Name = "$($prop.name)";Expression = $([scriptblock]::create($prop.Expression))}
                     $custom = $item | Select-object -property $tmpProp
                     $prop = $custom.psobject.properties.Name
                     $val = $custom.psobject.properties.Value
                }
                else {                
                    $val = $item.$prop
                }
                  Write-Verbose "[END    ] Adding property $prop $val"
                  $tmpHash.Add($prop, $val)
            } #foreach prop

            #create the object
            New-Object -TypeName PSObject -property $tmpHash

        } #foreach item

        $results
        Write-Verbose "[END    ] Ending: $($MyInvocation.Mycommand)"
    } #end

} #end function

#define a global variable with type information
$global:ObjectAgeData = Get-Content $PSScriptRoot\objectage-types.json | ConvertFrom-Json 

#load the formatting file
Update-FormatData $PSScriptRoot\objage.format.ps1xml

所有这些都应该被视为概念验证。但我希望你能尝试一下。或者至少看看我写了什么以及为什么。这里有许多活动部件。确保您了解这一切是如何运作的。如果您有任何疑问,请随时发表评论。

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

取消回复欢迎 发表评论:

关灯