Internal/Invoke-RScript.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
function Invoke-RScript {

    <#

    .SYNOPSIS Invokes a RScript command

    .PARAMETER ArgumentList
    Specifies the arguments that are passed to RScript.
    All arguments are casted to string.

    #>


    [CmdletBinding()]
    param (
        [Parameter( Mandatory )]
        [string[]]
        $ArgumentList,

        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        [int]
        $Timeout,

        [switch] $ParseOutput
    )

    if ( -not $RScriptPath ) {
        throw 'Path to Rscript.exe is not net. Please run Set-RScriptPath.'
    } elseif ( -not ( Test-Path $RScriptPath -PathType Leaf )) {
        throw 'Path to Rscript.exe is invalid. Please re-run Set-RScriptPath.'
    }

    $arguments = $ArgumentList | ForEach-Object {
        Write-Output '-e'
        Write-Output """$( $_.Replace('"', '\"') )"""
    }

    # Configure process
    $process = New-Object System.Diagnostics.Process
    $process.StartInfo.Filename = $RScriptPath
    $process.StartInfo.Arguments = $arguments
    $process.StartInfo.WorkingDirectory = Get-Location
    $process.StartInfo.RedirectStandardOutput = $true
    $process.StartInfo.RedirectStandardError = $true
    $process.StartInfo.UseShellExecute = $false
    $process.StartInfo.CreateNoWindow = $true

    # Connect output events
    $standardOutputBuffer = New-Object System.Collections.SortedList
    $standardErrorBuffer = New-Object System.Collections.SortedList

    $EventAction = {
        if ( -not [String]::IsNullOrEmpty( $EventArgs.Data )) {
            $Event.MessageData.Add( $event.EventIdentifier, $EventArgs.Data ) | Out-Null
            Write-Verbose $EventArgs.Data
        }
    }

    $outputEvent = Register-ObjectEvent -InputObject $process `
        -EventName 'OutputDataReceived' -Action $EventAction -MessageData $standardOutputBuffer
    $errorEvent = Register-ObjectEvent -InputObject $process `
        -EventName 'ErrorDataReceived' -Action $EventAction -MessageData $standardErrorBuffer

    try {
        $processCall = "$( $process.StartInfo.FileName ) $( $process.StartInfo.Arguments )"
        if ( $processCall.Length -ge 250 ) { $processCall = "$( $processCall.Substring(252) )..." }
        Write-Verbose "Process started: $processCall"

        $process.Start() | Out-Null
        $process.BeginOutputReadLine()
        $process.BeginErrorReadLine()

        # Wait for exit
        if ( $Timeout ) {
            $process.WaitForExit( $Timeout * 1000 ) | Out-Null
        }
        $process.WaitForExit() | Out-Null # Ensure streams are flushed

        Write-Verbose "Process exited (code $( $process.ExitCode )) after $( $process.TotalProcessorTime )."
    } finally {
        Unregister-Event -SourceIdentifier $outputEvent.Name
        Unregister-Event -SourceIdentifier $errorEvent.Name
    }

    $lineIndex = 1

    # Process output
    if ( $standardOutputBuffer.Count  ) {
        if ( $ParseOutput ) {
            $standardOutputBuffer.Values | ForEach-Object {
                $prefix = "[$lineIndex] "
                if ( $_.StartsWith( $prefix ) ) {
                    $lineIndex += 1
                    Write-Output $_.Substring( $prefix.Length ).Trim()
                }
            }
        } else {
            $standardOutput = $standardOutputBuffer.Values -join "`r`n"
            Write-Output $standardOutput
        }
    } else {
        Write-Verbose 'No process output'
    }

    # process error
    if ( $standardErrorBuffer.Count ) {
        foreach ( $line in $standardErrorBuffer.Values ) {
            if ( $line ) {
                Write-Warning $line -ErrorAction 'Continue'
            }
        }
    } else {
        Write-Verbose 'No process error output'
    }

    if ( $process.ExitCode ) {
        throw "Proccess failed ($processCall) after $( $process.TotalProcessorTime )."
    } elseif ( $Timeout -gt 0 -and $process.TotalProcessorTime.TotalSeconds -ge $Timeout ) {
        throw "Process timed out ($processCall) after $( $process.TotalProcessorTime )."
    }
}