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

[玩转系统] 使用 PowerShell 复制到多个目标

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

使用 PowerShell 复制到多个目标


为了纪念今天,即 2022 年 2 月 22 日,我想分享一个 PowerShell 函数,该函数允许您将文件复制到多个目的地。如果您查看 Copy-Item 的帮助(您应该这样做),您会发现 Destination 参数不采用数组。没关系。我可以解决这个问题。但是,我有一个免责声明,您应该将其视为概念验证而不是生产就绪的代码。

第一步是创建 Copy-Item cmdlet 的副本。我使用了 PSScriptTools 模块中的 Copy-Command 功能,您可以从 PowerShell 库安装该模块。我使用该函数创建了一个基于 Copy-Item 的“包装”函数。 Copy-Command 复制原始参数。我编辑了 Destination 参数以接受位置数组。

[Parameter(
    Mandatory,
    Position = 1,
    ValueFromPipelineByPropertyName,
    HelpMessage = "Specify a file system destination."
)]
[ValidateScript({Test-Path $_})]
[string[]]$Destination,

由于我的新函数的参数与 Copy-Item 相同,因此我可以使用 PSBoundParameters 构建 Copy-Item 命令。但是,我需要删除 Destination 参数,因为 Copy-Item 不是为支持数组而编写的。

  [void]($PSBoundParameters.Remove("Destination"))

该行位于 Process 脚本块中,因为我定义了 Destination 以按值获取管道输入。

我的新函数背后的概念非常简单。为每个目标运行 Copy-Item 命令。但是,如果我要复制许多文件,我希望它能够快速运行并且或多或少是并行的。就在那时我意识到我可以使用 Start-ThreadJob。如果您没有安装 ThreadJob 模块,可以从 PowerShell Gallery 获取它。与启动新运行空间的 Start-Job 不同,Start-Threadjob 在本地进程内的单独线程中创建作业。这往往会更快。

棘手的部分是弄清楚如何传递参数。我可以得到参数值。

start-threadjob {param($splat,$location) Copy-item @splat -destination $location} -arguments @($psboundparameters,$location)

我将在未来的项目中牢记这个概念。相反,我决定从 PSBoundParameters 重新创建参数。如果我可以创建一个命令字符串,我可以将其转换为脚本块,然后将其传递给 Start-ThreadJob。

$PSBoundParameters.GetEnumerator() | ForEach-Object -Begin { $p = "Copy-Item" } -Process {
    if ($_.value -eq $True) {
        $p += " -$($_.key)"
    }
    else {
        $p += " -$($_.key) $($_.value)"
    }
} -End {
    $p += " -destination $location"
}

在 ForEach-Object Begin 参数中,我使用 Copy-Item 命令初始化一个字符串。然后,对于每个枚举的 PSBoundParameter,我重建参数名称和值。 “陷阱”是处理诸如 -Recurse 或 -WhatIf 之类的开关。因此,如果该值等于 True,我将假设该键是一个 switch 参数。通常,我不会在 If 语句中进行显式布尔比较,但在本例中,我检查 $_.value 的值,而不仅仅是检查它是否存在。当我完成后,$p 将看起来像“Copy-Item -Verbose -WhatIf -Path C:\Scripts\WMIDiag.exe -destination d:\temp”。我可以将其转换为脚本块并将其作为 ThreadJob 运行。

$sb = [scriptblock]::Create($p)
#WhatIf code ommitted here
$j = Start-ThreadJob $sb

我所采取的方法,根据您运行复制命令的方式,可以为每个要复制的文件创建一个线程作业。有一点开销,所以这对于许多小文件来说可能表现不佳,但在复制大文件时我会采用它。

如果我相信我永远不会犯错误,我就可以停下来。但是,我可能想使用 Copy-Item 中的 -Passthru。或者需要看看是否有错误。这意味着我必须得到工作结果。在我的函数的 Begin 块中,我将初始化一个通用列表。

$jobs = [System.Collections.Generic.List[object]]::new()

我会将每个作业添加到在 Process 脚本块中创建的列表中。

 $jobs.add($j)

在结束脚本块中,假设有作业,我将等待最后一个作业完成,获取结果并删除作业。

If ($jobs) {
    Write-Verbose "$((Get-Date).TimeOfDay) [END    ] Processing $($jobs.count) thread jobs"
    $jobs | Wait-Job | Receive-Job
    $jobs | Remove-Job
}

现在,我可以用一个命令将文件复制到 2 个(或更多)目录!

[玩转系统] 使用 PowerShell 复制到多个目标

想尝试吗?

#requires -version 5.1
#requires -module ThreadJob

Function Copy-ItemToMultiDestination {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Medium'
    )]
    [alias("cp2")]
    Param(
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            HelpMessage = "Specify a file system source path."
        )]
        [ValidateScript({ Test-Path $_ })]
        [string[]]$Path,
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipelineByPropertyName,
            HelpMessage = "Specify a file system destination."
        )]
        [ValidateScript({ Test-Path $_ })]
        [string[]]$Destination,
        [switch]$Container,
        [switch]$Force,
        [string]$Filter,
        [string[]]$Include,
        [string[]]$Exclude,
        [switch]$Recurse,
        [switch]$PassThru,
        [Parameter(ValueFromPipelineByPropertyName)]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]$Credential
    )

    Begin {

        Write-Verbose "$((Get-Date).TimeOfDay) [BEGIN  ] Starting $($MyInvocation.Mycommand)"
        #initialize a list for thread jobs
        $jobs = [System.Collections.Generic.List[object]]::new()

    } #begin

    Process {
        [void]($PSBoundParameters.Remove("Destination"))

        foreach ($location in $Destination) {
            Write-Verbose "$((Get-Date).TimeOfDay) [PROCESS] Creating copy job"
            $PSBoundParameters.GetEnumerator() | ForEach-Object -Begin { $p = "Copy-Item" } -Process {
                if ($_.value -eq $True) {
                    $p += " -$($_.key)"
                }
                else {
                    $p += " -$($_.key) $($_.value)"
                }
            } -End {
                $p += " -destination $location"
            }

            Write-Verbose "$((Get-Date).TimeOfDay) [PROCESS] $p"
            $sb = [scriptblock]::Create($p)

            #Start-ThreadJob doesn't support -whatif
            if ($PSCmdlet.ShouldProcess($p)) {
                $j = Start-ThreadJob $sb
                #add each job to the list
                $jobs.add($j)
            }
        } #foreach location

    } #process

    End {
        If ($jobs) {
            Write-Verbose "$((Get-Date).TimeOfDay) [END    ] Processing $($jobs.count) thread jobs"
            $jobs | Wait-Job | Receive-Job
            $jobs | Remove-Job
        }

        Write-Verbose "$((Get-Date).TimeOfDay) [END    ] Ending $($MyInvocation.Mycommand)"

    } #end

} #end function Copy-ItemToMultiDestination

请记住,这是一个概念证明。尽管我的思绪已经在思考其他命令,但我可以使用其中一些技术来“更新”。玩得开心。

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

取消回复欢迎 发表评论:

关灯