Get-ChildItem.Length неправильно

Я пишу рекурсивную функцию, которая проходит через каталог и копирует каждый файл и папку в нем. Первая проверка, которую я имею в функции, состоит в том, чтобы видеть, имеет ли пройденный путь дочерние элементы. Чтобы выяснить это, я использую следующий метод:

[array]$arrExclude = @("Extras")
Function USBCopy
{
Param ([string]$strPath, [string]$strDestinationPath)
    try
    {
        $pathChildren = Get-ChildItem -Path $strPath
        if($pathChildren.Length -gt 0)
        {
            foreach($child in $pathChildren)
            {
                if($arrExclude -notcontains $child)
                {
                    $strPathChild = "$strPath\$child"
                    $strDestinationPathChild = "$strDestinationPath\$child" 
                    Copy-Item $strPathChild -Destination $strDestinationPathChild
                    USBCopy $strPathChild $strDestinationPathChild  
                }   
            }
        }              
    }
    catch
    {
        Write-Error ("Error running USBCopy: " + $Error[0].Exception.Message)
    }    
}

По большей части моя функция работает, но мой код скажет, что каталог пуст, когда в нем фактически есть 1 файл. Когда я отлаживаю свою функцию, переменная скажет, что у папки есть дочерние элементы, но длина переменной равна 0. Кто-нибудь знает, как обойти это?

2 ответа

Решение

Пытаться $pathChildren.Count вместо $pathChildren.Length - это вернет количество элементов в массиве.

PetSerAl, как и много раз ранее, предоставил критический указатель в кратком комментарии по этому вопросу (и он также помог в уточнении этого ответа):

$pathChildren = @(Get-ChildItem -Path $strPath)

Использование @(...) оператор подвыражения массива гарантирует, что все выходные данные вложенной команды будут обрабатываться как массив, даже если выводится только 1 объект, так что .Length гарантированно будет массив .Length имущество.

Однако в PSv3+ доступ к .Count вместо .Length, как в полезном ответе WillPanic, работает тоже - см. ниже.

Без @(...) результатом может быть один объект, поскольку PowerShell автоматически разворачивает выходную коллекцию, которая содержит только 1 объект, что дает только этот один объект, что подразумевает следующее:

  • до PSv2:

    • Если этот один объект имеет .Length свойство, его значения возвращаются.
      В данном случае это верно, если единственный возвращаемый объект представляет файл ([System.IO.FileInfo] экземпляр) (что, в свою очередь, верно, если каталог содержит ровно 1 файл и не содержит подкаталогов, кроме скрытых элементов).
      [System.IO.FileInfo] инстанс .Length свойство возвращает размер файла в байтах. Значение 0 подразумевает пустой файл.
      (Если единственным возвращенным объектом был каталог ([System.IO.DirectoryInfo] пример, .Length вернулся бы $null потому что такие случаи не имеют .Length имущество.)
  • в PSv3+ обходной путь больше не нужен строго, если вы используете .Count потому что вы можете обрабатывать даже скаляр (отдельный объект), как если бы это был массив, с неявным .Length / .Count [1] свойства и способность индексировать в (например,
    <scalar>[0] ), но есть предостережения:

    • Если Set-StrictMode -Version 2 или выше, доступ к .Length а также .Count свойства, которые на самом деле не существуют в скаляре под рукой, вызывают ошибку.
      Однако такое поведение весьма прискорбно, поскольку считается, что эти свойства существуют неявно - если вы согласны, выскажите свой голос в этой проблеме GitHub.

    • Если скаляр сам по себе имеет такое свойство, как .Length или же .Count или поддерживает индексирование, которое имеет приоритет - вот почему .Count должны быть использованы в этом случае (как указано, [System.IO.FileInfo] экземпляры имеют .Length свойство, сообщающее размер файла в байтах); см. ниже примеры.

    • С помощью @(...) избегает таких столкновений, потому что результатом всегда является массив.

    • Перечисление членов является дополнительным аспектом объединения, которое позволяет применять элемент (свойство или метод) элементов, содержащихся в коллекции, на уровне коллекции, и в этом случае доступ к члену осуществляется неявно для каждого элемента в коллекции, и результирующие значения возвращаются в виде массива; см. пример ниже.
      Чтобы разрешить конфликты имен с перечислением членов, нужен другой подход - см. Мой ответ.


Примеры PSv3+ унифицированной обработки коллекции

PS> (666).Length
1  # Scalar 666 was implicitly treated as a collection of length 1

PS> (666).Count
1  # Ditto - ** .Count is preferable, because it less often means something else **

# Caveat: A *string* scalar has a native .Length property
PS> ('666').Length; ('666').Count
3  # .Length: The string types's native property: the number of *characters*
1  # .Count: PowerShell's implicit collection handling: 1 *element*

PS> (666)[0]; (666)[-1]
666  # Index [0] always yields the scalar itself.
666  # Ditto for [-1], the *last* element.

# Member enumeration example: get the .Day property value from each
# [datetime] instance stored in an array.
PS> ((Get-Date), (Get-Date).AddDays(-1)).Day
20
19

[1] Как указывает PetSerAl, до PSv5.1 массив .Count собственность была псевдонимом собственности .Length , добавленный PowerShell ETS (система расширенного типа - см. Get-Help about_Types.ps1xml , Тем не менее, это свойство псевдонима на самом деле не требовалось с PSv3, когда явным образом реализованные члены типа интерфейса.NET были также представлены PowerShell, предоставляя доступ к типу массива. ICollection.Count имущество. Поэтому v6 больше не будет иметь псевдонима, после чего .Count будет иметь прямой доступ ICollection.Count - увидеть этот вопрос GitHub.
Обратите внимание, что магия PowerShell все еще задействована, когда дело доходит до вызова .Count на "поддельном" массиве (скаляр), однако.

Другие вопросы по тегам