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

[玩转系统] 扩展的 PowerShell 脚本库存工具

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

扩展的 PowerShell 脚本库存工具


有一天,我分享了我为解决 Iron Scripter PowerShell 挑战而编写的代码。缺点之一是我没有解决包含一个属性来指示哪个文件正在使用给定命令的挑战。我还觉得 Windows PowerShell 在大型文件集上的性能可以做得更好。我正在尝试处理近 5000 个 PowerShell 文件。

模块诞生

我做的第一件事是将代码重新组织到 PowerShell 模块中。这使我能够将一些代码分解为单独的函数,如下所示。

Function Get-ASTToken {
    [CmdletBinding()]

    Param (
        [Parameter(Mandatory, Position = 0)]
        [string]$Path
    )

    Begin {
        Write-Verbose "[BEGIN  ] Starting: $($MyInvocation.Mycommand)"
        New-Variable astTokens -force
        New-Variable astErr -force
    } #begin

    Process {
        $cPath = Convert-Path $Path
        Write-Verbose "[PROCESS] Getting AST Tokens from $cpath"
        $AST = [System.Management.Automation.Language.Parser]::ParseFile($cPath, [ref]$astTokens, [ref]$astErr)
        [void]$AST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
        $astTokens
    } #process

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

一个优点是我现在可以构建 Pester 测试。您无法纠缠测试调用 .NET Framework 的代码,但我可以测试是否调用了 PowerShell 函数。如果我要构建测试,我可以模拟这个函数的结果。

我现在还为我的自定义对象定义一个类。

class PSInventory {
    [string]$Name
    [int32]$Total
    [string[]]$Files
    hidden [datetime]$Date = (Get-Date)
    hidden [string]$Computername = [System.Environment]::MachineName

    PSInventory([string]$Name) {
        $this.Name = $Name
    }
}

在我的代码中,我可以创建如下结果:

$item = [PSInventory]::new($TextInfo.ToTitleCase($Value.tolower()))

顺便说一句,我的原始代码让我烦恼的事情之一是命令名称按找到的方式显示。多年来我并没有保持一致,所以这意味着看到像“Write-verbose”和“select-Object”这样的输出。我的解决方案是使用我的朋友 Gladys Kravitz 教我的一个技巧,即使用 TextInfo 对象。

$TextInfo = (Get-Culture).TextInfo

该对象有一个我正在调用的 ToTitleCase() 方法。为了确保一致性,我将包含派生命令名称的 $Value 变量转换为小写,然后将其转换为 TitleCase。好的。

并行和作业

该模块还包含一个控制器函数,尽管它也可能是一个脚本,它使用模块命令来处理给定的文件夹。

Function Get-PSScriptInventory {
    [cmdletbinding()]
    [alias("psi")]
    [OutputType("PSInventory")]

    Param(
        [Parameter(Position = 0, Mandatory, HelpMessage = "Specify the root folder path")]
        [string]$Path,
        [Parameter(HelpMessage = "Recurse for files")]
        [switch]$Recurse,
        [Parameter(HelpMessage = "Specify the number of files to batch process. A value of 0 means do not run in batches or parallel.")]
        [ValidateSet(0,50,100,250,500)]
        [int]$BatchSize = 0
    )

该函数包含一个参数来指示是否要“批处理”文件夹。仅当您有大量文件时,这才有意义。

如果您运行的是 PowerShell 7,文件将分为多个批次,每个批次会并行处理。

if ($IsCoreCLR) {
    Write-Verbose "[PROCESS] Processing in parallel"
    $files = Get-PSFile -Path $Path -Recurse:$Recurse
    $totalfilecount = $files.count
    $sets = @{}
    $c = 0
    for ($i = 0 ; $i -lt $files.count; $i += $batchsize) {
        $c++
        $start = $i
        $end = $i + ($BatchSize-1)
        $sets.Add("Set$C", @($files[$start..$end]))
    }
    $results = $sets.GetEnumerator() | ForEach-Object -Parallel {
        Write-Host "[$(Get-Date -format 'hh:mm:ss.ffff')] Processing $($_.name) in parallel" -ForegroundColor cyan
        Write-Information "Processing $($_.name)" -tags meta
        $_.value | Measure-ScriptFile
    }
} #coreCLR

[玩转系统] 扩展的 PowerShell 脚本库存工具

[玩转系统] 扩展的 PowerShell 脚本库存工具

ForEach-Object -begin {
    $totalfilecount = 0
    $tmp = [System.Collections.Generic.List[object]]::new()
    $jobs = @()

    #define the scriptblock to run in a thread job
    $sb = {
        Param([object[]]$Files)
        $files | Measure-ScriptFile
    }
} -process {
    if ($tmp.Count -ge $batchsize) {
        Write-Host "[$(Get-Date -format 'hh:mm:ss.ffff')] Processing set of $($tmp.count) files" -ForegroundColor cyan
        Write-Information "Starting threadjob" -Tags meta
        $jobs += Start-ThreadJob -ScriptBlock $sb -ArgumentList @(, $tmp.ToArray()) -Name tmpJob
        $tmp.Clear()
    }
    $totalfilecount++
    $tmp.Add($_)
} -end {
    #use the remaining objects
    Write-Host "[$(Get-Date -format 'hh:mm:ss.ffff')] Processing remaining set of $($tmp.count) of files" -ForegroundColor cyan
    $jobs += Start-ThreadJob -ScriptBlock $sb -ArgumentList @(, $tmp.ToArray()) -name tmpJob
}
#wait for jobs to complete
Write-Verbose "[PROCESS] Waiting for $($jobs.count) jobs to complete"
$results = $jobs | Wait-Job | Receive-Job

这与后台作业类似,但运行速度似乎更快一些。这需要 PowerShell 库中的 ThreadJob 模块。即便如此,Windows PowerShell 中的速度仍然较慢。我敢打赌,除了其他性能改进之外,PowerShell 7 中还对 AST 进行了改进。不过,您可以将此代码视为概念证明。

事实上,我希望您尝试一下整个模块,旨在用于教育目的并作为概念验证平台。我希望您能看看我是如何构建该模块的。我如何处理跨平台需求。我如何使用“写入信息”等命令。我如何使用 PowerShell 类。

您可以在 Github 上找到此代码:https://github.com/jdhisolutions/PSScriptingInventory。如果您有意见、疑问或问题,请随时发布问题。我也希望您能尝试一下 Iron Scripter 挑战。

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

取消回复欢迎 发表评论:

关灯