Joel Kolb
Joel Kolb

Reputation: 13

Using PowerShell to run Javascript in Internet Explorer on Windows Server

I have this piece of a PowerShell script:

$IE = New-Object -com InternetExplorer.Application
$IE.Navigate($URL)
While ($IE.ReadyState -Ne 4) {Start-Sleep -Milliseconds 100}
$IE.Document.ParentWindow.ExecScript("var JSIEVariable = new XMLSerializer().serializeToString(document);", "javascript")
$Obj = $IE.Document.ParentWindow.GetType().InvokeMember("JSIEVariable", 4096, 
$Null, $IE.Document.parentWindow, $Null)
$HTML = $Obj.ToString()
$IE.Quit()

On Windows 10 it works fine but on Windows Server 2016 for lines 4, 5 and 6 I get the error:

You cannot call a method on a null-valued expression.

I'm pretty sure it has something to do with extra security in Windows Server preventing IE from running Javascript. There must be some way to dial back that security to be more on par with Windows 10 so that this script can run properly, but I can't figure out how. I've turned off IE Enhanced Security Configuration and ensured that Active Scripting is enabled. Aside from that I don't know what else to do.

Upvotes: 0

Views: 4671

Answers (2)

Joel Kolb
Joel Kolb

Reputation: 13

A coworker helped me figure this out. It doesn't have anything to do with IE security, at least not as far as anything that hasn't already been covered. The problem is 'Microsoft.mshtml.dll' is missing from the GAC. It won't be present on a clean install of Windows Server but installing something like Office or Visual Studio will add it. However, i would bet that most people running a Windows Server wouldn't want to do that just for the sake of getting this working. What I did was copy the following folder/file structure from my Windows 10 PC to my server, closed out all instances of PowerShell and ISE and when I opened PowerShell again and ran the script everything worked.

C:\Windows\assembly\GAC\Microsoft.mshtml C:\Windows\assembly\GAC\Microsoft.mshtml\7.0.3300.0__b03f5f7f11d50a3a C:\Windows\assembly\GAC\Microsoft.mshtml\7.0.3300.0__b03f5f7f11d50a3a\Microsoft.mshtml.dll C:\Windows\assembly\GAC\Microsoft.mshtml\7.0.3300.0__b03f5f7f11d50a3a__AssemblyInfo__.ini

Upvotes: 0

Theo
Theo

Reputation: 61148

This may have something to do with Internet Explorer 'protected mode'. If IE indeed is in protected mode, the $IE object gets lost after the .Navigate() command and any action after that will result in the error You cannot call a method on a null-valued expression.

To handle this, here's a function that tries to reconnect the $IE object.

function Connect-InternetExplorer {
    # creates a new 'InternetExplorer.Application' object and navigates to the given url.
    # If IE is in 'protected mode', the function tries to reconnect using the window handle
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true, Position = 0)]
        $Url,

        [switch]$Visible
    )
    # test if Internet Explorer is in 'Protected Mode'
    # see https://www.lifewire.com/how-to-disable-protected-mode-in-internet-explorer-2624507
    $ieProtectedMode = ((Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3' -Name '2500').2500 -ne 3)

    $ie = New-Object -ComObject 'InternetExplorer.Application' -ErrorAction SilentlyContinue
    $ie.Visible = [bool]$Visible
    $ie.Silent = $true
    $hwnd = $ie.Hwnd
    $ie.Navigate($Url)

    if ($ieProtectedMode) {
        $oldErrorActionPreference = $ErrorActionPreference
        $ErrorActionPreference = 'Stop'

        $objShell = New-Object -ComObject 'Shell.Application'
        Start-Sleep -Milliseconds 100
        try {
            $ie = $objShell.Windows() | Where-Object {$_.HWND -eq $Hwnd}
            $ie.Visible = [bool]$Visible
        }
        catch {
            # sometimes the Shell.Application does not find the window quickly enough,
            Start-Sleep -Milliseconds 500
            try {
                $ie = $objShell.Windows() | Where-Object {$_.HWND -eq $Hwnd}
                $ie.Visible = [bool]$Visible
            }
            catch {
                Write-Warning "Could not connect to the InternetExplorer ComObject."
            }
        }
        finally {
            $ErrorActionPreference = $oldErrorActionPreference
            # clean up the Com object
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objShell) | Out-Null
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        }
    }

    if (!$ie) { return $null }
    while ($ie.Busy -eq $true) { Start-Sleep -Milliseconds 50 }
    return $ie
}

# this replaces the first three lines of your original code
$IE = Connect-InternetExplorer -Url $URL
if ($IE) {
    $IE.Document.ParentWindow.ExecScript("var JSIEVariable = new XMLSerializer().serializeToString(document);", "javascript")
    $Obj = $IE.Document.ParentWindow.GetType().InvokeMember("JSIEVariable", 4096, $Null, $IE.Document.parentWindow, $Null)
    $HTML = $Obj.ToString()
    $IE.Quit()

    # clean up the $IE Com object
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($IE) | Out-Null
    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()
}
else {
    Write-Warning "Could not connect Internet Explorer"
}

Hope that helps

Upvotes: 1

Related Questions