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(
            ParameterSetName='Id',
            HelpMessage = "Filepath and name to save extension to. Recommending ending as .lua")]
        [ValidateScript( { Test-Path -Path $_ -IsValid })]
        [String]$SavePath,

        [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.info
            } 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
        
        if ($SavePath) {
            $exts.body | Out-File $SavePath | Out-Null
        }
        $exts
    }
}

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

        [Parameter()]
        [String]$Description,

        [Parameter()]
        [ValidateSet(
          "Collection",
          "Response"
        )]
        [String]$Type = "Collection",

        [Parameter(HelpMessage="Filepath and name to save new extension to. Recommending ending as .lua")]
        [ValidateScript({ Test-Path -Path $_ -IsValid })]
        [String]$SavePath
    )
    
    $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}"
    
    
    if ($SavePath) {
        Write-Host "`nCreated $Type extension from template and saved to $SavePath"
        $template | Out-File -FilePath $SavePath
        return $true
    }
    else {
        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.info.name -OR -NOT $header.info.type) { 
            Throw "Extension Header is incomplete or incorrectly formatted. Recommend using a template header" 
        }
        
        $postbody['name'] = $header.info.name 
        if (($header.info.type).toLower() -eq "collection") {
            $postbody['type'] = "collection"
        } else {
            $postbody['type'] = "response"
        }
        $postbody['description'] = $header.info.guid

        if ($header.info.guid) {
            $ext = Get-ICExtension -where @{ description = $header.info.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.info.name
        if ($header.info.type -match "collection") {
            $postbody['type'] = "collection"
        } else {
            $postbody['type'] = "response"
        }
        $postbody['description'] = $header.info.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.info.guid -ne $ext.description)) {
                    Write-Warning "Extension Guids do not match. Cannot be updated, try importing the new extension!`nCurrent: $($ext.description)`nNew: $($header.info.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.info.guid }
            if ($ext) {
                Write-Verbose "Founding existing extension with matching guid $($header.info.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.info.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"
        }
    }
    $Results = @()
    $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.info.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
        } 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
    

    if (($env:OS -match "windows" -OR $isWindows) -AND (-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."
        }
    }
    if (($env:OS -match "windows" -OR $isWindows) -AND (Test-Path "$DevPath/luacheck.exe")) {
        $Config = "C:\Program Files\Infocyte\dev\.luacheckrc"
        if (-NOT (Test-Path $Config)) {
            'globals = { "hunt" }' > $Config
            'allow_defined = true' >> $Config
            'ignore = { "611", "612", "613", "614" }' >> $Config
        }
        Write-Host -ForegroundColor $LoggingColor "Linting $Path"
        $luacheck = Get-Content $Path | & "$Devpath\luacheck.exe" - --codes --config "C:\Program Files\Infocyte\dev\.luacheckrc"
        $luacheck | ForEach-Object {
            Write-Host $_
        }
    }

    $a = @()
    $a += "--debug"
    $a += "--extension `"$Path`""
    if ($Globals) {
        $Globals | ConvertTo-Json | Out-File -Force "$Devpath/globals.json" | Out-Null
        $a += "--extension-globals `"$Devpath/globals.json`""
    }
    if ($Arguments) {
        $Arguments | ConvertTo-Json | Out-File -Force "$Devpath/args.json" | Out-Null
        $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 "[PRINT] $line"
            }
        }
    }
    if ($exitError) {
        Write-Warning "Survey did not complete successfully. Address any bugs in the extension and try again."
    }
    #Remove-Item "$Devpath/args.json" | Out-Null
    #Remove-Item "$Devpath/globals.json" | Out-Null
    -NOT $exitError
}

Add-Type -Path "$PSScriptRoot\lib\tommy.dll"
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
    }

    $reader = [System.IO.StringReader]::new($preamble)
    try {
        [Tommy.TomlTable]$table = [Tommy.TOML]::Parse($reader)
    } catch {
        Throw "Improper header format. Should be a TOML file: $($_)"
    }
    try {
        $header = $table.ToString().replace(" =", ":").replace("`"`"`"", "`"").replace("'''", "'") | ConvertFrom-Json
    } catch {
        Throw "TOML header could not be converted to/from JSON: $($_)"
    }
    if ($header.filetype -ne "Infocyte Extension") {
        Throw "Incorrect filetype. Not an Infocyte Extension"
    }    
    if ($header.info.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.info.created = [datetime]$header.info.created
    $header.info.updated = [datetime]$header.info.updated    

    $header
}

# SIG # Begin signature block
# MIINOAYJKoZIhvcNAQcCoIINKTCCDSUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUjV19y+vNTAEUjOevFeeDXn6y
# Gwqgggp2MIIFHzCCBAegAwIBAgIQA7ShIT20JORyIag/jWOSyzANBgkqhkiG9w0B
# AQsFADB2MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMTUwMwYDVQQDEyxEaWdpQ2VydCBTSEEyIEhp
# Z2ggQXNzdXJhbmNlIENvZGUgU2lnbmluZyBDQTAeFw0xODA5MTIwMDAwMDBaFw0y
# MDExMTgxMjAwMDBaMF4xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVUZXhhczEPMA0G
# A1UEBxMGQXVzdGluMRYwFAYDVQQKEw1JbmZvY3l0ZSwgSW5jMRYwFAYDVQQDEw1J
# bmZvY3l0ZSwgSW5jMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApzcR
# GuszlupWLdmtti4glKsr6SS2sp370yioc2XyIwPzU/etsBQa41x6VO7HXjtSXSry
# p3SYaoLPQmBiAzKjDP6dzu0l7cQFbwPMky3SGqrC3Wr+Kw/qoMgn3wKBxzPJ53Gj
# s1oxNwyz2N7FwN977vErW9C/FgM/XuE7Zde/HGl3oxTJNtY++BG2Ri3rwi5hNbzV
# 5+avrJFW1DzHVBXYxbrE9vNy4V6s7dlZT2xZoJ3AtHoBCUMgHRKii3wHgFRaxiuz
# 6XzlvHzmnh02KUfoV6cX++bP4bRtsJjmvrfJV+Mhlh/MhUidhhQQx0spLIfxv+vZ
# OACP5jLm0g2fj4G4VQIDAQABo4IBvzCCAbswHwYDVR0jBBgwFoAUZ50PIAkMzIo6
# 5YJGcmL88cyQ5UAwHQYDVR0OBBYEFBqi6MjBKip4kQYxVCjC7yOrUHWFMA4GA1Ud
# DwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzBtBgNVHR8EZjBkMDCgLqAs
# hipodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1jcy1nMS5jcmwwMKAu
# oCyGKmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWhhLWNzLWcxLmNybDBM
# BgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3
# dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBiAYIKwYBBQUHAQEEfDB6MCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wUgYIKwYBBQUHMAKG
# Rmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJIaWdoQXNz
# dXJhbmNlQ29kZVNpZ25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B
# AQsFAAOCAQEABnQP0mJYXNzfz4pMCc4FbQ9qe8NjloGuIlUgXaxlFAoMKZWMueXq
# mciPWULaE+Wd5ChTuNsrWKG7bWYYWmmo7C1RWhdZhQT/C+k3lFcsSr6gdXAXiOmv
# 3xv3d3oqfNe19G6jt6akQ6LjEauRw4xKinoK/S61Pw9c1KtEAGT8djgX74h433fy
# FPiQd//ePnihKN+GXRCeLvSaDGuVrhHuI6UUhe3MK2/Nb8MzFddwkOOdpky1HBn4
# 8oFEAOzbrTVTTv4BWLNRvAiY8UO3D2Kt322UuAdXIKNxWB94UaFt2jg2QsRkTHGQ
# MmbQ8OgMIWWNcE9RcVKuobYbzUAGPoMimTCCBU8wggQ3oAMCAQICEAt+EJA8OEkP
# +i9nmoehp7kwDQYJKoZIhvcNAQELBQAwbDELMAkGA1UEBhMCVVMxFTATBgNVBAoT
# DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UE
# AxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTAeFw0xMzEwMjIx
# MjAwMDBaFw0yODEwMjIxMjAwMDBaMHYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE
# aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xNTAzBgNVBAMT
# LERpZ2lDZXJ0IFNIQTIgSGlnaCBBc3N1cmFuY2UgQ29kZSBTaWduaW5nIENBMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtEpefQcPQd7E9XYWNr1x/88/
# T3NLnNEN/krLV1hehRbdAhVUmfCPPC9NAngQaMjYNUs/wfdnzpgcrjO5LR2kClST
# xIWi3zWx9fE8p7M0+11IyUbJYkS8SJnrKElTwz2PwA7eNZjpYlHfPWtAYe4EQdrP
# p1xWltH5TLdEhIeYaeWCuRPmVb/IknCSCjFvf4syq89rWp9ixD7uvu1ZpFN/C/FS
# iIp7Cmcky5DN7NJNNEyw4bWfnMb2byzN5spTdAGfZzXeOEktzu05RIIZeU4asrX7
# u3jwSWanz/pclnWSixpy2f9QklPMPsJDMgkahhNpPPuBMjMyZHVzKCYdCDA7BwID
# AQABo4IB4TCCAd0wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYw
# EwYDVR0lBAwwCgYIKwYBBQUHAwMwfwYIKwYBBQUHAQEEczBxMCQGCCsGAQUFBzAB
# hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYIKwYBBQUHMAKGPWh0dHA6Ly9j
# YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RD
# QS5jcnQwgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9v
# dENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYc
# aHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4E
# FgQUZ50PIAkMzIo65YJGcmL88cyQ5UAwHwYDVR0jBBgwFoAUsT7DaQP4v0cB1Jgm
# GggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBAGoO/34TfAalS8AujPlTZAniuliR
# MFDszJ/h06gvSEY2GCnQeChfmFZADx66vbE7h1zcW9ggDe0aFk3VESQhS/EnaZAT
# 6xGhAdr9tU55WXW9OCpqw/aOQSuKoovXLFFR2ZygyONOumyoR9JO0WgfjAJXO7Mp
# ao5qICq58gBiZLrI6QD5zKTUupo12K8sZWwWfFgh3kow0PrrJF0GyZ0Wt61KRdMl
# 4gzwQKpcTax+zQaCuXZGaQjYMraC/uOpWDRDG45nZ5c/aDEWNjiVPof3x8OvnXp3
# Gdnek7X9biv8lPk9t0wSNSwwvuiNngVwmkgT9IzW5x6sOOeo860Mt3rsZ+0xggIs
# MIICKAIBATCBijB2MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTUwMwYDVQQDEyxEaWdpQ2VydCBT
# SEEyIEhpZ2ggQXNzdXJhbmNlIENvZGUgU2lnbmluZyBDQQIQA7ShIT20JORyIag/
# jWOSyzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkq
# hkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGC
# NwIBFTAjBgkqhkiG9w0BCQQxFgQUKGeysnsLD0b0Y+ZBtFg4Q77yvHIwDQYJKoZI
# hvcNAQEBBQAEggEAMAxsXruSC8WzfIyKmnkMnGTvBoZr7J488P1VgRKJ3VpJptZk
# CuQzizWmxcQt5YzNq6c46q3C+333wRCZTxBoHbmIsWSdphaEZjhVn0dwtyPuGH3q
# lyOH7ZgnP41QPGyKx3k+Hu0f5qzt5hgTyxf1w7zg/q2Yx9d+jaUSiSFYXm1xFLi4
# uBpXVfLM6I9hObXFuyopmN85QOoB7z221oDCTGCB+nJ37NMp36xVk1I4E/drVGNG
# dQhXEik9Bexi5KVMTzTD8PGrWW0ZDqEOSroHIVMO11w5MPcFZxcvU9CI1t1IWyf6
# ZY6J9gSVMYbOjr5o9BkQJzGWQrY2qQYIxNtVjg==
# SIG # End signature block