Public/Extensions.ps1

function Get-TMExtension {
    <#
    .SYNOPSIS
    Gets/Downloads a Reference Design from the TDS BitBucket tm-reference-designs repo
 
    .DESCRIPTION
    This function
 
    .PARAMETER Name
    The name of the Reference Design to download
 
    .PARAMETER Credential
    The credentials used to log into BitBucket
 
    .PARAMETER SkipDependencies
    Switch indicating that the Reference Designs on which the named Reference Design
    depends on should not be downloaded
 
    .PARAMETER ManifestUri
    The uri of the raw rd-manifest.json. Defaults to the file in the __Releases directory
 
    .PARAMETER Dev
    Switch indicating that all reference designs should be searched/retrieved rather
    than just the published ones
 
    .PARAMETER ProviderName
    One or more specific Providers to get. All other Providers will be removed from the
    downloaded Reference Design
 
    .EXAMPLE
    $Credential = Get-StoredCredential -Name 'ME'
    Get-TMExtension -Name 'vmware-vcenter' -Credential $Credential -Dev
 
    .OUTPUTS
    None
    #>


    [alias('Get-TMReferenceDesign')]
    [CmdletBinding(DefaultParameterSetName = 'Console')]
    param (
        [Parameter(Mandatory = $false, Position = 0, ParameterSetName = 'Console')]
        [String[]]$Name,

        [Parameter(Mandatory = $false, Position = 1)]
        [PSCredential]$Credential,

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

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

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

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

        # [Parameter(Mandatory = $false, ParameterSetName = "UI")]
        # [Switch]$ShowMenu
        # TODO: Consider if we could add a $Path/$Location type
        # parameter to allow the user to specify a filesystem
    )

    # Some of the TMD functions will be needed in this function
    if (!( Get-Module 'TMD.Common')) {
        Import-Module 'TMD.Common'
    }

    ## Create a $PackageNames variable with all of the Reference Design names (Lower, no spaces)
    $PackageNames = $Name.ToLower() -replace ' - ', '-' -replace ' ', '-'

    ## Save the Progress Preference now so that it can be changed and set back later
    $ProgressPreferenceBackup = $Global:ProgressPreference
    $Global:ProgressPreference = 'SilentlyContinue'

    $ExtensionFolder = Join-Path $Global:userFilesRoot 'Reference Designs'
    $LocalExtensions = [System.Collections.ArrayList]::new()
    $ExtensionsToDownload = [System.Collections.ArrayList]::new()
    # $ShouldInstall = $false

    ## TODO: I don't like this credential handling. If a user supplies a credential, it should not be written to disk, but only used.
    ## To mitigate this, you could do a few things -StoreCredentialAs (Name), or
    ## -CredentialName {Which would hand off to Get-StoredCredential}

    # Check if the credential is already cached
    $CredentialPath = Join-Path -Path $Global:UserPaths.credentials -ChildPath 'Bitbucket.credential'
    if (!$Credential) {
        $Credential = (Test-Path -Path $CredentialPath) ? (Get-StoredCredential -Name 'Bitbucket') : (Get-Credential)
    }

    # Save the credential for future use
    Add-StoredCredential -Name 'Bitbucket' -Credential $Credential
    Write-Verbose 'Credential was stored as Bitbucket.credential'

    # Get the reference design manifest from bitbucket
    Write-Host 'Getting available Reference Designs from Bitbucket...'
    try {
        if ([String]::IsNullOrWhiteSpace($ManifestUri)) {
            $ManifestUri = "https://git.tdsops.com/projects/TM/repos/tm-reference-designs/raw/__Releases/$(if ($Dev) {'dev-'})rd-manifest.json?at=refs%2Fheads%2Fmain"
        }
        $AvailableExtensions = Invoke-RestMethod -Method Get -Uri $ManifestUri -Authentication Basic -Credential $Credential

    } catch {
        throw "Couldn't get the manifest file. Check credentials and try again"
    }
    Write-Host "`tFound " -NoNewline
    Write-Host "$($AvailableExtensions.Count)" -NoNewline -ForegroundColor DarkBlue
    Write-Host ' available Reference Design(s)'

    # Fix the version so that it can be compared later in the script
    for ($i = 0; $i -lt $AvailableExtensions.Count; $i++) {
        $AvailableExtensions[$i].Version = ConvertTo-SemanticVersion -VersionString $AvailableExtensions[$i].Version
        for ($j = 0; $j -lt $AvailableExtensions[$i].extension.dependencies.extensions.Count; $j++) {
            $AvailableExtensions[$i].extension.dependencies.extensions[$j].version = ConvertTo-SemanticVersion -VersionString $AvailableExtensions[$i].extension.dependencies.extensions[$j].version
        }
    }

    # Either show the UI to get the user's selections or
    switch ($PSCmdlet.ParameterSetName) {
        'Console' {
            Write-Verbose 'Console mode was selected'
            # If no name was supplied, just output the Extension objects
            if (!$PackageNames) {
                $AvailableExtensions | ForEach-Object {
                    $_
                }
                return
            } else {
                $PackageNames | ForEach-Object {
                    $Extension = $AvailableExtensions | Where-Object name -EQ $_ |
                        Sort-Object -Property version | Select-Object -Last 1
                        if ($Extension) {
                            [void]$ExtensionsToDownload.Add($Extension)
                        }
                    }
                }
            }
            <#
        "UI" {
            Write-Verbose "UI mode was selected"
            # $ExePath = "..\exe\RD Installer.exe"
            $ExePath = "C:\DEV\git\tm-reference-designs\_ReferenceDesignManager\ReferenceDesignManager\bin\Debug\net5.0-windows\ReferenceDesignManager.exe"
            $StartInfo = [System.Diagnostics.ProcessStartInfo]::new()
            $StartInfo.RedirectStandardError = $true
            $StartInfo.RedirectStandardOutput = $true
            $StartInfo.FileName = $ExePath
            # $StartInfo.ArgumentList = @(
            # "--repocredential `"$CredentialPath`""
            # "--powershell"
            # )
            $StartInfo.Arguments = "--repocredential `"$CredentialPath`" --powershell"
 
            $ExtensionInstallerProc = [System.Diagnostics.Process]::new()
            $ExtensionInstallerProc.StartInfo = $StartInfo
 
            # Start the UI and wait for it to exit so that the stdout can be parsed
            Write-Host "Showing UI..."
            $ExtensionInstallerProc.Start()
            $ExtensionInstallerProc.WaitForExit()
            $StdOut = $ExtensionInstallerProc.StandardOutput.ReadToEnd()
 
            if ([String]::IsNullOrWhiteSpace($StdOut)) {
                throw "Output was not received from $(Split-Path $ExePath -Leaf)"
            }
 
            $UserChoices = $StdOut | ConvertFrom-Json
            $UserChoices.ExtensionsToDownload | ForEach-Object {
                [void]$ExtensionsToDownload.Add(
                    @{
                        Name = $_
                        Version = $null
                    }
                )
            }
            $ShouldInstall = $UserChoices.ShouldInstall
        }
        #>

        }

        # Make sure one or more available Reference Designs were found
        if ($ExtensionsToDownload.Count -eq 0) {
            throw "No available Reference Designs were found with the specified name(s): $($PackageNames.ToString())"
        }

        ## Create the Destination Downloaded Folder
        $DownloadedFolder = Join-Path $ExtensionFolder 'Downloaded'
        Test-FolderPath $DownloadedFolder

        # Get all of the Reference Designs that are already downloaded
        Write-Host 'Checking for downloaded Reference Designs...'
        Write-Verbose "Looking in '$DownloadFolder'"
        try {
            Get-ChildItem -Path $DownloadedFolder -Filter 'package.json' -Recurse -ErrorAction SilentlyContinue |
                Get-Content -Raw | ConvertFrom-Json | ForEach-Object {
                    [void]$LocalExtensions.Add($_)
                }
    } catch {
        Write-Verbose $_
    }

    Write-Host "`tFound " -NoNewline
    Write-Host "$($LocalExtensions.Count)" -NoNewline -ForegroundColor DarkBlue
    Write-Host ' local Reference Design(s)'

    # Check if the selected Reference Designs have any dependencies on other Reference Designs
    if (!$SkipDependencies) {
        Write-Host 'Checking for Reference Design dependencies...'
        $Count = $ExtensionsToDownload.Count
        for ($i = 0; $i -lt $Count; $i++) {
            $Design = $ExtensionsToDownload[$i]
            foreach ($Dependency in $Design.extension.dependencies.extensions) {
                $AvailableExtension = $AvailableExtensions | Where-Object Name -EQ $Dependency.name |
                    Sort-Object -Property version | Select-Object -Last 1
                if ($AvailableExtension) {
                    $LocalExtension = $LocalExtensions | Where-Object Name -EQ $Dependency.name |
                        Sort-Object -Property version | Select-Object -Last 1
                    if ($LocalExtension) {
                        if ($LocalExtension.version -lt $Dependency.version) {
                            Write-Host "`tFound Reference Design to update: " -NoNewline
                            Write-Host "$($AvailableExtension.Name) - v$($AvailableExtension.Version.ToString())"
                            [void]$ExtensionsToDownload.Add($AvailableExtension)
                        }
                    } else {
                        Write-Host "`tFound additional Reference Design to download: " -NoNewline
                        Write-Host "$($AvailableExtension.Name) - v$($AvailableExtension.Version.ToString())"
                        [void]$ExtensionsToDownload.Add($AvailableExtension)
                    }
                }
            }
        }
    }

    # Start downloading the named reference designs
    Write-Host 'Downloading Reference Designs...'
    # $EncounteredError = $false
    foreach ($ExtensionSpec in $ExtensionsToDownload) {
        Write-Host "`tProcessing: " -NoNewline
        Write-Host $ExtensionSpec.ProductName -ForegroundColor DarkBlue

        ## Resolve the file name and the destination path
        $ZipFileName = $ExtensionSpec.Name + '-' + $ExtensionSpec.Version + '.zip'
        $Url = 'https://git.tdsops.com/projects/TM/repos/tm-reference-designs/raw/__Releases/{0}?at=refs%2Fheads%2Fmain' -f $ZipFileName
        $DestinationFilePath = Join-Path -Path $ExtensionFolder -ChildPath 'Downloaded' -AdditionalChildPath $ZipFileName

        try {
            ## Call to Bitbucket to download the file
            Write-Host "`t`tDownloading $ZipFileName ($([Math]::Round(($ExtensionSpec.Size / 1MB), 2)) MB)..." -NoNewline
            Invoke-WebRequest -Uri $Url -Credential $Credential -Authentication Basic -Method Get -OutFile $DestinationFilePath
            Write-Host 'Done' -ForegroundColor Green
        } catch {
            Write-Error $_
            continue
        }

        try {
            # Unzip the downloaded file
            Write-Host "`t`tExpanding archive..." -NoNewline
            $DestinationDirectory = Join-Path -Path $ExtensionFolder -ChildPath 'Downloaded' -AdditionalChildPath $ExtensionSpec.ProductName
            Expand-Archive -Path $DestinationFilePath -DestinationPath $DestinationDirectory -Force
            Write-Host 'Done' -ForegroundColor DarkGreen
        } catch {
            Write-Error $_
            continue
        }

        # Remove the .zip file now that its contents been extracted
        Remove-Item -Path $DestinationFilePath -Force

        if ($PSBoundParameters.ContainsKey('ProviderName')) {
            # Remove the actions and Datascripts that don't belong to the specified providers
            $ScriptPaths = @(
                (Join-Path -Path $DestinationDirectory -ChildPath '_REFERENCE DESIGN' 'Actions')
                (Join-Path -Path $DestinationDirectory -ChildPath '_REFERENCE DESIGN' 'ETL')
            )
            Get-ChildItem -Path $ScriptPaths -Directory -Exclude $ProviderName -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force

            # Remove the providers from providers.json that weren't specified in ProviderName
            $FilteredProviders = [System.Collections.ArrayList]::new()
            $ProviderFile = Join-Path -Path $DestinationDirectory -ChildPath '_REFERENCE DESIGN' -AdditionalChildPath 'Providers.json'
            $Providers = , @(Get-Content -Path $ProviderFile | ConvertFrom-Json -Depth 10)
            foreach ($Provider in $Providers) {
                if ($Provider.name -in $ProviderName) {
                    [void]($FilteredProviders.Add($Provider))
                }
            }
            ConvertTo-Json -Depth 10 -InputObject $FilteredProviders | Set-Content -Path $ProviderFile -Force
        }
    }

    ## Set the progress preference back
    $Global:ProgressPreference = $ProgressPreferenceBackup

    # if ($ShouldInstall -and !$EncounteredError) {
    # Install-TMExtension -Name $UserChoices.ExtensionsToDownload
    # }
}


function Get-TMLocalExtension {
    <#
    .SYNOPSIS
    Gets one or more Reference Designs that have been downloaded loacally
 
    .DESCRIPTION
    This function scans the C:\Users\<user>\TMD_Files\Reference Designs directory
    for the package.json file(s) associated with Reference Designs that have been
    downloaded locally
 
    .PARAMETER Name
    The name(s) of the downloaded Reference Design(s) to retrieve
 
    .PARAMETER List
    Switch indicating that only a list of downloaded Reference Design names should be returned
 
    .EXAMPLE
    Get-TMLocalExtension -Name 'vmware-vcenter', 'vmware-hcx'
 
    .EXAMPLE
    Get-TMLocalExtension -List
 
    .OUTPUTS
    An array of strings representing the downloaded Reference Design names if the -List switch is used
    otherwise a PSCustomObject for each name that was passed
    #>

    [alias('Get-TMLocalReferenceDesign')]
    [CmdletBinding(DefaultParameterSetName = 'Named')]
    [OutputType([PSCustomObject[]], ParameterSetName = 'Named')]
    [OutputType([String[]], ParameterSetName = 'List')]
    param (
        [Parameter(
            Mandatory = $false,
            ParameterSetName = 'Named',
            ValueFromPipeline = $true)]
        [String[]]$Name,

        [Parameter(Mandatory = $false, ParameterSetName = 'List')]
        [Switch]$List
    )

    begin {
        if (!(Get-Module 'TMD.Common')) {
            Import-Module 'TMD.Common'
        }
        $ExtensionDirectory = Join-Path $Global:userFilesRoot 'Reference Designs'
        $LocalExtensions = Get-ChildItem -Path $ExtensionDirectory -Filter 'package.json' -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object {
            $Package = Get-Content -Path $_.FullName -Raw | ConvertFrom-Json -Depth 100
            Add-Member -InputObject $Package -Name 'path' -Value (Split-Path $_.FullName) -MemberType NoteProperty -PassThru -Force
        }
    }

    process {
        if ($List) {
            $LocalExtensions | ForEach-Object {
                $_.Name
            }
        } else {
            if ($Name) {
                $Name | ForEach-Object {
                    $LocalExtensions | Where-Object Name -EQ $_
                }
            } else {
                $LocalExtensions
            }
        }
    }
}


function Import-TMExtension {
    <#
    .SYNOPSIS
    Imports a downloaded Reference Design to the specified project on the specified server
 
    .DESCRIPTION
    This function will apply the different configuration components of
    a Reference Design into a TransitonManager project.
 
    .PARAMETER Name
    The name of the locally downloaded Reference Design to apply/install
 
    .PARAMETER TMSession
    The name of the TM Session to use when creating a Team
 
    .PARAMETER Project
    The project in which the Reference Design will be applied
 
    .PARAMETER Include
    An array of components to include when performing the export.
    Options are:
        *
        ServerSettings
        ProjectSettings
        Code
        DependencyTypes
        FieldSpecs
        Bundles
        Providers
        Actions
        DataScripts
        Recipes
        AssetViews
        Events
        AppTypes
        DeviceTypes
        Teams
        TaskCategories
    NOTE: ServerSettings, ProjectSettings and Code are not components, but sets of components that are expanded to the following during runtime:
        ServerSettings: App Types, Dependency Types, Device Types, Teams, and Task Categories
        ProjectSettings: Asset Views, Bundles, Events, FieldSpecs, Providers, and Tags
        Code: Actions, DataScripts, Recipes, and (On Import Only) SetupScripts.
 
    .PARAMETER Exclude
    An array of components to skip when performing the import.
    Options are:
        ServerSettings
        ProjectSettings
        Code
        DependencyTypes
        FieldSpecs
        Bundles
        Providers
        Actions
        Datascripts
        Recipes
        Tags
        AssetViews
        Events
        AppTypes
        DeviceTypes
        Teams
        TaskCategories
        SetupScripts
    NOTE: ServerSettings, ProjectSettings and Code are not components, but sets of components that are expanded to the following during runtime:
        ServerSettings: App Types, Dependency Types, Device Types, Teams, and Task Categories
        ProjectSettings: Asset Views, Bundles, Events, FieldSpecs, Providers, and Tags
        Code: Actions, DataScripts, Recipes, and (On Import Only) SetupScripts.
 
    .PARAMETER SkipDependencies
    Switch indicating that the Reference Designs on which the named Reference Design
    depends on should not be imported
 
    .PARAMETER ProviderName
    One or more specific Providers to import. All other Providers will not be
    imported into the project
 
    .EXAMPLE
    $Credential = Get-StoredCredential -Name 'ME'
    $ImportSplat = @{
        Name = 'vmware-vcenter'
        Project = 'New Project - VMware vCenter'
        TMSession = 'TMAD60'
    }
    Import-TMExtension @ImportSplat
 
    .OUTPUTS
    None
    #>


    [alias('Import-TMReferenceDesign')]
    [CmdletBinding(DefaultParameterSetName = 'ByInclusion')]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true)]
        [ArgumentCompleter( { Get-TMLocalExtension -List } )]
        [String[]]$Name,

        [Parameter(Mandatory = $false, Position = 1)]
        [PSObject]$TMSession = 'Default',

        [Parameter(Mandatory = $false, Position = 2)]
        [String]$Project,

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByExclusion')]
        [ValidateSet(
            'ServerSettings',
            'ProjectSettings',
            'Code',
            'DependencyTypes',
            'FieldSpecs',
            'Bundles',
            'Providers',
            'Actions',
            'DataScripts',
            'Recipes',
            'Tags',
            'AssetData',
            'AssetViews',
            'Events',
            'AppTypes',
            'DeviceTypes',
            'Teams',
            'TaskCategories',
            'SetupScripts'
        )]
        [String[]]$Exclude,

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByInclusion')]
        [ValidateSet(
            '*',
            'ServerSettings',
            'ProjectSettings',
            'Code',
            'DependencyTypes',
            'FieldSpecs',
            'Bundles',
            'Providers',
            'Actions',
            'DataScripts',
            'Recipes',
            'Tags',
            'AssetData',
            'AssetViews',
            'Events',
            'AppTypes',
            'DeviceTypes',
            'Teams',
            'TaskCategories',
            'SetupScripts'
        )]
        [String[]]$Include = '*',

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

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

    begin {

        # As per TM-22256, ServerSettings, ProjectSettings, and Code will be used to contain a set of components to exclude/include.
        $ServerSettings = 'AppTypes',
        'DependencyTypes',
        'DeviceTypes',
        'Teams',
        'TaskCategories'
        $ProjectSettings = 'AssetViews',
        'Bundles',
        'Events',
        'FieldSpecs',
        'Providers',
        'Tags'
        $Code = 'Actions',
        'DataScripts',
        'Recipes',
        # (On Import Only)
        'SetupScripts'
        if ($Include -contains 'ServerSettings') { $Include += $ServerSettings }
        if ($Include -contains 'ProjectSettings') { $Include += $ProjectSettings }
        if ($Include -contains 'Code') { $Include += $Code }
        if ($Exclude -contains 'ServerSettings') { $Exclude += $ServerSettings }
        if ($Exclude -contains 'ProjectSettings') { $Exclude += $ProjectSettings }
        if ($Exclude -contains 'Code') { $Exclude += $Code }

        # Get the session configuration
        Write-Verbose 'Checking for cached TMSession'
        $TMSession = Get-TMSession $TMSession
        Write-Debug 'TMSession:'
        Write-Debug ($TMSession | ConvertTo-Json -Depth 5)
        if (-not $TMSession) {
            throw "TMSession '$TMSession' not found. Use New-TMSession command before using features."
        }

        if (!(Get-Module 'TMD.Common')) {
            Import-Module 'TMD.Common'
        }

        $FilterByProvider = $PSBoundParameters.ContainsKey('ProviderName')

        ## Tag Color Map
        ## PowerShell Colors:
        ## Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
        ## TM Tag Colors:
        ## 'Grey', 'Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Cyan', 'Purple', 'Pink', 'White'
        $TagColorToForeground = @{
            'Grey'   = 'DarkGray'
            'Red'    = 'Red'
            'Orange' = 'DarkYellow'
            'Yellow' = 'Yellow'
            'Green'  = 'Green'
            'Blue'   = 'Blue'
            'Cyan'   = 'Cyan'
            'Purple' = 'DarkMagenta'
            'Pink'   = 'Magenta'
            'White'  = 'White'
        }

        # Set up a configuration object to determine which components to install later in the process block
        $Components = @{
            DependencyTypes = $true
            FieldSpecs      = $true
            Bundles         = $true
            Providers       = $true
            Actions         = $true
            Datascripts     = $true
            Recipes         = $true
            Tags            = $true
            AssetViews      = $true
            Events          = $true
            AppTypes        = $true
            DeviceTypes     = $true
            Teams           = $true
            TaskCategories  = $true
            SetupScripts    = $true
        }

        switch ($PSCmdlet.ParameterSetName) {
            'ByInclusion' {
                if ($Include -notcontains '*') {
                    foreach ($Key in $Components.GetEnumerator().Name) {
                        if ($Include -notcontains $Key) {
                            $Components.$Key = $false
                        }
                    }
                }
            }

            'ByExclusion' {
                # Process the Exclude parameter and turn components off
                $ComponentSets = 'ServerSettings', 'ProjectSettings', 'Code'
                $Exclude | ForEach-Object {
                    #Component sets are not actual components, so we don't want to process them.
                    if ($_ -notin $ComponentSets) {
                        $Components.$_ = $false
                    }
                }
            }
        }

        # Get a list of all of the Reference Designs that have been saved locally
        $ExtensionDirectory = Join-Path $Global:userFilesRoot 'Reference Designs'
        $LocalExtensions = Get-TMLocalExtension
        if (!$LocalExtensions) {
            throw "No locally saved reference designs were found in $ExtensionDirectory. Use Get-TMExtension to download before installing."
        }

        # Determine the project to export
        if (-not [String]::IsNullOrWhiteSpace($Project) -and ($TMSession.userContext.project.name -ne $Project)) {
            Enter-TMProject -ProjectName $Project -TMSession $TMSession
        } else {
            $Project = $TMSession.userContext.project.name
        }
    }

    process {
        # Get the Extension spec from the passed Names and verify they are already downloaded
        $ExtensionToInstall = [System.Collections.Stack]::new()
        $DependencyCheckQueue = [System.Collections.Queue]@()
        foreach ($ExtensionName in $Name) {
            $ExtensionName = $ExtensionName.ToLower().Replace(' - ', '-').Replace(' ', '-')
            $Extension = $LocalExtensions | Where-Object Name -EQ $ExtensionName
            if (!$Extension) {
                throw "The reference design $ExtensionName has not been downloaded locally. Use Get-TMExtension and try again."
            }

            ## Check for Dependencies to add to the list before the Extension being installed
            if (!$SkipDependencies) {

                ## Add This Extension to the DependencyCheckList
                $DependencyCheckQueue.Enqueue($Extension)
            }

            ## Add the Extension being installed to the list, after dependencies are added
            $ExtensionToInstall.Push($Extension)
        }

        ## Check every Reference Design for Dependencies
        if (-Not $SkipDependencies) {

            Write-Host 'Checking Dependency Requirements'
            while ($DependencyCheckQueue.Count -ne 0) {

                # ## Check Each of the Extensions that are queued for installation for Dependencies
                # foreach ($ExtensionCheck in $ExtensionsToCheckDependenciesFor) {
                $ExtensionCheck = $DependencyCheckQueue.Dequeue()

                ## Remove Placeholder tokens provided by the template file creation
                $DependentExtensions = @()
                $ExtensionCheck.referenceDesign.dependencies.referenceDesigns | Where-Object { $_.name -ne '<PLACEHOLDER>' } | ForEach-Object {
                    if ($_) { $DependentExtensions += $_ }
                }
                $ExtensionCheck.extension.dependencies.extensions | Where-Object { $_.name -ne '<PLACEHOLDER>' } | ForEach-Object {
                    if ($_) { $DependentExtensions += $_ }
                }

                foreach ($DependencyItem in $DependentExtensions) {

                    ## Get the Highest version number that matches the requirement
                    $Dependency = $LocalExtensions
                    | Where-Object { $_.Name -eq $DependencyItem.Name -and $_.Version -ge $DependencyItem.Version }
                    | Sort-Object -Property 'version' -Descending
                    | Select-Object -First 1

                    ## Ensure a dependency was found
                    if (!$Dependency) {
                        throw "The dependency Reference Design $($DependencyItem.Name) (Minimum v$($DependencyItem.Version)) was not found on the local hard drive. Use Get-TMExtension and try again."
                    }

                    if ($ExtensionToInstall -notcontains $Dependency) {
                        Write-Host 'Required Prerequisite Extension also will be installed: ' -NoNewline
                        Write-Host $Dependency.name -ForegroundColor Cyan -NoNewline
                        Write-Host " v$($Dependency.version)" -ForegroundColor DarkGray
                        # $ExtensionToInstall += $Dependency
                        $ExtensionToInstall.Push($Dependency)
                        [void]($DependencyCheckQueue.Enqueue($Dependency))
                    }
                }
            }
        }
        ## Create an array of Setup Scripts to run
        $SetupScriptsToRun = [System.Collections.ArrayList]@()

        ## Iterate over each Reference Design to Install
        while ($ExtensionToInstall.Count) {

            ## Get the next item from the Stack
            $Extension = $ExtensionToInstall.Pop()

            Write-Host 'Importing: ' -NoNewline
            Write-Host $Extension.productName -NoNewline -ForegroundColor Cyan
            Write-Host ', Version: ' -NoNewline
            Write-Host $Extension.Version -ForegroundColor DarkGray

            $ExtensionPath = Join-Path $Extension.Path '_REFERENCE DESIGN'

            # Create the dependency types
            if ($Components.DependencyTypes) {
                Write-Host 'Loading Dependency Types...'
                $DependencyTypes = [system.collections.arraylist]@()
                $DependencyTypesFolderPath = Join-Path $ExtensionPath 'DependencyTypes'
                if (Test-Path $DependencyTypesFolderPath -ErrorAction SilentlyContinue) {
                    $DependencyTypeFiles = Get-ChildItem -Path $DependencyTypesFolderPath -ErrorAction SilentlyContinue
                    $DependencyTypeFiles | ForEach-Object {
                        [void]($DependencyTypes.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }

                } else {
                    $DependencyTypesFilePath = Join-Path $ExtensionPath 'DependencyTypes.json'
                    if (Test-Path $DependencyTypesFilePath -ErrorAction SilentlyContinue) {
                        $DependencyTypes = Get-ChildItem -Path $DependencyTypesFilePath -ErrorAction SilentlyContinue | Get-Content | ConvertFrom-Json
                    }
                }

                ## Check if there were any entries
                if ($DependencyTypes.Count -eq 0) {
                    Write-Host "`tNo Dependency Types to add"
                } else {

                    ## Get the Server's existing Dependency Types
                    $ServerDependencyTypes = Get-TMDependencyType -TMSession $TMSession
                    foreach ($DependencyType in $DependencyTypes) {

                        ## If the server doesn't have the dependency type
                        if ($ServerDependencyTypes.label -notcontains $DependencyType.label) {

                            ## Add it to the server
                            Write-Host "`tLoading Dependency Type: " -NoNewline
                            Write-Host "$($DependencyType.label)" -ForegroundColor DarkBlue
                            $DependencyType = [TMDependencyType]::new($DependencyType.label)
                            New-TMDependencyType -TMSession $TMSession -DependencyType $DependencyType
                        }
                    }
                }
            }

            # Create the App types
            if ($Components.AppTypes) {
                Write-Host 'Loading App Types...'
                $AppTypes = [system.collections.arraylist]@()
                $AppTypesFolderPath = Join-Path $ExtensionPath 'AppTypes'
                if (Test-Path $AppTypesFolderPath -ErrorAction SilentlyContinue) {
                    $AppTypeFiles = Get-ChildItem -Path $AppTypesFolderPath -ErrorAction SilentlyContinue
                    $AppTypeFiles | ForEach-Object {
                        [void]($AppTypes.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }
                } else {
                    $AppTypesFilePath = Join-Path $ExtensionPath 'AppTypes.json'
                    if (Test-Path $AppTypesFilePath -ErrorAction SilentlyContinue) {
                        $AppTypes = Get-Content -Path $AppTypesFilePath | ConvertFrom-Json
                    }
                }

                ## Check if there were any entries
                if ($AppTypes.Count -eq 0) {
                    Write-Host "`tNo App Types to add"
                } else {

                    ## Get the Server's existing App Types
                    $ServerAppTypes = Get-TMAssetOption -Type 'App Type' -TMSession $TMSession
                    foreach ($AppType in $AppTypes) {

                        ## If the server doesn't have the App type
                        if ($ServerAppTypes.label -notcontains $AppType.label) {

                            ## Add it to the server
                            Write-Host "`tLoading App Type: " -NoNewline
                            Write-Host "$($AppType.label)" -ForegroundColor DarkBlue
                            $AppType = [TMAppType]::new($AppType.label)
                            New-TMAssetOption -Type 'App Type' -Name $AppType.label -TMSession $TMSession
                        }
                    }
                }
            }

            # Create the Device types
            if ($Components.DeviceTypes) {
                Write-Host 'Loading Device Types...'
                $DeviceTypes = [system.collections.arraylist]@()
                $DeviceTypesFolderPath = Join-Path $ExtensionPath 'DeviceTypes'
                if (Test-Path $DeviceTypesFolderPath -ErrorAction SilentlyContinue) {
                    $DeviceTypeFiles = Get-ChildItem -Path $DeviceTypesFolderPath -ErrorAction SilentlyContinue
                    $DeviceTypeFiles | ForEach-Object {
                        [void]($DeviceTypes.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }
                } else {

                    $DeviceTypesFilePath = Join-Path $ExtensionPath 'DeviceTypes.json'
                    if (Test-Path $AppTypesFilePath -ErrorAction SilentlyContinue) {
                        $DeviceTypes = Get-Content -Path $DeviceTypesFilePath | ConvertFrom-Json
                    }
                }

                if ($DeviceTypes.Count -eq 0) {
                    Write-Host "`tNo Device Types to add"
                } else {

                    ## Get the Server's existing Device Types
                    $ServerDeviceTypes = Get-TMAssetOption -Type 'Asset Type' -TMSession $TMSession
                    foreach ($DeviceType in $DeviceTypes) {

                        ## If the server doesn't have the Device type
                        if ($ServerDeviceTypes.label -notcontains $DeviceType.label) {

                            ## Add it to the server
                            Write-Host "`tLoading Device Type: " -NoNewline
                            Write-Host "$($DeviceType.label)" -ForegroundColor DarkBlue
                            $DeviceType = [TMAssetType]::new($DeviceType.label)
                            New-TMAssetOption -Type 'Asset Type' -Name $DeviceType.label -TMSession $TMSession
                        }
                    }
                }
            }

            # Create the Task Categories
            if ($Components.TaskCategories) {
                Write-Host 'Loading Task Categories...'
                $TaskCategories = [system.collections.arraylist]@()
                $TaskCategoriesFolderPath = Join-Path $ExtensionPath 'TaskCategories'
                if (Test-Path $TaskCategoriesFolderPath -ErrorAction SilentlyContinue) {
                    $TaskCategoryFiles = Get-ChildItem -Path $TaskCategoriesFolderPath -ErrorAction SilentlyContinue
                    $TaskCategoryFiles | ForEach-Object {
                        [void]($TaskCategories.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }
                } else {
                    $TaskCategoriesFilePath = Join-Path $ExtensionPath 'TaskCategories.json'
                    if (Test-Path $TaskCategoriesFilePath -ErrorAction SilentlyContinue) {
                        $TaskCategories = Get-Content -Path $TaskCategoriesFilePath | ConvertFrom-Json
                    }
                }

                if ($TaskCategories.Count -eq 0) {
                    Write-Host "`tNo Task Categroies to add"
                } else {

                    ## Get the Server's existing Task Categories
                    $ServerTaskCategories = Get-TMAssetOption -Type 'Task Category' -TMSession $TMSession
                    foreach ($TaskCategory in $TaskCategories) {

                        ## If the server doesn't have the Device type
                        if ($ServerTaskCategories.label -notcontains $TaskCategory.label) {

                            ## Add it to the server
                            Write-Host "`tLoading Task Category: " -NoNewline
                            Write-Host "$($TaskCategory.label)" -ForegroundColor DarkBlue
                            $TaskCategory = [TMTaskCategory]::new($TaskCategory.label)
                            New-TMAssetOption -Type 'Task Category' -Name $TaskCategory.label -TMSession $TMSession
                        }
                    }
                }
            }

            # Load the Teams
            if ($Components.Teams) {
                Write-Host 'Loading Teams...'
                $Teams = [system.collections.arraylist]@()
                $TeamsFolderPath = Join-Path $ExtensionPath 'Teams'
                if (Test-Path $TeamsFolderPath -ErrorAction SilentlyContinue) {
                    $TeamFiles = Get-ChildItem -Path $TeamsFolderPath -ErrorAction SilentlyContinue
                    $TeamFiles | ForEach-Object {
                        [void]($Teams.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }
                } else {
                    $TeamsFilePath = Join-Path $ExtensionPath 'Teams.json'
                    if (Test-Path $TeamsFilePath -ErrorAction SilentlyContinue) {
                        $Teams = Get-Content -Path $TeamsFilePath | ConvertFrom-Json
                    }
                }

                if ($Teams.Count -eq 0) {
                    Write-Host "`tNo Teams to add"
                } else {
                    foreach ($Team in $Teams) {
                        Write-Host "`tLoading Team: " -NoNewline
                        Write-Host "$($Team.Code)" -ForegroundColor DarkBlue
                        New-TMTeam -Code $Team.Code -Description $Team.Description -Level $Team.Level -Help $Team.Help -TMSession $TMSession
                    }
                }
            }

            # Load the Tags
            if ($Components.Tags) {
                Write-Host 'Loading Tags...'
                $Tags = [system.collections.arraylist]@()
                $TagsFolderPath = Join-Path $ExtensionPath 'Tags'
                if (Test-Path $TagsFolderPath -ErrorAction SilentlyContinue) {
                    $TagFiles = Get-ChildItem -Path $TagsFolderPath -ErrorAction SilentlyContinue
                    $TagFiles | ForEach-Object {
                        [void]($Tags.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }
                } else {
                    $TagsFilePath = Join-Path $ExtensionPath 'Tags.json'
                    if (Test-Path $TagsFilePath -ErrorAction SilentlyContinue) {
                        $Tags = Get-Content -Path $TagsFilePath | ConvertFrom-Json
                    }
                }
                if ($Tags.Count -eq 0) {
                    Write-Host "`tNo Tags to add"
                } else {
                    foreach ($Tag in $Tags) {
                        Write-Host "`tLoading Tag: " -NoNewline
                        Write-Host "$($Tag.Name)" -ForegroundColor $TagColorToForeground.($Tag.Color)
                        New-TMTag -Name $Tag.Name -Description $Tag.description -Color $Tag.color -TMSession $TMSession
                    }
                }
            }

            # Load the Field Specs
            if ($Components.FieldSpecs) {
                Write-Host 'Loading Field Specs...'
                $FieldSpecs = $null

                ## Check Extension Package v3 (Folder of Files)
                $FieldSpecsFolderPath = Join-Path $ExtensionPath 'FieldSpecs'
                if (Test-Path $FieldSpecsFolderPath -ErrorAction SilentlyContinue) {

                    ## Create the FieldSpecs Object to add all of the fields to
                    $FieldSpecs = [pscustomobject]@{
                        APPLICATION = [PSCustomObject]@{
                            lastUpdated = ''
                            dateCreated = ''
                            domain      = 'APPLICATION'
                            fields      = @()
                            version     = 1
                        }
                        DATABASE    = [PSCustomObject]@{
                            lastUpdated = ''
                            dateCreated = ''
                            domain      = 'DATABASE'
                            fields      = @()
                            version     = 1
                        }
                        DEVICE      = [PSCustomObject]@{
                            lastUpdated = ''
                            dateCreated = ''
                            domain      = 'DEVICE'
                            fields      = @()
                            version     = 1
                        }
                        STORAGE     = [PSCustomObject]@{
                            lastUpdated = ''
                            dateCreated = ''
                            domain      = 'STORAGE'
                            fields      = @()
                            version     = 1
                        }
                    }

                    ## Handle Single JSON field files from folders
                    $SharedFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Shared') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue'
                    foreach ($FieldFile in $SharedFieldFiles) {
                        $FieldSpecs.APPLICATION.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                        $FieldSpecs.DATABASE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                        $FieldSpecs.DEVICE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                        $FieldSpecs.STORAGE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                    }

                    $ApplicationFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Application') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue'
                    foreach ($FieldFile in $ApplicationFieldFiles) {
                        $FieldSpecs.APPLICATION.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                    }

                    $DatabaseFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Database') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue'
                    foreach ($FieldFile in $DatabaseFieldFiles) {
                        $FieldSpecs.DATABASE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                    }

                    $DeviceFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Device') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue'
                    foreach ($FieldFile in $DeviceFieldFiles) {
                        $FieldSpecs.DEVICE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                    }

                    $StorageFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Storage') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue'
                    foreach ($FieldFile in $StorageFieldFiles) {
                        $FieldSpecs.STORAGE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                    }

                } else {

                    ## Check Extension Package <v3 (Single FieldSpecs.json file)
                    $FieldSpecsFilePath = Join-Path $ExtensionPath 'FieldSpecs.json'
                    if (Test-Path $FieldSpecsFilePath -ErrorAction SilentlyContinue) {

                        ## Version 6.0.2.1+ (and TODO version 6.1?, 5x?)
                        ## Requires the class names to be lower case
                        if ($TMSession.TMVersion -ge '6.1.0') {
                            ## Convert each Asset Class name to lowercase
                            $FieldSpecsData = ((Get-Content -Path $FieldSpecsFilePath) -replace 'constraints', 'constraint')
                        } else {
                            ## No conversion is necessary, assign the provided fields as the updates
                            $FieldSpecsData = (((Get-Content -Path $FieldSpecsFilePath) -replace 'constraint', 'constraints') -replace 'constraintss', 'constraints')
                        }
                        $FieldSpecs = ($FieldSpecsData | ConvertFrom-Json -ErrorAction SilentlyContinue)
                    }
                }

                ## Determine if any fields are needed
                if (-Not $FieldSpecs) {
                    Write-Host "`tNo Asset Field Specs to add"
                } else {
                    'APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE' | ForEach-Object {
                        $DomainColor = switch ($_) {
                            'APPLICATION' { 'DarkMagenta' }
                            'DEVICE' { 'DarkCyan' }
                            'DATABASE' { 'DarkYellow' }
                            'STORAGE' { 'DarkGreen' }
                        }
                        foreach ($Field in $FieldSpecs.$_.fields) {
                            Write-Host "`tLoading Field Spec: [" -NoNewline
                            Write-Host "$_" -ForegroundColor $DomainColor -NoNewline
                            Write-Host '] ' -NoNewline
                            Write-Host "$($Field.label)" -ForegroundColor DarkBlue
                        }
                    }
                    Update-TMFieldSpecs -FieldSpecs $FieldSpecs -TMSession $TMSession
                }
            }

            # Load the Bundles
            if ($Components.Bundles) {
                Write-Host 'Loading Bundles...'
                $Bundles = [system.collections.arraylist]@()
                $BundlesFolderPath = Join-Path $ExtensionPath 'Bundles'
                if (Test-Path $BundlesFolderPath -ErrorAction SilentlyContinue) {
                    $BundleFiles = Get-ChildItem -Path $BundlesFolderPath -ErrorAction SilentlyContinue
                    $BundleFiles | ForEach-Object {
                        [void]($Bundles.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }
                } else {
                    $BundlesFilePath = Join-Path $ExtensionPath 'Bundles.json'
                    if (Test-Path $BundlesFilePath -ErrorAction SilentlyContinue) {
                        $Bundles = Get-Content -Path $BundlesFilePath | ConvertFrom-Json
                    }
                }
                if ($Bundles.Count -eq 0) {
                    Write-Host "`tNo Bundles to add"
                } else {
                    foreach ($Bundle in $Bundles) {
                        Write-Host "`tLoading Bundle: " -NoNewline
                        Write-Host "$($Bundle.Name)" -ForegroundColor DarkBlue
                        New-TMBundle -Bundle $Bundle -TMSession $TMSession
                    }
                }
            }

            # Load the Events
            if ($Components.Events) {
                Write-Host 'Loading Events...'
                $Events = [system.collections.arraylist]@()
                $EventsFolderPath = Join-Path $ExtensionPath 'Events'
                if (Test-Path $EventsFolderPath -ErrorAction SilentlyContinue) {
                    $EventFiles = Get-ChildItem -Path $EventsFolderPath -ErrorAction SilentlyContinue
                    $EventFiles | ForEach-Object {
                        [void]($Events.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }
                } else {
                    $EventsFilePath = Join-Path $ExtensionPath 'Events.json'
                    if (Test-Path $EventsFilePath -ErrorAction SilentlyContinue) {
                        $Events = Get-Content -Path $EventsFilePath | ConvertFrom-Json
                    }
                }
                if ($Events.Count -eq 0) {
                    Write-Host "`tNo Events to add"
                } else {
                    foreach ($TMEvent in $Events) {
                        Write-Host "`tLoading Event: " -NoNewline
                        Write-Host "$($TMEvent.Name)" -ForegroundColor DarkBlue
                        New-TMEvent -InputObject $TMEvent -TMSession $TMSession
                    }
                }
            }

            # Load the Providers
            if ($Components.Providers) {
                Write-Host 'Loading Providers...'
                $Providers = [system.collections.arraylist]@()
                $ProvidersFolderPath = Join-Path $ExtensionPath 'Providers'
                if (Test-Path $ProvidersFolderPath -ErrorAction SilentlyContinue) {
                    $ProviderFiles = Get-ChildItem -Path $ProvidersFolderPath -ErrorAction SilentlyContinue
                    $ProviderFiles | ForEach-Object {
                        [void]($Providers.Add((Get-Content -Path $_.FullName | ConvertFrom-Json)))
                    }
                } else {
                    $ProvidersFilePath = Join-Path $ExtensionPath 'Providers.json'
                    if (Test-Path $ProvidersFilePath -ErrorAction SilentlyContinue) {
                        $Providers = Get-Content -Path $ProvidersFilePath | ConvertFrom-Json
                    }
                }
                if ($Providers.Count -eq 0) {
                    Write-Host "`tNo Providers to add"
                } else {
                    foreach ($Provider in $Providers) {
                        if ($FilterByProvider -and ($Provider.Name -notin $ProviderName)) {
                            continue
                        }
                        Write-Host "`tLoading Provider: " -NoNewline
                        Write-Host "$($Provider.Name)" -ForegroundColor DarkBlue
                        New-TMProvider -Provider $Provider -TMSession $TMSession
                    }
                }
            }

            # Load Actions
            if ($Components.Actions) {
                Write-Host 'Loading Actions...'
                $Actions = @()
                $ActionsPath = Join-Path $ExtensionPath 'Actions'
                if (Test-Path -Path $ActionsPath -ErrorAction SilentlyContinue) {
                    $Actions = Get-ChildItem -Path $ActionsPath -Recurse -Include '*.ps1' -ErrorAction SilentlyContinue | ForEach-Object {
                        Read-TMActionScriptFile -Path $_.FullName -TMSession $TMSession
                    }
                }

                if ($FilterByProvider) {
                    $Actions = $Actions | Where-Object { $_.Provider.Name -in $ProviderName }
                }

                if ($Actions.count -eq 0) {
                    Write-Host "`tNo Actions to add"
                } else {
                    foreach ($Action in $Actions) {
                        Write-Host "`tLoading Action: " -NoNewline
                        Write-Host "$($Action.Name)" -ForegroundColor DarkBlue
                        New-TMAction -Action $Action -Update -TMSession $TMSession
                    }
                }
            }

            # DataScripts
            if ($Components.Datascripts) {
                Write-Host 'Loading DataScripts...'
                $DataScripts = @()
                $DataScriptsPath = Join-Path $ExtensionPath 'DataScripts'
                if (Test-Path -Path $DataScriptsPath -ErrorAction SilentlyContinue) {
                    $DataScripts = Get-ChildItem -Path $DataScriptsPath -Recurse -Include '*.groovy' -ErrorAction SilentlyContinue | ForEach-Object {
                        Read-TMDatascriptFile -Path $_.FullName
                    }
                }

                if ($FilterByProvider) {
                    $DataScripts = $DataScripts | Where-Object { $_.Provider.Name -in $ProviderName }
                }

                if ($DataScripts.count -eq 0) {
                    Write-Host "`tNo DataScripts to add"
                } else {
                    foreach ($DataScript in $DataScripts) {
                        Write-Host "`tLoading DataScript: " -NoNewline
                        Write-Host "$($DataScript.Name)" -ForegroundColor DarkBlue
                        New-TMDatascript -Datascript $DataScript -Update -TMSession $TMSession
                    }
                }
            }

            # Recipes
            if ($Components.Recipes) {
                Write-Host 'Loading Recipes...'
                $Recipes = @()
                $RecipesPath = Join-Path $ExtensionPath 'Recipes'
                if (Test-Path -Path $RecipesPath -ErrorAction SilentlyContinue) {
                    $Recipes = Get-ChildItem -Path $RecipesPath -Recurse -Include '*.json' -ErrorAction SilentlyContinue | ForEach-Object {
                        Read-TMRecipeScriptFile -Path $_.FullName -UpdateFieldNames
                    }
                }

                if ($Recipes.count -eq 0) {
                    Write-Host "`tNo DataScripts to add"
                } else {
                    foreach ($Recipe in $Recipes) {
                        Write-Host "`tLoading Recipe: " -NoNewline
                        Write-Host "$($Recipe.Name)" -ForegroundColor DarkBlue
                        New-TMRecipe -Recipe $Recipe -Update -TMSession $TMSession
                    }
                }
            }

            # Load the AssetViews
            if ($Components.AssetViews) {

                ## Convert any Custom Field Label Tokens from a template
                ## Get a FieldToLabel map to build string replacements
                $AssetViews = [System.Collections.ArrayList]@()
                $LabelReplacements = [System.Collections.ArrayList]@()
                $FieldToLabelMap = Get-TMFieldToLabelMap -TMSession $TMSession

                ## Create a replacement token for each asset/label = field set
                foreach ($DomainClass in @('DEVICE', 'APPLICATION', 'DATABASE', 'STORAGE' )) {

                    foreach ($FieldName in $FieldToLabelMap.$DomainClass.Keys) {
                        if ($FieldName -match 'custom\d+') {

                            # Write-Host $FieldName
                            [void]($LabelReplacements.Add([pscustomobject]@{
                                        DomainClass = $DomainClass
                                        FieldLabel  = $FieldToLabelMap.$DomainClass.$FieldName
                                        FieldName   = $FieldName
                                    }
                                )
                            )
                        }
                    }
                }

                $AssetViewsFolderPath = Join-Path $ExtensionPath 'AssetViews'
                $AssetViewsFilePath = Join-Path $ExtensionPath 'AssetViews.json'

                Write-Host 'Loading AssetViews...'
                if (Test-Path $AssetViewsFolderPath -ErrorAction SilentlyContinue) {
                    $AssetViews = Get-ChildItem -Path $AssetViewsFolderPath -File -ErrorAction SilentlyContinue |
                        Where-Object { $_.Extension -in @('.json') } |
                        ForEach-Object {
                            Get-Content -Path $_.FullName | ConvertFrom-Json
                        }
                } else {
                    if (-Not (Test-Path $AssetViewsFilePath -ErrorAction SilentlyContinue)) {
                        Write-Host "`tNo AssetViews to add"
                    } else {
                        $AssetViews = Get-Content -Path $AssetViewsFilePath | ConvertFrom-Json
                        if ($AssetViews.Count -eq 0) { Write-Host "`tNo Asset Views to add" }
                    }
                }
                foreach ($AssetView in $AssetViews) {
                    Write-Host "`tLoading AssetView: " -NoNewline
                    Write-Host "$($AssetView.Name)" -ForegroundColor DarkBlue

                    ## Replace all 'customN' items with a codestring to replace the correct items in the offline package
                    $SourceCodeLines = ($AssetView | ConvertTo-Json -Depth 100) -split "`r`n" -split "`r" -split "`n"
                    $UpdatedSourceCodeLines = [System.Collections.ArrayList]::new()

                    foreach ($SourceCodeLine in $SourceCodeLines) {
                        $CurrentLine = $SourceCodeLine
                        $LabelReplacements | ForEach-Object {
                            $ReplaceString = "customN\|$($_.DomainClass)\|$($_.FieldLabel)\|"
                            if ($CurrentLine -match $ReplaceString) {
                                $CurrentLine = $CurrentLine -replace $ReplaceString, $_.FieldName
                            }
                        }
                        [void]($UpdatedSourceCodeLines.Add($CurrentLine))
                    }
                    $AssetView = $UpdatedSourceCodeLines -Join "`n" | ConvertFrom-Json

                    New-TMAssetViewConfiguration $AssetView -TMSession $TMSession -Update
                }
            }

            # SetupScripts
            if ($Components.SetupScripts) {
                Write-Host 'Collecting Setup Scripts...'
                $SetupScriptsFolderPath = Join-Path $ExtensionPath '_SETUP'
                if (-Not (Test-Path $SetupScriptsFolderPath -ErrorAction SilentlyContinue)) {
                    Write-Host "`tNo Setup Scripts to run"
                } else {
                    $SetupScriptFiles = Get-ChildItem -Path $SetupScriptsFolderPath -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -eq '.ps1' }
                    foreach ($SetupScriptFile in $SetupScriptFiles) {
                        Write-Host "`Queuing Setup Script: " -NoNewline
                        Write-Host "$($SetupScriptFile.Name)" -ForegroundColor DarkBlue
                        [void]$SetupScriptsToRun.Add($SetupScriptFile)
                    }
                }
            }
        }

        ## Sort the Setup Scripts to run by name
        $SortedSetupScripts = $SetupScriptsToRun | Sort-Object -Property 'Name'

        ## Iterate over each setup script and run them each
        foreach ($SetupScript in $SortedSetupScripts) {

            Write-Host "`tRunning Setup Script: " -NoNewline
            Write-Host "$($SetupScript.Name)" -ForegroundColor DarkBlue

            ## Try the script
            try {

                & "$($SetupScript.FullName)"
            } catch {
                Write-Host 'Setup Script Failed!'
                Write-Error $_
            }
        }
    }
}

function Export-TMExtension {
    <#
    .SYNOPSIS
    Exports a TransitionManager project into a portable configuration package
    that can be applied in a different project and/or on a different server
 
    .DESCRIPTION
    This function will retreieve the different configuration components from a
    TransitonManager project and save them locally in a standardized format
    that can then be used to import those configurations on another TM project
    or TM instance.
 
    .PARAMETER Name
    What the Reference Design will be named
 
    .PARAMETER TMSession
    The name of the TM Session to use when creating a Team
 
    .PARAMETER Project
    The project that will be exported
 
    .PARAMETER Include
    An array of components to include when performing the export.
    Options are:
        *
        ServerSettings
        ProjectSettings
        Code
        DependencyTypes
        FieldSpecs
        Bundles
        Providers
        Actions
        DataScripts
        Recipes
        AssetViews
        Events
        AppTypes
        DeviceTypes
        Teams
        TaskCategories
    NOTE: ServerSettings, ProjectSettings and Code are not components, but sets of components that are expanded to the following during runtime:
        ServerSettings: App Types, Dependency Types, Device Types, Teams, and Task Categories
        ProjectSettings: Asset Views, Bundles, Events, FieldSpecs, Providers, and Tags
        Code: Actions, DataScripts, Recipes, and (On Import Only) SetupScripts.
    .PARAMETER Exclude
    An array of components to skip when performing the export.
    Options are:
        ServerSettings
        ProjectSettings
        Code
        DependencyTypes
        FieldSpecs
        Bundles
        Providers
        Actions
        DataScripts
        Recipes
        AssetViews
        Events
        AppTypes
        DeviceTypes
        Teams
        TaskCategories
    NOTE: ServerSettings, ProjectSettings and Code are not components, but sets of components that are expanded to the following during runtime:
        ServerSettings: App Types, Dependency Types, Device Types, Teams, and Task Categories
        ProjectSettings: Asset Views, Bundles, Events, FieldSpecs, Providers, and Tags
        Code: Actions, DataScripts, Recipes, and (On Import Only) SetupScripts.
    .PARAMETER OutPath
    The directory where the Reference Design will be saved
 
    .PARAMETER PackageFile
    Produce an updated package.json file for the Extension
 
    .EXAMPLE
    $Credential = Get-StoredCredential -Name 'ME'
    $ExportSplat = @{
        Name = 'VMWare - vCenter'
        Server = 'tmddev.transitionmanager.net'
        Project = 'Extension - VMware vCenter'
        Credential = $Credential
        PackageFile = $true
    }
    Export-TMExtension @ExportSplat
 
    .NOTES
    -Force is always applied to the New-TMExtensionPackageFile command if -PackageFile is used.
    This evaluates the output path of the extension exported at the directory path targted.
 
    .OUTPUTS
    None
    #>


    [alias('Export-TMExtension')]
    [CmdletBinding(DefaultParameterSetName = 'ByInclusion')]
    param (
        [Parameter(Mandatory = $false, Position = 0)]
        [String]$Name,

        [Parameter(Mandatory = $false, Position = 1)]
        [PSObject]$TMSession = 'Default',

        [Parameter(Mandatory = $false, Position = 2)]
        [String]$Project,

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByInclusion')]
        [ValidateSet(
            '*',
            'ServerSettings',
            'ProjectSettings',
            'Code',
            'DependencyTypes',
            'FieldSpecs',
            'Bundles',
            'Providers',
            'Actions',
            'DataScripts',
            'Recipes',
            'Tags',
            'AssetData',
            'AssetViews',
            'Events',
            'AppTypes',
            'DeviceTypes',
            'Teams',
            'TaskCategories'
        )]
        [String[]]$Include = '*',

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByExclusion')]
        [ValidateSet(
            'ServerSettings',
            'ProjectSettings',
            'Code',
            'DependencyTypes',
            'FieldSpecs',
            'Bundles',
            'Providers',
            'Actions',
            'DataScripts',
            'Recipes',
            'Tags',
            'AssetData',
            'AssetViews',
            'Events',
            'AppTypes',
            'DeviceTypes',
            'Teams',
            'TaskCategories'
        )]
        [String[]]$Exclude,

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

        [Parameter(Mandatory = $false, Position = 5)]
        [AllowNull()]
        [String]$OutPath = '',

        [Parameter(Mandatory = $false, Position = 6)]
        [Switch]$ClearExistingFiles,

        [Parameter(Mandatory = $false, Position = 7)]
        [Switch]$PackageFile
    )

    begin {
        # As per TM-22256, ServerSettings, ProjectSettings, and Code will be used to contain a set of components to exclude/include.
        $ServerSettings = 'AppTypes',
        'DependencyTypes',
        'DeviceTypes',
        'Teams',
        'TaskCategories'
        $ProjectSettings = 'AssetViews',
        'Bundles',
        'Events',
        'FieldSpecs',
        'Providers',
        'Tags'
        $Code = 'Actions',
        'DataScripts',
        'Recipes'
        # (On Import Only)
        # 'SetupScripts'
        if ($Include -contains 'ServerSettings') { $Include += $ServerSettings }
        if ($Include -contains 'ProjectSettings') { $Include += $ProjectSettings }
        if ($Include -contains 'Code') { $Include += $Code }
        if ($Exclude -contains 'ServerSettings') { $Exclude += $ServerSettings }
        if ($Exclude -contains 'ProjectSettings') { $Exclude += $ProjectSettings }
        if ($Exclude -contains 'Code') { $Exclude += $Code }

        # Get the session configuration
        Write-Verbose 'Checking for cached TMSession'
        $TMSession = Get-TMSession $TMSession
        Write-Debug 'TMSession:'
        Write-Debug ($TMSession | ConvertTo-Json -Depth 5)
        if (-not $TMSession) {
            throw "TMSession '$TMSession' not found. Use New-TMSession command before using features."
        }

        if (!(Get-Module 'TMD.Common')) {
            Import-Module 'TMD.Common'
        }

        # Default to the reference design directory if an out path wasn't specified
        if ([String]::IsNullOrWhiteSpace($OutPath)) {
            Write-Verbose 'No OutPath as provided and will use the default export location'
            $OutPath = Join-Path -Path $Global:userPaths.referencedesigns -ChildPath 'Exported' -AdditionalChildPath $TMSession.TMServer
            $OutPath = Join-Path -Path $Global:userPaths.referencedesigns -ChildPath 'Exported' -AdditionalChildPath $TMSession.TMServer

            ## Create the Extension's root path
            Test-FolderPath -Path $OutPath
        }

        # Set up a configuration object to determine which components to install later in the process block
        $Components = @{
            DependencyTypes = $true
            FieldSpecs      = $true
            Bundles         = $true
            Providers       = $true
            Actions         = $true
            DataScripts     = $true
            Recipes         = $true
            Tags            = $true
            AssetData       = $true
            AssetViews      = $true
            Events          = $true
            AppTypes        = $true
            DeviceTypes     = $true
            Teams           = $true
            TaskCategories  = $true
        }

        switch ($PSCmdlet.ParameterSetName) {
            'ByInclusion' {
                if ($Include -notcontains '*') {
                    foreach ($Key in $Components.GetEnumerator().Name) {
                        if ($Include -notcontains $Key) {
                            $Components.$Key = $false
                        }
                    }
                }
            }

            'ByExclusion' {
                # Process the Exclude parameter and turn components off
                $ComponentSets = 'ServerSettings', 'ProjectSettings', 'Code'
                $Exclude | ForEach-Object {
                    #Component sets are not actual components, so we don't want to process them.
                    if ($_ -notin $ComponentSets) {
                        $Components.$_ = $false
                    }
                }
            }
        }

        # Write about what is going to be exported
        Write-Host 'Exporting Component Types from Project:'
        foreach ($Component in $Components.Keys) {

            ## For each Enabled Component
            if ($Components.$Component) {
                Write-Host "`t$Component" -ForegroundColor Cyan
            }
        }

        # Determine the project to export
        if (-not [String]::IsNullOrWhiteSpace($Project) -and ($TMSession.userContext.project.name -ne $Project)) {
            Enter-TMProject -ProjectName $Project -TMSession $TMSession
        } else {
            $Project = $TMSession.userContext.project.name
        }

        ## Set the name to the project name if it wasn't provided
        $Name = [String]::IsNullOrWhiteSpace($Name) ? $Project : $Name

        ## Write a Banner
        Write-Host 'Exporting TM Extension From:'
        Write-Host "`tServer: " -NoNewline
        Write-Host $TMSession.TMServer -ForegroundColor Cyan
        Write-Host "`tProject: " -NoNewline
        Write-Host $Project -ForegroundColor Cyan
        Write-Host "`tAt: " -NoNewline
        Write-Host (Get-Date).ToString() -ForegroundColor Cyan
        Write-Host "`tTo: " -NoNewline
        Write-Host $OutPath -ForegroundColor Cyan

        $FilterByProvider = $false
        Write-Host "`tProviders:`t" -NoNewline
        if ($ProviderName) {
            Write-Host $ProviderName -ForegroundColor Cyan
            $FilterByProvider = $true
        } else {
            Write-Host 'All Providers' -ForegroundColor Cyan
        }

        ## Clear existing files from the output folder
        if ($ClearExistingFiles) {
            Write-Host 'Export is Clearing Existing files before writing!!' -ForegroundColor Magenta

            ## Delete Files
            $ExistingFiles = Get-ChildItem -Path (Join-Path -Path $OutPath $Name -ChildPath '_REFERENCE DESIGN') -Recurse -File -Force -ErrorAction SilentlyContinue
            foreach ($ExistingFile in $ExistingFiles) {
                Write-Verbose "Removing Old File: $($ExistingFile.Name)"
                Remove-Item $ExistingFile -Force -Recurse
            }

            ## Delete Folders
            $ExistingFolders = Get-ChildItem -Path (Join-Path -Path $OutPath $Name -ChildPath '_REFERENCE DESIGN') -Recurse -Directory -Force -ErrorAction SilentlyContinue
            foreach ($ExistingFolder in $ExistingFolders) {
                Write-Verbose "Removing Old File: $($ExistingFolder.Name)"
                Remove-Item $ExistingFolder -Force -Recurse -ErrorAction 'SilentlyContinue'
            }
        }
    }

    process {
        # Set up the directory structure for all of the output files
        $OutputDirectory = Join-Path $OutPath $Name
        $ExtensionDirectory = Join-Path $OutputDirectory '_REFERENCE DESIGN'
        $ExtensionDataDirectory = Join-Path $OutputDirectory '_REFERENCE DATA'

        Test-FolderPath $ExtensionDirectory
        Test-FolderPath (Join-Path $OutputDirectory 'Documentation')
        Test-FolderPath (Join-Path $OutputDirectory '_REFERENCE DATA')

        ##
        ## Export all of the components
        ##

        # Export the Dependency Types
        if ($Components.DependencyTypes) {
            Write-Host 'Exporting Dependency Types...' -NoNewline
            $DependencyTypesFolderPath = Join-Path $ExtensionDirectory 'DependencyTypes'
            Test-FolderPath -FolderPath $DependencyTypesFolderPath

            $DependencyTypes = Get-TMDependencyType -ResetIDs -TMSession $TMSession
            if (!$DependencyTypes) {
                Write-Host "`tNo Dependency Types to export"
            } else {
                $DependencyTypes | ForEach-Object {
                    if (-Not [string]::IsNullOrWhiteSpace(${_.label}?.Trim())) {
                        $Filename = Get-FilenameSafeString -String $_.label
                        $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $DependencyTypesFolderPath "$Filename.json") -Force
                    }
                }
                Write-Host "`t$($DependencyTypes.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Dependency Type(s) exported'
            }
        }

        # Export the App Types
        if ($Components.AppTypes) {
            Write-Host "Exporting App Types...`t" -NoNewline
            $AppTypesFolderPath = Join-Path $ExtensionDirectory 'AppTypes'
            Test-FolderPath -FolderPath $AppTypesFolderPath

            $AppTypes = Get-TMAssetOption -Type 'App Type' -ResetIDs -TMSession $TMSession
            if (!$AppTypes) {
                Write-Host "`tNo App Types to export"
            } else {
                $AppTypes | ForEach-Object {
                    if (-Not [string]::IsNullOrWhiteSpace(${_.label}?.Trim())) {
                        $Filename = Get-FilenameSafeString -String $_.label
                        $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $AppTypesFolderPath "$Filename.json") -Force
                    }
                }
                Write-Host "`t$($AppTypes.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'App Type(s) exported'
            }
        }

        # Export the Device Types
        if ($Components.DeviceTypes) {
            Write-Host 'Exporting Device Types...' -NoNewline
            $DeviceTypesFolderPath = Join-Path $ExtensionDirectory 'DeviceTypes'
            Test-FolderPath -FolderPath $DeviceTypesFolderPath

            $DeviceTypes = Get-TMAssetOption -Type 'Asset Type' -ResetIDs -TMSession $TMSession
            if (!$DeviceTypes) {
                Write-Host "`tNo Device Types to export"
            } else {
                $DeviceTypes | ForEach-Object {
                    if (-Not [string]::IsNullOrWhiteSpace(${_.label}?.Trim())) {
                        $Filename = Get-FilenameSafeString -String $_.label
                        $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $DeviceTypesFolderPath "$Filename.json") -Force
                    }
                }
                Write-Host "`t$($DeviceTypes.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Device Type(s) exported'
            }
        }

        # Export the Task Categories
        if ($Components.TaskCategories) {
            Write-Host 'Exporting Task Categories...' -NoNewline
            $TaskCategoriesFolderPath = Join-Path $ExtensionDirectory 'TaskCategories'
            Test-FolderPath -FolderPath $TaskCategoriesFolderPath

            $TaskCategories = Get-TMAssetOption -Type 'Task Category' -ResetIDs -TMSession $TMSession
            if (!$TaskCategories) {
                Write-Host "`tNo Task Categories to export"
            } else {
                $TaskCategories | ForEach-Object {
                    if (-Not [string]::IsNullOrWhiteSpace(${_.label}?.Trim())) {
                        $Filename = Get-FilenameSafeString -String $_.label
                        $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $TaskCategoriesFolderPath "$Filename.json") -Force
                    }
                }
                Write-Host "`t$($TaskCategories.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Task Categories exported'
            }
        }

        # Export the Field Specs
        if ($Components.FieldSpecs) {
            Write-Host 'Exporting Field Specs...' -NoNewline
            $FieldSpecs = Get-TMFieldSpecs -ResetIDs -CustomOnly -TMSession $TMSession
            if (!$FieldSpecs) {
                Write-Host "`tNo Asset Field Specs to export"
            } else {
                $FieldSpecsFolderPath = Join-Path $ExtensionDirectory 'FieldSpecs'
                Test-FolderPath -FolderPath (Join-Path $FieldSpecsFolderPath 'Shared')
                Test-FolderPath -FolderPath (Join-Path $FieldSpecsFolderPath 'Application')
                Test-FolderPath -FolderPath (Join-Path $FieldSpecsFolderPath 'Database')
                Test-FolderPath -FolderPath (Join-Path $FieldSpecsFolderPath 'Device')
                Test-FolderPath -FolderPath (Join-Path $FieldSpecsFolderPath 'Storage')

                ## Iterate through each Domain Class
                ##### This is done in this order expressly described because shared fields will be UPDATED because it exists in each domain.
                ##### This order ensures that if there is variation in the color/order, etc. Application will win.
                ##### This is because the Shared fields are inserted with ONE configuration across each domain.
                $DomainClasses = @('STORAGE', 'DATABASE', 'DEVICE', 'APPLICATION')
                foreach ($DomainClass in $DomainClasses) {

                    ## Iterate over each Domain Field
                    $DomainFields = $FieldSpecs.$DomainClass.fields
                    foreach ($DomainField in $DomainFields) {

                        ## Get the Shared-or-Domain class
                        $FieldClass = ($DomainField.shared -eq 1 ? 'Shared' : $DomainClass)

                        ## Cast the file name to a safe character set
                        [string]$FieldFileName = (Get-FilenameSafeString ([string]$DomainField.order + '-' + $DomainField.label)) + '.json'
                        $FieldPath = Join-Path -Path $FieldSpecsFolderPath -ChildPath $FieldClass -AdditionalChildPath $FieldFileName
                        if ($DomainField.PSObject.Properties.Name.contains('constraints')) {
                            [void](Add-Member -InputObject $DomainField -NotePropertyName 'constraint' -NotePropertyValue $DomainField.constraints -Force)
                            $DomainField.PSObject.Properties.Remove('constraints')
                        }
                        if ($DomainField.PSObject.Properties.Name.contains('default')) {
                            [void](Add-Member -InputObject $DomainField -NotePropertyName 'defaultValue' -NotePropertyValue $DomainField.default -Force)
                            $DomainField.PSObject.Properties.Remove('defaultValue')
                        }
                        Set-Content -Path $FieldPath -Value ($DomainField | ConvertTo-Json -Depth 5) -Force
                    }
                }
                Write-Host "`tAsset Field Specs exported"
            }
        }

        # Export the Bundles
        if ($Components.Bundles) {
            Write-Host "Exporting Bundles...`t" -NoNewline
            $BundlesFolderPath = Join-Path $ExtensionDirectory 'Bundles'
            Test-FolderPath -FolderPath $BundlesFolderPath

            $Bundles = Get-TMBundle -ResetIDs -TMSession $TMSession
            if (!$Bundles) {
                Write-Host "`tNo Bundles to export"
            } else {
                $Bundles | ForEach-Object {
                    $Filename = Get-FilenameSafeString -String $_.Name
                    $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $BundlesFolderPath "$Filename.json") -Force
                }
                Write-Host "`t$($Bundles.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Bundle(s) exported'
            }
        }

        # Export the Events
        if ($Components.Events) {
            Write-Host "Exporting Events...`t" -NoNewline
            $EventsFolderPath = Join-Path $ExtensionDirectory 'Events'
            Test-FolderPath -FolderPath $EventsFolderPath

            $Events = Get-TMEvent -ResetIDs -TMSession $TMSession
            if (!$Events) {
                Write-Host "`tNo Events to export"
            } else {
                $Events | ForEach-Object {
                    $Filename = Get-FilenameSafeString -String $_.Name
                    $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $EventsFolderPath "$Filename.json") -Force
                }
                Write-Host "`t$($Events.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Event(s) exported'
            }
        }

        # Export the Teams
        if ($Components.Teams) {
            Write-Host "Exporting Teams...`t" -NoNewline
            $TeamsFolderPath = Join-Path $ExtensionDirectory 'Teams'
            Test-FolderPath -FolderPath $TeamsFolderPath

            $Teams = Get-TMTeam -TMSession $TMSession
            if (!$Teams) {
                Write-Host "`tNo Teams to export"
            } else {
                $Teams | ForEach-Object {
                    $Filename = Get-FilenameSafeString -String $_.Code
                    $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $TeamsFolderPath "$Filename.json") -Force
                }
                Write-Host "`t$($Teams.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Team(s) exported'
            }
        }

        # Export the Providers
        $Providers = Get-TMProvider -ResetIDs -TMSession $TMSession
        if (-not $FilterByProvider) {
            $ProviderName = $Providers.Name
        }
        if ($Components.Providers) {
            Write-Host "Exporting Providers...`t" -NoNewline
            $ProvidersFolderPath = Join-Path $ExtensionDirectory 'Providers'
            Test-FolderPath -FolderPath $ProvidersFolderPath

            if (!$Providers) {
                Write-Host "`tNo Providers to export"
            } else {
                if ($FilterByProvider) {
                    $Providers = $Providers | Where-Object Name -In $ProviderName
                }
                $Providers | ForEach-Object {
                    $Filename = Get-FilenameSafeString -String $_.Name
                    $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $ProvidersFolderPath "$Filename.json") -Force
                }
                Write-Host "`t$($Providers.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Provider(s) exported'
            }
        }

        # Export the Actions
        if ($Components.Actions) {

            Write-Host "Exporting Actions...`t" -NoNewline
            $ActionsFolderPath = Join-Path $ExtensionDirectory 'Actions'
            Test-FolderPath $ActionsFolderPath
            $Actions = Get-TMAction -SaveCodePath $ActionsFolderPath -ProviderName $ProviderName -ResetIDs -Passthru -TMSession $TMSession
            if (!$Actions) {
                Write-Host "`tNo Actions to export"
            } else {
                Write-Host "`t$($Actions.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Action(s) exported'
            }
        }

        # Export the DataScript Scripts
        if ($Components.DataScripts) {

            Write-Host 'Exporting DataScripts...' -NoNewline
            $DataScriptsFolderPath = Join-Path $ExtensionDirectory 'DataScripts'
            Test-FolderPath $DataScriptsFolderPath
            $DataScripts = Get-TMDatascript -ResetIDs -SaveCodePath $DataScriptsFolderPath -ProviderName $ProviderName -Passthru -TMSession $TMSession
            if (!$DataScripts) {
                Write-Host "`tNo Datascripts to export"
            } else {
                # TODO: Is the 'Private' folder supposed to be excluded?
                Write-Host "`t$($DataScripts.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Datascript(s) exported'
            }
        }

        # Export the Recipes
        if ($Components.Recipes) {

            Write-Host "Exporting Recipes...`t" -NoNewline
            $RecipesFolderPath = Join-Path $ExtensionDirectory 'Recipes'
            Test-FolderPath $RecipesFolderPath
            $Recipes = Get-TMRecipe -ResetIDs -SaveCodePath $RecipesFolderPath -Passthru -TMSession $TMSession
            if (!$Recipes) {
                Write-Host "`tNo Recipes to export"
            } else {
                Write-Host "`t$($Recipes.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Recipe(s) exported'
            }
        }

        # Export the Tags
        if ($Components.Tags) {
            Write-Host "Exporting Tags...`t" -NoNewline
            $TagsFolderPath = Join-Path $ExtensionDirectory 'Tags'
            Test-FolderPath -FolderPath $TagsFolderPath

            $Tags = Get-TMTag -ResetIDs -TMSession $TMSession
            if (!$Tags) {
                Write-Host "`tNo Tags to export"
            } else {
                $Tags | ForEach-Object {
                    $Filename = Get-FilenameSafeString -String $_.Name
                    $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $TagsFolderPath "$Filename.json") -Force
                }
                Write-Host "`t$($Tags.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Tags(s) exported'
            }
        }

        # Export the AssetViews
        if ($Components.AssetViews) {
            Write-Host "Exporting AssetViews...`t" -NoNewline
            $AssetViewsFolderPath = Join-Path $ExtensionDirectory 'AssetViews'
            Test-FolderPath -FolderPath $AssetViewsFolderPath

            $AssetViews = Get-TMAssetViewConfiguration -ResetIDs -TMSession $TMSession -ExcludeSystem
            if (!$AssetViews) {
                Write-Host "`tNo AssetViews to export"
            } else {

                ## Get a FieldToLabel map to build string replacements
                $LabelReplacements = [System.Collections.ArrayList]@()
                $FieldToLabelMap = Get-TMFieldToLabelMap -TMSession $TMSession

                ## Create a replacement token for each asset/label = field set
                foreach ($DomainClass in @('DEVICE', 'APPLICATION', 'DATABASE', 'STORAGE' )) {

                    foreach ($FieldName in $FieldToLabelMap.$DomainClass.Keys) {
                        if ($FieldName -match 'custom\d+') {

                            # Write-Host $FieldName
                            [void]($LabelReplacements.Add([pscustomobject]@{
                                        DomainClass = $DomainClass
                                        FieldLabel  = $FieldToLabelMap.$DomainClass.$FieldName
                                        FieldName   = $FieldName
                                    }
                                )
                            )
                        }
                    }
                }

                $AssetViews | ForEach-Object {
                    $AssetViewName = $_.name

                    ## Replace all customN in the JSON Data
                    $SourceCodeLines = ($_ | ConvertTo-Json -Depth 10) -split "`n"
                    $UpdatedSourceCodeLines = [System.Collections.ArrayList]::new()
                    foreach ($SourceCodeLine in $SourceCodeLines) {
                        $CurrentLine = $SourceCodeLine
                        $LabelReplacements | ForEach-Object {
                            if ($CurrentLine -match $_.FieldName) {
                                $CurrentLine = $CurrentLine -replace $_.FieldName, "customN|$($_.DomainClass)|$($_.FieldLabel)|"
                            }
                        }
                        [void]($UpdatedSourceCodeLines.Add($CurrentLine))
                    }
                    Test-FolderPath -FolderPath $AssetViewsFolderPath
                    $AssetViewFileName = Get-FilenameSafeString $AssetViewName
                    $UpdatedSourceCodeLines -join "`n" | Set-Content -Path (Join-Path $AssetViewsFolderPath "$AssetViewFileName.json") -Force
                }
                Write-Host "`t$($AssetViews.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'AssetView(s) exported'
            }
        }

        # Export Asset Data
        if ($Components.AssetData) {
            Write-Host 'Exporting Asset Data...' -NoNewline
            Export-TMExcel -Path $ExtensionDataDirectory -TMSession $TMSession
            Write-Host "`t`tAsset Data exported"
        }

        # Write-Host 'Creating package.json file...'
        # New-TMExtensionPackageFile -Name $Name -Path $OutputDirectory
    }
}

function New-TMExtensionPackageFile {
    <#
    .SYNOPSIS
    Creates a new package.json file for a locally downloaded/exported Reference Design
 
    .DESCRIPTION
    This function will look at the
 
    .PARAMETER Name
    Parameter description
 
    .PARAMETER Path
    Parameter description
 
    .PARAMETER Description
    Parameter description
 
    .PARAMETER Author
    Parameter description
 
    .PARAMETER Email
    Parameter description
 
    .PARAMETER Passthru
    A switch indicating that the created package object should be sent back
    to the caller
 
    .EXAMPLE
    New-TMExtensionPackageFile -Name 'TM - Rules Engine' -Path 'C:\DEV\Downloaded Reference Design'
 
    .EXAMPLE
    $PackageSplat = @{
        Name = VMware - vCenter'
        Path = 'C:\Users\user\TMD_Files\Reference Designs\Downloaded\VMware - vCenter'
        Description = 'This is a detailed description of the Reference Design'
        Author = 'Me'
        Email = 'me@tdsi.com'
    }
    New-TMExtensionPackageFile @PackageSplat
 
    .EXAMPLE
    $ExtensionPackage = New-TMExtensionPackageFile -Name 'New Extension' -Path 'C:\DEV\Extension' -Passthru
    Write-Host "There are $($ExtensionPackage.Inventory.actions.Count) action(s) included in this Reference Design"
 
    .OUTPUTS
    A [PSCustomObject] if -Passthru is specified, otherwise nothing
    #>


    [alias('New-TMReferenceDesignPackageFile')]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [String]$Name,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateScript( { (Get-Item $_) -is [System.IO.DirectoryInfo] })]
        [String]$Path,

        [Parameter(Mandatory = $false, Position = 2)]
        [String]$Description,

        [Parameter(Mandatory = $false, Position = 3)]
        [String]$Author,

        [Parameter(Mandatory = $false, Position = 4)]
        [String]$Email,

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

    )

    $Inventory = @{
        Artifacts       = [System.Collections.ArrayList]::new()
        ArtifactLinks   = [System.Collections.ArrayList]::new()
        Actions         = [System.Collections.ArrayList]::new()
        DataScripts     = [System.Collections.ArrayList]::new()
        Recipes         = [System.Collections.ArrayList]::new()
        Bundles         = [System.Collections.ArrayList]::new()
        Providers       = [System.Collections.ArrayList]::new()
        DependencyTypes = [System.Collections.ArrayList]::new()
        FieldSpecs      = @{}
    }
    $ExtensionPath = Join-Path $Path '_REFERENCE DESIGN'

    ## Read the Actions Folder to get the name of Scripts
    $ActionFiles = Get-ChildItem -Path (Join-Path $ExtensionPath 'Actions') -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.ps1' }
    foreach ($ActionFile in $ActionFiles) {

        ## Convert the Script into a proper ActionObject
        $Action = Read-TMActionScriptFile -Path $ActionFile.FullName -TMSession $TMSession

        ## Some files are not setup or created with all of the metadata. Fill in details based on the filename.
        if (-Not $Action.Name) {

            ## Add this Action to the Actions array for writing to JSON
            [void]$Inventory.Actions.Add(
                [PSCustomObject]@{
                    name        = $ActionFile.BaseName
                    description = ''
                }
            )
        } else {

            ## Add this Action to the Actions array for writing to JSON
            [void]$Inventory.Actions.Add(
                [PSCustomObject]@{
                    name        = $Action.Name
                    description = $Action.Description
                }
            )
        }
    }

    ## Read the DataScript Scripts Folder to get the name Scripts
    if (Test-Path($ExtensionPath + '\DataScripts')) {
        $DataScriptFiles = Get-ChildItem -Path (Join-Path $ExtensionPath 'DataScripts') -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.groovy' }
    } elseif (Test-Path($ExtensionPath + '\ETL')) {
        $DataScriptFiles = Get-ChildItem -Path (Join-Path $ExtensionPath 'ETL') -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.groovy' }
    }

    foreach ($DataScriptFile in $DataScriptFiles) {

        ## Convert the Script into a proper TMDatascript Object
        $DataScript_File = Read-TMDatascriptFile -Path $DataScriptFiles.FullName -ErrorAction 'SilentlyContinue'

        ## Some files are not setup or created with all of the metadata. Fill in details based on the filename.
        if (-Not $DataScript_File) {
            [void]$Inventory.DataScripts.Add(
                [PSCustomObject]@{
                    name        = $DataScript_File.name
                    description = ''
                }
            )
        } else {

            ## Add this DataScript Script to the array for writing to JSON
            [void]$Inventory.DataScripts.Add(
                [PSCustomObject]@{
                    name        = $DataScript_File.name
                    description = $DataScript_File.description
                }
            )
        }
    }

    ## Read the Recipes Folder to get the name Scripts
    $RecipeFiles = Get-ChildItem -Path (Join-Path $ExtensionPath 'Recipes') -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.json' }
    foreach ($RecipeFile in $RecipeFiles) {

        ## Convert the Script into a proper TMRecipeScript Object
        $Recipe = Read-TMRecipeScriptFile -Path $RecipeFile.FullName

        ## Some files are not setup or created with all of the metadata. Fill in details based on the filename.
        if (-Not $Recipe) {
            ## Add this Recipe Script to the array for writing to JSON
            [void]$Inventory.Recipes.Add(
                [PSCustomObject]@{
                    name        = $RecipeFile.name
                    description = $RecipeFile.description
                }
            )
        } else {

            ## Add this Recipe Script to the array for writing to JSON
            [void]$Inventory.Recipes.Add(
                [PSCustomObject]@{
                    name        = $Recipe.name
                    description = $Recipe.description
                }
            )
        }
    }
}