I am writing a script to work in the programs "Windows PowerShell" version 5.1 and "PowerShell" version 7 in the operating system "Windows 10".
I am creating an object via COM using a class HtmlDocument
from 'Windows Forms':
$dom = New-Object -ComObject "HTMLFile"
I want to use the method write
of this class. The documentation says that it gets a System.String
type parameter. But passing a string of type System.String
to the method write
does not work:
$dom = New-Object -ComObject "HTMLFile"
[System.String] $str = "Text"
$dom.write($str) # Error: type mismatch
I checked what type of parameter the method write
requires:
$dom = New-Object -ComObject "HTMLFile"
$dom | Get-Member write
And I got this result:
TypeName: System.__ComObject#{3050f55f-98b5-11cf-bb82-00aa00bdce0b}
Name MemberType Definition
---- ---------- ----------
write Method void write (SAFEARRAY(Variant) psarray)
I read that SAFEARRAY and Variant are the structures in C used in marshaling when working with COM. Using these structures, you can transfer data in an array of any type. SAFEARRAY
is an array that, in addition to data, stores information about itself. Variant
is an array element, it can be of different types.
I found on 'Stack Overflow' the answers jedigo, iRon, mark from which it became clear to me that a byte array should be passed to the write
method:
$dom = New-Object -ComObject "HTMLFile"
[byte[]] $str = 84, 0, 101, 0, 120, 0, 116, 0 # "Text" in encoding UTF-16LE
$dom.write($str)
or the same thing:
$dom = New-Object -ComObject "HTMLFile"
$str = [Text.Encoding]::Unicode.GetBytes("Text") # UTF-16LE
$dom.write($str)
My question: how did people know that a byte array needs to be passed to the write
method? This does not follow from the SAFEARRAY(Variant)
information. Did they guess? Did they find the answer by going through different types? Is there any documentation in which this can be found?
I need to know the answer to this question because if in the future I work from 'PowerShell' via COM, then I may come across more similar cases and I will need to know the type of variable passed to the component.
CodePudding user response:
TL/DR: You have to know or guess with a little help from the documentation.
Technically passing a byte array of UTF-16 is not what the documentation says but the receiving function might handle multiple input formats.
If we look at the documentation for IHTMLDocument2::write
we see it wants
A SAFEARRAY of BSTR that specifies the text and HTML tags to write.
The example code on that page does:
...
SAFEARRAY *psaStrings = SafeArrayCreateVector(VT_VARIANT, 0, 1); // count is 1
VARIANT *param;
HRESULT hr = SafeArrayAccessData(psaStrings, (LPVOID*)¶m);
param->vt = VT_BSTR;
param->bstrVal = bstr; //SysAllocString
hr = SafeArrayUnaccessData(psaStrings);
hr = document->write(psaStrings);
This is a SAFEARRAY with 1 entry in the array, a VARIANT. The variant is of type BSTR. BSTR is a counted array of WCHARs but can often be treated the same as a UTF-16LE string.
When Powershell asks COM, the part about BSTR has been lost. The array item type was probably not a part of the type library.
Some COM functions will only accept a single variant type while others might support multiple. Support for multiple might be hand-coded to a limited set or it might call VariantChangeType
(unlikely).