PowerAdobe.psm1
function ConvertTo-JsonLiteral { <# .SYNOPSIS Converts an object to a JSON-formatted string. .DESCRIPTION The ConvertTo-Json cmdlet converts any object to a string in JavaScript Object Notation (JSON) format. The properties are converted to field names, the field values are converted to property values, and the methods are removed. .PARAMETER Object Specifies the objects to convert to JSON format. Enter a variable that contains the objects, or type a command or expression that gets the objects. You can also pipe an object to ConvertTo-JsonLiteral .PARAMETER Depth Specifies how many levels of contained objects are included in the JSON representation. The default value is 0. .PARAMETER AsArray Outputs the object in array brackets, even if the input is a single object. .PARAMETER DateTimeFormat Changes DateTime string format. Default "yyyy-MM-dd HH:mm:ss" .PARAMETER NumberAsString Provides an alternative serialization option that converts all numbers to their string representation. .PARAMETER BoolAsString Provides an alternative serialization option that converts all bool to their string representation. .PARAMETER PropertyName Uses PropertyNames provided by user (only works with Force) .PARAMETER NewLineFormat Provides a way to configure how new lines are converted for property names .PARAMETER NewLineFormatProperty Provides a way to configure how new lines are converted for values .PARAMETER PropertyName Allows passing property names to be used for custom objects (hashtables and alike are unaffected) .PARAMETER ArrayJoin Forces any array to be a string regardless of depth level .PARAMETER ArrayJoinString Uses defined string or char for array join. By default it uses comma with a space when used. .PARAMETER Force Forces using property names from first object or given thru PropertyName parameter .EXAMPLE Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral .EXAMPLE Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -Depth 3 .EXAMPLE Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NewLineFormat $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" } -NumberAsString -BoolAsString .EXAMPLE Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NumberAsString -BoolAsString -DateTimeFormat "yyyy-MM-dd HH:mm:ss" .EXAMPLE # Keep in mind this advanced replace will break ConvertFrom-Json, but it's sometimes useful for projects like PSWriteHTML Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NewLineFormat $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" } -NumberAsString -BoolAsString -AdvancedReplace @{ '.' = '\.'; '$' = '\$' } .NOTES General notes #> [cmdletBinding()] param( [alias('InputObject')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, Mandatory)][Array] $Object, [int] $Depth, [switch] $AsArray, [string] $DateTimeFormat = "yyyy-MM-dd HH:mm:ss", [switch] $NumberAsString, [switch] $BoolAsString, [System.Collections.IDictionary] $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $NewLineFormatProperty = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $AdvancedReplace, [string] $ArrayJoinString, [switch] $ArrayJoin, [string[]]$PropertyName, [switch] $Force ) Begin { $TextBuilder = [System.Text.StringBuilder]::new() $CountObjects = 0 filter IsNumeric() { return $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] ` -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] ` -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal] } filter IsOfType() { return $_ -is [bool] -or $_ -is [char] -or $_ -is [datetime] -or $_ -is [string] ` -or $_ -is [timespan] -or $_ -is [URI] ` -or $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] ` -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] ` -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal] } [int] $MaxDepth = $Depth [int] $InitialDepth = 0 } Process { for ($a = 0; $a -lt $Object.Count; $a++) { $CountObjects++ if ($CountObjects -gt 1) { $null = $TextBuilder.Append(',') } if ($Object[$a] -is [System.Collections.IDictionary]) { $null = $TextBuilder.AppendLine("{") for ($i = 0; $i -lt ($Object[$a].Keys).Count; $i++) { $Property = ([string[]]$Object[$a].Keys)[$i] $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $null = $TextBuilder.Append("`"$DisplayProperty`":") $Value = ConvertTo-StringByType -Value $Object[$a][$Property] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace $null = $TextBuilder.Append("$Value") if ($i -ne ($Object[$a].Keys).Count - 1) { $null = $TextBuilder.AppendLine(',') } } $null = $TextBuilder.Append("}") } elseif ($Object[$a] | IsOfType) { $Value = ConvertTo-StringByType -Value $Object[$a] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace $null = $TextBuilder.Append($Value) } else { $null = $TextBuilder.AppendLine("{") if ($Force -and -not $PropertyName) { $PropertyName = $Object[0].PSObject.Properties.Name } elseif ($Force -and $PropertyName) { } else { $PropertyName = $Object[$a].PSObject.Properties.Name } $PropertyCount = 0 foreach ($Property in $PropertyName) { $PropertyCount++ $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $null = $TextBuilder.Append("`"$DisplayProperty`":") $Value = ConvertTo-StringByType -Value $Object[$a].$Property -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace $null = $TextBuilder.Append("$Value") if ($PropertyCount -ne $PropertyName.Count) { $null = $TextBuilder.AppendLine(',') } } $null = $TextBuilder.Append("}") } $InitialDepth = 0 } } End { if ($CountObjects -gt 1 -or $AsArray) { "[$($TextBuilder.ToString())]" } else { $TextBuilder.ToString() } } } function Join-UriQuery { <# .SYNOPSIS Provides ability to join two Url paths together including advanced querying .DESCRIPTION Provides ability to join two Url paths together including advanced querying which is useful for RestAPI/GraphApi calls .PARAMETER BaseUri Primary Url to merge .PARAMETER RelativeOrAbsoluteUri Additional path to merge with primary url (optional) .PARAMETER QueryParameter Parameters and their values in form of hashtable .PARAMETER EscapeUriString If set, will escape the url string .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' -QueryParameter @{ page = 1 per_page = 20 search = 'SearchString' } .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz/wp-json/wp/v2/posts' -QueryParameter @{ page = 1 per_page = 20 search = 'SearchString' } .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .NOTES General notes #> [alias('Join-UrlQuery')] [CmdletBinding()] param ( [parameter(Mandatory)][uri] $BaseUri, [parameter(Mandatory = $false)][uri] $RelativeOrAbsoluteUri, [Parameter()][System.Collections.IDictionary] $QueryParameter, [alias('EscapeUrlString')][switch] $EscapeUriString ) Begin { Add-Type -AssemblyName System.Web } Process { if ($BaseUri -and $RelativeOrAbsoluteUri) { $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri } else { $Url = $BaseUri } if ($QueryParameter) { $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) foreach ($key in $QueryParameter.Keys) { $Collection.Add($key, $QueryParameter.$key) } } $uriRequest = [System.UriBuilder] $Url if ($Collection) { $uriRequest.Query = $Collection.ToString() } if (-not $EscapeUriString) { $uriRequest.Uri.AbsoluteUri } else { [System.Uri]::EscapeUriString($uriRequest.Uri.AbsoluteUri) } } } function Remove-EmptyValue { <# .SYNOPSIS Removes empty values from a hashtable recursively. .DESCRIPTION This function removes empty values from a given hashtable. It can be used to clean up a hashtable by removing keys with null, empty string, empty array, or empty dictionary values. The function supports recursive removal of empty values. .PARAMETER Hashtable The hashtable from which empty values will be removed. .PARAMETER ExcludeParameter An array of keys to exclude from the removal process. .PARAMETER Recursive Indicates whether to recursively remove empty values from nested hashtables. .PARAMETER Rerun Specifies the number of times to rerun the removal process recursively. .PARAMETER DoNotRemoveNull If specified, null values will not be removed. .PARAMETER DoNotRemoveEmpty If specified, empty string values will not be removed. .PARAMETER DoNotRemoveEmptyArray If specified, empty array values will not be removed. .PARAMETER DoNotRemoveEmptyDictionary If specified, empty dictionary values will not be removed. .EXAMPLE $hashtable = @{ 'Key1' = ''; 'Key2' = $null; 'Key3' = @(); 'Key4' = @{} } Remove-EmptyValue -Hashtable $hashtable -Recursive Description ----------- This example removes empty values from the $hashtable recursively. #> [alias('Remove-EmptyValues')] [CmdletBinding()] param( [alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable, [string[]] $ExcludeParameter, [switch] $Recursive, [int] $Rerun, [switch] $DoNotRemoveNull, [switch] $DoNotRemoveEmpty, [switch] $DoNotRemoveEmptyArray, [switch] $DoNotRemoveEmptyDictionary ) foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } } if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } } } function ConvertTo-StringByType { <# .SYNOPSIS Private function to use within ConvertTo-JsonLiteral .DESCRIPTION Private function to use within ConvertTo-JsonLiteral .PARAMETER Value Value to convert to JsonValue .PARAMETER Depth Specifies how many levels of contained objects are included in the JSON representation. The default value is 0. .PARAMETER AsArray Outputs the object in array brackets, even if the input is a single object. .PARAMETER DateTimeFormat Changes DateTime string format. Default "yyyy-MM-dd HH:mm:ss" .PARAMETER NumberAsString Provides an alternative serialization option that converts all numbers to their string representation. .PARAMETER BoolAsString Provides an alternative serialization option that converts all bool to their string representation. .PARAMETER PropertyName Uses PropertyNames provided by user (only works with Force) .PARAMETER ArrayJoin Forces any array to be a string regardless of depth level .PARAMETER ArrayJoinString Uses defined string or char for array join. By default it uses comma with a space when used. .PARAMETER Force Forces using property names from first object or given thru PropertyName parameter .EXAMPLE $Value = ConvertTo-StringByType -Value $($Object[$a][$i]) -DateTimeFormat $DateTimeFormat .NOTES General notes #> [cmdletBinding()] param( [Object] $Value, [int] $Depth, [int] $MaxDepth, [string] $DateTimeFormat, [switch] $NumberAsString, [switch] $BoolAsString, [System.Collections.IDictionary] $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $NewLineFormatProperty = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $AdvancedReplace, [System.Text.StringBuilder] $TextBuilder, [string[]] $PropertyName, [switch] $ArrayJoin, [string] $ArrayJoinString, [switch] $Force ) Process { if ($null -eq $Value) { "`"`"" } elseif ($Value -is [string]) { $Value = $Value.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) foreach ($Key in $AdvancedReplace.Keys) { $Value = $Value.Replace($Key, $AdvancedReplace[$Key]) } "`"$Value`"" } elseif ($Value -is [DateTime]) { "`"$($($Value).ToString($DateTimeFormat))`"" } elseif ($Value -is [bool]) { if ($BoolAsString) { "`"$($Value)`"" } else { $Value.ToString().ToLower() } } elseif ($Value -is [System.Collections.IDictionary]) { if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) { "`"$($Value)`"" } else { $Depth++ $null = $TextBuilder.AppendLine("{") for ($i = 0; $i -lt ($Value.Keys).Count; $i++) { $Property = ([string[]]$Value.Keys)[$i] $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $null = $TextBuilder.Append("`"$DisplayProperty`":") $OutputValue = ConvertTo-StringByType -Value $Value[$Property] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent $null = $TextBuilder.Append("$OutputValue") if ($i -ne ($Value.Keys).Count - 1) { $null = $TextBuilder.AppendLine(',') } } $null = $TextBuilder.Append("}") } } elseif ($Value -is [System.Collections.IList] -or $Value -is [System.Collections.ReadOnlyCollectionBase]) { if ($ArrayJoin) { $Value = $Value -join $ArrayJoinString $Value = "$Value".Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) "`"$Value`"" } else { if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) { $Value = "$Value".Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) "`"$Value`"" } else { $CountInternalObjects = 0 $null = $TextBuilder.Append("[") foreach ($V in $Value) { $CountInternalObjects++ if ($CountInternalObjects -gt 1) { $null = $TextBuilder.Append(',') } if ($Force -and -not $PropertyName) { $PropertyName = $V.PSObject.Properties.Name } elseif ($Force -and $PropertyName) { } else { $PropertyName = $V.PSObject.Properties.Name } $OutputValue = ConvertTo-StringByType -Value $V -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -PropertyName $PropertyName -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent $null = $TextBuilder.Append($OutputValue) } $null = $TextBuilder.Append("]") } } } elseif ($Value -is [System.Enum]) { "`"$($($Value).ToString())`"" } elseif (($Value | IsNumeric) -eq $true) { $Value = $($Value).ToString().Replace(',', '.') if ($NumberAsString) { "`"$Value`"" } else { $Value } } elseif ($Value -is [PSObject]) { if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) { "`"$($Value)`"" } else { $Depth++ $CountInternalObjects = 0 $null = $TextBuilder.AppendLine("{") if ($Force -and -not $PropertyName) { $PropertyName = $Value.PSObject.Properties.Name } elseif ($Force -and $PropertyName) { } else { $PropertyName = $Value.PSObject.Properties.Name } foreach ($Property in $PropertyName) { $CountInternalObjects++ if ($CountInternalObjects -gt 1) { $null = $TextBuilder.AppendLine(',') } $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $null = $TextBuilder.Append("`"$DisplayProperty`":") $OutputValue = ConvertTo-StringByType -Value $Value.$Property -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent $null = $TextBuilder.Append("$OutputValue") } $null = $TextBuilder.Append("}") } } else { $Value = $Value.ToString().Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) "`"$Value`"" } } } function Join-Uri { <# .SYNOPSIS Provides ability to join two Url paths together .DESCRIPTION Provides ability to join two Url paths together .PARAMETER BaseUri Primary Url to merge .PARAMETER RelativeOrAbsoluteUri Additional path to merge with primary url .EXAMPLE Join-Uri 'https://evotec.xyz/' '/wp-json/wp/v2/posts' .EXAMPLE Join-Uri 'https://evotec.xyz/' 'wp-json/wp/v2/posts' .EXAMPLE Join-Uri -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .EXAMPLE Join-Uri -BaseUri 'https://evotec.xyz/test/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .NOTES General notes #> [alias('Join-Url')] [cmdletBinding()] param( [parameter(Mandatory)][uri] $BaseUri, [parameter(Mandatory)][uri] $RelativeOrAbsoluteUri ) return ($BaseUri.OriginalString.TrimEnd('/') + "/" + $RelativeOrAbsoluteUri.OriginalString.TrimStart('/')) } function Invoke-AdobeQuery { <# .SYNOPSIS Executes a query against the Adobe User Management API. .DESCRIPTION The Invoke-AdobeQuery function sends HTTP requests to the Adobe User Management API. It handles GET and other HTTP methods, manages pagination, and processes responses. .PARAMETER BaseUri The base URI for the Adobe User Management API. Defaults to 'https://usermanagement.adobe.io/v2/usermanagement'. .PARAMETER Url The endpoint URL for the specific API call. .PARAMETER Method The HTTP method to use for the request (e.g., GET, POST). .PARAMETER Data The data to include in the body of the request, applicable for methods like POST. .PARAMETER QueryParameter Additional query parameters to include in the request URI. .EXAMPLE Invoke-AdobeQuery -Url "users" -Method "GET" .EXAMPLE Invoke-AdobeQuery -Url "groups" -Method "POST" -Data $groupData #> [CmdletBinding()] param( [string] $BaseUri = 'https://usermanagement.adobe.io/v2/usermanagement', [Parameter(Mandatory)][string] $Url, [Parameter(Mandatory)][string] $Method, [Parameter()][System.Collections.IDictionary[]] $Data, [Parameter()][System.Collections.IDictionary] $QueryParameter ) $Organization = $($($Script:AdobeTokenInformation).Organization) if ($Method -eq 'GET') { $Page = 0 $UsedUrl = $Url Do { $UsedUrl = $Url.Replace('{orgId}', $Organization) $UsedUrl = $UsedUrl.Replace('{page}', $Page) $UriToUse = Join-UriQuery -BaseUri $BaseUri -RelativeOrAbsoluteUri $UsedUrl -QueryParameter $QueryParameter Write-Verbose -Message "Invoke-AdobeQuery - Url: $UriToUse / Method: $Method / Page: $Page" try { $Response = $null $Response = Invoke-WebRequest -Method Get -Uri $UriToUse -Headers $Script:AdobeTokenInformation.Headers -ErrorAction Stop -Verbose:$false } catch { if ($_.Exception.Response.StatusCode -eq 'TooManyRequests') { $TimeToRetry = $_.Exception.Response.Headers.RetryAfter Write-Warning -Message "Invoke-AdobeQuery - Too many requests. Retry after $TimeToRetry seconds" } else { $ErrorDetails = $_.ErrorDetails if ($_.ErrorDetails.Message) { try { $Message = $_.Exception.Message $ErrorCode = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction Stop if ($ErrorCode) { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Error code $($ErrorCode.error_Code) / $($ErrorCode.message)" } else { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Error $Message" } } catch { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Error $($_.Exception.Message), ErrorDetails: $($ErrorDetails.Message)" } } else { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Error $($_.Exception.Message), ErrorDetails: $($ErrorDetails.Message)" } } } if ($Response) { $Output = $Response.Content | ConvertFrom-Json -ErrorAction SilentlyContinue if ($null -ne $Output.users) { $Output.users } elseif ($null -ne $Output.groups) { $Output.groups } elseif ($null -ne $Output.user) { $Output.user } else { $Output } if ($Output.Headers."-X-Page-Count") { $MaximumPageCount = $Output.Headers."-X-Page-Count" } } else { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Terminating" break } $Page++ } while (-not $Output.LastPage -and $Page -le $MaximumPageCount) } else { $UriToUse = Join-UriQuery -BaseUri $BaseUri -RelativeOrAbsoluteUri "$Url/$Organization" -QueryParameter $QueryParameter Write-Verbose -Message "Invoke-AdobeQuery - Url: $UriToUse / Method: $Method" try { if ($PSVersionTable.PSVersion.Major -lt 6) { $DataJSON = @($Data) | ConvertTo-JsonLiteral -Depth 5 -AsArray } else { $DataJSON = @($Data) | ConvertTo-Json -Depth 5 -AsArray } $Response = Invoke-WebRequest -Method $Method -Uri $UriToUse -Headers $Script:AdobeTokenInformation.Headers -ErrorAction Stop -Verbose:$false -Body $DataJSON } catch { if ($_.Exception.Response.StatusCode -eq 'TooManyRequests') { $TimeToRetry = $_.Exception.Response.Headers.RetryAfter Write-Warning -Message "Invoke-AdobeQuery - Too many requests. Retry after $TimeToRetry seconds" } else { $ErrorDetails = $_.ErrorDetails if ($_.ErrorDetails.Message) { try { $Message = $_.Exception.Message $ErrorCode = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction Stop if ($ErrorCode) { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Error code $($ErrorCode.error_Code) / $($ErrorCode.message)" } else { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Error $Message" } } catch { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Error $($_.Exception.Message), ErrorDetails: $($ErrorDetails.Message)" } } else { Write-Warning -Message "Invoke-AdobeQuery - Unable to connect to organization '$Organization'. Error $($_.Exception.Message), ErrorDetails: $($ErrorDetails.Message)" } } } if ($Response -and $Response.Content) { $Output = $Response.Content | ConvertFrom-Json -ErrorAction SilentlyContinue $Output } elseif ($Response) { $Response } } } function Add-AdobeGroupMember { <# .SYNOPSIS Adds a member to an Adobe group. .DESCRIPTION The Add-AdobeGroupMember cmdlet adds a specified user to one or more Adobe groups. It supports bulk processing for adding multiple groups at once. .PARAMETER GroupName The name(s) of the Adobe group(s) to which the user will be added. .PARAMETER Email The email address of the user to be added to the group(s). .PARAMETER BulkProcessing Enables bulk processing mode for adding multiple groups simultaneously. .EXAMPLE Add-AdobeGroupMember -GroupName "Admins" -Email "john.doe@example.com" .EXAMPLE Add-AdobeGroupMember -GroupName "Admins","Developers" -Email "john.doe@example.com" -BulkProcessing #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string[]] $GroupName, [Parameter(Mandatory)][string] $Email, [switch] $BulkProcessing ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Add-AdobeGroupMember - You need to connect to Adobe first using Connect-Adobe' return } if ($GroupName.Count -gt 10) { Write-Warning -Message 'Add-AdobeGroupMember - Only ten groups can be added at a time' return } $Data = [ordered] @{ user = $Email requestID = "action_$(Get-Random)" do = @( @{ 'add' = @{ 'group' = @( $GroupName ) } } ) } Remove-EmptyValue -Hashtable $Data -Recursive -Rerun 2 if ($BulkProcessing) { return $Data } $Data | ConvertTo-Json -Depth 5 | Write-Verbose $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess($GroupName, 'Add Adobe Group Member')) { $false } else { $true } } Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Data -QueryParameter $QueryParameter } function Connect-Adobe { <# .SYNOPSIS Connects to the Adobe API. .DESCRIPTION The Connect-Adobe cmdlet establishes a connection to the Adobe API using the provided credentials and scopes. It handles token retrieval and caching for subsequent API calls. .PARAMETER ClientID The Client ID for Adobe API authentication. .PARAMETER ClientSecret The Client Secret for Adobe API authentication. .PARAMETER ClientSecretEncrypted The encrypted Client Secret for Adobe API authentication. .PARAMETER Scopes The scopes for API access. .PARAMETER Organization The Adobe organization identifier. .PARAMETER ExistingToken Use an existing token if available. .PARAMETER DoNotSuppress Suppress suppression of the token information. .PARAMETER Force Force a new token retrieval even if a valid token exists. .EXAMPLE Connect-Adobe -ClientID "your_client_id" -ClientSecret "your_client_secret" -Scopes "openid, AdobeID" -Organization "your_org_id" #> [CmdletBinding()] param( [parameter(Mandatory)][string] $ClientID, [Parameter(Mandatory, ParameterSetName = 'ClearText')] [parameter(Mandatory)][string] $ClientSecret, [Parameter(Mandatory, ParameterSetName = 'Encrypted')] [alias('ApplicationSecretEncrypted', 'ApplicationKeyEncrypted')] [string] $ClientSecretEncrypted, [parameter(Mandatory)][string] $Scopes, [parameter(Mandatory)][string] $Organization, [switch] $ExistingToken, [switch] $DoNotSuppress, [switch] $Force ) # Check for curent token $CurrentTime = (Get-Date).AddSeconds(2) if ($ClientSecretEncrypted) { try { $ApplicationKeyTemp = $ClientSecretEncrypted | ConvertTo-SecureString -ErrorAction Stop } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw } else { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Connect-Adobe - Error: $ErrorMessage" return } } $ApplicationKey = [System.Net.NetworkCredential]::new([string]::Empty, $ApplicationKeyTemp).Password } else { $ApplicationKey = $ClientSecret } if ($Script:AdobeTokenInformation.Expires -lt $CurrentTime -or $Force) { if ($ExistingToken) { Write-Verbose -Message "Connect-Adobe - Using existing token within command" $Url = $Script:AdobeTokenInformation.Url $Headers = $Script:AdobeTokenInformation.Headers $Body = $Script:AdobeTokenInformation.Body } else { $Headers = [ordered] @{ 'Content-Type' = 'application/x-www-form-urlencoded' } $Body = [ordered] @{ 'client_id' = $ClientID 'client_secret' = $ApplicationKey 'grant_type' = 'client_credentials' 'scope' = $Scopes } $Url = 'https://ims-na1.adobelogin.com/ims/token/v3' $Script:AdobeTokenInformation = [ordered] @{ Organization = $Organization Url = $Url Headers = $Headers Token = $null Expires = $null Body = $Body } } try { $Response = Invoke-RestMethod -Method Post -Uri $Url -Headers $Headers -Body $Body -ErrorAction Stop -Verbose:$false } catch { Write-Warning -Message "Connect-Adobe - Unable to connect to organization '$Organization' with user '$UserName'. Error $($_.Exception.Message)" return } $Script:AdobeTokenInformation.Token = $Response.access_token $Script:AdobeTokenInformation.Expires = [DateTime]::Now + ([TimeSpan]::FromSeconds($Response.expires_in)) $Script:AdobeTokenInformation.Headers = @{ #'Content-Type' = 'application/x-www-form-urlencoded' 'Authorization' = "Bearer $($Script:AdobeTokenInformation.Token)" "X-Api-Key" = "$ClientID" 'Content-type' = 'application/json' } if ($DoNotSuppress) { $Script:AdobeTokenInformation } } else { $WhenExpires = $Script:AdobeTokenInformation.expires - $CurrentTime Write-Verbose -Message "Connect-Adobe - Using existing cached token (Expires in: $WhenExpires)" if ($DoNotSuppress -and -not $ExistingToken) { $Script:AdobeTokenInformation } } } function Get-AdobeGroup { <# .SYNOPSIS Retrieves Adobe groups. .DESCRIPTION The Get-AdobeGroup cmdlet lists all user groups within the Adobe system. It requires an active Adobe connection. .EXAMPLE Get-AdobeGroup #> [CmdletBinding()] param( ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Get-AdobeGroup - You need to connect to Adobe first using Connect-Adobe' return } Invoke-AdobeQuery -Url "groups/{orgId}/{page}" -Method 'GET' } function Get-AdobeGroupMember { <# .SYNOPSIS Retrieves members of a specified Adobe group. .DESCRIPTION The Get-AdobeGroupMember cmdlet fetches all members belonging to a specific group within the Adobe system. Requires an active Adobe connection. .PARAMETER GroupName The name of the group to retrieve members for. .EXAMPLE Get-AdobeGroupMember -GroupName "Admins" #> [CmdletBinding()] param( [parameter(Mandatory)][string] $GroupName ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Get-AdobeGroup - You need to connect to Adobe first using Connect-Adobe' return } Invoke-AdobeQuery -Url "users/{orgId}/{page}/$GroupName" -Method 'GET' } function Get-AdobeUser { <# .SYNOPSIS Retrieves Adobe user information. .DESCRIPTION The Get-AdobeUser cmdlet fetches details of a specific Adobe user or lists all users if no email is provided. Requires an active Adobe connection. .PARAMETER Email The email address of the user to retrieve information for. .EXAMPLE Get-AdobeUser -Email "jane.doe@example.com" .EXAMPLE Get-AdobeUser #> [CmdletBinding()] param( [string] $Email ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Get-AdobeUser - You need to connect to Adobe first using Connect-Adobe' return } if ($Email) { #Get user information : GET /v2/usermanagement/organizations/{orgId}/users/{userString} Invoke-AdobeQuery -Url "organizations/{orgId}/users/$Email" -Method 'GET' } else { #List all users : GET /v2/usermanagement/users/{orgId}/{page} Invoke-AdobeQuery -Url "users/{orgId}/{page}" -Method 'GET' } } function Invoke-AdobeBulk { <# .SYNOPSIS Executes bulk operations on Adobe users. .DESCRIPTION The Invoke-AdobeBulk cmdlet performs multiple Adobe user operations in a single request. It processes actions defined in a script block in batches of 20 to comply with Adobe's API limitations, ensures actions are executed in order, and aggregates the results to provide a single consolidated output at the end. .PARAMETER Actions A script block containing the bulk actions to execute. Each action should be a PowerShell cmdlet that modifies Adobe user or group information. .PARAMETER Suppress Suppresses the output of errors. When this switch is used, any errors encountered during the execution of bulk actions will not be displayed. .EXAMPLE # Execute bulk operations to add and update Adobe users Invoke-AdobeBulk { Add-AdobeUser -EmailAddress "john.doe@example.com" -Country "US" -FirstName "John" -LastName "Doe" -Type "createFederatedID" -BulkProcessing Set-AdobeUser -EmailAddress "john.doe@example.com" -LastName "Doe-Smith" -BulkProcessing } .EXAMPLE # Execute bulk operations and suppress error output Invoke-AdobeBulk { Add-AdobeUser -EmailAddress "jane.doe@example.com" -Country "UK" -FirstName "Jane" -LastName "Doe" -Type "createEnterpriseID" -BulkProcessing Set-AdobeUser -EmailAddress "jane.doe@example.com" -LastName "Doe-Smith" -BulkProcessing } -Suppress .NOTES - Ensure that you have connected to Adobe using `Connect-Adobe` before executing bulk operations. - The cmdlet processes actions in the order they are provided and aggregates the results to provide a comprehensive status report at the end of execution. - Adobe API limits bulk requests to 20 actions per batch. This cmdlet automatically handles batching to adhere to this limitation. - The aggregated output includes counts of completed, not completed, and completed in test mode actions, as well as any errors encountered during execution. #> [CmdletBinding(SupportsShouldProcess)] param( [scriptblock] $Actions, [switch] $Suppress ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Invoke-AdobeBulk - You need to connect to Adobe first using Connect-Adobe' return } $AllActions = & $Actions [Array] $ActionsToExecute = foreach ($Action in $AllActions) { $Action } # Initialize aggregation variables $aggregatedResult = [ordered] @{ completed = 0 notCompleted = 0 completedInTestMode = 0 result = 'success' errors = @() } $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess("Updates", 'Do bulk updates')) { $false } else { $true } } # Process actions in batches of 20 for ($i = 0; $i -lt $ActionsToExecute.Count; $i += 20) { $Batch = $ActionsToExecute[$i..([math]::Min($i + 19, $ActionsToExecute.Count - 1))] $Batch | ConvertTo-Json -Depth 5 | Write-Verbose $batchOutput = Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Batch -QueryParameter $QueryParameter foreach ($ErrorMessage in $batchOutput.Errors) { Write-Warning -Message "Invoke-AdobeBulk - Processing error [user: $($ErrorMessage.User), index: $($ErrorMessage.Index)] - $($ErrorMessage.Message)" } # Aggregate results $aggregatedResult.completed += $batchOutput.completed $aggregatedResult.notCompleted += $batchOutput.notCompleted $aggregatedResult.completedInTestMode += $batchOutput.completedInTestMode if ($batchOutput.errors) { $aggregatedResult.errors += $batchOutput.errors #$aggregatedResult.failed += $batchOutput.errors.Count } # Update overall result status if ($batchOutput.result -eq 'failure') { $aggregatedResult.result = 'failure' } elseif ($batchOutput.result -ne 'success' -and $aggregatedResult.result -ne 'failure') { $aggregatedResult.result = 'partial' } } # Determine overall result based on aggregated data if ($aggregatedResult.failed -eq $ActionsToExecute.Count) { $aggregatedResult.result = 'failure' } elseif ($aggregatedResult.notCompleted -gt 0) { $aggregatedResult.result = 'partial' } else { $aggregatedResult.result = 'success' } if (-not $Suppress) { # Output the aggregated results $aggregatedResult } } function New-AdobeGroup { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string] $Name, [Parameter()][string] $Description, [ValidateSet('ignoreIfAlreadyExists', 'updateIfAlreadyExists')] [string] $Option = 'ignoreIfAlreadyExists', [switch] $BulkProcessing ) $OptionList = @{ ignoreIfAlreadyExists = 'ignoreIfAlreadyExists' updateIfAlreadyExists = 'updateIfAlreadyExists' } $OptionConverted = $OptionList[$Option] if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Get-AdobeUser - You need to connect to Adobe first using Connect-Adobe' return } $Data = [ordered] @{ usergroup = $Name requestID = "action_$(Get-Random)" do = @( [ordered] @{ 'createUserGroup' = [ordered] @{ name = $Name description = $Description option = $OptionConverted } } ) } Remove-EmptyValue -Hashtable $Data -Recursive -Rerun 2 if ($BulkProcessing) { return $Data } $Data | ConvertTo-Json -Depth 5 | Write-Verbose $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess($Name, 'Create Adobe Group')) { $false } else { $true } } Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Data -QueryParameter $QueryParameter } function New-AdobeUser { <# .SYNOPSIS Creates a new Adobe user. .DESCRIPTION The New-AdobeUser cmdlet adds a new user to the Adobe system with specified details. It supports bulk processing and different user types. .PARAMETER EmailAddress The email address of the new user. .PARAMETER Country The country of the new user. .PARAMETER FirstName The first name of the new user. .PARAMETER LastName The last name of the new user. .PARAMETER Option Determines how to handle existing users. Options are 'ignoreIfAlreadyExists' or 'updateIfAlreadyExists'. .PARAMETER Type Specifies the type of Adobe ID to create. Valid values are 'createEnterpriseID', 'addAdobeID', or 'createFederatedID'. .PARAMETER BulkProcessing Switch to enable bulk processing mode. .EXAMPLE New-AdobeUser -EmailAddress "jane.doe@example.com" -Country "US" -FirstName "Jane" -LastName "Doe" -Type "createFederatedID" #> [Alias('Add-AdobeUser')] [cmdletbinding(SupportsShouldProcess)] param( [string] $EmailAddress, [string] $Country, [string] $FirstName, [string] $LastName, [ValidateSet('ignoreIfAlreadyExists', 'updateIfAlreadyExists')] [string] $Option = 'ignoreIfAlreadyExists', [Parameter(Mandatory)] [ValidateSet('createEnterpriseID', 'addAdobeID', 'createFederatedID')] [string] $Type, [switch] $BulkProcessing ) $List = @{ createEnterpriseID = 'createEnterpriseID' addAdobeID = 'addAdobeID' createFederatedID = 'createFederatedID' } $OptionList = @{ ignoreIfAlreadyExists = 'ignoreIfAlreadyExists' updateIfAlreadyExists = 'updateIfAlreadyExists' } $OptionConverted = $OptionList[$Option] if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'New-AdobeUser - You need to connect to Adobe first using Connect-Adobe' return } # we need to convert the type to the correct format so it preservers problem casing $ConvertedType = $List[$Type] $Data = [ordered] @{ user = $EmailAddress requestID = "action_$(Get-Random)" do = @( [ordered] @{ $ConvertedType = [ordered] @{ email = $EmailAddress country = $Country firstname = $FirstName lastname = $LastName option = $OptionConverted } } ) } if ($BulkProcessing) { return $Data } $Data | ConvertTo-Json -Depth 5 | Write-Verbose $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess($EmailAddress, 'Add Adobe User')) { $false } else { $true } } Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Data -QueryParameter $QueryParameter } function Remove-AdobeGroup { <# .SYNOPSIS Removes an Adobe user group. .DESCRIPTION The Remove-AdobeGroup cmdlet deletes a specified user group from the Adobe system. It requires an active Adobe connection. .PARAMETER Name The name of the Adobe group to remove. .PARAMETER BulkProcessing Switch to enable bulk processing mode. .EXAMPLE Remove-AdobeGroup -Name "MarketingTeam" #> [CmdletBinding(SupportsShouldProcess)] param( [alias('GroupName')][Parameter(Mandatory)][string] $Name, [switch] $BulkProcessing ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Remove-AdobeGroup - You need to connect to Adobe first using Connect-Adobe' return } $Data = [ordered] @{ usergroup = $Name requestID = "action_$(Get-Random)" do = @( [ordered] @{ 'deleteUserGroup' = [ordered] @{ } } ) } Remove-EmptyValue -Hashtable $Data -Recursive -Rerun 2 if ($BulkProcessing) { return $Data } $Data | ConvertTo-Json -Depth 5 | Write-Verbose $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess($Name, 'Remove Adobe Group Member')) { $false } else { $true } } Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Data -QueryParameter $QueryParameter } function Remove-AdobeGroupMember { <# .SYNOPSIS Removes a member from one or more Adobe groups. .DESCRIPTION The Remove-AdobeGroupMember cmdlet removes a specified user from one or more Adobe groups. Use the -All switch to remove the user from all groups. .PARAMETER GroupName The name(s) of the Adobe group(s) from which the user will be removed. .PARAMETER Email The email address of the user to be removed from the group(s). .PARAMETER All Removes the user from all Adobe groups. .EXAMPLE Remove-AdobeGroupMember -GroupName "Marketing" -Email "user@example.com" .EXAMPLE Remove-AdobeGroupMember -All -Email "user@example.com" #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string[]] $GroupName, [Parameter(Mandatory)][string] $Email, [switch] $All ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Remove-AdobeGroupMember - You need to connect to Adobe first using Connect-Adobe' return } if ($GroupName.Count -gt 10) { Write-Warning -Message 'Remove-AdobeGroupMember - Only ten groups can be added at a time' return } if ($All) { $Data = [ordered] @{ user = $Email requestID = "action_$(Get-Random)" do = @( @{ 'remove' = 'all' } ) } } else { $Data = [ordered] @{ user = $Email requestID = "action_$(Get-Random)" do = @( @{ 'remove' = @{ 'group' = @( $GroupName ) } } ) } } Remove-EmptyValue -Hashtable $Data -Recursive -Rerun 2 $Data | ConvertTo-Json -Depth 5 | Write-Verbose $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess($GroupName, 'Remove Adobe Group Member')) { $false } else { $true } } Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Data -QueryParameter $QueryParameter } function Remove-AdobeUser { <# .SYNOPSIS Removes an Adobe user. .DESCRIPTION The Remove-AdobeUser cmdlet deletes a specified user from the Adobe system. Optionally, it can also delete the user's account. .PARAMETER EmailAddress The email address of the Adobe user to remove. .PARAMETER DoNotDeleteAccount When specified, the user's account will not be deleted, only their association with groups. .PARAMETER BulkProcessing Switch to enable bulk processing mode. .EXAMPLE Remove-AdobeUser -EmailAddress "jane.doe@example.com" -DoNotDeleteAccount #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string] $EmailAddress, [switch] $DoNotDeleteAccount, [switch] $BulkProcessing ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Remove-AdobeUser - You need to connect to Adobe first using Connect-Adobe' return } $Data = [ordered] @{ user = $EmailAddress requestID = "action_$(Get-Random)" do = @( [ordered] @{ 'removeFromOrg' = [ordered] @{ deleteAccount = -not $DoNotDeleteAccount.IsPresent } } ) } Remove-EmptyValue -Hashtable $Data -Recursive -Rerun 2 if ($BulkProcessing) { return $Data } $Data | ConvertTo-Json -Depth 5 | Write-Verbose $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess($EmailAddress, 'Remove Adobe User')) { $false } else { $true } } Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Data -QueryParameter $QueryParameter } function Set-AdobeGroup { <# .SYNOPSIS Updates an Adobe user group. .DESCRIPTION The Set-AdobeGroup cmdlet updates the name and/or description of an existing Adobe user group. .PARAMETER Name The current name of the Adobe group to update. .PARAMETER NewName The new name for the Adobe group. .PARAMETER Description The new description for the Adobe group. .PARAMETER BulkProcessing Switch to enable bulk processing mode. .EXAMPLE Set-AdobeGroup -Name "Developers" -NewName "Senior Developers" -Description "Group for senior developer roles" #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string] $Name, [Parameter()][string] $NewName, [Parameter()][string] $Description, [switch] $BulkProcessing ) if (-not $Script:AdobeTokenInformation) { Write-Warning -Message 'Set-AdobeGroup - You need to connect to Adobe first using Connect-Adobe' return } if (-not $NewName -and -not $Description) { Write-Warning -Message 'Set-AdobeGroup - You need to provide either a new name or a description' return } $Data = [ordered] @{ usergroup = $Name requestID = "action_$(Get-Random)" do = @( [ordered] @{ 'updateUserGroup' = [ordered] @{ name = $NewName description = $Description } } ) } Remove-EmptyValue -Hashtable $Data -Recursive -Rerun 2 if ($BulkProcessing) { return $Data } $Data | ConvertTo-Json -Depth 5 | Write-Verbose $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess($Name, 'Set Adobe Group')) { $false } else { $true } } Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Data -QueryParameter $QueryParameter } function Set-AdobeUser { <# .SYNOPSIS Update Adobe User information using Adobe API .DESCRIPTION Update Adobe User information using Adobe API. Applies only to Enterprise and Federated users. Independent Adobe IDs are managed by the individual user and cannot be updated through the User Management API. Attempting to update information for a user who has an Adobe ID will result in an error. .PARAMETER EmailAddress The current email address of the Adobe user to update. .PARAMETER NewEmailAddress The new email address for the Adobe user. .PARAMETER Country The country of the Adobe user. .PARAMETER FirstName The first name of the Adobe user. .PARAMETER LastName The last name of the Adobe user. .PARAMETER UserName The username of the Adobe user. .PARAMETER BulkProcessing Switch to enable bulk processing mode. .EXAMPLE Set-AdobeUser -EmailAddress 'przemek@test.pl' -NewEmailAddress 'przemek@test1.pl' -LastName 'Klys' -WhatIf -Verbose $SetInformation .NOTES General notes #> [cmdletbinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string] $EmailAddress, [string] $NewEmailAddress, [string] $Country, [string] $FirstName, [string] $LastName, [string] $UserName, [switch] $BulkProcessing ) if (-not $Script:AdobeTokenInformation) { # Ensure Adobe is connected before proceeding Write-Warning -Message 'Set-AdobeUser - You need to connect to Adobe first using Connect-Adobe' return } $UpdateObject = [ordered] @{ "update" = [ordered] @{ email = $NewEmailAddress country = $Country firstname = $FirstName lastname = $LastName username = $UserName } } # Remove any empty values from the update object Remove-EmptyValue -Hashtable $UpdateObject -Recursive -Rerun 2 if (-not $UpdateObject) { # Warn if no update values are provided Write-Warning -Message 'Set-AdobeUser - You need to provide at least one value to update' return } $Data = [ordered] @{ user = $EmailAddress requestID = "action_$(Get-Random)" do = @( $UpdateObject ) } if ($BulkProcessing) { # Return data for bulk processing return $Data } $Data | ConvertTo-Json -Depth 5 | Write-Verbose $QueryParameter = [ordered] @{ testOnly = if ($PSCmdlet.ShouldProcess($EmailAddress, 'Update Adobe User')) { $false } else { $true } } # Invoke the Adobe API with the prepared data Invoke-AdobeQuery -Url "action" -Method 'POST' -Data $Data -QueryParameter $QueryParameter } if ($PSVersionTable.PSEdition -eq 'Desktop' -and (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release -lt 379893) { Write-Warning "This module requires .NET Framework 4.5.2 or later."; return } # Export functions and aliases as required Export-ModuleMember -Function @('Add-AdobeGroupMember', 'Connect-Adobe', 'Get-AdobeGroup', 'Get-AdobeGroupMember', 'Get-AdobeUser', 'Invoke-AdobeBulk', 'New-AdobeGroup', 'New-AdobeUser', 'Remove-AdobeGroup', 'Remove-AdobeGroupMember', 'Remove-AdobeUser', 'Set-AdobeGroup', 'Set-AdobeUser') -Alias @('Add-AdobeUser') # SIG # Begin signature block # MIItqwYJKoZIhvcNAQcCoIItnDCCLZgCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCytbyNQI3bKaBx # oN1yyDk3hPrYZA6K9dvVGyPUMBS2X6CCJq4wggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ # CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw # MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k # jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9 # NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9 # URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY # E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS # 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa # wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w # c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR # Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2 # 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK # ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC # AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2 # O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH # BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6 # mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/ # SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY # gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9 # kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ # 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew # Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm # Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA # SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr # y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR # ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu # v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnp # BOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5 # NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEy # NTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYD # VQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQAD # ggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBD # Er4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo # 76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rO # H3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9R # eNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgX # j3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTV # DSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16J # idj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/C # acBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93N # Rxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1X # CB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMB # AAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUB # Af8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s # BwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9X # LAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwz # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1l # U3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhho # dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNl # cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZU # aW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQS # R9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWB # b0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDC # zFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1 # UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3Wp # ByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGE # sshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8 # a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNF # YagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7Q # EY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgE # deoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/J # ceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHXzCCBUegAwIBAgIQB8JSdCgUotar/iTq # F+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAwMDAwMFoX # DTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1pa2/FgsOz # dzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYDVQQDDBhQ # cnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmVOrRBVRQA # 8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVEh0C/Daeh # vxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNdGVXRYOLn # 47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0235CN4Rr # W+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuAo3+jVB8w # iUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw8/FNzGNP # lAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP0ib98XLf # QpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxiW4oHYO28 # eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFKRqwvSSr4 # fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKABGoIqSW0 # 5nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQIDAQABo4IC # AzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE # FHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK # BggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz # ODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j # cmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3 # dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu # Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB # CwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50ZHzoWs6E # BlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa1W47YSrc # 5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2CbE3JroJ # f2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0djvQSx51 # 0MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N9E8hUVev # xALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgizpwBasrx # h6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38wwtaJ3KY # D0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Yn8kQMB6/ # Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Zn3exUAKq # G+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe6nB6bSYH # v8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGCBlMwggZP # AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw # PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2 # IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgBZQMEAgEF # AKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgor # BgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3 # DQEJBDEiBCA6X5qRzQSykYggEW82m29AFHlGVNq402WRnCt3QjeTBzANBgkqhkiG # 9w0BAQEFAASCAgAgVPF846XoCxs8dDwpGkXgGRv3BQJuC4fI5wq8DwUfq7EKYuFB # QcuMGg6hHykI/irLU3aFw6jzr8VjpdUTvM4h+8JVJ/JYyhCbjt+4bXmlWq0WqUu8 # wD2o7n7lpBWSJDM7HOx3Bq2SYkbtkHaR8vfeUFOGOCS2kxiXKCwyT8xm8DX6CM0c # r9+ARdQiIabCVUFyPm9E58GgkvPKt32hMfxOgrSGg05KGO/qB2GrYBL5f3fJUykh # nIplN+HgJpWWvpXZFqg19Ogn672ws5amKRqTSp4f646uT0wTiYozqu2hnU9qvx03 # G0OeNHVBl77QXffrrhtg/riHG7tA5gZbSGKwjnvflBRTxHMWOnCBfeGcjLy3azH/ # yFJVxNMpwP2gU5fwaOXnJgNwZilRCEN8D3u3ibA2DX3BC6fiEb7XzcPoJGnAIFVt # vQGEl3XXdj86b5dokne7AQHaRVW+4ZU0f064/HyAez9eySmAgwuuiOmMXn/CQpmj # eKMzzyISnDLhgLeTJpkUM4BgADRBEDikfZCxBDTRvBrEjvEvO+DXwY90KI1d9tWh # AVcOO1hIyAif9raSIVWws1p6k20bCyUPqT7v5AGuOSUE7GlhVrEHeJmS+k9Gy4Tw # ABuAzddEGgX42e4gYh+wtK7xAgHpy7uOiM999MZgC7JHV59X1OLG5I28IqGCAyAw # ggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYD # VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH # NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTj # MwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG # CSqGSIb3DQEJBTEPFw0yNDEyMTMwNzQzMzBaMC8GCSqGSIb3DQEJBDEiBCDn9Orb # iz4ML8XDgOHwqYEJsHqpzaDC6i/1Kywrjv+DLTANBgkqhkiG9w0BAQEFAASCAgBc # sTF6WwIP5h1Jt2NLSbMC5p1eYyGpnkCdg9WksZrJy0NzcPh/CspfsFZT6svD2UOZ # hNmyIO7lbfIioAwGvLLrLBJy8ZdEFmAa+RzPRAVVFARGXYpb16P8pLVp0ywdF2xi # 46PVpuhJbBE09+vGKo09m0J5mRdcoOcVeO8HvrQIfLa2OtvqyzwF9vnYhct4LsAq # WF9VJ2RqkJin6qZxkojuLw8l2/9cdKP98gldfDS9MTpMSMQuMwoWM5ZPiKCPxxNf # VQRY6eZOnRjqoOEH1X0SupdcoL394iWbukILYNSAYOUACPUKaxaJ7hDyTTx//jsk # +MkWPxaQD6SjXjO/HzRH4zR9Y2BdCEDhzfTf13sLkLCvXzBo/P3FFq2mOP7jRZ68 # vQZa5qVw/IvtxvGhAvx4/qvqxg/jp31gqd0ro42v78zFU1G+muCHdS7SBE94W+KB # GWntaqNdsDHgtklLo+mXGR8AuT1XTs2sL6/tmevF1o0FOjBoIEVaaxvsAdT0N7/m # avYb9KAM23kFpRyhwHk106eH87SJyCdAacbcGp8KF9J9bY5iH79PDRStFO7YeBRw # TVhZqir8QAW7R/EDAPSI5PCauhk67uDazUuTOXr6PQtosmSdxUIXVENUnPhurxP8 # rWVfkoYaguT1dAzjMzQ05EYy1CaWoUsFDaHgyHQEaw== # SIG # End signature block |