Execution.psm1

function Invoke-SelfElevation() {
    # Self-elevate the script if required
    if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'administrator')) {
        if ([int] (Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
            # $elevateVersion = '1.0.0'

            # $elevateDir = Join-Path $RootDir "_tools"
            # $elevateExe = Join-Path $elevateDir 'elevate.exe'

            # if (!(Test-Path $elevateExe)) {
            # if (-not (Test-Path $elevateDir)) {
            # New-Item $elevateDir -ItemType Directory > $null
            # }

            # Log info 'Downloading elevate'
            # Invoke-WebRequest "https://github.com/DoCode/elevation/releases/download/$elevateVersion/elevate.exe" -UseBasicParsing -OutFile $elevateExe
            # }

            # Always append '-newWindow' to leave elevated window open
            $Global:ScriptArgs += ' -newWindow'
            $Global:ScriptArgs = $Global:ScriptArgs.Trim()

            $argumentList = "-NoProfile -ExecutionPolicy Unrestricted -Command `"& `"$($MyInvocation.PSCommandPath)`" $Global:ScriptArgs`""
            Start-Process -FilePath powershell -ArgumentList $argumentList -WorkingDirectory $PSScriptRoot -Verb runas

            # $argumentList = "powershell -NoProfile -ExecutionPolicy Unrestricted -Command `"& `"$PSCommandPath`" $ScriptArgs`""
            # Start-Process -FilePath $elevateExe -ArgumentList $argumentList -WorkingDirectory $elevateDir
            exit 0
        }
    }
}

function Exit-WithAndWaitOnExplorer([int] $exitCode) {
    $parentProcessId = Get-CimInstance Win32_Process -Filter "ProcessId = $PID" | Select-Object -ExpandProperty ParentProcessId
    if ($parentProcessId) {
        $parentParentProcessId = Get-CimInstance Win32_Process -Filter "ProcessId = $parentProcessId" | Select-Object -ExpandProperty ParentProcessId
        if ($parentParentProcessId) {
            $parentParentProcessName = Get-CimInstance Win32_Process -Filter "ProcessId = $parentParentProcessId" | Select-Object -ExpandProperty Name
        }
    }

    $newWindow = $Global:ScriptArgs.ToLowerInvariant().Contains('-newWindow'.ToLowerInvariant())
    if (($parentParentProcessId -and $parentParentProcessName -eq 'explorer.exe') -or $newWindow) {
        Log info 'Press any key to continue . . . '
        $HOST.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') > $null
        $HOST.UI.RawUI.FlushInputBuffer()
    }

    exit $exitCode
}

function Start-NativeExecution() {
    $backupEap = $Script:ErrorActionPreference
    $Script:ErrorActionPreference = 'Continue'

    try {
        if ($args.Length -lt 1) {
            Log warning 'No arguments specified'
            return
        }

        Log trace "Execute: '$args'"

        $command = $args[0] | Get-QuotedPath
        $arguments = $args | Select-Object -Skip 1 | Get-QuotedPath

        Log trace "Command: '$command'"
        if ($arguments -and $arguments.Length -gt 0) {
            Log trace "Arguments: '$arguments'"
        }

        $wrapperScriptBlock = [ScriptBlock]::Create("& $command $arguments")

        $calledFromPrompt = Test-CalledFromPrompt
        if ($calledFromPrompt) {
            $wrapperScriptBlock = [ScriptBlock]::Create("& $command $arguments")
        } else {
            $wrapperScriptBlock = [ScriptBlock]::Create("& $command $arguments 2>&1")
        }

        Log trace "WrapperScriptBlock: '$wrapperScriptBlock'"

        $messages = & $wrapperScriptBlock

        # NOTE: If $wrapperScriptBlock's command doesn't have a native invocation,
        # $LASTEXITCODE will point to the obsolete value
        Log trace "LASTEXITCODE: $LASTEXITCODE"
        if (-not $?) {
            Log error "Execution of '$args' failed with exit code $LASTEXITCODE."
            $logLevel = 'error'
        } else {
            if ($LASTEXITCODE -ne 0) {
                $logLevel = 'warning'
            } else {
                $logLevel = 'info'
            }
        }

        if ($calledFromPrompt -and (Test-Path Variable:\messages)) {
            if ($messages -is [System.Object[]]) {
                foreach ($message in $messages) {
                    if ($message.GetType() -eq [System.Management.Automation.ErrorRecord]) {
                        $lines = $message.Exception.Message.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries)
                    } elseif ($message.GetType() -eq [string]) {
                        $lines = $message.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries)
                    }

                    if (Test-Path Variable:\lines) {
                        $lines | Log $logLevel
                    }
                }
            }

            if ($messages -is [string]) {
                $messages.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries) | Log $logLevel
            }
        }
    } catch {
        if ($_.Exception -and $_.Exception.Message) {
            $_.Exception.Message.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries) | Log error
        }
    } finally {
        if (-not (Test-Path Variable:\messages)) {
            $messages = $null
        }

        $Script:ErrorActionPreference = $backupEap
    }

    return $messages
}

function Get-QuotedPath {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string] $Path
    )

    process {
        Log trace "Path: $Path"

        if ($Path -match '\s') {
            return "`"$Path`""
        } else {
            return $Path
        }
    }
}

function Test-CalledFromPrompt() {
    $command = (Get-PSCallStack)[-2].Command
    Log trace "PromptCommand: $command"

    return ($command -eq 'prompt')
}