Powershell Copy-Item дает 2 разных результата для одного и того же пути - PullRequest
0 голосов
/ 05 июня 2018

Проблема


Я пишу сценарий развертывания с использованием powershell.Использование команды Copy-Item не дает одинакового результата между запусками, даже с одинаковыми данными.(Это не кажется идемпотентом)

См. Результаты ниже для примера того, что я имею в виду.Я понимаю, что могу удалить файлы в C:\products перед копированием «новой» версии в, но мне более любопытно, является ли это ожидаемым поведением в Powershell или нет.(И я предполагаю, что это так)

Я бы лучше выучил «правильный» способ использования Copy-Item вместо того, чтобы взламывать мое отсутствие знаний.

Я пытался использоватьпуть target\* вместо target, но он взорвал мою папку lib в корне showcase, чего я не ожидал даже с флагом -Recurse.

C:\products
  - showcase
    - showcase.jar
    - logback.jar
    - foo.jar
    - junit.jar
    - etc, etc

IЯ также немного запутался с документацией здесь (в частности, примеры 7,10 и 11), поскольку 7 использует флаг -Recurse, а 11 - нет.Между тем пример 10 говорит следующее:

Если папка сценариев содержит файлы в подпапках, эти подпапки будут скопированы с целыми деревьями файлов.

, но никогда не будет указывать флаг рекурсиилибо.


Результаты

Перед развертыванием

C:\products
  - backup

Первоначальное развертывание

C:\products
  - showcase
    - showcase.jar
    - lib
      - <dependencies for showcase.jar>
  - backup
    <empty>
# Expected, as there wasn't a prior deployment yet.

Второе развертывание

C:\products
  - showcase
    - showcase.jar
    - lib
      - <dependencies for showcase.jar>
    - target
      - showcase.jar
      - lib
        - <dependencies for inner showcase.jar>
  - backup
    - showcase
      - showcase.jar
      - lib
        - <dependencies for "old" showcase.jar>
  # Expected, the old version was backed up

Сценарий развертывания и как он называется

./deploy.ps1 -hostname foobar (it's a remote server)

deploy.ps1

param( [String]$hostname, [switch]$debug )

$folder_to_copy = "target"
$init_file = "./init.ps1"

function Backup-Binary ( [String]$source, [String]$name, [String]$dest ) {

    $FullyQualifiedSourcePath = "$source\$name"

    if (Test-Path $FullyQualifiedSourcePath) {
        Write-Host "Backing up $FullyQualifiedSourcePath to the destination $dest"
        Copy-Item -Recurse -Force -Path $FullyQualifiedSourcePath -Destination $dest -ErrorAction Stop
    } else {
        Write-Output "Directory $FullyQualifiedSourcePath didn't exist. May be an initial deployment. Continuing with deployment."
    }

}

function Verify-Env ([String]$env_var) {
     $check = [Environment]::GetEnvironmentVariable($env_var)
     if ($check -eq $null) {
         Write-Error "$env_var environment variable doesn't exist. Exiting program."
         exit 1
     }
}

function Deploy-Local {

    # Checks that the given string exists as an Environment variable
    Verify-Env "DEPLOY_DRIVE"
    Verify-Env "CI_PROJECT_NAME"

    # C:\products or D:\products *most* of the time.
    $root = "$env:DEPLOY_DRIVE\products"
    $destination = "$root\$env:CI_PROJECT_NAME"
    $backup_loc = "$root\backup"

    # Removes prior binaries
    Backup-Binary -source $root -name $env:CI_PROJECT_NAME -dest $backup_loc

    # Copy binary locally since we're deploying to local machine.
    Copy-Item -Recurse -Force -Path $folder_to_copy -Destination $destination -ErrorAction Stop

    # Run the init script the developer has written
    Invoke-Expression "$init_file" -ErrorAction Stop

}

function Deploy-Remote {

    $session = New-PSSession -ComputerName $hostname

    $DEPLOY_DRIVE = Invoke-Command -Session $session -ScriptBlock { 
        [Environment]::GetEnvironmentVariable("DEPLOY_DRIVE")
    } -ErrorAction Stop

    $root = "$DEPLOY_DRIVE\products"
    $destination = "$root\$env:CI_PROJECT_NAME"
    $backup_loc = "$root\backup"

    Write-Host "$root = root $destination = destination $backup_loc = backup_location"

    Invoke-Command -Session $session -ScriptBlock ${function:Backup-Binary} -ArgumentList $root,$env:CI_PROJECT_NAME,$backup_loc -ErrorAction Stop

    Copy-Item -Recurse -Force -ToSession $session -Path $folder_to_copy -Destination $destination -ErrorAction Stop

    Invoke-Command -Session $session -FilePath $init_file -ErrorAction Stop

    Remove-PSSession $session -ErrorAction Stop

}

if ( $hostname.ToLower() -eq $env:COMPUTERNAME.ToLower() ) {

    Deploy-Local

} else {

    Deploy-Remote

}

1 Ответ

0 голосов
/ 06 июня 2018

Этот ответ дается @ mklement0 из предоставленной ссылки (проблема, с которой я начал работать на github)

@ huffstler:

Да, но этота же проблема, что и # 2934, только в контексте более крупной команды:

  • Когда subdir.a копируется, $ b еще не существует, поэтому содержимое a копируется непосредственно в $ b (поведение (a) выше).
  • К моменту времени subdir.b копируется, $ b уже существует, и вот тогда возникает несоответствие: содержимое b копируется в $ b / b (поведение (b) выше).

Следовательно, если вы запустите командуопять же, вы получите желаемое поведение.

Учитывая текущее поведение, вы можете обойти эту проблему следующим образом:

Get-ChildItem -Path $a |
ForEach-Object { New-Item -Force -Type Directory $b } { 
  Copy-Item -Recurse -Path $_.FullName -Destination $b
}

Но более важный вопрос заключается в том, следует ли разрешать это несоответствие в сторонуповедение (а) или поведение (б).

Вы явно ожидали (а), но у других могут быть другие ожидания, основанные на xcopy.

Честно говоря, я был озадачен, обнаружив такое же несоответствиекак с Copy-Item -Recurse в мире Unix (cp -R).

Единственный способ в настоящее время получить предсказуемое поведение - это:

  • Убедитесь, что цельреж.уже существует.

  • Затем, в зависимости от того, хотите ли вы поведение (a) или (b):

  • Чтобы получить поведение (a):Явно нацеливайтесь на содержимое исходного каталога:

    Copy-Item -Recurse -Force $a/* $b

    • Обратите внимание на необходимость -Force, которая необходима для обеспечения того, чтобы скрытые элементы также копировались.

    • С помощью cp в Unix вы можете просто $a/., но это не работает в PowerShell.

  • Чтобы получить поведение (б): никаких дальнейших действий не требуется.

...