I am using a custom function that imports data using an API. The function requires several different parameters, which I am currently defining with one $Params hashtable. From there I am adding other parameters that may or may not be added based on certain conditions. I have noticed that storing all parameters in the hashtable is not working properly, but will only work if particularly specified in the function call.
A basic version of the code that does not currently work is:
$ExtraParam = 'ccc'
$Params = @{
Par1 = 'aaa'
Par2 = 'bbb'
}
$Params.Par3 = $ExtraParam
Import-APIData @Params
The API is returning the following error message:
{"errorId":"error.request.invalidRepresentation.malformed","errorCode":0,"message":"Invalid parameter value was specified."}
However, if I run the following code it works perfectly fine:
$ExtraParam = 'abc'
$Params = @{
Par1 = '123'
Par2 = '456'
}
Import-APIData @Params -Par3 $ExtraParam
I have verified that the parameters coming into the function are all of the correct type. The custom function I am calling is proprietary so I cannot share the entire function, but I was just wondering if there was anything that would reformat the parameters in the hashtable as opposed to explicitly calling it in the function call. Given that the two chunks of code are essentially the same I am hoping someone can point out some fundamental difference that may cause the top portion to not work.
Per a request of a commenter, here is the actual function. The _CallAPI function at the bottom is what actually calls the API. I will not be able to share that, but using the $Params
output right above it (commented out in the code) may hopefully be enough to troubleshoot.
function Update-ICUser {
<#
.SYNOPSIS
Updates user on a Gensys ICWS server.
.PARAMETER ICSession
string ICSession. ICSession to update a user.
.PARAMETER Id
string Id. Id of the user to update.
.PARAMETER InputObject
object InputObject. Full IC User is needed.
.PARAMETER Argument
string Argument. Any query string parameters associated with the Api call.
.Example
$ICSession = New-ICSession -ComputerName server1 -Credential $Credential
Get-ICUser -Id user1 -Full |Update-ICUser -ICSession $ICsession -Extension 1234
Example 1: Update a user's extension.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory,
Position = 0)]
[ICSession]$ICSession,
[Parameter(Mandatory,
ValueFromPipeline,
ParameterSetName = 'InputObject',
Position = 1)]
[object]$InputObject,
[Parameter(Mandatory,
ParameterSetName = 'Manual',
Position = 2)]
[string]$Id,
[Parameter(ParameterSetName = 'Manual',
Position = 3)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$DisplayName,
[Parameter(ParameterSetName = 'Manual',
Position = 4)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$OutboundAni,
[Parameter(ParameterSetName = 'Manual',
Position = 5)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$Title,
[Parameter(ParameterSetName = 'Manual',
Position = 6)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$OfficeLocation,
[Parameter(ParameterSetName = 'Manual',
Position = 7)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$Notes,
[Parameter(ParameterSetName = 'Manual',
Position = 8)]
[Parameter(ParameterSetName = 'InputObject')]
[int]$Cost = 0,
[Parameter(ParameterSetName = 'Manual',
Position = 9)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$AutoAnswerAcdInteractions = $false,
[Parameter(ParameterSetName = 'Manual',
Position = 10)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$AutoAnswerNonAcdInteractions = $true,
[Parameter(ParameterSetName = 'Manual',
Position = 11)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$ApplicationId = 'InteractionDesktop',
[Parameter(ParameterSetName = 'Manual',
Position = 12)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$ExcludeFromDirectory = $false,
[Parameter(ParameterSetName = 'Manual',
Position = 13)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$Extension,
[Parameter(ParameterSetName = 'Manual',
Position = 14)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$FaxCapability = $false,
[Parameter(ParameterSetName = 'Manual',
Position = 15)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$OutlookIntegrationEnabled = $false,
[Parameter(ParameterSetName = 'Manual',
Position = 16)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$FirstName,
[Parameter(ParameterSetName = 'Manual',
Position = 17)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$LastName,
[Parameter(ParameterSetName = 'Manual',
Position = 18)]
[Parameter(ParameterSetName = 'InputObject')]
[string[]]$WorkgroupList,
[Parameter(ParameterSetName = 'Manual',
Position = 19)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$DepartmentName,
[Parameter(ParameterSetName = 'Manual',
Position = 20)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$City,
[Parameter(ParameterSetName = 'Manual',
Position = 21)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$PostalCode,
[Parameter(ParameterSetName = 'Manual',
Position = 22)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$State,
[Parameter(ParameterSetName = 'Manual',
Position = 23)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$StreetAddress,
[Parameter(ParameterSetName = 'Manual',
Position = 24)]
[Parameter(ParameterSetName = 'InputObject')]
[int]$HomeSite,
[Parameter(ParameterSetName = 'Manual',
Position = 25)]
[Parameter(ParameterSetName = 'InputObject')]
[string[]]$LicenseList,
[Parameter(ParameterSetName = 'Manual',
Position = 26)]
[Parameter(ParameterSetName = 'InputObject')]
[string[]]$RoleList
)
Try {
if ($InputObject) {
$User = $InputObject
$Id = $InputObject.configurationId.id
}
else {
$User = Get-ICUser -ICSession $ICSession -Id $Id -Full
}
'Id','InputObject','ICSession' |Foreach-Object {
$PSBoundParameters.Remove($PSItem) |Out-Null
}
$PSBoundParameters.GetEnumerator() |Foreach-Object {
if ($PSItem.Key -in ('Title','DepartmentName','City','State','StreetAddress','FirstName','LastName','Notes')) {
if ($PSItem.Key -in ('State','FirstName','LastName')) {
$Key = switch ($PSItem.Key) {
'State' {'stateOrProvince'}
'FirstName' {'givenName'}
'LastName' {'surName'}
}
}
else {
$Key = $PSItem.Key
}
$User.personalInformationProperties.$Key = $PSItem.Value
}
elseif ($PSItem.Key -in ('WorkgroupList','LicenseList','RoleList')) {
$Key = $PSItem.Key
if ($Key = 'WorkgroupList' -and $WorkgroupList) {
[array]$Value = Get-ICWorkgroup -ICSession $SessionKey -Id $WorkgroupList
$User.Workgroups = $Value
}
if ($Key = 'LicenseList' -and $LicenseList) {
$Value = @((Get-ICLicenseAllocation -ICSession $SessionKey -Id $LicenseList).configurationId)
if ($User.licenseProperties.PSobject.Properties.name -match 'additionalLicenses') {
$User.licenseProperties.additionalLicenses = $Value
}
else {
$User.licenseProperties | Add-Member -MemberType NoteProperty -Name 'additionalLicenses' -Value $Value -Force
}
}
if ($Key = 'RoleList' -and $RoleList) {
[array]$Value = Get-ICRole -ICSession $SessionKey -Id $RoleList
if ($User.roles.PSobject.Properties.name -match 'actualValue') {
$User.Roles.actualValue = $Value
}
else {
$User.roles | Add-Member -MemberType NoteProperty -Name 'actualValue' -Value $Value -Force
}
}
}
else {
$Key = $PSItem.Key
$Value = $PSItem.Value
$User |Add-Member -MemberType NoteProperty -Name $Key -Value $Value -Force
}
}
$Body = $User |ConvertTo-Json -Depth 5
$Params = @{
Area = $Configuration.Area.Configuration
Resource = $Configuration.Resource.Users
ICSession = $ICSession
Id = $Id
Method = 'Put'
ContentType = 'application/json'
Body = $Body
}
# $Params
_CallApi @Params
}
Catch {
_ExceptionError
}
}
I will also provide everything on the console. In this example the $AutoAnswerAcd
parameter is the one that is failing, but I have gotten it to fail and work by adding/removing other parameters. I have not detected a pattern that makes it fail yet, other than that it always works outside of the splat. You can see this pattern below.
PS C:\> $SessionKey = New-ICSession -ComputerName $server -Credential $Credential
PS C:\> $Params = @{
>> ICSession = $SessionKey
>> Id = 'tautomation'
>> WorkgroupList = "MultiSite-UserSpan"
>> RoleList = "Business User"
>> LicenseList = "I3_ACCESS_RECORDER"
>> DepartmentName = "Infr & Ops"
>> Title = "Process Automation Engineer"
>> OfficeLocation = "Remote"
>> AutoAnswerAcdInteractions = $true
>> }
PS C:\> Update-ICUser @Params
Exception: C:\Repos\ICTools\Code\Private\_ExceptionError.ps1:10
Line |
10 | throw "$($Exception)`n$($Exception.Exception.Message)"
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| {"errorId":"error.request.invalidRepresentation.malformed","errorCode":0,"message":"Invalid parameter value was specified."} Response status code does not indicate
| success: 400 (Bad Request). {"errorId":"error.request.invalidRepresentation.malformed","errorCode":0,"message":"Invalid parameter value was specified."} Response status
| code does not indicate success: 400 (Bad Request).
PS C:\> $Params = @{
>> ICSession = $SessionKey
>> Id = 'tautomation'
>> WorkgroupList = "MultiSite-UserSpan"
>> RoleList = "Business User"
>> LicenseList = "I3_ACCESS_RECORDER"
>> DepartmentName = "Infr & Ops"
>> Title = "Process Automation Engineer"
>> OfficeLocation = "Remote"
>> }
PS C:\> Update-ICUser @Params
id uri
-- ---
tautomation /configuration/users/tautomation
PS C:\> Update-ICUser @Params -AutoAnswerAcdInteractions $true
id uri
-- ---
tautomation /configuration/users/tautomation
PS C:\>
Thanks!
CodePudding user response:
Ok, so think I've found what's happening.
Firstly, here's an instrumented version of your code that gives some log output as it runs - that'll help see where it's going wrong...
function Update-ICUser {
[CmdletBinding()]
param (
[Parameter(Mandatory,
Position = 0)]
[string]$ICSession,
[Parameter(Mandatory,
ValueFromPipeline,
ParameterSetName = 'InputObject',
Position = 1)]
[object]$InputObject,
[Parameter(Mandatory,
ParameterSetName = 'Manual',
Position = 2)]
[string]$Id,
[Parameter(ParameterSetName = 'Manual',
Position = 3)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$DisplayName,
[Parameter(ParameterSetName = 'Manual',
Position = 4)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$OutboundAni,
[Parameter(ParameterSetName = 'Manual',
Position = 5)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$Title,
[Parameter(ParameterSetName = 'Manual',
Position = 6)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$OfficeLocation,
[Parameter(ParameterSetName = 'Manual',
Position = 7)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$Notes,
[Parameter(ParameterSetName = 'Manual',
Position = 8)]
[Parameter(ParameterSetName = 'InputObject')]
[int]$Cost = 0,
[Parameter(ParameterSetName = 'Manual',
Position = 9)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$AutoAnswerAcdInteractions = $false,
[Parameter(ParameterSetName = 'Manual',
Position = 10)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$AutoAnswerNonAcdInteractions = $true,
[Parameter(ParameterSetName = 'Manual',
Position = 11)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$ApplicationId = 'InteractionDesktop',
[Parameter(ParameterSetName = 'Manual',
Position = 12)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$ExcludeFromDirectory = $false,
[Parameter(ParameterSetName = 'Manual',
Position = 13)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$Extension,
[Parameter(ParameterSetName = 'Manual',
Position = 14)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$FaxCapability = $false,
[Parameter(ParameterSetName = 'Manual',
Position = 15)]
[Parameter(ParameterSetName = 'InputObject')]
[bool]$OutlookIntegrationEnabled = $false,
[Parameter(ParameterSetName = 'Manual',
Position = 16)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$FirstName,
[Parameter(ParameterSetName = 'Manual',
Position = 17)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$LastName,
[Parameter(ParameterSetName = 'Manual',
Position = 18)]
[Parameter(ParameterSetName = 'InputObject')]
[string[]]$WorkgroupList,
[Parameter(ParameterSetName = 'Manual',
Position = 19)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$DepartmentName,
[Parameter(ParameterSetName = 'Manual',
Position = 20)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$City,
[Parameter(ParameterSetName = 'Manual',
Position = 21)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$PostalCode,
[Parameter(ParameterSetName = 'Manual',
Position = 22)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$State,
[Parameter(ParameterSetName = 'Manual',
Position = 23)]
[Parameter(ParameterSetName = 'InputObject')]
[string]$StreetAddress,
[Parameter(ParameterSetName = 'Manual',
Position = 24)]
[Parameter(ParameterSetName = 'InputObject')]
[int]$HomeSite,
[Parameter(ParameterSetName = 'Manual',
Position = 25)]
[Parameter(ParameterSetName = 'InputObject')]
[string[]]$LicenseList,
[Parameter(ParameterSetName = 'Manual',
Position = 26)]
[Parameter(ParameterSetName = 'InputObject')]
[string[]]$RoleList
)
if ($InputObject) {
$User = $InputObject
$Id = $InputObject.configurationId.id
}
else {
$User = [pscustomobject] @{
personalInformationProperties = [pscustomobject] @{
DepartmentName = $null
Title = $null
}
licenseProperties = [pscustomobject] @{}
roles = [pscustomobject] @{}
Workgroups = $null
}
#Get-ICUser -ICSession $ICSession -Id $Id -Full
}
#write-host "user = '$($User |ConvertTo-Json -Depth 5)'"
'Id','InputObject','ICSession' |Foreach-Object {
$PSBoundParameters.Remove($PSItem) |Out-Null
}
$PSBoundParameters.GetEnumerator() |Foreach-Object {
write-host "processing key '$($PSItem.Key)'"
if ($PSItem.Key -in ('Title','DepartmentName','City','State','StreetAddress','FirstName','LastName','Notes')) {
if ($PSItem.Key -in ('State','FirstName','LastName')) {
$Key = switch ($PSItem.Key) {
'State' {'stateOrProvince'}
'FirstName' {'givenName'}
'LastName' {'surName'}
}
}
else {
$Key = $PSItem.Key
}
$User.personalInformationProperties.$Key = $PSItem.Value
}
elseif ($PSItem.Key -in ('WorkgroupList','LicenseList','RoleList')) {
$Key = $PSItem.Key
if ($Key = 'WorkgroupList' -and $WorkgroupList) {
write-host " WorkgroupList - casting to array"
#$Value = @( Get-ICWorkgroup -ICSession $SessionKey -Id $WorkgroupList )
[array] $Value = @()
#$User.Workgroups = $Value
}
if ($Key = 'LicenseList' -and $LicenseList) {
write-host " LicenseList - casting to array"
#$Value = @((Get-ICLicenseAllocation -ICSession $SessionKey -Id $LicenseList).configurationId)
$Value = @()
if ($User.licenseProperties.PSobject.Properties.name -match 'additionalLicenses') {
$User.licenseProperties.additionalLicenses = $Value
}
else {
$User.licenseProperties | Add-Member -MemberType NoteProperty -Name 'additionalLicenses' -Value $Value -Force
}
}
if ($Key = 'RoleList' -and $RoleList) {
write-host " RoleList - casting to array"
#[array]$Value = Get-ICRole -ICSession $SessionKey -Id $RoleList
[array]$Value = @()
if ($User.roles.PSobject.Properties.name -match 'actualValue') {
$User.Roles.actualValue = $Value
}
else {
$User.roles | Add-Member -MemberType NoteProperty -Name 'actualValue' -Value $Value -Force
}
}
}
else {
write-host "adding key '$($PSItem.Key)' with value '$($PSItem.Value)'"
write-host " value before is '$($Value.GetType().FullName)'"
$Key = $PSItem.Key
write-host " value after is '$($Value.GetType().FullName)'"
$Value = $PSItem.Value
#write-host ($PSItem.Key | convertto-json)
#write-host ($PSItem.Value | convertto-json)
#write-host $PSItem.Key.GetType().FullNane
#write-host $PSItem.Value.GetType().FullNane
#write-host "user before = '$($User |ConvertTo-Json -Depth 5)'"
$User |Add-Member -MemberType NoteProperty -Name $Key -Value $Value -Force
#write-host "user after = '$($User |ConvertTo-Json -Depth 5)'"
}
}
$Body = $User |ConvertTo-Json -Depth 5
write-host $Body
}
and when you call it with, e.g. this:
$Params = @{
ICSession = "aaa"
Id = 'tautomation'
WorkgroupList = "MultiSite-UserSpan"
RoleList = "Business User"
LicenseList = "I3_ACCESS_RECORDER"
DepartmentName = "Infr & Ops"
Title = "Process Automation Engineer"
OfficeLocation = "Remote"
AutoAnswerAcdInteractions = $true
}
Update-ICUser @Params
you get this output:
processing key 'DepartmentName'
processing key 'OfficeLocation'
adding key 'OfficeLocation' with value 'Remote'
processing key 'Title'
processing key 'WorkgroupList'
WorkgroupList - casting to array
LicenseList - casting to array
RoleList - casting to array
processing key 'AutoAnswerAcdInteractions'
adding key 'AutoAnswerAcdInteractions' with value 'True'
value before is 'System.Object[]'
value after is 'System.Object[]'
processing key 'RoleList'
WorkgroupList - casting to array
LicenseList - casting to array
RoleList - casting to array
processing key 'LicenseList'
WorkgroupList - casting to array
LicenseList - casting to array
RoleList - casting to array
{
"personalInformationProperties": {
"DepartmentName": "Infr & Ops",
"Title": "Process Automation Engineer"
},
"licenseProperties": {
"additionalLicenses": []
},
"roles": {
"actualValue": []
},
"Workgroups": null,
"OfficeLocation": "Remote",
"AutoAnswerAcdInteractions": [
true
]
}
So there's a few things to note:
casting to array
casting to array
seems to be getting logged for three parameters (WorkgroupList
,RoleList
andLicenseList
) when processing each of them.If you look, your code is doing this:
if ($Key = 'WorkgroupList' -and $WorkgroupList)
but=
is an assignment operator, not a comparison operator, so what you really want isif ($Key -eq 'WorkgroupList' -and $WorkgroupList)
It's the same for
if ($Key = 'LicenseList' -and $LicenseList)
andif ($Key = 'RoleList' -and $RoleList)
- change those to-eq
as well and that's one bug stomped...adding key 'AutoAnswerAcdInteractions' with value 'True'
The
$Value
variable is of typeSystem.Object[]
before and after assigning the parameter value. If you look at the previous parameter in the logs you'll seeWorkgroupList - casting to array
- this is changing the type of$Value
to be an array. Any future attempts to assign values will automatically convert the value to an array, so$true
becomes[ $true ]
, which is where your problem is coming from.Note the same cast is happening in
RoleList
, but notLicenseList
.Since the order of keys is not guaranteed in a hashtable or
$PSBoundParameters
, your issue only happens if:$WorkgroupList
or$RoleList
are specified in the function call- They happen to be enumerated before
$AutoAnswerAcdInteractions
To fix this, refactor
WorkgroupList
andRoleList
to be the same asLicenseList
- i.e. instead of:[array]$Value = <expression>
use the Array subexpression operator:
$Value = @( <expression> )
This will ensure the value stored in the variable is an array without changing the type of the variable for future parameters.
Note that the reason the extra parameter appears to work is it changes the order of the items in $PSBoundParameters
. For example:
$Params = @{
ICSession = "aaa"
Id = 'tautomation'
WorkgroupList = "MultiSite-UserSpan"
RoleList = "Business User"
LicenseList = "I3_ACCESS_RECORDER"
DepartmentName = "Infr & Ops"
Title = "Process Automation Engineer"
OfficeLocation = "Remote"
}
Update-ICUser @Params -AutoAnswerAcdInteractions $true
gives this output
processing key 'AutoAnswerAcdInteractions'
adding key 'AutoAnswerAcdInteractions' with value 'True'
processing key 'DepartmentName'
processing key 'Title'
processing key 'WorkgroupList'
WorkgroupList - casting to array
processing key 'RoleList'
RoleList - casting to array
processing key 'LicenseList'
LicenseList - casting to array
processing key 'OfficeLocation'
adding key 'OfficeLocation' with value 'Remote'
value before is 'System.Object[]'
value after is 'System.Object[]'
{
"personalInformationProperties": {
"DepartmentName": "Infr & Ops",
"Title": "Process Automation Engineer"
},
"licenseProperties": {
"additionalLicenses": []
},
"roles": {
"actualValue": []
},
"Workgroups": null,
"AutoAnswerAcdInteractions": true,
"OfficeLocation": [
"Remote"
]
}
Note that AutoAnswerAcdInteractions
is processed before any of the paths that change the type of $Value
to an array.
Incidentally, the code also works if you omit WorkgroupList
and RoleList
because $Value
never gets changed to an array:
$Params = @{
ICSession = "aaa"
Id = 'tautomation'
#WorkgroupList = "MultiSite-UserSpan"
#RoleList = "Business User"
LicenseList = "I3_ACCESS_RECORDER"
DepartmentName = "Infr & Ops"
Title = "Process Automation Engineer"
OfficeLocation = "Remote"
AutoAnswerAcdInteractions = $true
}
Update-ICUser @Params
with the output:
processing key 'OfficeLocation'
adding key 'OfficeLocation' with value 'Remote'
processing key 'Title'
processing key 'AutoAnswerAcdInteractions'
adding key 'AutoAnswerAcdInteractions' with value 'True'
value before is 'System.String'
value after is 'System.String'
processing key 'DepartmentName'
processing key 'LicenseList'
LicenseList - casting to array
{
"personalInformationProperties": {
"DepartmentName": "Infr & Ops",
"Title": "Process Automation Engineer"
},
"licenseProperties": {
"additionalLicenses": []
},
"roles": {},
"Workgroups": null,
"OfficeLocation": "Remote",
"AutoAnswerAcdInteractions": true
}