Public/Extensions.ps1

$DomainClasses = @('STORAGE', 'DATABASE', 'DEVICE', 'APPLICATION')

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

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

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

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

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


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


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

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

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

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

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

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

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

        [Parameter(Mandatory = $false)]
        [scriptblock] $Where = { $true } # This default value will act as a no-filter
    )

    begin {

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

        # Get the session configuration
        Write-Verbose 'Checking for cached TMSession'
        $TMSession = Get-TMSession $TMSession
        Write-Debug 'TMSession:'
        Write-Debug ($TMSession | ConvertTo-Json -Depth 5)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                foreach ($DependencyItem in $DependentExtensions) {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

                $TaskCategories = $TaskCategories | Where-Object -FilterScript $Where
                if ($TaskCategories.Count -eq 0) {
                    Write-Host "`tNo Task Categories to add"
                } else {

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

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

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

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

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

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

                $Tags = $Tags | Where-Object -FilterScript $Where
                if ($Tags.Count -eq 0) {
                    Write-Host "`tNo Tags to add"
                } else {
                    foreach ($Tag in $Tags) {
                        Write-Host "`tLoading Tag: " -NoNewline
                        Write-Host "$($Tag.Name)" -ForegroundColor $TagColorToForeground.($Tag.Color)
                        New-TMTag -Name $Tag.Name -Description $Tag.description -Color $Tag.color -TMSession $TMSession
                    }
                }
            }

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

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

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

                    ## Handle Single JSON field files from folders
                    $SharedFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Shared') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue'

                    $ServerFields = Get-TMFieldSpecs -TMSession $TMSession -SkipCache -CustomOnly
                    foreach ($fieldFile in $SharedFieldFiles) {
                        $newSharedFields = @(Get-Content -Path $FieldFile.FullName | ConvertFrom-Json)
                        foreach ($domainClass in $DomainClasses) {
                            $newSharedFields.ForEach({
                                if (($ServerFields.$domainClass.fields | Where-Object -Not shared)?.label?.Contains( $_.label )) {
                                    throw "Label '{0}' already exists for [{2}] field '{1}'" -f $_.label, $_.field, $_.control
                                }
                            })
                            $FieldSpecs.$domainClass.fields += $newSharedFields
                        }
                    }

                    foreach ($domainClass in $DomainClasses) {
                        $domainFieldSpecsFolderPath = Join-Path -Path $FieldSpecsFolderPath -ChildPath $domainClass
                        $domainFieldFiles = Get-ChildItem -Path $domainFieldSpecsFolderPath -Filter '*.json' -Force -ErrorAction SilentlyContinue
                        foreach ($fieldFile in $domainFieldFiles) {
                            $fields = Get-Content -Path $FieldFile.FullName | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
                            $FieldSpecs.$domainClass.fields += $fields
                        }
                    }
                } else {

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

                        ## Version 6.0.2.1+ (and TODO version 6.1?, 5x?)
                        ## Requires the class names to be lower case
                        ## Convert each Asset Class name to lowercase
                        $FieldSpecsData = ((Get-Content -Path $FieldSpecsFilePath) -replace 'constraints', 'constraint')
                        $FieldSpecs = ($FieldSpecsData | ConvertFrom-Json -ErrorAction SilentlyContinue)
                    }
                }

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

                            ## Convert JSON defaultValue fields to a JSON string if they're not
                            if ($Field.control -eq 'JSON' -and $Field.defaultValue.GetType().Name -ne 'String') {
                                $Field.defaultValue = $Field.defaultValue | ConvertTo-Json -Depth 100 -Compress
                            }
                        }
                    }
                    Update-TMFieldSpecs -FieldSpecs $FieldSpecs -TMSession $TMSession
                }
            }

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

                $Bundles = $Bundles | Where-Object -FilterScript $Where
                if ($Bundles.Count -eq 0) {
                    Write-Host "`tNo Bundles to add"
                } else {
                    foreach ($Bundle in $Bundles) {
                        Write-Host "`tLoading Bundle: " -NoNewline
                        Write-Host "$($Bundle.Name)" -ForegroundColor DarkBlue
                        New-TMBundle -Bundle $Bundle -TMSession $TMSession
                    }
                }
            }

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

                $Events = $Events | Where-Object -FilterScript $Where
                if ($Events.Count -eq 0) {
                    Write-Host "`tNo Events to add"
                } else {
                    foreach ($TMEvent in $Events) {
                        Write-Host "`tLoading Event: " -NoNewline
                        Write-Host "$($TMEvent.Name)" -ForegroundColor DarkBlue
                        New-TMEvent -InputObject $TMEvent -TMSession $TMSession
                    }
                }
            }

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

                $Providers = $Providers | Where-Object -FilterScript $Where
                if ($Providers.Count -eq 0) {
                    Write-Host "`tNo Providers to add"
                } else {
                    foreach ($Provider in $Providers) {
                        if ($FilterByProvider -and ($Provider.Name -notin $ProviderName)) {
                            continue
                        }
                        Write-Host "`tLoading Provider: " -NoNewline
                        Write-Host "$($Provider.Name)" -ForegroundColor DarkBlue
                        New-TMProvider -Provider $Provider -TMSession $TMSession
                    }
                }
            }

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

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

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

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

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

                $DataScripts = $DataScripts | Where-Object -FilterScript $Where
                if ($DataScripts.count -eq 0) {
                    Write-Host "`tNo DataScripts to add"
                } else {
                    foreach ($DataScript in $DataScripts) {
                        if($DataScript.semVer){
                            $DataScript.semVer = [String]::IsNullOrWhiteSpace($DataScript.semVer) ? '1.0.0' : $DataScript.semVer
                        } else {
                            Add-Member -InputObject $DataScript -NotePropertyName semVer -NotePropertyValue '1.0.0' -Force
                        }
                        Write-Host "`tLoading DataScript: " -NoNewline
                        Write-Host "$($DataScript.Name) " -ForegroundColor DarkBlue -NoNewline
                        Write-Host "v$($DataScript.semVer)" -ForegroundColor DarkYellow
                        New-TMDatascript -Datascript $DataScript -Update -TMSession $TMSession
                    }
                }
            }

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

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

            # Load the AssetViews
            if ($Components.AssetViews) {
                Write-Host 'Loading AssetViews...'
                $AssetViewsFolderPath = Join-Path $ExtensionPath 'AssetViews'
                if (Test-Path $AssetViewsFolderPath -ErrorAction SilentlyContinue) {

                    $AssetViews = [System.Collections.ArrayList]@()
                    $AssetViewConfigurationFiles = Get-ChildItem -Path $AssetViewsFolderPath -File -ErrorAction SilentlyContinue |
                        Where-Object { $_.Extension -in @('.json') }

                    foreach ($AssetViewConfigurationFile in $AssetViewConfigurationFiles) {
                        $AssetView = Get-Content -Path $AssetViewConfigurationFile.FullName | ConvertFrom-Json -ErrorAction 'SilentlyContinue'
                        if ($AssetView) {
                            [void]($AssetViews.Add($AssetView))
                        }
                    }
                } else {
                    $AssetViewsFilePath = Join-Path $ExtensionPath 'AssetViews.json'
                    if (-Not (Test-Path $AssetViewsFilePath -ErrorAction SilentlyContinue)) {
                        Write-Host "`tNo AssetViews to add"
                    } else {
                        $AssetViews = Get-Content -Path $AssetViewsFilePath | ConvertFrom-Json  -ErrorAction 'SilentlyContinue'
                        if ($AssetViews.Count -eq 0) { Write-Host "`tNo Asset Views to add" }
                    }
                }

                $AssetViews = $AssetViews | Where-Object -FilterScript $Where
                foreach ($AssetView in $AssetViews) {
                    Write-Host "`tLoading AssetView: " -NoNewline
                    Write-Host "$($AssetView.Name)" -ForegroundColor DarkBlue
                    New-TMAssetViewConfiguration $AssetView -TMSession $TMSession -Update
                }
            }

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

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

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

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

            ## Try the script
            try {

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

function Compress-FolderBranch {
    <#
    .SYNOPSIS
        Zips a single extension folder under a unique root, storing entries as
        "<rootLeafName>\<extension>\..." inside the archive. Other folders under
        root are excluded (only the named extension is added).
 
    .PARAMETER RootPath
        Full path to the unique 'root' folder (its leaf name is used as the
        top-level folder inside the zip).
 
    .PARAMETER Extension
        Name of the single extension folder under root to zip (e.g. "extension1").
 
    .PARAMETER Name
        Output zip filename, used verbatim (include the .zip yourself).
 
    .PARAMETER OutputDirectory
        Directory where the .zip is written. Defaults to "$($userPaths.referencedesigns)\Exported".
 
    .EXAMPLE
        Compress-FolderBranch -RootPath "C:\data\root" -Extension "extension1" -Name "extension1.zip"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$RootPath,

        [Parameter(Mandatory)]
        [string]$Extension,

        [Parameter(Mandatory)]
        [string]$ZipFileName,

        [string]$OutputDirectory = (Join-Path $userPaths.referencedesigns 'Exported')
    )

    # --- Validate inputs ---
    $root = (Resolve-Path -LiteralPath $RootPath -ErrorAction Stop).ProviderPath
    if (-not (Test-Path -LiteralPath $root -PathType Container)) {
        throw "RootPath is not a folder: $root"
    }

    $extensionPath = Join-Path $root $Extension
    if (-not (Test-Path -LiteralPath $extensionPath -PathType Container)) {
        throw "Extension folder not found under root: $extensionPath"
    }

    # --- Resolve output location ---
    Test-FolderPath -Path $OutputDirectory

    $zipPath = Join-Path $OutputDirectory $ZipFileName
    if (Test-Path -LiteralPath $zipPath) {
        Remove-Item -LiteralPath $zipPath -Force
    }

    foreach ($folderVariable in @('root', 'extensionPath', 'zipPath')){
        Write-Verbose "$folderVariable is $(Get-Variable -Name $folderVariable -ValueOnly)"
    }

    # --- Gather every file under root\extension (recursively) ---
    $files = Get-ChildItem -LiteralPath $extensionPath -Recurse -File -Force

    Write-Verbose "Zipping $($files.Count) file(s) from $extensionPath"
    Write-Debug "ZipPath = $zipPath"
    $zip = [System.IO.Compression.ZipFile]::Open(
        $zipPath, [System.IO.Compression.ZipArchiveMode]::Create)
    try {
        # Length of the root's parent path, so the relative path begins at rootLeaf
        $parentLen = (Split-Path $root -Parent).Length + 1  # +1 for trailing slash

        foreach ($file in $files) {
            # Relative path from root's parent => "<rootLeaf>\<extension>\..."
            $relative = $file.FullName.Substring($parentLen)
            # Zip entries use forward slashes
            $entryName = $relative -replace '\\', '/'

            $null = [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
                $zip, $file.FullName, $entryName,
                [System.IO.Compression.CompressionLevel]::Optimal)
        }
    }
    finally {
        $zip.Dispose()
    }

    Write-Verbose "Created: $zipPath"
}


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


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

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

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

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

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

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

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

        [Parameter(ParameterSetName = 'zipSwitch')] 
        [Parameter(ParameterSetName = 'ByInclusion')]
        [switch] $Zip,
        [Parameter(ParameterSetName = 'zipString')] 
        [Parameter(ParameterSetName = 'ByInclusion')]
        [string] $ZipPath,
        
        [Parameter(Mandatory = $false, Position = 6)]
        [Switch]$ClearExistingFiles
    )

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

        $TMSession = Get-TMSession $TMSession

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

        # Default to the reference design directory if an out path wasn't specified

        $timestamp = (Get-Date).ToString("yyyy-MM-dd-THHmm")

        if ([String]::IsNullOrWhiteSpace($OutPath)) {
            Write-Verbose 'No OutPath as provided and will use the default export location'
            $OutPath = Join-Path -Path $Global:userPaths.referencedesigns -ChildPath 'Exported' -AdditionalChildPath $TMSession.TMServer

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        ##
        ## Export all of the components
        ##

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

            $DependencyTypes = Get-TMDependencyType -ResetIDs -TMSession $TMSession
            if (!$DependencyTypes) {
                Write-Host "`tNo Dependency Types to export"
            } else {
                foreach ($DependencyType in $DependencyTypes) {
                    if ($DependencyType.label) {
                        $Filename = Get-FilenameSafeString -String $DependencyType.label
                        $DependencyType | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $DependencyTypesFolderPath "$Filename.json") -Force
                    }
                }
                Write-Host "`t$($DependencyTypes.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Dependency Type(s) exported'
            }
        }

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

            $AppTypes = Get-TMAssetOption -Type 'App Type' -ResetIDs -TMSession $TMSession
            if (!$AppTypes) {
                Write-Host "`tNo App Types to export"
            } else {
                foreach ($AppType in $AppTypes) {
                    if ($AppType.label) {
                        $Filename = Get-FilenameSafeString -String $AppType.label
                        $AppType | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $AppTypesFolderPath "$Filename.json") -Force
                    }
                }
                Write-Host "`t$($AppTypes.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'App Type(s) exported'
            }
        }

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

            $DeviceTypes = Get-TMAssetOption -Type 'Asset Type' -ResetIDs -TMSession $TMSession
            if (!$DeviceTypes) {
                Write-Host "`tNo Device Types to export"
            } else {
                foreach ($DeviceType in $DeviceTypes) {
                    if ($DeviceType.label) {
                        $Filename = Get-FilenameSafeString -String $DeviceType.label
                        $DeviceType | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $DeviceTypesFolderPath "$Filename.json") -Force
                    }
                }
                Write-Host "`t$($DeviceTypes.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Device Type(s) exported'
            }
        }

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

            $TaskCategories = Get-TMAssetOption -Type 'Task Category' -ResetIDs -TMSession $TMSession
            if (!$TaskCategories) {
                Write-Host "`tNo Task Categories to export"
            } else {
                foreach ($TaskCategory in $TaskCategories) {
                    if ($TaskCategory.label) {
                        $Filename = Get-FilenameSafeString -String $TaskCategory.label
                        $TaskCategory | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $TaskCategoriesFolderPath "$Filename.json") -Force
                    }
                }
                Write-Host "`t$($TaskCategories.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Task Categories exported'
            }
        }

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

                ## Iterate through each Domain Class
                ##### This is done in this order expressly described because shared fields will be UPDATED because it exists in each domain.
                ##### This order ensures that if there is variation in the color/order, etc. Application will win.
                ##### This is because the Shared fields are inserted with ONE configuration across each domain.

                foreach ($DomainClass in $DomainClasses) {

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

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

                        ## Cast the file name to a safe character set
                        [string]$FieldFileName = (Get-FilenameSafeString $DomainField.label) + '.json'
                        $FieldPath = Join-Path -Path $FieldSpecsFolderPath -ChildPath $FieldClass -AdditionalChildPath $FieldFileName
                        if ($DomainField.PSObject.Properties.Name.contains('constraints')) {
                            [void](Add-Member -InputObject $DomainField -NotePropertyName 'constraint' -NotePropertyValue $DomainField.constraints -Force)
                            $DomainField.PSObject.Properties.Remove('constraints')
                        }
                        if ($DomainField.PSObject.Properties.Name.contains('default')) {
                            [void](Add-Member -InputObject $DomainField -NotePropertyName 'defaultValue' -NotePropertyValue $DomainField.default -Force)
                            $DomainField.PSObject.Properties.Remove('defaultValue')
                        }

                        ## Convert JSON Field default Values to an Object
                        if ($DomainField.control -eq 'JSON') {
                            $DomainField.defaultValue = (ConvertFrom-Json $DomainField.defaultValue -ErrorAction SilentlyContinue)
                        }
                        Set-Content -Path $FieldPath -Value ($DomainField | ConvertTo-Json -Depth 5) -Force
                        Get-Item -Path $FieldPath | ForEach-Object {
                            $_.CreationTime = $FieldSpecs.$DomainClass.dateCreated
                            $_.LastWriteTime = $FieldSpecs.$DomainClass.lastUpdated
                        }
                    }
                }
                Write-Host "`tAsset Field Specs exported"
            }
        }

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

            $Bundles = Get-TMBundle -ResetIDs -TMSession $TMSession
            if (!$Bundles) {
                Write-Host "`tNo Bundles to export"
            } else {
                foreach($Bundle in $Bundles) {
                    $Filename = Get-FilenameSafeString -String $Bundle.Name
                    $Bundle | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $BundlesFolderPath "$Filename.json") -Force

                    ## Bundles do not have a datestamp
                    # Get-Item -Path $FieldPath | ForEach-Object {
                    # $_.CreationTime = $Bundle.dateCreated
                    # $_.LastWriteTime = $Bundle.lastUpdated
                    # }
                }
                Write-Host "`t$($Bundles.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Bundle(s) exported'
            }
        }

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

            $Events = Get-TMEvent -ResetIDs -TMSession $TMSession
            if (!$Events) {
                Write-Host "`tNo Events to export"
            } else {
                foreach ($Event in $Events) {
                    $Filename = Get-FilenameSafeString -String $Event.Name
                    $FilePath = Join-Path $EventsFolderPath "$Filename.json"

                    $dateCreated = $Event.dateCreated
                    $lastUpdated = $Event.lastUpdated
                    
                    $Event.Project.Id = $null
                    $Event.Project.name = $null
                    $Event.dateCreated = $null
                    $Event.lastUpdated = $null
                    $Event.Tags = @()
                    $Event.Bundles = @()
                    $Event.EstStartTime = $null
                      $Event.EstCompletionTime = $null
                    
                    $Event | ConvertTo-Json -Depth 100 | Set-Content -Path $FilePath -Force
                    Get-Item -Path $FilePath | ForEach-Object {
                        $_.CreationTime = $dateCreated
                        $_.LastWriteTime = $lastUpdated
                    }
                }
                Write-Host "`t$($Events.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Event(s) exported'
            }
        }

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

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

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

            if (!$Providers) {
                Write-Host "`tNo Providers to export"
            } else {
                if ($FilterByProvider) {
                    $Providers = $Providers | Where-Object Name -In $ProviderName
                }
                foreach ($Provider in $Providers) {
                    $Filename = Get-FilenameSafeString -String $Provider.Name
                    $FilePath = Join-Path $ProvidersFolderPath "$Filename.json"
                    
                    $dateCreated = $Provider.dateCreated
                    $lastUpdated = $Provider.lastUpdated
                    
                    $Provider.dateCreated = $null
                    $Provider.lastUpdated = $null

                    $Provider | ConvertTo-Json -Depth 100 | Set-Content -Path $FilePath -Force
                    Get-Item -Path $FilePath | ForEach-Object {
                        $_.CreationTime = $dateCreated
                        $_.LastWriteTime = $lastUpdated
                    }
                }
                Write-Host "`t$($Providers.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Provider(s) exported'
            }
        }

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

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

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

            Write-Host 'Exporting DataScripts...' -NoNewline
            $DataScriptsFolderPath = Join-Path $ExtensionDirectory 'DataScripts'
            Test-FolderPath $DataScriptsFolderPath
            $DataScripts = Get-TMDatascript -ResetIDs -SaveCodePath $DataScriptsFolderPath -ProviderName $ProviderName -Passthru -TMSession $TMSession -ErrorAction SilentlyContinue
            if (!$DataScripts) {
                Write-Host "`tNo Datascripts to export"
            } else {
                Write-Host "`t$($DataScripts.Count) " -NoNewline -ForegroundColor DarkBlue
                Write-Host 'Datascript(s) exported'
            }
        }

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

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

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

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

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

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

            foreach ($AssetView in $AssetViews) {
                Test-FolderPath -FolderPath $AssetViewsFolderPath
                $AssetViewFileName = Get-FilenameSafeString $AssetView.Name
                $AssetViewFilePath = Join-Path $AssetViewsFolderPath "$AssetViewFileName.json"
                

                $Created = $AssetView.createdOn
                $Updated = $AssetView.updatedOn  ?? $AssetView.createdOn
                
                $AssetView.createdBy = $null
                $AssetView.createdOn = $null
                $AssetView.updatedOn = $null

                $AssetView | ConvertTo-Json -Depth 100 | Set-Content -Path $AssetViewFilePath -Force
                Get-Item -Path $AssetViewFilePath | ForEach-Object {
                    $_.CreationTime = $Created
                    $_.LastWriteTime = $Updated
                }
            }
            Write-Host "`t$($AssetViews.Count) " -NoNewline -ForegroundColor DarkBlue
            Write-Host 'AssetView(s) exported'
        }

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

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

        ### Zipping the output
        $shouldZip = $false

        if ($Zip.IsPresent) {
            $shouldZip = $true

            $zipFileName = '{0}_{1}_{2}.zip' -f $TMSession.TMServer, $Project, $timestamp
            $ZipOutDir = Join-Path $userPaths.referencedesigns 'Exported'
        }
        elseif ($PSBoundParameters.ContainsKey('ZipPath') ) {
            $shouldZip = $true

            if (Test-Path -LiteralPath $ZipPath -PathType Container) {
                # -ZipPath is an existing directory: use it as the container for a
                # default-named zip. Do not append .zip and do not touch the folder.
                $ZipOutDir = $ZipPath
                $zipFileName = '{0}_{1}_{2}.zip' -f $TMSession.TMServer, $Project, $timestamp
            }
            else {
                # Append .zip if no file extension was provided
                if (-not [System.IO.Path]::GetExtension($ZipPath)) {
                    $ZipPath += '.zip'
                }

                $zipFileName = Split-Path -Path $ZipPath -Leaf
                $zipDir = Split-Path -Path $ZipPath -Parent

                $ZipOutDir = $zipDir ? $zipDir : $(Join-Path $userPaths.referencedesigns 'Exported')
            }
        }
        
        if ($shouldZip) {
            Write-Verbose "rootpath: $OutPath"
            Write-Verbose "zipfilename: $zipfilename"
            Write-Verbose "zipoutdir: $zipoutdir"

            Compress-FolderBranch `
                -RootPath $OutPath `
                -Extension $Name `
                -OutputDirectory $ZipOutDir `
                -ZipFileName $zipFileName `
                -Verbose:$VerbosePreference
        }

    }

}


function New-TMExtensionPackageFile {
    <#
    .SYNOPSIS
    Creates a new package.json file for a locally downloaded/exported Extension
 
    .DESCRIPTION
    This function will create a new package.json file for a TransitionManager Extension
 
    .PARAMETER Name
    Name of the Extension to create. A package-name will also be created
 
    .PARAMETER Path
    The path at which the package file should be created
 
    .PARAMETER Description
    A nice, verbose description of the Extension
 
    .PARAMETER Author
    The name of the author to put in the package.json
 
    .PARAMETER Email
    Email address for the Author of the extension.
 
    .EXAMPLE
    New-TMExtensionPackageFile -Name 'TM - Rules Engine' -Path '.'
 
    .EXAMPLE
    $PackageSplat = @{
        Name = VMware - vCenter'
        Path = 'C:\Users\user\TMD_Files\Extensions\Downloaded\VMware - vCenter'
        Description = 'This is a detailed description of the Reference Design'
        Author = 'Me'
        Email = 'me@tdsi.com'
    }
    New-TMExtensionPackageFile @PackageSplat
 
    .EXAMPLE
    $ExtensionPackage = New-TMExtensionPackageFile -Name 'New Extension' -Path 'C:\DEV\Extension' -Passthru
    Write-Host "There are $($ExtensionPackage.Inventory.actions.Count) action(s) included in this Reference Design"
 
    #>


    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [String]$Name,

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

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

        [Parameter(Mandatory = $false)]
        [String]$Version = '1.0.0',

        [Parameter(Mandatory = $false)]
        [String]$Author = 'TransitionManager Automation Experts',

        [Parameter(Mandatory = $false, Position = 5)]
        [String]$Email = 'support@transitionmanager.com',

        [Parameter(Mandatory = $false, Position = 6)]
        [String]$Homepage = 'https://support.transitionmanager.com'
    )

    # 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
        extension   = @{

            ## Review https://tds.atlassian.net/wiki/spaces/TMENG/pages/531857409/Extension+Packaging+Format+Definition
            packageFormatVersion = 4

            dependencies         = @{
                installedApplications = @(
                    @{
                        name    = 'PowerShell'
                        type    = 'Software'
                        version    = "^$($PSVersionTable.PSVersion.ToString())"
                        source  = 'https://github.com/PowerShell/PowerShell'
                    }
                )
                powershellModules     = @(
                    @{
                        name    = '<PLACEHOLDER>'
                        version = '1.0.0'
                        source  = ''
                    }
                )
                extensions            = @(
                    @{
                        name    = '<PLACEHOLDER>'
                        version = '1.0.0'
                    }
                )
            }
        }
        inventory   = $Inventory
    }

    ## Create the file
    Test-FolderPath $Path
    $PackageFilePath = Join-Path $Path 'package.json'
    $Package | ConvertTo-Json -Depth 5 | Set-Content -Path $PackageFilePath -Force

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