lib/Actions.ps1



# Actions
Function New-TMAction {
    param(
        [Parameter(Mandatory = $false)][String]$TMSession = "Default",
        [Parameter(Mandatory = $true)][PSObject]$Action,
        [Parameter(Mandatory = $false)][String]$Server = $global:TMSessions[$TMSession].TMServer,
        [Parameter(Mandatory = $false)]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL,
        [Parameter(Mandatory = $false)][PSObject]$Project,
        [Parameter(Mandatory = $false)][Switch]$Update,
        [Parameter(Mandatory = $false)][Switch]$Passthru
    )

    ## Use the TM Session provided
    if ($global:TMSessions[$TMSession].TMVersion -like '4.6*') {
        New-TMAction46 @PSBoundParameters

    }
    else {
        New-TMAction474 @PSBoundParameters
    }
}
Function New-TMAction46 {
    param(
        [Parameter(Mandatory = $false)][String]$TMSession = "Default",
        [Parameter(Mandatory = $true)][PSObject]$Action,
        [Parameter(Mandatory = $false)][String]$Server = $global:TMSessions[$TMSession].TMServer,
        [Parameter(Mandatory = $false)]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL,
        [Parameter(Mandatory = $false)][PSObject]$Project,
        [Parameter(Mandatory = $false)][Switch]$Passthru

    )
    ## Get Session Configuration
    $TMSessionConfig = $global:TMSessions[$TMSession]
    if (-not $TMSessionConfig) {
        Write-Host 'TMSession: [' -NoNewline
        Write-Host $TMSession -ForegroundColor Cyan
        Write-Host '] was not Found. Please use the New-TMSession command.'
        Throw "TM Session Not Found. Use New-TMSession command before using features."
    }

    #Honor SSL Settings
    if ($TMSessionConfig.AllowInsecureSSL) {
        $TMCertSettings = @{SkipCertificateCheck = $true }
    }
    else {
        $TMCertSettings = @{SkipCertificateCheck = $false }
    }

    ## Check for existing Action
    $ActionCheck = Get-TMAction -Name $Action.name -TMSession $TMSession
    if ($ActionCheck) {
        if ($Passthru) {
            return $ActionCheck
        }
        else {
            return
        }
    }
    else {
        ## No Credential exists. Create it
        $instance = $Server.Replace('/tdstm', '')
        $instance = $instance.Replace('https://', '')
        $instance = $instance.Replace('http://', '')

        $uri = "https://"
        $uri += $instance
        $uri += '/tdstm/ws/apiAction'

        ## Lookup Cross References
        if (-not $Project) {
            $Project = $Projects | Where-Object { $_.name -eq $Action.project.name }
        }
        $ProjectID = $Project.id

        $ProviderID = (Get-TMProvider -TMSession $TMSession | Where-Object { $_.name -eq $Action.provider.name }).id
        $CredentialID = (Get-TMCredential -TMSession $TMSession | Where-Object { $_.name -eq $Action.credential.name }).id

        ## Fix up the object
        $Action.PSObject.properties.Remove('id')
        $Action.actionType = $Action.actionType.id
        $Action.project.id = $ProjectID
        $Action.provider.id = $ProviderID
        $Action.credential.id = $CredentialID
        $Action.version = 1
        $PostBodyJSON = $Action | ConvertTo-Json -Depth 100

        Set-TMHeaderAccept "JSON" -TMSession $TMSession
        Set-TMHeaderContentType "JSON" -TMSession $TMSession
        try {
            $response = Invoke-WebRequest -Method Post -Uri $uri -WebSession $TMSessionConfig.TMWebSession -Body $PostBodyJSON @TMCertSettings
            if ($response.StatusCode -eq 200) {
                $responseContent = $response.Content | ConvertFrom-Json
                if ($responseContent.status -eq "success") {
                    if ($Passthru) {
                        return $responseContent.data
                    }
                    else {
                        return
                    }
                }
            }
        }
        catch {
            Write-Host "Unable to create Action."
            return $_
        }
    }
}
Function New-TMAction474 {
    param(
        [Parameter(Mandatory = $false)][String]$TMSession = "Default",
        [Parameter(Mandatory = $true)][PSObject]$Action,
        [Parameter(Mandatory = $false)][String]$Server = $global:TMSessions[$TMSession].TMServer,
        [Parameter(Mandatory = $false)]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL,
        [Parameter(Mandatory = $false)][PSObject]$Project,
        [Parameter(Mandatory = $false)][Switch]$Update,
        [Parameter(Mandatory = $false)][Switch]$Passthru
    )
    ## Get Session Configuration
    $TMSessionConfig = $global:TMSessions[$TMSession]
    if (-not $TMSessionConfig) {
        Write-Host 'TMSession: [' -NoNewline
        Write-Host $TMSession -ForegroundColor Cyan
        Write-Host '] was not Found. Please use the New-TMSession command.'
        Throw "TM Session Not Found. Use New-TMSession command before using features."
    }

    #Honor SSL Settings
    if ($TMSessionConfig.AllowInsecureSSL) {
        $TMCertSettings = @{SkipCertificateCheck = $true }
    }
    else {
        $TMCertSettings = @{SkipCertificateCheck = $false }
    }

    ## Check for existing Action
    $ActionCheck = Get-TMAction -Name $Action.name -TMSession $TMSession
    if ($ActionCheck -and -not $Update) {
        if ($PassThru) {
            return $ActionCheck
        }
        else {
            return
        }
    }
    else {


        ## No Credential exists. Create it
        $instance = $Server.Replace('/tdstm', '')
        $instance = $instance.Replace('https://', '')
        $instance = $instance.Replace('http://', '')

        $uri = "https://"
        $uri += $instance
        $uri += '/tdstm/ws/apiAction'

        ## Cleans the Action Name of characters we don't want
        $Action.name = $Action.name -replace "\\", '' -replace "\/", '' -replace "\:", '' -replace ">", '' -replace "<", '' -replace '\(', '' -replace '\)', '' -replace '\*', ''
        # $Action.name = $SafeActionName

        ## If The Existing action should be updated
        if ($ActionCheck -and $Update) {

            ## When the Action is an update, use important details from the current object.
            $Action.id = $ActionCheck.id
            $Action.dateCreated = $ActionCheck.dateCreated
            $Action.lastUpdated = $ActionCheck.lastUpdated
            # $Action.debugEnabled = $ActionCheck.debugEnabled
            $Action.version = $ActionCheck.version
            $action.project = $ActionCheck.project


            ## Set the HTTP call details
            $uri += '/' + $Action.id
            $HttpMethod = 'Put'
        }
        else {

            ## Process as a new object.
            $HttpMethod = 'Post'
            $Action.PSObject.Properties.Remove('id')
        }

        ## Lookup Provider and Credential IDs, Field Specs
        if ([String]::IsNullOrWhiteSpace($Action.provider.name)) {
            Write-Error -Message "Provider name is blank for Action: $($Action.name)"
            return
        }
        $ProviderID = (Get-TMProvider -Name $Action.provider.name -TMSession $TMSession).id
        if (!$ProviderID) {
            # Create the provider if it doesn't exist
            $NowFormatted = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ' -AsUTC).ToString()
            $Provider = [PSCustomObject]@{
                id          = $null
                name        = $Action.provider.name
                description = ""
                comment     = ""
                dateCreated = $NowFormatted
                lastUpdated = $NowFormatted
            }
            $ProviderID = (New-TMProvider -Provider $Provider -PassThru -TMSession $TMSession).id
        }
        $Action.provider.id = $ProviderID

        ## If the Credential name is provided, look up the proper credential and add the ID
        if ($Action.credential) {
            if ($Action.credential.name) {
                $CredentialID = (Get-TMCredential -Name $Action.credential.name -TMSession $TMSession ).id
                if ($CredentialID) { $Action.credential.id = $CredentialID }
                $Action.remoteCredentialMethod = 'SUPPLIED'
            }
            else {
                $Action.credential = $null
                $Action.remoteCredentialMethod = 'USER_PRIV'
            }
        }

        $FieldSettings = Get-TMFieldSpecs -TMSession $TMSession

        ## Fix the Parameters to the correct custom field name
        if ($Action.methodParams) {

            ## Unwrap
            if ($Action.methodParams.GetType().Name -eq 'String') {
                $Parameters = $Action.methodParams | ConvertFrom-Json
            }
            else {
                $Parameters = $Action.methodParams
            }

            for ($j = 0; $j -lt $Parameters.Count; $j++) {
                if ($Parameters[$j].context -in @('DEVICE', 'APPLICATION', 'STORAGE', 'DATABASE')) {

                    ## Check if a fieldLabel param exists in the Parameters[$j].
                    ## This is added by the New-TMIntegrationPlugin command so the correct
                    ## Custom fields can be assigned when the new Action is installed.
                    if ($Parameters[$j].PSobject.Properties.name -match 'fieldLabel') {

                        ## Update the Project's assigned Field by associating the label to the current field list.
                        $Parameters[$j].fieldName = ($FieldSettings.($Parameters[$j].context).fields | Where-Object { $_.label -eq $Parameters[$j].fieldLabel }).field

                        ## Remove the 'fieldLabel property as it's invalid column definition
                        $Parameters[$j].PSObject.Properties.Remove('fieldLabel')
                    }
                }
            }
            $Action.methodParams = (ConvertTo-Array -InputObject $Parameters | ConvertTo-Json -Depth 100 -Compress ).toString()
        }

        $PostBodyJSON = $Action | ConvertTo-Json

        Set-TMHeaderAccept "JSON" -TMSession $TMSession
        Set-TMHeaderContentType "JSON" -TMSession $TMSession
        try {
            $response = Invoke-WebRequest -Method $HttpMethod -Uri $uri -WebSession $TMSessionConfig.TMWebSession -Body $PostBodyJSON @TMCertSettings
            if ($response.StatusCode -eq 200) {
                $responseContent = $response.Content | ConvertFrom-Json
                if ($responseContent.status -eq "success") {
                    if ($PassThru) {
                        return $responseContent.data
                    }
                }
            }
            elseif ($response.StatusCode -eq 204) {
                return
            }
        }
        catch {
            Write-Host "Unable to create Action."
            return $_
        }
    }

}
Function Get-TMAction {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(Mandatory = $false)]
        [String]$TMSession = "Default",

        [Parameter(Mandatory = $false, ParameterSetName = 'ByName')]
        [String[]]$Name,

        [Parameter(Mandatory = $false, ParameterSetName = 'ById')]
        [String[]]$Id,

        [Parameter(Mandatory = $false)]
        [String[]]$ProviderName,

        [Parameter(Mandatory = $false)]
        [String]$Server = $global:TMSessions[$TMSession].TMServer,

        [Parameter(Mandatory = $false)]
        [Bool]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL,

        [Parameter(Mandatory = $false)]
        [Switch]$ResetIDs,

        [Parameter(Mandatory = $false)]
        [String]$SaveCodePath,

        [Parameter(Mandatory = $false)]
        [Switch]$Passthru
    )

    ## Get Session Configuration
    $TMSessionConfig = $global:TMSessions[$TMSession]
    if (-not $TMSessionConfig) {
        Write-Host 'TMSession: [' -NoNewline
        Write-Host $TMSession -ForegroundColor Cyan
        Write-Host '] was not Found. Please use the New-TMSession command.'
        Throw "TM Session Not Found. Use New-TMSession command before using features."
    }

    #Honor SSL Settings
    $TMCertSettings = $TMSessionConfig.AllowInsecureSSL ? @{SkipCertificateCheck = $true } : @{SkipCertificateCheck = $false }

    # Format the uri
    $instance = $Server.Replace('/tdstm', '').Replace('https://', '').Replace('http://', '')
    $uri = "https://$instance/tdstm/ws/apiAction"

    try {
        $response = Invoke-WebRequest -Method Get -Uri $uri -WebSession $TMSessionConfig.TMWebSession @TMCertSettings
    }
    catch {
        throw $_
    }

    if ($response.StatusCode -in @(200, 204)) {
        $Result = ($response.Content | ConvertFrom-Json).data
        $Result = $Result | Where-Object actionType -EQ 'POWER_SHELL'
    }
    else {
        throw "Unable to collect Actions."
    }

    ## Return the details -- Filter based on passed parameters
    if ($ProviderName) {
        $Result = $Result | Where-Object { $_.provider.name -in $ProviderName }
    }
    elseif ($Name) {
        $Result = $Result | Where-Object { $_.name -in $Name }
    }
    elseif ($Id) {
        $Result = $Result | Where-Object { $_.id -in $Id }
    }

    # Get Field / Label maps to translate the parameters
    $FieldToLabelMap = Get-TMFieldToLabelMap -Server $Server -TMSession $TMSession -AllowInsecureSSL $AllowInsecureSSL

    for ($i = 0; $i -lt $Result.Count; $i++) {

        ## Fix the Parameters to the correct custom field name
        if ($Result[$i].methodParams) {
            $Parameters = $Result[$i].methodParams | ConvertFrom-Json
            $Parameters = $Parameters | Sort-Object -Property 'paramName'
            for ($j = 0; $j -lt $Parameters.Count - 1; $j++) {
                if ($Parameters[$j].context -in @('DEVICE', 'APPLICATION', 'STORAGE', 'DATABASE')) {

                    ## The custom column field identifer is lost when the IDs get reset. Add a fieldLabel node to put the custom field label. This will get replaced/updated by the Import
                    $FieldLabel = $FieldToLabelMap."$($Parameters[$j].context)"."$($Parameters[$j].fieldName)"
                    $Parameters[$j] | Add-Member -NotePropertyName 'fieldLabel' -NotePropertyValue $FieldLabel -Force

                    ## Clear the Custom field number associated to the Parameter
                    if ($ResetIDs -and ($Parameters[$j].fieldName -like "custom*")) {
                        $Parameters[$j].fieldName = 'customN'
                    }
                }
            }
            $Result[$i].methodParams = $Parameters
        }
    }

    if ($ResetIDs) {
        ## Clear pertinent data in each Action
        for ($i = 0; $i -lt $Result.Count; $i++) {
            $Result[$i].id = $null
            $Result[$i].project.id = $null
            if ($Result[$i].credential) { $Result[$i].credential.id = $null }
            $Result[$i].provider.id = $null
        }
    }


    ## Save the Code Files to a folder
    if ($SaveCodePath) {

        ## Save Each of the Script Source Data
        foreach ($Item in $Result) {

            ## Get a FileName safe version of the Provider Name
            $SafeProviderName = Get-FilenameSafeString $Item.provider.name
            $SafeActionName = Get-FilenameSafeString $Item.name

            ## Create the Provider Action Folder path
            $ProviderPath = Join-Path $SaveCodePath $SafeProviderName
            Test-FolderPath -FolderPath $ProviderPath

            ## Create a File ame for the Action
            $ProviderScriptConfigPath = Join-Path $ProviderPath ($SafeActionName + '.TMAction.json')
            $ProviderScriptPath = Join-Path $ProviderPath ($SafeActionName + '.TMAction.ps1')

            # ## Collect the Script into a Script Block
            # try {

            # $Script = [ScriptBlock]::Create($Item.script)
            # }
            # catch {
            # Write-Host 'There was an error converting the Script for action ' -NoNewline
            # Write-Host $Item.Name -ForegroundColor Magenta
            # throw $_.Exception.Message
            # }


            # ## Build a config of the important References
            # $TMConfig = [PSCustomObject]@{
            # ActionName = $Item.name
            # ProviderName = $Item.provider.name
            # Credential = $Item.credential.name
            # } | ConvertTo-Json | Out-String

            # ## Collect the Parameters
            # $Parameters = $Item.methodParams ? ($Item.methodParams | ConvertFrom-Json) : $null

            # ## Create a Script String output
            # ## Note - Indentations don't look correct here, but they produce good looking code. Don't adjust.
            # $ScriptOutput = [System.Text.StringBuilder]::new()
            # [void]$ScriptOutput.AppendLine("<####### TransitionManager Action Script ######")
            # [void]$ScriptOutput.AppendLine()
            # [void]$ScriptOutput.AppendLine(" ActionName = $SafeActionName ")
            # [void]$ScriptOutput.AppendLine(" ProviderName = $SafeProviderName ")
            # [void]$ScriptOutput.AppendLine((" CredentialName = " + $TMConfig.Credential))
            # [void]$ScriptOutput.AppendLine()
            # [void]$ScriptOutput.AppendLine((" Description = " + $Item.description ))
            # [void]$ScriptOutput.AppendLine('#>')
            # [void]$ScriptOutput.AppendLine()

            # if ($Parameters) {
            # ## Open a Parameters Array
            # [void]$ScriptOutput.AppendLine("## Parameter Configuration")
            # [void]$ScriptOutput.AppendLine('$Params = @{')

            # for ($i = 0; $i -lt $Parameters.Count; $i++) {
            # ## Get the singular Param
            # $Parameter = $Parameters[$i]
            # $ParameterName = $Parameter.paramName -replace ' ', ''

            # ## Add Strings to the Parameter Name if it has any offending (non-word) characters
            # if ($ParameterName -match '\W') {
            # $ParameterName = "`"" + $ParameterName + "`""
            # }

            # ## Add the Param Object Header
            # [void]$ScriptOutput.AppendLine((" " + $ParameterName + " = @{"))

            # [void]$ScriptOutput.AppendLine((" Context = `"" + $Parameter.context + "`""))


            # ## Add Conditional Fields
            # if ($Parameter.fieldLabel) {
            # [void]$ScriptOutput.AppendLine((" FieldLabel = `"" + $Parameter.fieldLabel + "`""))
            # }

            # if ($Parameter.context -eq 'USER_DEF') {
            # [void]$ScriptOutput.AppendLine((" Value = @`"" ))
            # [void]$ScriptOutput.AppendLine($Parameter.value)
            # [void]$ScriptOutput.AppendLine("`"@")
            # }

            # ## Write the param details that are always present
            # if (![String]::IsNullOrWhiteSpace($Parameter.description)) {
            # [void]$ScriptOutput.AppendLine((" Description = @`"" ))
            # [void]$ScriptOutput.AppendLine($Parameter.description)
            # [void]$ScriptOutput.AppendLine("`"@")
            # }
            # else {
            # [void]$ScriptOutput.AppendLine((" Description = `"`""))
            # }

            # ## Close the Parameter Object
            # [void]$ScriptOutput.AppendLine(' }')
            # }

            # ## Close the Parameters Object
            # [void]$ScriptOutput.AppendLine('}')

            # }

            # ## Write the Configuration Footer
            # [void]$ScriptOutput.AppendLine('## End of TM Configuration, Begin Script')

            # [void]$ScriptOutput.AppendLine()

            # ## Write the Script to the Configuration
            # [void]$ScriptOutput.AppendLine($Script)
            # [void]$ScriptOutput.AppendLine()

            ## Split the script out of the Item, and save both files.
            $ScriptBlock = $Item.script
            Set-Content -Path $ProviderScriptPath -Force -Value $ScriptBlock
            $Item.PSObject.Properties.Remove('script')
            Set-Content -Path $ProviderScriptConfigPath -Force -Value ($Item | ConvertTo-Json -Depth 10)
        }
    }

    if ($Passthru -or !$SaveCodePath) {
        ## Return a single value, or an Array
        if ($Result.Count -eq 1) {
            return $Result[0]
        }
        else {
            return $Result
        }
    }
}

Function Read-TMActionScriptFile {
    param(
        [Parameter(Mandatory = $true)]$Path
    )

    ## First order of business is to determine if this Script file has a counterpart Json file
    $ActionConfigJsonPath = $Path -Replace '.TMAction.ps1', '.TMAction.json'
    $ActionConfigFile = Get-Item -Path $ActionConfigJsonPath -ErrorAction 'SilentlyContinue'

    ## Check if there is a config JSON file, if so, get the Action from the 2 files
    if ($ActionConfigFile) {

        ## Get the Action Object that doesn't have the source code
        $TMAction = Get-Content -Path $ActionConfigFile | ConvertFrom-Json

        ## Convert the methodParams to a String, as it should be
        if ($TMAction?.methodParams?.GetType().Name -eq 'Object[]') {
            $TMAction.methodParams = $TMAction.methodParams | ConvertTo-Json -Compress -Depth 10
        }

        Add-Member -InputObject $TMAction -NotePropertyName script -NotePropertyValue ( Get-Content -Path $Path -Raw)
    }

    ## Perform a read of the PS file to capture the ReferenceDesign formatted settings
    else {
        ## Name the Input File
        $Content = Get-Content -Path $Path -Raw

        ## Ignore Empty Files
        if (-Not $Content) {
            return
        }

        $ContentLines = Get-Content -Path $Path

        ## Create Automation Token Variables Parse the Script File
        New-Variable astTokens -Force
        New-Variable astErr -Force
        $ast = [System.Management.Automation.Language.Parser]::ParseInput($Content, [ref]$astTokens, [ref]$astErr)

        ##
        ## Assess the Script Parts to get delineating line numbers
        ##

        ## Locate the Delimiting line
        $ConfigBlockEndLine = $astTokens | `
            Where-Object { $_.Text -like '## End of TM Configuration, Begin Script*' } |`
            Select-Object -First 1 | `
            Select-Object -ExpandProperty Extent | `
            Select-Object -ExpandProperty StartLineNumber

        ## Test to see if the Script is formatted output with Metadata
        if (-not $ConfigBlockEndLine) {

            ## There is no metadata, create the basic object with just the source code
            $ActionConfig = [pscustomobject]@{
                ActionName   = (Get-Item -Path $Path).BaseName -replace '.TMAction', ''
                Description  = ""
                ProviderName = (Get-Item -Path $Path).Directory.BaseName
            }

            ## Create the objects the below constructor expect
            $ConfigBlockEndLine = -1
            $Params = $null
            $TMActionParams = [System.Collections.ArrayList] @()
        }
        else {

            ##
            ## Read the Script Header to gather the configurations
            ##

            ## Get all of the lines in the header comment
            $TMConfigHeader = 0..$ConfigBlockEndLine | ForEach-Object {

                if ($astTokens[$_].kind -eq 'comment') { $astTokens[$_] }
            } | Select-Object -First 1 | Select-Object -ExpandProperty Text

            ## Create a Properties object that will store the values listed in the header of the script file
            $ActionConfig = [PSCustomObject]@{
            }

            ## Process each line of the Header string
            $TMConfigHeader -split "`n" | ForEach-Object {

                ## Process each line of the comment
                if ($_ -like '*=*') {
                    $k, $v = $_ -split '='
                    $k = $k.Trim() -replace "'", '' -replace '"', ''
                    $v = $v.Trim() -replace "'", '' -replace '"', ''
                    $ActionConfig | Add-Member -NotePropertyName $k -NotePropertyValue $v
                }
            }

            ##
            ## Read the Script Block
            ##

            ## Create a Text StrinBuilder to collect the Script into
            $ActionConfigStringBuilder = New-Object System.Text.StringBuilder

            ## For each line in the Code Block, add it to the Action Script Code StringBuilder
            0..$ConfigBlockEndLine | ForEach-Object {
                $ActionConfigStringBuilder.AppendLine($ContentLines[$_]) | Out-Null
            }
            $ActionConfigScriptString = $ActionConfigStringBuilder.ToString()
            $ActionConfigScriptBlock = [scriptblock]::Create($ActionConfigScriptString)

            ## Invoke the Script Block to create the $Params Object in this scope
            ## this line populates the $Params object from the Action Script
            Invoke-Command -ScriptBlock $ActionConfigScriptBlock -NoNewScope

            ## Collect the Parameters
            $TMActionParams = [System.Collections.ArrayList] @()
            ## Action Parameter Class Definition
            # {
            # "desc": "",
            # "type": "string",
            # "value": "",
            # "context": "DEVICE",
            # "encoded": false,
            # "readonly": false,
            # "required": false,
            # "fieldName": null,
            # "paramName": "IPAddress",
            # "fieldLabel": "IP Address"
            # }

            ## Process the Parameters into Action Params
            foreach ($ParamLabel in $Params.Keys) {

                ## Create a new Params Object to load to the Action
                $NewParamConfig = [PSCustomObject]@{
                    type        = 'string'
                    value       = ''
                    description = ''
                    context     = ''
                    fieldLabel  = ''
                    fieldName   = ''
                    required    = $false
                    encoded     = $false
                    readonly    = $false
                }

                ## Read the existing Params configuration, assembling each additional Paramater option
                $ScriptParamConfig = $Params.$ParamLabel
                $ScriptParamConfig.Keys | ForEach-Object {
                    switch ($_.toLower()) {
                        'value' {
                            $NewParamConfig.value = $ScriptParamConfig[$_]
                            break
                        }
                        'type' {
                            $NewParamConfig.type = $ScriptParamConfig[$_]
                            break
                        }
                        'description' {
                            $NewParamConfig.description = $ScriptParamConfig[$_]
                            break
                        }
                        'desc' {
                            $NewParamConfig.description = $ScriptParamConfig[$_]
                            break
                        }
                        'context' {
                            $NewParamConfig.context = $ScriptParamConfig[$_]
                            break
                        }
                        'fieldlabel' {
                            $NewParamConfig.fieldLabel = $ScriptParamConfig[$_]
                            break
                        }
                        'fieldName' {
                            $NewParamConfig.fieldName = $ScriptParamConfig[$_]
                            break
                        }
                        'required' {
                            $NewParamConfig.required = (ConvertTo-Boolean $ScriptParamConfig[$_])
                            break
                        }
                    }
                }

                ## Add the Parameter Name from the Configuration Object
                Add-Member -InputObject $NewParamConfig -NotePropertyName 'paramName' -NotePropertyValue $ParamLabel
                $TMActionParams.Add($NewParamConfig) | Out-Null
            }

        }

        ## Note where the Action Code is located
        $StartCodeBlockLine = $ConfigBlockEndLine + 1
        $EndCodeBlockLine = $ast[-1].Extent.EndLineNumber

        ## Create a Text StrinBuilder to collect the Script into
        $ActionScriptStringBuilder = New-Object System.Text.StringBuilder

        ## For each line in the Code Block, add it to the Action Script Code StringBuilder
        $StartCodeBlockLine..$EndCodeBlockLine | ForEach-Object {
            $ActionScriptStringBuilder.AppendLine($ContentLines[$_]) | Out-Null
        }

        ## Convert the StringBuilder to a Multi-Line String
        $ActionScriptCode = $ActionScriptStringBuilder.ToString()


        ## If no Parameters were assembled, provide an empty array
        if (-not $TMActionParams) { $TMActionParams = [System.Collections.ArrayList] @() }
        # }


        ## Assemble the Action Object
        $TMAction = [pscustomobject]@{
            id                     = $null
            name                   = $ActionConfig.ActionName
            description            = $ActionConfig.Description
            debugEnabled           = $false

            methodParams           = ($TMActionParams | ConvertTo-Json -Compress)
            script                 = $ActionScriptCode
            reactionScripts        = '{"PRE":"","ERROR":"// Put the task on hold and add a comment with the cause of the error\n task.error( response.stderr )","FINAL":"","FAILED":"","LAPSED":"","STATUS":"// Check the HTTP response code for a 200 OK \n if (response.status == SC.OK) { \n \t return SUCCESS \n } else { \n \t return ERROR \n}","DEFAULT":"// Put the task on hold and add a comment with the cause of the error\n task.error( response.stderr )\n","STALLED":"","SUCCESS":"// Update Asset Fields\nif(response?.data?.assetUpdates){\n\tfor (field in response.data.assetUpdates) {\n \t\tasset.\"${field.key}\" = field.value;\n\t}\n}\ntask.done()"}'

            provider               = @{
                id   = $null
                name = $ActionConfig.ProviderName
            }
            project                = @{
                id   = $null
                name = $null
            }

            remoteCredentialMethod = 'USER_PRIV'
            credential             = $null

            asyncQueue             = $null

            version                = 1
            dateCreated            = Get-Date
            lastUpdated            = Get-Date

            timeout                = 0
            commandLine            = $null
            dictionaryMethodName   = "Select..."
            callbackMethod         = $null
            connectorMethod        = $null
            pollingStalledAfter    = 0
            pollingInterval        = 0
            pollingLapsedAfter     = 0
            defaultDataScript      = $null
            useWithTask            = 0
            reactionScriptsValid   = 1
            docUrl                 = ''
            isRemote               = $true
            actionType             = 'POWER_SHELL'
            useWithAsset           = 0
            isPolling              = 0
            endpointUrl            = ''
            apiCatalog             = $null
        }
    }

    ## Return the Action Object
    return $TMAction

}

function Invoke-TMActionScript {
    <#
    .SYNOPSIS
    Runs the Script Code in the specified TransitionManager Action

    .DESCRIPTION
    Runs the Script Code in the specified TransitionManager Action

    .PARAMETER TMSession
    Name of an open TMSession. Use Get-TMSession to get open sessions

    .EXAMPLE
    Invoke-TMActionScript -Name 'Ping Remote Machine'

    .OUTPUTS
    This command does not output any content
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)][String]$TMSession = "Default",
        [Parameter(Mandatory = $true)][String]$Name,
        [Parameter(Mandatory = $false)][String]$Server = $global:TMSessions[$TMSession].TMServer,
        [Parameter(Mandatory = $false)]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL,
        [Parameter(Mandatory = $false)][PSObject]$Project = $global:TMSessions[$TMSession].UserContext.Project
    )


    ## Get Session Configuration
    $TMSessionConfig = $global:TMSessions[$TMSession]
    if (-not $TMSessionConfig) {
        Write-Host 'TMSession: [' -NoNewline
        Write-Host $TMSession -ForegroundColor Cyan
        Write-Host '] was not Found. Please use the New-TMSession command.'
        Throw "TM Session Not Found. Use New-TMSession command before using features."
    }

    ## Get the TM Action
    $TMAction = Get-TMAction @PSBoundParameters
    if (-Not $TMAction) {
        throw 'Unable to get the Action'
    }

    ## Try converting and running the script block from the Action
    try {
        $ActionScriptBlock = [scriptblock]::Create($TMAction.Script)

        ## Invoke the script block, No New Scope to use the available variables,
        ## and with Debug enabled to allow stepping into the file via debugging mode
        Invoke-Command -ScriptBlock $ActionScriptBlock -NoNewScope -Debug
    }
    catch {
        throw $_
    }
}