使用 New-Object -ComObject "HTMLFile" 解析本地 HTML 文件损坏了吗?

Parsing local HTML file using New-Object -ComObject "HTMLFile" broken?

我已经 运行 设置了 6 个月的密码过期脚本,没有任何问题。该脚本将读取静态 html 文件并更改内存中的一些内容,然后将向所有密码过期的用户发送一封 html 电子邮件。

脚本似乎在过去一周左右的时间里坏了。经过进一步调查,我已将错误缩小到 Powershell 应该创建新 ComObject 并将该 HTML 文件写入 ComObject 的部分。

我现在得到错误:

No coercion operator is defined between types 'System.Array' and 'System.String'
At line:1 char:1
+ $html.write($source);
+ ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException

当我 运行 以下代码行时会发生上述错误:

$html = New-Object -ComObject "HTMLFile"
$src = Get-Content -path "./passwordreminder.html" -Raw
$html.write($src)

当我调用 write() 方法时出现错误。

自从过去 6 个月以来它一直运行良好,我能想到的唯一改变的是我的 powershell 版本。我相信当我开始 运行ning 这个脚本时我使用的是 Powershell v4.0,但是在 Windows 更新之后我猜 Powershell 现在是 v5.0。见下文:

Name                           Value
----                           -----
PSVersion                      5.0.10105.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.34209
BuildVersion                   10.0.10105.0
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3

脚本在 Windows Server 2012 R2 OS 上 运行ning OS。

有人有什么想法吗?

我在其他问题中看到一些建议调用 ComObject 上的 IHTMLDocument2_write() 方法,但是当我尝试调用它时,此方法不存在。

更新:

我能够确认这在我的 Powershell 版本中 确实损坏

我只能在具有相同 OS 但低于 Powershell 版本的不同服务器上测试相同的代码:

Name                           Value
----                           -----
PSVersion                      4.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.34014
BuildVersion                   6.3.9600.17090
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion      2.2

并且代码按预期工作。

有人知道这个新版本的 Powershell 有什么用吗?

您可以尝试使用 Internet Explorer COM 对象:

$ie = New-Object -COM 'InternetExplorer.Application'

$ie.Navigate("file://$($PWD.Path)/passwordreminder.html")
do {
  Start-Sleep -Milliseconds 100
} until ($ie.ReadyState -eq 4)

# do stuff

不过,我没有 PowerShell v5,所以无法测试。如果 HTMLFile 损坏,这也可能是。

如果需要重复运行,可以在外循环中调用 Navigate() 方法(以及等待它完成页面加载的循环)。

$ie = New-Object -COM 'InternetExplorer.Application'

foreach (...) {
  $ie.Navigate("file://$($PWD.Path)/passwordreminder.html")
  do {
    Start-Sleep -Milliseconds 100
  } until ($ie.ReadyState -eq 4)

  # do stuff
}

通过添加路径引用并指定对象类型解决

Add-Type -Path "C:\Program Files (x86)\Microsoft.NET\Primary Interop Assemblies\Microsoft.mshtml.dll"

$webpage = New-Object mshtml.HTMLDocumentClass

这是完整的代码

$url = 'http://website'
$outFile = 'C:\content.txt'
$showCount = 10;

[net.httpwebrequest]$httpwebrequest = [net.webrequest]::create($url)
[net.httpWebResponse]$httpwebresponse = $httpwebrequest.getResponse()
$reader = new-object IO.StreamReader($httpwebresponse.getResponseStream())
$html = $reader.ReadToEnd()
$reader.Close()

Add-Type -Path "C:\Program Files (x86)\Microsoft.NET\Primary Interop Assemblies\Microsoft.mshtml.dll"


$webpage = New-Object mshtml.HTMLDocumentClass
$webpage.IHTMLDocument2_write($html)

$topicElements = $webpage.documentElement.getElementsByClassName('topic')

$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$content = '[www.hkgalden.com] [' + $time + '] '

$i = 0;
foreach ($topicElement in $topicElements) {
    $titleElement = $topicElement.getElementsByClassName('title')[0].getElementsByTagName('a')[0]
    $title = $titleElement.innerText

    $usernameElement = $topicElement.getElementsByClassName('username')[0]
    $username = $usernameElement.innerText

    $content += $username + ': ' + $title + ' // '
    $i++
    if ($i -gt $showCount) {
        break
    }
}
#$content
$content | Out-File -Encoding utf8 $outFile

如果您提供 UCS-2 字节数组而不是字符串,这似乎可以正常工作:

$html = New-Object -ComObject "HTMLFile"
$src = Get-Content -path "./passwordreminder.html" -Raw
$src = [System.Text.Encoding]::Unicode.GetBytes($src)
try
{
    # This works in PowerShell 4
    $html.IHTMLDocument2_write($src)
}
catch
{
    # This works in PowerShell 5
    $html.write($src)
}

此代码片段通过 Add-Type -AssemblyName cmdlet 添加 .NET Framework 的 mshtml.HTMLDocumentClass 类型。

Add-Type -AssemblyName "Microsoft.mshtml"
$html = New-Object -ComObject "HTMLFile"
$svc = Get-Service | Select-Object Name, Status | ConvertTo-Html
$svc | Out-File -FilePath .\report.html -Force
$htmlFile = Get-Content -Path .\report.html -Raw
$html.IHTMLDocument2_write($htmlFile)

$html 变量包含 "HTMLFile" 对象引用及其所有方法和属性。