lib/ReferenceDesigns.ps1

function Get-TMReferenceDesign {
    <#
    .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-TMReferenceDesign -Name 'vmware-vcenter' -Credential $Credential -Dev

    .OUTPUTS
    None
    #>


    [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'

    $RDFolder = Join-Path $Global:userFilesRoot 'Reference Designs'
    $LocalReferenceDesigns = [System.Collections.ArrayList]::new()
    $ReferenceDesignsToDownload = [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"
        }
        $AvailableReferenceDesigns = 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 "$($AvailableReferenceDesigns.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 $AvailableReferenceDesigns.Count; $i++) {
        $AvailableReferenceDesigns[$i].Version = ConvertTo-SemanticVersion -VersionString $AvailableReferenceDesigns[$i].Version
        for ($j = 0; $j -lt $AvailableReferenceDesigns[$i].referenceDesign.dependencies.referenceDesigns.Count; $j++) {
            $AvailableReferenceDesigns[$i].referenceDesign.dependencies.referenceDesigns[$j].version = ConvertTo-SemanticVersion -VersionString $AvailableReferenceDesigns[$i].referenceDesign.dependencies.referenceDesigns[$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 RD objects
            if (!$PackageNames) {
                $AvailableReferenceDesigns | ForEach-Object {
                    $_
                }
                return
            }
            else {
                $PackageNames | ForEach-Object {
                    $RD = $AvailableReferenceDesigns | Where-Object name -EQ $_ |
                    Sort-Object -Property version | Select-Object -Last 1
                    if ($RD) {
                        [void]$ReferenceDesignsToDownload.Add($RD)
                    }
                }
            }
        }
        <#
        "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"

            $RDInstallerProc = [System.Diagnostics.Process]::new()
            $RDInstallerProc.StartInfo = $StartInfo

            # Start the UI and wait for it to exit so that the stdout can be parsed
            Write-Host "Showing UI..."
            $RDInstallerProc.Start()
            $RDInstallerProc.WaitForExit()
            $StdOut = $RDInstallerProc.StandardOutput.ReadToEnd()

            if ([String]::IsNullOrWhiteSpace($StdOut)) {
                throw "Output was not received from $(Split-Path $ExePath -Leaf)"
            }

            $UserChoices = $StdOut | ConvertFrom-Json
            $UserChoices.ReferenceDesignsToDownload | ForEach-Object {
                [void]$ReferenceDesignsToDownload.Add(
                    @{
                        Name = $_
                        Version = $null
                    }
                )
            }
            $ShouldInstall = $UserChoices.ShouldInstall
        }
        #>

    }

    # Make sure one or more available Reference Designs were found
    if ($ReferenceDesignsToDownload.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 $RDFolder '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 |
        Get-Content -Raw | ConvertFrom-Json | ForEach-Object {
            [void]$LocalReferenceDesigns.Add($_)
        }
    }
    catch {
        Write-Verbose $_
    }

    Write-Host "`tFound " -NoNewline
    Write-Host "$($LocalReferenceDesigns.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 = $ReferenceDesignsToDownload.Count
        for ($i = 0; $i -lt $Count; $i++) {
            $Design = $ReferenceDesignsToDownload[$i]
            foreach ($Dependency in $Design.referenceDesign.dependencies.referenceDesigns) {
                $AvailableRD = $AvailableReferenceDesigns | Where-Object Name -EQ $Dependency.name |
                Sort-Object -Property version | Select-Object -Last 1
                if ($AvailableRD) {
                    $LocalRD = $LocalReferenceDesigns | Where-Object Name -EQ $Dependency.name |
                    Sort-Object -Property version | Select-Object -Last 1
                    if ($LocalRD) {
                        if ($LocalRD.version -lt $Dependency.version) {
                            Write-Host "`tFound Reference Design to update: " -NoNewline
                            Write-Host "$($AvailableRD.Name) - v$($AvailableRD.Version.ToString())"
                            [void]$ReferenceDesignsToDownload.Add($AvailableRD)
                        }
                    }
                    else {
                        Write-Host "`tFound additional Reference Design to download: " -NoNewline
                        Write-Host "$($AvailableRD.Name) - v$($AvailableRD.Version.ToString())"
                        [void]$ReferenceDesignsToDownload.Add($AvailableRD)
                    }
                }
            }
        }
    }

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

        ## Resolve the file name and the destination path
        $ZipFileName = $RDSpec.Name + '-' + $RDSpec.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 $RDFolder 'Downloaded' $ZipFileName

        try {
            ## Call to Bitbucket to download the file
            Write-Host "`t`tDownloading $ZipFileName ($([Math]::Round(($RDSpec.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 $RDFolder 'Downloaded' $RDSpec.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 $DestinationDirectory '_REFERENCE DESIGN' 'Actions')
                (Join-Path $DestinationDirectory '_REFERENCE DESIGN' 'ETL')
            )
            Get-ChildItem -Path $ScriptPaths -Directory -Exclude $ProviderName | Remove-Item -Recurse -Force

            # Remove the providers from providers.json that weren't specified in ProviderName
            $FilteredProviders = [System.Collections.ArrayList]::new()
            $ProviderFile = Join-Path $DestinationDirectory '_REFERENCE DESIGN' '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-TMReferenceDesign -Name $UserChoices.ReferenceDesignsToDownload
    # }
}


function Get-TMLocalReferenceDesign {
    <#
    .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-TMLocalReferenceDesign -Name 'vmware-vcenter', 'vmware-hcx'

    .EXAMPLE
    Get-TMLocalReferenceDesign -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
    #>


    [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'
        }
        $RDDirectory = Join-Path $Global:userFilesRoot 'Reference Designs'
        $LocalReferenceDesigns = Get-ChildItem -Path $RDDirectory -Filter 'package.json' -Recurse -Force | 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
        }
    }

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


function Import-TMReferenceDesign {
    <#
    .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 Exclude
    An array of components to skip when performing the import.
    Options are:
        DependencyTypes
        FieldSpecs
        Bundles
        Providers
        Actions
        Datascripts
        Recipes
        Tags
        AssetViews
        Events
        AppTypes
        DeviceTypes
        Teams
        TaskCategories
        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-TMReferenceDesign @ImportSplat

    .OUTPUTS
    None
    #>


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

        [Parameter(Mandatory = $false, Position = 1)]
        [String]$TMSession = "Default",

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

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByExclusion')]
        [ValidateSet(
            '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(
            '*',
            '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 {
        # Get the session configuration
        Write-Verbose "Checking for cached TMSession"
        $TMSessionConfig = $global:TMSessions[$TMSession]
        Write-Debug "TMSessionConfig:"
        Write-Debug ($TMSessionConfig | ConvertTo-Json -Depth 5)
        if (-not $TMSessionConfig) {
            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
                $Exclude | ForEach-Object {
                    $Components.$_ = $false
                }
            }
        }

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

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

    process {
        # Get the RD spec from the passed Names and verify they are already downloaded
        $RDToInstall = [System.Collections.Stack]::new()
        $DependencyCheckQueue = [System.Collections.Queue]@()
        foreach ($RDName in $Name) {
            $RDName = $RDName.ToLower().Replace(' - ', '-').Replace(' ', '-')
            $RD = $LocalReferenceDesigns | Where-Object Name -EQ $RDName
            if (!$RD) {
                throw "The reference design $RDName has not been downloaded locally. Use Get-TMReferenceDesign 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($RD)
            }

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

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

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

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

                ## Remove Placeholder tokens provided by the template file creation
                $DependentRds = $ExtensionCheck.referenceDesign.dependencies.referenceDesigns | Where-Object { $_.name -ne '<PLACEHOLDER>' }

                foreach ($DependencyItem in $DependentRds) {

                    ## Get the Highest version number that matches the requirement
                    $Dependency = $LocalReferenceDesigns
                    | 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-TMReferenceDesign and try again."
                    }

                    if ($RDToInstall -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
                        # $RDToInstall += $Dependency
                        $RDToInstall.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 ($RDToInstall.Count) {

            ## Get the next item from the Stack
            $Rd = $RDToInstall.Pop()

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

            # $ReferenceDesignPath = $RD.Path
            $ReferenceDesignPath = Join-Path $RD.Path '_REFERENCE DESIGN'

            # Create the dependency types
            if ($Components.DependencyTypes) {
                Write-Host 'Loading Dependency Types...'
                $DependencyTypesFilePath = Join-Path $ReferenceDesignPath 'DependencyTypes.json'
                if (!(Test-Path $DependencyTypesFilePath)) {
                    Write-Host "`tNo Dependency Types to add"
                }
                else {

                    ## Get the Server's existing Dependency Types
                    $ServerDependencyTypes = Get-TMDependencyType -TMSession $TMSession

                    $DependencyTypes = Get-Content -Path $DependencyTypesFilePath | ConvertFrom-Json
                    if ($DependencyTypes.Count -eq 0) { Write-Host "`tNo Dependency Types to add" }
                    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
                            # New-TMAssetOption -TMSession $TMSession -Type 'Dependency Type' -InputObject [TMAssetOption]::new($DependencyType)
                        }
                    }
                }
            }

            # Create the App types
            if ($Components.AppTypes) {
                Write-Host 'Loading App Types...'
                $AppTypesFilePath = Join-Path $ReferenceDesignPath 'AppTypes.json'
                if (!(Test-Path $AppTypesFilePath)) {
                    Write-Host "`tNo App Types to add"
                }
                else {

                    ## Get the Server's existing App Types
                    $ServerAppTypes = Get-TMAssetOption -Type 'App Type' -TMSession $TMSession

                    $AppTypes = Get-Content -Path $AppTypesFilePath | ConvertFrom-Json
                    if ($AppTypes.Count -eq 0) { Write-Host "`tNo App Types to add" }
                    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...'
                $DeviceTypesFilePath = Join-Path $ReferenceDesignPath 'DeviceTypes.json'
                if (!(Test-Path $DeviceTypesFilePath)) {
                    Write-Host "`tNo Device Types to add"
                }
                else {

                    ## Get the Server's existing Device Types
                    $ServerDeviceTypes = Get-TMAssetOption -Type 'Asset Type' -TMSession $TMSession

                    $DeviceTypes = Get-Content -Path $DeviceTypesFilePath | ConvertFrom-Json
                    if ($DeviceTypesFilePath.Count -eq 0) { Write-Host "`tNo Device Types to add" }
                    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...'
                $TaskCategoriesFilePath = Join-Path $ReferenceDesignPath 'TaskCategories.json'
                if (!(Test-Path $TaskCategoriesFilePath)) {
                    Write-Host "`tNo Task Categories to add"
                }
                else {

                    ## Get the Server's existing Task Categories
                    $ServerTaskCategories = Get-TMAssetOption -Type 'Task Category' -TMSession $TMSession

                    $TaskCategories = Get-Content -Path $TaskCategoriesFilePath | ConvertFrom-Json
                    if ($TaskCategories.Count -eq 0) { Write-Host "`tNo Task Categories to add" }
                    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 Field Specs
            if ($Components.FieldSpecs) {
                Write-Host 'Loading Field Specs...'
                $FieldSpecsFilePath = Join-Path $ReferenceDesignPath 'FieldSpecs.json'
                if (!(Test-Path $FieldSpecsFilePath)) {
                    Write-Host "`tNo Asset Field Specs to add"
                }
                else {
                    $TMVersion = [Version]::parse($TMSessionConfig.TMVersion)

                    ## Version 6.0.2.1+ (and TODO version 6.1?, 5x?)
                    ## Requires the class names to be lower case
                    if ($TMVersion.Major -eq 6 -and $TMVersion -ge '6.1.0') {
                        ## Convert each Asset Class name to lowercase
                        $FieldSpecsTemp = ((Get-Content -Path $FieldSpecsFilePath) -replace 'constraints', 'constraint')
                    }
                    else {
                        ## No conversion is necessary, assign the provided fields as the updates
                        $FieldSpecsTemp = (((Get-Content -Path $FieldSpecsFilePath) -replace 'constraint', 'constraints') -replace 'constraintss', 'constraints')
                    }
                    $FieldSpecs = $FieldSpecsTemp | ConvertFrom-Json
                    '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...'
                $BundlesFilePath = Join-Path $ReferenceDesignPath 'Bundles.json'
                if (!(Test-Path $BundlesFilePath)) {
                    Write-Host "`tNo Bundles to add"
                }
                else {
                    $Bundles = Get-Content -Path $BundlesFilePath | ConvertFrom-Json
                    if ($Bundles.Count -eq 0) { Write-Host "`tNo Bundles to add" }
                    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...'
                $EventsFilePath = Join-Path $ReferenceDesignPath 'Events.json'
                if (!(Test-Path $EventsFilePath)) {
                    Write-Host "`tNo Events to add"
                }
                else {
                    $Events = Get-Content -Path $EventsFilePath | ConvertFrom-Json
                    if ($Events.Count -eq 0) { Write-Host "`tNo Events to add" }
                    foreach ($TMEvent in $Events) {
                        Write-Host "`tLoading Event: " -NoNewline
                        Write-Host "$($TMEvent.Name)" -ForegroundColor DarkBlue
                        New-TMEvent $TMEvent -TMSession $TMSession
                    }
                }
            }

            # Load the Teams
            if ($Components.Teams) {
                Write-Host 'Loading Teams...'
                $TeamsFilePath = Join-Path $ReferenceDesignPath 'Teams.json'
                if (!(Test-Path $TeamsFilePath)) {
                    Write-Host "`tNo Teams to add"
                }
                else {
                    $Teams = Get-Content -Path $TeamsFilePath | ConvertFrom-Json
                    if ($Teams.Count -eq 0) { Write-Host "`tNo Teams to add" }
                    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 Providers
            if ($Components.Providers) {
                Write-Host 'Loading Providers...'
                $ProvidersFilePath = Join-Path $ReferenceDesignPath 'Providers.json'
                if (-Not (Test-Path $ProvidersFilePath)) {
                    Write-Host "`tNo Providers to add"
                }
                else {
                    $Providers = Get-Content -Path $ProvidersFilePath | ConvertFrom-Json
                    if ($Providers.Count -eq 0) { Write-Host "`tNo Providers to add" }
                    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...'
                $ActionsFilePath = Join-Path $ReferenceDesignPath 'Actions.json'
                if ($FilterByProvider) {
                    $ActionsFolderPath = Get-ChildItem -Path (Join-Path $ReferenceDesignPath 'Actions') -Directory | Where-Object { $_.Name -in $ProviderName }
                }
                else {
                    $ActionsFolderPath = Join-Path $ReferenceDesignPath 'Actions'
                }

                if (-Not (Test-Path $ActionsFolderPath)) {
                    Write-Host "`tNo Actions to add"
                }
                else {
                    # TODO: Is the 'Private' folder supposed to be excluded?
                    if (Test-Path $ActionsFilePath) {
                        $ActionFiles = Get-Content -Path $ActionsFilePath | ConvertFrom-Json
                    }
                    else {
                        $ActionFiles = Get-ChildItem -Path $ActionsFolderPath -Recurse -File | Where-Object { $_.Extension -eq '.ps1' }
                    }

                    foreach ($ActionFile in $ActionFiles) {
                        if (Test-Path $ActionFile) {
                            $Action = Read-TMActionScriptFile -Path $ActionFile
                        }
                        else {
                            $Action = $ActionFile
                            ## Reads the Action source code from the local file and copies it into the JSON object.
                            $SourceFileScript = Read-TMActionScriptFile -Path (Get-ChildItem -Path $ActionsFolderPath -Recurse -File | Where-Object { $_.Name -eq ($Action.name + '.ps1') })
                            $Action.script = $SourceFileScript.script
                        }
                        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...'
                $DatascriptFilePath = Join-Path $ReferenceDesignPath 'Datascripts.json'
                if (Test-Path($ReferenceDesignPath + '\ETL')) {
                    if ($FilterByProvider) {
                        $DataScriptFolderPath = Get-ChildItem -Path (Join-Path $ReferenceDesignPath 'ETL') -Directory | Where-Object { $_.Name -in $ProviderName }
                    }
                    else {
                        $DataScriptFolderPath = Join-Path $ReferenceDesignPath 'ETL'
                    }
                }
                elseif (Test-Path($ReferenceDesignPath + '\DataScripts')) {
                    if ($FilterByProvider) {
                        $DataScriptFolderPath = Get-ChildItem -Path (Join-Path $ReferenceDesignPath 'DataScripts') -Directory | Where-Object { $_.Name -in $ProviderName }
                    }
                    else {
                        $DataScriptFolderPath = Join-Path $ReferenceDesignPath 'DataScripts'
                    }
                }


                if (-Not (Test-Path $DataScriptFolderPath)) {
                    Write-Host "`tNo DataScripts to add"
                }
                else {
                    ## Get the Datascript files from the Datascript folder
                    if (Test-Path $DatascriptFilePath) {
                        $DataScriptFiles = Get-Content -Path $DatascriptFilePath | ConvertFrom-Json
                    }
                    else {
                        $DataScriptFiles = Get-ChildItem -Path $DataScriptFolderPath -Recurse -File | Where-Object { $_.Extension -eq '.groovy' }
                    }

                    foreach ($DataScriptFile in $DataScriptFiles) {
                        if (Test-Path $DataScriptFile) {
                            $DataScript = Read-TMDatascriptFile -Path $DataScriptFile
                        }
                        else {
                            $DataScript = $DataScriptFile
                            ## Reads the Datascript source code from the local file and copies it into the JSON object.
                            $SourceFileScript = Read-TMDatascriptFile -Path (Get-ChildItem -Path $DataScriptFolderPath -Recurse -File | Where-Object { $_.Name -eq ($DataScript.name + '.groovy') })
                            $DataScript.etlSourceCode = $SourceFileScript.etlSourceCode
                        }

                        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...'

                $RecipesFolderPath = Join-Path $ReferenceDesignPath 'Recipes'
                $RecipeFilePath = Join-Path $ReferenceDesignPath 'Recipes.json'

                if (-Not (Test-Path $RecipesFolderPath)) {
                    Write-Host "`tNo Recipes to add"
                }
                else {
                    if (Test-Path $RecipeFilePath) {
                        $RecipeFiles = Get-Content -Path $RecipeFilePath | ConvertFrom-Json
                    }
                    else {
                        $RecipeFiles = Get-ChildItem -Path $RecipesFolderPath -File | Where-Object { $_.Extension -in @('.json') }
                    }

                    foreach ($RecipeFile in $RecipeFiles) {
                        if (Test-Path $RecipeFile) {
                            $Recipe = Read-TMRecipeScriptFile -Path $RecipeFile -UpdateFieldNames
                        }
                        else {
                            $Recipe = $RecipeFile
                            ## Reads the Recipe source code from the local file and copies it into the JSON object.
                            $SourceFileScript = Read-TMRecipeScriptFile -Path (Get-ChildItem -Path $RecipesFolderPath -Recurse -File | Where-Object { $_.Name -eq ($Recipe.name + '.groovy') })
                            $Recipe.sourceCode = $SourceFileScript.sourceCode
                        }

                        Write-Host "`tLoading Recipe: " -NoNewline
                        Write-Host "$($Recipe.Name)" -ForegroundColor DarkBlue
                        New-TMRecipe -Recipe $Recipe -Update -TMSession $TMSession
                    }
                }
            }

            # Load the Tags
            if ($Components.Tags) {
                Write-Host 'Loading Tags...'
                $TagsFilePath = Join-Path $ReferenceDesignPath 'Tags.json'
                if (-Not (Test-Path $TagsFilePath)) {
                    Write-Host "`tNo Tags to add"
                }
                else {
                    $Tags = Get-Content -Path $TagsFilePath | ConvertFrom-Json
                    if ($Tags.Count -eq 0) { Write-Host "`tNo Tags to add" }
                    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 AssetViews
            if ($Components.AssetViews) {

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

                ## 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 $ReferenceDesignPath 'AssetViews'
                $AssetViewsFilePath = Join-Path $ReferenceDesignPath 'AssetViews.json'

                Write-Host 'Loading AssetViews...'
                if (Test-Path $AssetViewsFolderPath) {
                    $AssetViews = Get-ChildItem -Path $AssetViewsFolderPath -File |
                        Where-Object { $_.Extension -in @('.json') } |
                        ForEach-Object {
                            Get-Content -Path $_.FullName | ConvertFrom-Json
                        }
                } else {
                    if (-Not (Test-Path $AssetViewsFilePath)) {
                        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 = ($AssetViews | 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 $ReferenceDesignPath '_SETUP'
                if (-Not (Test-Path $SetupScriptsFolderPath)) {
                    Write-Host "`tNo Setup Scripts to run"
                }
                else {
                    $SetupScriptFiles = Get-ChildItem -Path $SetupScriptsFolderPath -File | 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-TMReferenceDesign {
    <#
    .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 Exclude
    An array of components to skip when performing the export.
    Options are:
        DependencyTypes
        FieldSpecs
        Bundles
        Providers
        Actions
        DataScripts
        Recipes
        AssetViews
        Events
        AppTypes
        DeviceTypes
        Teams
        TaskCategories

    .PARAMETER OutPath
    The directory where the Reference Design will be saved

    .EXAMPLE
    $Credential = Get-StoredCredential -Name 'ME'
    $ExportSplat = @{
        Name = 'VMWare - vCenter'
        Server = 'tmddev.transitionmanager.net'
        Project = 'RD - VMware vCenter'
        Credential = $Credential
    }
    Export-TMReferenceDesign @ExportSplat

    .OUTPUTS
    None
    #>


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

        [Parameter(Mandatory = $false, Position = 1)]
        [String]$TMSession = "Default",

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

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByInclusion')]
        [ValidateSet(
            '*',
            '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(
            '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
    )

    begin {

        # Get the session configuration
        Write-Verbose "Checking for cached TMSession"
        $TMSessionConfig = $global:TMSessions[$TMSession]
        Write-Debug "TMSessionConfig:"
        Write-Debug ($TMSessionConfig | ConvertTo-Json -Depth 5)
        if (-not $TMSessionConfig) {
            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 $Global:userPaths.referencedesigns 'Exported' $TMSessionConfig.TMServer
        }

        # 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
                $Exclude | ForEach-Object {
                    $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 ($TMSessionConfig.userContext.project.name -ne $Project)) {
            Enter-TMProject -ProjectName $Project -TMSession $TMSession
        }
        else {
            $Project = $TMSessionConfig.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 $TMSessionConfig.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

        ## 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 $OutPath $Name '_REFERENCE DESIGN') -Recurse -File -Force
            foreach ($ExistingFile in $ExistingFiles) {
                Write-Verbose "Removing Old File: $($ExistingFile.Name)"
                Remove-Item $ExistingFile -Force -Recurse
            }

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

        $FilterByProvider = $false
        if ($ProviderName) {
            Write-Host 'Export limited to Provider: ' -NoNewline
            Write-Host $ProviderName -ForegroundColor Cyan
            $FilterByProvider = $true
        }
        else {
            Write-Host 'Collecting ' -NoNewline
            Write-Host 'All Providers' -ForegroundColor Cyan
        }
    }

    process {
        # Set up the directory structure for all of the output files
        $OutputDirectory = Join-Path $OutPath $Name
        $ReferenceDesignDirectory = Join-Path $OutputDirectory '_REFERENCE DESIGN'
        $ReferenceDataDirectory = Join-Path $OutputDirectory '_REFERENCE DATA'
        $ActionDirectory = Join-Path $ReferenceDesignDirectory 'Actions'
        $DataScriptDirectory = Join-Path $ReferenceDesignDirectory 'DataScripts'
        $RecipeDirectory = Join-Path $ReferenceDesignDirectory 'Recipes'

        Test-FolderPath $ReferenceDesignDirectory
        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
            $DependencyTypes = Get-TMDependencyType -ResetIDs -TMSession $TMSession
            if (!$DependencyTypes) {
                Write-Host "`tNo Dependency Types to export"
            }
            else {
                $DependencyTypesFilePath = Join-Path $ReferenceDesignDirectory 'DependencyTypes.json'
                $DependencyTypes | ConvertTo-Json -Depth 100 | Set-Content -Path $DependencyTypesFilePath -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
            $AppTypes = Get-TMAssetOption -Type 'App Type' -ResetIDs -TMSession $TMSession
            if (!$AppTypes) {
                Write-Host "`tNo App Types to export"
            }
            else {
                $AppTypesFilePath = Join-Path $ReferenceDesignDirectory 'AppTypes.json'
                $AppTypes | ConvertTo-Json -Depth 100 | Set-Content -Path $AppTypesFilePath -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
            $DeviceTypes = Get-TMAssetOption -Type 'Asset Type' -ResetIDs -TMSession $TMSession
            if (!$DeviceTypes) {
                Write-Host "`tNo Device Types to export"
            }
            else {
                $DeviceTypesFilePath = Join-Path $ReferenceDesignDirectory 'DeviceTypes.json'
                $DeviceTypes | ConvertTo-Json -Depth 100 | Set-Content -Path $DeviceTypesFilePath -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
            $TaskCategories = Get-TMAssetOption -Type 'Task Category' -ResetIDs -TMSession $TMSession
            if (!$TaskCategories) {
                Write-Host "`tNo Task Categories to export"
            }
            else {
                $TaskCategoriesFilePath = Join-Path $ReferenceDesignDirectory 'TaskCategories.json'
                $TaskCategories | ConvertTo-Json -Depth 100 | Set-Content -Path $TaskCategoriesFilePath -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 {
                $FieldSpecsFilePath = Join-Path $ReferenceDesignDirectory 'FieldSpecs.json'
                $FieldSpecs | ConvertTo-Json -Depth 100 | Set-Content -Path $FieldSpecsFilePath -Force
                Write-Host "`tAsset Field Specs exported"
            }
        }

        # Export the Bundles
        if ($Components.Bundles) {
            Write-Host "Exporting Bundles...`t" -NoNewline
            $Bundles = Get-TMBundle -ResetIDs -TMSession $TMSession
            if (!$Bundles) {
                Write-Host "`tNo Bundles to export"
            }
            else {
                $BundlesFilePath = Join-Path $ReferenceDesignDirectory 'Bundles.json'
                $Bundles | ConvertTo-Json -Depth 100 | Set-Content -Path $BundlesFilePath -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
            $Events = Get-TMEvent -ResetIDs -TMSession $TMSession
            if (!$Events) {
                Write-Host "`tNo Events to export"
            }
            else {
                $EventsFilePath = Join-Path $ReferenceDesignDirectory 'Events.json'
                $Events | ConvertTo-Json -Depth 100 | Set-Content -Path $EventsFilePath -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
            $Teams = Get-TMTeam -TMSession $TMSession
            if (!$Teams) {
                Write-Host "`tNo Teams to export"
            }
            else {
                $TeamsFilePath = Join-Path $ReferenceDesignDirectory 'Teams.json'
                $Teams | ConvertTo-Json -Depth 100 | Set-Content -Path $TeamsFilePath -Force
                Write-Host "`t$($Teams.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host "Team(s) exported"
            }
        }

        # Export the Providers
        if ($Components.Providers) {
            Write-Host "Exporting Providers...`t" -NoNewline
            $Providers = Get-TMProvider -ResetIDs -TMSession $TMSession
            if (!$Providers) {
                Write-Host "`tNo Providers to export"
            }
            else {
                if ($FilterByProvider) {
                    $Providers = $Providers | Where-Object Name -In $ProviderName
                }
                $ProvidersFilePath = Join-Path $ReferenceDesignDirectory 'Providers.json'
                $Providers | ConvertTo-Json -Depth 100 | Set-Content -Path $ProvidersFilePath -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
            Test-FolderPath $ActionDirectory
            $ActionsFolderPath = Join-Path $ReferenceDesignDirectory 'Actions'
            $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
            Test-FolderPath $DataScriptDirectory
            $DataScriptsFolderPath = Join-Path $ReferenceDesignDirectory 'DataScripts'
            $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
            Test-FolderPath $RecipeDirectory
            $RecipesFolderPath = Join-Path $ReferenceDesignDirectory 'Recipes'
            $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
            $Tags = Get-TMTag -ResetIDs -TMSession $TMSession
            if (!$Tags) {
                Write-Host "`tNo Tags to export"
            }
            else {
                $TasksFilePath = Join-Path $ReferenceDesignDirectory 'Tags.json'
                $Tags | ConvertTo-Json -Depth 100 | Set-Content -Path $TasksFilePath -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
            $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

                ## 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 $ReferenceDesignDirectory 'AssetViews'
                Test-FolderPath -FolderPath $AssetViewsFolderPath
                $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))
                    }
                    $UpdatedSourceCodeLines -join "`n" | Set-Content -Path (Join-Path $AssetViewsFolderPath "$AssetViewName.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 $ReferenceDataDirectory -TMSession $TMSession
            Write-Host "`t`tAsset Data exported"
        }

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

function New-TMReferenceDesignPackageFile {
    <#
    .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-TMReferenceDesignPackageFile -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-TMReferenceDesignPackageFile @PackageSplat

    .EXAMPLE
    $RDPackage = New-TMReferenceDesignPackageFile -Name 'New RD' -Path 'C:\DEV\RD' -Passthru
    Write-Host "There are $($RDPackage.Inventory.actions.Count) action(s) included in this Reference Design"

    .OUTPUTS
    A [PSCustomObject] if -Passthru is specified, otherwise nothing
    #>


    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      = @{}
    }
    $RDPath = Join-Path $Path '_REFERENCE DESIGN'

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

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

        ## 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($RDPath + '\DataScripts')) {
        $DataScriptFiles = Get-ChildItem -Path (Join-Path $RDPath 'DataScripts') -Recurse | Where-Object { $_.Name -like '*.groovy' }
    }
    elseif (Test-Path($RDPath + '\ETL')) {
        $DataScriptFiles = Get-ChildItem -Path (Join-Path $RDPath 'ETL') -Recurse | 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 $RDPath 'Recipes') -Recurse | 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
                }
            )
        }
    }

    # Get the bundles to add to the inventory
    $BundleFile = Join-Path $RDPath 'Bundles.json'
    if (Test-Path -Path $BundleFile) {
        Get-Content -Path $BundleFile | ConvertFrom-Json | ForEach-Object {
            [void]$Inventory.Bundles.Add(
                [PSCustomObject]@{
                    name        = $_.name
                    description = $_.description
                }
            )
        }
    }

    # Get the providers to add to the inventory
    $ProvidersFile = Join-Path $RDPath 'Providers.json'
    if (Test-Path $ProvidersFile) {
        Get-Content -Path $ProvidersFile | ConvertFrom-Json | ForEach-Object {
            [void]$Inventory.Providers.Add(
                [PSCustomObject]@{
                    name        = $_.name
                    description = $_.description
                }
            )
        }
    }

    # Get the Dependency Types to add to the inventory
    $DependencyTypesFile = Join-Path $RDPath 'DependencyTypes.json'
    if (Test-Path $DependencyTypesFile) {
        Get-Content -Path $DependencyTypesFile | ConvertFrom-Json | ForEach-Object {
            [void]$Inventory.DependencyTypes.Add($_.label)
        }
    }

    # Get the field specs to add to the inventory
    $FieldSpecsFile = Join-Path $RDPath 'FieldSpecs.json'
    if (Test-Path $FieldSpecsFile) {
        $AllFieldSpecs = Get-Content -Path $FieldSpecsFile | ConvertFrom-Json

        ## Determine if the property names are lowercase or uppercase
        $DomainClasses = @()
        if($AllFieldSpecs.PSObject.Properties.Name[0]){
            $DomainClasses = @('APPLICATION', 'DATABASE', 'DEVICE', 'STORAGE' )

        } else {
            $DomainClasses = @('application', 'database', 'device', 'storage' )

        }

        $DomainClasses | ForEach-Object {
            $Domain = $_
            $Inventory.FieldSpecs.Add($Domain, @())
            $AllFieldSpecs.$_.fields | ForEach-Object {
                $Inventory.FieldSpecs.$Domain += [PSCustomObject]@{
                    name = $_.label
                    type = $_.control
                }
            }
        }
    }

    # Create the package object which will be written to a file
    $Package = [PSCustomObject]@{
        name            = $Name.ToLower().Replace(' - ', '-').Replace(' ', '-')
        version         = '1.0.0'
        productName     = $Name
        description     = $Description ?? ''
        homepage        = ''
        email           = $Email ?? ''
        author          = $Author ?? ''
        published       = $false
        referenceDesign = @{
            compatibility = @{
                transitionManager = @{
                    minVer = ''
                    maxVer = ''
                }
                tmd               = @{
                    minVer = ''
                    maxVer = ''
                }
                powershell        = @{
                    minVer = ''
                    maxVer = ''
                }
                referenceDesigns  = @()
                designSpecific    = @()
            }
            dependencies  = @{
                installedApplications = @(
                    @{
                        name    = 'PowerShell'
                        type    = 'Software'
                        version    = "^$($PSVersionTable.PSVersion.ToString())"
                        source  = 'https://www.github.com/TransitionManager/TMD-Resources/PowerShell'
                    }
                )
                powershellModules     = @(
                    @{
                        name    = '<PLACEHOLDER>'
                        version = '1.0.0'
                        source  = ''
                    }
                )
                referenceDesigns      = @(
                    @{
                        name    = '<PLACEHOLDER>'
                        version = '1.0.0'
                    }
                )
            }
        }
        inventory       = $Inventory
    }

    # Create the file
    $Package | ConvertTo-Json -Depth 5 | Set-Content -Path (Join-Path $Path 'package.json') -Force

    # Send the package back if Passthru was specified
    if ($Passthru) {
        $Package
    }
}

function New-TMConfigInventory {
    <#
    .SYNOPSIS
    Creates a new inventory.json file with all of the configuration assets it contains

    .DESCRIPTION
    This function will collect all of the TM Server/Project configuration artifiacts to an inventory.json file



    .PARAMETER TMSession
    The TM Session to use

    .PARAMETER Path
    Output folder or file path for the inventory file. Include '.json' in the file name to create that file, otherwise the path is used as a Directory.

    .PARAMETER Passthru
    Return the TMConfigInventory File

    .EXAMPLE
    New-TMConfigInventory -TMProfile Default -Path 'C:\Temp\testfile.json'

    .OUTPUTS
    A [PSCustomObject] if -Passthru is specified, otherwise nothing
    #>


    param(
        [Parameter(Mandatory = $false, Position = 0)]
        [String]$TMSession = 'Default',

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

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

        [Parameter(Mandatory = $false, Position = 1)]
        [String]$OutPath,
        # (

        <#
            ## Root the folder on the User's Reference Design folder
            $UserPaths.referencedesigns

            ## Add a directory for the Server
            $Server

            ## Add a Folder for the TM Project
            $global:TMSessions[$TMSession].UserContext.Project.Name

            ## Name the file
            'Inventory.json'
            #>


        # Join-Path $UserPaths.referencedesigns $Server $global:TMSessions[$TMSession].UserContext.Project.Name 'Inventory.json'
        # ),

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

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

    )

    ## Test the optional input file path
    if ($OutPath) {

        ## Test if a Json file was named
        if ($OutPath -like '*.json') {

            ## Parse the path
            $ParentDirectory = Split-Path -Path $OutPath -Parent
            Test-FolderPath -FolderPath $ParentDirectory

        } elseif (-not ([String]::IsNullOrWhiteSpace($OutPath))) {
            Test-FolderPath -FolderPath $OutPath
            $OutPath += (Join-Path $OutPath 'Inventory.json').FullName
        }
    }


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

    ##
    ## Collect the Inventory
    ##

    ## Read the inventory path if there is one provided
    if (Test-Path $InventoryPath) {
        try {
            $Data = Get-Content $InventoryPath | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
        } catch {
            Write-Host 'The Inventory file could not be converted from json. Performing new Inventory using the TM Server settings supplied' -ForegroundColor Yellow
        }
    }

    ## Allow reading from a server connection, if no Inventory was provided
    if (-Not $Data) {

        ## Create the inventory Package
        $Data = @{
            Inventory     = @{

                ## Add Useful Data
                TMServer        = $TMServer
                TMProject       = $TMProject
                DateCollected   = (Get-Date).ToLongDateString()

                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      = @{}
                Artifacts       = [System.Collections.ArrayList]::new()
                ArtifactLinks   = [System.Collections.ArrayList]::new()
            }

            Relationships = @{
                Providers       = @{}
                Actions         = @{}
                DataScripts     = @{}
                Recipes         = @{}
                Bundles         = @{}
                Fields          = @{}
                DependencyTypes = @{}
            }
        }

        ## Add a 'TM Server'
        [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                Id   = "tmserver-$TMServer"
                Type = 'TMServer'
                Name = $TMServer
            }
        )
        ## Add a 'TM Project'
        [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                Id       = "tmproject-$TMProject"
                Type     = 'TMProject'
                Name     = $TMProject

                ParentId = $TMServer
            }
        )

        ## Collect FieldSpecs
        $Inventory.fieldSpecs = Get-TMFieldSpecs -TMSession $TMSession

        ## Add a 'FieldSpecs' object to the Artifact inventory (for proper diagram structuring)
        [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                Id       = 'tmfieldspecs'
                Type     = 'FieldSpecs'
                Name     = 'FieldSpecs'

                ParentId = "tmproject-$TMProject"
            }
        )

        ## Process each Domain Class
        foreach ($DomainClass in $Inventory.fieldSpecs.PSobject.Properties.Name) {
            ## Add an Artifact to the inventory
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmfieldspecdomain-$DomainClass"
                    Type     = 'FieldSpecDomain'
                    Name     = $DomainClass

                    ParentId = 'tmfieldspecs'
                }
            )
            ## Handle Each Domain Class field
            $ClassFields = $Inventory.fieldSpecs.$DomainClass.fields
            foreach ($Field in $ClassFields) {

                ## Add a property for this Field in the Relationships data
                $Data.Relationships.Fields.("$($DomainClass)-$($Field.label)") = @{
                    Actions         = [System.Collections.ArrayList]@()
                    DataScripts     = [System.Collections.ArrayList]@()
                    Recipes         = [System.Collections.ArrayList]@()
                    RecipeGroups    = [System.Collections.ArrayList]@()
                    RecipeTaskSpecs = [System.Collections.ArrayList]@()
                }

                ## Add a AssetClass for each domain to the inventory
                [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                        Id          = "tmassetfield-$($DomainClass)-$($Field.label)"
                        Type        = 'AssetField'
                        Name        = $Field.label

                        Constraints = $Field.constraints
                        DomainClass = $DomainClass
                        ParentId    = "tmfieldspecdomain-$DomainClass"
                    }
                )
            }
        }

        ## Collect Actions
        $Inventory.actions = Get-TMAction -Passthru -TMSession $TMSession
        foreach ($Item in $Inventory.actions) {

            ## Add a property for this Field in the Relationships data
            $Data.Relationships.Actions.($Item.name) = @{
                Fields          = [System.Collections.ArrayList]@()
                Recipes         = [System.Collections.ArrayList]@()
                RecipeTaskSpecs = [System.Collections.ArrayList]@()
            }

            ## Add an Artifact to the inventory
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmaction-$($Item.id)"
                    Type     = 'Action'
                    Name     = $Item.name

                    ParentId = "tmprovider-$($item.provider.id)"
                }
            )
        }

        ## Collect Data Scripts
        $Inventory.dataScripts = Get-TMDatascript -Passthru -TMSession $TMSession
        foreach ($Item in $Inventory.dataScripts) {

            ## Add a property for this Field in the Relationships data
            $Data.Relationships.DataScripts.($Item.name) = @{
                Fields = [System.Collections.ArrayList]@()
            }

            ## Add an Artifact to the inventory
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmdatascript-$($Item.id)"
                    Type     = 'DataScript'
                    Name     = $Item.name

                    ParentId = "tmprovider-$($item.provider.id)"
                }
            )
        }

        ## Collect Recipes
        $Inventory.recipes = Get-TMRecipe -AsJson -Passthru -TMSession $TMSession
        foreach ($Item in $Inventory.recipes) {

            ## Add a property for this Field in the Relationships data
            $Data.Relationships.Recipes.($Item.name) = @{
                Actions         = [System.Collections.ArrayList]@()
                RecipeGroups    = [System.Collections.ArrayList]@()
                RecipeTaskSpecs = [System.Collections.ArrayList]@()
                Fields          = [System.Collections.ArrayList]@()
            }

            ## Add an Artifact to the inventory
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmrecipe-$($Item.id)"
                    Type     = 'Recipe'
                    Name     = $Item.name

                    ParentId = "tmprovider-$($item.provider.id)"
                }
            )
        }

        ## Collect Bundles
        $Inventory.bundles = Get-TMBundle -TMSession $TMSession
        foreach ($Item in $Inventory.bundles) {

            ## Add an Artifact to the inventory
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmbundle-$($Item.id)"
                    Type     = 'Bundle'
                    Name     = $Item.name

                    ParentId = "tmproject-$TMProject"
                }
            )
        }

        ## Collect Providers
        $Inventory.providers = Get-TMProvider -TMSession $TMSession
        foreach ($Item in $Inventory.providers) {

            ## Add an Artifact to the inventory
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmprovider-$($Item.id)"
                    Type     = 'Provider'
                    Name     = $Item.name

                    ParentId = "tmproject-$TMProject"
                }
            )

            ## Add a Provider-Actions Artifact
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmprovider-actions-$($Item.id)"
                    Type     = 'ProviderActions'
                    Name     = $Item.name + ' Actions'

                    ParentId = "tmprovider-$($Item.id)"
                }
            )

            ## Add a Provider-Actions Artifact
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmprovider-datascripts-$($Item.id)"
                    Type     = 'ProviderActions'
                    Name     = $Item.name + ' Data Scripts'

                    ParentId = "tmprovider-$($Item.id)"
                }
            )
        }

        ## Collect Dependency Types
        $Inventory.dependencyTypes = Get-TMDependencyType -TMSession $TMSession
        foreach ($Item in $Inventory.dependencyTypes) {

            ## Add an Artifact to the inventory
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmdependencytype-$($Item.label)"
                    Type     = 'DependencyType'
                    Name     = $Item.label

                    ParentId = "tmserver-$TMServer"
                }
            )
        }

        ##
        ## Save the Inventory data for convenience
        ##
        if (-Not ([String]::IsNullOrWhiteSpace($InventoryPath))) {
            $Data | ConvertTo-Json -Depth 100 -Compress | Set-Content -Path $InventoryPath -Force
        }
    }

    ##
    ## Calculate the Artifact Relationships
    ##

    ## ACTION(parameter) > Asset Field
    foreach ($Action in $Inventory.actions) {

        if ($Action.MethodParams) {

            ## Iterater on each Method Parameter
            foreach ($Param in $Action.methodParams) {

                ## only map params that use a field label
                if ($Param.context -ne 'USER_DEF' -and $Param.fieldLabel) {

                    ## Record the connection in the hashmap
                    [void]($Data.Relationships.Actions.($Action.Name).Fields.Add("$($Param.context)-$($Param.fieldLabel)"))

                    ## Add the Artifact Link
                    [void]$Inventory.ArtifactLinks.Add([PSCustomObject]@{
                            Id   = "tmaction-$($Action.Id)-tmassetfield-$($Param.context)-$($Param.fieldLabel)"
                            From = "tmaction-$($Action.Id)"
                            To   = "tmassetfield-$($Param.context)-$($Param.fieldLabel)"
                            type = 'TM Server hosts Project'
                        }
                    )
                }
            }
        }
    }

    ## Recipe > Actions, Groups, TaskSpecs
    foreach ($Recipe in $Inventory.recipes) {

        ## Create a Groups object
        [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                Id       = "tmrecipe-$($Recipe.id)-groups"
                Type     = 'RecipeGroup'
                Name     = $Recipe.name + ' Groups'

                ParentId = "tmrecipe-$($Recipe.id)"
            }
        )

        ## Create a TaskSpecs object
        [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                Id       = "tmrecipe-$($Recipe.id)-taskspecs"
                Type     = 'RecipeGroup'
                Name     = $Recipe.name + ' Groups'

                ParentId = "tmrecipe-$($Recipe.id)"
            }
        )

        ## Check each Group in the recipe
        foreach ($Group in $Recipe.sourceCodeJson.groups) {

            ## Record the connection in the hashmap
            [void]($Data.Relationships.Recipes.($Recipe.Name).RecipeGroups.Add($Group.name))

            ## Record the Group
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmrecipegroup-$($Recipe.id)-$($group.name)"
                    Type     = 'RecipeGroup'
                    Name     = $group.name

                    ParentId = "tmrecipe-$($Recipe.id)-groups"
                }
            )

            ## TODO - Add Group Asset Field resolution so the groups can be bound to the Asset-Fields
        }

        ## Check each Task Spec in the recipe
        foreach ($TaskSpec in $Recipe.sourceCodeJson.tasks) {

            ## Record the connection in the hashmap
            [void]($Data.Relationships.Recipes.($Recipe.Name).RecipeTaskSpecs.Add($TaskSpec.title))

            ## Record the Task Spec
            [void]$Inventory.Artifacts.Add([PSCustomObject]@{
                    Id       = "tmtaskspec-$($Recipe.id)-$($TaskSpec.id)"
                    Type     = 'RecipeTaskSpec'
                    Name     = $TaskSpec.title

                    ParentId = "tmrecipe-$($Recipe.id)-taskspecs"
                }
            )

            ## Record Tasks that have a method assigned
            if ($TaskSpec.invoke.method) {

                ## Record the connection in the hashmap
                [void]($Data.Relationships.Recipes.($Recipe.Name).Actions.Add($TaskSpec.invoke.method))

                [void]$Inventory.ArtifactLinks.Add([PSCustomObject]@{
                        Id   = "tmaction-$($Action.Id)-tmtaskspec-$($Recipe.id)-$($TaskSpec.id)"
                        From = "tmaction-$($Action.Id)"
                        To   = "tmtaskspec-$($Recipe.id)-$($TaskSpec.id)"
                        type = 'TM Server hosts Project'
                    }
                )
            }

            ## Record Tasks that have a method assigned
            if ($TaskSpec.filter.group) {

                foreach ($Group in $TaskSpec.filter.group) {

                    ## Record the connection in the hashmap
                    [void]($Data.Relationships.Recipes.($Recipe.Name).RecipeGroups.Add($Group.name))

                    [void]$Inventory.ArtifactLinks.Add([PSCustomObject]@{
                            Id   = "tmaction-$($Action.Id)-tmtaskspec-$($Recipe.id)-$($TaskSpec.id)"
                            From = "tmaction-$($Action.Id)"
                            To   = "tmrecipegroup-$($Recipe.id)-$($group.name)"
                            type = 'TM Server hosts Project'
                        }
                    )
                }
            }
        }
    }


    ## Datascripts to
    ## > Asset Field

    # Send the package back if Passthru was specified
    if ($Passthru) {
        $Data
    }

    # ## Create a flat inventory of Artifacts


    # # Send the package back if Passthru was specified
    # if ($Passthru) {
    # $Inventory
    # }
}
function Read-TMConfigInventory {
    <#
    .SYNOPSIS
    Creates a new inventory.json file with all of the configuration assets it contains, as read from an Extension directory

    .DESCRIPTION
    This function will read all of the TM Entities within an Extension directory

    .PARAMETER Path
    Path of the TM Extension to read

    .PARAMETER OutPath
    Filename to create containing the inventory

    .PARAMETER Passthru
    Return the TMConfigInventory File

    .EXAMPLE
    Read-TMConfigInventory -Path 'C:\Users\tbaker\TMD_Files\Reference Designs\EXTENSIONS\VMware\vCenter' -OutPath 'C:\Temp\testfile.json' -Passthru

    .OUTPUTS
    A [PSCustomObject] if -Passthru is specified, otherwise nothing
    #>


    param(

        [Parameter(Mandatory = $true, Position = 1)]
        [String]$Path,

        [Parameter(Mandatory = $false, Position = 1)]
        [String]$OutPath,

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

    )

    ## Create the inventory Package
    $Inventory = @{
        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      = @{}
        Artifacts       = [System.Collections.ArrayList]::new()
        ArtifactLinks   = [System.Collections.ArrayList]::new()

        Relationships = @{
            Providers       = @{}
            Actions         = @{}
            DataScripts     = @{}
            Recipes         = @{}
            Bundles         = @{}
            Fields          = @{}
            DependencyTypes = @{}
        }
    }

    ## Lookup the local package file to get the name and
    $PackageFilePath = "$Path\package.json"
    if(Test-Path $PackageFilePath){
        $PackageData = Get-Content -Path $PackageFilePath | ConvertFrom-Json
        $Inventory.Extension = @{
            Name = $PackageData.name
            ProductName = $PackageData.productName
        }
    }

    ## Update the path to the internal _REFERENCE DESIGN folder
    if($Path -notlike "*_REFERENCE DESIGN"){
        $Path = "$Path\_REFERENCE DESIGN"
    }

    ##
    ## Collect the Inventory
    ##
    #

    ## Collect JSON File data
    $ErrorActionPreference = 'SilentlyContinue'
    $Inventory.AppTypes = Get-Content (Join-Path $Path 'AppTypes.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
    $Inventory.Bundles = Get-Content (Join-Path $Path 'Bundles.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
    $Inventory.DeviceTypes = Get-Content (Join-Path $Path 'DeviceTypes.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
    $Inventory.Providers = Get-Content (Join-Path $Path 'Providers.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
    $Inventory.Tags = Get-Content (Join-Path $Path 'Tags.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
    $Inventory.TaskCategories = Get-Content (Join-Path $Path 'TaskCategories.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
    $Inventory.Teams = Get-Content (Join-Path $Path 'Teams.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
    $Inventory.FieldSpecs = Get-Content (Join-Path $Path 'FieldSpecs.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
    $ErrorActionPreference = 'Continue'

    ## Create Relationship properties for each of the fields
    # foreach ($DomainClass in $Inventory.FieldSpecs.PSobject.Properties.Name) {

    # ## Handle Each Domain Class field
    # $ClassFields = $Inventory.fieldSpecs.$DomainClass.fields
    # foreach ($Field in $ClassFields) {

    # ## Add a property for this Field in the Relationships data
    # $Inventory.Relationships.Fields.("$($DomainClass)-$($Field.label)") = @{
    # Actions = [System.Collections.ArrayList]@()
    # DataScripts = [System.Collections.ArrayList]@()
    # Recipes = [System.Collections.ArrayList]@()
    # RecipeGroups = [System.Collections.ArrayList]@()
    # RecipeTaskSpecs = [System.Collections.ArrayList]@()
    # }
    # }
    # }

    ## Create Relationship properties for each of the Providers
    # foreach ($Provider in $Inventory.Providers) {
    # $Inventory.Relationships.Providers.($Provider.Name) = @{
    # Actions = [System.Collections.ArrayList]@()
    # DataScripts = [System.Collections.ArrayList]@()
    # }
    # }

    ## Collect Actions
    $ActionFiles = Get-ChildItem -Path "$Path\Actions" -Recurse -Filter "*.ps1"
    foreach ($Item in $ActionFiles) {

        $Action = Read-TMActionScriptFile -Path $Item.FullName

        ## Add an Artifact to the inventory
        [void]$Inventory.Actions.Add($Action)

        ## Add a property for this Field in the Relationships data
        # $Inventory.Relationships.Actions.($Action.name) = @{
        # Provider = $Action.provider.name
        # Fields = [System.Collections.ArrayList]@()
        # Recipes = [System.Collections.ArrayList]@()
        # RecipeTaskSpecs = [System.Collections.ArrayList]@()
        # }

        # ## Add this Action to the Provider's list as well
        # [void]($Inventory.Relationships.Providers.($Action.provider.name).Actions.Add($Action.Name))
    }

    ## Collect Data Scripts
    $DataScriptFiles = Get-ChildItem -Path "$Path\DataScripts" -Recurse -Filter "*.groovy"
    foreach ($Item in $DataScriptFiles) {

        $DataScript = Read-TMDatascriptFile -Path $Item.FullName

        ## Add an Artifact to the inventory
        [void]$Inventory.DataScripts.Add($DataScript)

        ## Add a property for this Field in the Relationships data
        # $Inventory.Relationships.DataScripts.($DataScript.name) = @{
        # Provider = $ActioDatascript.provider.name
        # Fields = [System.Collections.ArrayList]@()
        # }

        # ## Add this Datascript to the Provider's list as well
        # [void]($Inventory.Relationships.Providers.($DataScript.provider.name).Datascripts.Add($DataScript.Name))
    }

    ## Create a flat inventory of Artifacts


    # Send the package back if Passthru was specified
    if (-not ([String]::IsNullOrWhiteSpace($OutPath))) {
        $Inventory | ConvertTo-Json -Depth 100 | Set-Content -Path $OutPath -Force
    }

    # Send the package back if Passthru was specified
    if ($Passthru) {
        $Inventory
    }
}