EntraAuth.Azure.LogAnalytics.psm1
|
$script:ModuleRoot = $PSScriptRoot class ServiceTransformAttribute : System.Management.Automation.ArgumentTransformationAttribute { [object] Transform([System.Management.Automation.EngineIntrinsics] $Intrinsics, [object] $InputData) { if ($null -eq $InputData) { return @{ Azure = 'Azure' } } if ($InputData -is [hashtable]) { return $InputData } if ($InputData -is [ordered]) { return $InputData } if ($InputData -is [string]) { return @{ Azure = $InputData } } if ($InputData.Azure -or $InputData.LogAnalytics) { $map = @{ } if ($InputData.Azure) { $map.Azure = $InputData.Azure } if ($InputData.LogAnalytics) { $map.LogAnalytics = $InputData.LogAnalytics } return $map } return @{ Azure = $InputData -as [string] } } } [flags()] enum WorkspaceFeatures { DataAuthorizationMode = 1 DisableLocalAuth = 2 EnableDataExport = 4 OnlyResourceAccess = 8 ImmediateDataPurge = 16 } function ConvertTo-CollectionRule { <# .SYNOPSIS Converts an Azure data collection rule resource to a module collection rule object. .DESCRIPTION Transforms a raw Azure Monitor data collection rule resource into an EntraAuth.Azure.LogAnalytics.DataCollectionRule object with normalized streams, destinations, data flows, status, timestamps, and source-object properties. .PARAMETER InputObject The raw Azure Monitor data collection rule resource to convert. Null input produces no output. .EXAMPLE PS C:\> $ruleResource | ConvertTo-CollectionRule Converts a raw Azure data collection rule resource into the module's standard collection rule object. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] $InputObject ) process { if (-not $InputObject) { return } $streams = @{} foreach ($streamName in $InputObject.properties.streamDeclarations.PSObject.Properties.Name) { $streams[$streamName] = [PSCustomObject]@{ Name = $streamName Columns = $InputObject.properties.streamDeclarations.$streamName.columns Object = $InputObject.properties.streamDeclarations.$streamName } } [PSCustomObject]@{ PSTypeName = 'EntraAuth.Azure.LogAnalytics.DataCollectionRule' Subscription = ($InputObject.id -split '/')[2] ResourceGroup = ($InputObject.id -split '/')[4] Name = $InputObject.name ID = $InputObject.id Location = $InputObject.location Streams = $streams DataSources = $InputObject.properties.dataSources Destinations = $InputObject.properties.destinations DataFlows = $InputObject.properties.dataFlows EndpointName = $InputObject.properties.dataCollectionEndpointId -replace '^.+/' EndpointID = $InputObject.properties.dataCollectionEndpointId ImmutableID = $InputObject.properties.immutableId Status = $InputObject.properties.provisioningState Tags = $InputObject.tags Created = $InputObject.systemData.createdAt Modified = $InputObject.systemData.lastModifiedAt Properties = $InputObject.properties CollectionRuleName = $InputObject.name Object = $InputObject } } } function ConvertTo-Endpoint { <# .SYNOPSIS Converts an Azure data collection endpoint resource to a module endpoint object. .DESCRIPTION Transforms a raw Azure Monitor data collection endpoint resource into an EntraAuth.Azure.LogAnalytics.DataCollectionEndpoint object with normalized resource, ingestion link, status, timestamp, and source-object properties. .PARAMETER InputObject The raw Azure Monitor data collection endpoint resource to convert. Null input produces no output. .EXAMPLE PS C:\> $endpointResource | ConvertTo-Endpoint Converts a raw Azure data collection endpoint resource into the module's standard endpoint object. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] $InputObject ) process { if (-not $InputObject) { return } [PSCustomObject]@{ PSTypeName = 'EntraAuth.Azure.LogAnalytics.DataCollectionEndpoint' Subscription = ($InputObject.id -split '/')[2] ResourceGroup = ($InputObject.id -split '/')[4] Name = $InputObject.name ID = $InputObject.id Location = $InputObject.location LinkIngestion = $InputObject.properties.logsIngestion.endpoint LinkMetrics = $InputObject.properties.metricsIngestion.endpoint LinkConfiguration = $InputObject.properties.configurationAccess.endpoint ImmutableID = $InputObject.properties.immutableId PublicNetwork = $InputObject.properties.networkAcls.publicNetworkAccess Status = $InputObject.properties.provisioningState Tags = $InputObject.tags Created = $InputObject.systemData.createdAt Modified = $InputObject.systemData.lastModifiedAt Properties = $InputObject.properties EndpointName = $InputObject.name Object = $InputObject } } } function ConvertTo-Table { <# .SYNOPSIS Converts an Azure table resource to a module table object. .DESCRIPTION Transforms a raw Azure Log Analytics table resource into an EntraAuth.Azure.LogAnalytics.Table object with normalized subscription, resource group, workspace, schema, retention, and source-object properties. .PARAMETER InputObject The raw Azure Log Analytics table resource to convert. Null input produces no output. .EXAMPLE PS C:\> $tableResource | ConvertTo-Table Converts a raw Azure table resource returned by the Azure API into the module's standard table object. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] $InputObject ) process { if (-not $InputObject) { return } [PSCustomObject]@{ PSTypeName = 'EntraAuth.Azure.LogAnalytics.Table' Subscription = ($InputObject.id -split '/')[2] ResourceGroup = ($InputObject.id -split '/')[4] WorkspaceName = ($InputObject.id -split '/')[8] Name = $InputObject.name DisplayName = $InputObject.properties.schema.displayName Description = $InputObject.properties.schema.description ID = $InputObject.id Plan = $InputObject.properties.plan ProtectionLevel = $InputObject.properties.protectionLevel RetentionInDays = $InputObject.properties.retentionInDays TotalRetentionInDays = $InputObject.properties.totalRetentionInDays Solutions = $InputObject.properties.schema.solutions Columns = $InputObject.properties.schema.columns ProvisioningState = $InputObject.properties.provisioningState Properties = $InputObject.properties TableName = $InputObject.name Object = $InputObject } } } function ConvertTo-Workspace { <# .SYNOPSIS Converts an Azure workspace resource to a module workspace object. .DESCRIPTION Transforms a raw Azure Log Analytics workspace resource into an EntraAuth.Azure.LogAnalytics.Workspace object with normalized subscription, resource group, status, timestamps, and source-object properties. .PARAMETER InputObject The raw Azure Log Analytics workspace resource to convert. Null input produces no output. .EXAMPLE PS C:\> $workspaceResource | ConvertTo-Workspace Converts a raw Azure workspace resource returned by the Azure API into the module's standard workspace object. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] $InputObject ) process { if (-not $InputObject) { return } [PSCustomObject]@{ PSTypeName = 'EntraAuth.Azure.LogAnalytics.Workspace' Subscription = ($InputObject.id -split '/')[2] ResourceGroup = ($InputObject.id -split '/')[4] Name = $InputObject.name ID = $InputObject.id Location = $InputObject.location Status = $InputObject.properties.provisioningState Tags = $InputObject.tags Created = $InputObject.properties.createdDate Modified = $InputObject.properties.modifiedDate Properties = $InputObject.properties # For commands taking a workspace by pipeline WorkspaceName = $InputObject.name Object = $InputObject } } } function Resolve-Subscription { <# .SYNOPSIS Resolves a subscription name or ID to a subscription ID. .DESCRIPTION Returns a supplied subscription ID unchanged or resolves an exact subscription display name through Azure. Name resolution succeeds only when exactly one subscription matches and reports an error through the calling cmdlet for missing or ambiguous names. .PARAMETER Name The subscription display name or subscription ID to resolve. .PARAMETER Services A hashtable containing the service mappings used to query subscriptions. .PARAMETER Cache A hashtable caching subscriptions retrieved. Use to avoid repeated lookups within a command, but be sure to discard it when switching tenants. .PARAMETER Cmdlet The $PSCmdlet variable of the calling command, used to ensure errors happen within the scope of the caller, hiding this internal helper command from the user. .EXAMPLE PS C:\> Resolve-Subscription -Name 'Production' -Services $services -Cmdlet $PSCmdlet Resolves the subscription whose display name is Production using the supplied services and returns its subscription ID, reporting any resolution error through the calling cmdlet. #> [OutputType([string])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [hashtable] $Services, [hashtable] $Cache = @{}, [Parameter(Mandatory = $true)] $Cmdlet ) process { # We don't validate GUIDs - we assume the user knew better # Presumably, bad input would still only lead to subsequent request failing if ($Name -as [guid]) { return $Name } if ($Cache.Count -lt 1) { foreach ($subscription in Get-EaaSubscription -ServiceMap $Services) { $Cache[$subscription.id] = $subscription } } $subscriptions = $Cache.Values | Where-Object DisplayName -EQ $Name if (@($subscriptions).Count -eq 1) { return $subscriptions.SubscriptionID } if (@($subscriptions).Count -gt 1) { Stop-PSFFunction -Message "Ambiguous Subscription! $Name resolved to $(@($subscriptions).Count) subscriptions ($($subscriptions.SubscriptionID -join ', '))" -Cmdlet $Cmdlet -EnableException $true } Stop-PSFFunction -Message "Invalid Subscription! $Name could not be resolved" -Cmdlet $Cmdlet -EnableException $true } } function Get-EalaDataCollectionEndpoint { <# .SYNOPSIS Retrieves Azure Monitor data collection endpoints. .DESCRIPTION Retrieves data collection endpoints from an Azure subscription. Results can be scoped to a resource group and filtered by name with wildcard patterns. .PARAMETER Subscription The name or ID of the Azure subscription containing the data collection endpoints. .PARAMETER ResourceGroup The resource group containing the data collection endpoints. When omitted, endpoints from all resource groups in the subscription are returned. .PARAMETER Name The endpoint name or wildcard pattern to retrieve. When omitted, all matching endpoints are returned. Defaults to: * .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Get-EalaDataCollectionEndpoint -Subscription 'Production' -ResourceGroup 'rg-monitoring' -Name 'dce-*' Retrieves all data collection endpoints whose names begin with dce- from the specified resource group. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(ValueFromPipelineByPropertyName = $true)] [string] $Name, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet if ($Name -and $Name -notmatch '\*' -and $ResourceGroup) { Invoke-EntraRequest -Service $services.Azure -Path "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.Insights/dataCollectionEndpoints/$Name" -Query @{ 'api-version' = '2024-03-11' } | ConvertTo-Endpoint return } if (-not $Name) { $Name = '*' } $rgString = '' if ($ResourceGroup) { $rgString = "resourceGroups/$ResourceGroup/" } Invoke-EntraRequest -Service $services.Azure -Path "subscriptions/$subscriptionID/$($rgString)providers/Microsoft.Insights/dataCollectionEndpoints" -Query @{ 'api-version' = '2024-03-11' } | Where-Object Name -Like $Name | ConvertTo-Endpoint } } function New-EalaDataCollectionEndpoint { <# .SYNOPSIS Creates an Azure Monitor data collection endpoint. .DESCRIPTION Creates a data collection endpoint with optional network access, platform, identity, tag, and SKU settings. If no location is specified, the resource group's location is used. .PARAMETER Subscription The name or ID of the target Azure subscription. .PARAMETER ResourceGroup The name of the resource group in which to create the endpoint. .PARAMETER Name The name of the data collection endpoint to create. .PARAMETER Location The Azure region in which to create the endpoint. Defaults to: Location of the Resource Group .PARAMETER Description A description of the data collection endpoint. .PARAMETER NetworkAccess The public network access mode. Valid values are Enabled, Disabled, and SecuredByPerimeter. Defaults to: Enabled .PARAMETER Tags A hashtable of tags to assign to the endpoint. .PARAMETER Kind The operating system kind associated with the endpoint. Valid values are Windows and Linux. .PARAMETER SystemIdentity Enables a system-assigned managed identity on the endpoint. .PARAMETER Sku A hashtable defining the endpoint SKU. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> New-EalaDataCollectionEndpoint -Subscription 'Production' -ResourceGroup 'rg-monitoring' -Name 'dce-prod' -Location 'eastus' -NetworkAccess SecuredByPerimeter -SystemIdentity Creates a data collection endpoint with perimeter-secured network access and a system-assigned identity. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true)] [string] $Name, [string] $Location, [string] $Description, [ValidateSet('Enabled', 'Disabled', 'SecuredByPerimeter')] [string] $NetworkAccess = 'Enabled', [hashtable] $Tags, [ValidateSet('Windows', 'Linux')] [string] $Kind, [switch] $SystemIdentity, [hashtable] $Sku, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} # https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet $actualLocation = $Location if (-not $actualLocation) { $actualLocation = (Get-EaaResourceGroup -ServiceMap $services -Subscription $subscriptionID -Name $ResourceGroup).Location } $body = @{ location = $actualLocation properties = @{ networkAcls = @{ publicNetworkAccess = $NetworkAccess } } } if ($Description) { $body.properties.description = $Description } if ($Kind) { $body.kind = $Kind } if ($Tags) { $body.tags = $Tags } if ($Sku) { $body.sku = $Sku } if ($SystemIdentity) { $body.identity = @{ tenantId = (Get-EntraToken -Service $services.Azure).TenantId type = 'SystemAssigned' } } $param = @{ Service = $services.Azure Method = 'PUT' Path = "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.Insights/dataCollectionEndpoints/$Name" ContentType = 'application/json' Query = @{ 'api-version' = '2024-03-11' } } Invoke-PSFProtectedCommand -Action "Creating Data Collection Endpoint $Name in $subscriptionID > $ResourceGroup" -Target $Name -ScriptBlock { Invoke-EntraRequest @param -Body $body | ConvertTo-Endpoint } -EnableException $true -PSCmdlet $PSCmdlet } } function Remove-EalaDataCollectionEndpoint { <# .SYNOPSIS Removes an Azure Monitor data collection endpoint. .DESCRIPTION Deletes a data collection endpoint from an Azure subscription and resource group. Endpoint objects can be supplied through the pipeline by property name. .PARAMETER Subscription The name or ID of the Azure subscription containing the endpoint. .PARAMETER ResourceGroup The name of the resource group containing the endpoint. .PARAMETER Name The name of the data collection endpoint to remove. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .EXAMPLE PS C:\> Get-EalaDataCollectionEndpoint -Subscription 'Test' -ResourceGroup 'rg-test' -Name 'dce-old' | Remove-EalaDataCollectionEndpoint Retrieves a data collection endpoint and removes it through the pipeline. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $Name, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet Invoke-PSFProtectedCommand -Action "Deleting Data Collection Endpoint $Name in $subscriptionID > $ResourceGroup" -Target $Name -ScriptBlock { Invoke-EntraRequest -Service $services.Azure -Method DELETE -Path "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.Insights/dataCollectionEndpoints/$Name" -Query @{ 'api-version' = '2024-03-11' } } -EnableException $true -PSCmdlet $PSCmdlet } } function Get-EalaDataCollectionRule { <# .SYNOPSIS Retrieves Azure Monitor data collection rules. .DESCRIPTION Retrieves data collection rules from an Azure subscription. Results can be scoped to a resource group and filtered by name with wildcard patterns. .PARAMETER Subscription The name or ID of the Azure subscription containing the data collection rules. .PARAMETER ResourceGroup The resource group containing the rules. When omitted, rules from all resource groups in the subscription are returned. .PARAMETER Name The rule name or wildcard pattern to retrieve. When omitted, all matching rules are returned. Defaults to: * .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Get-EalaDataCollectionRule -Subscription 'Production' -ResourceGroup 'rg-monitoring' -Name 'dcr-app-*' Retrieves data collection rules whose names begin with dcr-app- from the specified resource group. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [string] $Name, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet if ($Name -and $Name -notmatch '\*' -and $ResourceGroup) { Invoke-EntraRequest -Service $services.Azure -Path "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.Insights/dataCollectionRules/$Name" -Query @{ 'api-version' = '2024-03-11' } | ConvertTo-CollectionRule return } if (-not $Name) { $Name = '*' } $rgString = '' if ($ResourceGroup) { $rgString = "resourceGroups/$ResourceGroup/" } Invoke-EntraRequest -Service $services.Azure -Path "subscriptions/$subscriptionID/$($rgString)providers/Microsoft.Insights/dataCollectionRules" -Query @{ 'api-version' = '2024-03-11' } | Where-Object Name -Like $Name | ConvertTo-CollectionRule } } function New-EalaDataCollectionRule { <# .SYNOPSIS Creates an Azure Monitor data collection rule. .DESCRIPTION Creates a data collection rule with configurable data sources, destinations, data flows, references, agent settings, identity, and SKU. A Log Analytics workspace can be added automatically as a destination. .PARAMETER Subscription The name or ID of the target Azure subscription. .PARAMETER ResourceGroup The name of the resource group in which to create the rule. .PARAMETER WorkspaceName The name of a Log Analytics workspace to add as a destination. .PARAMETER TableName The name of the table the DCR should send data to. Will be ignored if no WorkspaceName is provided. .PARAMETER Name The name of the data collection rule to create. .PARAMETER Location The Azure region in which to create the rule. Defaults to: Location of the Resource Group .PARAMETER Tags A hashtable of tags to assign to the rule. .PARAMETER Kind The operating system kind associated with the rule. Valid values are Windows and Linux. .PARAMETER SystemIdentity Enables a system-assigned managed identity on the rule. .PARAMETER Destinations A hashtable defining destinations for collected data. Example: @{ logAnalytics = @( @{ name = 'MyWorkspace' workspaceId = 'bcff068b-18e7-4105-be9c-caba3bea59ce' workspaceResourceId = '16df342b-f30f-4103-986b-12a2feea9992' } ) } https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create?view=rest-monitor-2024-03-11&tabs=HTTP#datacollectionruledestinations .PARAMETER EndpointID The Azure resource ID of the data collection endpoint to associate with the rule. .PARAMETER DataFlows An array of hashtables that map data streams to destinations and transformations. https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create?view=rest-monitor-2024-03-11&tabs=HTTP#dataflow .PARAMETER DataSources A hashtable defining the data sources collected by the rule. https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create?view=rest-monitor-2024-03-11&tabs=HTTP#datacollectionruledatasources .PARAMETER DirectDataSources A hashtable defining direct data sources for the rule. https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create?view=rest-monitor-2024-03-11&tabs=HTTP#datacollectionruledirectdatasources .PARAMETER References A hashtable defining reference data used by the rule. https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create?view=rest-monitor-2024-03-11&tabs=HTTP#datacollectionrulereferences .PARAMETER AgentSettings A hashtable defining settings for agents that use the rule. https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create?view=rest-monitor-2024-03-11&tabs=HTTP#datacollectionruleagentsettings .PARAMETER Sku A hashtable defining the rule SKU. https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create?view=rest-monitor-2024-03-11&tabs=HTTP#datacollectionruleresourcesku .PARAMETER FilePatterns What File Patterns to look for on a Monitoring Agent. Has no effect on messages/data sent directly, such as through the Write-EalaTableEntry command. Defaults to: C:\Test.json .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .EXAMPLE PS C:\> New-EalaDataCollectionRule -Subscription 'Production' -ResourceGroup 'rg-monitoring' -WorkspaceName 'law-prod' -Name 'dcr-app' -Location 'eastus' -Kind Windows Creates a Windows data collection rule and configures the specified workspace as a destination. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(ValueFromPipelineByPropertyName = $true)] [PsfValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$', ErrorMessage = 'Invalid Name: {0}. Workspace names must be 4-63 characters long and only contain regular letters, numbers, and dashes.')] [string] $WorkspaceName, [Parameter(ValueFromPipelineByPropertyName = $true)] [string] $TableName, [Parameter(Mandatory = $true)] [string] $Name, [string] $Location, [hashtable] $Tags, [ValidateSet('Windows', 'Linux')] [string] $Kind, [switch] $SystemIdentity, [hashtable] $Destinations, [string] $EndpointID, [hashtable[]] $DataFlows, [hashtable] $DataSources, [hashtable] $DirectDataSources, [hashtable] $References, [hashtable] $AgentSettings, [hashtable] $Sku, [string[]] $FilePatterns = @('C:\test.json'), [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} # https://learn.microsoft.com/en-us/rest/api/monitor/data-collection-rules/create } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet $actualLocation = $Location if (-not $actualLocation) { $actualLocation = (Get-EaaResourceGroup -ServiceMap $services -Subscription $subscriptionID -Name $ResourceGroup).Location } $body = @{ id = "/subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.Insights/dataCollectionRules/$Name" location = $actualLocation type = 'Microsoft.Insights/dataCollectionRules' name = $Name properties = @{ dataSources = @{} destinations = @{ logAnalytics = @() } } } if ($Destinations) { foreach ($key in $Destinations.Keys) { $body.properties.destinations[$key] = @($Destinations[$key]) } } if ($DataSources) { foreach ($key in $DataSources.Keys) { $body.properties.dataSources[$key] = @($DataSources[$key]) } } if ($DirectDataSources) { $body.properties.directDataSources = @{} foreach ($key in $DirectDataSources.Keys) { $body.properties.directDataSources[$key] = @($DirectDataSources[$key]) } } if ($References) { $body.properties.references = $References } if ($DataFlows) { $body.properties.dataFlows = @($DataFlows) } if ($EndpointID) { $body.properties.dataCollectionEndpointId = $EndpointID } if ($Kind) { $body.kind = $Kind } if ($AgentSettings) { $body.properties.agentSettings = $AgentSettings } if ($WorkspaceName) { $workspaceObject = Get-EalaWorkspace -Subscription $subscriptionID -ResourceGroup $ResourceGroup -Name $WorkspaceName -ServiceMap $services $body.properties.destinations.logAnalytics += @{ workspaceResourceId = $workspaceObject.ID workspaceId = $workspaceObject.Properties.customerId name = $workspaceObject.Name } if ($TableName) { $tableObject = $workspaceObject | Get-EalaTable -Name $TableName # 32 characters is the limit $streamname = 'Custom-{0}' -f $tableObject.Name if ($streamname.Length -gt 32) { $streamname = $streamname.SubString(0, 32) } $body.properties.streamDeclarations = @{ $streamname = @{ columns = @(@($tableObject.Columns).ForEach{ @{ name = $_.name; type = $_.type } }) } } $logFiles = $body.properties.dataSources['logFiles'] if (-not $logFiles) { $logFiles = @() } $logFiles += @{ streams = @($streamname) filePatterns = $FilePatterns format = 'json' name = $streamname } $body.properties.dataSources['logFiles'] = $logFiles $dataFlowsTemp = $body.properties.dataFlows if (-not $dataFlowsTemp) { $dataFlowsTemp = @() } $dataFlowsTemp += @{ streams = @($streamname) destinations = @($workspaceObject.Name) transformKql = 'source' outputStream = $streamname } $body.properties.dataFlows = $dataFlowsTemp } } if ($Tags) { $body.tags = $Tags } if ($Sku) { $body.sku = $Sku } if ($SystemIdentity) { $body.identity = @{ tenantId = (Get-EntraToken -Service $services.Azure).TenantId type = 'SystemAssigned' } } # PUT https://management.azure.com/subscriptions/fe923424-d71c-48fc-a446-29f295c1f08c/resourceGroups/rg_psframework/providers/Microsoft.Insights/dataCollectionRules/TestDCR?api-version=2023-03-11&ignoreMissingTables=true $param = @{ Service = $services.Azure Method = 'PUT' Path = "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.Insights/dataCollectionRules/$Name" ContentType = 'application/json' Query = @{ 'api-version' = '2024-03-11' ignoreMissingTables = $true } } Invoke-PSFProtectedCommand -Action "Creating Data Collection Rule $Name in $subscriptionID > $ResourceGroup" -Target $Name -ScriptBlock { Invoke-EntraRequest @param -Body $body | ConvertTo-CollectionRule } -EnableException $true -PSCmdlet $PSCmdlet } } function Remove-EalaDataCollectionRule { <# .SYNOPSIS Removes an Azure Monitor data collection rule. .DESCRIPTION Deletes a data collection rule from an Azure subscription and resource group. .PARAMETER Subscription The name or ID of the Azure subscription containing the rule. .PARAMETER ResourceGroup The name of the resource group containing the rule. .PARAMETER Name The name of the data collection rule to remove. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Get-EalaDataCollectionRule -Subscription 'Test' -ResourceGroup 'rg-test' -Name 'dcr-old' | Remove-EalaDataCollectionRule Retrieves a data collection rule and removes it through the pipeline. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $Name, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet Invoke-PSFProtectedCommand -Action "Deleting Data Collection Rule $Name in $subscriptionID > $ResourceGroup" -Target $Name -ScriptBlock { Invoke-EntraRequest -Service $services.Azure -Method DELETE -Path "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.Insights/dataCollectionRules/$Name" -Query @{ 'api-version' = '2024-03-11' } } -EnableException $true -PSCmdlet $PSCmdlet } } function Write-EalaTableEntry { <# .SYNOPSIS Writes records to an Azure Monitor Logs table. .DESCRIPTION Sends one or more objects to an Azure Monitor Logs ingestion endpoint through a data collection rule. The target stream can be specified directly or inferred from the table name and the streams defined by the rule. Endpoint, rule, and authentication data can be cached when the command is called repeatedly. .PARAMETER Message One or more objects to submit as records. The objects are serialized as a JSON array in the ingestion request. .PARAMETER Subscription The name or ID of the Azure subscription containing the data collection endpoint and rule. .PARAMETER ResourceGroup The name of the resource group containing the data collection endpoint and rule. When omitted, the command searches the subscription for those resources. .PARAMETER DcrName The name of the data collection rule that defines the target stream. .PARAMETER DceName The name of the data collection endpoint used to ingest the records. .PARAMETER Table The table name used to select a matching stream from the data collection rule when Stream is not specified. If the rule contains only one stream, that stream is selected automatically. Defaults to: <default> .PARAMETER Stream The exact data collection rule stream to which the records are written. Use this parameter when the stream cannot be inferred unambiguously from the table name. .PARAMETER Cache A reusable hashtable in which authentication tokens, endpoints, rules, and resolved streams are cached. Use the same hashtable across calls to avoid retrieving these values repeatedly. Defaults to: @{} .PARAMETER EntraToken An existing EntraAuth access token for https://monitor.azure.com/. When omitted, the command obtains a compatible token from the current Azure connection. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Write-EalaTableEntry -Message $records -Subscription 'Production' -ResourceGroup 'rg-monitoring' -DcrName 'dcr-app' -DceName 'dce-app' -Table 'AppLogs_CL' Writes the objects in $records to the stream matching the AppLogs_CL table. .EXAMPLE PS C:\> Write-EalaTableEntry -Message $record -Subscription 'Production' -ResourceGroup 'rg-monitoring' -DcrName 'dcr-app' -DceName 'dce-app' -Stream 'Custom-AppLogs' -Cache $cache Writes a record to an explicitly selected stream and reuses the supplied cache for repeated calls. #> [CmdletBinding(DefaultParameterSetName = 'ByTable')] param ( [Parameter(Mandatory = $true)] [object[]] $Message, [Parameter(Mandatory = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true)] [PsfArgumentCompleter('EntraAuth.Azure.LogAnalytics.DataCollectionRule')] [string] $DcrName, [Parameter(Mandatory = $true)] [PsfArgumentCompleter('EntraAuth.Azure.LogAnalytics.DataCollectionEndpoint')] [string] $DceName, [Parameter(ParameterSetName = 'ByTable')] [string] $Table = '<default>', [Parameter(Mandatory = $true, ParameterSetName = 'ByStream')] [string] $Stream, [hashtable] $Cache = @{}, $EntraToken, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { if (-not $cache.$Subscription) { $cache[$Subscription] = @{ Endpoint = @{} Rule = @{} } } #region Resolve Token $services = $script:_serviceSelector.GetServiceMap($ServiceMap) if ($EntraToken) { $tokenToUse = $EntraToken } elseif ($Cache.$Subscription.Token) { $tokenToUse = $Cache.$Subscription.Token } else { Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $azToken = Get-EntraToken -Service $services.Azure $commonParam = @{ ClientID = $azToken.ClientID TenantID = $azToken.TenantID Resource = 'https://monitor.azure.com/' AuthenticationUrl = $azToken.AuthenticationUrl } if ($azToken.RefreshToken) { $tokenToUse = Connect-EntraService @commonParam -Scopes '.default' -UseRefreshToken $Cache.$Subscription.Token = $tokenToUse } elseif ($azToken.Certificate) { $tokenToUse = Connect-EntraService @commonParam -Certificate $azToken.Certificate $Cache.$Subscription.Token = $tokenToUse } elseif ($azToken.ClientSecret) { $tokenToUse = Connect-EntraService @commonParam -ClientSecret $azToken.ClientSecret $Cache.$Subscription.Token = $tokenToUse } elseif ($azToken.Type -eq 'Identity') { $param = @{} if ($azToken.IdentityID) { $param.IdentityID = $azToken.IdentityID } if ($azToken.IdentityType) { $param.IdentityType = $azToken.IdentityType } $tokenToUse = Connect-EntraService @param -Identity -Resource 'https://monitor.azure.com/' -AuthenticationUrl $azToken.AuthenticationUrl $Cache.$Subscription.Token = $tokenToUse } elseif ($azToken.Type -eq 'Federated') { $tokenToUse = Connect-EntraService @commonParam -Federated -FederationProvider $azToken.FederationProvider.Name $Cache.$Subscription.Token = $tokenToUse } elseif ($azToken.Type -eq 'AzAccount') { $tokenToUse = Connect-EntraService -AsAzAccount -ShowDialog $azTOken.ShowDialog -Resource 'https://monitor.azure.com/' -AuthenticationUrl $azToken.AuthenticationUrl $Cache.$Subscription.Token = $tokenToUse } } #endregion Resolve Token } process { $commonParam = @{ Subscription = $Subscription ServiceMap = $ServiceMap } if ($ResourceGroup) { $commonParam.ResourceGroup = $ResourceGroup } if (-not $Cache.$Subscription.Endpoint[$DceName]) { $Cache.$Subscription.Endpoint[$DceName] = Get-EalaDataCollectionEndpoint @commonParam -Name $DceName if (-not $Cache.$Subscription.Endpoint[$DceName]) { Stop-PSFFunction -String 'Write-EalaTableEntry.Error.EndpointNotFound' -StringValues $DceName -EnableException $true -Category ObjectNotFound -Cmdlet $PSCmdlet } } if (-not $Cache.$Subscription.Rule[$DcrName]) { $Cache.$Subscription.Rule[$DcrName] = Get-EalaDataCollectionRule @commonParam -Name $DcrName | Add-Member -MemberType NoteProperty -Name _Streams -Value @{} -PassThru -Force if (-not $Cache.$Subscription.Rule[$DcrName]) { Stop-PSFFunction -String 'Write-EalaTableEntry.Error.RuleNotFound' -StringValues $DcrName -EnableException $true -Category ObjectNotFound -Cmdlet $PSCmdlet } } #region Calculate Stream if ($Stream) { $streamName = $Stream } else { $streams = $Cache.$Subscription.Rule[$DcrName].properties.dataFlows.streams if ($Cache.$Subscription.Rule[$DcrName]._Streams.$Table) { $streamName = $Cache.$Subscription.Rule[$DcrName]._Streams.$Table } elseif (@($streams).Count -eq 1) { $streamName = $($streams) $Cache.$Subscription.Rule[$DcrName]._Streams[$Table] = $streamName } elseif (@($streams | Where-Object { $_ -match $Table }).Count -eq 1) { $streamName = $streams | Where-Object { $_ -match $Table } $Cache.$Subscription.Rule[$DcrName]._Streams[$Table] = $streamName } elseif (-not $streams) { Stop-PSFFunction -String 'Write-EalaTableEntry.Error.NoStreams' -StringValues $DcrName -EnableException $true -Category InvalidData -Cmdlet $PSCmdlet } else { Stop-PSFFunction -String 'Write-EalaTableEntry.Error.AmbiguousStreams' -StringValues ($streams -join ', '), $Table -EnableException $true -Category InvalidData -Cmdlet $PSCmdlet } } #endregion Calculate Stream Invoke-EntraRequest -Method Post -Path "$($Cache.$Subscription.Endpoint[$DceName].LinkIngestion)/dataCollectionRules/$($Cache.$Subscription.Rule[$DcrName].ImmutableId)/streams/$streamName" -Query @{ 'api-version' = '2023-01-01' } -ContentType 'application/json' -Body @($Message) -Token $tokenToUse } } function Get-EalaColumnTemplate { <# .SYNOPSIS Retrieves registered table column templates. .DESCRIPTION Returns column templates registered in the current module session. Template names can be filtered with a wildcard pattern. .PARAMETER Name The template name or wildcard pattern to retrieve. Defaults to: * .EXAMPLE PS C:\> Get-EalaColumnTemplate -Name 'Audit*' Returns all registered column templates whose names begin with Audit. #> [CmdletBinding()] param ( [PsfArgumentCompleter('EntraAuth.Azure.LogAnalytics.TableColumnTemplate')] [string] $Name = '*' ) process { $script:_TableColumnTemplates.Values | Where-Object Name -Like $Name } } function Get-EalaTable { <# .SYNOPSIS Retrieves tables from a Log Analytics workspace. .DESCRIPTION Retrieves Log Analytics tables and filters them by name. Unless Literal is specified, the filter also matches the custom-table _CL suffix. .PARAMETER Subscription The name or ID of the Azure subscription containing the workspace. .PARAMETER ResourceGroup The name of the resource group containing the workspace. .PARAMETER WorkspaceName The name of the Log Analytics workspace containing the tables. .PARAMETER Name The table name or wildcard pattern to retrieve. Defaults to: * .PARAMETER Literal Matches only the supplied table name pattern and disables automatic matching of the _CL custom-table suffix. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Get-EalaTable -Subscription 'Production' -ResourceGroup 'rg-monitoring' -WorkspaceName 'law-prod' -Name 'AppLogs' Retrieves tables matching AppLogs or AppLogs_CL from the specified workspace. .EXAMPLE PS C:\> Get-EalaWorkspace -Subscription 570b5874-4ced-4cc7-92ad-82308ccc7f93 -Name pslogging | Get-EalaTable -Name ps_* Retrieves all tables that start with "ps_" in the workspace "pslogging" under the specified subscription. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$', ErrorMessage = 'Invalid Name: {0}. Workspace names must be 4-63 characters long and only contain regular letters, numbers, and dashes.')] [string] $WorkspaceName, [Parameter(ValueFromPipelineByPropertyName = $true)] [string] $Name = '*', [switch] $Literal, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet Invoke-EntraRequest -Service $services.Azure -Path "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.OperationalInsights/workspaces/$WorkspaceName/tables/" -Query @{ 'api-version' = '2026-03-01' } | ConvertTo-Table | Where-Object { $_.Name -like $Name -or ( -not $Literal -and $_.Name -like "$($Name)_CL" ) } } } function New-EalaTable { <# .SYNOPSIS Creates a table in a Log Analytics workspace. .DESCRIPTION Creates a Log Analytics table from an explicit column schema or a registered column template. Custom table names receive the _CL suffix unless Literal is specified. .PARAMETER Subscription The name or ID of the target Azure subscription. .PARAMETER ResourceGroup The name of the resource group containing the workspace. .PARAMETER WorkspaceName The name of the Log Analytics workspace in which to create the table. .PARAMETER Name The table name. Unless Literal is specified, _CL is appended when it is not already present. .PARAMETER Plan The table plan. Options: - Basic: Can be queried, may have significant delays, lower cost - Analytics: All features, suitable for realtimne detection, but high cost - Auxiliary: Long Term Archive that is rarely read. Lowest cost. Defaults to: Basic .PARAMETER Categories The categories to associate with the table. Defaults to: @() .PARAMETER Columns An array of hashtables defining the table columns. Defaults to: @() Example: @{ name = 'Message' type = 'string' } @{ name = 'TimeGenerated' type = 'datetime' } @{ name = 'Level' type = 'string' } @{ name = 'Tags' type = 'dynamic' } Types: string, int, long, real, boolean, dateTime, guid, dynamic https://learn.microsoft.com/en-us/rest/api/loganalytics/tables/create-or-update?view=rest-loganalytics-2026-03-01&tabs=HTTP#column .PARAMETER ColumnTemplate The name of a registered column template. Column templates / presets can be defined using "Register-EalaColumnTemplate" and searched using "Get-EalaColumnTemplate". .PARAMETER Description A description of the table. .PARAMETER DisplayName The display name of the table. .PARAMETER Labels The labels to associate with the table. .PARAMETER Solutions The Azure solutions associated with the table. .PARAMETER RetentionInDays The interactive retention period, from 4 through 730 days. This parameter is supported only for the Analytics plan. .PARAMETER TotalRetentionInDays The total retention period, from 4 through 4383 days. .PARAMETER ProtectionLevel The table data protection level. Valid values are General and Protected. .PARAMETER Literal Uses Name exactly as supplied and disables automatic addition of the _CL suffix. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> New-EalaTable -Subscription 'Production' -ResourceGroup 'rg-monitoring' -WorkspaceName 'law-prod' -Name 'AppMetrics' -Plan Analytics -RetentionInDays 90 -ColumnTemplate 'StandardMetrics' Creates AppMetrics_CL from a registered column template with 90 days of interactive retention. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$', ErrorMessage = 'Invalid Name: {0}. Workspace names must be 4-63 characters long and only contain regular letters, numbers, and dashes.')] [string] $WorkspaceName, [Parameter(Mandatory = $true)] [PsfValidatePattern('^[A-Za-z0-9-_]+$', ErrorMessage = 'Invalid Name: {0}. Table-names may only contain default letters of the 26-letter alphabet, numbers, dash and underscore')] [string] $Name, [ValidateSet('Basic', 'Analytics', 'Auxiliary')] [string] $Plan = 'Basic', [string[]] $Categories = @(), [Parameter(Mandatory = $true, ParameterSetName = 'Columns')] [PsfValidateScript('EntraAuth.Azure.LogAnalytics.TableColumnValidation')] [hashtable[]] $Columns, [Parameter(Mandatory = $true, ParameterSetName = 'Template')] [PsfArgumentCompleter('EntraAuth.Azure.LogAnalytics.TableColumnTemplate')] [PsfValidateSet(TabCompletion = 'EntraAuth.Azure.LogAnalytics.TableColumnTemplate')] [string] $ColumnTemplate, [string] $Description, [string] $DisplayName, [string[]] $Labels, [string[]] $Solutions, [ValidateRange(4, 730)] [int] $RetentionInDays = -1, [ValidateRange(4, 4383)] [int] $TotalRetentionInDays = -1, [ValidateSet('General', 'Protected')] [string] $ProtectionLevel, [switch] $Literal, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} if ($PSBoundParameters.Keys -contains 'RetentionInDays' -and $Plan -ne 'Analytics') { Stop-PSFFunction -Message '"RetentionInDays" can be only specified in combination with an "Analytics" Plan' -EnableException $true -Cmdlet $PSCmdlet } if ($Columns) { $effectiveColumns = $Columns } else { $effectiveColumns = $script:_TableColumnTemplates[$ColumnTemplate].Columns } if (-not $effectiveColumns) { Stop-PSFFunction -Message 'No columns specified / resolved to!' -EnableException $true -Cmdlet $PSCmdlet } if (-not $Literal) { if ($Name -notmatch '_CL$') { $Name = "$($Name)_CL" } } } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet $body = @{ properties = @{ plan = $Plan schema = @{ columns = @($effectiveColumns) name = $Name } totalRetentionInDays = $TotalRetentionInDays } } if ($ProtectionLevel) { $body.properties.protectionLevel = $ProtectionLevel } if ($Plan -eq 'Analytics') { $body.properties.retentionInDays = $RetentionInDays } if ($Categories) { $body.properties.schema.categories = @($Categories) } if ($Description) { $body.properties.schema.description = $Description } if ($DisplayName) { $body.properties.schema.displayName = $DisplayName } if ($Labels) { $body.properties.schema.labels = @($Labels) } if ($Solutions) { $body.properties.schema.solutions = @($Solutions) } Invoke-PSFProtectedCommand -Action "Creating table $Name in $subscriptionID > $ResourceGroup > $WorkspaceName" -Target $Name -ScriptBlock { Invoke-EntraRequest -Service $services.Azure -Method PUT -Path "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.OperationalInsights/workspaces/$WorkspaceName/tables/$Name" -Query @{ 'api-version' = '2026-03-01' } -Body $body -ContentType 'application/json' | ConvertTo-Table } -EnableException $true -PSCmdlet $PSCmdlet } } function Register-EalaColumnTemplate { <# .SYNOPSIS Registers a reusable table column template. .DESCRIPTION Registers a named column schema in the current module session for use by New-EalaTable. This allows a module that needs a Log Analytics table to prepare just how that should be set up, simplifying deployment for users of that module. .PARAMETER Name The unique name used to identify the column template. .PARAMETER Description A description of the template and its intended use. .PARAMETER Columns An array of hashtables defining the names and data types of the template columns. Example: @{ name = 'Message' type = 'string' } @{ name = 'TimeGenerated' type = 'datetime' } @{ name = 'Level' type = 'string' } @{ name = 'Tags' type = 'dynamic' } Types: string, int, long, real, boolean, dateTime, guid, dynamic Documentation: https://learn.microsoft.com/en-us/rest/api/loganalytics/tables/create-or-update?view=rest-loganalytics-2026-03-01&tabs=HTTP#column .EXAMPLE PS C:\> Register-EalaColumnTemplate -Name 'AuditLog' -Description 'Standard audit event columns' -Columns @(@{ Name = 'EventId'; Type = 'int' }, @{ Name = 'Message'; Type = 'string' }) Registers an AuditLog template containing integer EventId and string Message columns. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name, [string] $Description, [Parameter(Mandatory = $true)] [PsfValidateScript('EntraAuth.Azure.LogAnalytics.TableColumnValidation')] [hashtable[]] $Columns ) process { $script:_TableColumnTemplates[$Name] = [PSCustomObject]@{ PSTypeName = 'EntraAuth.Azure.LogAnalytics.ColumnTemplate' Name = $Name Description = $Description Columns = $Columns } } } function Remove-EalaTable { <# .SYNOPSIS Removes a table from a Log Analytics workspace. .DESCRIPTION Deletes a table from a Log Analytics workspace. .PARAMETER Subscription The name or ID of the Azure subscription containing the workspace. .PARAMETER ResourceGroup The name of the resource group containing the workspace. .PARAMETER WorkspaceName The name of the Log Analytics workspace containing the table. .PARAMETER Name The name of the table to remove. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Get-EalaTable -Subscription 'Test' -ResourceGroup 'rg-test' -WorkspaceName 'law-test' -Name 'TempData' | Remove-EalaTable Retrieves a table and removes it through the pipeline. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$', ErrorMessage = 'Invalid Name: {0}. Workspace names must be 4-63 characters long and only contain regular letters, numbers, and dashes.')] [string] $WorkspaceName, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfValidatePattern('^[A-Za-z0-9-_]+$', ErrorMessage = 'Invalid Name: {0}. Table-names may only contain default letters of the 26-letter alphabet, numbers, dash and underscore')] [string] $Name, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet Invoke-PSFProtectedCommand -Action "Deleting table $Name from $subscriptionID > $ResourceGroup > $WorkspaceName" -Target $Name -ScriptBlock { Invoke-EntraRequest -Service $services.Azure -Method DELETE -Path "subscriptions/$subscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.OperationalInsights/workspaces/$WorkspaceName/tables/$Name" -Query @{ 'api-version' = '2026-03-01' } } -EnableException $true -PSCmdlet $PSCmdlet } } function Get-EalaWorkspace { <# .SYNOPSIS Retrieves Log Analytics workspaces. .DESCRIPTION Retrieves Log Analytics workspaces from an Azure subscription. Results can be scoped to a resource group and filtered by workspace name. .PARAMETER Subscription The name or ID of the Azure subscription containing the workspaces. .PARAMETER ResourceGroup The resource group containing the workspaces. When omitted, workspaces from all resource groups in the subscription are returned. .PARAMETER Name The name of the workspace to retrieve. When omitted, all matching workspaces are returned. Defaults to: * .PARAMETER ID The full Azure Resource ID of the Workspace to retrieve. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Get-EalaWorkspace -Subscription 'Production' -ResourceGroup 'rg-monitoring' -Name 'law-prod' Retrieves the law-prod workspace from the specified resource group. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Search')] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Search')] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Search')] [PsfValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$', ErrorMessage = 'Invalid Name: {0}. Workspace names must be 4-63 characters long and only contain regular letters, numbers, and dashes.')] [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'ByID')] $ID, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { if ($ID) { Invoke-EntraRequest -Service $services.Azure -Path $ID -Query @{ 'api-version' = '2026-03-01' } | ConvertTo-Workspace return } $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet $rgString = '' if ($ResourceGroup) { $rgString = "resourcegroups/$ResourceGroup/" } if ($Name -and $ResourceGroup) { Invoke-EntraRequest -Service $services.Azure -Path "subscriptions/$subscriptionID/$($rgString)providers/Microsoft.OperationalInsights/workspaces/$Name" -Query @{ 'api-version' = '2026-03-01' } | ConvertTo-Workspace return } $nameFilter = '*' if ($Name) { $nameFilter = $Name } Invoke-EntraRequest -Service $services.Azure -Path "subscriptions/$subscriptionID/$($rgString)providers/Microsoft.OperationalInsights/workspaces" -Query @{ 'api-version' = '2026-03-01' } | ConvertTo-Workspace | Where-Object Name -Like $nameFilter } } function New-EalaWorkspace { <# .SYNOPSIS Creates a Log Analytics workspace. .DESCRIPTION Creates a Log Analytics workspace with optional tags, identity, retention, SKU, feature flags, replication, and custom properties. .PARAMETER Subscription The name or ID of the target Azure subscription. .PARAMETER ResourceGroup The name of the resource group in which to create the workspace. .PARAMETER Name The name of the Log Analytics workspace to create. .PARAMETER Location The Azure region in which to create the workspace. Defaults to: Location of the Resource Group .PARAMETER Etag An entity tag used for optimistic concurrency control. .PARAMETER Tags A hashtable of tags to assign to the workspace. .PARAMETER SystemIdentity Enables a system-assigned managed identity on the workspace. .PARAMETER RetentionDays The workspace data retention period in days. The minimum value is 4. .PARAMETER Sku The pricing SKU to assign to the workspace. .PARAMETER Features One or more WorkspaceFeatures flags to enable. .PARAMETER ReplicationLocation The secondary Azure region in which to enable workspace replication. .PARAMETER Properties A hashtable of additional workspace properties. These values override properties generated by the command. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> New-EalaWorkspace -Subscription 'Production' -ResourceGroup 'rg-monitoring' -Name 'law-prod' -Location 'eastus' -Sku 'PerGB2018' -RetentionDays 90 -Features DisableLocalAuth Creates a workspace with 90-day retention and local authentication disabled. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true)] [PsfValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$', ErrorMessage = 'Invalid Name: {0}. Workspace names must be 4-63 characters long and only contain regular letters, numbers, and dashes.')] [string] $Name, [PsfArgumentCompleter('EntraAuth.Azure.Location')] [string] $Location, [string] $Etag, [hashtable] $Tags, [switch] $SystemIdentity, [Alias('retentionInDays')] [int] $RetentionDays, [PsfArgumentCompleter('EntraAuth.Azure.LogAnalytics.Sku')] [string] $Sku, [WorkspaceFeatures] $Features, [string] $ReplicationLocation, [hashtable] $Properties, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cmdlet $PSCmdlet } process { $actualLocation = $Location if (-not $actualLocation) { $actualLocation = (Get-EaaResourceGroup -ServiceMap $services -Subscription $subscriptionID -Name $ResourceGroup).Location } $body = @{ location = $actualLocation } if ($Etag) { $body.etag = $Etag } if ($SystemIdentity) { $body.identity = @{ tenantId = (Get-EntraToken -Service $services.Azure).TenantId type = 'SystemAssigned' } } if ($Tags) { $body.tags = $Tags } $propertySet = @{} foreach ($key in $Properties.Keys) { $propertySet[$key] = $Properties[$key] } if ($PSBoundParameters.Keys -contains 'RetentionDays') { $propertySet['retentionInDays'] = $RetentionDays } if ($PSBoundParameters.Keys -contains 'Sku') { $propertySet['sku'] = @{ name = $Sku } } if ($PSBoundParameters.Keys -contains 'Features') { $propertySet['features'] = @{ } if ($Features -band [WorkspaceFeatures]::DataAuthorizationMode) { $propertySet['features']['dataAuthorizationMode'] = $true } if ($Features -band [WorkspaceFeatures]::DisableLocalAuth) { $propertySet['features']['disableLocalAuth'] = $true } if ($Features -band [WorkspaceFeatures]::EnableDataExport) { $propertySet['features']['enableDataExport'] = $true } if ($Features -band [WorkspaceFeatures]::OnlyResourceAccess) { $propertySet['features']['enableLogAccessUsingOnlyResourcePermissions'] = $true } if ($Features -band [WorkspaceFeatures]::ImmediateDataPurge) { $propertySet['features']['immediatePurgeDataOn30Days'] = $true } } if ($ReplicationLocation) { $propertySet.replication = @{ enabled = $true location = $ReplicationLocation } } $body.properties = $propertySet Invoke-PSFProtectedCommand -Action "Creating Azure Log Analytics Workspace $Name in $SubscriptionID/$ResourceGroup" -Target $Name -ScriptBlock { Invoke-EntraRequest -Service $services.Azure -Method PUT -Path "subscriptions/$subscriptionID/resourcegroups/$ResourceGroup/providers/Microsoft.OperationalInsights/workspaces/$Name" -Query @{ 'api-version' = '2026-03-01' } -Body $body -ContentType 'application/json' | ConvertTo-Workspace } -EnableException $true -PSCmdlet $PSCmdlet } } function Remove-EalaWorkspace { <# .SYNOPSIS Removes a Log Analytics workspace. .DESCRIPTION Deletes a Log Analytics workspace from an Azure subscription and resource group. .PARAMETER Subscription The name or ID of the Azure subscription containing the workspace. .PARAMETER ResourceGroup The name of the resource group containing the workspace. .PARAMETER Name The name of the Log Analytics workspace to remove. .PARAMETER Force Request forced deletion. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Remove-EalaWorkspace -Subscription 'Test' -ResourceGroup 'rg-test' -Name 'law-old' -Force Forces deletion of the specified Log Analytics workspace. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$', ErrorMessage = 'Invalid Name: {0}. Workspace names must be 4-63 characters long and only contain regular letters, numbers, and dashes.')] [string] $Name, [switch] $Force, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet $query = @{ 'api-version' = '2026-03-01' } if ($Force) { $query.force = $true } Invoke-PSFProtectedCommand -Action "Deleting Azure Log Analytics Workspace $Name in $SubscriptionID/$ResourceGroup" -Target $Name -ScriptBlock { $null = Invoke-EntraRequest -Service $services.Azure -Method Delete -Path "subscriptions/$subscriptionID/resourcegroups/$ResourceGroup/providers/Microsoft.OperationalInsights/workspaces/$Name" -Query $query } -PSCmdlet $PSCmdlet -EnableException $true } } function Set-EalaWorkspace { <# .SYNOPSIS Updates a Log Analytics workspace. .DESCRIPTION Updates tags, identity, retention, SKU, features, replication, or custom properties on an existing Log Analytics workspace. At least one setting must be specified. .PARAMETER Subscription The name or ID of the Azure subscription containing the workspace. .PARAMETER ResourceGroup The name of the resource group containing the workspace. .PARAMETER Name The name of the Log Analytics workspace to update. .PARAMETER Tags A hashtable of tags to apply to the workspace. .PARAMETER SystemIdentity Enables a system-assigned managed identity on the workspace. .PARAMETER RetentionDays The workspace data retention period in days. .PARAMETER Sku The pricing SKU to assign to the workspace. .PARAMETER EnableFeatures One or more WorkspaceFeatures flags to enable. .PARAMETER DisableFeatures One or more WorkspaceFeatures flags to disable. .PARAMETER ReplicationLocation The secondary Azure region in which to enable workspace replication. .PARAMETER DisableReplication Disables replication for the workspace. .PARAMETER Properties A hashtable of additional workspace properties to update. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER ServiceMap Optional hashtable to map service names to specific EntraAuth service instances. Used for advanced scenarios where you want to use something other than the default Azure connection. Example: @{ Azure = 'MyAzure' } This will switch all Azure API calls to use the configuration defined in MyAzure. Defaults to: @{} .EXAMPLE PS C:\> Set-EalaWorkspace -Subscription 'Production' -ResourceGroup 'rg-monitoring' -Name 'law-prod' -RetentionDays 180 -EnableFeatures EnableDataExport -ReplicationLocation 'westus' Sets retention to 180 days, enables data export, and configures replication to westus. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.Subscription')] [string] $Subscription, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('EntraAuth.Azure.ResourceGroup')] [string] $ResourceGroup, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [PsfValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$', ErrorMessage = 'Invalid Name: {0}. Workspace names must be 4-63 characters long and only contain regular letters, numbers, and dashes.')] [string] $Name, [hashtable] $Tags, [switch] $SystemIdentity, [Alias('retentionInDays')] [int] $RetentionDays, [PsfArgumentCompleter('EntraAuth.Azure.LogAnalytics.Sku')] [string] $Sku, [Alias('Features')] [WorkspaceFeatures] $EnableFeatures, [WorkspaceFeatures] $DisableFeatures, [string] $ReplicationLocation, [switch] $DisableReplication, [hashtable] $Properties, [ServiceTransformAttribute()] [hashtable] $ServiceMap = @{} ) begin { $services = $script:_serviceSelector.GetServiceMap($ServiceMap) Assert-EntraConnection -Cmdlet $PSCmdlet -Service $services.Azure $subCache = @{} } process { $subscriptionID = Resolve-Subscription -Name $Subscription -Services $services -Cache $subCache -Cmdlet $PSCmdlet $body = @{} if ($SystemIdentity) { $body.identity = @{ tenantId = (Get-EntraToken -Service $services.Azure).TenantId type = 'SystemAssigned' } } if ($Tags) { $body.tags = $Tags } $propertySet = @{} foreach ($key in $Properties.Keys) { $propertySet[$key] = $Properties[$key] } if ($PSBoundParameters.Keys -contains 'RetentionDays') { $propertySet['retentionInDays'] = $RetentionDays } if ($PSBoundParameters.Keys -contains 'Sku') { $propertySet['sku'] = @{ name = $Sku } } $features = @{ } if ($PSBoundParameters.Keys -contains 'EnableFeatures') { if ($EnableFeatures -band [WorkspaceFeatures]::DataAuthorizationMode) { $features['dataAuthorizationMode'] = $true } if ($EnableFeatures -band [WorkspaceFeatures]::DisableLocalAuth) { $features['disableLocalAuth'] = $true } if ($EnableFeatures -band [WorkspaceFeatures]::EnableDataExport) { $features['enableDataExport'] = $true } if ($EnableFeatures -band [WorkspaceFeatures]::OnlyResourceAccess) { $features['enableLogAccessUsingOnlyResourcePermissions'] = $true } if ($EnableFeatures -band [WorkspaceFeatures]::ImmediateDataPurge) { $features['immediatePurgeDataOn30Days'] = $true } } if ($PSBoundParameters.Keys -contains 'DisableFeatures') { if ($DisableFeatures -band [WorkspaceFeatures]::DataAuthorizationMode) { $features['dataAuthorizationMode'] = $false } if ($DisableFeatures -band [WorkspaceFeatures]::DisableLocalAuth) { $features['disableLocalAuth'] = $false } if ($DisableFeatures -band [WorkspaceFeatures]::EnableDataExport) { $features['enableDataExport'] = $false } if ($DisableFeatures -band [WorkspaceFeatures]::OnlyResourceAccess) { $features['enableLogAccessUsingOnlyResourcePermissions'] = $false } if ($DisableFeatures -band [WorkspaceFeatures]::ImmediateDataPurge) { $features['immediatePurgeDataOn30Days'] = $false } } if ($features.Count -gt 0) { $propertySet['features'] = $features } if ($ReplicationLocation) { $propertySet.replication = @{ enabled = $true location = $ReplicationLocation } } if ($DisableReplication) { $propertySet.replication = @{ enabled = $false } } if ($propertySet.Count -gt 0) { $body.properties = $propertySet } if ($body.Count -lt 1) { Write-Error 'No changes specified, nothing to do!' return } Invoke-PSFProtectedCommand -Action "Creating Azure Log Analytics Workspace $Name in $SubscriptionID/$ResourceGroup" -Target $Name -ScriptBlock { Invoke-EntraRequest -Service $services.Azure -Method PATCH -Path "subscriptions/$subscriptionID/resourcegroups/$ResourceGroup/providers/Microsoft.OperationalInsights/workspaces/$Name" -Query @{ 'api-version' = '2026-03-01' } -Body $body -ContentType 'application/json' | ConvertTo-Workspace } -EnableException $true -PSCmdlet $PSCmdlet } } # Module-wide variables go here # For example if you want to cache some data, have some module-wide config settings, etc. ... those could go here # Example: # $script:config = @{ } $script:_services = @{ Azure = 'Azure' LogAnalytics = 'LogAnalytics' } $script:_serviceSelector = New-EntraServiceSelector -DefaultServices $script:_services # Table Templates for easier integration into other modules that need Log Analytics Workspace Tables $script:_TableColumnTemplates = @{} # Commands run on module import go here # E.g. Argument Completers could be placed here Import-PSFLocalizedString -Path "$script:ModuleRoot/en-us/strings.psd1" -Module EntraAuth.Azure.LogAnalytics -Language en-US Register-PSFTeppScriptblock -Name 'EntraAuth.Azure.LogAnalytics.Sku' -ScriptBlock { 'Free', 'Standard', 'Premium', 'PerNode', 'PerGB2018', 'Standalone', 'CapacityReservation', 'LACluster' } Register-PSFTeppScriptblock -Name 'EntraAuth.Azure.LogAnalytics.TableColumnTemplate' -ScriptBlock { Get-EalaColumnTemplate | ForEach-Object { @{ Text = $_.Name Tooltip = $_.Description } } } Register-PSFTeppScriptblock -Name 'EntraAuth.Azure.LogAnalytics.DataCollectionEndpoint' -ScriptBlock { if (-not $fakeBoundParameter.Subscription) { return } $param = @{ Subscription = $fakeBoundParameter.Subscription } if ($fakeBoundParameter.ResourceGroup) { $param.ResourceGroup = $fakeBoundParameter.ResourceGroup } Get-EalaDataCollectionEndpoint @param | ForEach-Object { @{ Text = $_.Name Tooltip = '{0} > {1} > {2} ({3})' -f $_.Subscription, $_.ResourceGroup, $_.Name, $_.Location } } } Register-PSFTeppScriptblock -Name 'EntraAuth.Azure.LogAnalytics.DataCollectionRule' -ScriptBlock { if (-not $fakeBoundParameter.Subscription) { return } $param = @{ Subscription = $fakeBoundParameter.Subscription } if ($fakeBoundParameter.ResourceGroup) { $param.ResourceGroup = $fakeBoundParameter.ResourceGroup } Get-EalaDataCollectionRule @param | ForEach-Object { @{ Text = $_.Name Tooltip = '{0} > {1} > {2} ({3})' -f $_.Subscription, $_.ResourceGroup, $_.Name, $_.Location } } } Set-PSFScriptblock -Name 'EntraAuth.Azure.LogAnalytics.TableColumnValidation' -Scriptblock { $legalTypes = 'string', 'int', 'long', 'real', 'boolean', 'dateTime', 'guid', 'dynamic' $legalKeys = 'dataTypeHint', 'description', 'displayName', 'isDefaultDisplay', 'isHidden', 'name', 'type' foreach ($entry in $_) { if ($entry -isnot [hashtable]) { throw 'Invalid column entry: Not a hashtable!' } if (-not $entry.Name) { throw "Invalid column entry: Must contain a 'name' key" } if (-not $entry.type) { throw "Invalid column entry $($entry.name): Must contain a 'type' key" } if ($entry.type -notin $legalTypes) { throw "Invalid column entry $($entry.name): Illegal type $($entry.type) | Supported types: $($legalTypes -join ', ') | https://learn.microsoft.com/en-us/rest/api/loganalytics/tables/create-or-update?view=rest-loganalytics-2026-03-01&tabs=HTTP#columntypeenum" } foreach ($key in $entry.Keys) { if ($key -notin $legalKeys) { throw "Invalid column property: $key | Legal Entries: $($legalKeys -join ', ') | https://learn.microsoft.com/en-us/rest/api/loganalytics/tables/create-or-update?view=rest-loganalytics-2026-03-01&tabs=HTTP#column" } } } $true } -Global $param = @{ Name = 'PSFrameworkLog' Description = 'Columns needed for PSFramework logging' Columns = @( @{ name = 'Message' type = 'string' } @{ name = 'TimeGenerated' type = 'datetime' } @{ name = 'Level' type = 'string' } @{ name = 'Tags' type = 'dynamic' } @{ name = 'Data' type = 'dynamic' } @{ name = 'ComputerName' type = 'string' } @{ name = 'Runspace' type = 'string' } @{ name = 'Username' type = 'string' } @{ name = 'ModuleName' type = 'string' } @{ name = 'FunctionName' type = 'string' } @{ name = 'File' type = 'string' } @{ name = 'Line' type = 'int' } @{ name = 'Callstack' type = 'string' } @{ name = 'TargetObject' type = 'dynamic' } @{ name = 'ErrorRecord' type = 'dynamic' } ) } Register-EalaColumnTemplate @param Export-ModuleMember -Function 'Get-EalaColumnTemplate','Get-EalaDataCollectionEndpoint','Get-EalaDataCollectionRule','Get-EalaTable','Get-EalaWorkspace','New-EalaDataCollectionEndpoint','New-EalaDataCollectionRule','New-EalaTable','New-EalaWorkspace','Register-EalaColumnTemplate','Remove-EalaDataCollectionEndpoint','Remove-EalaDataCollectionRule','Remove-EalaTable','Remove-EalaWorkspace','Set-EalaWorkspace','Write-EalaTableEntry' |