Powershell: невозможно найти тип при использовании классов PS 5

Я использую классы в PS с WinSCP Powershell Assembly. В одном из способов я использую разные типы из WinSCP.

Это отлично работает, пока у меня уже есть добавленная сборка, однако из-за того, как Powershell читает сценарий при использовании классов (я полагаю?), Выдается ошибка до того, как сборка может быть загружена.

Фактически, даже если я поставлю Write-Host вверху, он не загрузится.

Есть ли способ заставить что-то запускаться до того, как остальная часть файла будет проанализирована?

Transfer() {
    $this.Logger = [Logger]::new()
    try {

        Add-Type -Path $this.Paths.WinSCP            
        $ConnectionType = $this.FtpSettings.Protocol.ToString()
        $SessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol = [WinSCP.Protocol]::$ConnectionType
            HostName = $this.FtpSettings.Server
            UserName = $this.FtpSettings.Username
            Password = $this.FtpSettings.Password
        }

Приводит к такой ошибке:

Protocol = [WinSCP.Protocol]::$ConnectionType
Unable to find type [WinSCP.Protocol].

person dbso    schedule 16.03.2017    source источник
comment
Куда вы загружаете свою WinSCP DLL?   -  person Will Webb    schedule 16.03.2017
comment
Это первая строка в блоке try. Но неважно, где я его загружу. Даже если я помещу командлет в самую верхнюю строку с прямым путем к WinSCPnet.dll, он не загрузится - кажется, он обнаруживает недостающие типы перед запуском чего-либо.   -  person dbso    schedule 16.03.2017


Ответы (4)


Как вы обнаружили, PowerShell отказывается запускать сценарии, содержащие определения классов, которые ссылаются на недоступные на тот момент (еще не загруженные) типы - этап синтаксического анализа сценария не выполняется.

Правильным решением является создание скрипта module (*.psm1), связанный манифест (*.psd1) объявляет сборку, содержащую указанные типы, как предварительное условие через клавишу RequiredAssemblies.

См. альтернативное решение внизу, если использование модулей не вариант.

Вот упрощенное пошаговое руководство:

Создайте тестовый модуль tm следующим образом:

  • Создайте папку модуля ./tm и в ней манифест (*.psd1):

    # Create module folder
    mkdir ./tm
    
    # Create manifest file that declares the WinSCP assembly a prerequisite.
    # Modify the path to the assembly as needed; you may specify a relative path, but
    # note that the path must not contain variable references (e.g., $HOME).
    New-ModuleManifest ./tm/tm.psd1 -RootModule tm.psm1 `
      -RequiredAssemblies C:\path\to\WinSCPnet.dll
    
  • Создайте файл модуля скрипта (*.psm1) в папке модуля:

    Создайте файл ./tm/tm.psm1 с определением вашего класса; например.:

    class Foo {
      # Simply return the full name of the WinSCP type.
      [string] Bar() {
        return [WinSCP.Protocol].FullName
      }
    }
    

    Примечание. В реальном мире модули обычно размещаются в одном из стандартных мест, определенных в $env:PSMODULEPATH, так что на модуль можно ссылаться только по имени, без необходимости указывать (относительный) путь.

Используйте модуль:

PS> using module ./tm; (New-Object Foo).Bar()
WinSCP.Protocol

Оператор using module импортирует модуль и, в отличие от Import-Module, также делает класс, определенный в модуле, доступным для текущего сеанса.

Поскольку при импорте модуля сборка WinSCP неявно загружалась благодаря ключу RequiredAssemblies в манифесте модуля, создание экземпляра класса Foo, который ссылается на типы сборки, завершилось успешно.


Если ваш вариант использования не позволяет использовать модули, вы можете использовать Invoke-Expression в крайнем случае, но учтите, что обычно лучше избегать Invoke-Expression в интересах надежности и во избежание рисков для безопасности < sup> [1].

# Adjust this path as needed.
Add-Type -LiteralPath C:\path\to\WinSCPnet.dll

# By placing the class definition in a string that is invoked at *runtime*
# via Invoke-Expression, *after* the WinSCP assembly has been loaded, the
# class definition succeeds.
Invoke-Expression @'
class Foo {
  # Simply return the full name of the WinSCP type.
  [string] Bar() {
    return [WinSCP.Protocol].FullName
  }
}
'@

(New-Object Foo).Bar()

[1] Это не проблема в этом случае, но в целом, учитывая, что Invoke-Expression может вызывать любую команду, хранящуюся в строке, применяя ее к строкам не полностью под вашим контролем может привести к выполнению вредоносных команд. Это предостережение аналогично применимо и к другим языкам, например к встроенной в Bash команде eval.

person mklement0    schedule 16.03.2017

Хотя это не само по себе решение, я работал над этим. Тем не менее, я оставлю вопрос открытым, поскольку он все еще стоит

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

Enum Protocols {
    Sftp
    Ftp
    Ftps
}

И установили протокол в FtpSettings

$FtpSettings.Protocol = [Protocols]::Sftp

Я могу установить такой протокол

$SessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol = $this.FtpSettings.Protocol.ToString()
            HostName = $this.FtpSettings.Server
            UserName = $this.FtpSettings.Username
            Password = $this.FtpSettings.Password
        }

Я использовал подобное на [WinSCP.TransferMode]

$TransferOptions.TransferMode = "Binary" #[WinSCP.TransferMode]::Binary
person dbso    schedule 16.03.2017

Во-первых, я бы порекомендовал ответ mklement0.

Тем не менее, вы можете немного побегать, чтобы получить тот же эффект с немного меньшим объемом работы, что может быть полезно в небольших проектах или на ранних этапах.

Можно просто. создайте другой файл ps1 в своем коде, который содержит ваши классы, ссылающиеся на еще не загруженную библиотеку после загрузки указанной сборки.

##########
MyClasses.ps1

Class myClass
{
     [3rdParty.Fancy.Object] $MyFancyObject
}

Затем вы можете вызвать свою собственную библиотеку классов из основного скрипта с расширением.

#######
MyMainScriptFile.ps1

#Load fancy object's library
Import-Module Fancy.Module #If it's in a module
Add-Type -Path "c:\Path\To\FancyLibrary.dll" #if it's in a dll you have to reference

. C:\Path\to\MyClasses.ps1

Исходный синтаксический анализ пройдет проверку, скрипт запустится, ваша ссылка будет добавлена, а затем, по мере продолжения скрипта, файл. исходный файл будет прочитан и проанализирован, добавив ваши пользовательские классы без проблем, поскольку их справочная библиотека находится в памяти к моменту анализа кода.

По-прежнему гораздо лучше создать и использовать модуль с надлежащим манифестом, но это легко пройдет, и его очень легко запомнить и использовать.

person Takophiliac    schedule 12.11.2019

Дополнительное решение - поместить логику Add-Type в отдельный файл .ps1 (назовите его AssemblyBootStrap.ps1 или что-то в этом роде), а затем добавить его в раздел ScriptsToProcess манифеста вашего модуля. ScriptsToProcess запускается перед манифестом модуля, и сборки будут загружены в то время, когда определения классов ищут их.

person Justin Grote    schedule 08.06.2021