Azure/azure-functions-durable-extension

PowerShell Durable Function Activity Bindings with Table Bindings

Opened this issue · 6 comments

Discussed in #2904

Originally posted by Bionic711 August 28, 2024
I am currently working on a Durable Function written in PowerShell and having issues passing the input of the activityTrigger to the rowKey of a Table input in the same activity. {input} from the name of the activity trigger allows it to run, and logs show it was passed ["stringvalue"], but using the type as described in MSFT docs with the queue trigger does not. {activityTrigger} results in a 'not recognized as a named parameter' error. The interesting part is that when it reaches out to the table to pull the value when using {input}, it says that the rowKey or partitionKey cannot be ''.

https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-table-input?tabs=isolated-process%2Ctable-api%2Cnodejs-v4&pivots=programming-language-powershell

Thus far I have only tested locally with Azurite as the emulator.

Any ideas how to get this to work?

@andystaples would you happen to know the answer to this?

Here is the bindings:

{
    "bindings": [
        {
            "name": "input",
            "type": "activityTrigger",
            "direction": "in"
        },
        {
            "name": "mailbox",
            "type": "table",
            "tableName": "mailboxes",
            "partitionKey": "mailboxes",
            "rowKey": "{input}",
            "connection": "AzureWebJobsStorage",
            "direction": "in"
        },
        {
            "name": "results",
            "type": "table",
            "tableName": "results",
            "connection": "AzureWebJobsStorage",
            "direction": "out"
        }
    ],
    "disabled": false
}

Thanks for reaching out @Bionic711. This is the first time I hear of symptoms like this so we'll need to reproduce this in our end to dive deeper. Do you have a minimal reproducer you could share with us?

I spoke with @davidmrdavid and will be uploading a reproducer by mid next week.

This replicates the issue locally for me.

BaseTimer Function:

param($Timer, $TriggerMetadata)

$InstanceId = Start-DurableOrchestration -FunctionName "Orchestrator"
Write-Host "Started orchestration with ID = '$InstanceId'"
{
    "bindings": [
        {
            "name": "Timer",
            "type": "timerTrigger",
            "direction": "in",
            "schedule": "0 0 0 * * *",
            "runOnStartup": true
        },
        {
            "name": "starter",
            "type": "durableClient",
            "direction": "in"
        }
    ],
    "scriptFile": "run.ps1"
}

Orchestrator Function:

param($Orchestrator, $TriggerMetadata)
$context = $TriggerMetadata
$results = Invoke-DurableActivity -FunctionName "Query"
$retryOptions = New-DurableRetryOptions `
                    -FirstRetryInterval (New-Timespan -Seconds 5) `
                    -MaxNumberOfAttempts 1
$parallelTasks = @()
$parallelTasks = 
    foreach ($upn in $results.UPNs) {
        #Write-Host "Now creating task for $upn"
        Invoke-DurableActivity -FunctionName 'Permissions' -Input $upn -NoWait -RetryOptions $retryOptions
    }
$output = Wait-DurableTask -Task $parallelTasks
{
    "bindings": [
        {
            "name": "Orchestrator",
            "type": "orchestrationTrigger",
            "direction": "in"
        }
    ]
}

Query Function:

param($input, $TriggerMetadata)
$context = $TriggerMetadata
$upns = New-Object System.Collections.Generic.List[String]
$Mbxs = @(
    [PSCustomObject]@{
        UserPrinicpalName = 'john.doe@contoso.com'
        DisplayName       = 'John Doe'
        FirstName         = 'John'
        LastName          = 'Doe'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'jane.smith@contoso.com'
        DisplayName       = 'Jane Smith'
        FirstName         = 'Jane'
        LastName          = 'Smith'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'michael.brown@contoso.com'
        DisplayName       = 'Michael Brown'
        FirstName         = 'Michael'
        LastName          = 'Brown'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'emily.johnson@contoso.com'
        DisplayName       = 'Emily Johnson'
        FirstName         = 'Emily'
        LastName          = 'Johnson'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'david.williams@contoso.com'
        DisplayName       = 'David Williams'
        FirstName         = 'David'
        LastName          = 'Williams'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'sarah.jones@contoso.com'
        DisplayName       = 'Sarah Jones'
        FirstName         = 'Sarah'
        LastName          = 'Jones'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'chris.miller@contoso.com'
        DisplayName       = 'Chris Miller'
        FirstName         = 'Chris'
        LastName          = 'Miller'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'laura.davis@contoso.com'
        DisplayName       = 'Laura Davis'
        FirstName         = 'Laura'
        LastName          = 'Davis'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'kevin.garcia@contoso.com'
        DisplayName       = 'Kevin Garcia'
        FirstName         = 'Kevin'
        LastName          = 'Garcia'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'linda.martinez@contoso.com'
        DisplayName       = 'Linda Martinez'
        FirstName         = 'Linda'
        LastName          = 'Martinez'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'steven.rodriguez@contoso.com'
        DisplayName       = 'Steven Rodriguez'
        FirstName         = 'Steven'
        LastName          = 'Rodriguez'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'karen.hernandez@contoso.com'
        DisplayName       = 'Karen Hernandez'
        FirstName         = 'Karen'
        LastName          = 'Hernandez'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'brian.lopez@contoso.com'
        DisplayName       = 'Brian Lopez'
        FirstName         = 'Brian'
        LastName          = 'Lopez'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'nancy.gonzalez@contoso.com'
        DisplayName       = 'Nancy Gonzalez'
        FirstName         = 'Nancy'
        LastName          = 'Gonzalez'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'joshua.wilson@contoso.com'
        DisplayName       = 'Joshua Wilson'
        FirstName         = 'Joshua'
        LastName          = 'Wilson'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'betty.anderson@contoso.com'
        DisplayName       = 'Betty Anderson'
        FirstName         = 'Betty'
        LastName          = 'Anderson'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'mark.thomas@contoso.com'
        DisplayName       = 'Mark Thomas'
        FirstName         = 'Mark'
        LastName          = 'Thomas'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'patricia.taylor@contoso.com'
        DisplayName       = 'Patricia Taylor'
        FirstName         = 'Patricia'
        LastName          = 'Taylor'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'paul.moore@contoso.com'
        DisplayName       = 'Paul Moore'
        FirstName         = 'Paul'
        LastName          = 'Moore'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'jennifer.jackson@contoso.com'
        DisplayName       = 'Jennifer Jackson'
        FirstName         = 'Jennifer'
        LastName          = 'Jackson'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'george.white@contoso.com'
        DisplayName       = 'George White'
        FirstName         = 'George'
        LastName          = 'White'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'maria.harris@contoso.com'
        DisplayName       = 'Maria Harris'
        FirstName         = 'Maria'
        LastName          = 'Harris'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'frank.martin@contoso.com'
        DisplayName       = 'Frank Martin'
        FirstName         = 'Frank'
        LastName          = 'Martin'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'susan.thompson@contoso.com'
        DisplayName       = 'Susan Thompson'
        FirstName         = 'Susan'
        LastName          = 'Thompson'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'jason.garcia@contoso.com'
        DisplayName       = 'Jason Garcia'
        FirstName         = 'Jason'
        LastName          = 'Garcia'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'dorothy.martinez@contoso.com'
        DisplayName       = 'Dorothy Martinez'
        FirstName         = 'Dorothy'
        LastName          = 'Martinez'
    },
    [PSCustomObject]@{
        UserPrinicpalName = 'gregory.robinson@contoso.com'
        DisplayName       = 'Gregory Robinson'
        FirstName         = 'Gregory'
        LastName          = 'Robinson'
    }
)
foreach ($Mbx in $($Mbxs | Select -First 50))
{
    try {
        Push-OutputBinding -Name "mailboxes" -Value @{
            ETag                 = "*"
            PartitionKey         = "mailboxes"
            RowKey               = $Mbx.UserPrincipalName
            DisplayName          = $Mbx.DisplayName
            UserPrincipalName    = $Mbx.UserPrincipalName
            FirstName = $Mbx.FirstName
            LastName = $Mbx.LastName
        }
    }
    catch {
        if ($_.Exception.Response.StatusCode -eq 409) {
            Write-Host "409 Conflict Error: The resource already exists or cannot be created in its current state."
        }
        else {
            # Handle other errors
            Write-Error "An unexpected error occurred: $($_.Exception.Message)"
        }
    }
    if ([string]::IsNullOrEmpty($Mbx.UserPrincipalName))
    {
        Write-Host "Empty UserPrincipalName for $($Mbx.DisplayName)"
    }
    else
    {
         $upns.Add($Mbx.UserPrincipalName)
    }
}

$output = [PSCustomObject]@{
    UPNs       = $upns
    TotalCount = $totalCount
}

return $output
{
    "bindings": [
        {
            "name": "input",
            "type": "activityTrigger",
            "direction": "in"
        },
        {
            "name": "mailboxes",
            "type": "table",
            "tableName": "mailboxes",
            "connection": "AzureWebJobsStorage",
            "direction": "out"
        }
    ],
    "scriptFile": "run.ps1"
}

Permissions Function (where the issue is at):

param($input, $mailbox, $TriggerMetadata)
$context = $TriggerMetadata
Write-Host "`nPermissions received input: $($input)"
{
    "bindings": [
        {
            "name": "input",
            "type": "activityTrigger",
            "direction": "in"
        },
        {
            "name": "mailbox",
            "type": "table",
            "tableName": "mailboxes",
            "partitionKey": "mailboxes",
            "rowKey": "{input}",
            "connection": "AzureWebJobsStorage",
            "direction": "in"
        },
        {
            "name": "results",
            "type": "table",
            "tableName": "results",
            "connection": "AzureWebJobsStorage",
            "direction": "out"
        }
    ],
    "disabled": false
}

Host.json

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "hubName": "ExchangePermissionsHub",
      "maxConcurrentActivityFunctions": 20,
      "maxConcurrentOrchestratorFunctions": 1,
      "storeInputsInOrchestrationHistory": true,
      "traceInputsAndOutputs": true
    }
  },
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  },
  "managedDependency": {
    "enabled": true
  },
  "functionTimeout": "-1"
}

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment.