我在PS中使用WinSCP Powershell程序集中的类。在其中一种方法中,我使用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
无法找到类型[WinSCP.Protocol]。
答案 0 :(得分:10)
正如您所发现的, PowerShell拒绝运行包含引用当时不可用(尚未加载)类型的类定义的脚本 - 脚本解析阶段失败。
using assembly
语句在这种情况下也无济于事,因为在您的情况下,类型是在 PS类定义的上下文中引用的/ em> - 这may get fixed in PowerShell Core, however;正在this GitHub issue中跟踪所需的工作。正确的解决方案是创建一个脚本模块(*.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
; e.g:
class Foo {
# Simply return the full name of the WinSCP type.
[string] Bar() {
return [WinSCP.Protocol].FullName
}
}
注意:在现实世界中,模块通常放在$env:PSMODULEPATH
中定义的标准位置之一,因此模块只能由 name 引用,而无需指定一条(相对)路径。
使用模块:
PS> using module ./tm; (New-Object Foo).Bar()
WinSCP.Protocol
using module
语句导入模块,与Import-Module
不同 -
还使模块中定义的类可用于当前会话。
由于模块清单中的RequiredAssemblies
键导致模块隐式加载了WinSCP程序集,因此实例化引用程序集类型的类Foo
成功。
如果您的用例不允许使用模块,您可以在紧要关头使用Invoke-Expression
,但请注意,通常最好避免使用Invoke-Expression
坚固性的兴趣,以避免安全风险 [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
命令。
答案 1 :(得分:1)
虽然它本身不是解决方案,但我解决了这个问题。但是,我将问题保持开放,因为它仍然存在
我只使用字符串,而不是使用WinSCP类型。因为我已经拥有与WinSCP.Protocol
相同的枚举$FtpSettings.Protocol = [Protocols]::Sftp
并在FtpSettings中设置了协议
$SessionOptions = New-Object WinSCP.SessionOptions -Property @{
Protocol = $this.FtpSettings.Protocol.ToString()
HostName = $this.FtpSettings.Server
UserName = $this.FtpSettings.Username
Password = $this.FtpSettings.Password
}
我可以像这样设置协议
$TransferOptions.TransferMode = "Binary" #[WinSCP.TransferMode]::Binary
我在[WinSCP.TransferMode]上使用了类似的东西
dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING))
答案 2 :(得分:0)
首先,我会推荐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
原始解析将通过集合,脚本将开始,您的引用将被添加,然后随着脚本的继续,。源文件将被读取和解析,添加自定义类没有问题,因为它们的引用库在解析代码时已在内存中。
用适当的清单制作和使用模块仍然要好得多,但这很容易实现,并且很容易记住和使用。
答案 3 :(得分:0)
另一种解决方案是将您的 Add-Type 逻辑放入单独的 .ps1 文件(将其命名为 AssemblyBootStrap.ps1
或其他名称),然后将其添加到模块清单的 ScriptsToProcess
部分。 ScriptsToProcess
在模块清单之前运行,并且程序集将在类定义查找它们时加载。