Functions/GenXdev.Webbrowser/Invoke-WebbrowserEvaluation.ps1

<##############################################################################
Part of PowerShell module : GenXdev.Webbrowser
Original cmdlet filename : Invoke-WebbrowserEvaluation.ps1
Original author : René Vaessen / GenXdev
Version : 3.26.2026
################################################################################
Copyright (c) 2026 René Vaessen / GenXdev

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
################################################################################>


###############################################################################

<#
.SYNOPSIS
Executes JavaScript code in a selected web browser tab.

.DESCRIPTION
Executes JavaScript code in a selected browser tab with support for async/await,
promises, and data synchronization between PowerShell and the browser context.
Can execute code from strings, files, or URLs.

This function provides comprehensive access to browser APIs including IndexedDB,
localStorage, sessionStorage, and other web platform features. It includes
built-in error handling, timeout management, and support for yielding multiple
results from generator functions.

The function uses Playwright for browser automation, which provides privileged
access that bypasses standard JavaScript security restrictions in the context
of the current page. This enables access to storage APIs, cross-origin
resources (within the same tab), and other browser features that would
normally be restricted in standard web contexts.

Key capabilities:
- Async/await and Promise support
- Generator functions with yield support
- Data synchronization via $Global:Data
- Privileged access to browser storage APIs
- Bypasses same-origin policy restrictions for current page storage
- IndexedDB enumeration and data extraction
- DOM manipulation and web API access
- Error handling and timeout management

.PARAMETER Scripts
JavaScript code to execute. Can be string content, file paths, or URLs.
Accepts pipeline input.

.PARAMETER Inspect
Adds debugger statement before executing to enable debugging.

.PARAMETER NoAutoSelectTab
Prevents automatic tab selection if no tab is currently selected.

.PARAMETER Edge
Selects Microsoft Edge browser for execution.

.PARAMETER Chrome
Selects Google Chrome browser for execution.

.PARAMETER Page
Browser page object for execution when using ByReference mode.

.PARAMETER ByReference
Session reference object when using ByReference mode.

.EXAMPLE
// Execute simple JavaScript
Invoke-WebbrowserEvaluation "document.title = 'hello world'"

.EXAMPLE
PS>

// Synchronizing data
Select-WebbrowserTab -Force;
$Global:Data = @{ files= (Get-ChildItem *.* -file | % FullName)};

[int] $number = Invoke-WebbrowserEvaluation "

    document.body.innerHTML = JSON.stringify(data.files);
    data.title = document.title;
    return 123;
";

Write-Host "
    Document title : $($Global:Data.title)
    return value : $Number
";
.EXAMPLE
PS>

Support for promises
Select-WebbrowserTab -Force;
Invoke-WebbrowserEvaluation "
    let myList = [];
    return new Promise((resolve) => {
        let i = 0;
        let a = setInterval(() => {
            myList.push(++i);
            if (i == 10) {
                clearInterval(a);
                resolve(myList);
            }
        }, 1000);
    });
"
.EXAMPLE
PS>

// Support for promises and more

// this function returns all rows of all tables/datastores of all databases of indexedDb in the selected tab
// beware, not all websites use indexedDb, it could return an empty set

Select-WebbrowserTab -Force;
Set-WebbrowserTabLocation "https://www.youtube.com/"
Start-Sleep 3
$AllIndexedDbData = Invoke-WebbrowserEvaluation "

    // enumerate all indexedDB databases
    for (let db of await indexedDB.databases()) {

        // request to open database
        let openRequest = await indexedDB.open(db.name);

        // wait for eventhandlers to be called
        await new Promise((resolve,reject) => {
            openRequest.onsuccess = resolve;
            openRequest.onerror = reject
        });

        // obtain reference
        let openedDb = openRequest.result;

        // initialize result
        let result = { DatabaseName: db.name, Version: db.version, Stores: [] }

        // itterate object store names
        for (let i = 0; i < openedDb.objectStoreNames.length; i++) {

            // reference
            let storeName = openedDb.objectStoreNames[i];

            // start readonly transaction
            let tr = openedDb.transaction(storeName);

            // get objectstore handle
            let store = tr.objectStore(storeName);

            // request all data
            let getRequest = store.getAll();

            // await result
            await new Promise((resolve,reject) => {
                getRequest.onsuccess = resolve;
                getRequest.onerror = reject;
            });

            // add result
            result.Stores.push({ StoreName: storeName, Data: getRequest.result});
        }

        // stream this database contents to the PowerShell pipeline, and continue
        yield result;
    }
";

$AllIndexedDbData | Out-Host

// SECURITY NOTE: This basic example works because the module uses Playwright
// browser automation, which bypasses normal JavaScript security restrictions
// in the current page context. Standard web pages cannot access IndexedDB from
// other origins, but Playwright has the same privileges as the website itself.
// See the enhanced example below for more details on security considerations.

.EXAMPLE
PS>

// Enhanced IndexedDB enumeration with metadata and error handling

// This enhanced approach provides more comprehensive IndexedDB data extraction including
// database counts, error handling, and metadata. Unlike the basic example above, this
// version handles security restrictions, provides detailed store information, and
// includes record counts without necessarily retrieving all data.

Select-WebbrowserTab -Force;
Set-WebbrowserTabLocation "https://www.youtube.com/"
Start-Sleep 3
$EnhancedIndexedDbData = Invoke-WebbrowserEvaluation "

    // Enhanced IndexedDB enumeration with comprehensive error handling
    let results = [];

    for (let dbInfo of await indexedDB.databases()) {
        try {
            // Open database with timeout
            let db = await new Promise((resolve, reject) => {
                let req = indexedDB.open(dbInfo.name);
                req.onsuccess = () => resolve(req.result);
                req.onerror = () => reject(req.error);
                setTimeout(() => reject(new Error('Database open timeout')), 5000);
            });

            let dbResult = {
                DatabaseName: dbInfo.name,
                Version: dbInfo.version,
                ObjectStoreCount: db.objectStoreNames.length,
                Stores: []
            };

            // Process each object store
            for (let i = 0; i < db.objectStoreNames.length; i++) {
                let storeName = db.objectStoreNames[i];
                try {
                    let transaction = db.transaction(storeName, 'readonly');
                    let store = transaction.objectStore(storeName);

                    // Get record count (faster than retrieving all data)
                    let count = await new Promise((resolve, reject) => {
                        let req = store.count();
                        req.onsuccess = () => resolve(req.result);
                        req.onerror = () => reject(req.error);
                        setTimeout(() => reject(new Error('Count timeout')), 3000);
                    });

                    dbResult.Stores.push({
                        StoreName: storeName,
                        RecordCount: count,
                        KeyPath: store.keyPath,
                        AutoIncrement: store.autoIncrement,
                        IndexNames: Array.from(store.indexNames)
                    });

                } catch (storeError) {
                    dbResult.Stores.push({
                        StoreName: storeName,
                        Error: storeError.message
                    });
                }
            }

            results.push(dbResult);
            db.close();

        } catch (dbError) {
            results.push({
                DatabaseName: dbInfo.name,
                Error: dbError.message
            });
        }
    }

    yield results;
";

$EnhancedIndexedDbData | ConvertTo-Json -Depth 10

// Key differences from the basic example:
// 1. Includes error handling for database access issues
// 2. Provides metadata (KeyPath, AutoIncrement, IndexNames)
// 3. Gets record counts without retrieving all data (more efficient)
// 4. Handles timeout scenarios
// 5. Returns structured information about database schema
// 6. More suitable for large databases where retrieving all data would be slow

// SECURITY CONSIDERATIONS FOR INDEXEDDB ACCESS:
// Both examples work because this module uses Playwright browser automation,
// which bypasses standard JavaScript security restrictions in the context
// of the current page:

// Standard JavaScript Limitations:
// - Same-origin policy restricts access to IndexedDB from other origins
// - Some databases may be hidden or protected by browser security features
// - Cross-origin database access is typically blocked
// - Service worker databases may have additional protection

// How this example bypasses restrictions:
// - Uses Playwright browser automation for privileged access
// - Executes in the context of the actual page, not a sandboxed environment
// - Has the same permissions as the website itself for its own storage
// - Can access all databases created by the current origin/domain

// Limitations:
// - Cannot access databases from other origins/domains in the same browser
// - Cannot access databases from other browser profiles or private browsing
// - Some browser extensions may create isolated storage not accessible via JavaScript

// Alternative Approaches for Maximum Access:
// - Use GenXdev.Webbrowser with multiple tabs from different origins
// - Combine with file system access to browser profile directories (when possible)
// - Use browser automation to navigate between different domains

.EXAMPLE
PS>

Support for yielded pipeline results
Select-WebbrowserTab -Force;
Invoke-WebbrowserEvaluation "

    for (let i = 0; i < 10; i++) {

        await (new Promise((resolve) => setTimeout(resolve, 1000)));

        yield i;
    }
";
.EXAMPLE
PS> Get-ChildItem *.js | Invoke-WebbrowserEvaluation -Edge
.EXAMPLE
PS> ls *.js | et -e
.NOTES
Requires the Windows 10+ Operating System
#>

function Invoke-WebbrowserEvaluation {

    [CmdletBinding()]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
    [Alias('Eval', 'et')]

    param(
        ###############################################################################
        [Parameter(
            Position = 0,
            Mandatory = $false,
            HelpMessage = 'JavaScript code, file path or URL to execute',
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)
        ]
        [Alias('FullName')]
        [object[]] $Scripts,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Break in browser debugger before executing',
            ValueFromPipeline = $false)
        ]
        [switch] $Inspect,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            HelpMessage = 'Prevent automatic tab selection'
        )]
        [switch] $NoAutoSelectTab,
        ###############################################################################
        [Alias('e')]
        [parameter(
            Mandatory = $false,
            HelpMessage = 'Use Microsoft Edge browser'
        )]
        [switch] $Edge,
        ###############################################################################
        [Alias('ch')]
        [parameter(
            Mandatory = $false,
            HelpMessage = 'Use Google Chrome browser'
        )]
        [switch] $Chrome,
        ###############################################################################
        [Alias('c')]
        [parameter(
            Mandatory = $false,
            HelpMessage = ('Use Microsoft Edge or Google Chrome, ' +
                'depending on what the default browser is')
        )]
        [switch] $Chromium,
        ###############################################################################
        [Alias('ff')]
        [parameter(
            Mandatory = $false,
            HelpMessage = 'Use Firefox browser'
        )]
        [switch] $Firefox,
        ###############################################################################
        [Alias('wk')]
        [parameter(
            Mandatory = $false,
            HelpMessage = 'Use the Playwright-managed WebKit browser'
        )]
        [switch] $Webkit,
        ###############################################################################
        [Parameter(
            HelpMessage = 'Browser page object reference',
            ValueFromPipeline = $false
        )]
        [object] $Page,
        ###############################################################################
        [Parameter(
            HelpMessage = 'Browser session reference object',
            ValueFromPipeline = $false
        )]
        [PSCustomObject] $ByReference
    )

    Begin {
        # initialize reference tracking
        $reference = $null

        # resolve page from parameters or global Playwright state
        if (($null -eq $Page) -or ($null -eq $ByReference)) {

            if ($ByReference) {
                $reference = $ByReference
                $Page = $ByReference.Page
            }
            elseif ($Page) {
                $reference = @{ Page = $Page; data = $Global:Data }
            }
            else {
                # determine target browser type from switches
                $browserType = if ($Webkit) {
                    'WebKitPlaywright'
                }
                elseif ($Firefox) {
                    'FirefoxPlaywright'
                }
                else {
                    # Edge, Chrome, Chromium, or default map to
                    # ChromiumNormal
                    'ChromiumNormal'
                }

                # try the target browser type first, then fallback to others
                $activeBrowser = if (
                    $Global:GenXdevPlaywright.ContainsKey($browserType)) {
                    $Global:GenXdevPlaywright[$browserType]
                }
                elseif ($Global:GenXdevPlaywright.ContainsKey('ChromiumNormal') -and
                    $browserType -ne 'ChromiumNormal') {
                    $Global:GenXdevPlaywright['ChromiumNormal']
                }
                elseif ($Global:GenXdevPlaywright.ContainsKey('ChromeNormal') -and
                    $browserType -ne 'ChromeNormal') {
                    $Global:GenXdevPlaywright['ChromeNormal']
                }
                elseif ($Global:GenXdevPlaywright.ContainsKey('EdgeNormal') -and
                    $browserType -ne 'EdgeNormal') {
                    $Global:GenXdevPlaywright['EdgeNormal']
                }
                elseif ($Global:GenXdevPlaywright.ContainsKey('ChromiumPlaywright') -and
                    $browserType -ne 'ChromiumPlaywright') {
                    $Global:GenXdevPlaywright['ChromiumPlaywright']
                }
                elseif ($Global:GenXdevPlaywright.ContainsKey('FirefoxPlaywright') -and
                    $browserType -ne 'FirefoxPlaywright') {
                    $Global:GenXdevPlaywright['FirefoxPlaywright']
                }
                elseif ($Global:GenXdevPlaywright.ContainsKey('WebKitPlaywright') -and
                    $browserType -ne 'WebKitPlaywright') {
                    $Global:GenXdevPlaywright['WebKitPlaywright']
                }
                else {
                    $null
                }

                if (-not $activeBrowser) {
                    if ($NoAutoSelectTab) {
                        throw 'No Playwright browser is running. Use Open-PlayWrightBrowser to launch one first.'
                    }

                    # auto-launch the target browser type
                    Microsoft.PowerShell.Utility\Write-Verbose (
                        "No Playwright browser running. Auto-launching ${browserType}..."
                    )

                    try {
                        $activeBrowser = GenXdev\Open-PlayWrightBrowser `
                            -BrowserType $browserType `
                            -ConsentToThirdPartySoftwareInstallation
                    }
                    catch {
                        throw (
                            'Failed to auto-launch Playwright browser: ' +
                            "$($PSItem.Exception.Message)"
                        )
                    }
                }

                $Page = $activeBrowser.Page
                $reference = @{ Page = $Page; data = $Global:Data }
            }
        }
        else {
            $reference = $ByReference
        }

        # validate browser context
        if (($null -eq $Page)) {

            throw 'No browser page available. Use Open-PlayWrightBrowser to launch a browser first.'
        }

        # Define the custom JavaScript for Visibility API events and CSS overrides
        $visibilityScript = @'
document.addEventListener('visibilitychange', function() {
    console.log('Visibility changed to: ' + document.visibilityState);
});
'@


        $cssOverrideScript = @'
document.documentElement.style.setProperty('--default-color-scheme', 'dark');
'@


        # Subscribe to the FrameNavigated event to inject the custom JavaScript
        $null = Microsoft.PowerShell.Utility\Register-ObjectEvent -InputObject $page -EventName FrameNavigated -Action {
            $null = $page.EvaluateAsync($visibilityScript, @()).Wait()
            $null = $page.EvaluateAsync($cssOverrideScript, @()).Wait()
        }
    }


    process {
        Microsoft.PowerShell.Utility\Write-Verbose 'Processing JavaScript evaluation request...'

        # enumerate provided scripts
        foreach ($js in $Scripts) {

            try {

                if ($js -is [System.IO.FileInfo]) {

                    # make it a string
                    $js = $js.FullName;
                }

                Microsoft.PowerShell.Utility\Set-Variable -Name 'Data' -Value $reference.data -Scope Global

                # is it a file reference?
                if (($js -is [IO.FileInfo]) -or (($js -is [System.String]) -and [IO.File]::Exists($js))) {

                    # comming from Get-ChildItem command?
                    if ($js -is [IO.FileInfo]) {

                        # make it a string
                        $js = $js.FullName;
                    }

                    # it's a string with a path, load the content
                    $js = [IO.File]::ReadAllText($js, [System.Text.Encoding]::UTF8)
                }
                else {

                    # make it a string, if it isn't yet
                    if ($js -isnot [System.String] -or [string]::IsNullOrWhiteSpace($js)) {

                        $js = "$js";
                    }

                    if ([string]::IsNullOrWhiteSpace($js) -eq $false) {

                        [Uri] $uri = $null;
                        $isUri = (

                            [Uri]::TryCreate("$js", 'absolute', [ref] $uri) -or (
                                $js.ToLowerInvariant().StartsWith('www.') -and
                                [Uri]::TryCreate("http://$js", 'absolute', [ref] $uri)
                            )
                        ) -and $uri.IsWellFormedOriginalString() -and $uri.Scheme -like 'http*';

                        if ($IsUri) {
                            Microsoft.PowerShell.Utility\Write-Verbose 'is Uri'
                            $httpResult = Microsoft.PowerShell.Utility\Invoke-WebRequest -Uri $Js

                            if ($httpResult.StatusCode -eq 200) {

                                $type = 'text/javascript';

                                if ($httpResult.Content -Match "[`r`n\s`t;,]import ") {

                                    $type = 'module';
                                }
                                $ScriptHash = [GenXdev.Helpers.Hash]::FormatBytesAsHexString(
                                    [GenXdev.Helpers.Hash]::GetSha256BytesOfString($httpResult.Content));
                                $js = "
                                    let scripts = document.getElementsByTagName('script');
                                    for (let i = 0; i < scripts.length; i++) {

                                        let script = scripts[i];
                                        if (!!script && typeof script.getAttribute === 'function' && script.getAttribute('data-hash') === '$scriptHash') {
                                            return;
                                        }
                                    }
                                    let scriptTag = document.createElement('script');
                                    let scriptLoaded = false;
                                    let loaded = () => { };

                                    scriptTag.innerHTML = $(($httpResult.Content | Microsoft.PowerShell.Utility\ConvertTo-Json));
                                    scriptTag.setAttribute('type', '$type');
                                    scriptTag.setAttribute('data-hash', '$ScriptHash');
                                    let head = document.getElementsByTagName('head')[0];
                                    if (!head) {
                                        head = document.createElement('head');
                                        document.appendChild(head);
                                    }
                                    head.appendChild(scriptTag);
                                "
;
                            }
                            else {

                                throw "Downloading script '$js' resulted in http statuscode $($HttpResult.StatusCode) - $($HttpResult.StatusDescription)"
                            }
                        }
                    }
                }

                # '-Inspect' parameter provided?
                if ($Inspect -eq $true) {

                    # invoke a debug break-point
                    $js = "debugger;`r`n$js"
                }

                Microsoft.PowerShell.Utility\Write-Verbose "Processing: `r`n$($js.Trim())"

                # convert data object to json, and then again to make it a json string
                $json = ($reference.data | Microsoft.PowerShell.Utility\ConvertTo-Json -Compress -Depth 100 |
                    Microsoft.PowerShell.Utility\ConvertTo-Json -Compress -Depth 100);

                # Helper: convert Playwright JsonElement to native PS types
                function ConvertFromJsonElement ($Value) {
                    if ($null -eq $Value) { return $null }
                    if ($Value -is [System.Text.Json.JsonElement]) {
                        $je = [System.Text.Json.JsonElement]$Value
                        switch ($je.ValueKind) {
                            'Number' {
                                if ($je.TryGetInt64([ref]$null)) {
                                    $i = 0L; [void]$je.TryGetInt64([ref]$i); return $i
                                }
                                $d = 0.0; [void]$je.TryGetDouble([ref]$d); return $d
                            }
                            'String' { return $je.GetString() }
                            'True'   { return $true }
                            'False'  { return $false }
                            'Array'  { return @($je.EnumerateArray() |
                                    Microsoft.PowerShell.Core\ForEach-Object { ConvertFromJsonElement $_ }) }
                            default  { return $je.ToString() }
                        }
                    }
                    return $Value
                }

                # simple expression: no return, yield, await, or data sync needed
                # Use Playwright's native EvaluateAsync directly (no generator overhead)
                if ($js -notmatch '\b(?:return|yield|await|data\.)\b' -and
                    $js -notmatch '[{};]') {

                    Microsoft.PowerShell.Utility\Write-Verbose 'Simple expression evaluation'
                    $rawResult = $Page.EvaluateAsync($js, @()).GetAwaiter().GetResult()
                    ConvertFromJsonElement $rawResult
                    continue
                }

                # async expression without yield/data — wrap in async IIFE
                if ($js -notmatch '\b(?:yield|data\.)\b') {

                    Microsoft.PowerShell.Utility\Write-Verbose 'Async expression evaluation'
                    if ($js -notmatch '[{};]' -and $js -notmatch '^\s*return\b') {
                        $wrappedJs = "(async () => await ($js))()"
                    }
                    else {
                        $wrappedJs = "(async () => { $js })()"
                    }
                    $rawResult = $Page.EvaluateAsync($wrappedJs, @()).GetAwaiter().GetResult()
                    ConvertFromJsonElement $rawResult
                    continue
                }

                # complex script with yield or data sync — use generator wrapper
                # init result
                $result = $null;
                $ScriptHash = [GenXdev.Helpers.Hash]::FormatBytesAsHexString(
                    [GenXdev.Helpers.Hash]::GetSha256BytesOfString($js));

                $js = "(function(data) {

                let resultData = window['iwae$ScriptHash'] || {

                    started: false,
                    done: false,
                    success: true,
                    data: data,
                    returnValues: []
                }

                window['iwae$ScriptHash'] = resultData;

                function catcher(e) {

                    let resultData = window['iwae$ScriptHash'];
                    resultData.success = false;
                    resultData.done = true;
                    try {
                        resultData.returnValue = JSON.stringify(e);
                    }
                    catch (e2) {

                        resultData.returnValue = e+'';
                    }
                }

                if (!resultData.started) {

                    resultData.started = true;

                    try {

                        eval($("

                        (async () => {
                            let result;
                            try {

                                result = (async function*() { $js })();

                                let resultCount = 0;
                                let resultValue;
                                do {
                                    resultValue = await result.next();

                                    if (resultValue.value instanceof Promise) {

                                        resultValue.value = await resultValue.value;
                                    }

                                    let resultData = window['iwae$ScriptHash']

                                    if (resultCount++ === 0 && resultValue.done) {

                                        resultData.returnValue = resultValue.value;
                                    }
                                    else {
                                        if (!resultValue.done) {

                                            resultData.returnValues.push(resultValue.value);
                                        }
                                    }
                                } while (!resultValue.done)

                                let resultData = window['iwae$ScriptHash']
                                resultData.done = true;
                                resultData.success = true;
                            }
                            catch (e) {

                                catcher(e);
                            }
                        })()

                        " | Microsoft.PowerShell.Utility\ConvertTo-Json -Compress -Depth 100));
                    }
                    catch(e) {

                        catcher(e);
                    }
                }

                if (resultData.done) {

                    delete window['iwae$ScriptHash'];
                }

                let clone = JSON.parse(JSON.stringify(resultData));
                resultData.returnValues = [];
                return clone;
            })(JSON.parse($json));
        "
;
                [int] $pollCount = 0;
                $result = $null;
                do {
                    # de-serialize outputed result object
                    $result = $Page.EvaluateAsync($js, @()).Result
                    if ($null -eq $result) {

                        continue;
                    }
                    # Playwright returns JsonElement; stringify it so
                    # ConvertFrom-Json can process it
                    if ($result -is [System.Text.Json.JsonElement]) {
                        $result = $result.ToString()
                    }
                    $result = ($result | Microsoft.PowerShell.Utility\ConvertFrom-Json);

                    if ($null -ne $result) {

                        Microsoft.PowerShell.Utility\Write-Verbose "Got results: [$($result.getType())] $($result | Microsoft.PowerShell.Utility\ConvertTo-Json -Compress -Depth 100)"
                    }

                    # all good?
                    if ($result -is [PSCustomObject]) {

                        # there was an exception thrown?
                        if ($result.subtype -eq 'error') {

                            # re-throw
                            throw $result;
                        }

                        # got a data object?
                        if ($null -ne $result.data) {

                            # initialize
                            $reference.data = @{}

                            # enumerate properties
                            $result.data |
                                Microsoft.PowerShell.Utility\Get-Member -ErrorAction SilentlyContinue |
                                Microsoft.PowerShell.Core\Where-Object -Property MemberType -Like *Property* |
                                Microsoft.PowerShell.Core\ForEach-Object -ErrorAction SilentlyContinue {

                                    # set in a case-sensitive manner
                                    $reference.data."$($PSItem.Name)" = $result.data."$($PSItem.Name)"
                                }

                            Microsoft.PowerShell.Utility\Set-Variable -Name 'Data' -Value ($reference.data) -Scope Global
                        }

                        $pollCount++;

                        if (($null -ne $result.returnValues) -and ($result.returnValues.Length -gt 0)) {

                            $result.returnValues | Microsoft.PowerShell.Utility\Write-Output
                            $result.returnValues = @();
                        }
                        $result.returnValues = @();
                    }
                } while (!!$result -and !$result.done -and (-not [Console]::KeyAvailable));

                # result indicate an exception thrown?
                if ($result.success -eq $false) {

                    if ($result.returnValue -is [string]) {

                        # re-throw
                        throw $result.returnValue;
                    }

                    throw 'An unknown script parsing error occured';
                }

                if ($null -ne $result.returnValue) {

                    Microsoft.PowerShell.Utility\Write-Output $result.returnValue;
                }
            }
            Catch {

                throw "
                        $($PSItem.Exception) $($PSItem.InvocationInfo.PositionMessage)
                        $($PSItem.InvocationInfo.Line)
                    "

            }
        }
    }

    End {

    }
}