Подтвердить что ты не робот

Ловушки Powershell

Какая подводная ловушка 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

Хотелось бы узнать ваше, чтобы ходить по ним: -)

4b9b3361

Ответ 1

Мой личный фаворит

function foo() {
  param ( $param1, $param2 = $(throw "Need a second parameter"))
  ...
}

foo (1,2)

Для тех, кто не знаком с powershell, который выдает строка, потому что вместо передачи двух параметров он фактически создает массив и передает один параметр. Вы должны называть его следующим образом

foo 1 2

Ответ 2

Еще одна забавная. Не обрабатывая выражение по умолчанию, он записывает его в конвейер. Действительно раздражает, когда вы не понимаете, что определенная функция возвращает значение.

function example() {
  param ( $p1 ) {
  if ( $p1 ) {
    42
  }
  "done"
}

PS> example $true 
42
"done"

Ответ 3

$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 элементов. Примечание. Если элемент уже массива, помещающий @(), не имеет никакого эффекта - он все равно будет тем же самым массивом (т.е. Нет дополнительной оболочки массива).

Ответ 4

# The pipeline doesn't enumerate hashtables.
$ht = @{"foo" = 1; "bar" = 2}
$ht | measure

# Workaround: call GetEnumerator
$ht.GetEnumerator() | measure

Ответ 6

Вот что-то Ive спотыкается в последнее время (PowerShell 2.0 CTP):

$items = "item0", "item1", "item2"

$part = ($items | select-string "item0")

$items = ($items | where {$part -notcontains $_})

Как вы думаете, что $items будет в конце script?

Я ожидал "item1", "item2", но вместо этого значение $items: "item0", "item1", "item2".

Ответ 7

Скажем, у вас есть следующий 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 файл, чтобы он не имел дочерних узлов, только Root node и снова запускал эти утверждения:

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 является @'ed, он становится массивом длины 1.

Ответ 8

Есть несколько трюков для создания командных строк для утилит, которые не были созданы с помощью Powershell:

  • Чтобы запустить исполняемый файл, имя которого начинается с числа, представьте его с помощью Ampersand (&).

& 7zip.exe

  • Чтобы запустить исполняемый файл с пространством в любом месте пути, представьте его с помощью Ampersand (&) и оберните его в кавычки, как и любую строку. Это означает, что строки в переменной также могут быть выполнены.

# 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 stackoverflow.com | where { $_ -match '^reply' }

# Using Start-Process affords the most control. Start-Process -Wait SomeExecutable.com

  • Из-за того, как они отображают свой вывод, некоторые утилиты командной строки, как правило, зависают, когда запускаются внутри Powershell_ISE.exe, особенно когда вы ожидаете ввода от пользователя. Эти утилиты обычно работают нормально, когда запускаются внутри консоли Powershell.exe.

Ответ 9

В функции...

  • Тонкости ввода конвейера обработки в функции по отношению к использованию $_ или $input и по отношению к блокам begin, process и end.
  • Как обрабатывать шесть основных классов эквивалентности ввода, переданных функции (без ввода, нулевой, пустой строки, скалярного, списка, списка с нулевым и/или пустым) - для прямой вход и конвейер - и получите то, что вы ожидаете.
  • Правильный синтаксис вызова для отправки нескольких аргументов функции.

Я обсуждаю эти моменты и более подробно в своей статье Simple-Talk.com Вниз по кроличьей дыре - исследование в трубопроводах, функциях и параметрах PowerShell, а также предоставить сопроводительный wallchart - есть проблеск, показывающий различные вызовы синтаксиса вызова для функции, принимающей 3 аргумента: function syntax pitfalls


В модулях...

Эти пункты излагаются в моей статье Simple-Talk.com Дальше вниз по кроличьей дыре: модули PowerShell и инкапсуляция.

  • Точечный поиск файла внутри script с использованием относительного пути относительно вашего текущего каталога - не каталог, в котором находится script! Чтобы относиться к script, используйте эту функцию, чтобы найти каталог script: [Обновление для 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 - имя непосредственного родителя модуля должно соответствовать базовому имени модуля. На этой диаграмме показаны различные режимы отказа, когда это правило нарушено:

module naming requirements


2015.06.25 A Pitfall Reference Chart

Simple-Talk.com только что опубликовал последний из моих триумвиратов подробных статей о ловушках PowerShell. Первые две части представлены в виде викторины, которая поможет вам оценить избранную группу подводных камней; последняя часть представляет собой настенный рисунок (хотя для этого потребуется довольно высокая комната), содержащая 36 наиболее распространенных ловушек (некоторые из них адаптированы из ответов на этой странице), давая конкретные примеры и обходные пути для большинства. Подробнее здесь.

Ответ 10

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 в массиве), которые evalutates до $true, потому что в итоге вы получаете коллекцию из более чем одного элемента. Реверсирование порядка аргументов останавливает сравнение массива.

Эта функция очень удобна в определенных ситуациях, но это то, о чем вам нужно знать (точно так же, как обработка массивов в PowerShell).

Ответ 11

Функции 'foo' и 'bar' выглядят эквивалентно.

function foo() { $null  }
function bar() { }

например.

(foo) -eq $null
# True

(bar) -eq $null
# True

Но:

foo | %{ "foo" }
# Prints: foo

bar | %{ "bar" }
# PRINTS NOTHING

Возврат $null и возврат ничего не эквивалентен работе с каналами.


Этот вдохновлен примером Keith Hill...

function bar() {}

$list = @(foo)
$list.length
# Prints: 0

# Now let try the same but with a temporal variable.
$tmp = foo
$list = @($tmp)
$list.length
# Prints: 1

Ответ 12

Другой:

$x = 2
$y = 3
$a,$b = $x,$y*5 

из-за приоритета операторов в Bb нет 25; команда такая же, как ($ x, $y) * 5 правильная версия

$a,$b = $x,($y*5)

Ответ 13

Логические и побитовые операторы не соответствуют стандартным правилам приоритета. Оператор - и должен иметь более высокий приоритет, чем -или, но они оцениваются строго слева направо.

Например, сравните логические операторы между 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

Ответ 14

Это работает. Но почти наверняка не так, как вы думаете, что он работает.

PS> $a = 42;
PS> [scriptblock]$b = { $a }
PS> & $b
42

Ответ 15

Этот опробовал меня раньше, используя $o.SomeProperty, где он должен быть $($ o.SomeProperty).

Ответ 16

# $x is not defined
[70]: $x -lt 0
True
[71]: [int]$x -eq 0
True

Итак, что такое $x..?

Ответ 17

Другой, с которым я столкнулся в последнее время: параметры [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
    }
}

edit - лучшее обходное решение, которое я начал использовать...

Добавьте это в свой собственный тип 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
    }
}

Ответ 18

Забыв, что $_ перезаписывается в блоках, я несколько раз сбивал голову с путаницы, а также для нескольких совпадений reg-ex и массива $matches.. > & Л;

Ответ 19

Помня о том, чтобы явно вводить объекты 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:\Politics > $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

Ответ 20

Mine связаны с копированием файлов...

Квадратные скобки в именах файлов
Мне когда-то пришлось перемещать очень большую/сложную структуру папок, используя Move-Item -Path C:\Source -Destination C:\Dest. В конце процесса в исходном каталоге все еще было несколько файлов. Я заметил, что каждый оставшийся файл имеет квадратные скобки в имени.

Проблема заключалась в том, что параметр -Path обрабатывает квадратные скобки как подстановочные знаки.
НАПРИМЕР. Если вы хотите скопировать Log001 в Log200, вы можете использовать квадратные скобки следующим образом: Move-Item -Path C:\Source\Log[001-200].log.

В моем случае, чтобы квадратные скобки не интерпретировались как подстановочные знаки, я должен был использовать параметр -LiteralPath.

ErrorActionPreference
При использовании Move-Item и Copy-Item с параметром -Verbose переменная $ErrorActionPreference игнорируется.