Подводные камни
В какие ловушки Powershell вы попадаете?:-)
Мои являются:
# -----------------------------------
function foo()
{
@("text")
}
# Expected 1, actually 4.
(foo).length
# -----------------------------------
if(@($null, $null))
{
Write-Host "Expected to be here, and I am here."
}
if(@($null))
{
Write-Host "Expected to be here, BUT NEVER EVER."
}
# -----------------------------------
function foo($a)
{
# I thought this is right.
#if($a -eq $null)
#{
# throw "You can't pass $null as argument."
#}
# But actually it should be:
if($null -eq $a)
{
throw "You can't pass $null as argument."
}
}
foo @($null, $null)
# -----------------------------------
# There is try/catch, but no callstack reported.
function foo()
{
bar
}
function bar()
{
throw "test"
}
# Expected:
# At bar() line:XX
# At foo() line:XX
#
# Actually some like this:
# At bar() line:XX
foo
Хотелось бы знать ваши, чтобы погулять по ним:-)
22 ответа
Мой личный фаворит
function foo() {
param ( $param1, $param2 = $(throw "Need a second parameter"))
...
}
foo (1,2)
Для тех, кто не знаком с PowerShell, эта строка выбрасывается, потому что вместо передачи 2 параметров она фактически создает массив и передает один параметр. Вы должны назвать это следующим образом
foo 1 2
Еще один веселый. Необработанное выражение по умолчанию записывает его в конвейер. Действительно раздражает, когда вы не понимаете, что конкретная функция возвращает значение.
function example() {
param ( $p1 ) {
if ( $p1 ) {
42
}
"done"
}
PS> example $true
42
"done"
$files = Get-ChildItem . -inc *.extdoesntexist
foreach ($file in $files) {
"$($file.Fullname.substring(2))"
}
Сбой с:
You cannot call a method on a null-valued expression.
At line:3 char:25
+ $file.Fullname.substring <<<< (2)
Исправьте это так:
$files = @(Get-ChildItem . -inc *.extdoesntexist)
foreach ($file in $files) {
"$($file.Fullname.substring(2))"
}
Суть в том, что оператор foreach будет зацикливаться на скалярном значении, даже если это скалярное значение равно $null. Когда Get-ChildItem в первом примере ничего не возвращает, $files получает $null. Если вы ожидаете, что команда вернет массив элементов, но есть вероятность, что она вернет только 1 элемент или ноль элементов, поместите @() вокруг команды. Тогда вы всегда получите массив - будь то 0, 1 или N элементов. Примечание: если элемент уже представляет собой массив @()
не имеет никакого эффекта - это все равно будет тот же самый массив (то есть нет никакой дополнительной оболочки массива).
Проблемы с PowerShell
Есть несколько ошибок, которые постоянно повторяются в StackOverflow. Если вы не знакомы с этими ошибками PowerShell, рекомендуется провести небольшое исследование, прежде чем задавать новый вопрос. Возможно, даже стоит разобраться в этих подводных камнях PowerShell, прежде чем отвечать на вопрос PowerShell, чтобы убедиться, что вы научили задающего вопрос правильным вещам.
TL; DR: в PowerShell :
- оператор равенства сравнения:
(Пример Stackoverflow: простой синтаксис Powershell, если условие не работает) - круглые скобки и запятые не используются с аргументами
(пример Stackoverflow: как передать несколько параметров в функцию в PowerShell?) - выходные свойства основаны на первом объекте в конвейере
(пример Stackoverflow: отображаются не все свойства) - конвейер разворачивается
(пример Stackoverflow: конвейерные объекты массива вместо элементов массива по одному? )
a. коллекции отдельных элементов
(пример Stackoverflow: Powershell ArrayList превращает отдельный элемент массива обратно в строку )
b. встроенные массивы
(пример Stackoverflow: возврат многомерного массива из функции )
c. выходные коллекции
(пример Stackoverflow: почему PowerShell автоматически сглаживает массивы?) - должен быть слева от оператора сравнения равенства
(пример Stackoverflow: должно ли $ null быть слева от сравнения равенства) - круглые скобки и назначения заглушают конвейер
(пример Stackoverflow: импорт 16 МБ CSV в переменную создает> использование памяти 600 МБ) - оператор присваивания увеличения () может стать дорогостоящим.
Пример Stackoverflow: сценария повышение эффективности моегоPowerShell. - Командлет возвращает отдельные строки.
Пример Stackoverflow: многострочное регулярное выражение для соответствия блоку конфигурации.
Примеры и пояснения
Некоторые из подводных камней могут действительно показаться нелогичными, но часто могут быть объяснены некоторыми очень хорошими функциями PowerShell, а также , выражения / режимомаргумента и приведением типов .
1. Оператор сравнения сравнения равен:
В отличие от языка сценариев Microsoft VBScript и некоторых других языков программирования, оператор равенства сравнения отличается от оператора присваивания (
=
) и является:
-eq
.
Примечание: присвоение значения переменной при необходимости может проходить через это значение:
$a = $b = 3 # The value 3 is assigned to both variables $a and $b.
Это означает, что следующее утверждение может быть неожиданно :
If ($a = $b) {
# (assigns $b to $a and) returns a truthy if $b is e.g. 3
} else {
# (assigns $b to $a and) returns a falsy if $b is e.g. 0
}
2. Скобки и запятые не используются с аргументами.
В отличие от многих других языков программирования и способа определения примитивной функции PowerShell, для вызова функции не требуются круглые скобки или запятые для соответствующих аргументов. Используйте пробелы для разделения аргументов параметра:
MyFunction($Param1, $Param2 $Param3) {
# ...
}
MyFunction 'one' 'two' 'three' # assigns 'one' to $Param1, 'two' to $Param2, 'three' to $Param3
- Скобки и запятые используются для вызова методов ( .Net).
- Запятые используются для определения массивов.
MyFunction 'one', 'two', 'three'
(илиMyFunction('one', 'two', 'three')
) загрузит массив@('one', 'two', 'three')
в первый параметр ($Param1
). - Круглые скобки будут интерпретировать содержащееся содержимое как единую коллекцию в памяти (и заглушить конвейер PowerShell) и должны использоваться только как таковые, например, для вызова встроенной функции, например:
MyFunction (MyOtherFunction) # passes the results MyOtherFunction to the first positional parameter of MyFunction ($Param1)
MyFunction One $Two (getThree) # assigns 'One' to $Param1, $Two to $Param2, the results of getThree to $Param3
Примечание: цитирование текстовых аргументов (как слово
one
в последнем примере) требуется только в том случае, если он содержит пробелы или специальные символы.
3. Выходные свойства основаны на первом объекте в конвейере.
В конвейере PowerShell каждый объект обрабатывается и передается командлетом (который реализован для середины конвейера ) аналогично тому, как объекты обрабатываются и передаются рабочими станциями на конвейере. Это означает, что каждый командлет обрабатывает один элемент за раз, в то время как предыдущий командлет (рабочая станция) одновременно обрабатывает следующий. Таким образом, объекты не загружаются в память сразу (меньшее использование памяти) и уже могут быть обработаны до того, как будет предоставлен следующий (или даже существует). Недостатком этой функции является отсутствие контроля над тем, какие ( или сколько ) объекты должны следовать.
Поэтому большинство командлетов PowerShell предполагают, что все объекты в конвейере соответствуют первому и имеют одинаковые свойства, что обычно имеет место, но не всегда ...
$List =
[pscustomobject]@{ one = 'a1'; two = 'a2' },
[pscustomobject]@{ one = 'b1'; two = 'b2'; three = 'b3' }
$List |Select-Object *
one two
--- ---
a1 a2
b1 b2
Как видите, третий столбец
three
отсутствует в результатах, поскольку он не существует в первом объекте, а PowerShell уже выводил результаты до того, как узнал о существовании второго объекта.
На пути к обходному пути это поведение заключается в явном предварительном определении свойств (всех следующих объектов):
$List |Select-Object one, two, three
one two three
--- --- -----
a1 a2
b1 b2 b3
См. Также предложение: #13906
Добавить параметр -UnifyProperties в Select-Object
4. Трубопровод разворачивается.
Эта функция может пригодиться, если она соответствует простому ожиданию:
$Array = 'one', 'two', 'three'
$Array.Length
3
а. коллекции отдельных предметов
Но это может сбить с толку:
$Selection = $Array |Select-Object -First 2
$Selection.Length
2
$Selection[0]
one
когда коллекция ограничена одним элементом:
$Selection = $Array |Select-Object -First 1
$Selection.Length
3
$Selection[0]
o
Пояснение Когда конвейер выводит один элемент, который назначен переменной, он не назначается как коллекция (с 1 элементом, например:
), но как скалярный элемент (сам элемент, например:
@('one')
'one'
).
Это означает, что свойство
.Length
(который на самом деле является псевдонимом свойства
.Count
для массива) больше не применяется к массиву, а к строке:
'one'.length
что равно
3
. А в случае индекса
$Selection[0]
, первый символ строки
'one'[0]
(что соответствует символу
o
) возвращается.
Временное решение Чтобы обойти эту проблему, можно заставить элемент скалярного в массив с помощью оператора массива подвыражения
@( )
:
$Selection = $Array |Select-Object -First 1
@($Selection).Length
1
@($Selection)[0]
one
Зная, что в случае
$Selection
уже является массивом, его глубина не будет увеличиваться (
@ (@ ('one', 'two'))
, см. следующий раздел 4b. Встроенные коллекции сглаживаются ).
б. встроенные массивы
Когда массив (или коллекция) включает встроенные массивы, например:
$Array = @(@('a', 'b'), @('c', 'd'))
$Array.Count
2
Все встроенные элементы будут обрабатываться в конвейере и, следовательно, возвращать плоский массив при отображении или присвоении новой переменной:
$Processed = $Array |ForEach-Object { $_ }
$Processed.Count
4
$Processed
a
b
c
d
Чтобы перебрать встроенные массивы, вы можете использовать foreach
заявление :
foreach ($Item in $Array) { $Item.Count }
2
2
Или просто for
цикл :
for ($i = 0; $i -lt $Array.Count; $i++) { $Array[$i].Count }
2
2
c. выходные коллекции
Коллекции обычно разворачиваются, когда они помещаются в конвейер:
function GetList {
[Collections.Generic.List[String]]@('a', 'b')
}
(GetList).GetType().Name
Object[]
Чтобы вывести коллекцию как отдельный элемент, используйте оператор запятой
,
:
function GetList {
,[Collections.Generic.List[String]]@('a', 'b')
}
(GetList).GetType().Name
List`1
5. должен быть слева от оператора сравнения равенства.
Эта ошибка связана с этой функцией операторов сравнения :
Для скаляров это означает:
'a' -eq 'a' # returns $True
'a' -eq 'b' # returns $False
'a' -eq $Null # returns $False
$Null -eq $Null # returns $True
а для коллекций возвращаются совпадающие элементы, которые оцениваются как правдивым или ложнымистинное или ложное условие:
'a', 'b', 'c' -eq 'a' # returns 'a' (truthy)
'a', 'b', 'c' -eq 'd' # returns an empty array (falsy)
'a', 'b', 'c' -eq $Null # returns an empty array (falsy)
'a', $Null, 'c' -eq $Null # returns $Null (falsy)
'a', $Null, $Null -eq $Null # returns @($Null, $Null) (truthy!!!)
$Null, $Null, $Null -eq $Null # returns @($Null, $Null, $Null) (truthy!!!)
Другими словами, чтобы проверить, есть ли переменная (и исключить коллекцию, содержащую несколько s), поместите
$Null
слева от оператора сравнения равенства:
if ($Null -eq $MyVariable) { ...
6. Скобки и присваивания заглушают конвейер.
конвейеромPipeline PowerShell не только последовательность команд , соединенных операторами трубопроводов (
|
) (124-й символ таблицы ASCII). Это концепция одновременной потоковой передачи отдельных объектов через последовательность командлетов . Если командлет (или функция) написан в соответствии с Настоятельно рекомендуемыми рекомендациями по разработке и реализован для середины конвейера , он берет каждый отдельный объект из конвейера, обрабатывает его и передает результаты следующему командлету непосредственно перед тем, как он принимает и обрабатывает следующий объект в конвейере. Это означает, что для простого конвейера как:
Import-Csv .\Input.csv |Select-Object -Property Column1, Column2 |Export-Csv .\Output.csv
Поскольку последний командлет записывает объект в
.\Output.csv
файла, командлет выбирает свойства следующего объекта и
Import-Csv
читает следующий объект из
.\input.csv
файл (см. также: конвейер в Powershell). Это снизит использование памяти (особенно там, где нужно обработать много объектов / записей) и, следовательно, может привести к более высокой пропускной способности. Чтобы упростить конвейер, объекты PowerShell довольно толстые, поскольку каждый отдельный объект содержит всю информацию о свойствах (вместе, например, с именем свойства).
Следовательно, не рекомендуется перекрывать трубопровод без причины. Есть два сенария, которые заглушают трубопровод:
- Круглые скобки , например:
(Import-Csv .\Input.csv) |Select-Object -Property Column1, Column2 |Export-Csv .\Output.csv
Где все записи загружаются в виде массива объектов PowerShell в память перед передачей их командлету.
- Задания , например:
$Objects = Import-Csv .\Input.csv
$Objects |Select-Object -Property Column1, Column2 |Export-Csv .\Output.csv
Где все
.\Input.csv
записи загружаются в виде массива объектов PowerShell в
$Objects
(а также память), прежде чем передать его
Select-Object
командлет.
7. оператор присваивания увеличения () может стать дорогостоящим.
Оператор присваивания увеличения () - это синтаксический сахар для увеличения и назначения примитивов в виде .eg
$a += $b
куда
$a
назначается
$b + 1
. Оператор присваивания увеличения также может использоваться для добавления новых элементов в коллекцию (или в
String
типы и
hash tables
), но может стать довольно дорогим, так как затраты увеличиваются с каждой итерацией (размер коллекции). Причина этого в том, что объекты как коллекции массивов неизменяемы, и правая переменная не просто добавляется, а * добавляется и переназначается левой переменной. Подробнее см. Также: избегайте использования оператора присваивания увеличения (
+=
) для создания коллекции
8. Командлет возвращает отдельные строки.
Вероятно, есть еще несколько ошибок, связанных с командлетами , зная, что существует множество (внутренних и внешних) командлетов. В отличие от ошибок, связанных с движком, эти ошибки часто легче выделить (например, с помощью предупреждения), поскольку это случилось с ConvertTo-Json
(см. Неожиданные результаты ConvertTo-Json? Ответ: по умолчанию -Depth - 2 ) или «исправить». Но в Get-Content
которые тесно связаны с общей концепцией потоковой передачи объектов PowerShell (в данном случае строк) вместо того, чтобы передавать все (все содержимое файла) за один раз:
Get-Content .\Input.txt -Match '\r?\n.*Test.*\r?\n'
Никогда не будет работать, потому что по умолчанию возвращает поток объектов, каждый из которых содержит одну строку (строку без разрывов строк).
(Get-Content .\Input.txt).GetType().Name
Object[]
(Get-Content .\Input.txt)[0].GetType().Name
String
По факту:
Get-Content .\Input.txt -Match 'Test'
Возвращает все строки со словом
Test
в нем as помещает каждую отдельную строку в конвейер, а когда ввод является коллекцией, оператор возвращает элементы коллекции, которые соответствуют правому значению выражения .
Примечание: начиная с версии PowerShell 3,
Get-Contents
имеет
-Raw
параметр, который одновременно считывает все содержимое соответствующего файла. Это означает, что это:
Get-Content -Raw .\Input.txt -Match '\r?\n.*Test.*\r?\n'
будет работать, поскольку он загружает весь файл в память.
# The pipeline doesn't enumerate hashtables.
$ht = @{"foo" = 1; "bar" = 2}
$ht | measure
# Workaround: call GetEnumerator
$ht.GetEnumerator() | measure
Вот кое-что, на что я наткнулся в последнее время (PowerShell 2.0 CTP):
$items = "item0", "item1", "item2"
$part = ($items | select-string "item0")
$items = ($items | where {$part -notcontains $_})
как вы думаете, что $items будет в конце скрипта?
Я ожидал "item1", "item2", но вместо этого значение $items: "item0", "item1", "item2".
На функции...
- Тонкости обработки конвейерного ввода в функции по отношению к использованию
$_
или же$input
и в отношенииbegin
,process
, а такжеend
блоки. - Как обрабатывать шесть основных классов эквивалентности входных данных, доставляемых в функцию (без ввода, ноль, пустая строка, скаляр, список, список с нулевым и / или пустым) - как для прямого ввода, так и для ввода с конвейера - и получить то, что вы ожидать.
- Правильный синтаксис вызова для отправки нескольких аргументов в функцию.
Я обсуждаю эти и другие подробности в своей статье на Simple-Talk.com " Вниз по кроличьей норе" - исследование конвейеров, функций и параметров PowerShell, а также предоставляю сопровождающую настенную диаграмму - здесь есть краткий обзор, показывающий различные ловушки синтаксиса вызова для функция, принимающая 3 аргумента:
На модулях...
Эти моменты изложены в моей статье на Simple-Talk.com " Далее по кроличьей норе: модули PowerShell и инкапсуляция".
Точечный поиск файла внутри скрипта с использованием относительного пути относительно вашего текущего каталога, а не каталога, в котором находится скрипт! Чтобы относиться к сценарию, используйте эту функцию, чтобы найти каталог сценария: [Обновление для PowerShell V3+: Просто используйте встроенный
$PSScriptRoot
переменная]function Get-ScriptDirectory { Split-Path $script:MyInvocation.MyCommand.Path }
Модули должны храниться как
...Modules\name\name.psm1
или же...\Modules\any_subpath\name\name.psm1
, То есть вы не можете просто использовать...Modules\name.psm1
- имя непосредственного родителя модуля должно совпадать с базовым именем модуля. Эта диаграмма показывает различные режимы отказа, когда это правило нарушается:
2015.06.25 Справочная таблица ошибок
Simple-Talk.com только что опубликовал последний из моих триумвиратов углубленных статей о подводных камнях PowerShell. Первые две части в форме викторины, которая поможет вам оценить выбранную группу ловушек; последняя часть представляет собой настенную диаграмму (хотя для этого понадобится комната с довольно высокими потолками), содержащую 36 наиболее распространенных ошибок (некоторые из которых адаптированы из ответов на этой странице), где приведены конкретные примеры и обходные пути для большинства. Узнайте больше здесь.
Допустим, у вас есть следующий XML-файл:
<Root>
<Child />
<Child />
</Root>
Запустите это:
PS > $myDoc = [xml](Get-Content $pathToMyDoc)
PS > @($myDoc.SelectNodes("/Root/Child")).Count
2
PS > @($myDoc.Root.Child).Count
2
Теперь отредактируйте файл XML, чтобы у него не было дочерних узлов, а только корневой узел, и снова запустите эти операторы:
PS > $myDoc = [xml](Get-Content $pathToMyDoc)
PS > @($myDoc.SelectNodes("/Root/Child")).Count
0
PS > @($myDoc.Root.Child).Count
1
Это 1 раздражает, когда вы хотите перебрать коллекцию узлов, используя foreach тогда и только тогда, когда они есть. Вот как я узнал, что вы не можете использовать нотацию свойства (точка) свойства обработчика XML как простой ярлык. Я верю, что происходит то, что SelectNodes возвращает коллекцию 0. Когда @'ed, он преобразуется из XPathNodeList в Object[] (проверьте GetType()), но длина сохраняется. Динамически генерируемое свойство $myDoc.Root.Child (которое по существу не существует) возвращает $null. Когда $ null равен @ ', он становится массивом длины 1.
Есть несколько хитростей при построении командных строк для утилит, которые не были созданы с учетом Powershell:
- Чтобы запустить исполняемый файл, имя которого начинается с цифры, предваряйте его амперсандом (&).
& 7zip.exe
- Чтобы запустить исполняемый файл с пробелом в любом месте пути, предварите его амперсандом (&) и заключите в кавычки, как в любой строке. Это означает, что строки в переменной также могут быть выполнены.
# Executing a string with a space.
& 'c:\path with spaces\command with spaces.exe'
# Executing a string with a space, after first saving it in a variable.
$a = 'c:\path with spaces\command with spaces.exe'
& $a
- Параметры и аргументы передаются в устаревшие утилиты позиционно. Поэтому важно процитировать их так, как утилита ожидает их увидеть. В общем, можно заключить в кавычки, когда он содержит пробелы или не начинается с буквы, цифры или тире (-).
C:\Path\utility.exe '/parameter1' 'Value #1' 1234567890
- Переменные могут использоваться для передачи строковых значений, содержащих пробелы или специальные символы.
$b = 'string with spaces and special characters (-/&)'
utility.exe $b
- В качестве альтернативы, расширение массива также может использоваться для передачи значений.
$c = @('Value #1', $Value2)
utility.exe $c
- Если вы хотите, чтобы Powershell ждал завершения приложения, вы должны использовать выходные данные, либо передав их по конвейеру, либо используя Start-Process.
# Saving output as a string to a variable.
$output = ping.exe example.com | Out-String
# Piping the output.
ping stackru.com | where { $_ -match '^reply' }
# Using Start-Process affords the most control.
Start-Process -Wait SomeExecutable.com
- Из-за способа отображения своего вывода некоторые служебные программы командной строки будут зависать при запуске внутри Powershell_ISE.exe, особенно в ожидании ввода от пользователя. Эти утилиты обычно работают нормально при запуске в консоли Powershell.exe.
Другой:
$x = 2
$y = 3
$a,$b = $x,$y*5
из-за приоритета операторов в $b нет 25; команда такая же как ($x,$y)*5 правильная версия
$a,$b = $x,($y*5)
alex2k8, я думаю, что этот ваш пример хорош для обсуждения:
# -----------------------------------
function foo($a){
# I thought this is right.
#if($a -eq $null)
#{
# throw "You can't pass $null as argument."
#}
# But actually it should be:
if($null -eq $a)
{
throw "You can't pass $null as argument."
}
}
foo @($null, $null)
PowerShell может использовать некоторые из компараторов для таких массивов:
$array -eq $value
## Returns all values in $array that equal $value
Имея это в виду, исходный пример возвращает два элемента (два значения $null в массиве), которые оцениваются в $true, поскольку в итоге получается коллекция из более чем одного элемента. Изменение порядка аргументов останавливает сравнение массивов.
Эта функция очень удобна в определенных ситуациях, но об этом нужно знать (как при обработке массива в PowerShell).
Функции 'foo' и 'bar' выглядят эквивалентно.
function foo() { $null }
function bar() { }
Например
(foo) -eq $null
# True
(bar) -eq $null
# True
Но:
foo | %{ "foo" }
# Prints: foo
bar | %{ "bar" }
# PRINTS NOTHING
Возвращение $null и возвращение ничего не эквивалентно работе с каналами.
Этот вдохновлен примером Кит Хилл...
function bar() {}
$list = @(foo)
$list.length
# Prints: 0
# Now let's try the same but with a temporal variable.
$tmp = foo
$list = @($tmp)
$list.length
# Prints: 1
Логические и побитовые операторы не следуют стандартным правилам приоритета. Оператор - и должен иметь более высокий приоритет, чем - или все же они оцениваются строго слева направо.
Например, сравните логические операторы между PowerShell и Python (или практически любым другим современным языком):
# PowerShell
PS> $true -or $false -and $false
False
# Python
>>> True or False and False
True
... и побитовые операторы:
# PowerShell
PS> 1 -bor 0 -band 0
0
# Python
>>> 1 | 0 & 0
1
Еще один, с которым я недавно столкнулся: параметры [string], которые принимают входные данные конвейера, на практике не являются строго типизированными. Вы можете передать что-либо вообще, и PS будет принудительно вызывать это через ToString().
function Foo
{
[CmdletBinding()]
param (
[parameter(Mandatory=$True, ValueFromPipeline=$True)]
[string] $param
)
process { $param }
}
get-process svchost | Foo
К сожалению, нет способа отключить это. Лучший обходной путь, который я мог придумать:
function Bar
{
[CmdletBinding()]
param (
[parameter(Mandatory=$True, ValueFromPipeline=$True)]
[object] $param
)
process
{
if ($param -isnot [string]) {
throw "Pass a string you fool!"
}
# rest of function goes here
}
}
редактировать - лучший обходной путь, который я начал использовать...
Добавьте это в ваш пользовательский тип XML -
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>System.String</Name>
<Members>
<ScriptProperty>
<Name>StringValue</Name>
<GetScriptBlock>
$this
</GetScriptBlock>
</ScriptProperty>
</Members>
</Type>
</Types>
Затем напишите такие функции:
function Bar
{
[CmdletBinding()]
param (
[parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
[Alias("StringValue")]
[string] $param
)
process
{
# rest of function goes here
}
}
Обработка ExitCode процесса как логического.
например, с этим кодом:
$p = Start-Process foo.exe -NoNewWindow -Wait -PassThru
if ($p.ExitCode) {
# handle error
}
все хорошо, если только не сказано, что foo.exe не существует или не запускается по другой причине. в этом случае$p
будет $null
, а также [bool]($null.ExitCode)
ложно.
простое исправление - заменить логику на if ($p.ExitCode -ne 0) {}
, однако для ясности кода лучше следующее: if (($p -eq $null) -or ($p.ExitCode -ne 0)) {}
Это однажды меня опрокинула, используя $o.SomeProperty, где он должен быть $($o.SomeProperty).
Это работает. Но почти наверняка не так, как вы думаете, это работает.
PS> $a = 42;
PS> [scriptblock]$b = { $a }
PS> & $b
42
Забыв, что $_ перезаписывается в блоках, я пару раз почесал голову в замешательстве, а также для множества регулярных совпадений и массива $match. >.<
Мои оба связаны с копированием файлов...
Квадратные скобки в именах файлов
Однажды мне пришлось переместить очень большую / сложную структуру папок, используя Move-Item -Path C:\Source -Destination C:\Dest
, В конце процесса в исходном каталоге было еще несколько файлов. Я заметил, что у каждого оставшегося файла были квадратные скобки в имени.
Проблема заключалась в том, что -Path
Параметр обрабатывает квадратные скобки как символы подстановки.
НАПРИМЕР. Если вы хотите скопировать Log001 в Log200, вы можете использовать квадратные скобки следующим образом:Move-Item -Path C:\Source\Log[001-200].log
,
В моем случае, чтобы избежать интерпретации квадратных скобок как символов подстановки, я должен был использовать -LiteralPath
параметр.
ErrorActionPreference$ErrorActionPreference
переменная игнорируется при использовании Move-Item
а также Copy-Item
с -Verbose
параметр.
# $x is not defined
[70]: $x -lt 0
True
[71]: [int]$x -eq 0
True
Итак, что такое $x..?
Не забывайте явно вводить объекты pscustom из импортированных таблиц данных как числовые, чтобы их можно было правильно отсортировать:
$CVAP_WA=foreach ($i in $C){[PSCustomObject]@{ `
County=$i.county; `
TotalVote=[INT]$i.TotalBallots; `
RegVoters=[INT]$i.regvoters; `
Turnout_PCT=($i.TotalBallots/$i.regvoters)*100; `
CVAP=[INT]($B | ? {$_.GeoName -match $i.county}).CVAP_EST }}
PS C: \ Политика> $CVAP_WA | sort -desc TotalVote |ft -auto -wrap
County TotalVote RegVoters Turnout_PCT CVAP CVAP_TV_PCT CVAP_RV_PCT
------ --------- --------- ----------- ---- ----------- -----------
King 973088 1170638 83.189 1299290 74.893 90.099
Pierce 349377 442985 78.86 554975 62.959 79.837
Snohomish 334354 415504 80.461 478440 69.832 86.81
Spokane 227007 282442 80.346 342060 66.398 82.555
Clark 193102 243155 79.453 284190 67.911 85.52