lib/Extensions.ps1
function Get-TMExtension { <# .SYNOPSIS Gets/Downloads a Reference Design from the TDS BitBucket tm-reference-designs repo .DESCRIPTION This function .PARAMETER Name The name of the Reference Design to download .PARAMETER Credential The credentials used to log into BitBucket .PARAMETER SkipDependencies Switch indicating that the Reference Designs on which the named Reference Design depends on should not be downloaded .PARAMETER ManifestUri The uri of the raw rd-manifest.json. Defaults to the file in the __Releases directory .PARAMETER Dev Switch indicating that all reference designs should be searched/retrieved rather than just the published ones .PARAMETER ProviderName One or more specific Providers to get. All other Providers will be removed from the downloaded Reference Design .EXAMPLE $Credential = Get-StoredCredential -Name 'ME' Get-TMExtension -Name 'vmware-vcenter' -Credential $Credential -Dev .OUTPUTS None #> [alias('Get-TMReferenceDesign')] [CmdletBinding(DefaultParameterSetName = 'Console')] param ( [Parameter(Mandatory = $false, Position = 0, ParameterSetName = 'Console')] [String[]]$Name, [Parameter(Mandatory = $false, Position = 1)] [PSCredential]$Credential, [Parameter(Mandatory = $false)] [Switch]$SkipDependencies, [Parameter(Mandatory = $false)] [String]$ManifestUri, [Parameter(Mandatory = $false)] [Switch]$Dev, [Parameter(Mandatory = $false)] [String[]]$ProviderName # [Parameter(Mandatory = $false, ParameterSetName = "UI")] # [Switch]$ShowMenu # TODO: Consider if we could add a $Path/$Location type # parameter to allow the user to specify a filesystem ) # Some of the TMD functions will be needed in this function if (!( Get-Module 'TMD.Common')) { Import-Module 'TMD.Common' } ## Create a $PackageNames variable with all of the Reference Design names (Lower, no spaces) $PackageNames = $Name.ToLower() -replace ' - ', '-' -replace ' ', '-' ## Save the Progress Preference now so that it can be changed and set back later $ProgressPreferenceBackup = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' $ExtensionFolder = Join-Path $Global:userFilesRoot 'Reference Designs' $LocalExtensions = [System.Collections.ArrayList]::new() $ExtensionsToDownload = [System.Collections.ArrayList]::new() # $ShouldInstall = $false ## TODO: I don't like this credential handling. If a user supplies a credential, it should not be written to disk, but only used. ## To mitigate this, you could do a few things -StoreCredentialAs (Name), or ## -CredentialName {Which would hand off to Get-StoredCredential} # Check if the credential is already cached $CredentialPath = Join-Path -Path $Global:UserPaths.credentials -ChildPath 'Bitbucket.credential' if (!$Credential) { $Credential = (Test-Path -Path $CredentialPath) ? (Get-StoredCredential -Name 'Bitbucket') : (Get-Credential) } # Save the credential for future use Add-StoredCredential -Name 'Bitbucket' -Credential $Credential Write-Verbose 'Credential was stored as Bitbucket.credential' # Get the reference design manifest from bitbucket Write-Host 'Getting available Reference Designs from Bitbucket...' try { if ([String]::IsNullOrWhiteSpace($ManifestUri)) { $ManifestUri = "https://git.tdsops.com/projects/TM/repos/tm-reference-designs/raw/__Releases/$(if ($Dev) {'dev-'})rd-manifest.json?at=refs%2Fheads%2Fmain" } $AvailableExtensions = Invoke-RestMethod -Method Get -Uri $ManifestUri -Authentication Basic -Credential $Credential } catch { throw "Couldn't get the manifest file. Check credentials and try again" } Write-Host "`tFound " -NoNewline Write-Host "$($AvailableExtensions.Count)" -NoNewline -ForegroundColor DarkBlue Write-Host ' available Reference Design(s)' # Fix the version so that it can be compared later in the script for ($i = 0; $i -lt $AvailableExtensions.Count; $i++) { $AvailableExtensions[$i].Version = ConvertTo-SemanticVersion -VersionString $AvailableExtensions[$i].Version for ($j = 0; $j -lt $AvailableExtensions[$i].extension.dependencies.extensions.Count; $j++) { $AvailableExtensions[$i].extension.dependencies.extensions[$j].version = ConvertTo-SemanticVersion -VersionString $AvailableExtensions[$i].extension.dependencies.extensions[$j].version } } # Either show the UI to get the user's selections or switch ($PSCmdlet.ParameterSetName) { 'Console' { Write-Verbose 'Console mode was selected' # If no name was supplied, just output the Extension objects if (!$PackageNames) { $AvailableExtensions | ForEach-Object { $_ } return } else { $PackageNames | ForEach-Object { $Extension = $AvailableExtensions | Where-Object name -EQ $_ | Sort-Object -Property version | Select-Object -Last 1 if ($Extension) { [void]$ExtensionsToDownload.Add($Extension) } } } } <# "UI" { Write-Verbose "UI mode was selected" # $ExePath = "..\exe\RD Installer.exe" $ExePath = "C:\DEV\git\tm-reference-designs\_ReferenceDesignManager\ReferenceDesignManager\bin\Debug\net5.0-windows\ReferenceDesignManager.exe" $StartInfo = [System.Diagnostics.ProcessStartInfo]::new() $StartInfo.RedirectStandardError = $true $StartInfo.RedirectStandardOutput = $true $StartInfo.FileName = $ExePath # $StartInfo.ArgumentList = @( # "--repocredential `"$CredentialPath`"" # "--powershell" # ) $StartInfo.Arguments = "--repocredential `"$CredentialPath`" --powershell" $ExtensionInstallerProc = [System.Diagnostics.Process]::new() $ExtensionInstallerProc.StartInfo = $StartInfo # Start the UI and wait for it to exit so that the stdout can be parsed Write-Host "Showing UI..." $ExtensionInstallerProc.Start() $ExtensionInstallerProc.WaitForExit() $StdOut = $ExtensionInstallerProc.StandardOutput.ReadToEnd() if ([String]::IsNullOrWhiteSpace($StdOut)) { throw "Output was not received from $(Split-Path $ExePath -Leaf)" } $UserChoices = $StdOut | ConvertFrom-Json $UserChoices.ExtensionsToDownload | ForEach-Object { [void]$ExtensionsToDownload.Add( @{ Name = $_ Version = $null } ) } $ShouldInstall = $UserChoices.ShouldInstall } #> } # Make sure one or more available Reference Designs were found if ($ExtensionsToDownload.Count -eq 0) { throw "No available Reference Designs were found with the specified name(s): $($PackageNames.ToString())" } ## Create the Destination Downloaded Folder $DownloadedFolder = Join-Path $ExtensionFolder 'Downloaded' Test-FolderPath $DownloadedFolder # Get all of the Reference Designs that are already downloaded Write-Host 'Checking for downloaded Reference Designs...' Write-Verbose "Looking in '$DownloadFolder'" try { Get-ChildItem -Path $DownloadedFolder -Filter 'package.json' -Recurse -ErrorAction SilentlyContinue | Get-Content -Raw | ConvertFrom-Json | ForEach-Object { [void]$LocalExtensions.Add($_) } } catch { Write-Verbose $_ } Write-Host "`tFound " -NoNewline Write-Host "$($LocalExtensions.Count)" -NoNewline -ForegroundColor DarkBlue Write-Host ' local Reference Design(s)' # Check if the selected Reference Designs have any dependencies on other Reference Designs if (!$SkipDependencies) { Write-Host 'Checking for Reference Design dependencies...' $Count = $ExtensionsToDownload.Count for ($i = 0; $i -lt $Count; $i++) { $Design = $ExtensionsToDownload[$i] foreach ($Dependency in $Design.extension.dependencies.extensions) { $AvailableExtension = $AvailableExtensions | Where-Object Name -EQ $Dependency.name | Sort-Object -Property version | Select-Object -Last 1 if ($AvailableExtension) { $LocalExtension = $LocalExtensions | Where-Object Name -EQ $Dependency.name | Sort-Object -Property version | Select-Object -Last 1 if ($LocalExtension) { if ($LocalExtension.version -lt $Dependency.version) { Write-Host "`tFound Reference Design to update: " -NoNewline Write-Host "$($AvailableExtension.Name) - v$($AvailableExtension.Version.ToString())" [void]$ExtensionsToDownload.Add($AvailableExtension) } } else { Write-Host "`tFound additional Reference Design to download: " -NoNewline Write-Host "$($AvailableExtension.Name) - v$($AvailableExtension.Version.ToString())" [void]$ExtensionsToDownload.Add($AvailableExtension) } } } } } # Start downloading the named reference designs Write-Host 'Downloading Reference Designs...' # $EncounteredError = $false foreach ($ExtensionSpec in $ExtensionsToDownload) { Write-Host "`tProcessing: " -NoNewline Write-Host $ExtensionSpec.ProductName -ForegroundColor DarkBlue ## Resolve the file name and the destination path $ZipFileName = $ExtensionSpec.Name + '-' + $ExtensionSpec.Version + '.zip' $Url = 'https://git.tdsops.com/projects/TM/repos/tm-reference-designs/raw/__Releases/{0}?at=refs%2Fheads%2Fmain' -f $ZipFileName $DestinationFilePath = Join-Path $ExtensionFolder 'Downloaded' $ZipFileName try { ## Call to Bitbucket to download the file Write-Host "`t`tDownloading $ZipFileName ($([Math]::Round(($ExtensionSpec.Size / 1MB), 2)) MB)..." -NoNewline Invoke-WebRequest -Uri $Url -Credential $Credential -Authentication Basic -Method Get -OutFile $DestinationFilePath Write-Host 'Done' -ForegroundColor Green } catch { Write-Error $_ continue } try { # Unzip the downloaded file Write-Host "`t`tExpanding archive..." -NoNewline $DestinationDirectory = Join-Path $ExtensionFolder 'Downloaded' $ExtensionSpec.ProductName Expand-Archive -Path $DestinationFilePath -DestinationPath $DestinationDirectory -Force Write-Host 'Done' -ForegroundColor DarkGreen } catch { Write-Error $_ continue } # Remove the .zip file now that its contents been extracted Remove-Item -Path $DestinationFilePath -Force if ($PSBoundParameters.ContainsKey('ProviderName')) { # Remove the actions and Datascripts that don't belong to the specified providers $ScriptPaths = @( (Join-Path $DestinationDirectory '_REFERENCE DESIGN' 'Actions') (Join-Path $DestinationDirectory '_REFERENCE DESIGN' 'ETL') ) Get-ChildItem -Path $ScriptPaths -Directory -Exclude $ProviderName -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force # Remove the providers from providers.json that weren't specified in ProviderName $FilteredProviders = [System.Collections.ArrayList]::new() $ProviderFile = Join-Path $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-TMExtension -Name $UserChoices.ExtensionsToDownload # } } function Get-TMLocalExtension { <# .SYNOPSIS Gets one or more Reference Designs that have been downloaded loacally .DESCRIPTION This function scans the C:\Users\<user>\TMD_Files\Reference Designs directory for the package.json file(s) associated with Reference Designs that have been downloaded locally .PARAMETER Name The name(s) of the downloaded Reference Design(s) to retrieve .PARAMETER List Switch indicating that only a list of downloaded Reference Design names should be returned .EXAMPLE Get-TMLocalExtension -Name 'vmware-vcenter', 'vmware-hcx' .EXAMPLE Get-TMLocalExtension -List .OUTPUTS An array of strings representing the downloaded Reference Design names if the -List switch is used otherwise a PSCustomObject for each name that was passed #> [alias('Get-TMLocalReferenceDesign')] [CmdletBinding(DefaultParameterSetName = 'Named')] [OutputType([PSCustomObject[]], ParameterSetName = 'Named')] [OutputType([String[]], ParameterSetName = 'List')] param ( [Parameter( Mandatory = $false, ParameterSetName = 'Named', ValueFromPipeline = $true)] [String[]]$Name, [Parameter(Mandatory = $false, ParameterSetName = 'List')] [Switch]$List ) begin { if (!(Get-Module 'TMD.Common')) { Import-Module 'TMD.Common' } $ExtensionDirectory = Join-Path $Global:userFilesRoot 'Reference Designs' $LocalExtensions = Get-ChildItem -Path $ExtensionDirectory -Filter 'package.json' -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object { $Package = Get-Content -Path $_.FullName -Raw | ConvertFrom-Json -Depth 100 Add-Member -InputObject $Package -Name 'path' -Value (Split-Path $_.FullName) -MemberType NoteProperty -PassThru -Force } } process { if ($List) { $LocalExtensions | ForEach-Object { $_.Name } } else { if ($Name) { $Name | ForEach-Object { $LocalExtensions | Where-Object Name -EQ $_ } } else { $LocalExtensions } } } } function Import-TMExtension { <# .SYNOPSIS Imports a downloaded Reference Design to the specified project on the specified server .DESCRIPTION This function will apply the different configuration components of a Reference Design into a TransitonManager project. .PARAMETER Name The name of the locally downloaded Reference Design to apply/install .PARAMETER TMSession The name of the TM Session to use when creating a Team .PARAMETER Project The project in which the Reference Design will be applied .PARAMETER Exclude An array of components to skip when performing the import. Options are: DependencyTypes FieldSpecs Bundles Providers Actions Datascripts Recipes Tags AssetViews Events AppTypes DeviceTypes Teams TaskCategories SetupScripts .PARAMETER SkipDependencies Switch indicating that the Reference Designs on which the named Reference Design depends on should not be imported .PARAMETER ProviderName One or more specific Providers to import. All other Providers will not be imported into the project .EXAMPLE $Credential = Get-StoredCredential -Name 'ME' $ImportSplat = @{ Name = 'vmware-vcenter' Project = 'New Project - VMware vCenter' TMSession = 'TMAD60' } Import-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)] [String]$TMSession = 'Default', [Parameter(Mandatory = $false, Position = 2)] [String]$Project, [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByExclusion')] [ValidateSet( 'DependencyTypes', 'FieldSpecs', 'Bundles', 'Providers', 'Actions', 'DataScripts', 'Recipes', 'Tags', 'AssetData', 'AssetViews', 'Events', 'AppTypes', 'DeviceTypes', 'Teams', 'TaskCategories', 'SetupScripts' )] [String[]]$Exclude, [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByInclusion')] [ValidateSet( '*', 'DependencyTypes', 'FieldSpecs', 'Bundles', 'Providers', 'Actions', 'DataScripts', 'Recipes', 'Tags', 'AssetData', 'AssetViews', 'Events', 'AppTypes', 'DeviceTypes', 'Teams', 'TaskCategories', 'SetupScripts' )] [String[]]$Include = '*', [Parameter(Mandatory = $false)] [Switch]$SkipDependencies, [Parameter(Mandatory = $false)] [String[]]$ProviderName ) begin { # Get the session configuration Write-Verbose 'Checking for cached TMSession' $TMSessionConfig = $global:TMSessions[$TMSession] Write-Debug 'TMSessionConfig:' Write-Debug ($TMSessionConfig | ConvertTo-Json -Depth 5) if (-not $TMSessionConfig) { throw "TMSession '$TMSession' not found. Use New-TMSession command before using features." } if (!(Get-Module 'TMD.Common')) { Import-Module 'TMD.Common' } $FilterByProvider = $PSBoundParameters.ContainsKey('ProviderName') ## Tag Color Map ## PowerShell Colors: ## Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White ## TM Tag Colors: ## 'Grey', 'Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Cyan', 'Purple', 'Pink', 'White' $TagColorToForeground = @{ 'Grey' = 'DarkGray' 'Red' = 'Red' 'Orange' = 'DarkYellow' 'Yellow' = 'Yellow' 'Green' = 'Green' 'Blue' = 'Blue' 'Cyan' = 'Cyan' 'Purple' = 'DarkMagenta' 'Pink' = 'Magenta' 'White' = 'White' } # Set up a configuration object to determine which components to install later in the process block $Components = @{ DependencyTypes = $true FieldSpecs = $true Bundles = $true Providers = $true Actions = $true Datascripts = $true Recipes = $true Tags = $true AssetViews = $true Events = $true AppTypes = $true DeviceTypes = $true Teams = $true TaskCategories = $true SetupScripts = $true } switch ($PSCmdlet.ParameterSetName) { 'ByInclusion' { if ($Include -notcontains '*') { foreach ($Key in $Components.GetEnumerator().Name) { if ($Include -notcontains $Key) { $Components.$Key = $false } } } } 'ByExclusion' { # Process the Exclude parameter and turn components off $Exclude | ForEach-Object { $Components.$_ = $false } } } # Get a list of all of the Reference Designs that have been saved locally $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 ($TMSessionConfig.userContext.project.name -ne $Project)) { Enter-TMProject -ProjectName $Project -TMSession $TMSession } else { $Project = $TMSessionConfig.userContext.project.name } } process { # Get the Extension spec from the passed Names and verify they are already downloaded $ExtensionToInstall = [System.Collections.Stack]::new() $DependencyCheckQueue = [System.Collections.Queue]@() foreach ($ExtensionName in $Name) { $ExtensionName = $ExtensionName.ToLower().Replace(' - ', '-').Replace(' ', '-') $Extension = $LocalExtensions | Where-Object Name -EQ $ExtensionName if (!$Extension) { throw "The reference design $ExtensionName has not been downloaded locally. Use Get-TMExtension and try again." } ## Check for Dependencies to add to the list before the Extension being installed if (!$SkipDependencies) { ## Add This Extension to the DependencyCheckList $DependencyCheckQueue.Enqueue($Extension) } ## Add the Extension being installed to the list, after dependencies are added $ExtensionToInstall.Push($Extension) } ## Check every Reference Design for Dependencies if (-Not $SkipDependencies) { Write-Host 'Checking Dependency Requirements' while ($DependencyCheckQueue.Count -ne 0) { # ## Check Each of the Extensions that are queued for installation for Dependencies # foreach ($ExtensionCheck in $ExtensionsToCheckDependenciesFor) { $ExtensionCheck = $DependencyCheckQueue.Dequeue() ## Remove Placeholder tokens provided by the template file creation $DependentExtensions = @() $ExtensionCheck.referenceDesign.dependencies.referenceDesigns | Where-Object { $_.name -ne '<PLACEHOLDER>' } | ForEach-Object { if ($_) { $DependentExtensions += $_ } } $ExtensionCheck.extension.dependencies.extensions | Where-Object { $_.name -ne '<PLACEHOLDER>' } | ForEach-Object { if ($_) { $DependentExtensions += $_ } } foreach ($DependencyItem in $DependentExtensions) { ## Get the Highest version number that matches the requirement $Dependency = $LocalExtensions | Where-Object { $_.Name -eq $DependencyItem.Name -and $_.Version -ge $DependencyItem.Version } | Sort-Object -Property 'version' -Descending | Select-Object -First 1 ## Ensure a dependency was found if (!$Dependency) { throw "The dependency Reference Design $($DependencyItem.Name) (Minimum v$($DependencyItem.Version)) was not found on the local hard drive. Use Get-TMExtension and try again." } if ($ExtensionToInstall -notcontains $Dependency) { Write-Host 'Required Prerequisite Extension also will be installed: ' -NoNewline Write-Host $Dependency.name -ForegroundColor Cyan -NoNewline Write-Host " v$($Dependency.version)" -ForegroundColor DarkGray # $ExtensionToInstall += $Dependency $ExtensionToInstall.Push($Dependency) [void]($DependencyCheckQueue.Enqueue($Dependency)) } } } } ## Create an array of Setup Scripts to run $SetupScriptsToRun = [System.Collections.ArrayList]@() ## Iterate over each Reference Design to Install while ($ExtensionToInstall.Count) { ## Get the next item from the Stack $Extension = $ExtensionToInstall.Pop() Write-Host 'Importing: ' -NoNewline Write-Host $Extension.productName -NoNewline -ForegroundColor Cyan Write-Host ', Version: ' -NoNewline Write-Host $Extension.Version -ForegroundColor DarkGray $ExtensionPath = Join-Path $Extension.Path '_REFERENCE DESIGN' # Create the dependency types if ($Components.DependencyTypes) { Write-Host 'Loading Dependency Types...' $DependencyTypes = [system.collections.arraylist]@() $DependencyTypesFolderPath = Join-Path $ExtensionPath 'DependencyTypes' if (Test-Path $DependencyTypesFolderPath -ErrorAction SilentlyContinue) { $DependencyTypeFiles = Get-ChildItem -Path $DependencyTypesFolderPath -ErrorAction SilentlyContinue $DependencyTypeFiles | ForEach-Object { [void]($DependencyTypes.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $DependencyTypesFilePath = Join-Path $ExtensionPath 'DependencyTypes.json' if (Test-Path $DependencyTypesFilePath -ErrorAction SilentlyContinue) { $DependencyTypes = Get-ChildItem -Path $DependencyTypesFilePath -ErrorAction SilentlyContinue | Get-Content | ConvertFrom-Json } } ## Check if there were any entries if ($DependencyTypes.Count -eq 0) { Write-Host "`tNo Dependency Types to add" } else { ## Get the Server's existing Dependency Types $ServerDependencyTypes = Get-TMDependencyType -TMSession $TMSession foreach ($DependencyType in $DependencyTypes) { ## If the server doesn't have the dependency type if ($ServerDependencyTypes.label -notcontains $DependencyType.label) { ## Add it to the server Write-Host "`tLoading Dependency Type: " -NoNewline Write-Host "$($DependencyType.label)" -ForegroundColor DarkBlue $DependencyType = [TMDependencyType]::new($DependencyType.label) New-TMDependencyType -TMSession $TMSession -DependencyType $DependencyType } } } } # Create the App types if ($Components.AppTypes) { Write-Host 'Loading App Types...' $AppTypes = [system.collections.arraylist]@() $AppTypesFolderPath = Join-Path $ExtensionPath 'AppTypes' if (Test-Path $AppTypesFolderPath -ErrorAction SilentlyContinue) { $AppTypeFiles = Get-ChildItem -Path $AppTypesFolderPath -ErrorAction SilentlyContinue $AppTypeFiles | ForEach-Object { [void]($AppTypes.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $AppTypesFilePath = Join-Path $ExtensionPath 'AppTypes.json' if (Test-Path $AppTypesFilePath -ErrorAction SilentlyContinue) { $AppTypes = Get-Content -Path $AppTypesFilePath | ConvertFrom-Json } } ## Check if there were any entries if ($AppTypes.Count -eq 0) { Write-Host "`tNo App Types to add" } else { ## Get the Server's existing App Types $ServerAppTypes = Get-TMAssetOption -Type 'App Type' -TMSession $TMSession foreach ($AppType in $AppTypes) { ## If the server doesn't have the App type if ($ServerAppTypes.label -notcontains $AppType.label) { ## Add it to the server Write-Host "`tLoading App Type: " -NoNewline Write-Host "$($AppType.label)" -ForegroundColor DarkBlue $AppType = [TMAppType]::new($AppType.label) New-TMAssetOption -Type 'App Type' -Name $AppType.label -TMSession $TMSession } } } } # Create the Device types if ($Components.DeviceTypes) { Write-Host 'Loading Device Types...' $DeviceTypes = [system.collections.arraylist]@() $DeviceTypesFolderPath = Join-Path $ExtensionPath 'DeviceTypes' if (Test-Path $DeviceTypesFolderPath -ErrorAction SilentlyContinue) { $DeviceTypeFiles = Get-ChildItem -Path $DeviceTypesFolderPath -ErrorAction SilentlyContinue $DeviceTypeFiles | ForEach-Object { [void]($DeviceTypes.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $DeviceTypesFilePath = Join-Path $ExtensionPath 'DeviceTypes.json' if (Test-Path $AppTypesFilePath -ErrorAction SilentlyContinue) { $DeviceTypes = Get-Content -Path $DeviceTypesFilePath | ConvertFrom-Json } } if ($DeviceTypes.Count -eq 0) { Write-Host "`tNo Device Types to add" } else { ## Get the Server's existing Device Types $ServerDeviceTypes = Get-TMAssetOption -Type 'Asset Type' -TMSession $TMSession foreach ($DeviceType in $DeviceTypes) { ## If the server doesn't have the Device type if ($ServerDeviceTypes.label -notcontains $DeviceType.label) { ## Add it to the server Write-Host "`tLoading Device Type: " -NoNewline Write-Host "$($DeviceType.label)" -ForegroundColor DarkBlue $DeviceType = [TMAssetType]::new($DeviceType.label) New-TMAssetOption -Type 'Asset Type' -Name $DeviceType.label -TMSession $TMSession } } } } # Create the Task Categories if ($Components.TaskCategories) { Write-Host 'Loading Task Categories...' $TaskCategories = [system.collections.arraylist]@() $TaskCategoriesFolderPath = Join-Path $ExtensionPath 'TaskCategories' if (Test-Path $TaskCategoriesFolderPath -ErrorAction SilentlyContinue) { $TaskCategoryFiles = Get-ChildItem -Path $TaskCategoriesFolderPath -ErrorAction SilentlyContinue $TaskCategoryFiles | ForEach-Object { [void]($TaskCategories.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $TaskCategoriesFilePath = Join-Path $ExtensionPath 'TaskCategories.json' if (Test-Path $TaskCategoriesFilePath -ErrorAction SilentlyContinue) { $TaskCategories = Get-Content -Path $TaskCategoriesFilePath | ConvertFrom-Json } } if ($TaskCategories.Count -eq 0) { Write-Host "`tNo Task Categroies to add" } else { ## Get the Server's existing Task Categories $ServerTaskCategories = Get-TMAssetOption -Type 'Task Category' -TMSession $TMSession foreach ($TaskCategory in $TaskCategories) { ## If the server doesn't have the Device type if ($ServerTaskCategories.label -notcontains $TaskCategory.label) { ## Add it to the server Write-Host "`tLoading Task Category: " -NoNewline Write-Host "$($TaskCategory.label)" -ForegroundColor DarkBlue $TaskCategory = [TMTaskCategory]::new($TaskCategory.label) New-TMAssetOption -Type 'Task Category' -Name $TaskCategory.label -TMSession $TMSession } } } } # Load the Teams if ($Components.Teams) { Write-Host 'Loading Teams...' $Teams = [system.collections.arraylist]@() $TeamsFolderPath = Join-Path $ExtensionPath 'Teams' if (Test-Path $TeamsFolderPath -ErrorAction SilentlyContinue) { $TeamFiles = Get-ChildItem -Path $TeamsFolderPath -ErrorAction SilentlyContinue $TeamFiles | ForEach-Object { [void]($Teams.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $TeamsFilePath = Join-Path $ExtensionPath 'Teams.json' if (Test-Path $TeamsFilePath -ErrorAction SilentlyContinue) { $Teams = Get-Content -Path $TeamsFilePath | ConvertFrom-Json } } if ($Teams.Count -eq 0) { Write-Host "`tNo Teams to add" } else { foreach ($Team in $Teams) { Write-Host "`tLoading Team: " -NoNewline Write-Host "$($Team.Code)" -ForegroundColor DarkBlue New-TMTeam -Code $Team.Code -Description $Team.Description -Level $Team.Level -Help $Team.Help -TMSession $TMSession } } } # Load the Tags if ($Components.Tags) { Write-Host 'Loading Tags...' $Tags = [system.collections.arraylist]@() $TagsFolderPath = Join-Path $ExtensionPath 'Tags' if (Test-Path $TagsFolderPath -ErrorAction SilentlyContinue) { $TagFiles = Get-ChildItem -Path $TagsFolderPath -ErrorAction SilentlyContinue $TagFiles | ForEach-Object { [void]($Tags.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $TagsFilePath = Join-Path $ExtensionPath 'Tags.json' if (Test-Path $TagsFilePath -ErrorAction SilentlyContinue) { $Tags = Get-Content -Path $TagsFilePath | ConvertFrom-Json } } if ($Tags.Count -eq 0) { Write-Host "`tNo Tags to add" } else { foreach ($Tag in $Tags) { Write-Host "`tLoading Tag: " -NoNewline Write-Host "$($Tag.Name)" -ForegroundColor $TagColorToForeground.($Tag.Color) New-TMTag -Name $Tag.Name -Description $Tag.description -Color $Tag.color -TMSession $TMSession } } } # Load the Field Specs if ($Components.FieldSpecs) { Write-Host 'Loading Field Specs...' ## Check Extension Package v3 (Folder of Files) $FieldSpecsFolderPath = Join-Path $ExtensionPath 'FieldSpecs' if (Test-Path $FieldSpecsFolderPath -ErrorAction SilentlyContinue) { ## Create the FieldSpecs Object to add all of the fields to $FieldSpecs = [pscustomobject]@{ APPLICATION = [PSCustomObject]@{ lastUpdated = '' dateCreated = '' domain = 'APPLICATION' fields = @() version = 1 } DATABASE = [PSCustomObject]@{ lastUpdated = '' dateCreated = '' domain = 'DATABASE' fields = @() version = 1 } DEVICE = [PSCustomObject]@{ lastUpdated = '' dateCreated = '' domain = 'DEVICE' fields = @() version = 1 } STORAGE = [PSCustomObject]@{ lastUpdated = '' dateCreated = '' domain = 'STORAGE' fields = @() version = 1 } } ## Handle Single JSON field files from folders $SharedFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Shared') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $SharedFieldFiles) { $FieldSpecs.APPLICATION.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) $FieldSpecs.DATABASE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) $FieldSpecs.DEVICE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) $FieldSpecs.STORAGE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } $ApplicationFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Application') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $ApplicationFieldFiles) { $FieldSpecs.APPLICATION.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } $DatabaseFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Database') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $DatabaseFieldFiles) { $FieldSpecs.DATABASE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } $DeviceFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Device') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $DeviceFieldFiles) { $FieldSpecs.DEVICE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } $StorageFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Storage') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $StorageFieldFiles) { $FieldSpecs.STORAGE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } } else { ## Check Extension Package <v3 (Single FieldSpecs.json file) $FieldSpecsFilePath = Join-Path $ExtensionPath 'FieldSpecs.json' if (Test-Path $FieldSpecsFilePath -ErrorAction SilentlyContinue) { ## Version 6.0.2.1+ (and TODO version 6.1?, 5x?) ## Requires the class names to be lower case if ($TMSessionConfig.TMVersion -ge '6.1.0') { ## Convert each Asset Class name to lowercase $FieldSpecs = ((Get-Content -Path $FieldSpecsFilePath) -replace 'constraints', 'constraint') } else { ## No conversion is necessary, assign the provided fields as the updates $FieldSpecs = (((Get-Content -Path $FieldSpecsFilePath) -replace 'constraint', 'constraints') -replace 'constraintss', 'constraints') } $FieldSpecs = $FieldSpecs | ConvertFrom-Json } } ## Determine if any fields are needed if (-Not $FieldSpecs) { Write-Host "`tNo Asset Field Specs to add" } else { 'APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE' | ForEach-Object { $DomainColor = switch ($_) { 'APPLICATION' { 'DarkMagenta' } 'DEVICE' { 'DarkCyan' } 'DATABASE' { 'DarkYellow' } 'STORAGE' { 'DarkGreen' } } foreach ($Field in $FieldSpecs.$_.fields) { Write-Host "`tLoading Field Spec: [" -NoNewline Write-Host "$_" -ForegroundColor $DomainColor -NoNewline Write-Host '] ' -NoNewline Write-Host "$($Field.label)" -ForegroundColor DarkBlue } } Update-TMFieldSpecs -FieldSpecs $FieldSpecs -TMSession $TMSession } } # Load the Bundles if ($Components.Bundles) { Write-Host 'Loading Bundles...' $Bundles = [system.collections.arraylist]@() $BundlesFolderPath = Join-Path $ExtensionPath 'Bundles' if (Test-Path $BundlesFolderPath -ErrorAction SilentlyContinue) { $BundleFiles = Get-ChildItem -Path $BundlesFolderPath -ErrorAction SilentlyContinue $BundleFiles | ForEach-Object { [void]($Bundles.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $BundlesFilePath = Join-Path $ExtensionPath 'Bundles.json' if (Test-Path $BundlesFilePath -ErrorAction SilentlyContinue) { $Bundles = Get-Content -Path $BundlesFilePath | ConvertFrom-Json } } if ($Bundles.Count -eq 0) { Write-Host "`tNo Bundles to add" } else { foreach ($Bundle in $Bundles) { Write-Host "`tLoading Bundle: " -NoNewline Write-Host "$($Bundle.Name)" -ForegroundColor DarkBlue New-TMBundle -Bundle $Bundle -TMSession $TMSession } } } # Load the Events if ($Components.Events) { Write-Host 'Loading Events...' $Events = [system.collections.arraylist]@() $EventsFolderPath = Join-Path $ExtensionPath 'Events' if (Test-Path $EventsFolderPath -ErrorAction SilentlyContinue) { $EventFiles = Get-ChildItem -Path $EventsFolderPath -ErrorAction SilentlyContinue $EventFiles | ForEach-Object { [void]($Events.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $EventsFilePath = Join-Path $ExtensionPath 'Events.json' if (Test-Path $EventsFilePath -ErrorAction SilentlyContinue) { $Events = Get-Content -Path $EventsFilePath | ConvertFrom-Json } } if ($Events.Count -eq 0) { Write-Host "`tNo Events to add" } else { foreach ($TMEvent in $Events) { Write-Host "`tLoading Event: " -NoNewline Write-Host "$($TMEvent.Name)" -ForegroundColor DarkBlue New-TMEvent $TMEvent -TMSession $TMSession } } } # Load the Providers if ($Components.Providers) { Write-Host 'Loading Providers...' $Providers = [system.collections.arraylist]@() $ProvidersFolderPath = Join-Path $ExtensionPath 'Providers' if (Test-Path $ProvidersFolderPath -ErrorAction SilentlyContinue) { $ProviderFiles = Get-ChildItem -Path $ProvidersFolderPath -ErrorAction SilentlyContinue $ProviderFiles | ForEach-Object { [void]($Providers.Add((Get-Content -Path $_.FullName | ConvertFrom-Json))) } } else { $ProvidersFilePath = Join-Path $ExtensionPath 'Providers.json' if (Test-Path $ProvidersFilePath -ErrorAction SilentlyContinue) { $Providers = Get-Content -Path $ProvidersFilePath | ConvertFrom-Json } } if ($Providers.Count -eq 0) { Write-Host "`tNo Providers to add" } else { foreach ($Provider in $Providers) { if ($FilterByProvider -and ($Provider.Name -notin $ProviderName)) { continue } Write-Host "`tLoading Provider: " -NoNewline Write-Host "$($Provider.Name)" -ForegroundColor DarkBlue New-TMProvider -Provider $Provider -TMSession $TMSession } } } # Load Actions if ($Components.Actions) { Write-Host 'Loading Actions...' $Actions = @() $ActionsPath = Join-Path $ExtensionPath 'Actions' if (Test-Path -Path $ActionsPath -ErrorAction SilentlyContinue) { $Actions = Get-ChildItem -Path $ActionsPath -Recurse -Include '*.ps1' -ErrorAction SilentlyContinue | ForEach-Object { Read-TMActionScriptFile -Path $_.FullName } } if ($FilterByProvider) { $Actions = $Actions | Where-Object { $_.Provider.Name -in $ProviderName } } if ($Actions.count -eq 0) { Write-Host "`tNo Actions to add" } else { foreach ($Action in $Actions) { Write-Host "`tLoading Action: " -NoNewline Write-Host "$($Action.Name)" -ForegroundColor DarkBlue New-TMAction -Action $Action -Update -TMSession $TMSession } } } # DataScripts if ($Components.Datascripts) { Write-Host 'Loading DataScripts...' $DataScripts = @() $DataScriptsPath = Join-Path $ExtensionPath 'DataScripts' if (Test-Path -Path $DataScriptsPath -ErrorAction SilentlyContinue) { $DataScripts = Get-ChildItem -Path $DataScriptsPath -Recurse -Include '*.groovy' -ErrorAction SilentlyContinue | ForEach-Object { Read-TMDatascriptFile -Path $_.FullName } } if ($FilterByProvider) { $DataScripts = $DataScripts | Where-Object { $_.Provider.Name -in $ProviderName } } if ($DataScripts.count -eq 0) { Write-Host "`tNo DataScripts to add" } else { foreach ($DataScript in $DataScripts) { Write-Host "`tLoading DataScript: " -NoNewline Write-Host "$($DataScript.Name)" -ForegroundColor DarkBlue New-TMDatascript -Datascript $DataScript -Update -TMSession $TMSession } } } # Recipes if ($Components.Recipes) { Write-Host 'Loading Recipes...' $Recipes = @() $RecipesPath = Join-Path $ExtensionPath 'Recipes' if (Test-Path -Path $RecipesPath -ErrorAction SilentlyContinue) { $Recipes = Get-ChildItem -Path $RecipesPath -Recurse -Include '*.json' -ErrorAction SilentlyContinue | ForEach-Object { Read-TMRecipeScriptFile -Path $_.FullName -UpdateFieldNames } } if ($Recipes.count -eq 0) { Write-Host "`tNo DataScripts to add" } else { foreach ($Recipe in $Recipes) { Write-Host "`tLoading Recipe: " -NoNewline Write-Host "$($Recipe.Name)" -ForegroundColor DarkBlue New-TMRecipe -Recipe $Recipe -Update -TMSession $TMSession } } } # Load the AssetViews if ($Components.AssetViews) { ## Convert any Custom Field Label Tokens from a template ## Get a FieldToLabel map to build string replacements $AssetViews = [System.Collections.ArrayList]@() $LabelReplacements = [System.Collections.ArrayList]@() $FieldToLabelMap = Get-TMFieldToLabelMap ## Create a replacement token for each asset/label = field set foreach ($DomainClass in @('DEVICE', 'APPLICATION', 'DATABASE', 'STORAGE' )) { foreach ($FieldName in $FieldToLabelMap.$DomainClass.Keys) { if ($FieldName -match 'custom\d+') { # Write-Host $FieldName [void]($LabelReplacements.Add([pscustomobject]@{ DomainClass = $DomainClass FieldLabel = $FieldToLabelMap.$DomainClass.$FieldName FieldName = $FieldName } ) ) } } } $AssetViewsFolderPath = Join-Path $ExtensionPath 'AssetViews' $AssetViewsFilePath = Join-Path $ExtensionPath 'AssetViews.json' Write-Host 'Loading AssetViews...' if (Test-Path $AssetViewsFolderPath -ErrorAction SilentlyContinue) { $AssetViews = Get-ChildItem -Path $AssetViewsFolderPath -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in @('.json') } | ForEach-Object { Get-Content -Path $_.FullName | ConvertFrom-Json } } else { if (-Not (Test-Path $AssetViewsFilePath -ErrorAction SilentlyContinue)) { Write-Host "`tNo AssetViews to add" } else { $AssetViews = Get-Content -Path $AssetViewsFilePath | ConvertFrom-Json if ($AssetViews.Count -eq 0) { Write-Host "`tNo Asset Views to add" } } } foreach ($AssetView in $AssetViews) { Write-Host "`tLoading AssetView: " -NoNewline Write-Host "$($AssetView.Name)" -ForegroundColor DarkBlue ## Replace all 'customN' items with a codestring to replace the correct items in the offline package $SourceCodeLines = ($AssetView | ConvertTo-Json -Depth 100) -split "`r`n" -split "`r" -split "`n" $UpdatedSourceCodeLines = [System.Collections.ArrayList]::new() foreach ($SourceCodeLine in $SourceCodeLines) { $CurrentLine = $SourceCodeLine $LabelReplacements | ForEach-Object { $ReplaceString = "customN\|$($_.DomainClass)\|$($_.FieldLabel)\|" if ($CurrentLine -match $ReplaceString) { $CurrentLine = $CurrentLine -replace $ReplaceString, $_.FieldName } } [void]($UpdatedSourceCodeLines.Add($CurrentLine)) } $AssetView = $UpdatedSourceCodeLines -Join "`n" | ConvertFrom-Json New-TMAssetViewConfiguration $AssetView -TMSession $TMSession -Update } } # SetupScripts if ($Components.SetupScripts) { Write-Host 'Collecting Setup Scripts...' $SetupScriptsFolderPath = Join-Path $ExtensionPath '_SETUP' if (-Not (Test-Path $SetupScriptsFolderPath -ErrorAction SilentlyContinue)) { Write-Host "`tNo Setup Scripts to run" } else { $SetupScriptFiles = Get-ChildItem -Path $SetupScriptsFolderPath -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -eq '.ps1' } foreach ($SetupScriptFile in $SetupScriptFiles) { Write-Host "`Queuing Setup Script: " -NoNewline Write-Host "$($SetupScriptFile.Name)" -ForegroundColor DarkBlue [void]$SetupScriptsToRun.Add($SetupScriptFile) } } } } ## Sort the Setup Scripts to run by name $SortedSetupScripts = $SetupScriptsToRun | Sort-Object -Property 'Name' ## Iterate over each setup script and run them each foreach ($SetupScript in $SortedSetupScripts) { Write-Host "`tRunning Setup Script: " -NoNewline Write-Host "$($SetupScript.Name)" -ForegroundColor DarkBlue ## Try the script try { & "$($SetupScript.FullName)" } catch { Write-Host 'Setup Script Failed!' Write-Error $_ } } } } function Export-TMExtension { <# .SYNOPSIS Exports a TransitionManager project into a portable configuration package that can be applied in a different project and/or on a different server .DESCRIPTION This function will retreieve the different configuration components from a TransitonManager project and save them locally in a standardized format that can then be used to import those configurations on another TM project or TM instance. .PARAMETER Name What the Reference Design will be named .PARAMETER TMSession The name of the TM Session to use when creating a Team .PARAMETER Project The project that will be exported .PARAMETER Exclude An array of components to skip when performing the export. Options are: DependencyTypes FieldSpecs Bundles Providers Actions DataScripts Recipes AssetViews Events AppTypes DeviceTypes Teams TaskCategories .PARAMETER OutPath The directory where the Reference Design will be saved .EXAMPLE $Credential = Get-StoredCredential -Name 'ME' $ExportSplat = @{ Name = 'VMWare - vCenter' Server = 'tmddev.transitionmanager.net' Project = 'Extension - VMware vCenter' Credential = $Credential } Export-TMExtension @ExportSplat .OUTPUTS None #> [alias('Export-TMExtension')] [CmdletBinding(DefaultParameterSetName = 'ByInclusion')] param ( [Parameter(Mandatory = $false, Position = 0)] [String]$Name, [Parameter(Mandatory = $false, Position = 1)] [String]$TMSession = 'Default', [Parameter(Mandatory = $false, Position = 2)] [String]$Project, [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByInclusion')] [ValidateSet( '*', 'DependencyTypes', 'FieldSpecs', 'Bundles', 'Providers', 'Actions', 'DataScripts', 'Recipes', 'Tags', 'AssetData', 'AssetViews', 'Events', 'AppTypes', 'DeviceTypes', 'Teams', 'TaskCategories' )] [String[]]$Include = '*', [Parameter(Mandatory = $false, Position = 3, ParameterSetName = 'ByExclusion')] [ValidateSet( 'DependencyTypes', 'FieldSpecs', 'Bundles', 'Providers', 'Actions', 'DataScripts', 'Recipes', 'Tags', 'AssetData', 'AssetViews', 'Events', 'AppTypes', 'DeviceTypes', 'Teams', 'TaskCategories' )] [String[]]$Exclude, [Parameter(Mandatory = $false, Position = 4)] [String[]]$ProviderName, [Parameter(Mandatory = $false, Position = 5)] [AllowNull()] [String]$OutPath = '', [Parameter(Mandatory = $false, Position = 6)] [Switch]$ClearExistingFiles ) begin { # Get the session configuration Write-Verbose 'Checking for cached TMSession' $TMSessionConfig = $global:TMSessions[$TMSession] Write-Debug 'TMSessionConfig:' Write-Debug ($TMSessionConfig | ConvertTo-Json -Depth 5) if (-not $TMSessionConfig) { throw "TMSession '$TMSession' not found. Use New-TMSession command before using features." } if (!(Get-Module 'TMD.Common')) { Import-Module 'TMD.Common' } # Default to the reference design directory if an out path wasn't specified if ([String]::IsNullOrWhiteSpace($OutPath)) { Write-Verbose 'No OutPath as provided and will use the default export location' $OutPath = Join-Path $Global:userPaths.referencedesigns 'Exported' $TMSessionConfig.TMServer } # Set up a configuration object to determine which components to install later in the process block $Components = @{ DependencyTypes = $true FieldSpecs = $true Bundles = $true Providers = $true Actions = $true DataScripts = $true Recipes = $true Tags = $true AssetData = $true AssetViews = $true Events = $true AppTypes = $true DeviceTypes = $true Teams = $true TaskCategories = $true } switch ($PSCmdlet.ParameterSetName) { 'ByInclusion' { if ($Include -notcontains '*') { foreach ($Key in $Components.GetEnumerator().Name) { if ($Include -notcontains $Key) { $Components.$Key = $false } } } } 'ByExclusion' { # Process the Exclude parameter and turn components off $Exclude | ForEach-Object { $Components.$_ = $false } } } # Write about what is going to be exported Write-Host 'Exporting Component Types from Project:' foreach ($Component in $Components.Keys) { ## For each Enabled Component if ($Components.$Component) { Write-Host "`t$Component" -ForegroundColor Cyan } } # Determine the project to export if (-not [String]::IsNullOrWhiteSpace($Project) -and ($TMSessionConfig.userContext.project.name -ne $Project)) { Enter-TMProject -ProjectName $Project -TMSession $TMSession } else { $Project = $TMSessionConfig.userContext.project.name } ## Set the name to the project name if it wasn't provided $Name = [String]::IsNullOrWhiteSpace($Name) ? $Project : $Name ## Write a Banner Write-Host 'Exporting TM Extension From:' Write-Host "`tServer: " -NoNewline Write-Host $TMSessionConfig.TMServer -ForegroundColor Cyan Write-Host "`tProject: " -NoNewline Write-Host $Project -ForegroundColor Cyan Write-Host "`tAt: " -NoNewline Write-Host (Get-Date).ToString() -ForegroundColor Cyan Write-Host "`tTo: " -NoNewline Write-Host $OutPath -ForegroundColor Cyan ## Clear existing files from the output folder if ($ClearExistingFiles) { Write-Host 'Export is Clearing Existing files before writing!!' -ForegroundColor Magenta ## Delete Files $ExistingFiles = Get-ChildItem -Path (Join-Path $OutPath $Name '_REFERENCE DESIGN') -Recurse -File -Force -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 $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' } } $FilterByProvider = $false Write-Host "`tProviders:`t" -NoNewline if ($ProviderName) { Write-Host $ProviderName -ForegroundColor Cyan $FilterByProvider = $true } else { Write-Host 'All Providers' -ForegroundColor Cyan } } process { # Set up the directory structure for all of the output files $OutputDirectory = Join-Path $OutPath $Name $ExtensionDirectory = Join-Path $OutputDirectory '_REFERENCE DESIGN' $ExtensionDataDirectory = Join-Path $OutputDirectory '_REFERENCE DATA' Test-FolderPath $ExtensionDirectory Test-FolderPath (Join-Path $OutputDirectory 'Documentation') Test-FolderPath (Join-Path $OutputDirectory '_REFERENCE DATA') ## ## Export all of the components ## # Export the Dependency Types if ($Components.DependencyTypes) { Write-Host 'Exporting Dependency Types...' -NoNewline $DependencyTypesFolderPath = Join-Path $ExtensionDirectory 'DependencyTypes' Test-FolderPath -FolderPath $DependencyTypesFolderPath $DependencyTypes = Get-TMDependencyType -ResetIDs -TMSession $TMSession if (!$DependencyTypes) { Write-Host "`tNo Dependency Types to export" } else { $DependencyTypes | ForEach-Object { if (-Not [string]::IsNullOrWhiteSpace($_.label.Trim())) { $Filename = Get-FilenameSafeString -String $_.label $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $DependencyTypesFolderPath "$Filename.json") -Force } } Write-Host "`t$($DependencyTypes.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Dependency Type(s) exported' } } # Export the App Types if ($Components.AppTypes) { Write-Host "Exporting App Types...`t" -NoNewline $AppTypesFolderPath = Join-Path $ExtensionDirectory 'AppTypes' Test-FolderPath -FolderPath $AppTypesFolderPath $AppTypes = Get-TMAssetOption -Type 'App Type' -ResetIDs -TMSession $TMSession if (!$AppTypes) { Write-Host "`tNo App Types to export" } else { $AppTypes | ForEach-Object { if (-Not [string]::IsNullOrWhiteSpace($_.label.Trim())) { $Filename = Get-FilenameSafeString -String $_.label $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $AppTypesFolderPath "$Filename.json") -Force } } Write-Host "`t$($AppTypes.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'App Type(s) exported' } } # Export the Device Types if ($Components.DeviceTypes) { Write-Host 'Exporting Device Types...' -NoNewline $DeviceTypesFolderPath = Join-Path $ExtensionDirectory 'DeviceTypes' Test-FolderPath -FolderPath $DeviceTypesFolderPath $DeviceTypes = Get-TMAssetOption -Type 'Asset Type' -ResetIDs -TMSession $TMSession if (!$DeviceTypes) { Write-Host "`tNo Device Types to export" } else { $DeviceTypes | ForEach-Object { if (-Not [string]::IsNullOrWhiteSpace($_.label.Trim())) { $Filename = Get-FilenameSafeString -String $_.label $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $DeviceTypesFolderPath "$Filename.json") -Force } } Write-Host "`t$($DeviceTypes.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Device Type(s) exported' } } # Export the Task Categories if ($Components.TaskCategories) { Write-Host 'Exporting Task Categories...' -NoNewline $TaskCategoriesFolderPath = Join-Path $ExtensionDirectory 'TaskCategories' Test-FolderPath -FolderPath $TaskCategoriesFolderPath $TaskCategories = Get-TMAssetOption -Type 'Task Category' -ResetIDs -TMSession $TMSession if (!$TaskCategories) { Write-Host "`tNo Task Categories to export" } else { $TaskCategories | ForEach-Object { if (-Not [string]::IsNullOrWhiteSpace($_.label.Trim())) { $Filename = Get-FilenameSafeString -String $_.label $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $TaskCategoriesFolderPath "$Filename.json") -Force } } Write-Host "`t$($TaskCategories.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Task Categories exported' } } # Export the Field Specs if ($Components.FieldSpecs) { Write-Host 'Exporting Field Specs...' -NoNewline $FieldSpecs = Get-TMFieldSpecs -ResetIDs -CustomOnly -TMSession $TMSession if (!$FieldSpecs) { Write-Host "`tNo Asset Field Specs to export" } else { $FieldSpecsFolderPath = Join-Path $ExtensionDirectory 'FieldSpecs' Test-FolderPath -Path (Join-Path $FieldSpecsFolderPath 'Shared') Test-FolderPath -Path (Join-Path $FieldSpecsFolderPath 'Application') Test-FolderPath -Path (Join-Path $FieldSpecsFolderPath 'Database') Test-FolderPath -Path (Join-Path $FieldSpecsFolderPath 'Device') Test-FolderPath -Path (Join-Path $FieldSpecsFolderPath 'Storage') ## Iterate through each Domain Class ##### This is done in this order expressly expressly because shared fields will be UPDATED because it exists in each domain. ##### This order ensures that if there is variation in the color/order, etc. Application will win. ##### This is because the Shared fields are inserted with ONE configuration across each domain. $DomainClasses = @('STORAGE', 'DATABASE', 'DEVICE', 'APPLICATION') foreach ($DomainClass in $DomainClasses) { ## Iterate over each Domain Field $DomainFields = $FieldSpecs.$DomainClass.fields foreach ($DomainField in $DomainFields) { $FieldFileName = Get-FilenameSafeString "$($DomainField.order)-$($DomainField.label)" $FieldFilePath = Join-Path $FieldSpecsFolderPath ($DomainField.shared -eq 1 ? 'Shared' : $DomainClass) ($FieldFileName + '.json') Set-Content -Path $FieldFilePath -Value ($DomainField | ConvertTo-Json -Depth 5) -Force } } Write-Host "`tAsset Field Specs exported" } } # Export the Bundles if ($Components.Bundles) { Write-Host "Exporting Bundles...`t" -NoNewline $BundlesFolderPath = Join-Path $ExtensionDirectory 'Bundles' Test-FolderPath -FolderPath $BundlesFolderPath $Bundles = Get-TMBundle -ResetIDs -TMSession $TMSession if (!$Bundles) { Write-Host "`tNo Bundles to export" } else { $Bundles | ForEach-Object { $Filename = Get-FilenameSafeString -String $_.Name $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $BundlesFolderPath "$Filename.json") -Force } Write-Host "`t$($Bundles.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Bundle(s) exported' } } # Export the Events if ($Components.Events) { Write-Host "Exporting Events...`t" -NoNewline $EventsFolderPath = Join-Path $ExtensionDirectory 'Events' Test-FolderPath -FolderPath $EventsFolderPath $Events = Get-TMEvent -ResetIDs -TMSession $TMSession if (!$Events) { Write-Host "`tNo Events to export" } else { $Events | ForEach-Object { $Filename = Get-FilenameSafeString -String $_.Name $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $EventsFolderPath "$Filename.json") -Force } Write-Host "`t$($Events.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Event(s) exported' } } # Export the Teams if ($Components.Teams) { Write-Host "Exporting Teams...`t" -NoNewline $TeamsFolderPath = Join-Path $ExtensionDirectory 'Teams' Test-FolderPath -FolderPath $TeamsFolderPath $Teams = Get-TMTeam -TMSession $TMSession if (!$Teams) { Write-Host "`tNo Teams to export" } else { $Teams | ForEach-Object { $Filename = Get-FilenameSafeString -String $_.Code $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $TeamsFolderPath "$Filename.json") -Force } Write-Host "`t$($Teams.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Team(s) exported' } } # Export the Providers if ($Components.Providers) { Write-Host "Exporting Providers...`t" -NoNewline $ProvidersFolderPath = Join-Path $ExtensionDirectory 'Providers' Test-FolderPath -FolderPath $ProvidersFolderPath $Providers = Get-TMProvider -ResetIDs -TMSession $TMSession if (!$Providers) { Write-Host "`tNo Providers to export" } else { if ($FilterByProvider) { $Providers = $Providers | Where-Object Name -In $ProviderName } $Providers | ForEach-Object { $Filename = Get-FilenameSafeString -String $_.Name $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $ProvidersFolderPath "$Filename.json") -Force } Write-Host "`t$($Providers.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Provider(s) exported' } } # Export the Actions if ($Components.Actions) { Write-Host "Exporting Actions...`t" -NoNewline $ActionsFolderPath = Join-Path $ExtensionDirectory 'Actions' Test-FolderPath $ActionsFolderPath $Actions = Get-TMAction -SaveCodePath $ActionsFolderPath -ProviderName $ProviderName -ResetIDs -Passthru -TMSession $TMSession if (!$Actions) { Write-Host "`tNo Actions to export" } else { Write-Host "`t$($Actions.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Action(s) exported' } } # Export the DataScript Scripts if ($Components.DataScripts) { Write-Host 'Exporting DataScripts...' -NoNewline $DataScriptsFolderPath = Join-Path $ExtensionDirectory 'DataScripts' Test-FolderPath $DataScriptsFolderPath $DataScripts = Get-TMDatascript -ResetIDs -SaveCodePath $DataScriptsFolderPath -ProviderName $ProviderName -Passthru -TMSession $TMSession if (!$DataScripts) { Write-Host "`tNo Datascripts to export" } else { # TODO: Is the 'Private' folder supposed to be excluded? Write-Host "`t$($DataScripts.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Datascript(s) exported' } } # Export the Recipes if ($Components.Recipes) { Write-Host "Exporting Recipes...`t" -NoNewline $RecipesFolderPath = Join-Path $ExtensionDirectory 'Recipes' Test-FolderPath $RecipesFolderPath $Recipes = Get-TMRecipe -ResetIDs -SaveCodePath $RecipesFolderPath -Passthru -TMSession $TMSession if (!$Recipes) { Write-Host "`tNo Recipes to export" } else { Write-Host "`t$($Recipes.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Recipe(s) exported' } } # Export the Tags if ($Components.Tags) { Write-Host "Exporting Tags...`t" -NoNewline $TagsFolderPath = Join-Path $ExtensionDirectory 'Tags' Test-FolderPath -FolderPath $TagsFolderPath $Tags = Get-TMTag -ResetIDs -TMSession $TMSession if (!$Tags) { Write-Host "`tNo Tags to export" } else { $Tags | ForEach-Object { $Filename = Get-FilenameSafeString -String $_.Name $_ | ConvertTo-Json -Depth 100 | Set-Content -Path (Join-Path $TagsFolderPath "$Filename.json") -Force } Write-Host "`t$($Tags.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'Tags(s) exported' } } # Export the AssetViews if ($Components.AssetViews) { Write-Host "Exporting AssetViews...`t" -NoNewline $AssetViewsFolderPath = Join-Path $ExtensionDirectory 'AssetViews' Test-FolderPath -FolderPath $AssetViewsFolderPath $AssetViews = Get-TMAssetViewConfiguration -ResetIDs -TMSession $TMSession -ExcludeSystem if (!$AssetViews) { Write-Host "`tNo AssetViews to export" } else { ## Get a FieldToLabel map to build string replacements $LabelReplacements = [System.Collections.ArrayList]@() $FieldToLabelMap = Get-TMFieldToLabelMap ## Create a replacement token for each asset/label = field set foreach ($DomainClass in @('DEVICE', 'APPLICATION', 'DATABASE', 'STORAGE' )) { foreach ($FieldName in $FieldToLabelMap.$DomainClass.Keys) { if ($FieldName -match 'custom\d+') { # Write-Host $FieldName [void]($LabelReplacements.Add([pscustomobject]@{ DomainClass = $DomainClass FieldLabel = $FieldToLabelMap.$DomainClass.$FieldName FieldName = $FieldName } ) ) } } } $AssetViews | ForEach-Object { $AssetViewName = $_.name ## Replace all customN in the JSON Data $SourceCodeLines = ($_ | ConvertTo-Json -Depth 10) -split "`n" $UpdatedSourceCodeLines = [System.Collections.ArrayList]::new() foreach ($SourceCodeLine in $SourceCodeLines) { $CurrentLine = $SourceCodeLine $LabelReplacements | ForEach-Object { if ($CurrentLine -match $_.FieldName) { $CurrentLine = $CurrentLine -replace $_.FieldName, "customN|$($_.DomainClass)|$($_.FieldLabel)|" } } [void]($UpdatedSourceCodeLines.Add($CurrentLine)) } Test-FolderPath -FolderPath $AssetViewsFolderPath $AssetViewFileName = Get-FilenameSafeString $AssetViewName $UpdatedSourceCodeLines -join "`n" | Set-Content -Path (Join-Path $AssetViewsFolderPath "$AssetViewFileName.json") -Force } Write-Host "`t$($AssetViews.Count) " -NoNewline -ForegroundColor DarkBlue Write-Host 'AssetView(s) exported' } } # Export Asset Data if ($Components.AssetData) { Write-Host 'Exporting Asset Data...' -NoNewline Export-TMExcel -Path $ExtensionDataDirectory -TMSession $TMSession Write-Host "`t`tAsset Data exported" } Write-Host 'Creating package.json file...' New-TMExtensionPackageFile -Name $Name -Path $OutputDirectory } } function New-TMExtensionPackageFile { <# .SYNOPSIS Creates a new package.json file for a locally downloaded/exported Reference Design .DESCRIPTION This function will look at the .PARAMETER Name Parameter description .PARAMETER Path Parameter description .PARAMETER Description Parameter description .PARAMETER Author Parameter description .PARAMETER Email Parameter description .PARAMETER Passthru A switch indicating that the created package object should be sent back to the caller .EXAMPLE New-TMExtensionPackageFile -Name 'TM - Rules Engine' -Path 'C:\DEV\Downloaded Reference Design' .EXAMPLE $PackageSplat = @{ Name = VMware - vCenter' Path = 'C:\Users\user\TMD_Files\Reference Designs\Downloaded\VMware - vCenter' Description = 'This is a detailed description of the Reference Design' Author = 'Me' Email = 'me@tdsi.com' } New-TMExtensionPackageFile @PackageSplat .EXAMPLE $ExtensionPackage = New-TMExtensionPackageFile -Name 'New Extension' -Path 'C:\DEV\Extension' -Passthru Write-Host "There are $($ExtensionPackage.Inventory.actions.Count) action(s) included in this Reference Design" .OUTPUTS A [PSCustomObject] if -Passthru is specified, otherwise nothing #> [alias('New-TMReferenceDesignPackageFile')] param( [Parameter(Mandatory = $true, Position = 0)] [String]$Name, [Parameter(Mandatory = $true, Position = 1)] [ValidateScript( { (Get-Item $_) -is [System.IO.DirectoryInfo] })] [String]$Path, [Parameter(Mandatory = $false, Position = 2)] [String]$Description, [Parameter(Mandatory = $false, Position = 3)] [String]$Author, [Parameter(Mandatory = $false, Position = 4)] [String]$Email, [Parameter(Mandatory = $false)] [Switch]$Passthru ) $Inventory = @{ Artifacts = [System.Collections.ArrayList]::new() ArtifactLinks = [System.Collections.ArrayList]::new() Actions = [System.Collections.ArrayList]::new() DataScripts = [System.Collections.ArrayList]::new() Recipes = [System.Collections.ArrayList]::new() Bundles = [System.Collections.ArrayList]::new() Providers = [System.Collections.ArrayList]::new() DependencyTypes = [System.Collections.ArrayList]::new() FieldSpecs = @{} } $ExtensionPath = Join-Path $Path '_REFERENCE DESIGN' ## Read the Actions Folder to get the name of Scripts $ActionFiles = Get-ChildItem -Path (Join-Path $ExtensionPath 'Actions') -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.ps1' } foreach ($ActionFile in $ActionFiles) { ## Convert the Script into a proper ActionObject $Action = Read-TMActionScriptFile -Path $ActionFile.FullName ## Some files are not setup or created with all of the metadata. Fill in details based on the filename. if (-Not $Action.Name) { ## Add this Action to the Actions array for writing to JSON [void]$Inventory.Actions.Add( [PSCustomObject]@{ name = $ActionFile.BaseName description = '' } ) } else { ## Add this Action to the Actions array for writing to JSON [void]$Inventory.Actions.Add( [PSCustomObject]@{ name = $Action.Name description = $Action.Description } ) } } ## Read the DataScript Scripts Folder to get the name Scripts if (Test-Path($ExtensionPath + '\DataScripts')) { $DataScriptFiles = Get-ChildItem -Path (Join-Path $ExtensionPath 'DataScripts') -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.groovy' } } elseif (Test-Path($ExtensionPath + '\ETL')) { $DataScriptFiles = Get-ChildItem -Path (Join-Path $ExtensionPath 'ETL') -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.groovy' } } foreach ($DataScriptFile in $DataScriptFiles) { ## Convert the Script into a proper TMDatascript Object $DataScript_File = Read-TMDatascriptFile -Path $DataScriptFiles.FullName -ErrorAction 'SilentlyContinue' ## Some files are not setup or created with all of the metadata. Fill in details based on the filename. if (-Not $DataScript_File) { [void]$Inventory.DataScripts.Add( [PSCustomObject]@{ name = $DataScript_File.name description = '' } ) } else { ## Add this DataScript Script to the array for writing to JSON [void]$Inventory.DataScripts.Add( [PSCustomObject]@{ name = $DataScript_File.name description = $DataScript_File.description } ) } } ## Read the Recipes Folder to get the name Scripts $RecipeFiles = Get-ChildItem -Path (Join-Path $ExtensionPath 'Recipes') -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.json' } foreach ($RecipeFile in $RecipeFiles) { ## Convert the Script into a proper TMRecipeScript Object $Recipe = Read-TMRecipeScriptFile -Path $RecipeFile.FullName ## Some files are not setup or created with all of the metadata. Fill in details based on the filename. if (-Not $Recipe) { ## Add this Recipe Script to the array for writing to JSON [void]$Inventory.Recipes.Add( [PSCustomObject]@{ name = $RecipeFile.name description = $RecipeFile.description } ) } else { ## Add this Recipe Script to the array for writing to JSON [void]$Inventory.Recipes.Add( [PSCustomObject]@{ name = $Recipe.name description = $Recipe.description } ) } } # Get the bundles to add to the inventory $BundleFile = Join-Path $ExtensionPath '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 $ExtensionPath 'Providers.json' if (Test-Path $ProvidersFile -ErrorAction SilentlyContinue) { 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 $ExtensionPath 'DependencyTypes.json' if (Test-Path $DependencyTypesFile -ErrorAction SilentlyContinue) { Get-Content -Path $DependencyTypesFile | ConvertFrom-Json | ForEach-Object { [void]$Inventory.DependencyTypes.Add($_.label) } } ## Check Extension Package v3 (Folder of Files) $FieldSpecsFolderPath = Join-Path $ExtensionPath 'FieldSpecs' if (Test-Path $FieldSpecsFolderPath -ErrorAction SilentlyContinue) { ## Create the FieldSpecs Object to add all of the fields to $FieldSpecs = [pscustomobject]@{ APPLICATION = [PSCustomObject]@{ lastUpdated = '' dateCreated = '' domain = 'APPLICATION' fields = @() version = 1 } DATABASE = [PSCustomObject]@{ lastUpdated = '' dateCreated = '' domain = 'DATABASE' fields = @() version = 1 } DEVICE = [PSCustomObject]@{ lastUpdated = '' dateCreated = '' domain = 'DEVICE' fields = @() version = 1 } STORAGE = [PSCustomObject]@{ lastUpdated = '' dateCreated = '' domain = 'STORAGE' fields = @() version = 1 } } ## Handle Single JSON field files from folders $SharedFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Shared') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $SharedFieldFiles) { $FieldSpecs.APPLICATION.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) $FieldSpecs.DATABASE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) $FieldSpecs.DEVICE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) $FieldSpecs.STORAGE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } $ApplicationFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Application') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $ApplicationFieldFiles) { $FieldSpecs.APPLICATION.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } $DatabaseFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Database') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $DatabaseFieldFiles) { $FieldSpecs.DATABASE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } $DeviceFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Device') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $DeviceFieldFiles) { $FieldSpecs.DEVICE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } $StorageFieldFiles = Get-ChildItem -Path (Join-Path $FieldSpecsFolderPath 'Storage') -Filter '*.json' -Force -ErrorAction 'SilentlyContinue' foreach ($FieldFile in $StorageFieldFiles) { $FieldSpecs.STORAGE.fields += (Get-Content -Path $FieldFile.FullName | ConvertFrom-Json) } } else { ## Check Extension Package <v3 (Single FieldSpecs.json file) $FieldSpecsFilePath = Join-Path $ExtensionPath 'FieldSpecs.json' if (Test-Path $FieldSpecsFilePath -ErrorAction SilentlyContinue) { ## Version 6.0.2.1+ (and TODO version 6.1?, 5x?) ## Requires the class names to be lower case if ($TMSessionConfig.TMVersion -ge '6.1.0') { ## Convert each Asset Class name to lowercase $FieldSpecsData = ((Get-Content -Path $FieldSpecsFilePath) -replace 'constraints', 'constraint') } else { ## No conversion is necessary, assign the provided fields as the updates $FieldSpecsData = (((Get-Content -Path $FieldSpecsFilePath) -replace 'constraint', 'constraints') -replace 'constraintss', 'constraints') } } $FieldSpecs = $FieldSpecsData | ConvertFrom-Json } ## Set the Collected field specs to the Inventory data $Inventory.FieldSpecs = $FieldSpecs # 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 = @{ packageFormatVersion = 3 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 = '' } ) extensions = @( @{ name = '<PLACEHOLDER>' version = '1.0.0' } ) } } inventory = $Inventory } # Create the file $Package | ConvertTo-Json -Depth 5 | Set-Content -Path (Join-Path $Path 'package.json') -Force # Send the package back if Passthru was specified if ($Passthru) { $Package } } function New-TMExtensionInventory { <# .SYNOPSIS Creates a new inventory.json file with all of the configuration assets it contains .DESCRIPTION This function will collect all of the TM Server/Project configuration artifiacts to an inventory.json file .PARAMETER TMSession The TM Session to use .PARAMETER Path Output folder or file path for the inventory file. Include '.json' in the file name to create that file, otherwise the path is used as a Directory. .PARAMETER Passthru Return the TMConfigInventory File .EXAMPLE New-TMConfigInventory -TMProfile Default -Path 'C:\Temp\testfile.json' .OUTPUTS A [PSCustomObject] if -Passthru is specified, otherwise nothing #> param( [Parameter(Mandatory = $false, Position = 0)] [String]$TMSession = 'Default', [Parameter(Mandatory = $false)] [String]$InventoryPath, [Parameter(Mandatory = $false)] [String]$Server = $global:TMSessions[$TMSession].TMServer, [Parameter(Mandatory = $false, Position = 1)] [String]$OutPath, # ( <# ## Root the folder on the User's Reference Design folder $UserPaths.referencedesigns ## Add a directory for the Server $Server ## Add a Folder for the TM Project $global:TMSessions[$TMSession].UserContext.Project.Name ## Name the file 'Inventory.json' #> # Join-Path $UserPaths.referencedesigns $Server $global:TMSessions[$TMSession].UserContext.Project.Name 'Inventory.json' # ), [Parameter(Mandatory = $false)]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL, [Parameter(Mandatory = $false)] [Switch]$Passthru ) ## Test the optional input file path if ($OutPath) { ## Test if a Json file was named if ($OutPath -like '*.json') { ## Parse the path $ParentDirectory = Split-Path -Path $OutPath -Parent Test-FolderPath -FolderPath $ParentDirectory } elseif (-not ([String]::IsNullOrWhiteSpace($OutPath))) { Test-FolderPath -FolderPath $OutPath $OutPath += (Join-Path $OutPath 'Inventory.json').FullName } } ## Get Session Configuration $TMSessionConfig = $global:TMSessions[$TMSession] if (-not $TMSessionConfig) { Write-Host 'TMSession: [' -NoNewline Write-Host $TMSession -ForegroundColor Cyan Write-Host '] was not Found. Please use the New-TMSession command.' Throw 'TM Session Not Found. Use New-TMSession command before using features.' } ## ## Collect the Inventory ## ## Read the inventory path if there is one provided if (Test-Path $InventoryPath -ErrorAction SilentlyContinue) { try { $Data = Get-Content $InventoryPath | ConvertFrom-Json -ErrorAction 'SilentlyContinue' } catch { Write-Host 'The Inventory file could not be converted from json. Performing new Inventory using the TM Server settings supplied' -ForegroundColor Yellow } } ## Allow reading from a server connection, if no Inventory was provided if (-Not $Data) { ## Create the inventory Package $Data = @{ Inventory = @{ ## Add Useful Data TMServer = $TMServer TMProject = $TMProject DateCollected = (Get-Date).ToLongDateString() actions = [System.Collections.ArrayList]::new() dataScripts = [System.Collections.ArrayList]::new() recipes = [System.Collections.ArrayList]::new() bundles = [System.Collections.ArrayList]::new() providers = [System.Collections.ArrayList]::new() dependencyTypes = [System.Collections.ArrayList]::new() fieldSpecs = @{} Artifacts = [System.Collections.ArrayList]::new() ArtifactLinks = [System.Collections.ArrayList]::new() } Relationships = @{ Providers = @{} Actions = @{} DataScripts = @{} Recipes = @{} Bundles = @{} Fields = @{} DependencyTypes = @{} } } ## Add a 'TM Server' [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmserver-$TMServer" Type = 'TMServer' Name = $TMServer } ) ## Add a 'TM Project' [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmproject-$TMProject" Type = 'TMProject' Name = $TMProject ParentId = $TMServer } ) ## Collect FieldSpecs $Inventory.fieldSpecs = Get-TMFieldSpecs -TMSession $TMSession ## Add a 'FieldSpecs' object to the Artifact inventory (for proper diagram structuring) [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = 'tmfieldspecs' Type = 'FieldSpecs' Name = 'FieldSpecs' ParentId = "tmproject-$TMProject" } ) ## Process each Domain Class foreach ($DomainClass in $Inventory.fieldSpecs.PSobject.Properties.Name) { ## Add an Artifact to the inventory [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmfieldspecdomain-$DomainClass" Type = 'FieldSpecDomain' Name = $DomainClass ParentId = 'tmfieldspecs' } ) ## Handle Each Domain Class field $ClassFields = $Inventory.fieldSpecs.$DomainClass.fields foreach ($Field in $ClassFields) { ## Add a property for this Field in the Relationships data $Data.Relationships.Fields.("$($DomainClass)-$($Field.label)") = @{ Actions = [System.Collections.ArrayList]@() DataScripts = [System.Collections.ArrayList]@() Recipes = [System.Collections.ArrayList]@() RecipeGroups = [System.Collections.ArrayList]@() RecipeTaskSpecs = [System.Collections.ArrayList]@() } ## Add a AssetClass for each domain to the inventory [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmassetfield-$($DomainClass)-$($Field.label)" Type = 'AssetField' Name = $Field.label Constraints = $Field.constraints DomainClass = $DomainClass ParentId = "tmfieldspecdomain-$DomainClass" } ) } } ## Collect Actions $Inventory.actions = Get-TMAction -Passthru -TMSession $TMSession foreach ($Item in $Inventory.actions) { ## Add a property for this Field in the Relationships data $Data.Relationships.Actions.($Item.name) = @{ Fields = [System.Collections.ArrayList]@() Recipes = [System.Collections.ArrayList]@() RecipeTaskSpecs = [System.Collections.ArrayList]@() } ## Add an Artifact to the inventory [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmaction-$($Item.id)" Type = 'Action' Name = $Item.name ParentId = "tmprovider-$($item.provider.id)" } ) } ## Collect Data Scripts $Inventory.dataScripts = Get-TMDatascript -Passthru -TMSession $TMSession foreach ($Item in $Inventory.dataScripts) { ## Add a property for this Field in the Relationships data $Data.Relationships.DataScripts.($Item.name) = @{ Fields = [System.Collections.ArrayList]@() } ## Add an Artifact to the inventory [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmdatascript-$($Item.id)" Type = 'DataScript' Name = $Item.name ParentId = "tmprovider-$($item.provider.id)" } ) } ## Collect Recipes $Inventory.recipes = Get-TMRecipe -AsJson -Passthru -TMSession $TMSession foreach ($Item in $Inventory.recipes) { ## Add a property for this Field in the Relationships data $Data.Relationships.Recipes.($Item.name) = @{ Actions = [System.Collections.ArrayList]@() RecipeGroups = [System.Collections.ArrayList]@() RecipeTaskSpecs = [System.Collections.ArrayList]@() Fields = [System.Collections.ArrayList]@() } ## Add an Artifact to the inventory [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmrecipe-$($Item.id)" Type = 'Recipe' Name = $Item.name ParentId = "tmprovider-$($item.provider.id)" } ) } ## Collect Bundles $Inventory.bundles = Get-TMBundle -TMSession $TMSession foreach ($Item in $Inventory.bundles) { ## Add an Artifact to the inventory [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmbundle-$($Item.id)" Type = 'Bundle' Name = $Item.name ParentId = "tmproject-$TMProject" } ) } ## Collect Providers $Inventory.providers = Get-TMProvider -TMSession $TMSession foreach ($Item in $Inventory.providers) { ## Add an Artifact to the inventory [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmprovider-$($Item.id)" Type = 'Provider' Name = $Item.name ParentId = "tmproject-$TMProject" } ) ## Add a Provider-Actions Artifact [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmprovider-actions-$($Item.id)" Type = 'ProviderActions' Name = $Item.name + ' Actions' ParentId = "tmprovider-$($Item.id)" } ) ## Add a Provider-Actions Artifact [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmprovider-datascripts-$($Item.id)" Type = 'ProviderActions' Name = $Item.name + ' Data Scripts' ParentId = "tmprovider-$($Item.id)" } ) } ## Collect Dependency Types $Inventory.dependencyTypes = Get-TMDependencyType -TMSession $TMSession foreach ($Item in $Inventory.dependencyTypes) { ## Add an Artifact to the inventory [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmdependencytype-$($Item.label)" Type = 'DependencyType' Name = $Item.label ParentId = "tmserver-$TMServer" } ) } ## ## Save the Inventory data for convenience ## if (-Not ([String]::IsNullOrWhiteSpace($InventoryPath))) { $Data | ConvertTo-Json -Depth 100 -Compress | Set-Content -Path $InventoryPath -Force } } ## ## Calculate the Artifact Relationships ## ## ACTION(parameter) > Asset Field foreach ($Action in $Inventory.actions) { if ($Action.MethodParams) { ## Iterater on each Method Parameter foreach ($Param in $Action.methodParams) { ## only map params that use a field label if ($Param.context -ne 'USER_DEF' -and $Param.fieldLabel) { ## Record the connection in the hashmap [void]($Data.Relationships.Actions.($Action.Name).Fields.Add("$($Param.context)-$($Param.fieldLabel)")) ## Add the Artifact Link [void]$Inventory.ArtifactLinks.Add([PSCustomObject]@{ Id = "tmaction-$($Action.Id)-tmassetfield-$($Param.context)-$($Param.fieldLabel)" From = "tmaction-$($Action.Id)" To = "tmassetfield-$($Param.context)-$($Param.fieldLabel)" type = 'TM Server hosts Project' } ) } } } } ## Recipe > Actions, Groups, TaskSpecs foreach ($Recipe in $Inventory.recipes) { ## Create a Groups object [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmrecipe-$($Recipe.id)-groups" Type = 'RecipeGroup' Name = $Recipe.name + ' Groups' ParentId = "tmrecipe-$($Recipe.id)" } ) ## Create a TaskSpecs object [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmrecipe-$($Recipe.id)-taskspecs" Type = 'RecipeGroup' Name = $Recipe.name + ' Groups' ParentId = "tmrecipe-$($Recipe.id)" } ) ## Check each Group in the recipe foreach ($Group in $Recipe.sourceCodeJson.groups) { ## Record the connection in the hashmap [void]($Data.Relationships.Recipes.($Recipe.Name).RecipeGroups.Add($Group.name)) ## Record the Group [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmrecipegroup-$($Recipe.id)-$($group.name)" Type = 'RecipeGroup' Name = $group.name ParentId = "tmrecipe-$($Recipe.id)-groups" } ) ## TODO - Add Group Asset Field resolution so the groups can be bound to the Asset-Fields } ## Check each Task Spec in the recipe foreach ($TaskSpec in $Recipe.sourceCodeJson.tasks) { ## Record the connection in the hashmap [void]($Data.Relationships.Recipes.($Recipe.Name).RecipeTaskSpecs.Add($TaskSpec.title)) ## Record the Task Spec [void]$Inventory.Artifacts.Add([PSCustomObject]@{ Id = "tmtaskspec-$($Recipe.id)-$($TaskSpec.id)" Type = 'RecipeTaskSpec' Name = $TaskSpec.title ParentId = "tmrecipe-$($Recipe.id)-taskspecs" } ) ## Record Tasks that have a method assigned if ($TaskSpec.invoke.method) { ## Record the connection in the hashmap [void]($Data.Relationships.Recipes.($Recipe.Name).Actions.Add($TaskSpec.invoke.method)) [void]$Inventory.ArtifactLinks.Add([PSCustomObject]@{ Id = "tmaction-$($Action.Id)-tmtaskspec-$($Recipe.id)-$($TaskSpec.id)" From = "tmaction-$($Action.Id)" To = "tmtaskspec-$($Recipe.id)-$($TaskSpec.id)" type = 'TM Server hosts Project' } ) } ## Record Tasks that have a method assigned if ($TaskSpec.filter.group) { foreach ($Group in $TaskSpec.filter.group) { ## Record the connection in the hashmap [void]($Data.Relationships.Recipes.($Recipe.Name).RecipeGroups.Add($Group.name)) [void]$Inventory.ArtifactLinks.Add([PSCustomObject]@{ Id = "tmaction-$($Action.Id)-tmtaskspec-$($Recipe.id)-$($TaskSpec.id)" From = "tmaction-$($Action.Id)" To = "tmrecipegroup-$($Recipe.id)-$($group.name)" type = 'TM Server hosts Project' } ) } } } } ## Datascripts to ## > Asset Field # Send the package back if Passthru was specified if ($Passthru) { $Data } # ## Create a flat inventory of Artifacts # # Send the package back if Passthru was specified # if ($Passthru) { # $Inventory # } } function Read-TMExtensionInventory { <# .SYNOPSIS Creates a new inventory.json file with all of the configuration assets it contains, as read from an Extension directory .DESCRIPTION This function will read all of the TM Entities within an Extension directory .PARAMETER Path Path of the TM Extension to read .PARAMETER OutPath Filename to create containing the inventory .PARAMETER Passthru Return the TMConfigInventory File .EXAMPLE Read-TMConfigInventory -Path 'C:\Users\tbaker\TMD_Files\Reference Designs\EXTENSIONS\VMware\vCenter' -OutPath 'C:\Temp\testfile.json' -Passthru .OUTPUTS A [PSCustomObject] if -Passthru is specified, otherwise nothing #> param( [Parameter(Mandatory = $true, Position = 1)] [String]$Path, [Parameter(Mandatory = $false, Position = 1)] [String]$OutPath, [Parameter(Mandatory = $false)] [Switch]$Passthru ) ## Create the inventory Package $Inventory = @{ Actions = [System.Collections.ArrayList]::new() DataScripts = [System.Collections.ArrayList]::new() Recipes = [System.Collections.ArrayList]::new() Bundles = [System.Collections.ArrayList]::new() Providers = [System.Collections.ArrayList]::new() DependencyTypes = [System.Collections.ArrayList]::new() FieldSpecs = @{} Artifacts = [System.Collections.ArrayList]::new() ArtifactLinks = [System.Collections.ArrayList]::new() Relationships = @{ Providers = @{} Actions = @{} DataScripts = @{} Recipes = @{} Bundles = @{} Fields = @{} DependencyTypes = @{} } } ## Lookup the local package file to get the name and $PackageFilePath = "$Path\package.json" if (Test-Path $PackageFilePath -ErrorAction SilentlyContinue) { $PackageData = Get-Content -Path $PackageFilePath | ConvertFrom-Json $Inventory.Extension = @{ Name = $PackageData.name ProductName = $PackageData.productName } } ## Update the path to the internal _REFERENCE DESIGN folder if ($Path -notlike '*_REFERENCE DESIGN') { $Path = "$Path\_REFERENCE DESIGN" } ## ## Collect the Inventory ## # ## Collect JSON File data $ErrorActionPreference = 'SilentlyContinue' $Inventory.AppTypes = Get-Content (Join-Path $Path 'AppTypes.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue' $Inventory.Bundles = Get-Content (Join-Path $Path 'Bundles.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue' $Inventory.DeviceTypes = Get-Content (Join-Path $Path 'DeviceTypes.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue' $Inventory.Providers = Get-Content (Join-Path $Path 'Providers.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue' $Inventory.Tags = Get-Content (Join-Path $Path 'Tags.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue' $Inventory.TaskCategories = Get-Content (Join-Path $Path 'TaskCategories.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue' $Inventory.Teams = Get-Content (Join-Path $Path 'Teams.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue' $Inventory.FieldSpecs = Get-Content (Join-Path $Path 'FieldSpecs.json') | ConvertFrom-Json -ErrorAction 'SilentlyContinue' $ErrorActionPreference = 'Continue' ## Create Relationship properties for each of the fields # foreach ($DomainClass in $Inventory.FieldSpecs.PSobject.Properties.Name) { # ## Handle Each Domain Class field # $ClassFields = $Inventory.fieldSpecs.$DomainClass.fields # foreach ($Field in $ClassFields) { # ## Add a property for this Field in the Relationships data # $Inventory.Relationships.Fields.("$($DomainClass)-$($Field.label)") = @{ # Actions = [System.Collections.ArrayList]@() # DataScripts = [System.Collections.ArrayList]@() # Recipes = [System.Collections.ArrayList]@() # RecipeGroups = [System.Collections.ArrayList]@() # RecipeTaskSpecs = [System.Collections.ArrayList]@() # } # } # } ## Create Relationship properties for each of the Providers # foreach ($Provider in $Inventory.Providers) { # $Inventory.Relationships.Providers.($Provider.Name) = @{ # Actions = [System.Collections.ArrayList]@() # DataScripts = [System.Collections.ArrayList]@() # } # } ## Collect Actions $ActionFiles = Get-ChildItem -Path "$Path\Actions" -Recurse -Filter '*.ps1' -ErrorAction SilentlyContinue foreach ($Item in $ActionFiles) { $Action = Read-TMActionScriptFile -Path $Item.FullName ## Add an Artifact to the inventory [void]$Inventory.Actions.Add($Action) ## Add a property for this Field in the Relationships data # $Inventory.Relationships.Actions.($Action.name) = @{ # Provider = $Action.provider.name # Fields = [System.Collections.ArrayList]@() # Recipes = [System.Collections.ArrayList]@() # RecipeTaskSpecs = [System.Collections.ArrayList]@() # } # ## Add this Action to the Provider's list as well # [void]($Inventory.Relationships.Providers.($Action.provider.name).Actions.Add($Action.Name)) } ## Collect Data Scripts $DataScriptFiles = Get-ChildItem -Path "$Path\DataScripts" -Recurse -Filter '*.groovy' -ErrorAction SilentlyContinue foreach ($Item in $DataScriptFiles) { $DataScript = Read-TMDatascriptFile -Path $Item.FullName ## Add an Artifact to the inventory [void]$Inventory.DataScripts.Add($DataScript) ## Add a property for this Field in the Relationships data # $Inventory.Relationships.DataScripts.($DataScript.name) = @{ # Provider = $ActioDatascript.provider.name # Fields = [System.Collections.ArrayList]@() # } # ## Add this Datascript to the Provider's list as well # [void]($Inventory.Relationships.Providers.($DataScript.provider.name).Datascripts.Add($DataScript.Name)) } ## Create a flat inventory of Artifacts # Send the package back if Passthru was specified if (-not ([String]::IsNullOrWhiteSpace($OutPath))) { $Inventory | ConvertTo-Json -Depth 100 | Set-Content -Path $OutPath -Force } # Send the package back if Passthru was specified if ($Passthru) { $Inventory } } |