Код OP и этот ответ используют PSv3 + синтаксис. Литье хэш-таблицы в [pscustomobject]
не поддерживается в PSv2, но вы можете заменить [pscustomobject] $_
на New-Object PSCustomObject -Property $_
.
Как и во многих случаях в прошлом, PetSerAl предоставил ответ в кратких (но очень полезных) комментариях по этому вопросу; позвольте мне уточнить:
Вашей проблемы является не, что вы используете переменные ($p
), чтобы получить доступ к свойству по себе, который делает работы (например, $p = 'Year'; Get-Date | % { $_.$p }
).
Вместо того, проблема состоит в том, что $p
в блоке сценария { $_.$p }
не вычисляется до позже, в контексте Format-Table
вызова, что означает, что то же самое, фиксированное значение используется для всех входных объектов - а именно значение $p
в этой точке (что является последним значением, которое было присвоено $p
в цикле foreach
).
Чистейший и наиболее общее решение заключается в вызове .GetNewClosure()
на блоке сценария для привязки $p
в блоке сценария к значениюзатем тока, петля-итерационного конкретным.
$format += @{ Label = $p; Expression = { $_.$p }.GetNewClosure() }
Из docs (курсив):.
В этом случае новый блок сценария закрыт в течение местных переменных в объеме, что замыкание определяется в В другими словами, текущие значения локальных переменных захватываются и заключаются внутри блока сценария, связанного с модулем.
Обратите внимание, что автоматической переменной $_
не определен внутри foreach
цикла (PowerShell определяет его только в определенных контекстах как входной объект под руку, например, в блоках сценариев, переданных командлеты в трубопроводе), так что остается несвязанный, по желанию.
Предостережение:
.GetNewClosure()
Хотя, как используется выше удобно, он имеет неэффективность недостаток неизменно захват всех локальных переменных, а не только один (s) необходимо.
более эффективная альтернатива, что позволяет избежать этой проблемы - и в частности, также избегает ошибки (как в Windows PowerShell v5.1.14393.693 и PowerShell ядро v6.0.0-alpha.15), в котором закрытие через локальные переменные могут разрыв, а именно, когда вшита скрипт/функция имеет параметр с проверки атрибутов таких как [ValidateNotNull()]
и что параметр не связанный (значение не передается)[1] - это следующий, значительно более сложное выражение Совет шляпы снова PetSerAl, и ответ Burt_Harris в here :
$format += @{ Label = $p; Expression = & { $p = $p; { $_.$p }.GetNewClosure() } }
& { ... }
создает дочерний объект с собственными локальными переменными.
$p = $p
затем создает локальные переменную$p
от его унаследованного значения.
Чтобы обобщить этот подход, вы должны включить такой оператор для каждой переменной, указанной в блоке сценария.
{ $_.$p }.GetNewClosure()
затем выводит блок сценария, который закрывает локальные переменные дочерней области (только $p
в этом случае).
- Ошибка была указана как an issue in the PowerShell Core GitHub repository и с тех пор been fixed - мне непонятно, в каких версиях исправление будет отправлено.
Для простых случаев mjolinor's answer может сделать: это косвенно создает блок сценария с помощью расширенной строки, которая включает тогда текущую $p
значения буквально, но учтите, что подход сложно обобщить, потому что просто строгая значение переменной обычно не гарантирует, что она работает как часть PowerShell Исходный код (который должна оценивать расширенная строка для преобразования в скрипт блок).
Для того, чтобы соединить все это вместе:
# Sample array of hashtables.
# Each hashtable will be converted to a custom object so that it can
# be used with Format-Table.
$var = @(
@{id="1"; name="abc"; age="3" }
@{id="2"; name="def"; age="4" }
)
# The array of properties to output, which also serve as
# the case-exact column headers.
$properties = @("ID", "Name", "Age")
# Construct the array of calculated properties to use with Format-Table:
# an array of output-column-defining hashtables.
$format = @()
foreach ($p in $properties)
{
# IMPORTANT: Call .GetNewClosure() on the script block
# to capture the current value of $p.
$format += @{ Label = $p; Expression = { $_.$p }.GetNewClosure() }
# OR: For efficiency and full robustness (see above):
# $format += @{ Label = $p; Expression = & { $p = $p; { $_.$p }.GetNewClosure() } }
}
$var | ForEach-Object { [pscustomobject] $_ } | Format-Table $format
Это дает:
ID Name Age
-- ---- ---
1 abc 3
2 def 4
по желанию: выходные столбцы используют заголовки столбцов, указанные в $properties
в то время как содержащий правильные значения.
Обратите внимание, как я удалил ненужные ;
экземпляров и заменить встроенные псевдонимы %
и ft
с основными именами командлетов для ясности. Я также назначил различные значения age
, чтобы лучше продемонстрировать, что результат правильный.
простое решение, в этом конкретных случае:
Для ссылки на значение свойства как есть, без преобразования, достаточно использовать имя свойства как Expression
запись в вычисленном свойстве (хэш-таблица форматирования столбцов). Другими словами: вам не нужен экземпляр [scriptblock]
, содержащий выражение в этом случае ({ ... }
), только [string]
значение, содержащее имущество имя.
Поэтому следующий работал бы слишком:
# Use the property *name* as the 'Expression' entry's value.
$format += @{ Label = $p; Expression = $p }
Обратите внимание, что этот подход случается избежать исходной задачи, поскольку $p
оценивается в момент назначения, поэтому цикл-итерация - фиксируются конкретные значения.
[1] Для воспроизведения: function foo { param([ValidateNotNull()] $bar) {}.GetNewClosure() }; foo
терпит неудачу, когда .GetNewClosure()
называется, с ошибкой Exception calling "GetNewClosure" with "0" argument(s): "The attribute cannot be added because variable bar with value would no longer be valid."
То есть, попытка включить несвязанного значение -bar
параметр - переменная $bar
- в замыкании , который, по-видимому, затем по умолчанию равен $null
, что нарушает его атрибут проверки.
Передача действительного значения -bar
заставляет проблему уйти; например, foo -bar ''
.
Основания для рассмотрения это ошибка : Если сам лечит $bar
при отсутствии значения параметра в -bar
, как несуществующее, поэтому функции должна .GetNewClosure()
.
Плохо, предположим, что «свойства» содержат разные имена свойств. См. Отредактированные $ свойства. – derek
Вы можете просто использовать 'Expression = $ p'. – PetSerAl
Или вы можете использовать 'Expression = & {$ p = $ p; {$ _. $ p} .GetNewClosure()} ', если вам действительно нужен блок сценариев для' Expression'. – PetSerAl