extensions.ps1


function Get-ICExtension {
    [cmdletbinding(DefaultParameterSetName="List")]
    Param(
        [parameter(
            Mandatory, 
            ValueFromPipeline,
            ParameterSetName='Id')]
        [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })]
        [alias('extensionId')]
        [String]$Id,
        
        [parameter(
            ParameterSetName='List',
            HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")]
        [HashTable]$where=@{},

        [parameter(
            ParameterSetName='List')]
        [Switch]$NoLimit,

        [parameter(
            ParameterSetName='List')]
        [Switch]$CountOnly,

        [Parameter()]
        [Switch]$Save,

        [parameter(
            ParameterSetName = 'Id')]
        [parameter(
            ParameterSetName = 'List')]
        [Switch]$IncludeBody
    )

    PROCESS {

        if ($Id) {
            Write-Verbose "Looking up extension by Id."
            $Endpoint = "extensions/$Id"
            $exts = Get-ICAPI -Endpoint $Endpoint -ea 0
            if (-NOT $exts) {
                Write-Warning "Could not find extension with Id: $($Id)"
                return
            }
        } else {
            Write-Verbose "Getting extensions"
            $Endpoint = "extensions"
            $exts = Get-ICAPI -endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly
            if (-NOT $exts) {
                Write-Verbose "Could not find any extensions loaded with filter: $($where|convertto-json -Compress)"
                return
            }
            if ($CountOnly) { return $exts }
        }
        if (-NOT $IncludeBody) {
            return $exts
        }

        $n = 1
        if ($null -eq $exts.count) {
            $c = 1
        } else { 
            $c = $exts.count 
        }
        $exts | ForEach-Object {
            $ext = $_
            Write-Verbose "Getting Extension $($ext.name) [$($ext.id)]"
            try { $pc = [math]::Floor(($n / $c) * 100) } catch { $pc = -1 }
            Write-Progress -Id 1 -Activity "Getting Extention Body from Infocyte API" -Status "Requesting Body from Extension $n of $c" -PercentComplete $pc
            $extBody = Get-ICAPI -endpoint "extensions/$($ext.id)/LatestVersion" -fields body, sha256
            $Script = @{
                body = $extBody.body 
                sha256 = $extBody.sha256
            }
            Write-Verbose "Looking up user: $($ext.createdBy) and $($ext.updatedBy)"
            $ext.createdBy = (Get-ICAPI -endpoint users -where @{ id = $ext.createdBy } -fields email -ea 0).email
            $ext.updatedBy = (Get-ICAPI -endpoint users -where @{ id = $ext.updatedBy } -fields email -ea 0).email

            Write-Verbose "Parsing Extension Header for $($ext.name) [$($ext.id)]"
            try {
                $header = Parse-ICExtensionHeader -Body $Script.body
                if ($header) {
                    $h = @{}
                    $header.psobject.properties | % {
                        $h[$_.name] = $_.value
                    }
                }
                $ext | Add-Member -MemberType NoteProperty -Name args -Value $h.args
                $ext | Add-Member -MemberType NoteProperty -Name globals -Value $h.globals
                $ext | Add-Member -MemberType NoteProperty -Name header -Value $h
            } catch {
                Write-Warning "Could not parse header on $($ext.name) [$($ext.id)]: $($_)"
            }
            $ext | Add-Member -MemberType NoteProperty -Name script -Value $Script
            $n += 1
        }
        Write-Progress -Id 1 -Activity "Getting Extentions from Infocyte API" -Status "Complete" -Completed
        
        $SavePath = (Resolve-Path .\).Path
        if ($Save) {
            $exts | Foreach-Object {
                $FilePath = "$SavePath\$($($_.name).replace(" ","_")).lua" 
                Remove-Item $FilePath -Force | Out-Null
                [System.IO.File]::WriteAllLines($FilePath, $exts.script.body)
                Write-Verbose "Saved extension to $FilePath"
                # $exts.body | Out-File $SavePath | Out-Null
            }
        }
        Write-Output $exts
    }
}

function New-ICExtension {
    [cmdletbinding()]
    param(
        [parameter(mandatory=$true)]
        [String]$Name,
        
        [Parameter()]
        [String]$Author,

        [Parameter()]
        [String]$Description,

        [Parameter()]
        [ValidateSet(
            "Collection",
            "Response"
        )]
        [String]$Type = "Collection"
    )
    
    $CollectionTemplate = "https://raw.githubusercontent.com/Infocyte/extensions/master/examples/collection_template.lua"
    $ActionTemplate = "https://raw.githubusercontent.com/Infocyte/extensions/master/examples/response_template.lua"

    if ($Type -eq "Collection"){
        $template = (new-object Net.WebClient).DownloadString($CollectionTemplate)
    } else {
        $template = (new-object Net.WebClient).DownloadString($ActionTemplate)
    }
    
    $template = $template -replace '(?si)(?<start>^--\[=\[.+?name\s*=\s*")(?<field>.+?)(?<end>"\n)', "`${start}$Name`${end}"
    $template = $template -replace '(?si)(?<start>^--\[=\[.+?author\s*=\s*")(.+?)(?<end>"\n)', "`${start}$Author`${end}"
    $template = $template -replace '(?si)(?<start>^--\[=\[.+?description\s*=\s*)([^|].+?|\|.+?\|)(?<end>"\n)', "`${start}| $Description |`${end}"
    $template = $template -replace '(?si)(?<start>^--\[=\[.+?guid\s*=\s*)(.+?)(?<end>"\n)', "`${start}$([guid]::NewGuid().guid)`${end}"
    $dt = Get-Date -UFormat "%F"
    $template = $template -replace '(?si)(?<start>^--\[=\[.+?created\s*=\s*)(.+?)(?<end>"\n)',"`${start}$dt`${end}"
    $template = $template -replace '(?si)(?<start>^--\[=\[.+?updated\s*=\s*)(.+?)(?<end>"\n)',"`${start}$dt`${end}"
    
    
    $SavePath = (Resolve-Path ".\").path + "\$($Type)_extension.lua"
    Write-Host "`nCreated $Type extension from template and saved to $SavePath"
    Remove-Item $SavePath -Force | Out-Null
    [System.IO.File]::WriteAllLines($SavePath, $template)
    # $template | Out-File -FilePath $SavePath
    return $template    
}

function Import-ICExtension {
    [cmdletbinding()]
    Param(
        [parameter(
            Mandatory = $true,
            ParameterSetName = 'Path',
            ValueFromPipeline = $true
        )]
        [ValidateNotNullOrEmpty()]
        [string]$Path, # path to extension file

        [parameter(
            mandatory = $true,
            ParameterSetName  = 'String'
        )]
        [ValidateNotNullorEmpty()]
        [String]$Body,

        [Switch]$Active,

        [Switch]$Force
    )

    PROCESS {
        $Endpoint = "extensions"
        $postbody = @{}
        if ($Active) {
            $postbody['active'] = $true
        } else {
            $postbody['active'] = $false
        }

        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            Write-Verbose "Testing path: $Path"
            if (Test-Path $Path) {
                Write-Verbose "Using filename for Extension Name."
                $Body = Get-Content $Path -Raw
            } else {
                Throw "$Path does not exist!"
            }
        }

        $postbody['body'] = $Body
        $header = Parse-ICExtensionHeader -Body $Body
        if (-NOT $header.name -OR -NOT $header.type) { 
            Throw "Extension Header is incomplete or incorrectly formatted. Recommend using a template header" 
        }
        
        $postbody['name'] = $header.name 
        if (($header.type).toLower() -eq "collection") {
            $postbody['type'] = "collection"
        } else {
            $postbody['type'] = "response"
        }
        $postbody['description'] = $header.guid

        if ($header.guid) {
            $ext = Get-ICExtension -where @{ description = $header.guid }
            if ($ext) {
                if (-NOT $Force) {
                    Write-Warning "There is already an existing extension named $($ext.name) [$($ext.Id)] with guid $($ext.description). Try using Update-ICExtension or use -Force flag."
                    return
                }
                Write-Warning "There is already an existing extension named $($ext.name) [$($ext.Id)] with guid $($ext.description). Forcing update."
                $postbody['id'] = $ext.id
            } 
        }
        else {
            Write-Verbose "Adding new Extension named: $($postbody['name'])"
        }
        Invoke-ICAPI -Endpoint $Endpoint -body $postbody -method POST
        $globals = Get-ICAPI -endpoint ExtensionGlobalVariables -nolimit
        $header.globals | where-object { $_.name -notin $globals.name -AND $_.default } | ForEach-Object {
            $varbody = @{
                name = $_.name
                type = $_.type
                value = $_.default
            }
            Invoke-ICAPI -Endpoint ExtensionGlobalVariables -Method POST -body $varbody
        }
    }
}

function Update-ICExtension {
    <#
        Updates an existing extension with a new body from a file or string.
    #>

    [cmdletbinding(SupportsShouldProcess=$true)]
    Param(
        [parameter(
            mandatory=$false,
            ParameterSetName = 'Path'
        )]
        [parameter(
            mandatory=$false,
            ParameterSetName  = 'String'
        )]
        [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })]
        [alias('extensionId')]
        [String]$Id,

        [parameter(
            Mandatory = $true,
            ParameterSetName = 'Path',
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [alias('FullName')]
        [string]$Path, # <paths of the survey results (.bz2) files to upload>

        [parameter(
            mandatory = $true,
            ParameterSetName  = 'String'
        )]
        [ValidateNotNullorEmpty()]
        [String]$Body
    )
    
    PROCESS {
        $Endpoint = "extensions"
        $postbody = @{}

        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            Write-Verbose "Getting Script body from $Path"
            if (Test-Path $Path) {
                $Body = Get-Content $Path -Raw 
            } else {
                Write-Warning "$Path does not exist!"
                return
            }
        }

        $header = Parse-ICExtensionHeader -Body $Body
        Write-Verbose "Extension Header:`n$($header | ConvertTo-Json)"
        $postbody['body'] = $Body
        $postbody['name'] = $header.name
        if ($header.type -match "collection") {
            $postbody['type'] = "collection"
        } else {
            $postbody['type'] = "response"
        }
        $postbody['description'] = $header.guid

        if ($Id) {
            Write-Verbose "Looking up extension by Id"
            $ext = Get-ICExtension -id $Id
            if ($ext) {
                Write-Verbose "Extension found: `n$($ext | ConvertTo-Json)"
                $postbody['id'] = $Id
                $postbody['active'] = $ext.active
                if (-NOT $postbody['name']) { $postbody['name'] = $ext.name }
                if (-NOT $postbody['type']) { $postbody['type'] = $ext.type }
                if (-NOT $postbody['description']) { $postbody['description'] = $ext.description }
                if ($ext.description -AND ($header.guid -ne $ext.description)) {
                    Write-Warning "Extension Guids do not match. Cannot be updated, try importing the new extension!`nCurrent: $($ext.description)`nNew: $($header.guid)"
                    return
                }
            } else {
                Write-Warning "Extension with id $id not found!"
                return
            }
        } 
        else {
            Write-Verbose "Looking up extension by Guid"
            $ext = Get-ICExtension -ea 0 -where @{ description = $header.guid }
            if ($ext) {
                Write-Verbose "Founding existing extension with matching guid $($header.guid). Updating id $($ext.id)"
                $postbody['id'] = $ext.id
                if (-NOT $postbody['name']) { $postbody['name'] = $ext.name }
                if (-NOT $postbody['type']) { $postbody['type'] = $ext.type }
                if (-NOT $postbody['description']) { $postbody['description'] = $ext.description }
            } 
            else {
                Write-Warning "Could not find existing extension with Guid: $($header.guid)"
                return
            }
        }
        Write-Verbose "Updating Extension: $($ext['name']) [$($ext.id)] with `n$($postbody|convertto-json)"
        if ($PSCmdlet.ShouldProcess($($ext.name), "Will update extension $($postbody['name']) [$postbody['id'])]")) {
            Invoke-ICAPI -Endpoint $Endpoint -body $postbody -method POST
        }
    }
}
function Remove-ICExtension {
    [cmdletbinding(DefaultParameterSetName = 'Id', SupportsShouldProcess=$true)]
    Param(
        [parameter(
            Mandatory, 
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Id')]
        [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })]
        [alias('extensionId')]
        [String]$Id
    )

    PROCESS {
        $Endpoint = "extensions/$Id"
        $ext = Get-ICExtension -id $Id
        if (-NOT $ext) {
            Write-Error "Extension with id $id not found!"
            return
        }
        
        if ($PSCmdlet.ShouldProcess($($ext.Id), "Will remove $($ext.name) with extensionId '$($ext.Id)'")) {
            try {
                Invoke-ICAPI -Endpoint $Endpoint -Method DELETE
                Write-Verbose "Removed extension $($ext.name) [$($ext.Id)]"
                $true
            } catch {
                Write-Warning "Extension $($ext.name) [$($ext.Id)] could not be removed!"
            }
        }
    }
}

function Import-ICOfficialExtensions {
    [cmdletbinding()]
    Param(
        [Switch]$IncludeContributed,
        [Switch]$Update
    )

    $InstanceExtensions = Get-ICExtension -NoLimit
    Write-Verbose "Pulling Official Extensions from Github: https://api.github.com/repos/Infocyte/extensions/contents/official/"
    try {
        $Extensions = Invoke-WebRequest -Uri "https://api.github.com/repos/Infocyte/extensions/contents/official/collection" | Select-Object -ExpandProperty content | ConvertFrom-Json
    }
    catch {
        Write-Warning "Could not download extensions from https://api.github.com/repos/Infocyte/extensions/contents/official/collection"
    }
    try {
        $Extensions += Invoke-WebRequest -Uri "https://api.github.com/repos/Infocyte/extensions/contents/official/response" | Select-Object -ExpandProperty content | ConvertFrom-Json
    }
    catch {
        Write-Warning "Could not download extensions from https://api.github.com/repos/Infocyte/extensions/contents/official/response"
    }
    If ($IncludeContributed) {
        Write-Verbose "Pulling Official Extensions from Github: https://api.github.com/repos/Infocyte/extensions/contents/contrib/"
        try {
            $Extensions += Invoke-WebRequest -Uri "https://api.github.com/repos/Infocyte/extensions/contents/contrib/collection" | Select-Object -ExpandProperty content | ConvertFrom-Json
        }
        catch {
            Write-Warning "Could not download extensions from https://api.github.com/repos/Infocyte/extensions/contents/contrib/collection"
        }
        try {
            $Extensions += Invoke-WebRequest -Uri "https://api.github.com/repos/Infocyte/extensions/contents/contrib/response" | Select-Object -ExpandProperty content | ConvertFrom-Json
        }
        catch {
            Write-Warning "Could not download extensions from https://api.github.com/repos/Infocyte/extensions/contents/contrib/response"
        }
    }

    $Extensions | ForEach-Object {
        $filename = ($_.name -split "\.")[0]
        try {
            $ext = (new-object Net.WebClient).DownloadString($_.download_url)
        } catch {
            Write-Warning "Could not download extension. [$_]"
            continue
        }
        try {
            $header = Parse-ICExtensionHeader -Body $ext
        } catch {
            Write-Warning "Could not parse header on $($filename)"; 
            continue
        }       
        $existingExt = $InstanceExtensions | Where-Object { $_.description -eq $header.guid }
        if ($existingExt) {
            if ($Update) {
                Write-Verbose "Updating extension $($header.name) [$($existingExt.id)] with guid $($header.guid):`n$existingExt"
                Update-ICExtension -Id $existingExt.id -Body $ext   
            }
            else {
                Write-Warning "Extension $($header.name) [$($existingExt.id)] with guid $($header.guid) already exists. Try using -Update to update it."
            }
        } else {
            Write-Verbose "Importing extension $($header.name) [$($header.Type)] with guid $($header.guid)"
            Import-ICExtension -Body $ext -Active -Force:$Update
        }
    }
}

# For Extension Developers
function Test-ICExtension {
    [cmdletbinding()]
    [alias("Invoke-ICExtension")]
    param(
        [parameter(mandatory=$true)]
        [String]$Path,
        
        [Object]$Globals,

        [Object]$Arguments
    )

    $LoggingColor = 'DarkCyan'
            
    If ($env:OS -match "windows" -AND (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))) {
        Throw "You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!"
    } elseif ($IsLinux -AND (id -u) -ne 0) {
        Throw "You do not have permissions to run this script!`nPlease re-run this with sudo!"
    }

    if (-NOT (Test-Path $Path)) {
        Throw "$Path not found"
    }
    
    # Clear-Host
    $agentname = "agent.exe"
    if ($IsWindows -OR $env:OS -match "windows") {
        $Devpath = "C:/Program Files/Infocyte/dev"
        $AgentPath = "C:/Program Files/Infocyte/Agent"
    } else {
        $Devpath = "/opt/infocyte/dev"
        $AgentPath = "/opt/infocyte/agent"
    }


    # Check for Agent
    if (Test-Path "$DevPath/$agentname") {     
        $DevVer = (& "$DevPath/$agentname" "--version").split(" ")[2]
    } else {
        New-Item $Devpath -ItemType Directory | Out-Null
        if (Test-Path "$AgentPath/$agentname") {
            $AgentVer = (& "$AgentPath/$agentname" "--version").split(" ")[2]
            Write-Warning "$Devpath/$agentname not found but latest version ($AgentVer) was found within your agent folder ($AgentPath). Copying this over."
            Copy-Item -Path "$AgentPath/$agentname" -Destination "$Devpath" | Out-Null
            $DevVer = (& "$DevPath/$agentname" "--version").split(" ")[2]
        } else {
            Write-Warning "Infocyte Agent not found. Install an Agent or download it into $DevPath"
            return
        }
    }
    # Update Agent
    if (Test-Path "$AgentPath/$agentname") {
        $AgentVer = (& "$AgentPath/$agentname" "--version").split(" ")[2]
        if ($AgentVer -gt $DevVer) {
            Write-Warning "$agentname ($DevVer) has an update: ($AgentVer). Copy '$AgentPath/$agentname' to '$Devpath/$agentname'.`n
                `tRun this command to do so: Copy-Item -Path '$AgentPath/$agentname' -Destination '$Devpath/$agentname'"

        }
    }

    $Path = Get-item $Path | Select-Object -ExpandProperty FullName
    $ext = Get-item $Path | Select-Object -ExpandProperty name
    
    # Check for luacheck
    if ($env:OS -match "windows" -OR $isWindows) {

        if (-NOT (Test-Path "$DevPath/luacheck.exe")) {
            $url = "https://github.com/mpeterv/luacheck/releases/download/0.23.0/luacheck.exe"
            Write-Host -ForegroundColor $LoggingColor "$Devpath/luacheck.exe not found (used for linting). Attempting to download from Github."
            # Download luacheck from Github
            #[Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls13
            $wc = New-Object Net.WebClient
            $wc.Encoding = [System.Text.Encoding]::UTF8
            $wc.UseDefaultCredentials = $true
            try {
                $wc.DownloadFile($URL, "$Devpath/luacheck.exe") | Out-Null
            } catch {
                Write-Warning "Could not download luacheck.exe from $URL."
            }
        }
    }
    else {
        $luacheck = (which luacheck)
        if ($luacheck -match "not found") {
            Write-Warning "luacheck not found (used for linting). Try installing it first"
        }
    }

    # luacheck config setup
    $Config = "$Devpath/.luacheckrc"
    if (-NOT (Test-Path $Config)) {
        $configString = 'globals = { "hunt" }'
        $configString += 'allow_defined = true'
        $configString += 'ignore = { "113", "611", "612", "613", "614", "631" }'
        [System.IO.File]::WriteAllLines($Config, $configString)
    }

    # Run luacheck
    if (($env:OS -match "windows" -OR $isWindows) -AND (Test-Path "$DevPath/luacheck.exe")) {        
        Write-Host -ForegroundColor $LoggingColor "Linting $Path"
        $luacheck = Get-Content $Path | & "$Devpath/luacheck.exe" - --codes --config $Config
        $luacheck | ForEach-Object {
            Write-Host $_
        }
    } 
    elseif ($IsLinux -AND $luacheck -notmatch "not found") {
        Write-Host -ForegroundColor $LoggingColor "Linting $Path"
        $luacheck = Get-Content $Path | luacheck - --codes --config $Config
        $luacheck | ForEach-Object {
            Write-Host $_
        }
    }

    Remove-Item "$Devpath/args.json" -ea 0 | Out-Null
    Remove-Item "$Devpath/globals.json" -ea 0 | Out-Null

    $a = @()
    $a += "--debug"
    $a += "--extension `"$Path`""
    if ($Globals) {
        [System.IO.File]::WriteAllLines("$Devpath/globals.json", ($Globals | ConvertTo-Json))
        $a += "--extension-globals `"$Devpath/globals.json`""
    }
    if ($Arguments) {
        [System.IO.File]::WriteAllLines("$Devpath/args.json", ($Arguments | ConvertTo-Json))
        $a += "--extension-args `"$Devpath/args.json`""
    }
    $a += "survey --no-compress --only-extensions"

    $completedsuccessfully = $false
    $agentOutputRegex = '^\[(?<datestamp>\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}\.\d+\sUTC)\]\[(?<level>.+?)\]\[(?<logtype>.+?)\]\s(?<msg>.+)'
    $color = 'green'

    Write-Host -ForegroundColor $LoggingColor "Executing $ext with $agentname (Version: $DevVer)"
    Write-Host -ForegroundColor $LoggingColor "$Devpath/$agentname $a"
    $psi = New-object System.Diagnostics.ProcessStartInfo
    $psi.CreateNoWindow = $true
    $psi.UseShellExecute = $false
    $psi.RedirectStandardOutput = $true
    $psi.RedirectStandardError = $false
    $psi.FileName = "$Devpath/$agentname"
    $psi.Arguments = $a
    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $psi
    $process.Start() | Out-Null
    Write-Verbose "Args: $($psi.Arguments)"
    #$process.WaitForExit()
    if ($process.HasExited) {
        Write-Warning "Something went wrong on survey running: $Devpath/$agentname $($a.ToString())"
    }
    while ($line -OR -NOT $process.HasExited) {
        $line = $process.StandardOutput.ReadLine()
        if ($line -Match $agentOutputRegex) {
            $AgentOutput = $Matches
            
            if ($AgentOutput.msg -match "System inspections complete") {
                # End
                Write-Verbose "System inspections complete!"
                break
            }
            elseif ($AgentOutput.logtype -eq "agent::survey") {
                Write-Verbose "$line"
            }
            elseif ($AgentOutput.msg -match "Logging initialized") {
                Write-Host -ForegroundColor $LoggingColor "Running Extension..."
            }
            else {
                #Color code output
                Switch ($AgentOutput.level) {
                    "ERROR" { $color = 'Red' }
                    "WARN" { $color = 'DarkYellow' }
                    "DEBUG" { $color = 'Yellow' }
                    "VERBOSE" { $color = 'Yellow' }
                    "TRACE" { $color = 'Yellow' }
                    "INFO" { $color = 'Blue' }
                    default {
                        Write-Warning "[Unknown] $($AgentOutput.msg)"        
                    }
                } 

                if ($AgentOutput.logtype -eq "agent::extensions" -AND $AgentOutput.level -eq "ERROR") {
                    Write-Host -ForegroundColor $color "[$($AgentOutput.level)][$($AgentOutput.logtype)] $($AgentOutput.msg)"
                    $exitError = $true
                }
                else {
                    Write-Host -ForegroundColor $color "[$($AgentOutput.level)] $($AgentOutput.msg)"
                }
            } 
        } else {
            # print and error output
            if ($color -eq 'Red') {
                Write-Host -ForegroundColor Red "[ERROR] $line"
            } else {
                Write-Host -ForegroundColor DarkGray "$line"
            }
        }
    }
    if ($exitError) {
        Write-Warning "Survey did not complete successfully. Address any bugs in the extension and try again."
    }
    -NOT $exitError
}

function Parse-ICExtensionHeader {
    [cmdletbinding(DefaultParameterSetName = 'Body')]
    Param(
        [parameter(
            Mandatory, 
            ValueFromPipeline,
            ParameterSetName = 'Body')]
        [ValidateNotNullOrEmpty()]
        [String]$Body,

        [parameter(
            Mandatory, 
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Path')]
        [ValidateNotNullorEmpty()]
        [String]$Path
    )

    if ($Path) {
        $Body = Get-Content $Path -Raw
        #$reader = New-Object -TypeName System.IO.StreamReader -ArgumentList $Path
    }
    if ($Body -match '(?si)^--\[=\[(?<preamble>.+?)\]=\]') {
        $preamble = $matches.preamble.trim()
    } else {
        Write-Error "Could not parse header (should be a comment section wrapped by --[=[ <header> ]=] )"
        return
    }

    $header = ConvertFrom-Yaml $preamble

    if ($header.filetype -ne "Infocyte Extension") {
        Throw "Incorrect filetype. Not an Infocyte Extension"
    }    
    if ($header.guid -notmatch $GUID_REGEX) { 
        Throw "Incorrect guid format: $($header.guid). Should be a guid of form: $GUID_REGEX.
            Use the following command to generate a new one: [guid]::NewGuid().Guid"
 
    }

    $header.created = [datetime]$header.created
    $header.updated = [datetime]$header.updated    

    $header
}

# SIG # Begin signature block
# MIINFwYJKoZIhvcNAQcCoIINCDCCDQQCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUEtlPdgXFqXueEbeq9H3/PcDj
# UqagggpZMIIFITCCBAmgAwIBAgIQD1SHruUyzkN01AFx5d7oATANBgkqhkiG9w0B
# AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz
# c3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMTExNzAwMDAwMFoXDTIyMTEy
# OTIzNTk1OVowXjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQH
# EwZBdXN0aW4xFjAUBgNVBAoTDUluZm9jeXRlLCBJbmMxFjAUBgNVBAMTDUluZm9j
# eXRlLCBJbmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNUhaEiZbu
# H7Q6oqA0uq0klkstXkdJU3eJukMrrLpxKdtFqtJwFXcSYp5G/WFlwDRJ8v8fisfp
# SiPS1WajFUHe3EWLh2oXjf44eQYVWQ8SqAn2J8dDLNJ5bWY0w7MD2GrSiTwN0Vi9
# X9pJKJDdm7mJo7bSlZ9p7XvNoraSAx/hkODalPSMvCIVAEOZutlzeWyJ4p0DbTDA
# kjQPF4EZ7JqxYXFeItoi0uYZQNEHbBxr+5SG45ziC8vuwyljIbo+mKD/PwT48OQl
# 9cdnI651Hz+r5kL3t48WvxYrAUJ7g8EJyw2uYnVnSroIC3TmUQHeXS6FuqeObuX7
# MqapBKRcTHvxAgMBAAGjggHFMIIBwTAfBgNVHSMEGDAWgBRaxLl7KgqjpepxA8Bg
# +S32ZXUOWDAdBgNVHQ4EFgQUxKDwshqav/aGaGVOFv67CuwSjcYwDgYDVR0PAQH/
# BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGA1UdHwRwMG4wNaAzoDGGL2h0
# dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3MtZzEuY3JsMDWg
# M6Axhi9odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcx
# LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUFBwIBFhxodHRw
# czovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBhAYIKwYBBQUHAQEE
# eDB2MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTgYIKwYB
# BQUHMAKGQmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJB
# c3N1cmVkSURDb2RlU2lnbmluZ0NBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3
# DQEBCwUAA4IBAQByJW5tIcmouIY7tdngPLdEOM4FYqLGf9IjKPMS0s+NeTaP/0hp
# dmNeGFEvMozfgDA/gPFCUaRVJwy4rKsGnCznCE1YDA6UFDGZq3VLUbzC6GDP4aY8
# EbfDMbF54TVuOKRue9a6KnVE67gOj+g862qAR6fm/GdeO/KrdvCT1A7xbyg02cCq
# +QgdkYoxI3bsiUwgZ33I2rn2T2zSp8C+RX2bZ8rgtXHxgYLCJdayqMptRsPbxOlQ
# Z7dRhkQXg5D/PyUnpWASF+sLQQ0IMvx8ZKy/P01IhKU0pTJ8OFSYKwPLQnYm1Zp0
# JT/IXZ/tzmtY/StdhaCs3LlOkuHxl2iERxdtMIIFMDCCBBigAwIBAgIQBAkYG1/V
# u2Z1U0O1b5VQCDANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UE
# ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD
# VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAw
# WhcNMjgxMDIyMTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl
# cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdp
# Q2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG
# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/
# 5aid2zLXcep2nQUut4/6kkPApfmJ1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH
# 03sjlOSRI5aQd4L5oYQjZhJUM1B0sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxK
# hwjfDPXiTWAYvqrEsq5wMWYzcT6scKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr
# /mzLfnQ5Ng2Q7+S1TqSp6moKq4TzrGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi
# 6CxR93O8vYWxYoNzQYIH5DiLanMg0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCC
# AckwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAww
# CgYIKwYBBQUHAwMweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6
# MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1s
# AAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw
# CgYIYIZIAYb9bAMwHQYDVR0OBBYEFFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1Ud
# IwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+
# 7A1aJLPzItEVyCx8JSl2qB1dHC06GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbR
# knUPUbRupY5a4l4kgU4QpO4/cY5jDhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7
# uq+1UcKNJK4kxscnKqEpKBo6cSgCPC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7
# qPjFEmifz0DLQESlE/DmZAwlCEIysjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPa
# s7CM1ekN3fYBIM6ZMWM9CBoYs4GbT8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR
# 6mhsRDKyZqHnGKSaZFHvMYICKDCCAiQCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTAT
# BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEx
# MC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBD
# QQIQD1SHruUyzkN01AFx5d7oATAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEK
# MAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3
# AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUOIEsCjQQ0NG7lJR4
# /CxZyQI7FJMwDQYJKoZIhvcNAQEBBQAEggEAhGKGdZsOzDxF/hgHdR9pjhjtnKLQ
# lGW4G01xDv0eleI8tjm1w2leu4PQViLoW/Lj4BRQ+pk4b9ZN4/6mudph7ph9DECT
# zzqsOqk+G/ykdZjQequlbkLZiL8HoeAwD4haA8osrROXjisTF7hF1zM+DEj+jbJy
# CWpZfcGAjPOCYcLyF/CgVtB8BwkJh1tyb7fEjkGmwGzyR3SoPQID1yA0PKpPiyCi
# 285jNhkjZk63Nm7yXI21S1+kXLvC9vRhBYeTjhY7llCRjymXkQrC6f7huM1FQT/x
# r840kpZVnU+WDL69G+J9q5hZilwgN5GfC66EzPsjWW1DUjew3YHirie+BA==
# SIG # End signature block