Консоль Powershell отличается от сценария

#powershell #parameter-passing #tostring

Вопрос:

Может ли кто-нибудь сказать мне, почему эта команда отлично работает в консоли Powershell, возвращая один отпечаток большого пальца, но при запуске в качестве сценария она просто возвращает все отпечатки большого пальца сертификата:

 $crt = (Get-ChildItem -Path Cert:LocalMachineWebHosting | Where-Object {$_.Subject.Contains($certcn)}).thumbprint
 

$certcn-это строка, содержащая домен. например «www.test.com»

Комментарии:

1. $certcn нигде не определено

Ответ №1:

Я все понял. $certcn был получен из $args[0]. Оказывается, $args[0] не является строкой, и хотя PS с удовольствием использовал бы ее в качестве строки в других командах, он не сделал бы этого с Where-Object. Не уверен, какой тип $args[0] на самом деле, но выполнение $certcn = $args[0].tostring() исправило это.

Комментарии:

1. Пожалуйста, подумайте о том, чтобы отметить свой ответ как решение вашего вопроса.

2. Повторно выясняем, какой тип $args[0] на самом деле: используйте $args[0].GetType().FullName . Чтобы узнать о членах типа, передайте экземпляр по Get-Member адресу : Get-Member -InputObject $args[0] . Было бы интересно узнать, какой был фактический тип, так как он, по-видимому, был преобразован в пустую строку , что необычно.

Ответ №2:

Единственное объяснение вашего симптома заключается в том, что значение переменной $certcn :

  • либо: является пустой строкой ( '' ), потому 'someString'.Contains('') что возвращает $true значение для любой входной строки.
  • или: неявно преобразуется в пустую строку, хотя на практике это происходит нечасто; вот несколько примеров (см. Проблему GitHub # для [pscustomobject] ошибки строкования, упомянутой ниже):
     # An empty array stringifies to ''
    'someString'.Contains(@()) # -> $true 
    
    # A single-element array containing $null stringifies to ''
    'someString'.Contains(@($null)) # -> $true 
    
    # Due to a longstanding bug, [pscustomobject] instances, when
    # stringified via .ToString(), convert to the empty string. 
    # This makes the command equivalent to `.Contains(@(''))`, which is again
    # the same as `.Contains('')`
    'someString'.Contains(@([pscustomobject] @{ foo=1 })) # -> $true 
     

$args[0] это не строка

Автоматическая $args переменная-это массив, содержащий все позиционные аргументы, которые не были привязаны к объявленным параметрам, если таковые имеются.

$args может содержать элементы любого типа данных, и что это за тип, определяется исключительно вызывающим.

Однако, если вы формально объявите параметр, вы можете ввести его, что означает, что если вызывающий объект передает аргумент другого типа данных, предпринимается попытка преобразовать аргумент в тип параметра (что может завершиться неудачей, но, по крайней мере, сбой будет «громким», и причина очевидна).


Надежное решение для вашего сценария:

 param(
  [Parameter(Mandatory)] # Ensure that the caller passes a value.
  [string] $CertCN       # Type-constrain to a string.
  # , ... declare other parameters as needed
)

# $CertCN is now guaranteed to be a *string* that is *non-empty*.
$crt = 
  (Get-ChildItem -Path Cert:LocalMachineWebHosting | 
     Where-Object { $_.Subject.Contains($CertCN) }).thumbprint
 

Примечание:

  • Использование [Parameter()] атрибута в блоке объявления параметров ( param(...) ) делает ваш сценарий продвинутым, что означает, что $args он не поддерживается, требуя, чтобы все аргументы были привязаны к явно объявленным параметрам; однако при необходимости вы можете определить универсальный параметр с [Parameter(ValueFromRemaningArguments)] помощью. (Еще одна вещь, которая делает скрипт или функцию продвинутой, — это использование [CmdletBinding()] атрибута над param(...) блоком в целом.)
  • [Parameter(Mandatory)] , в дополнение к обеспечению того, чтобы вызывающий объект передавал значение параметра, неявно также предотвращает передачу пустой строки (или $null ) — хотя вы могли бы явно разрешить это с помощью [AllowEmptyString()]
  • Кроме того, расширенные сценарии и функции автоматически предотвращают передачу массивов в [string] типизированные параметры, что желательно. (В отличие от этого, простые функции и сценарии просто упорядочивают массивы, как это происходит в расширяемых строках (интерполяция строк); например, amp; { param([string] $foo) $foo } 1, 2 привязки '1 2' , которые вы также получите с "$(1, 2)" помощью )

Предостережение:

При передаче значения в [string] типизированный параметр PowerShell принимает скаляр (не-набор) любого типа, и любой нестроковой тип автоматически преобразуется в строку с помощью .ToString() . Обычно это желательно и удобно, но может привести к бесполезным усложнениям; например:

 amp; { param([string] $str) $str } @{} # -> 'System.Collections.Hashtable'
 

Экземпляры хэш-таблиц ( @{ ... } ) строятся в соответствии с их именем типа, что бесполезно, и это поведение является поведением по умолчанию для любого типа, который явно не реализует значимое строковое представление путем переопределения .ToString() метода.

Если это вызывает беспокойство, вы можете изменить свой сценарий, чтобы убедиться, что передаваемое значение аргумента уже является строкой, используя [ValidateScript()] атрибут.

 param(
  [Parameter(Mandatory)]
  # Ensure that the argument value is a [string] to begin with.
  # Note: The `ErrorMessage` property requires PowerShell (Core) 7 
  [ValidateScript({ $_ -is [string] }, ErrorMessage='Please pass a *string* argument.')]
  $CertCN  # Do not type-constrain, so that the original type can be inspected.
  # , ... declare other parameters as needed
)

# ...
 

Как указано в комментариях к коду, использование ErrorMessage свойства в требуется PowerShell (ядро) 7 , к сожалению. В Windows PowerShell неизменно отображается стандартное сообщение об ошибке, которое совсем не удобно для пользователя.