internal/functions/invoke-process.ps1


<#
    .SYNOPSIS
        Invoke a process
         
    .DESCRIPTION
        Invoke a process and pass the needed parameters to it
         
    .PARAMETER Path
        Path to the program / executable that you want to start
         
    .PARAMETER Params
        Array of string parameters that you want to pass to the executable
         
    .PARAMETER TimeoutInMinutes
        Number of minutes the process is allowed to run, before you want it to exit
         
    .PARAMETER ShowOriginalProgress
        Instruct the cmdlet to show the standard output in the console
         
        Default is $false which will silence the standard output
         
    .PARAMETER OutputCommandOnly
        Instruct the cmdlet to only output the command that you would have to execute by hand
         
        Will include full path to the executable and the needed parameters based on your selection
         
    .PARAMETER EnableException
        This parameters disables user-friendly warnings and enables the throwing of exceptions
        This is less user friendly, but allows catching exceptions in calling scripts
         
    .EXAMPLE
        PS C:\> Invoke-Process -Path "C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\AXBuild.exe" -Params "xppcompileall","/altbin=`"C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin`"","/aos=01","/dbserver=`"SQLTEST`"","/modelstore=`"AXTEST_model`"","/log=`"c:\temp\ax2012.tools\AxBuildLog`"","/compiler=`"C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\ax32serv.exe`""
         
        This will invoke the "C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\AXBuild.exe" executable.
        All parameters will be passed to it.
        The standard output will be redirected to a local variable.
        The error output will be redirected to a local variable.
        The standard output will be written to the verbose stream before exiting.
         
        If an error should occur, both the standard output and error output will be written to the console / host.
         
    .EXAMPLE
        PS C:\> Invoke-Process -OutputCommandOnly -Path "C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\AXBuild.exe" -Params "xppcompileall","/altbin=`"C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin`"","/aos=01","/dbserver=`"SQLTEST`"","/modelstore=`"AXTEST_model`"","/log=`"c:\temp\ax2012.tools\AxBuildLog`"","/compiler=`"C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\ax32serv.exe`""
         
        This will generate the command for the "C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\AXBuild.exe" executable.
        All parameters will be included in the output command.
         
    .EXAMPLE
        PS C:\> Invoke-Process -ShowOriginalProgress -Path "C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\AXBuild.exe" -Params "xppcompileall","/altbin=`"C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin`"","/aos=01","/dbserver=`"SQLTEST`"","/modelstore=`"AXTEST_model`"","/log=`"c:\temp\ax2012.tools\AxBuildLog`"","/compiler=`"C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\ax32serv.exe`""
         
        This will invoke the "C:\Program Files\Microsoft Dynamics AX\60\Server\AXTEST\Bin\AXBuild.exe" executable.
        All parameters will be passed to it.
        The standard output will be outputted directly to the console / host.
        The error output will be outputted directly to the console / host.
         
    .NOTES
        Author: Mötz Jensen (@Splaxi)
#>


function Invoke-Process {
    [CmdletBinding()]
    [OutputType([System.String], ParameterSetName = "Generate")]
    param (
        [Parameter(Mandatory = $true)]
        
        [Alias('Executable')]
        [string] $Path,

        [Parameter(Mandatory = $true)]
        [string[]] $Params,

        [int32] $TimeoutInMinutes = 0,

        [switch] $ShowOriginalProgress,

        [Parameter(ParameterSetName = "Generate")]
        [switch] $OutputCommandOnly,

        [switch] $EnableException
    )

    Invoke-TimeSignal -Start

    if (-not (Test-PathExists -Path $Path -Type Leaf)) { return }

    if (Test-PSFFunctionInterrupt) { return }

    [Int32] $millisecondFactor = 60000

    [Int32] $timeoutForExit = 0

    if ($TimeoutInMinutes -eq 0) {
        $timeoutForExit = [Int32]::MaxValue
    }
    else {
        $timeoutForExit = $TimeoutInMinutes * $millisecondFactor
    }

    $tool = Split-Path -Path $Path -Leaf

    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = "$Path"
    $pinfo.WorkingDirectory = Split-Path -Path $Path -Parent

    if (-not $ShowOriginalProgress) {
        Write-PSFMessage -Level Verbose "Output and Error streams will be redirected (silence mode)"

        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
    }

    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = "$($Params -join " ")"
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo

    Write-PSFMessage -Level Verbose "Starting the $tool" -Target "$($params -join " ")"

    if ($OutputCommandOnly) {
        Write-PSFMessage -Level Host "$Path $($pinfo.Arguments)"
        return
    }
    
    $p.Start() | Out-Null
    
    if (-not $ShowOriginalProgress) {
        $stdout = $p.StandardOutput.ReadToEnd()
        $stderr = $p.StandardError.ReadToEnd()
    }

    Write-PSFMessage -Level Verbose "Waiting for the $tool to complete"

    if(-not ($p.WaitForExit($timeoutForExit))) {
        Write-PSFMessage -Level Host "Timeout for exit has been <c='em'>reached</c>. Will execute a kill operation now."
        
        $p.Kill()

        Write-PSFMessage -Level Host "Standard output was: \r\n $stdout"
        Write-PSFMessage -Level Host "Error output was: \r\n $stderr"

        $messageString = "Stopping because Timeout has been reached."
        Stop-PSFFunction -Message "Stopping because of Timeout." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) -StepsUpward 1
        return
    }
    
    if ($p.ExitCode -ne 0 -and (-not $ShowOriginalProgress)) {
        Write-PSFMessage -Level Host "Exit code from $tool indicated an error happened. Will output both standard stream and error stream."
        Write-PSFMessage -Level Host "Standard output was: \r\n $stdout"
        Write-PSFMessage -Level Host "Error output was: \r\n $stderr"

        $messageString = "Stopping because an Exit Code from $tool wasn't 0 (zero) like expected."
        Stop-PSFFunction -Message "Stopping because of Exit Code." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) -StepsUpward 1
        return
    }
    else {
        Write-PSFMessage -Level Verbose "Standard output was: \r\n $stdout"
    }

    Invoke-TimeSignal -End
}