With my current Security related projects I'm doing a lot of work using Azure's native automation capabilities. It's been a major surprise for me to realise that the wisdom of the majority of Security Providers is to argue for the purchase of XSOAR licenses to provide an automation capability with Microsoft Sentinel when all the tooling for automation has existed in Azure well before Sentinel was a product.
Surprisingly little seems to be written about using PowerShell Core Durable Functions in the Enterprise. Yet, they perform a unique role with automation that can't easily be matched with other technologies. The intention of this blog is to provide an overview of developing PowerShell Core Durable Functions.
Durable Functions are workflows. As workflows, they are intended to be used as multi-step processes that can execute tasks at ordered stages with remote systems. They are ideal for multi-stage operations that may involve deployments across different cloud environments while interfacing with different systems. Unlike regular PowerShell Function Apps, Durable Function Apps are intended to be long running processes as workflows with human-in-the-loop might take hours or days for each task in a workflow to complete. This capability for longevity affords Durable Functions a unique niche in building long running automation.
By utilising WebApps for automation we can also leverage inherent benefits that come from Microsoft's PaaS services such as:
- Cost Efficiency: Pay-per-use model combined with long-running workflows can be more cost-effective than traditional VMs or containers.
- Event-Driven Architecture: Seamlessly integrate with Azure Event Grid, Service Bus, and other event-driven services.
- Monitoring and Diagnostics: Built-in integration with Azure Monitor and Application Insights for observability.
Why PowerShell for Serverless Automation?
PowerShell Core is completely dotNet based as a platform. This means that it can be used to interface with almost any part of the Microsoft technology stack. It's a feature rich scripting language that requires no compiling or specific IDE to work with it.
As we develop longer running automation processes, we are likely to deploy a dedicated App Service Plan for compute and if we are also hosting Logic Apps. App Service Plans are either Windows or Linux hosts and that platform choice impacts what type of Function App may be hosted and run from them in the future. Logic Apps, like PowerShell Apps must be run from a Windows App Service Plan while Python requires a separate Linux hosted plan to be used in the environment. Purely for cost saving alone, deploying a single App Service Plan for hosting all automation makes sense.
When we also consider the built in module support for Az with PowerShell Function Apps it makes a compelling argument that the language should be central for all Microsoft technology automation.
Understanding Durable Functions in PowerShell
Core Concepts
Orchestrator Functions
- Deterministic Nature
The deterministic nature of orchestrator functions means that every time an orchestrator function is replayed, it must produce the same sequence of actions and results as it did in its original execution. The Durable Task Framework continually re-executes the orchestrator function's code (known as replay) to reconstruct its state in case of process failures, scaling, or resuming after being paused. This is a key understanding to realise that the Orchestrator's role is to sequence and execute activities rather than process actionable code itself. - Durable State Management
The orchestrator function does not retain in-memory state between executions. Instead, it reconstructs its state by re-executing its code based on previously recorded actions. Deterministic behaviour ensures this reconstruction is accurate.
Note that generating random numbers, retrieving external data or using timestamps directly within an Orchestrator will produce different results on each replay. All of these activities much be executed within Activity functions that are marshalled by the orchestrator.
Activity Functions
- Task Execution
Activity functions are lightweight functions that perform non-deterministic tasks like I/O operations, database interactions, or API calls. We may consider these to be "standard" PowerShell functions in every sense. - Sequence Architecture
As Durable Functions are workflows, each activity should be considered to be a "step" or "task" in that workflow. Sequencing the execution of tasks is the reserved purpose of the Orchestrator function. Activity Functions should not be involved in calling other activity functions. However, you can develop a common set of PowerShell modules that can be used by all Activity Functions which negates any need for trying to have one Activity Function call code within another.
Client Activities
A client activity in Durable Functions acts as an entry point for external systems, users, or scheduled triggers to interact with an orchestration. This includes:
- Starting a new orchestration
- Querying orchestration status
- Sending external signals (events) to running instances
- Terminating orchestration instances
- Purging old instances
Client activities use Durable Function bindings, specifically 'DurableClient', to interact with orchestrator instances.
All Durable Functions require the deployment of a Dedicated Storage Account for use by the Durable Function App. Tables within the Storage Account are used for tracking the status or running workflows and tasks. Although the monitoring and sequencing of task management is externalised from the Function App to the Storage Account, the interfacing with Orchestrator and Activity Functions is web based using APIs hosted on the local running app.
Typically, the Client activities we use with Durable Functions are exposed with PowerShell extensions from the "Durable SDK". Sometimes (when things go horribly wrong) some of the more advanced client activities calls have to be made using REST.
Durable Function Patterns
Orchestrator Functions and Activity Functions may be configured to interact with each other differently based on the type of business problem we are trying to solve.
- We might wish to orchestrate a procedural sequence of tasks as a ‘Function Chain’.
- We might have a need to remotely trigger processes on machines, like copying files to their drives or initiating application updates. Our Orchestrator function may ‘Fan Out’ activity to multiple Activity Functions running in parallel.
- We might want to create an application to consistently ‘Monitor’ other systems or processes at intervals, then returning to sleep.
These different Function App scenarios can be addressed by different activity sequencing configurations or “patterns”.
Function Chaining Pattern
The Function Chaining pattern is used for workflows where steps must execute in a specific sequence, with each step depending on the output of the previous one. This type of activity sequencing is used with deployment pipelines, change management workflows, or order processing systems. The type of PowerShell Orchestrator used for activity chaining is represented below where the result of one activity is chained as the input to the next:
function Run-ChainedWorkflow {
param($Context)
try {
# Sequential execution of activities
$result1 = Invoke-DurableActivity -FunctionName 'Process-Order' -Context $Context
$result2 = Invoke-DurableActivity -FunctionName 'Send-Confirmation' -Input $result1 -Context $Context
$result3 = Invoke-DurableActivity -FunctionName 'Update-Inventory' -Input $result2 -Context $Context
return "Workflow completed: $result3"
}
catch {
# Handle failures and implement retry logic
$Context.SetCustomStatus("Error: $($_.Exception.Message)")
throw
}
}
The strength of using chaining in this manner I that it incorporates built-in error handling and automatic checkpointing - if your process fails halfway through, it can be resumed from the last successful step rather than starting over.
Fan-Out/Fan-In Pattern
When you need to process multiple items in parallel and then aggregate the results, the Fan-Out/Fan-In pattern is a unique option only available for durable functions. This pattern is useful any scenario requiring parallel computation.
function Run-ParallelOrchestrator {
param($Context)
# Fan-out: Distribute work
$workItems = Invoke-DurableActivity -FunctionName 'Get-WorkBatch' -Context $Context
$tasks = @()
foreach ($item in $workItems) {
$task = Invoke-DurableActivity -FunctionName 'Process-WorkItem' -Input $item -Context $Context -NoWait
$tasks += $task
}
# Fan-in: Wait for all parallel executions
$outputs = Wait-DurableTask -Tasks $tasks -Context $Context
return $outputs
}
This pattern allows you to distribute work across multiple function instances, process them in parallel, and then combine the results. The pattern handles the complexity of managing multiple concurrent executions while ensuring all tasks complete before proceeding to the next Activity.
Monitoring Pattern
The Monitoring pattern is essential for long-running business processes that require periodic checking or polling.
function Monitor-JobStatus {
param($Context)
$jobId = $Context.GetInput()
$pollingInterval = 30 # seconds
while (Invoke-DurableActivity -FunctionName 'Check-JobStatus' -Input $jobId -Context $Context) {
# Create durable timer
$nextCheck = $Context.CurrentUtcDateTime.AddSeconds($pollingInterval)
Start-DurableTimer -Context $Context -FireAt $nextCheck
# Implement exponential backoff
$pollingInterval *= 2
}
# Job completed, perform cleanup
Invoke-DurableActivity -FunctionName 'Cleanup-Job' -Input $jobId -Context $Context
}
In recent months Microsoft has updated its example documentation with Durable Functions to include more PowerShell Core examples which may be found here:
https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview?tabs=in-process%2Cnodejs-v3%2Cv1-model&pivots=powershell
The next blog in this series will discuss using a PowerShell Core Durable Function to retrieve Microsoft Defender Vulnerability data. If you want to skip ahead, the example project for that is online and viewable here:
https://github.com/LaurieRhodes/PUBLIC-Get-Defender-Vulnerabilities
- Log in to post comments