Simplifying Azure Log Analytics Table Retention Management: A Modern Approach
The Challenge: Retention Management at Scale
If you've ever managed Azure Log Analytics workspaces at enterprise scale, you'll know the pain of manually configuring retention policies across dozens—or even hundreds—of tables. The Azure portal, whilst functional, becomes cumbersome when you need to update retention settings for multiple tables across different workspaces. Add to this the confusion of phantom tables (tables that appear in the API but contain no actual data) and varying plan types with different retention limits, and you've got a recipe for frustration.
Why This Matters More Than Ever
Cost Optimisation is Critical
With organisations increasingly focused on cloud cost management, Log Analytics retention settings directly impact your Azure bill. Analytics plan tables can retain data for up to 12 years, whilst Basic plan tables max out at 90 days. Getting this wrong means either:
- Paying for unnecessary long-term storage of data you don't need
- Losing critical audit data by setting retention too low
Compliance Requirements are Tightening
Regulatory frameworks like GDPR, SOX, and industry-specific standards often dictate specific data retention periods. Manual management increases the risk of compliance violations through human error or inconsistent application of policies.
Operational Efficiency Matters
Time spent clicking through the Azure portal for each table is time not spent on strategic initiatives. When you're managing security operations, every minute counts.
Enter Modern Table Retention Management
I've developed a PowerShell solution that transforms this tedious process into a streamlined, interactive experience. Here's why it's a game-changer:
🎯 Smart Table Discovery
The script doesn't just show you every table that exists in the API—it intelligently queries your workspace's Usage data to identify tables that actually contain billable data. No more phantom tables cluttering your selection list.
🖥️ Modern Console Interface
Forget traditional PowerShell menus. The solution utilises Out-ConsoleGridView
to provide a modern, grid-based interface that works consistently across Windows, Linux, and macOS. Think of it as bringing the best parts of a GUI to the command line.
⚡ Batch Operations
Select multiple tables at once and update them in a single operation. The interactive interface shows you current retention settings alongside proposed changes, eliminating guesswork.
🔒 Safety First
Multiple confirmation steps ensure you can't accidentally misconfigure retention settings. The tool shows you exactly what will change before making any modifications.
Real-World Impact
Consider a typical scenario: You need to align 50 Analytics tables to a 2-year retention policy for compliance requirements. Traditionally, this would involve:
- Azure Portal Route: 50+ individual clicks, prone to errors, no audit trail
- Manual PowerShell: Writing custom scripts, handling authentication, managing errors
- Our Solution: 2-minute interactive process with complete audit logging
The time savings are obvious, but the real value lies in consistency and reduced error rates.
Why Interactive Beats Automated
You might wonder why not just fully automate this process. The answer lies in the nature of Log Analytics data:
- Different tables serve different purposes - security logs vs. performance metrics
- Retention requirements vary by data type - audit logs vs. troubleshooting data
- Business context matters - what's critical for one organisation may be noise for another
The interactive approach provides the efficiency of automation whilst maintaining the human insight necessary for good decisions.
Looking Forward
As Azure continues to evolve its Log Analytics offerings, tools like this become increasingly valuable. The upcoming changes to pricing models and the introduction of new table types mean that retention management will only become more complex.
Having a reliable, interactive tool that adapts to these changes—whilst providing a consistent user experience—is no longer a nice-to-have. It's essential infrastructure for any organisation serious about cloud operations.
Getting Started
The beauty of this approach is its simplicity. With just three parameters—Tenant ID, retention days, and table plan—you can transform a manual, error-prone process into a guided, interactive experience.
Quick Start Example
# Set Analytics tables to 1 year retention
.\Set-LATableRetention.ps1 -TenantID "your-tenant-id" -RetentionDays 365
# Set Basic plan tables to 30 days
.\Set-LATableRetention.ps1 -TenantID "your-tenant-id" -RetentionDays 30 -TablePlan "Basic"
Whether you're managing a single workspace or dozens across multiple subscriptions, the time investment in setting up this solution pays dividends from the first use.
Download the Script
Ready to streamline your Log Analytics retention management? The complete script is available below with full documentation and examples.
<#
.SYNOPSIS
Azure Log Analytics Table Retention Management Script
.DESCRIPTION
This script provides an interactive interface for managing retention policies on Log Analytics workspace tables.
It utilises Out-ConsoleGridView for all user interactions, providing a modern console-based GUI experience.
Key Features:
- Interactive workspace selection
- Plan-based table filtering (Analytics, Basic)
- Multi-select table management
- Retention period validation
- Comprehensive logging and error handling
- Real-time table existence validation utilising workspace Usage data
.PARAMETER TenantID
The Azure Active Directory Tenant ID for authentication.
This is required for establishing the Azure connection.
.PARAMETER TablePlan
The default table plan type to work with. Valid values are 'Analytics' or 'Basic'.
Defaults to 'Analytics' if not specified.
Note: Users can override this during interactive filtering.
.PARAMETER RetentionDays
The number of days to set for table retention.
Must be between 4 and 4383 days (approximately 12 years).
Different plan types have different valid ranges:
- Analytics: 4-4383 days
- Basic: 4-90 days
.EXAMPLE
.\Set-LATableRetention.ps1 -TenantID "12345678-1234-1234-1234-123456789012" -RetentionDays 365
Sets table retention to 1 year (365 days) utilising interactive selection for workspace and tables.
.EXAMPLE
.\Set-LATableRetention.ps1 -TenantID "12345678-1234-1234-1234-123456789012" -RetentionDays 30 -TablePlan "Basic"
Sets table retention to 30 days with a preference for Basic plan tables.
.NOTES
Version: 2.0
Author: System Administrator
Creation Date: 29/05/2025
Purpose/Change: Modern interactive Log Analytics table retention management
Prerequisites:
- PowerShell 5.1 or later
- Az.Accounts module
- Az.OperationalInsights module
- Microsoft.PowerShell.ConsoleGuiTools module
- Appropriate Azure RBAC permissions on Log Analytics workspaces
Required Permissions:
- Log Analytics Contributor or higher on target workspaces
- Microsoft.OperationalInsights/workspaces/tables/write
- Microsoft.OperationalInsights/workspaces/query/read
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, HelpMessage = "Enter your Tenant Id")]
[string] $TenantID,
[Parameter(Mandatory = $false)]
[ValidateSet('Analytics', 'Basic')]
[string] $TablePlan = 'Analytics',
[Parameter(Mandatory = $true, HelpMessage = "Enter retention days (Analytics: 4-4383, Basic: 4-90)")]
[ValidateRange(4, 4383)]
[int] $RetentionDays
)
# Initialisation
# Initialise logging infrastructure with timestamp-based log file
$TimeStamp = Get-Date -Format yyyyMMdd_HHmmss
$LogFileName = "LARetention_$TimeStamp.log"
# Logging Functions
<#
.SYNOPSIS
Centralised logging function with multiple severity levels and colour coding.
.DESCRIPTION
Provides consistent logging output to both console and log file.
Console output utilises colour coding based on severity level for better readability.
.PARAMETER Message
The message to log
.PARAMETER Severity
The severity level: Information, Warning, or Error
#>
function Write-Log {
param(
[Parameter(Mandatory = $true)]
[string]$Message,
[Parameter(Mandatory = $false)]
[ValidateSet('Information', 'Warning', 'Error')]
[string]$Severity = 'Information'
)
# Create timestamped log entry
$timestamp = Get-Date -Format "dd/MM/yyyy HH:mm:ss"
$logMessage = "[$timestamp] [$Severity] $Message"
# Display to console with appropriate colour coding
switch ($Severity) {
'Information' { Write-Host $logMessage -ForegroundColor Green }
'Warning' { Write-Host $logMessage -ForegroundColor Yellow }
'Error' { Write-Host $logMessage -ForegroundColor Red }
}
# Append to log file for audit trail
Add-Content -Path $LogFileName -Value $logMessage
}
# Data Retrieval Functions
<#
.SYNOPSIS
Retrieves and validates Log Analytics tables that actually contain data in the specified workspace.
.DESCRIPTION
This function performs a comprehensive table discovery process:
1. Queries the workspace Usage table to identify tables with actual data
2. Retrieves table configuration from the Azure Management API
3. Cross-references both sources to ensure only real, accessible tables are returned
4. Filters out system tables, phantom tables, and tables in invalid states
.PARAMETER Workspace
The Log Analytics workspace object containing connection details
.OUTPUTS
Array of custom objects representing valid tables with their properties:
- TableName: The name of the table
- TotalRetentionInDays: Current retention period in days
- Plan: The table's pricing plan (Analytics, Basic, etc.)
- SubscriptionId: Azure subscription ID
- ResourceGroupName: Resource group containing the workspace
- WorkspaceName: Name of the Log Analytics workspace
#>
function Get-AllWorkspaceTables {
param(
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[object]$Workspace
)
try {
# Resource ID Parsing
# Extract Azure resource components from the workspace resource ID
$resourceId = $Workspace.ResourceId
Write-Log "Processing workspace with ResourceId: $resourceId" -Severity Information
# Use regex to parse the standard Azure resource ID format
if ($resourceId -match '/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\.OperationalInsights/workspaces/([^/]+)') {
$subscriptionId = $matches[1]
$resourceGroupName = $matches[2]
$workspaceName = $matches[3]
} else {
throw "Unable to parse workspace resource ID format: $resourceId"
}
Write-Log "Extracted resource details: Subscription=$subscriptionId, ResourceGroup=$resourceGroupName, Workspace=$workspaceName" -Severity Information
# Usage Query for Table Discovery
# Query the Usage table to identify tables that actually contain billable data
# This prevents showing phantom tables that exist in the API but have no actual data
Write-Log "Executing KQL query to discover tables with actual data..." -Severity Information
$kqlQuery = @"
Usage
| where TimeGenerated > ago(30d)
| where IsBillable == true
| summarize DataGB = sum(Quantity) / 1000 by DataType
| where DataGB > 0
| project TableName = DataType
| sort by TableName asc
"@
try {
$tablesWithData = Invoke-AzOperationalInsightsQuery -WorkspaceId $Workspace.CustomerId -Query $kqlQuery -ErrorAction Stop
$existingTableNames = $tablesWithData.Results | Select-Object -ExpandProperty TableName
Write-Log "Usage query identified $($existingTableNames.Count) tables with billable data" -Severity Information
}
catch {
Write-Log "Usage query failed, proceeding with API-only discovery: $($_.Exception.Message)" -Severity Warning
$existingTableNames = @()
}
# API Table Configuration Retrieval
# Retrieve table configuration from Azure Management API
$baseUri = "https://management.azure.com"
$uri = "$baseUri/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.OperationalInsights/workspaces/$workspaceName/tables?api-version=2021-12-01-preview"
Write-Log "Retrieving table configurations from: $uri" -Severity Information
# Prepare authentication headers
$token = (Get-AzAccessToken).Token
$headers = @{
'Authorization' = "Bearer $token"
'Content-Type' = 'application/json'
}
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers
Write-Log "API returned $($response.value.Count) table definitions" -Severity Information
# Table Validation and Filtering
# Apply comprehensive filtering to ensure only valid, accessible tables are included
$validTables = @()
foreach ($table in $response.value) {
# Skip tables missing essential properties
if (-not $table.name -or -not $table.properties) {
Write-Log "Excluding table without required name or properties" -Severity Warning
continue
}
# Skip tables in transitional or failed states
if ($table.properties.provisioningState -eq 'Deleting' -or $table.properties.provisioningState -eq 'Failed') {
Write-Log "Excluding table '$($table.name)' in invalid state: $($table.properties.provisioningState)" -Severity Warning
continue
}
# Cross-reference with Usage query results if available
if ($existingTableNames.Count -gt 0 -and $table.name -notin $existingTableNames) {
Write-Log "Excluding table '$($table.name)' - no data found in Usage query" -Severity Information
continue
}
# Skip system tables without proper retention configuration
if (-not $table.properties.PSObject.Properties['totalRetentionInDays'] -and
-not $table.properties.PSObject.Properties['retentionInDays'] -and
$null -eq $table.properties.plan) {
Write-Log "Excluding system table '$($table.name)' without retention configuration" -Severity Information
continue
}
$validTables += $table
}
Write-Log "Filtered result: $($validTables.Count) valid tables identified" -Severity Information
# Object Construction
# Transform API response into standardised objects for consistent handling
$allTables = $validTables | Select-Object @{
Name = 'TableName'
Expression = { $_.name }
}, @{
Name = 'TotalRetentionInDays'
Expression = {
# Handle different retention property names and provide reasonable defaults
if ($_.properties.totalRetentionInDays) {
$_.properties.totalRetentionInDays
} elseif ($_.properties.retentionInDays) {
$_.properties.retentionInDays
} else {
90 # Standard default retention for tables without explicit configuration
}
}
}, @{
Name = 'Plan'
Expression = {
# Default to Analytics plan for tables without explicit plan assignment
if ($_.properties.plan) { $_.properties.plan } else { "Analytics" }
}
}, @{
Name = 'SubscriptionId'
Expression = { $subscriptionId }
}, @{
Name = 'ResourceGroupName'
Expression = { $resourceGroupName }
}, @{
Name = 'WorkspaceName'
Expression = { $workspaceName }
}
Write-Log "Successfully processed $($allTables.Count) tables for workspace '$workspaceName'" -Severity Information
# Debug Output
# Provide detailed logging of discovered tables for troubleshooting
Write-Log "Table inventory for workspace '$workspaceName':" -Severity Information
foreach ($table in $allTables) {
Write-Log " └─ $($table.TableName): Plan=$($table.Plan), Retention=$($table.TotalRetentionInDays) days" -Severity Information
}
return $allTables
}
catch {
Write-Log "Critical error in table discovery process: $($_.Exception.Message)" -Severity Error
Write-Log "Full exception details: $($_.Exception)" -Severity Error
throw
}
}
<#
.SYNOPSIS
Filters the complete table list to only include tables matching the specified plan type.
.DESCRIPTION
Applies case-insensitive filtering to return only tables with the specified pricing plan.
Also validates that returned tables have valid retention periods greater than zero.
.PARAMETER AllTables
Array of table objects to filter
.PARAMETER Plan
The plan type to filter by (Analytics, Basic, etc.)
.OUTPUTS
Array of table objects matching the specified plan criteria
#>
function Get-TablesByPlan {
param(
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[array]$AllTables,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Plan
)
Write-Log "Applying plan filter: '$Plan'" -Severity Information
# Apply case-insensitive plan filtering with retention validation
$filteredTables = $AllTables | Where-Object {
$_.Plan -ieq $Plan -and $_.TotalRetentionInDays -gt 0
}
if (-not $filteredTables) {
Write-Log "No tables found matching plan '$Plan'" -Severity Warning
# Provide helpful information about available plans
$availablePlans = ($AllTables | Where-Object { $_.Plan -ne "Not Set" } |
Select-Object -ExpandProperty Plan | Sort-Object -Unique) -join ', '
Write-Log "Available plans in workspace: $availablePlans" -Severity Information
return @()
}
Write-Log "Plan filter '$Plan' matched $($filteredTables.Count) tables" -Severity Information
return $filteredTables
}
# User Interface Functions
<#
.SYNOPSIS
Provides interactive table selection utilising Out-ConsoleGridView with optional plan-based filtering.
.DESCRIPTION
Displays tables in a modern console grid interface allowing users to select multiple tables.
Includes plan information and current retention settings for informed decision making.
Supports both filtered and unfiltered views based on user preference.
.PARAMETER AllTables
Complete array of available table objects
.PARAMETER FilterByPlan
Optional plan type to pre-filter the displayed tables
.OUTPUTS
Array of user-selected table objects
#>
function Select-Tables {
param (
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[array]$AllTables,
[Parameter(Mandatory = $false)]
[string]$FilterByPlan = $null
)
# Validate input data
if ($AllTables.Count -eq 0) {
throw "No tables available for selection. Verify workspace contains accessible tables."
}
# Table Filtering Logic
# Apply optional plan-based filtering with fallback to full list
$tablesToShow = if ($FilterByPlan) {
$filtered = $AllTables | Where-Object { $_.Plan -ieq $FilterByPlan -and $_.TotalRetentionInDays -gt 0 }
if (-not $filtered) {
# Provide user feedback when filter returns no results
Write-Host "No tables found with plan: $FilterByPlan" -ForegroundColor Yellow
$availablePlans = ($AllTables | Where-Object { $_.Plan -ne 'Not Set' } |
Select-Object -ExpandProperty Plan | Sort-Object -Unique) -join ', '
Write-Host "Available plans: $availablePlans" -ForegroundColor Yellow
Write-Host "`nDisplaying all available tables instead..." -ForegroundColor Yellow
$AllTables
} else {
$filtered
}
} else {
$AllTables
}
# Display Data Preparation
# Format table data for optimal display in the grid view
$tableDetails = $tablesToShow | Select-Object @{
Name = 'TableName'
Expression = { $_.TableName }
}, @{
Name = 'Plan'
Expression = { $_.Plan }
}, @{
Name = 'Current Retention'
Expression = { "$($_.TotalRetentionInDays) days" }
}
# Interactive Selection
# Present user interface and capture selections
$title = if ($FilterByPlan) {
"Select Tables to Update ($FilterByPlan Plan)"
} else {
"Select Tables to Update (All Plans)"
}
Write-Log "Presenting table selection interface with $($tablesToShow.Count) options" -Severity Information
$selected = $tableDetails | Out-ConsoleGridView -Title $title
# Validate user selection
if ($null -eq $selected -or $selected.Count -eq 0) {
throw "Operation cancelled - no tables selected by user"
}
Write-Log "User selected $($selected.Count) tables for processing" -Severity Information
# Return original table objects corresponding to user selections
return $AllTables | Where-Object { $_.TableName -in $selected.TableName }
}
<#
.SYNOPSIS
Provides interactive workspace selection utilising Out-ConsoleGridView with single-selection enforcement.
.DESCRIPTION
Displays available Log Analytics workspaces in a console grid interface.
Shows workspace name, resource group, and location for informed selection.
Enforces single selection to ensure clear workspace targeting.
.PARAMETER Workspaces
Array of Log Analytics workspace objects from Get-AzOperationalInsightsWorkspace
.OUTPUTS
Single selected workspace object
#>
function Select-Workspace {
param (
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[array]$Workspaces
)
# Validate input data
if ($Workspaces.Count -eq 0) {
throw "No Log Analytics workspaces available. Verify Azure permissions and workspace existence."
}
# Display Data Preparation
# Format workspace data for clear identification in the selection interface
$workspaceDetails = $Workspaces | Select-Object @{
Name = 'Name'
Expression = { $_.Name }
}, @{
Name = 'ResourceGroup'
Expression = { $_.ResourceGroupName }
}, @{
Name = 'Location'
Expression = { $_.Location }
}
# Interactive Selection
# Present single-selection interface for workspace choice
Write-Log "Presenting workspace selection interface with $($Workspaces.Count) options" -Severity Information
$selected = $workspaceDetails | Out-ConsoleGridView -Title "Select a Log Analytics Workspace" -OutputMode Single
# Validate selection
if ($null -eq $selected) {
throw "Operation cancelled - no workspace selected by user"
}
Write-Log "User selected workspace: '$($selected.Name)'" -Severity Information
# Return the original workspace object corresponding to the selection
return $Workspaces | Where-Object { $_.Name -eq $selected.Name }
}
# Main Execution Block
<#
.SYNOPSIS
Main script execution flow with comprehensive error handling and user interaction.
.DESCRIPTION
Orchestrates the complete retention management workflow including:
- Azure authentication and connection establishment
- Interactive workspace selection
- Table discovery and filtering
- User-driven table selection and confirmation
- Batch retention updates with progress tracking
- Summary reporting
#>
try {
# Prerequisites and Environment Setup
Write-Log "Initialising Log Analytics Table Retention Management Script v2.0" -Severity Information
# Verify and install required PowerShell modules
$requiredModules = @('Az.Accounts', 'Az.OperationalInsights', 'Microsoft.PowerShell.ConsoleGuiTools')
Write-Log "Checking required PowerShell modules: $($requiredModules -join ', ')" -Severity Information
foreach ($module in $requiredModules) {
if (-not (Get-Module -ListAvailable -Name $module)) {
Write-Log "Installing missing module: $module" -Severity Warning
Install-Module -Name $module -Scope CurrentUser -Force -AllowClobber
}
Import-Module $module -Force
Write-Log "Module '$module' loaded successfully" -Severity Information
}
# Configure environment to suppress Azure PowerShell breaking change warnings
Set-Item -Path Env:SuppressAzurePowerShellBreakingChangeWarnings -Value $true
$WarningPreference = 'SilentlyContinue'
Write-Log "Environment configured for clean execution" -Severity Information
# Azure Authentication and Connection
Write-Log "Initiating Azure authentication for Tenant ID: $TenantID" -Severity Information
Write-Log "Note: Azure may display native subscription selection interface if multiple subscriptions are available" -Severity Information
# Establish Azure connection
Connect-AzAccount -TenantId $TenantID -ErrorAction Stop
# Verify successful authentication and retrieve context
$currentContext = Get-AzContext
if ($currentContext) {
Write-Log "Azure authentication successful" -Severity Information
Write-Log "Active subscription: '$($currentContext.Subscription.Name)' ($($currentContext.Subscription.Id))" -Severity Information
} else {
throw "Failed to establish Azure PowerShell context after authentication"
}
# Workspace Discovery and Selection
Write-Log "Discovering available Log Analytics workspaces..." -Severity Information
$workspaces = Get-AzOperationalInsightsWorkspace | Where-Object { $_.ProvisioningState -eq "Succeeded" }
if (-not $workspaces) {
throw "No accessible Log Analytics workspaces found. Verify Azure permissions and workspace existence."
}
Write-Log "Found $($workspaces.Count) accessible workspace(s)" -Severity Information
# Interactive workspace selection
$workspace = Select-Workspace -Workspaces $workspaces
if (-not $workspace) {
throw "Workspace selection failed - operation cannot continue"
}
Write-Log "Target workspace selected: '$($workspace.Name)' in resource group '$($workspace.ResourceGroupName)'" -Severity Information
# Table Discovery and Analysis
Write-Log "Beginning comprehensive table discovery for workspace '$($workspace.Name)'..." -Severity Information
$allTables = Get-AllWorkspaceTables -Workspace $workspace
if (-not $allTables -or $allTables.Count -eq 0) {
Write-Log "No manageable tables found in workspace '$($workspace.Name)'" -Severity Warning
Write-Log "This may indicate: no data ingestion, insufficient permissions, or workspace configuration issues" -Severity Warning
return
}
Write-Host "`nWorkspace Analysis Complete: $($allTables.Count) manageable tables discovered" -ForegroundColor Green
# Generate plan-based summary for user awareness
$planSummary = $allTables | Where-Object { $_.Plan -ne "Not Set" } | Group-Object Plan | Select-Object Name, Count
if ($planSummary) {
Write-Host "`nTable Distribution by Pricing Plan:" -ForegroundColor Cyan
foreach ($plan in $planSummary) {
Write-Host " ├─ $($plan.Name): $($plan.Count) tables" -ForegroundColor Yellow
}
Write-Host "" # Add spacing for readability
}
# User-Driven Filtering and Selection
# Use the TablePlan parameter to determine filtering approach
Write-Log "Using TablePlan parameter: '$TablePlan' for table filtering" -Severity Information
# Apply filtering based on the TablePlan parameter
if ($TablePlan -eq "Analytics" -or $TablePlan -eq "Basic") {
Write-Log "Applying plan filter: '$TablePlan'" -Severity Information
$planTables = Get-TablesByPlan -AllTables $allTables -Plan $TablePlan
if (-not $planTables -or $planTables.Count -eq 0) {
Write-Log "No tables found matching plan '$TablePlan' - operation terminated" -Severity Warning
return
}
$selectedTables = Select-Tables -AllTables $planTables -FilterByPlan $TablePlan
} else {
# Fallback to showing all tables if TablePlan is somehow invalid
Write-Log "Presenting all available tables for selection" -Severity Information
$selectedTables = Select-Tables -AllTables $allTables
}
Write-Log "Table selection complete: $($selectedTables.Count) tables chosen for retention update" -Severity Information
# Update Confirmation and Validation
Write-Host "`nPreparing retention update confirmation interface..." -ForegroundColor Yellow
# Prepare comprehensive confirmation data showing before/after state
$confirmationData = $selectedTables | Select-Object @{
Name = 'TableName'
Expression = { $_.TableName }
}, @{
Name = 'CurrentRetention'
Expression = { "$($_.TotalRetentionInDays) days" }
}, @{
Name = 'NewRetention'
Expression = { "$RetentionDays days" }
}, @{
Name = 'Plan'
Expression = { $_.Plan }
}
# Present final confirmation interface
Write-Log "Presenting update confirmation interface for final user approval" -Severity Information
$confirmSelection = $confirmationData | Out-ConsoleGridView -Title "Confirm Table Updates - Select tables to proceed (ESC to cancel)"
# Validate final confirmation
if ($null -eq $confirmSelection -or $confirmSelection.Count -eq 0) {
Write-Log "Operation cancelled by user during final confirmation" -Severity Warning
return
}
# Determine final table set for updates
$confirmedTables = $selectedTables | Where-Object { $_.TableName -in $confirmSelection.TableName }
Write-Log "Final confirmation: $($confirmedTables.Count) tables approved for retention update to $RetentionDays days" -Severity Information
# Batch Retention Updates
Write-Log "Beginning batch retention updates..." -Severity Information
$successCount = 0
$errorCount = 0
foreach ($table in $confirmedTables) {
try {
# Construct Azure Management API endpoint for table configuration
$uri = "https://management.azure.com/subscriptions/$($table.SubscriptionId)/resourcegroups/$($table.ResourceGroupName)/providers/Microsoft.OperationalInsights/workspaces/$($table.WorkspaceName)/tables/$($table.TableName)?api-version=2021-12-01-preview"
# Prepare update payload
$body = @{
properties = @{
plan = $TablePlan
totalRetentionInDays = $RetentionDays
}
} | ConvertTo-Json
# Prepare authenticated request headers
$token = (Get-AzAccessToken).Token
$headers = @{
'Authorization' = "Bearer $token"
'Content-Type' = 'application/json'
}
# Execute retention update via REST API
Invoke-RestMethod -Uri $uri -Method PUT -Headers $headers -Body $body | Out-Null
Write-Log "✓ Successfully updated '$($table.TableName)': Plan=$TablePlan, Retention=$RetentionDays days" -Severity Information
$successCount++
}
catch {
Write-Log "✗ Failed to update '$($table.TableName)': $($_.Exception.Message)" -Severity Error
$errorCount++
}
}
# Execution Summary
Write-Log "Batch retention update completed - Summary: $successCount successful, $errorCount failed out of $($confirmedTables.Count) tables" -Severity Information
# Provide additional guidance if there were failures
if ($errorCount -gt 0) {
Write-Log "Review error messages above for failed updates. Common causes include insufficient permissions or invalid retention values for specific table types." -Severity Warning
}
Write-Log "Log Analytics Table Retention Management Script execution completed successfully" -Severity Information
Write-Log "Detailed execution log saved to: $LogFileName" -Severity Information
}
catch {
# Comprehensive error handling for any unhandled exceptions
Write-Log "Critical script execution failure: $($_.Exception.Message)" -Severity Error
Write-Log "Full error details: $($_.Exception)" -Severity Error
Write-Log "Script terminated due to unrecoverable error - review log file: $LogFileName" -Severity Error
throw
}
Subscribers Only Preview
What's Included:
- ✅ Prerequisites checker and automatic module installation
- ✅ Cross-platform compatibility (Windows, Linux, macOS)
- ✅ Detailed logging and audit trail functionality
System Requirements:
- PowerShell 7.4.x or later
- Azure account with Log Analytics Contributor permissions
- Internet connectivity for module installation
The script is designed for enterprise use whilst remaining accessible to smaller teams just starting their Azure journey. All code is provided with comprehensive error handling and follows PowerShell best practices.
What's your experience with Log Analytics retention management? Have you found other innovative approaches to simplify Azure administration tasks?
I hope you've found this guide helpful in enhancing your security posture. If you've enjoyed this content and would like to support more like it, please consider joining the Supporters Tier. Your support helps me continue creating practical security automation content for the community.