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 ETL scripts 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
        ETLScripts
        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',
            'ETLScripts',
            'Recipes',
            'Tags',
            'AssetData',
            'AssetViews',
            'Events',
            'AppTypes',
            'DeviceTypes',
            'Teams',
            'TaskCategories'
        )]
        [String[]]$Exclude,

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByInclusion')]
        [ValidateSet(
            '*',
            'DependencyTypes',
            'FieldSpecs',
            'Bundles',
            'Providers',
            'Actions',
            'ETLScripts',
            'Recipes',
            'Tags',
            'AssetData',
            'AssetViews',
            'Events',
            'AppTypes',
            'DeviceTypes',
            'Teams',
            'TaskCategories'
        )]
        [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
            ETLScripts      = $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 = @()
        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."
            }
            $RDToInstall += $RD
            if (!$SkipDependencies) {
                # Resolve any dependencies

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

                foreach ($DependencyItem in $DependentRds) {
                    $Dependency = $LocalReferenceDesigns | Where-Object { $_.Name -eq $DependencyItem.Name -and $_.Version -eq $DependencyItem.Version }
                    if (!$Dependency) {
                        throw "The dependency Reference Design $($DependencyItem.Name) (v$($DependencyItem.Version)) was not found on the local hard drive. Use Get-TMReferenceDesign and try again."
                    }
                    $RDToInstall += $Dependency
                }
            }
        }

        ## Create an array of Setup Scripts to run
        $SetupScriptsToRun = [System.Collections.ArrayList]@()

        ## Iterate over each Reference Design to Install
        foreach ($RD in $RDToInstall) {

            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
                    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
                    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
                    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
                    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 {
                    $FieldSpecs = Get-Content -Path $FieldSpecsFilePath | 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
                    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
                    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
                    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
                    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...'
                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?
                    $ActionFiles = Get-ChildItem -Path $ActionsFolderPath -Recurse -File | Where-Object { $_.Extension -eq '.ps1' }
                    foreach ($ActionFile in $ActionFiles) {
                        $Action = Read-TMActionScriptFile -Path $ActionFile
                        Write-Host "`tLoading Action: " -NoNewline
                        Write-Host "$($Action.Name)" -ForegroundColor DarkBlue
                        New-TMAction -Action $Action -Update -TMSession $TMSession
                    }
                }
            }

            # ETL Scripts
            if ($Components.ETLScripts) {
                Write-Host 'Loading ETL Scripts...'
                if ($FilterByProvider) {
                    $ETLScriptFolderPath = Get-ChildItem -Path (Join-Path $ReferenceDesignPath 'ETL') -Directory | Where-Object { $_.Name -in $ProviderName }
                }
                else {
                    $ETLScriptFolderPath = Join-Path $ReferenceDesignPath 'ETL'
                }

                if (-Not (Test-Path $ETLScriptFolderPath)) {
                    Write-Host "`tNo ETL Scripts to add"
                }
                else {
                    ## Get the Recipe files from the Recipe folder
                    $ETLScriptFiles = Get-ChildItem -Path $ETLScriptFolderPath -Recurse -File | Where-Object { $_.Extension -eq '.groovy' }
                    foreach ($ETLScriptFile in $ETLScriptFiles) {
                        $ETLScript = Read-TMETLScriptFile -Path $ETLScriptFile
                        Write-Host "`tLoading ETL Script: " -NoNewline
                        Write-Host "$($ETLScript.Name)" -ForegroundColor DarkBlue
                        New-TMETLScript -ETLScript $ETLScript -Update -TMSession $TMSession
                    }
                }
            }

            # Recipes
            if ($Components.Recipes) {
                Write-Host 'Loading Recipes...'
                $RecipesFolderPath = Join-Path $ReferenceDesignPath 'Recipes'

                if (-Not (Test-Path $RecipesFolderPath)) {
                    Write-Host "`tNo Recipes to add"
                }
                else {
                    $RecipeFiles = Get-ChildItem -Path $RecipesFolderPath -File | Where-Object { $_.Extension -eq '.groovy' }
                    foreach ($RecipeFile in $RecipeFiles) {
                        $Recipe = Read-TMRecipeScriptFile -Path $RecipeFile
                        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
                    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) {
                Write-Host 'Loading AssetViews...'
                $AssetViewsFilePath = Join-Path $ReferenceDesignPath 'AssetViews.json'
                if (-Not (Test-Path $AssetViewsFilePath)) {
                    Write-Host "`tNo AssetViews to add"
                }
                else {
                    $AssetViews = Get-Content -Path $AssetViewsFilePath | ConvertFrom-Json
                    foreach ($AssetView in $AssetViews) {
                        Write-Host "`tLoading AssetView: " -NoNewline
                        Write-Host "$($AssetView.Name)" -ForegroundColor DarkBlue
                        New-TMAssetViewConfiguration $AssetView -TMSession $TMSession
                    }
                }
            }

            # 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
        ETLScripts
        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',
            'ETLScripts',
            'Recipes',
            'Tags',
            'AssetData',
            'AssetViews',
            'Events',
            'AppTypes',
            'DeviceTypes',
            'Teams',
            'TaskCategories'
        )]
        [String[]]$Include = '*',

        [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByExclusion')]
        [ValidateSet(
            'DependencyTypes',
            'FieldSpecs',
            'Bundles',
            'Providers',
            'Actions',
            'ETLScripts',
            '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 = $null
    )

    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)) {
            $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
            ETLScripts      = $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
                }
            }
        }

        # 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-Host 'Starting Export from Server: ' -NoNewline
        Write-Host $TMSessionConfig.TMServer -ForegroundColor Yellow -NoNewline
        Write-Host ', Project: ' -NoNewline
        Write-Host $Project -ForegroundColor Magenta -NoNewline

        $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'
        $ETLDirectory = Join-Path $ReferenceDesignDirectory 'ETL'
        $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...'
            $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 to '$DependencyTypesFilePath'"
            }
        }

        # Export the App Types
        if ($Components.AppTypes) {
            Write-Host 'Exporting App Types...'
            $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 to '$AppTypesFilePath'"
            }
        }

        # Export the Device Types
        if ($Components.DeviceTypes) {
            Write-Host 'Exporting Device Types...'
            $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 to '$DeviceTypesFilePath'"
            }
        }

        # Export the Task Categories
        if ($Components.TaskCategories) {
            Write-Host 'Exporting Task Categories...'
            $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 to '$TaskCategoriesFilePath'"
            }
        }

        # Export the Field Specs
        if ($Components.FieldSpecs) {
            Write-Host 'Exporting Field Specs...'
            $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 "`t$($FieldSpecs.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host "Asset Field Spec(s) exported to '$FieldSpecsFilePath'"
            }
        }

        # Export the Bundles
        if ($Components.Bundles) {
            Write-Host 'Exporting Bundles...'
            $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 to '$BundlesFilePath'"
            }
        }

        # Export the Events
        if ($Components.Events) {
            Write-Host 'Exporting Events...'
            $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 to '$EventsFilePath'"
            }
        }

        # Export the Events
        if ($Components.Teams) {
            Write-Host 'Exporting Teams...'
            $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 to '$TeamsFilePath'"
            }
        }

        # Export the Providers
        if ($Components.Providers) {
            Write-Host 'Exporting Providers...'
            $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 to '$ProvidersFilePath'"
            }
        }

        # Export the Actions
        if ($Components.Actions) {
            Write-Host 'Exporting Actions...'
            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 to '$ActionsFolderPath'"
            }
        }

        # Export the ETL Scripts
        if ($Components.ETLScripts) {
            Write-Host 'Exporting ETL Scripts...'
            Test-FolderPath $ETLDirectory
            $ETLScriptsFolderPath = Join-Path $ReferenceDesignDirectory 'ETL'
            $ETLScripts = Get-TMETLScript -ResetIDs -SaveCodePath $ETLScriptsFolderPath -ProviderName $ProviderName -Passthru -TMSession $TMSession
            if (!$ETLScripts) {
                Write-Host "`tNo ETL Scripts to export"
            }
            else {
                # TODO: Is the 'Private' folder supposed to be excluded?
                Write-Host "`t$($ETLScripts.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host "ETL Script(s) exported to '$ETLScriptsFolderPath'"
            }
        }

        # Export the Recipes
        if ($Components.Recipes) {
            Write-Host 'Exporting Recipes...'
            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 {
                # TODO: Is the 'Private' folder supposed to be excluded?
                Write-Host "`t$($Recipes.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host "Recipe(s) exported to '" -NoNewline
                Write-Host "$RecipesFolderPath" -NoNewline
                Write-Host "'"
            }
        }

        # Export the Tags
        if ($Components.Tags) {
            Write-Host 'Exporting Tags...'
            $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 to '$TasksFilePath'"
            }
        }

        # Export the AssetViews
        if ($Components.AssetViews) {
            Write-Host 'Exporting AssetViews...'
            $AssetViews = Get-TMAssetViewConfiguration -ResetIDs -TMSession $TMSession
            if (!$AssetViews) {
                Write-Host "`tNo AssetViews to export"
            }
            else {
                $TasksFilePath = Join-Path $ReferenceDesignDirectory 'AssetViews.json'
                $AssetViews | ConvertTo-Json -Depth 100 | Set-Content -Path $TasksFilePath -Force
                Write-Host "`t$($AssetViews.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host "AssetView(s) exported to '$TasksFilePath'"
            }
        }

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

        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 = @{
        actions         = [System.Collections.ArrayList]::new()
        etlScripts      = [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 ETL Scripts Folder to get the name Scripts
    $ETLFiles = Get-ChildItem -Path (Join-Path $RDPath 'ETL') -Recurse | Where-Object { $_.Name -like '*.groovy' }
    foreach ($ETLFile in $ETLFiles) {

        ## Convert the Script into a proper TMETLScript Object
        $EtlScript = Read-TMETLScriptFile -Path $ETLFile.FullName -ErrorAction 'SilentlyContinue'

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

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

    ## Read the Recipes Folder to get the name Scripts
    $RecipeFiles = Get-ChildItem -Path (Join-Path $RDPath 'Recipes') -Recurse | Where-Object { $_.Name -like '*.groovy' }
    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
        'application', 'database', 'device', 'storage' | 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
    }
}