BrazenCloud.ActionBuilder.psm1

<#
    Code in this file will be added to the beginning of the .psm1. For example,
    you should place any using statements here.
#>

class BcAction {
    [BcManifest]$Manifest
    [BcParameter[]]$Parameters
    [BcRepository]$Repository
    [hashtable]$Execution
    [string]$WindowsScript
    [string]$LinuxScript
    [string[]]$ExtraFolders

    BcAction() {
        $this.Manifest = [BcManifest]::new()
        $this.Repository = [BcRepository]::new()
    }

    [bool] Test() {
        if ($null -eq $this.Manifest) {
            Write-Warning 'No manifest.'
            return $false
        }
        if ($null -eq $this.WindowsScript -and $null -eq $this.LinuxScript) {
            Write-Warning 'Missing a script.'
            return $false
        }
        return $true
    }

    Export(
        [string]$Path
    ) {
        if ($this.Test()) {
            if (Test-Path $Path -PathType Leaf) {
                Throw 'Path must be a directory.'
            }
            if (-not (Test-Path $Path)) {
                New-Item $Path -ItemType Directory -Force
            }

            $outDir = (Get-Item $Path).FullName

            # output the manifest
            $this.Manifest.Export("$outDir\manifest.txt", $true)

            # output the parameters
            if ($this.Parameters.Count -gt 0) {
                $this.Parameters | Select-Object -ExcludeProperty "Value" | ConvertTo-Json -AsArray | Out-File $outDir\parameters.json
            }

            # output the execution files, if exists
            if ($null -ne $this.Execution) {
                $this.Execution | ConvertTo-Json | Out-File $outDir\execution.json
            }

            # output the repository.json file
            $this.Repository | ConvertTo-Json | Out-File $outDir\repository.json

            # output the windows script, if exists
            if ($null -ne $this.WindowsScript) {
                if (-not (Test-Path $outDir\windows)) {
                    New-Item $outDir\windows -ItemType Directory
                }
                $this.WindowsScript | Out-File $outDir\windows\script.ps1
            }

            # output the linux script, if exists
            if ($null -ne $this.LinuxScript) {
                if (-not (Test-Path $outDir\linux)) {
                    New-Item $outDir\linux -ItemType Directory
                }
                ($this.LinuxScript.Replace("`r", '')) -join "`n" | Out-File $outDir\linux\script.sh
            }

            # copy the extra files, if present
            if ($this.ExtraFolders.Count -gt 0) {
                foreach ($dir in $this.ExtraFolders) {
                    if (Test-Path $dir) {
                        Get-ChildItem $dir | Copy-Item -Destination $outDir -Recurse -Force
                    } else {
                        Write-Warning "Unable to copy '$($this.ExtraFolders)', path does not exist"
                    }
                }
            }
        } else {
            Throw 'Test failed. Be sure the action meets the minimum criteria of having both a manifest and at least a Windows or Linux script.'
        }

    }

    SetScript(
        [string]$OperatingSystem,
        [string]$Script
    ) {
        switch ($OperatingSystem) {
            'Windows' {
                $this.WindowsScript = $Script
            }
            'Linux' {
                $this.LinuxScript = $Script
            }
            default {
                Throw "Unsupported OS. Use either 'Windows' or 'Linux'"
            }
        }
    }
}
class BcManifest {
    [string]$Header
    [string]$WindowsCommand
    [string]$LinuxCommand

    BcManifest() {
        $this.Header = 'COPY . .'
        $this.WindowsCommand = 'RUN_WIN "PowerShell.exe -ExecutionPolicy Bypass -File .\windows\script.ps1"'
        $this.LinuxCommand = 'RUN_LIN linux/script.sh'
    }

    Export(
        [string]$Path,
        [bool]$Force
    ) {
        $str = "$($this.Header)`n$($this.WindowsCommand)`n$($this.LinuxCommand)"
        if ((Test-Path $path) -and -not $Force) {
            Throw 'File already exists. Use $Force to overwrite.'
        } else {
            $str | Out-File $Path -Force:$Force
        }
    }
}
class BcParameter {
    [string]$Name
    [ValidateRange(0, 3)]
    [int]$Type
    [string]$DefaultValue
    [string]$Description
    [bool]$IsOptional
    [string]$Value

    BcParameter() {
        $this.IsOptional = $true
    }

    [string]ToString(
        [bool]$Compress
    ) {
        return ($this | ConvertTo-Json -Compress:$Compress)
    }
    [string] GetWindowsIsEmptyStatement() {
        return "`$settings.'$($this.Name)'.ToString().Length -gt 0"
    }
    [string] GetLinuxIsEmptyStatement() {
        return "[ ! -z ""`$$($this.GetBashParameterName())"" ]"
    }
    [string] GetIsEmptyStatement(
        [string]$OperatingSystem
    ) {
        if ($OperatingSystem -eq 'Windows') {
            return $this.GetWindowsIsEmptyStatement()
        } elseif ($OperatingSystem -eq 'Linux') {
            return $this.GetLinuxIsEmptyStatement()
        } else {
            Throw "Unsupported OS value: '$OperatingSystem'"
        }
    }
    [string] GetWindowsIsTrueStatement() {
        return "`$settings.'$($this.Name)'.ToString() -eq 'true'"
    }
    [string] GetLinuxIsTrueStatement() {
        return "[[ `${$($this.GetBashParameterName())} == ""true"" ]]"
    }
    [string] GetIsTrueStatement (
        [string]$OperatingSystem
    ) {
        if ($OperatingSystem -eq 'Windows') {
            return $this.GetWindowsIsTrueStatement()
        } elseif ($OperatingSystem -eq 'Linux') {
            return $this.GetLinuxIsTrueStatement()
        } else {
            Throw "Unsupported OS value: '$OperatingSystem'"
        }
    }
    [string] GetLinuxValue() {
        if ($this.Type -eq 0) {
            if ($this.Value.Length -gt 0) {
                return $this.Value -replace "\{value\}", "`$$($this.GetBashParameterName())"
            } else {
                return "`$$($this.GetBashParameterName())"
            }
        } else {
            return $this.Value
        }
    }
    [string] GetWindowsValue() {
        if ($this.Type -eq 0) {
            if ($this.Value.Length -gt 0) {
                return $this.Value -replace "\{value\}", "`$(`$settings.'$($this.Name)')"
            } else {
                return "`$(`$settings.'$($this.Name)')"
            }
        } else {
            return $this.Value
        }
    }
    [string] GetValue(
        [string]$OperatingSystem
    ) {
        if ($OperatingSystem -eq 'Windows') {
            return $this.GetWindowsValue()
        } elseif ($OperatingSystem -eq 'Linux') {
            return $this.GetLinuxValue()
        } else {
            Throw "Unsupported OS value: '$OperatingSystem'"
        }
    }
    [string] GetBashParameterName() {
        return $this.Name -replace '[^a-zA-Z0-9_]', ''
    }
}
class BcRepository {
    [string]$Description
    [string]$Language
    [string[]]$Tags
    [string]$Glyph

    BcRepository() {}
}
Function Get-Template {
    [cmdletbinding()]
    param (
        [switch]$All
    )
    @{
        Windows  = @{
            if     = @{
                if                 = @'
if ({condition}) {
    {command}
}
'@

                bool               = @'
if ($settings.'{param}'.ToString() -eq 'true') {
    {command}
}
'@

                combine            = @'
{customParam}if ( {exists} ) {
    $arr = & {
        {if}
    }
    & {command}
} {else}
'@

                combineCustomParam = @'
if ({condition}) {
    & {command}
} else
'@

                ifElse             = @'
if ( {condition} ) {
    {command}
} {else}
'@

                param              = @'
if ($settings.'{param}'.ToString().Length -gt 0) {
    {value}
}
'@

                string             = @'
if ($settings.'{param}'.Length -gt 0) {
    {command}
}
'@

                else               = @'
else {
    {command}
}
'@

                elseIf             = @'
elseif ( {condition} ) {
    {command}
}
'@

                elseIfElse         = @'
elseif ( {condition} ) {
    {command}
} {else}
'@

            }
            script = @'
Set-Location $PSScriptRoot
$settings = Get-Content ..\settings.json | ConvertFrom-Json
 
{ preCommands }
 
{ if }
'@

        }
        Linux    = @{
            if     = @{
                if                 = @'
if {condition} ; then
    {command}
fi
'@

                bool               = @'
if [ ${param} == "true" ]; then
    {command}
fi
'@

                combine            = @'
declare -a arr
 
{customParam}if {exists} ; then
    {if}
    {command}
{else}
fi
'@

                combineCustomParam = @'
if {condition}; then
    {command}
el
'@

                ifElse             = @'
 
if {condition} ; then
    {command}
{else}
fi
'@

                param              = @'
if [ ! -z {param} ]; then
    arr+=({value})
fi
'@

                string             = @'
if [ ${#{param}} -gt 0 ]; then
    {command}
fi
'@

                else               = @'
else
    {command}
'@

                elseIf             = @'
elif {condition}; then
    {command}
fi
'@

                elseIfElse         = @'
elif {condition}; then
    {command}
{else}
'@

            }
            script = @'
#!/bin/bash
cd "${0%/*}"
 
# check for current package manager
declare -A osInfo;
osInfo[/etc/redhat-release]=yum
osInfo[/etc/arch-release]=pacman
osInfo[/etc/gentoo-release]=emerge
osInfo[/etc/SuSE-release]=zypp
osInfo[/etc/debian_version]=apt-get
osInfo[/etc/alpine-release]=apk
 
for f in ${!osInfo[@]}
do
    if [[ -f $f ]];then
        #echo Package manager: ${osInfo[$f]}
        pman=${osInfo[$f]}
    fi
done
 
# check if jq is installed
if ! [ -x "$(command -v jq)" ]; then
    echo "Installing jq"
 
    # check for sudo, install
    if [ -x "$(command -v sudo)" ]; then
        sudo $pman install jq -y
    else
        $pman install jq -y
    fi
else
    echo "jq already installed"
fi
 
{ preCommands }
 
{ prereqs }
 
{ jq }
 
{ if }
'@

            jq     = @'
{bashParam}=$(jq -r '."{param}"' ../settings.json)
'@

            prereq = @'
# check if {package} is installed
if ! [ -x "$(command -v {testCommand})" ]; then
    echo "Installing {package}"
 
    # check for sudo, install
    if [ -x "$(command -v sudo)" ]; then
        sudo $pman install {package} -y
    else
        $pman install {package} -y
    fi
else
    echo "{package} already installed"
fi
'@

        }
        Manifest = @'
COPY . .
 
RUN_WIN "powershell.exe -ExecutionPolicy Bypass -File .\windows\script.ps1"
 
RUN_LIN linux/script.sh
'@

    }
}
function makeCommand {
    [OutputType('String')]
    param (
        [string]$Command,
        [validateSet('Windows', 'Linux')]
        [string]$OS,
        [switch]$Redirect,
        [string]$Parameters,
        [BcParameter[]]$RequiredParameters
    )
    if ($Parameters.Length -gt 0) {
        $Command = "$Command $Parameters"
    }

    if ($RequiredParameters.Count -gt 0) {
        $reqStr = ($RequiredParameters | ForEach-Object { $_.GetValue($OS) }) -join " "
        $Command = "$Command $reqStr"
    }

    switch ($OS) {
        'Windows' {
            if ($Redirect.IsPresent) {
                $Command = "$Command | Out-File ..\results\out.txt -Append"
            }
        }
        'Linux' {
            if ($Redirect.IsPresent) {
                $Command = "$Command >> ../results/out.txt"
            }
        }
    }
    $Command
}
Function Export-BcAbAction {
    [cmdletbinding()]
    param (
        [string]$ConfigPath,
        [string]$OutPath,
        [switch]$ClearOutputFolders
    )
    [hashtable[]]$json = Get-Content $ConfigPath | ConvertFrom-Json -AsHashtable
    foreach ($actionHt in $json) {
        Write-Verbose "Starting $($actionHt['Name'])"
        $h = Get-Help New-BcAbAction
        $splat = @{}
        foreach ($parameter in $h.parameters.parameter.Name) {
            $splat[$parameter] = $null -ne $actionHt.$parameter ? $actionHt.$parameter : $null
        }
        $action = New-BcAbAction @splat
        if ($ClearOutputFolders.IsPresent) {
            if (Test-Path "$OutPath\$($actionHt.Name)") {
                Get-ChildItem "$OutPath\$($actionHt.Name)" -Recurse -File | ForEach-Object {
                    Remove-Item $_ -Force
                }
                Get-ChildItem "$OutPath\$($actionHt.Name)" -Recurse -Directory | ForEach-Object {
                    Remove-Item $_ -Recurse -Force
                }
            }
        }
        $action.Export("$OutPath\$($actionHt.Name)")
    }
}
Function New-BcAbAction {
    [OutputType('BcAction')]
    [cmdletbinding()]
    param (
        [string]$Name,
        [string]$Description,
        [string[]]$Tags,
        [string]$Language,
        [string]$Command,
        [ValidateSet('Windows', 'Linux')]
        [string[]]$OperatingSystems,
        [switch]$IncludeParametersParameter,
        [string]$ParametersParameterDescription,
        [string]$DefaultParameters,
        [switch]$RedirectCommandOutput,
        [ValidateSet('Combine', 'All', 'One')]
        [string]$ParameterLogic,
        [hashtable[]]$ActionParameters,
        [string[]]$ExtraFolders,
        [hashtable[]]$RequiredPackages,
        [string[]]$PreCommands,
        [string]$OutPath
    )

    $action = [BcAction]::new()

    # Build the parameters
    $action.Parameters = foreach ($param in $ActionParameters) {
        New-BcAbParameter @param
    }

    # Get Required Parameters
    $reqParams = $action.Parameters | Where-Object { $_.IsOptional -eq $false }

    # Add the default parameters parameter, if requested
    if (-not $ParametersParameterDescription.Length -gt 0) {
        $ParametersParameterDescription = 'Parameters typed here are passed directly to the command.'
    }
    if ($IncludeParametersParameter.IsPresent) {
        $paramSplat = @{
            Name         = 'Custom Parameters'
            DefaultValue = $DefaultParameters
            Description  = $ParametersParameterDescription
        }
        $action.Parameters += New-BcAbParameter @paramSplat
    }

    # update repository and manifest
    foreach ($tag in $Tags) {
        if ($action.Repository.Tags -notcontains $tag) {
            $action.Repository.Tags += $tag
        }
    }
    if ($action.Repository.Tags -notcontains $Name) {
        $action.Repository.Tags += $Name
    }
    $action.Repository.Description = $Description
    if ($OperatingSystems -contains 'Windows') {
        if (-not $Language.Length -gt 0) {
            $action.Repository.Language = 'Generated PowerShell'
        } else {
            $action.Repository.Language = $Language
        }
        if ($action.Repository.Tags -notcontains 'Windows') {
            $action.Repository.Tags += 'Windows'
        }
    } else {
        $action.Manifest.WindowsCommand = $null
    }
    if ($OperatingSystems -contains 'Linux') {
        if (-not $Language.Length -gt 0) {
            $action.Repository.Language = 'Generated Bash'
        } else {
            $action.Repository.Language = $Language
        }
        if ($action.Repository.Tags -notcontains 'Linux') {
            $action.Repository.Tags += 'Linux'
        }
    } else {
        $action.Manifest.LinuxCommand = $null
    }

    # declare splat
    $mcSplat = @{
        Command            = $Command
        OS                 = ''
        Redirect           = $RedirectCommandOutput.IsPresent
        Parameters         = $DefaultParameters
        RequiredParameters = $reqParams
    }

    # add extra folder if passed
    if ($ExtraFolders.Count -gt 0) {
        $action.ExtraFolders = $ExtraFolders
    }

    $preCmds = if ($PreCommands.Count -gt 0) {
        $PreCommands -join "`n"
    } else {
        $null
    }

    # if no parameters and no includeParametersParameter
    # then this is simple
    if ($ActionParameters.Count -eq 0 -and -not $IncludeParametersParameter.IsPresent) {
        Write-Verbose 'No parameters passed'
        if ($OperatingSystems -contains 'Windows') {
            $mcSplat['OS'] = 'Windows'
            $action.WindowsScript = $templates['Windows']['script'] `
                -replace '\{ preCommands \}', $preCmds `
                -replace '\{ if \}', (makeCommand @mcSplat)
        }
        if ($OperatingSystems -contains 'Linux') {
            $mcSplat['OS'] = 'Linux'
            $action.LinuxScript = $templates['Linux']['script'] `
                -replace '\{ preCommands \}', $preCmds `
                -replace '\{ if \}', (makeCommand @mcSplat) `
                -replace '\{ jq \}', ''
        }
        # if it has action parameters
    } elseif ($ActionParameters.Count -gt 0 -or $IncludeParametersParameter) {
        foreach ($os in $OperatingSystems) {
            $mcSplat.OS = $os

            $logicSplat = @{
                Parameters            = $Action.Parameters
                Command               = $Command
                OperatingSystem       = $os
                RedirectCommandOutput = $RedirectCommandOutput.IsPresent
                DefaultParameters     = $IncludeParametersParameter ? $null : $DefaultParameters
                Type                  = $ParameterLogic
            }

            $ifs = New-BcAbScript @logicSplat

            $jqs = if ($os -eq 'Linux') {
                foreach ($aParam in $Action.Parameters) {
                    $templates['Linux']['jq'] `
                        -replace '\{bashParam\}', $aParam.GetBashParameterName() `
                        -replace '\{param\}', $aParam.Name
                }
            } else {
                $null
            }

            $prereqs = if ($os -eq 'Linux') {
                if ($RequiredPackages.Count -gt 0) {
                    foreach ($package in $RequiredPackages) {
                        $templates['Linux']['prereq'] `
                            -replace '\{package\}', $package.Name `
                            -replace '\{testCommand\}', $package.TestCommand
                    }
                } else {
                    $null
                }
            } else {
                $null
            }
            

            $action.SetScript($os, (
                    $templates[$os]['script'] `
                        -replace '\{ preCommands \}', $preCmds `
                        -replace '\{ jq \}', ($jqs -join "`n") `
                        -replace '\{ prereqs \}', $prereqs `
                        -replace '\{ if \}', ($ifs -join "`n")
                )
            )
        }
    }
    $action
}
Function New-BcAbAllScript {
    [cmdletbinding()]
    param (
        [BcParameter[]]$Parameters,
        [ValidateSet('Windows', 'Linux')]
        [string]$OperatingSystem,
        [string]$Command,
        [switch]$RedirectCommandOutput,
        [string]$DefaultParameters
    )

    $mcSplat = @{
        Command    = $Command
        OS         = $OperatingSystem
        Redirect   = $RedirectCommandOutput.IsPresent
        Parameters = $DefaultParameters
    }

    foreach ($param in ($Parameters | Where-Object { $_.Name -ne 'Custom Parameters' })) {
        # if this param has a default value, use it, else it must have come from the passed actionParameters var
        if ($param.Type -eq 2) {
            $mcSplat.Parameters = $param.GetValue($OperatingSystem)
            $templates[$OperatingSystem]['if']['bool'] `
                -replace '{param}', $Param.Name `
                -replace '"?{command}"?', (makeCommand @mcSplat)
        } elseif ($Param.Type -eq 0) {
            $mcSplat.Parameters = $param.GetValue($OperatingSystem)
            $templates[$OperatingSystem]['if']['string'] `
                -replace '{param}', $Param.Name `
                -replace '{command}', (makeCommand @mcSplat)
        }
    }
    $Parameters | Where-Object { $_.Name -eq 'Custom Parameters' } | ForEach-Object {
        $mcSplat.Parameters = $_.GetValue($OperatingSystem)
        makeCommand @mcSplat
    }
}
Function New-BcAbCombineScript {
    [cmdletbinding()]
    param (
        [BcParameter[]]$Parameters,
        [ValidateSet('Windows', 'Linux')]
        [string]$OperatingSystem,
        [string]$Command,
        [switch]$RedirectCommandOutput,
        [string]$DefaultParameters
    )

    $orStatement = @{
        Windows = ' -or '
        Linux   = ' || '
    }

    $mcSplat = @{
        Command    = $Command
        OS         = $OperatingSystem
        Redirect   = $RedirectCommandOutput.IsPresent
        Parameters = $DefaultParameters
    }

    # if the 'Custom Parameters' is present, make sure that is first in the script
    $customParam = if ($Parameters.Name -contains 'Custom Parameters') {
        $param = $Parameters | Where-Object { $_.Name -eq 'Custom Parameters' }
        $condition = if ($param.Type -eq 2) {
            $param.GetIsTrueStatement($OperatingSystem)
        } elseif ($param.Type -eq 0) {
            $param.GetIsEmptyStatement($OperatingSystem)
        }
        $mcSplat.Parameters = $param.GetValue($OperatingSystem)
        $templates[$OperatingSystem]['if']['combineCustomParam'] `
            -replace '{condition}', $condition `
            -replace '{command}', (makeCommand @mcSplat)
    } else {
        $null
    }

    # build the array of conditions. If any of them are true, then the ifs will run
    $statements = foreach ($param in $Parameters | Where-Object { $_.Name -ne 'Custom Parameters' }) {
        if ($param.Type -eq 2) {
            $param.GetIsTrueStatement($OperatingSystem)
        } elseif ($param.Type -eq 0) {
            $param.GetIsEmptyStatement($OperatingSystem)
        }
    }
    # Main if with the above conditions
    $mainIf = $templates[$OperatingSystem]['if']['combine'] `
        -replace '\{exists\}', ($statements -join $orStatement[$OperatingSystem])

    # build the internal if array
    $ifArr = foreach ($param in $Parameters | Where-Object { $_.Name -ne 'Custom Parameters' }) {
        if ($param.Type -eq 2) {
            $templates[$OperatingSystem]['if']['if'] `
                -replace '{condition}', $Param.GetIsTrueStatement($OperatingSystem) `
                -replace '{command}', ($OperatingSystem -eq 'Linux' ? "arr+=(""$($param.GetValue($OperatingSystem))"")" : "`"$($param.GetValue($OperatingSystem))`"")
        } elseif ($param.Type -eq 0) {
            $templates[$OperatingSystem]['if']['if'] `
                -replace '{condition}', $Param.GetIsEmptyStatement($OperatingSystem) `
                -replace '{command}', ($OperatingSystem -eq 'Linux' ? "arr+=(""$($param.GetValue($OperatingSystem))"")" : "`"$($param.GetValue($OperatingSystem))`"")
        }
    }

    # fix the spacing so that it looks nice
    $ifArr = foreach ($str in $ifArr) {
        $space = $OperatingSystem -eq 'Windows' ? ' ' : ' '
        $str -split '\n' -join "`n$space"
    }

    # Build the combine logic
    $mcSplat.Parameters = $OperatingSystem -eq 'Windows' ? '$arr' : '${arr[*]}'
    $out = ( ( $mainIf -replace '\{if\}', ($ifArr -join "`n$space") ) ) `
        -replace '\{command\}', (makeCommand @mcSplat) `
        -replace '\{customParam\}', $customParam
    # Add the else
    $mcSplat['Parameters'] = $DefaultParameters
    $out -replace '\{else\}', (
        ($templates[$OperatingSystem]['if']['else'] `
            -replace '\{command\}', (makeCommand @mcSplat)) -join "`n"
    )
}
Function New-BcAbConfig {
    [cmdletbinding()]
    param (
        [Parameter( Mandatory )]
        [string]$Name,
        [string]$Description,
        [string[]]$Tags,
        [string]$Language,
        [ValidateSet('Windows', 'Linux')]
        [string[]]$OperatingSystems,
        [Parameter( Mandatory )]
        [string]$Command,
        [string[]]$ExtraFolders,
        [bool]$IncludeParametersParameter,
        [string]$ParametersParameterDescription,
        [string]$DefaultParameters,
        [bool]$RedirectCommandOutput,
        [ValidateSet('Combine', 'All', 'One')]
        [string]$ParameterLogic = 'Combine',
        [hashtable[]]$ActionParameters,
        [hashtable[]]$RequiredPackages,
        [string[]]$PreCommands,
        [string]$OutPath,
        [switch]$Force
    )
    $ht = [ordered]@{
        Name                           = $Name
        Description                    = $Description ? $Description : ""
        OperatingSystems               = $OperatingSystems ? $OperatingSystems : @("Windows", "Linux")
        Tags                           = $Tags ? $Tags : @()
        Language                       = $Language
        Command                        = $Command
        ExtraFolders                   = $ExtraFolders ? $ExtraFolders : @()
        IncludeParametersParameter     = $IncludeParametersParameter ? $IncludeParametersParameter : $false
        ParametersParameterDescription = $ParametersParameterDescription ? $ParametersParameterDescription : ""
        DefaultParameters              = $DefaultParameters ? $DefaultParameters : ""
        RedirectCommandOutput          = $RedirectCommandOutput ? $RedirectCommandOutput : $false
        ParameterLogic                 = $ParameterLogic
        ActionParameters               = $ActionParameters
        RequiredPackages               = $RequiredPackages
        PreCommands                    = $PreCommands ? $PreCommands : @()
    }

    if ($PSBoundParameters.Keys -notcontains 'ActionParameters') {
        $ht['ActionParameters'] = @(
            [ordered]@{
                Name              = ''
                CommandParameters = ''
                Description       = ''
                Required          = $false
            }
        )
    }

    if ($PSBoundParameters.Keys -notcontains 'RequiredPackages') {
        $ht['RequiredPackages'] = @(
            [ordered]@{
                Name        = ''
                TestCommand = ''
            }
        )
    }

    if ($PSBoundParameters.Keys -contains 'OutPath') {
        if (Test-Path $OutPath -PathType Container) {
            $OutPath = "$OutPath\config.json"
        } elseIf ((Test-Path $OutPath -PathType Leaf) -and -not $Force.IsPresent) {
            Throw "Output Path: '$OutPath' already exists. Use -Force to overwrite."
        }
        $ht | ConvertTo-Json -AsArray -Depth 3 | Out-File $OutPath -Force:$Force.IsPresent
    } else {
        $ht
    }
    
}
Function New-BcAbConfigActionParameters {
    [cmdletbinding()]
    param (
        [string]$Name,
        [string]$CommandParameters,
        [string]$Description
    )
    [ordered]@{
        Name              = $Name
        CommandParameters = $CommandParameters
        Description       = $Description
    }
}
Function New-BcAbOneScript {
    [cmdletbinding()]
    param (
        [BcParameter[]]$Parameters,
        [ValidateSet('Windows', 'Linux')]
        [string]$OperatingSystem,
        [string]$Command,
        [switch]$RedirectCommandOutput,
        [string]$DefaultParameters
    )

    $mcSplat = @{
        Command    = $Command
        OS         = $OperatingSystem
        Redirect   = $RedirectCommandOutput.IsPresent
        Parameters = $DefaultParameters
    }

    if ($Parameters.Name -contains 'Custom Parameters') {
        $firstParam = $Parameters | Where-Object { $_.Name -eq 'Custom Parameters' }
        $startingIndex = 0
    } else {
        $firstParam = $firstParam
        $startingIndex = 1
    }

    # Build the first if
    $out = if ($firstParam.Type -eq 2) {
        $mcSplat.Parameters = $firstParam.GetValue($OperatingSystem)
        $templates[$OperatingSystem]['if']['ifElse'] `
            -replace '\{condition\}', $firstParam.GetIsTrueStatement($OperatingSystem) `
            -replace '\{command\}', (makeCommand @mcSplat)
    } elseif ($firstParam.Type -eq 0) {
        $mcSplat.Parameters = $firstParam.GetValue($OperatingSystem)
        $templates[$OperatingSystem]['if']['ifElse'] `
            -replace '\{condition\}', $firstParam.GetIsEmptyStatement($OperatingSystem) `
            -replace '\{command\}', (makeCommand @mcSplat)
    }

    # Build the remaining ifs as the elseifelse
    for ($x = $startingIndex; $x -lt $Parameters.Count; $x++) {
        if ($Parameters[$x].Name -ne 'Custom Parameters') {
            # if this param has a default value, use it, else it must have come from the passed actionParameters var
            $ifStr = if ($Parameters[$x].Type -eq 2) {
                $mcSplat.Parameters = $Parameters[$x].GetValue($OperatingSystem)
                $templates[$OperatingSystem]['if']['elseIfElse'] `
                    -replace '\{condition\}', $Parameters[$x].GetIsTrueStatement($OperatingSystem) `
                    -replace '\{command\}', (makeCommand @mcSplat)
            } elseif ($Parameters[$x].Type -eq 0) {
                $mcSplat.Parameters = $Parameters[$x].GetValue($OperatingSystem)
                $templates[$OperatingSystem]['if']['elseIfElse'] `
                    -replace '\{condition\}', $Parameters[$x].GetIsEmptyStatement($OperatingSystem) `
                    -replace '\{command\}', (makeCommand @mcSplat)
            }
            $out = $out -replace '\{else\}', $ifStr
        }
    }
    # Build the final else statement
    if ($DefaultParameters.Length -gt 0) {
        $mcSplat.Parameters = $DefaultParameters
        $out = $out `
            -replace '\{else\}', $templates[$OperatingSystem]['if']['else'] `
            -replace '\{command\}', (makeCommand @mcSplat)
    } else {
        $mcSplat.Parameters = $null
        $out = $out `
            -replace '\{else\}', $templates[$OperatingSystem]['if']['else'] `
            -replace '\{command\}', (makeCommand @mcSplat)
    }
    $out
}
Function New-BcAbParameter {
    [OutputType('BcParameter')]
    [cmdletbinding()]
    param (
        [string]$Name,
        [string]$CommandParameters,
        [string]$Description,
        [string]$DefaultValue,
        [bool]$Required = $false
    )
    $param = [BcParameter]::new()
    $param.Description = $Description
    $param.Name = $Name

    if ($CommandParameters -match '\{value\}' -or $CommandParameters.Length -eq 0) {
        $param.Type = 0 # string
        $param.Value = $CommandParameters
    } else {
        $param.Type = 2 # boolean
        $param.Value = $CommandParameters
    }

    if ($PSBoundParameters.Keys -contains 'DefaultValue') {
        $param.DefaultValue = $DefaultValue
    }

    if ($Required) {
        $param.IsOptional = $false
    }

    $param
}
Function New-BcAbScript {
    [cmdletbinding()]
    param (
        [BcParameter[]]$Parameters,
        [ValidateSet('Windows', 'Linux')]
        [string]$OperatingSystem,
        [string]$Command,
        [switch]$RedirectCommandOutput,
        [string]$DefaultParameters,
        [Parameter( Mandatory )]
        [ValidateSet('Combine', 'One', 'All')]
        [string]$Type
    )
    switch ($Type) {
        'Combine' {
            $PSBoundParameters.Remove('Type') | Out-Null
            New-BcAbCombineScript @PSBoundParameters
        }
        'One' {
            $PSBoundParameters.Remove('Type') | Out-Null
            New-BcAbOneScript @PSBoundParameters
        }
        'All' {
            $PSBoundParameters.Remove('Type') | Out-Null
            New-BcAbAllScript @PSBoundParameters
        }
    }
}
<#
    Code in this file will be added to the end of the .psm1. For example,
    you should set variables or other environment settings here.
#>

$script:templates = Get-Template -All