lib/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)][String]$TMSession = 'Default',
        [Parameter(Mandatory = $false)][String]$Server = $global:TMSessions[$TMSession].TMServer,
        [Parameter(Mandatory = $false)]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL,
        [Parameter(Mandatory = $false)][Switch]$ResetIDs,
        [Parameter(Mandatory = $false)][Switch]$CustomOnly,
        [Parameter(Mandatory = $false)][Switch]$Label
    )
    ## 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.'
    }

    #Honor SSL Settings
    if ($TMSessionConfig.AllowInsecureSSL) {
        $TMCertSettings = @{SkipCertificateCheck = $true }
    } else {
        $TMCertSettings = @{SkipCertificateCheck = $false }
    }

    $instance = $Server.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 $TMSessionConfig.TMWebSession @TMCertSettings
    } catch {
        return $_
    }

    if ($response.StatusCode -eq 200) {
        $Result = ($response.Content | ConvertFrom-Json)
    } else {
        return 'Unable to collect Field Settings.'
    }

    if ($CustomOnly) {
        foreach ($AssetClass in $Result.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 $Result.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 $Result
}
Function Update-TMFieldSpecs {
    param(
        [Parameter(Mandatory = $false)][String]$TMSession = 'Default',
        [Parameter(Mandatory = $false)][String]$Server = $global:TMSessions[$TMSession].TMServer,
        [Parameter(Mandatory = $false)]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL,
        [Parameter(Mandatory = $true)][PSObject]$FieldSpecs
    )

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

    #Honor SSL Settings
    if ($TMSessionConfig.AllowInsecureSSL) {
        $TMCertSettings = @{SkipCertificateCheck = $true }
    } else {
        $TMCertSettings = @{SkipCertificateCheck = $false }
    }

    $TMVersion = [Version]::parse($TMSessionConfig.TMVersion)

    ## Define the domain classes
    $DomainClasses = @('APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE')

    # Get the Existing Field Spec
    $ServerFields = Get-TMFieldSpecs -TMSession $TMSession

    # 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

                ## 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')) {

                    if ($TMVersion.Major -eq 6 -and $TMVersion -ge '6.1.0') {
                        ## Check to see if it's in the list
                        ## For Each field that already exists, get the list of constraints
                        $Field.constraint.values | ForEach-Object {
                            ## If the ReturnField Constraints doesn't have the value, add it
                            if ($ReturnField.constraint.values -notcontains $_) {
                                $ReturnField.constraint.values += $_
                            }
                        }
                    } else {
                        ## For Each field that already exists, get the list of constraint
                        $Field.constraints.values | ForEach-Object {

                            ## If the ReturnField Constraint doesn't have the value, add it
                            if ($ReturnField.constraints.values -notcontains $_) {
                                $ReturnField.constraints.values += $_
                            }
                        }
                    }
                }

                ## Remove the Added field from the Incoming Field Spec object so it doesn't get added again
                $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."
        }
    }

    ## Build the URL to post back to
    $uri = 'https://'
    $uri += $Server
    $uri += '/tdstm/ws/customDomain/fieldSpec/ASSETS'

    ## Create the Field Settings body, differs for TM separate TM versions
    $TMVersion = [Version]::parse($TMSessionConfig.TMVersion)
    if ((($TMVersion.Major -eq 5) -and ($TMVersion -ge '5.0.4')) -or
        (($TMVersion.Major -eq 6 -and $TMVersion -ge '6.0.0.2'))) {

        ## Build an API post body with the Project ID
        $PostBody = [PSCustomObject]@{
            fieldSettings = $UpdateFields
            projectId     = $TMSessions[$TMsession].UserContext.project.id
        } | ConvertTo-Json -Depth 100 -Compress
    } else {

        $PostBody = $UpdateFields | ConvertTo-Json -Depth 100 -Compress
    }

    Set-TMHeaderContentType -ContentType 'JSON' -TMSession $TMSession

    try {
        $response = Invoke-WebRequest -Method Post -Uri $uri -WebSession $TMSessionConfig.TMWebSession -Body $PostBody @TMCertSettings
        if ($response.StatusCode -eq 200) {
            $ResponseJson = $response.Content | ConvertFrom-Json -Depth 100

            if ($ResponseJson.status -eq 'success') {
                # Write-Host 'Field Specs been updated.'
                return
            } else {
                throw $ResponseJson.errors
            }
        } elseif ($response.StatusCode -eq 204) {
            return
        } else { throw $_ }
    } catch {
        return $_
    }

}
Function Add-TMFieldListValues {
    param(
        [Parameter(Mandatory = $false)][String]$TMSession = 'Default',
        [Parameter(Mandatory = $false)][String]$Server = $global:TMSessions[$TMSession].TMServer,
        [Parameter(Mandatory = $false)]$AllowInsecureSSL = $global:TMSessions[$TMSession].AllowInsecureSSL,
        [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
    )

    if ($Values.Count -eq 0) {
        Write-Verbose 'There were no updates to make to the field'
        return
    }

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

    #Honor SSL Settings
    if ($TMSessionConfig.AllowInsecureSSL) {
        $TMCertSettings = @{SkipCertificateCheck = $true }
    } else {
        $TMCertSettings = @{SkipCertificateCheck = $false }
    }

    $TMVersion = [Version]::parse($TMSessionConfig.TMVersion)

    ## Define the domain classes
    $DomainClasses = @{
        Application = 'APPLICATION'
        Device      = 'DEVICE'
        Database    = 'DATABASE'
        Storage     = 'STORAGE'
    }

    # Get the Existing Field Spec
    $FieldSpecs = Get-TMFieldSpecs -TMSession $TMSession

    ## 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
    }

    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 = 'constraints'
        if ($TMVersion.Major -eq 6 -and $TMVersion -ge '6.1.0') {
            $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://$($Server)/tdstm/ws/customDomain/fieldSpec/ASSETS"
    Set-TMHeaderContentType -ContentType 'JSON' -TMSession $TMSession

    ## Create the Field Settings body, differs for TM separate TM versions
    $TMVersion = [Version]::parse($TMSessionConfig.TMVersion)
    if ((($TMVersion.Major -eq 5) -and ($TMVersion -ge '5.0.4')) -or
            (($TMVersion.Major -eq 6 -and $TMVersion -ge '6.0.0.2'))) {

        ## Build an API post body with the Project ID
        $PostBody = [PSCustomObject]@{
            fieldSettings = $FieldSpecs
            projectId     = $TMSessions[$TMsession].UserContext.project.id
        } | ConvertTo-Json -Depth 100 -Compress
    } else {

        $PostBody = $FieldSpecs | ConvertTo-Json -Depth 100 -Compress
    }

    try {
        $response = Invoke-WebRequest -Method Post -Uri $uri -WebSession $TMSessionConfig.TMWebSession -Body $PostBody @TMCertSettings
        if ($response.StatusCode -eq 200) {
            $ResponseJson = $response.Content | ConvertFrom-Json -Depth 100

            if ($ResponseJson.status -ne 'success') {
                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)]
        [String]$TMSession = 'Default',

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

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

        [Parameter(Mandatory = $false)]
        [ValidateSet('APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE')]
        [String]$Domain
    )

    $AllFields = Get-TMFieldSpecs -TMSession $TMSession -Server $Server -AllowInsecureSSL $AllowInsecureSSL
    $FieldLabelMap = @{}

    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)
        }
    }

    if ($Domain) {
        $FieldLabelMap.$Domain
    } else {
        $FieldLabelMap
    }
}


function Get-TMLabelToFieldMap {
    param(
        [Parameter(Mandatory = $false)]
        [String]$TMSession = 'Default',

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

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

        [Parameter(Mandatory = $false)]
        [ValidateSet('APPLICATION', 'DEVICE', 'DATABASE', 'STORAGE')]
        [String]$Domain
    )

    $AllFields = Get-TMFieldSpecs -TMSession $TMSession -Server $Server -AllowInsecureSSL $AllowInsecureSSL
    $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 ($Domain) {
        $LabelFieldMap.$Domain
    } else {
        $LabelFieldMap
    }
}