【问题标题】:Copy-Item 'preview' of output输出的复制项“预览”
【发布时间】:2018-08-08 20:39:18
【问题描述】:

关于 PowerShell Copy-Item 命令的快速问题。我想知道您是否有一个目录结构并想覆盖另一个目录结构,有没有办法在“预览”模式下运行 Copy-Item 命令。它将输出从目录 a 覆盖的文件到目录 b 但实际上不执行 Copy-Item 命令。

任何帮助或建议表示赞赏。

谢谢。

【问题讨论】:

  • 使用参数-WhatIf 运行Copy-Item。但是,对于您要实现的目标,robocopy 将是一个更合适的工具。添加参数/l 进行试运行。

标签: powershell


【解决方案1】:

tl;dr

  • Copy-Item -WhatIf 不会为您提供所需的详细程度 - 见下文。

  • 改用robocopy.exe -l(仅限Windows),正如Ansgar Wiechers 建议的那样,因为它单独列出了复制的文件,包括动态省略那些已经存在于目标目录(默认情况下具有相同的大小和上次修改的时间戳)。


Get-Help about_CommonParameters 记录了许多(但不是全部)cmdlet 支持的-WhatIf 通用参数,其目的是预览操作 没有实际执行。

但是,此功能是以抽象方式实现的,并且通常无法提供人们希望的详细信息。

恰当的例子:虽然Copy-Item 确实支持-WhatIf,但它可能无法为您提供所需的详细程度,因为如果源项目是一个目录,仅输出 单行 行,如下所示:

What if: Performing the operation "Copy Directory" on target "Item: sourceDir Destination: destDir".

请注意,无论您的 Copy-Item 调用是否包含 -Recurse 开关,您都会看到同一行。

即使您手动确保目标目录存在并将/* 附加到源目录路径以查看单个文件名,您也只能在 child 级别看到它们,而不是进一步沿着子树向下,你永远不会得到 robocopy -l 提供的关于哪些文件实际需要替换的动态信息。

【讨论】:

    【解决方案2】:

    有趣的问题! 这是我在 Powershell 中完成这一切的尝试,因此不需要 RoboCopy。

    function Copy-Preview {
        [CmdletBinding(DefaultParameterSetName = 'ByPath')]
        param(
            [Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'ByPath', Position = 0)]
            [ValidateScript({ Test-Path $_ })]
            [string]$Path,
    
            [Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'ByLiteralPath', Position = 0)]
            [ValidateScript({ Test-Path $_ })]
            [string]$LiteralPath,
    
            [Parameter(ValueFromPipeline = $true, Mandatory = $true, Position = 1)]
            [string]$Destination,
    
            [string]$Filter = $null,
            [string]$Include = $null,
            [string]$Exclude = $null,
            [switch]$Recurse,
            [switch]$Force
        )
    
        if ($PSCmdlet.ParameterSetName -eq 'ByLiteralPath') { $Path = $LiteralPath }
    
        # determine if $Path and $Destination hold a file or a directory
        $srcIsFolder = (Test-Path $Path -PathType Container -ErrorAction SilentlyContinue)
        # cannot use Test-Path here because then the path has to exist.
        # assume if it has an extension the path is a file, otherwise a directory
        # NOTE:
        # This is certainly not fullproof, so to avoid problems, always make sure 
        # the destination ends with a backslash if a directory is intended.
        $destIsFolder = (-not ([System.IO.Path]::HasExtension($Destination)))
    
        if ($destIsFolder -and !(Test-Path $Destination -PathType Container)) {
            Write-Host "Destination path does not exist yet. All files from '$Path' will be copied fresh" -ForegroundColor Green
            return
        }
        elseif ($srcIsFolder -and (!$destIsFolder)) {
            # should not happen: source is a directory, while the destination is a file..
            Write-Error "When parameter Path points to a directory, the Destination cannot be a file.."
            return
        }
    
        $count = 0
        if ($srcIsFolder -and $destIsFolder) {
            # Both the source and the destinations are folders
            # make sure both paths are qualified for .Replace() further down
            if (-not $Path.EndsWith("\")) { $Path += "\" }
            if (-not $Destination.EndsWith("\")) { $Destination += "\" }
    
            $splat = @{
                Filter  = $Filter
                Include = $Include
                Exclude = $Exclude
                Recurse = $Recurse
                Force   = $Force
            }
            # add either Path or LiteralPath to the parameters as they are mutually exclusive
            if ($PSCmdlet.ParameterSetName -eq 'ByPath') { $splat.Path = $Path }
            else { $splat.LiteralPath = $LiteralPath }
    
            $srcFiles  = Get-ChildItem @splat | Select-Object -ExpandProperty FullName
    
            # reuse the splat parameters hash for the destination, but change the Path
            if ($splat.LiteralPath) {($splat.Remove("LiteralPath"))}
            $splat.Path = $Destination
    
            $destFiles = Get-ChildItem @splat | Select-Object -ExpandProperty FullName
    
            foreach ($srcItem in $srcFiles) { 
                $destItem = $srcItem.Replace($Path, $Destination)
                if ($destFiles -contains $destItem) {
                    Write-Host "'$destItem' would be overwritten"
                    $count++
                }
            }
        }
        elseif (!$srcIsFolder) {
            # the source is a file
            if (!$destIsFolder) {
                # the destination is also a file
                if (Test-Path $Destination -PathType Leaf) {
                    Write-Host "'$Destination' would be overwritten"
                    $count++
                }
            }
            else {
                # source is file, destination is a directory
                $destItem = Join-Path $Destination (Split-Path $Path -Leaf)
                if (Test-Path $destItem -PathType Leaf) {
                    Write-Host "'$destItem' would be overwritten"
                    $count++
                }
            }
        }
    
        $msg = "$count item{0} would be overwritten by Copy-Item" -f $(if ($count -ne 1) { 's' })
        $dash = "-" * ($msg.Length)
        Write-Host "$dash`r`n$msg" -ForegroundColor Green
    }
    

    【讨论】:

      【解决方案3】:

      您可以使用 -whatif 参数。

      Copy-item -path myfile.txt -destination myfoldertoCopyTo\ -whatif

      【讨论】:

      • OP 正在复制目录结构并希望预览“它正在覆盖哪些文件”,即每个文件的信息,-WhatIf 没有提供。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-23
      • 1970-01-01
      • 2010-12-24
      • 2021-09-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多