Private/Association/Invoke-JCAssociation.ps1
Function Invoke-JCAssociation { [CmdletBinding(DefaultParameterSetName = 'ById')] Param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 0)][ValidateNotNullOrEmpty()][ValidateSet('add', 'get', 'remove')][string]$Action, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 1, 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')][string]$Type ) DynamicParam { # Build dynamic parameters $RuntimeParameterDictionary = Get-DynamicParamAssociation -Action:($Action) -Type:($Type) Return $RuntimeParameterDictionary } Begin { # Debug message for parameter call Invoke-Command -ScriptBlock:($ScriptBlock_DefaultDebugMessageBegin) -ArgumentList:($MyInvocation, $PsBoundParameters, $PSCmdlet) -NoNewScope $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 $URL_Template_Associations_MemberOf = '/api/v2/{0}/{1}/memberof' # $SourcePlural, $SourceId $URL_Template_Associations_Membership = '/api/v2/{0}/{1}/membership' # $SourcePlural (systemgroups,usergroups), $SourceId $URL_Template_Associations_TargetType = '/api/v2/{0}/{1}/{2}' # $SourcePlural, $SourceId, $TargetPlural # Only direct bindings and don’t traverse through groups $URL_Template_Associations_Targets = '/api/v2/{0}/{1}/associations?targets={2}' # $SourcePlural, $SourceId, $TargetSingular $URL_Template_Associations_Members = '/api/v2/{0}/{1}/members' # $SourcePlural, $SourceId # ScriptBlock used for building get associations results Function Format-JCAssociation { Param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 0)][ValidateNotNullOrEmpty()][string]$Action , [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 1)][ValidateNotNullOrEmpty()][string]$Uri , [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 2)][ValidateNotNullOrEmpty()][string]$Method , [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 3)][ValidateNotNullOrEmpty()][object]$Source , [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 4)][ValidateNotNullOrEmpty()][switch]$IncludeInfo , [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 5)][ValidateNotNullOrEmpty()][switch]$IncludeNames , [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 6)][ValidateNotNullOrEmpty()][switch]$IncludeVisualPath , [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 7)][ValidateNotNullOrEmpty()][switch]$Raw ) Write-Debug ('[UrlTemplate]:' + $Uri) $Associations = Invoke-JCApi -Method:($Method) -Paginate:($true) -Url:($Uri) $AssociationsOut = @() ForEach ($Association In $Associations) { # For source determine if association is direct or indirect $associationType = If (($Association | ForEach-Object {($_.paths.to | Measure-Object).Count}) -eq 1 -or ($Association | ForEach-Object {($_.paths.to | Measure-Object).Count}) -eq 0) {'direct'} ElseIf (($Association | ForEach-Object {($_.paths.to | Measure-Object).Count}) -gt 1) {'indirect'} Else {'unknown;The count of paths is:' + ($_.paths.to | Measure-Object).Count} # Raw switch allows for the user to return an unformatted version of what the api endpoint returns If ($Raw) { Add-Member -InputObject:($Association) -NotePropertyName:('associationType') -NotePropertyValue:($associationType); $AssociationsOut += $Association } Else { $AssociationHash = [ordered]@{ 'action' = $Action; 'associationType' = $associationType; 'id' = $Source.($Source.ById); 'type' = $Source.TypeNameSingular; 'name' = $null; 'info' = $null; 'targetId' = $Association.id; 'targetType' = $Association.type; 'targetName' = $null; 'targetInfo' = $null; 'visualPathById' = $null; 'visualPathByName' = $null; 'visualPathByType' = $null; }; # Dynamically get the rest of the properties and add them to the object $Association | ForEach-Object {$_.PSObject.Properties.name} | Select-Object -Unique | Where-Object {$_ -notin ('id', 'type')} | ForEach-Object {$AssociationHash.Add($_, $Association.($_)) | Out-Null} # If any "Return*" switch is provided get the target object If ($IncludeInfo -or $IncludeNames -or $IncludeVisualPath) { $Target = Get-JCObject -Type:($Association.type) -Id:($Association.id) } # Show source and target info If ($IncludeInfo) { $AssociationHash.info = $Source $AssociationHash.targetInfo = $Target } # Show names of source and target If ($IncludeNames) { $AssociationHash.name = $Source.($Source.ByName) $AssociationHash.targetName = $Target.($Target.ByName) } # Map out the associations path and show If ($IncludeVisualPath) { class AssociationMap { [string]$Id; [string]$Name; [string]$Type; AssociationMap([string]$i, [string]$n, [string]$t) {$this.Id = $i; $this.Name = $n; $this.Type = $t; } } $Association.paths | ForEach-Object { $AssociationVisualPath = @() [AssociationMap]$AssociationVisualPathRecord = [AssociationMap]::new($Source.($Source.ById), $Source.($Source.ByName), $Source.TypeNameSingular) $AssociationVisualPath += $AssociationVisualPathRecord $_.to | ForEach-Object { $AssociationPathToItemInfo = Get-JCObject -Type:($_.type) -Id:($_.id) $AssociationVisualPath += [AssociationMap]::new($_.id, $AssociationPathToItemInfo.($AssociationPathToItemInfo.ByName), $_.type) } ($AssociationVisualPath | ForEach-Object {$_.PSObject.Properties.name} | Select-Object -Unique) | ForEach-Object { $KeyName_visualPath = 'visualPathBy' + $_ $AssociationHash.($KeyName_visualPath) = ('"' + ($AssociationVisualPath.($_) -join '" -> "') + '"') } } } # Convert the hashtable to an object where the Value has been populated $AssociationsUpdated = [PSCustomObject]@{} $AssociationHash.GetEnumerator() | ForEach-Object {If ($_.Value) {Add-Member -InputObject:($AssociationsUpdated) -NotePropertyName:($_.Key) -NotePropertyValue:($_.Value)}} $AssociationsOut += $AssociationsUpdated } } Return $AssociationsOut } # 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) { If (($Source | Measure-Object).Count -gt 1) { Write-Warning -Message:('Found "' + [string]($Source | Measure-Object).Count + '" "' + $Type + '" with the "' + $SourceSearchBy.Replace('By', '').ToLower() + '" of "' + $SourceItemSearchByValue + '"') } ForEach ($SourceItem In $Source) { $SourceItemId = $SourceItem.($SourceItem.ById) $SourceItemName = $SourceItem.($SourceItem.ByName) $SourceItemTypeName = $SourceItem.TypeName $SourceItemTypeNameSingular = $SourceItemTypeName.TypeNameSingular $SourceItemTypeNamePlural = $SourceItemTypeName.TypeNamePlural $SourceItemTargets = $SourceItem.Targets | Where-Object { $_.TargetSingular -in $TargetType -or $_.TargetPlural -in $TargetType } ForEach ($SourceItemTarget In $SourceItemTargets) { $SourceItemTargetSingular = $SourceItemTarget.TargetSingular $SourceItemTargetPlural = $SourceItemTarget.TargetPlural # Build Url based upon source and target combinations If (($SourceItemTypeNamePlural -eq 'systems' -and $SourceItemTargetPlural -eq 'systemgroups') -or ($SourceItemTypeNamePlural -eq 'users' -and $SourceItemTargetPlural -eq 'usergroups')) { $Uri_Associations_GET = $URL_Template_Associations_MemberOf -f $SourceItemTypeNamePlural, $SourceItemId } ElseIf (($SourceItemTypeNamePlural -eq 'systemgroups' -and $SourceItemTargetPlural -eq 'systems') -or ($SourceItemTypeNamePlural -eq 'usergroups' -and $SourceItemTargetPlural -eq 'users')) { $Uri_Associations_GET = $URL_Template_Associations_Membership -f $SourceItemTypeNamePlural, $SourceItemId } ElseIf (($SourceItemTypeNamePlural -eq 'activedirectories' -and $SourceItemTargetPlural -eq 'users') -or ($SourceItemTypeNamePlural -eq 'users' -and $SourceItemTargetPlural -eq 'activedirectories')) { $Uri_Associations_GET = $URL_Template_Associations_Targets -f $SourceItemTypeNamePlural, $SourceItemId, $SourceItemTargetSingular } Else { $Uri_Associations_GET = $URL_Template_Associations_TargetType -f $SourceItemTypeNamePlural, $SourceItemId, $SourceItemTargetPlural } # Call endpoint If ($Action -eq 'get') { $AssociationOut = @() $Association = Format-JCAssociation -Action:($Action) -Uri:($Uri_Associations_GET) -Method:('GET') -Source:($SourceItem) -IncludeInfo:($IncludeInfo) -IncludeNames:($IncludeNames) -IncludeVisualPath:($IncludeVisualPath) -Raw:($Raw) If ($Direct -eq $true) { $AssociationOut += $Association | Where-Object {$_.associationType -eq 'direct'} } If ($Indirect -eq $true) { $AssociationOut += $Association | Where-Object {$_.associationType -eq 'indirect'} } If (!($Direct) -and !($Indirect)) { $AssociationOut += $Association } If ($Raw) { $Results += $AssociationOut | Select-Object -Property:('*') -ExcludeProperty:('associationType') } Else { $Results += $AssociationOut } } Else { # For target determine to search by id or name but always prefer id If ($TargetId -and $TargetName) { Write-Error ('Both the -TargetId and -TargetName have been provided. Function only accepts -TargetId or -TargetName.') -ErrorAction:('Stop') } ElseIf ($TargetId) { $TargetSearchByValue = $TargetId $TargetSearchBy = 'ById' } ElseIf ($TargetName) { $TargetSearchByValue = $TargetName $TargetSearchBy = 'ByName' } Else { Write-Error ('-TargetId or -TargetName parameter must be populated.') -ErrorAction:('Stop') } # Get Target object $Target = Get-JCObject -Type:($SourceItemTargetSingular) -SearchBy:($TargetSearchBy) -SearchByValue:($TargetSearchByValue) ForEach ($TargetItem In $Target) { $TargetItemId = $TargetItem.($TargetItem.ById) $TargetItemName = $TargetItem.($TargetItem.ByName) $TargetItemTypeNameSingular = $TargetItem.TypeName.TypeNameSingular $TargetItemTypeNamePlural = $TargetItem.TypeName.TypeNamePlural # Build the attributes for the json body string $AttributesValue = If ($Action -eq 'add' -and $Attributes) { $Attributes | ConvertTo-Json -Depth:(100) -Compress } Else { 'null' } # Get the existing association before removing it If ($Action -eq 'remove') { $RemoveAssociation = Format-JCAssociation -Action:($Action) -Uri:($Uri_Associations_GET) -Method:('GET') -Source:($SourceItem) -IncludeInfo:($IncludeInfo) -IncludeNames:($IncludeNames) -IncludeVisualPath:($IncludeVisualPath) -Raw:($Raw) | Where-Object {$_.TargetId -eq $TargetItemId} $IndirectAssociations = $RemoveAssociation | Where-Object {$_.associationType -ne 'direct'} $Results += $RemoveAssociation | Where-Object {$_.associationType -eq 'direct'} } If ($TargetItemId -ne $IndirectAssociations.targetId) { # Build uri and body If (($SourceItemTypeNamePlural -eq 'systems' -and $SourceItemTargetPlural -eq 'systemgroups') -or ($SourceItemTypeNamePlural -eq 'users' -and $SourceItemTargetPlural -eq 'usergroups')) { $Uri_Associations_POST = $URL_Template_Associations_Members -f $TargetItemTypeNamePlural, $TargetItemId $JsonBody = '{"op":"' + $Action + '","type":"' + $SourceItemTypeNameSingular + '","id":"' + $SourceItemId + '","attributes":' + $AttributesValue + '}' } ElseIf (($SourceItemTypeNamePlural -eq 'systemgroups' -and $SourceItemTargetPlural -eq 'systems') -or ($SourceItemTypeNamePlural -eq 'usergroups' -and $SourceItemTargetPlural -eq 'users')) { $Uri_Associations_POST = $URL_Template_Associations_Members -f $SourceItemTypeNamePlural, $SourceItemId $JsonBody = '{"op":"' + $Action + '","type":"' + $TargetItemTypeNameSingular + '","id":"' + $TargetItemId + '","attributes":' + $AttributesValue + '}' } Else { $Uri_Associations_POST = $URL_Template_Associations_Targets -f $SourceItemTypeNamePlural, $SourceItemId, $SourceItemTargetSingular $JsonBody = '{"op":"' + $Action + '","type":"' + $TargetItemTypeNameSingular + '","id":"' + $TargetItemId + '","attributes":' + $AttributesValue + '}' } # Send body to endpoint. Write-Verbose ('"' + $Action + '" the association between the "' + $SourceItemTypeNameSingular + '" "' + $SourceItemName + '" and the "' + $TargetItemTypeNameSingular + '" "' + $TargetItemName + '"') Write-Debug ('[UrlTemplate]:' + $Uri_Associations_POST + '; Body:' + $JsonBody + ';') 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) { $Results += Invoke-JCApi -Body:($JsonBody) -Method:('POST') -Url:($Uri_Associations_POST) } } # Get the newly created association If ($Action -eq 'add') { $Results += Format-JCAssociation -Action:($Action) -Uri:($Uri_Associations_GET) -Method:('GET') -Source:($SourceItem) -IncludeInfo:($IncludeInfo) -IncludeNames:($IncludeNames) -IncludeVisualPath:($IncludeVisualPath) -Raw:($Raw) | Where-Object {$_.TargetId -eq $TargetItemId} } } } } } } Else { Write-Error ('Unable to find the "' + $Type + '" called "' + $SourceItemSearchByValue + '".') } } Catch { Invoke-Command -ScriptBlock:($ScriptBlock_TryCatchError) -ArgumentList:($_) -NoNewScope } } End { Return $Results } } |