Private/Association/Invoke-JCAssociation.ps1

Function Invoke-JCAssociation {
    [CmdletBinding(DefaultParameterSetName = 'ById')]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'The verb of the command calling it. Different verbs will make different parameters required.')][ValidateSet('add', 'get', 'new', 'remove', 'set')][System.String]$Action
        , [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'The type of the object.')][ValidateNotNullOrEmpty()][ValidateSet('command', 'ldap_server', 'policy', 'application', 'radius_server', 'system_group', 'system', 'user_group', 'user', 'g_suite', 'office_365')][Alias('TypeNameSingular')][System.String]$Type
        , [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Bypass user prompts and dynamic ValidateSet.')][ValidateNotNullOrEmpty()][Switch]$Force
    )
    DynamicParam {
        # Build dynamic parameters
        $RuntimeParameterDictionary = Get-DynamicParamAssociation -Action:($Action) -Type:($Type) -Force:($true)
        Return $RuntimeParameterDictionary
    }
    Begin {
        # Debug message for parameter call
        $PSBoundParameters | Out-DebugParameter | Write-Debug
        $Results = @()
    }
    Process {
        # For DynamicParam with a default value, set that value and then convert the DynamicParam inputs into new variables for the script to use
        Invoke-Command -ScriptBlock:($ScriptBlock_DefaultDynamicParamProcess) -ArgumentList:($PsBoundParameters, $PSCmdlet, $RuntimeParameterDictionary) -NoNewScope
        Try {
            # All the bindings, recursive , both direct and indirect
            $Command_Template_Associations_MemberOf = 'JumpCloud.SDK.V2\Get-JcSdk{0}Member -{0}Id:("{1}")'
            $Command_Template_Associations_Membership = 'JumpCloud.SDK.V2\Get-JcSdk{0}Membership -{0}Id:("{1}")'
            $Command_Template_Associations_TargetType = 'JumpCloud.SDK.V2\Get-JcSdk{0}Traverse{2} -{0}Id:("{1}")'
            # Only direct bindings and don’t traverse through groups
            $Command_Template_Associations_Targets_Get = 'JumpCloud.SDK.V2\Get-JcSdk{0}Association -{0}Id:("{1}") -Targets:("{2}")'
            $Command_Template_Associations_Targets_Post = 'JumpCloud.SDK.V2\Set-JcSdk{0}Association -{0}Id:("{1}") -Id:("{2}") -Op:("{3}") -Type:("{4}") -Attributes:("{5}")'
            $Command_Template_Associations_Members = 'JumpCloud.SDK.V2\Set-JcSdk{0}Member -{0}Id:("{1}") -Id:("{2}") -Op:("{3}")'
            # Determine to search by id or name but always prefer id
            If ($Id) {
                $SourceItemSearchByValue = $Id
                $SourceSearchBy = 'ById'
            } ElseIf ($Name) {
                $SourceItemSearchByValue = $Name
                $SourceSearchBy = 'ByName'
            } Else {
                Write-Error ('-Id or -Name parameter must be populated.') -ErrorAction:('Stop')
            }
            # Get SourceInfo
            $Source = Get-JCObject -Type:($Type) -SearchBy:($SourceSearchBy) -SearchByValue:($SourceItemSearchByValue)
            If ($Source) {
                ForEach ($SourceItem In $Source) {
                    $SourceItemId = $SourceItem.($SourceItem.ById)
                    $SourceItemName = $SourceItem.($SourceItem.ByName)
                    $SourceItemTypeName = $SourceItem.TypeName
                    $SourceItemTypeNameSingular = $SourceItemTypeName.TypeNameSingular
                    $SourceItemTargets = $SourceItem.Targets |
                    Where-Object { $_.TargetSingular -in $TargetType -or $_.TargetPlural -in $TargetType }
                    ForEach ($SourceItemTarget In $SourceItemTargets) {
                        $SourceItemTargetSingular = $SourceItemTarget.TargetSingular
                        # Build Command based upon source and target combinations
                        If (($SourceItemTypeNameSingular -eq 'system' -and $SourceItemTargetSingular -eq 'system_group') -or ($SourceItemTypeNameSingular -eq 'user' -and $SourceItemTargetSingular -eq 'user_group')) {
                            $Command_Associations_GET = $Command_Template_Associations_MemberOf -f $SourceItemTypeNameSingular.Replace('_', ''), $SourceItemId
                        } ElseIf (($SourceItemTypeNameSingular -eq 'system_group' -and $SourceItemTargetSingular -eq 'system') -or ($SourceItemTypeNameSingular -eq 'user_group' -and $SourceItemTargetSingular -eq 'user')) {
                            $Command_Associations_GET = $Command_Template_Associations_Membership -f $SourceItemTypeNameSingular.Replace('_', ''), $SourceItemId
                        } ElseIf (($SourceItemTypeNameSingular -eq 'activedirectory' -and $SourceItemTargetSingular -eq 'user') -or ($SourceItemTypeNameSingular -eq 'user' -and $SourceItemTargetSingular -eq 'activedirectory')) {
                            $Command_Associations_GET = $Command_Template_Associations_Targets_Get -f $SourceItemTypeNameSingular.Replace('_', ''), $SourceItemId, $SourceItemTargetSingular.Replace('_', '')
                        } Else {
                            $Command_Associations_GET = $Command_Template_Associations_TargetType -f $SourceItemTypeNameSingular.Replace('_', ''), $SourceItemId, $SourceItemTargetSingular.Replace('_', '')
                        }
                        $Command_Associations_GET = $Command_Associations_GET.Replace('usergroupId', 'GroupId').Replace('systemgroupId', 'GroupId')
                        # Call endpoint
                        If ($Action -eq 'get') {
                            $AssociationOut = @()
                            # If switches are not passed in, set them to be false so they can be used with Format-JCAssociation
                            If (!($IncludeInfo)) { $IncludeInfo = $false; }
                            If (!($IncludeNames)) { $IncludeNames = $false; }
                            If (!($IncludeVisualPath)) { $IncludeVisualPath = $false; }
                            If (!($Raw)) { $Raw = $false; }
                            # Get associations and format the output
                            $Association = Format-JCAssociation -Command:($Command_Associations_GET) -Source:($SourceItem) -IncludeInfo:($IncludeInfo) -IncludeNames:($IncludeNames) -IncludeVisualPath:($IncludeVisualPath) -Raw:($Raw)
                            If ($Direct -eq $true) {
                                $AssociationOut += $Association | Where-Object { $_.associationType -eq 'direct' -or $_.associationType -eq "direct`/indirect" }
                            }
                            If ($Indirect -eq $true) {
                                $AssociationOut += $Association | Where-Object { $_.associationType -eq 'indirect' -or $_.associationType -eq "direct`/indirect" }
                            }
                            If (!($Direct) -and !($Indirect)) {
                                $AssociationOut += $Association
                            }
                            If ($Raw) {
                                $Result = $AssociationOut | Select-Object -Property:('*') -ExcludeProperty:('associationType')
                                $Results += $Result
                            } Else {
                                $Result = $AssociationOut
                                $Results += $Result | Select-Object * `
                                    , @{Name = 'action'; Expression = { $Action } }
                            }
                        } Else {
                            # For target determine to search by id or name but always prefer id
                            If ($TargetId) {
                                $TargetSearchByValue = $TargetId
                                $TargetSearchBy = 'ById'
                            } ElseIf ($TargetName) {
                                $TargetSearchByValue = $TargetName
                                $TargetSearchBy = 'ByName'
                            } Else {
                                Write-Error ('-TargetId or -TargetName parameter must be populated.') -ErrorAction:('Stop')
                            }
                            If ($associationType -ne 'indirect') {
                                # Get Target object
                                $Target = Get-JCObject -Type:($SourceItemTargetSingular) -SearchBy:($TargetSearchBy) -SearchByValue:($TargetSearchByValue)
                                If ($Target) {
                                    ForEach ($TargetItem In $Target) {
                                        $TargetItemId = $TargetItem.($TargetItem.ById)
                                        $TargetItemName = $TargetItem.($TargetItem.ByName)
                                        $TargetItemTypeNameSingular = $TargetItem.TypeName.TypeNameSingular
                                        # Build the attributes for the json body string
                                        $AttributesValue = If ($Action -eq 'add' -and $Attributes) {
                                            $foundAttributes = $Attributes | ConvertTo-Json -Depth:(99) -Compress
                                            $foundAttributes = $foundAttributes | ConvertFrom-Json
                                            $additionalProperties = $foundAttributes.AdditionalProperties
                                            # Determine: AttributeSudoEnabled / AttributeSudoWithoutPassword
                                            $attributeDictionary = New-Object 'system.collections.generic.dictionary[System.String, System.object]'
                                            if ($additionalProperties.sudo.enabled -And -Not $additionalProperties.sudo.withoutPassword) {
                                                $attributeDictionary.sudo = @{'enabled' = $true; 'withoutPassword' = $false }
                                                $Command_Template_Associations_Targets_Post = 'JumpCloud.SDK.V2\Set-JcSdk{0}Association -{0}Id:("{1}") -Id:("{2}") -Op:("{3}") -Type:("{4}") -Attributes:("{5}")'
                                            }
                                            if ($additionalProperties.sudo.withoutPassword -And -Not $additionalProperties.sudo.enabled) {
                                                $attributeDictionary.sudo = @{'enabled' = $false; 'withoutPassword' = $true }
                                                $Command_Template_Associations_Targets_Post = 'JumpCloud.SDK.V2\Set-JcSdk{0}Association -{0}Id:("{1}") -Id:("{2}") -Op:("{3}") -Type:("{4}") -Attributes:("{5}")'
                                            }
                                            if ($additionalProperties.sudo.enabled -And $additionalProperties.sudo.withoutPassword) {
                                                $attributeDictionary.sudo = @{'enabled' = $true; 'withoutPassword' = $true }
                                                $Command_Template_Associations_Targets_Post = 'JumpCloud.SDK.V2\Set-JcSdk{0}Association -{0}Id:("{1}") -Id:("{2}") -Op:("{3}") -Type:("{4}") -Attributes:("{5}")'
                                            }
                                            if (-Not $additionalProperties.sudo.enabled -And -Not $additionalProperties.sudo.withoutPassword) {
                                                $Command_Template_Associations_Targets_Post = 'JumpCloud.SDK.V2\Set-JcSdk{0}Association -{0}Id:("{1}") -Id:("{2}") -Op:("{3}") -Type:("{4}")'
                                            }
                                            # Return Attributes
                                            $attributeDictionary
                                        } Else {
                                            'null'
                                        }
                                        # Validate that the association exists
                                        $TestAssociation = Format-JCAssociation -Command:($Command_Associations_GET) -Source:($SourceItem) -TargetId:($TargetItemId) -IncludeNames:($true)
                                        $IndirectAssociations = $TestAssociation | Where-Object { $_.associationType -eq 'indirect' }
                                        $DirectAssociations = $TestAssociation | Where-Object { $_.associationType -eq 'direct' -or $_.associationType -eq "direct`/indirect" }
                                        If ($DirectAssociations.associationType -eq "direct`/indirect") { $DirectAssociations.associationType = 'direct' }
                                        # If the target is not only an indirect association
                                        If ($TargetItemId -in $DirectAssociations.targetId -or $Action -eq 'add') {
                                            If (($SourceItemTypeNameSingular -eq 'system' -and $SourceItemTargetSingular -eq 'system_group') -or ($SourceItemTypeNameSingular -eq 'user' -and $SourceItemTargetSingular -eq 'user_group')) {
                                                $Command_Associations_POST = $Command_Template_Associations_Members -f $TargetItemTypeNameSingular.Replace('_', ''), $TargetItemId, $SourceItemId, $Action
                                            } ElseIf (($SourceItemTypeNameSingular -eq 'system_group' -and $SourceItemTargetSingular -eq 'system') -or ($SourceItemTypeNameSingular -eq 'user_group' -and $SourceItemTargetSingular -eq 'user')) {
                                                $Command_Associations_POST = $Command_Template_Associations_Members -f $SourceItemTypeNameSingular.Replace('_', ''), $SourceItemId, $TargetItemId, $Action
                                            } Else {
                                                $Command_Associations_POST = $Command_Template_Associations_Targets_Post -f $SourceItemTypeNameSingular.Replace('_', ''), $SourceItemId, $TargetItemId, $Action, $TargetItemTypeNameSingular, $AttributesValue
                                            }
                                            $Command_Associations_POST = $Command_Associations_POST.Replace('usergroupId', 'GroupId').Replace('systemgroupId', 'GroupId').Replace(' -Attributes:("null")', '').Replace(' -Attributes:("{}")', '')
                                            # Send body to endpoint.
                                            Write-Verbose ('"' + $Action + '" the association between the "' + $SourceItemTypeNameSingular + '" "' + $SourceItemName + '" and the "' + $TargetItemTypeNameSingular + '" "' + $TargetItemName + '"')
                                            Write-Debug ('[CommandTemplate]:' + $Command_Associations_POST + ';')
                                            If (!($Force)) {
                                                Do {
                                                    $HostResponse = Read-Host -Prompt:('Are you sure you want to "' + $Action + '" the association between the "' + $SourceItemTypeNameSingular + '" called "' + $SourceItemName + '" and the "' + $TargetItemTypeNameSingular + '" called "' + $TargetItemName + '"?[Y/N]')
                                                }
                                                Until ($HostResponse -in ('y', 'n'))
                                            }
                                            If ($HostResponse -eq 'y' -or $Force) {
                                                Try {
                                                    $Error.Clear()
                                                    $JCApi = if ($Action -in ('add', 'new')) {
                                                        if ( -not $TestAssociation ) {
                                                            $sdkFunction = $Command_Associations_POST.split(" ") | Select-Object -First 1
                                                            # Get Parameters
                                                            $regex = [regex]'-(.*):(\(\"(.*)\"\))'
                                                            $Params = $Command_Associations_POST.split(" ") | Select-Object -Skip 1
                                                            $paramHash = @{}
                                                            foreach ($item in $params) {
                                                                # Get the params & values
                                                                $patterns = (Select-String -inputObject $item -pattern $regex)
                                                                $paramHash.Add($patterns.matches.groups[1].value, $patterns.matches.groups[3].value)
                                                            }
                                                            if ('Attributes' -in $paramHash.keys) {
                                                                $paramHash['Attributes'] = $AttributesValue
                                                            }
                                                            Invoke-Command -ScriptBlock {
                                                                Param ($sdkFunction, $paramHash)
                                                                # Invoke Command Expression:
                                                                . "$sdkFunction" @paramHash
                                                            } -ArgumentList ($sdkFunction, $paramHash)
                                                        } else {
                                                            Write-Verbose ('" The association between the "' + $SourceItemTypeNameSingular + '" "' + $SourceItemName + '" and the "' + $TargetItemTypeNameSingular + '" "' + $TargetItemName + '"' + '" Already exists "')
                                                            $TestAssociation
                                                        }
                                                    } elseif ($Action -in ('remove')) {
                                                        if ( $TestAssociation ) {
                                                            Invoke-Expression -Command:($Command_Associations_POST)
                                                        } else {
                                                            Write-Verbose ('" The association between the "' + $SourceItemTypeNameSingular + '" "' + $SourceItemName + '" and the "' + $TargetItemTypeNameSingular + '" "' + $TargetItemName + '"' + '" Does not exist "')
                                                            $TestAssociation
                                                        }
                                                    } else {
                                                        Write-Error(" Unknown Action; $action ")
                                                    }
                                                    If ([System.String]::IsNullOrEmpty($Error)) {
                                                        $RetryCounter = 0
                                                        # Validate that the new association has been created
                                                        If ($Action -in ('add', 'new')) {
                                                            Do {
                                                                $AddAssociationValidation = Format-JCAssociation -Command:($Command_Associations_GET) -Source:($SourceItem) -TargetId:($TargetItemId) -IncludeNames:($true) | Where-Object { $_.TargetId -eq $TargetItemId }
                                                                $RetryCounter += 1
                                                                Write-Debug ("[Validation]Retrying $Action association validation: $RetryCounter")
                                                            }
                                                            While ([System.String]::IsNullOrEmpty($AddAssociationValidation) -or $RetryCounter -le 5)
                                                            If ($AddAssociationValidation) {
                                                                $Result = $AddAssociationValidation
                                                            } Else {
                                                                Write-Error ('Association not found. Unable to validate that the association between "' + $SourceItemTypeNameSingular + '" "' + $SourceItemSearchByValue + '" and "' + $TargetItemTypeNameSingular + '" "' + $TargetSearchByValue + '" was created.')
                                                            }
                                                        }
                                                        # Validate that the old association has been removed
                                                        If ($Action -eq 'remove') {
                                                            Do {
                                                                $RemoveAssociationValidation = Format-JCAssociation -Command:($Command_Associations_GET) -Source:($SourceItem) -TargetId:($TargetItemId) -IncludeNames:($true)
                                                                $RetryCounter += 1
                                                                Write-Debug ("[Validation]Retrying $Action association validation: $RetryCounter")
                                                            }
                                                            While (-not [System.String]::IsNullOrEmpty($RemoveAssociationValidation) -or $RetryCounter -le 5)
                                                            If (!($RemoveAssociationValidation) -or $RemoveAssociationValidation.associationType -eq 'indirect') {
                                                                $Result = $DirectAssociations
                                                            } Else {
                                                                Write-Error ('Association found. Unable to validate that the association between "' + $SourceItemTypeNameSingular + '" "' + $SourceItemSearchByValue + '" and "' + $TargetItemTypeNameSingular + '" "' + $TargetSearchByValue + '" has been removed.')
                                                            }
                                                        }
                                                        # Append record status
                                                        $Results += If ($Result) {
                                                            $Result | Select-Object * `
                                                                , @{Name = 'action'; Expression = { $Action } }
                                                        }
                                                    }
                                                } Catch {
                                                    Write-Error ($_)
                                                }
                                            }
                                        }
                                    }
                                } Else {
                                    Write-Error ('Unable to find the target "' + $SourceItemTargetSingular + '" called "' + $TargetSearchByValue + '".')
                                }
                            } Else {
                                Write-Verbose ('Association is ' + $associationType + ' between "' + $SourceItemTypeNameSingular + '" "' + $SourceItemSearchByValue + '" and "' + $TargetItemTypeNameSingular + '" "' + $TargetSearchByValue + '".')
                            }
                        }
                    }
                }
            } Else {
                Write-Error ('Unable to find the "' + $Type + '" called "' + $SourceItemSearchByValue + '".')
            }
        } Catch {
            Invoke-Command -ScriptBlock:($ScriptBlock_TryCatchError) -ArgumentList:($_, $true) -NoNewScope
        }
    }
    End {
        If ($Results) {
            Return $Results
        }
    }
}