Public/FieldSettings.ps1
## TM Field Settings Function Get-TMNextSharedColumn { param( [Parameter(Mandatory = $false, ValueFromPipeline = $true)][PSObject]$FieldSpecs ) ## Get furthest Custom field number number in each for ($i = 1; $i -ne 100; $i++) { if ( (-Not ($FieldSpecs.APPLICATION.fields | Where-Object { $_.field -eq 'custom' + $i })) ` -and (-Not ($FieldSpecs.DEVICE.fields | Where-Object { $_.field -eq 'custom' + $i })) ` -and (-Not ($FieldSpecs.DATABASE.fields | Where-Object { $_.field -eq 'custom' + $i })) ` -and (-Not ($FieldSpecs.STORAGE.fields | Where-Object { $_.field -eq 'custom' + $i })) ) { return 'custom' + $i } } } Function Get-TMNextCustomColumn { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)][PSObject]$ClassFields ) for ($i = 1; $i -ne 100; $i++) { if (-Not ($ClassFields.fields | Where-Object { $_.field -eq 'custom' + $i })) { return 'custom' + $i } } } Function Get-TMFieldSpecs { param( [Parameter(Mandatory = $false)][PSObject]$TMSession = 'Default', [Parameter(Mandatory = $false)][Switch]$ResetIDs, [Parameter(Mandatory = $false)][Switch]$CustomOnly, [Parameter(Mandatory = $false)][Switch]$Label, [Parameter(Mandatory = $false)][Switch]$SkipCache ) ## Get Session Configuration $TMSession = Get-TMSession $TMSession if (-Not $SkipCache) { $FieldSpecs = $TMSession.DataCache.FieldSpecs } if (-Not ($FieldSpecs.PSObject.Properties.Name -contains 'APPLICATION') -or $SkipCache) { #Honor SSL Settings $TMCertSettings = @{SkipCertificateCheck = $TMSession.AllowInsecureSSL } $Instance = $TMSession.TMServer.Replace('/tdstm', '') $instance = $instance.Replace('https://', '') $instance = $instance.Replace('http://', '') $uri = 'https://' $uri += $instance $uri += '/tdstm/ws/customDomain/fieldSpec/ASSETS' try { $response = Invoke-WebRequest -Method Get -Uri $uri -WebSession $TMSession.TMWebSession @TMCertSettings } catch { return $_ } if ($response.StatusCode -eq 200) { $FieldSpecs = ($response.Content | ConvertFrom-Json) if (-Not $SkipCache) { $TMSession.DataCache.FieldSpecs = ($response.Content | ConvertFrom-Json) } } else { return 'Unable to collect Field Settings.' } } if ($CustomOnly) { foreach ($AssetClass in $FieldSpecs.PSObject.Properties.Value) { $AssetClass.fields = $AssetClass.fields | Where-Object { $_.udf -ne '0' } } } if ($ResetIDs) { ## Read each domain field to reset the IDs foreach ($AssetClass in $FieldSpecs.PSObject.Properties.Value) { ## Sort the fields by label and collect a new ordered list of the fields $AssetClass.fields = $AssetClass.fields | Sort-Object -Property 'label' ## Iterate over all of the fields for ($i = 0; $i -lt $AssetClass.fields.Count; $i++) { ## If the Field is a custom field. if ($AssetClass.fields[$i].field -like 'custom*') { ## Change the custom field number to be set on import $AssetClass.fields[$i].field = 'customN' } ## If the Field Control is a JSON, sets the JSONFieldID to Null. if ($AssetClass.fields[$i].control -eq 'JSON') { $AssetClass.fields[$i].jsonFieldId = $null } } } } return $FieldSpecs } Function Update-TMFieldSpecs { param( [Parameter(Mandatory = $false)][PSObject]$TMSession = 'Default', [Parameter(Mandatory = $true)][PSObject]$FieldSpecs, [Parameter(Mandatory = $false)][Switch]$SkipCache ) ## Get Session Configuration $TMSession = Get-TMSession $TMSession ## Clear related caches that will need to be updated $TMSession.DataCache.FieldSpecsLabelToFieldMap = $Null $TMSession.DataCache.FieldSpecsFieldToLabelMap = $Null ## Define the domain classes $DomainClasses = @('APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE') # Get the Existing Field Spec $ServerFields = Get-TMFieldSpecs -TMSession $TMSession -SkipCache # workaround for a bug not present since 6.4.2 if ( $TMSession.TMVersion -lt [version] '6.4.2' ) { $ValuesToAdd = @('', 'Yes', 'No') foreach ($Domain in $DomainClasses) { $FieldsToUpdate = $ServerFields.$Domain.fields | Where-Object control -EQ 'YesNo' foreach ($field in $FieldsToUpdate) { try { $field.constraint | Add-Member -MemberType NoteProperty -Name 'values' -Value $ValuesToAdd -ErrorAction Stop } catch { foreach ($ValueToAdd in $ValuesToAdd) { try { $field.constraint.values += $ValueToAdd } catch { Write-Debug "Field $($field.field) already had value '$ValueToAdd'" } } } } } } # Create the Updated FieldSpec Object and clear the fields $UpdateFields = $ServerFields | ConvertTo-Json -Depth 100 | ConvertFrom-Json -Depth 100 foreach ($DomainClass in $DomainClasses) { $UpdateFields.$DomainClass.fields = @() } # Collect the existing Asset Class Fields, updating them if they exist foreach ($DomainClass in $DomainClasses) { $ServerClassFields = $ServerFields.$DomainClass.fields $NewFields = $FieldSpecs.$DomainClass.fields foreach ($Field in $ServerClassFields) { ## Look for an existing field name $MatchingNewField = $NewFields | Where-Object { $_.label -eq $Field.label } if ($MatchingNewField) { ## Get the UPDATED Field $ReturnField = $MatchingNewField | ConvertTo-Json -Depth 100 -Compress | ConvertFrom-Json if ($ReturnField.Count -gt 1) { Throw "Duplicate Field Detected - $($Field.label)" } ## Update with the original Field ID to keep the existing column $ReturnField.field = $Field.field ## Set the Constraints iist if ($ReturnField.control -in @('List', 'YesNo')) { ## Ensure that the incomming data has the right constraint(s) property Add-Member -InputObject $ReturnField -NotePropertyName 'constraint' -NotePropertyValue ($ReturnField.constraint ?? $ReturnField.constraints) -Force ## Check to see if it's in the list ## For Each field that already exists, get the list of constraints $Field.constraint.PSObject.Properties.Name | ForEach-Object { ## If the ReturnField Constraints doesn't have the value, add it if ($ReturnField.constraint.PSObject.Properties.Name -notcontains $_) { $ReturnField.constraint.values += $_ } } ## Remove the Added field from the Incoming Field Spec object so it doesn't get added again $NewFields.PSObject.Properties.Remove($MatchingNewField) } #### TODO: This is now no longe working against 61. This line has been moved iunto ## Remove the Added field from the Incoming Field Spec object so it doesn't get added again # $NewFields.PSObject.Properties.Remove($MatchingNewField) # $NewFields.PSObject.Properties.Remove($MatchingNewField) } else { ## There is no matching field, return the original Field $ReturnField = $Field } $UpdateFields.$DomainClass.fields += $ReturnField } } # Add Shared Fields ## This is only done on one asset class (0, which exists every time). Since shared fields go to all asset classes. $NewSharedFields = $FieldSpecs.$DomainClass[0].fields | Where-Object { $_.shared -eq 1 } foreach ($NewSharedField in $NewSharedFields) { ## Don't allow duplicates by label if ( ($UpdateFields.APPLICATION.fields | Where-Object { $_.label -eq $NewSharedField.label }) ` -or ($UpdateFields.DEVICE.fields | Where-Object { $_.label -eq $NewSharedField.label }) ` -or ($UpdateFields.DATABASE.fields | Where-Object { $_.label -eq $NewSharedField.label }) ` -or ($UpdateFields.STORAGE.fields | Where-Object { $_.label -eq $NewSharedField.label }) ` ) { ## Field Already Exists and has been updated # Write-Host "Shared Field Name:"$NewSharedField.label"is already in use." Continue } ## Field doesn't exist $ReturnField = $NewSharedField | ConvertTo-Json -Depth 100 -Compress | ConvertFrom-Json if ($ReturnField.field -eq 'customN') { $ReturnField.field = Get-TMNextSharedColumn $UpdateFields } $UpdateFields.APPLICATION.fields += $ReturnField $UpdateFields.DEVICE.fields += $ReturnField $UpdateFields.DATABASE.fields += $ReturnField $UpdateFields.STORAGE.fields += $ReturnField # Write-Host 'ADDING SHARED Field Name: ' $ReturnField.label } ## Add NON Shared fields, one for each Asset Class foreach ($DomainClass in $DomainClasses) { $NewNonSharedFields = $FieldSpecs.$DomainClass.fields | Where-Object { $_.shared -eq 0 } foreach ($Field in $NewNonSharedFields) { ## Don't allow duplicates by label if ($UpdateFields.$DomainClass.fields | Where-Object { $_.label -eq $Field.label }) { ## Field Already Exists and has been updated # Write-Host $DomainClass 'Field Name: ' $Field.label ' is already in use.' Continue } ## Field doesn't exist $ReturnField = $Field if ($ReturnField.field -eq 'customN') { $ReturnField.field = Get-TMNextCustomColumn $UpdateFields.$DomainClass } $UpdateFields.$DomainClass.fields += $ReturnField # Write-Host 'ADDING '$DomainClass 'Field Name: ' $Field.label } } ## Validate the number of fields being created foreach ($DomainClass in $DomainClasses) { ## Count the number of Custom Fields $CustomFieldCount = ($UpdateFields.$DomainClass.fields | Where-Object { $_.field -like 'custom*' }).Count ## Disallow an upload if there are more than 96 custom fields if ($CustomFieldCount -gt 96) { Throw "Adding these fields would result in a total of $CustomFieldCount fields in the $DomainClass class." } ## Adjust every domain field spec to the correct format ## Ensure that the incomming data has the right constraint(s) property $UpdateFields.$DomainClass.fields | ForEach-Object { Add-Member -InputObject $_ -NotePropertyName 'constraint' -NotePropertyValue ($_.constraint ?? $_.constraints) -Force if ($_.PSObject.Properties.Name.Contains('constraints')) { $_.PSObject.Properties.Remove('constraints') } Add-Member -InputObject $_ -NotePropertyName 'defaultValue' -NotePropertyValue ($_.defaultValue ?? $_.default) -Force if ($_.PSObject.Properties.Name.Contains('default')) { $_.PSObject.Properties.Remove('default') } } } ## Build the URL to post back to $uri = 'https://' $uri += $TMSession.TMServer $uri += '/tdstm/ws/customDomain/fieldSpec/ASSETS' # Capitalize the domain $DomainClasses | ForEach-Object { $UpdateFields.$_.domain = $_ } ## Build an API post body with the Project ID $PostBody = [PSCustomObject]@{ fieldSettings = $UpdateFields projectId = $TMSession.UserContext.project.id } | ConvertTo-Json -Depth 100 -Compress $UpdateFieldsWebRequest = @{ Uri = $uri WebSession = $TMSession.TMWebSession Body = $PostBody SkipCertificateCheck = $TMSession.AllowInsecureSSL Method = 'Post' } Set-TMHeaderContentType -ContentType 'JSON' -TMSession $TMSession try { $response = Invoke-WebRequest @UpdateFieldsWebRequest if ($response.StatusCode -eq 200) { $ResponseJson = $response.Content | ConvertFrom-Json -Depth 100 if ($ResponseJson.status -eq 'success') { if (-Not $SkipCache) { $TMSession.DataCache.FieldSpecs = $UpdateFields } return } else { throw $ResponseJson.errors } } elseif ($response.StatusCode -eq 204) { return } else { throw $_ } } catch { return $_ } } Function Set-TMFieldSpecs { param( [Parameter(Mandatory = $false)][PSObject]$TMSession = 'Default', [Parameter(Mandatory = $true)][PSObject]$FieldSpecs, [Switch]$Force, [Switch]$SkipCache ) ## This command is potentially destructive if (-Not $Force) { Write-Host 'This command is used to overwrite an existing field spec object and should be used with care.' Write-Host 'The counterpart command ' -NoNewline Write-Host 'Update-TMFieldSpecs ' -NoNewline -ForegroundColor Yellow Write-Host 'might be more useful as it merges changes into an existing FieldSpec.' Write-Host 'To use this command to overwrite a FieldSpec, use of the -Force switch is required' throw 'You must use the force switch with this command' } ## Get Session Configuration $TMSession = Get-TMSession $TMSession ## Clear related caches that will need to be updated $TMSession.DataCache.FieldSpecsLabelToFieldMap = $Null $TMSession.DataCache.FieldSpecsFieldToLabelMap = $Null #Honor SSL Settings $TMCertSettings = @{SkipCertificateCheck = $TMSession.AllowInsecureSSL } ## Build the URL to post back to $uri = 'https://' $uri += $TMSession.TMServer $uri += '/tdstm/ws/customDomain/fieldSpec/ASSETS' ## Create the Field Settings body ## Build an API post body with the Project ID $PostBody = [PSCustomObject]@{ fieldSettings = $FieldSpecs projectId = $TMsession.UserContext.project.id } | ConvertTo-Json -Depth 100 -Compress Set-TMHeaderContentType -ContentType 'JSON' -TMSession $TMSession try { $response = Invoke-WebRequest -Method Post -Uri $uri -WebSession $TMSession.TMWebSession -Body $PostBody @TMCertSettings if ($response.StatusCode -eq 200) { $ResponseJson = $response.Content | ConvertFrom-Json -Depth 100 if ($ResponseJson.status -eq 'success') { if (-Not $SkipCache) { $TMSession.DataCache.FieldSpecs = $FieldSpecs } return } else { throw $ResponseJson.errors } } elseif ($response.StatusCode -eq 204) { return } else { throw $_ } } catch { return $_ } } Function Add-TMFieldListValues { param( [Parameter(Mandatory = $false)][PSObject]$TMSession = 'Default', [Parameter(Mandatory = $true)][ValidateSet('Application', 'Database', 'Device', 'Storage')][String]$Domain, [Parameter(Mandatory = $true)][String]$FieldLabel, [Parameter(Mandatory = $true)][Array]$Values, [Parameter(Mandatory = $false)][ValidateSet('Asc', 'Desc')] [String]$SortConstraintList, [Switch]$SkipCache ) if ($Values.Count -eq 0) { Write-Verbose 'There were no updates to make to the field' return } ## Get Session Configuration $TMSession = Get-TMSession $TMSession #Honor SSL Settings $TMCertSettings = @{SkipCertificateCheck = $TMSession.AllowInsecureSSL } ## Define the domain classes $DomainClasses = @{ Application = 'APPLICATION' Device = 'DEVICE' Database = 'DATABASE' Storage = 'STORAGE' } # Get the Existing Field Spec $FieldSpecs = Get-TMFieldSpecs -TMSession $TMSession -SkipCache ## Create the Updated FieldSpec Object and clear the fields $Field = $FieldSpecs.($DomainClasses[$Domain]).fields | Where-Object { $_.label -eq $FieldLabel } if (-Not $Field) { Write-Host "Field: $($FieldLabel) not found!" -ForegroundColor Red return } ## Only Add values to a List control if ($Field -and $Field.control -ne 'List') { Write-Verbose "Skipping update on $($Field.label) as it is a $($Field.control) type." return } foreach ($NewItem in $Values) { ## Version 6.0.2.1+ (and TODO version 6.1?, 5x?) ## Requires the class names to be lower case $CONSTRAINTS_PROPERTY_NAME = 'constraint' ## Convert each Asset Class name to lowercase ## Check to see if it's in the list if (-Not $Field.$CONSTRAINTS_PROPERTY_NAME.values.Contains($NewItem)) { $Field.$CONSTRAINTS_PROPERTY_NAME.values += $NewItem } ## Sort the list if required if ($SortConstraintList -eq 'Asc') { $Field.$CONSTRAINTS_PROPERTY_NAME.values = $Field.$CONSTRAINTS_PROPERTY_NAME.values | Sort-Object } if ($SortConstraintList -eq 'Desc') { $Field.$CONSTRAINTS_PROPERTY_NAME.values = $Field.$CONSTRAINTS_PROPERTY_NAME.values | Sort-Object -Descending } } ## Put the data back $FieldSpecs.($DomainClasses[$Domain]).fields | Where-Object { $_.label -eq $FieldLabel } | ForEach-Object { $_ = $Field } ## Construct an update call $uri = "https://$($TMSession.TMServer)/tdstm/ws/customDomain/fieldSpec/ASSETS" Set-TMHeaderContentType -ContentType 'JSON' -TMSession $TMSession ## Create the Field Settings body ## Build an API post body with the Project ID $PostBody = [PSCustomObject]@{ fieldSettings = $FieldSpecs projectId = $TMsession.UserContext.project.id } | ConvertTo-Json -Depth 100 -Compress try { $response = Invoke-WebRequest -Method Post -Uri $uri -WebSession $TMSession.TMWebSession -Body $PostBody @TMCertSettings if ($response.StatusCode -eq 200) { $ResponseJson = $response.Content | ConvertFrom-Json -Depth 100 if ($ResponseJson.status -eq 'success') { if (-Not $SkipCache) { $TMSession.DataCache.FieldSpecs = $FieldSpecs } } else { throw $ResponseJson.errors } } elseif ($response.StatusCode -eq 204) { return } else { ThrowError 'Unable to save Field Settings' } } catch { throw $_ } } function Get-TMFieldToLabelMap { param( [Parameter(Mandatory = $false)] [PSObject]$TMSession = 'Default', [Parameter(Mandatory = $false)] [ValidateSet('APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE')] [String]$Domain, [Switch]$SkipCache ) $TMSession = Get-TMSession $TMSession if (-Not $SkipCache) { $FieldLabelMap = $TMSession.DataCache.FieldSpecsFieldToLabelMap } if (-Not $FieldLabelMap.Values -or $SkipCache) { $AllFields = Get-TMFieldSpecs -TMSession $TMSession -SkipCache:$SkipCache $FieldLabelMap = @{ TemplateKeys = @{} } foreach ($DomainName in @('APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE')) { $FieldLabelMap.Add($DomainName, @{}) foreach ($Spec in $AllFields.$DomainName.fields) { # Add-Member -InputObject $FieldLabelMap.$DomainName -NotePropertyName $Spec.field -NotePropertyValue $Spec.label -Force Write-Verbose "Domain: $($DomainName), Field: $($Spec.field), Label: $($Spec.label)" $FieldLabelMap.$DomainName.Add($Spec.field, $Spec.label) $TemplateKey = "customN|$($DomainName)|$($Spec.label)|" Write-Verbose "Adding TemplateKey $TemplateKey with value $($Spec.field)" $FieldLabelMap.TemplateKeys.Add($TemplateKey, $Spec.field) } } } if (-Not $SkipCache) { $TMSession.DataCache.FieldSpecsFieldToLabelMap = $FieldLabelMap } if ($Domain) { $FieldLabelMap.$Domain } else { $FieldLabelMap } } function Get-TMLabelToFieldMap { param( [Parameter(Mandatory = $false)] [PSObject]$TMSession = 'Default', [Parameter(Mandatory = $false)] [ValidateSet('APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE')] [String]$Domain, [Switch]$SkipCache ) $TMSession = Get-TMSession $TMSession if (-Not $SkipCache) { $LabelFieldMap = $TMSession.DataCache.FieldSpecsLabelToFieldMap } if (-Not $LabelFieldMap.Values -or $SkipCache) { $AllFields = Get-TMFieldSpecs -TMSession $TMSession -SkipCache:$SkipCache $LabelFieldMap = @{} foreach ($DomainName in @('APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE')) { $LabelFieldMap.Add($DomainName, @{}) foreach ($Spec in $AllFields.$DomainName.fields) { $LabelFieldMap.$DomainName.Add($Spec.label, $Spec.field) } } } if (-Not $SkipCache) { $TMSession.DataCache.FieldSpecsLabelToFieldMap = $LabelFieldMap } if ($Domain) { $LabelFieldMap.$Domain } else { $LabelFieldMap } } |