Invoke-AzOperationalInsightsQueryExport.ps1

<#PSScriptInfo
 
.VERSION 1.3
 
.GUID 89b5e4a0-3220-42c9-9cf3-324254283d16
 
.AUTHOR jbritt@microsoft.com
 
.COMPANYNAME Microsoft
 
.COPYRIGHT Microsoft
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
https://www.powershellgallery.com/packages/Invoke-AzOperationalInsightsQueryExport
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
April 26, 2020 1.3
    Updated the Export format to CSV. Special thanks to Alex Delino (San Diego State University) for their input on
    exporting to CSV as well as providing sample recommended updates).
 
    Also provided a console only option to avoid export file if desired using switch "-Console"
#>


<#
.SYNOPSIS
  A script to programatically export raw data from Log Analytics to an export file
   
  Note This script currently leverages the Az cmdlets
   
.DESCRIPTION
  This script takes a SubscriptionID, ResourceGroup (of the log analytics workspace), workspaceName as parameters, ExportDirectory, Interval for Timespan, and
  either a SavedSearchID or a Query and provides the option to export raw data from the target Log Analytics workspace to an export file. All parameters are optional
  and if not provided, will automatically build logic for you given answers to menu items.
 
.PARAMETER SubscriptionId
    The subscriptionID of the Azure Subscription where you Log Analytics workspaces are that you want to choose from
 
.PARAMETER WorkspaceName
    The log Analytics Workspace Name
     
.PARAMETER ResourceGroup
    The log Analytics Resource Group Name
 
.PARAMETER Interval
    The number of minutes you want to query for related to timespan (Default is 60 mins)
 
.PARAMETER ExportDirectory
    Directory to export your log data to (default is current working directory of script where an export folder will be created)
 
.PARAMETER SavedSearchID
    A saved search ID from Log Analytics that you can use to run a saved query (pulls the query details from the SavedSearchID)
 
.PARAMETER Query
    Allows you to provide an adhoc query on the command line and bypass SavedSearchId option
 
.PARAMETER Console
    Provide results to console only - no export file created
 
.EXAMPLE
  .\Invoke-AzOperationalInsightsQueryExport.ps1 -ResourceGroup "MyLAWorkspaceRG" -WorkspaceName "myLAWorkspace" -subscriptionId "0869826f-9de3-4363-bb31-ddb6a0a1471a"
  Will use resource group and workspace name as your target workspace within specified subscription and will prompt for other details.
   
.EXAMPLE
  .\Invoke-AzOperationalInsightsQueryExport.ps1 -ExportDirectory "<.\exportDirectory>"
  Will export log to export directory specific. Will prompt for subscriptionID, SavedSearchID to use, as well as Log Analytics workspace to leverage for log export
 
.EXAMPLE
  .\Invoke-AzOperationalInsightsQueryExport.ps1
  Will prompt for subscriptionId, Workspace to use, SavedSearchId to use and will export to the "export" folder within the working directory of the script
 
.EXAMPLE
  .\Invoke-AzOperationalInsightsQueryExport.ps1 -ExportDirectory ".\ExportDirectory" -subscriptionId "0869826f-9de3-4363-bb31-ddb6a0a1471a" -query "Heartbeat | summarize dcount(ComputerIP) by bin(TimeGenerated, 1h)"
  Will leverage the specified export directory (relative to current working directory of PS console or specify fully qualified directory) and will leverage the query specified.
  It will prompt for a Log Analytics workspace to use within the subscription specified by subscriptionId.
 
.EXAMPLE
  .\Invoke-AzOperationalInsightsQueryExport.ps1 -subscriptionId "0869826f-9de3-4363-bb31-ddb6a0a1471a" -query "Heartbeat | summarize dcount(ComputerIP) by bin(TimeGenerated, 1h) | render timechart" -interval 480
  Will leverage the .\export directory (relative to current working directory PS script) and will leverage the query specified.
  It will prompt for a Log Analytics workspace to use within the subscription specified by subscriptionId. It will override the default of 60 mins for interval to scan and query across 8 hours (480 mins)
 
.EXAMPLE
  .\Invoke-AzOperationalInsightsQueryExport.ps1 -Console
  Will allow you to be prompted for all required parameters and display the results to the console only. No file will be generated.
 
.NOTES
   AUTHOR: Jim Britt Senior Program Manager - Azure CXP API
   LASTEDIT: April 26, 2020 1.3
    Updated the Export format to CSV. Special thanks to Alex Delino (San Diego State University) for their input on
    exporting to CSV as well as providing sample recommended updates).
 
    Also provided a console only option to avoid export file if desired using switch "-Console"
 
   August 06, 2019
    Initial
 
.LINK
    This script posted to and discussed at the following locations:PowerShell Gallery
    https://aka.ms/ExportAzLALogs
#>

[cmdletbinding(
    DefaultParameterSetName='Default'
)]
param
(
    # SubscriptionId of where your Log Analytics Workspace is to get saved SearchID or leveraging query param (optional)
    [Parameter(ParameterSetName='Default')]
    [Parameter(ParameterSetName='SavedSearch')]
    [Parameter(ParameterSetName='Query')]
    [Parameter(ParameterSetName='Console')]
    [guid]$SubscriptionID,

    # Resource Group name for Log Analytics workspace (used with workspacename parameter)
    [Parameter(ParameterSetName='Default')]
    [Parameter(ParameterSetName='SavedSearch')]
    [Parameter(ParameterSetName='Query')]
    [Parameter(ParameterSetName='Console')]
    [string]$ResourceGroup,
    
    # Workspace Name (optional)
    [Parameter(ParameterSetName='Default')]
    [Parameter(ParameterSetName='SavedSearch')]
    [Parameter(ParameterSetName='Query')]
    [Parameter(ParameterSetName='Console')]
    [string]$WorkspaceName,
    
    # Log Analytics SavedSearchID (use ad hoc query if preferred)
    [Parameter(ParameterSetName='SavedSearch')]
    [string]$SavedSearchID,
    
    # Ad hoc query in lieu of SavedSearchID
    [Parameter(ParameterSetName='Query')]
    [string]$Query,

    # Base folder for export
    [Parameter(ParameterSetName='Default')]
    [Parameter(ParameterSetName='SavedSearch')]
    [Parameter(ParameterSetName='Query')]
    [string]$ExportDirectory,

    [Parameter(ParameterSetName='Console')]
    [switch]$Console=$False,
    
    [Parameter(ParameterSetName='Default')]
    [Parameter(ParameterSetName='SavedSearch')]
    [Parameter(ParameterSetName='Query')]
    [Parameter(ParameterSetName='Console')]
    [int]$Interval

)


# Function used to build numbers in selection tables for menus
function Add-IndexNumberToArray (
    [Parameter(Mandatory=$True)]
    [array]$array
    )
{
    for($i=0; $i -lt $array.Count; $i++) 
    { 
        Add-Member -InputObject $array[$i] -Name "#" -Value ($i+1) -MemberType NoteProperty 
    }
    $array
}

<# MAIN SCRIPT #>
# If Interval is not specified - default is 60 mins
If(!($Interval))
{
    $Timespan = New-TimeSpan -Minutes 60
}
else
{
    $Timespan = New-TimeSpan -Minutes $Interval
}

$error.clear()
# Find out where we are running
if ($null -ne $MyInvocation.MyCommand.Path)
{
    $CurrentDir = Split-Path $MyInvocation.MyCommand.Path
}
else
{
    # Sometimes $myinvocation is null, it depends on the PS console host
    $CurrentDir = "."
}
if (!($ExportDirectory))
{
    $ExportDirectory = "$($CurrentDir)\Export"
}
else
{
    $ExportDirectory = $ExportDirectory
}
if(!(Test-path $ExportDirectory))
{ 
    $NULL = new-item -ItemType Directory -Path $ExportDirectory
}

# Login to Azure - if already logged in, use existing credentials.
Write-Host "Authenticating to Azure..." -ForegroundColor Cyan
try
{
    $AzureLogin = Get-AzSubscription
}
catch
{
    $null = Login-AzAccount
    $AzureLogin = Get-AzSubscription
}

# Authenticate to Azure if not already authenticated
# Ensure this is the subscription where your Management Groups are that house Blueprints for import/export operations
If($AzureLogin -and !($SubscriptionID))
{
    [array]$SubscriptionArray = Add-IndexNumberToArray (Get-AzSubscription) 
    [int]$SelectedSub = 0

    # use the current subscription if there is only one subscription available
    if ($SubscriptionArray.Count -eq 1) 
    {
        $SelectedSub = 1
    }
    # Get SubscriptionID if one isn't provided
    while($SelectedSub -gt $SubscriptionArray.Count -or $SelectedSub -lt 1)
    {
        Write-host "Please select a subscription from the list below for the " -NoNewline
        write-host $Mode -ForegroundColor Yellow -NoNewline
        write-host " Operation"
        $SubscriptionArray | Select-Object "#", Name, ID | Format-Table
        try
        {
            $SelectedSub = Read-Host "Please enter a selection from 1 to $($SubscriptionArray.count) for the $Mode Operation"
        }
        catch
        {
            Write-Warning -Message 'Invalid option, please try again.'
        }
    }
    if($($SubscriptionArray[$SelectedSub - 1].Name))
    {
        $SubscriptionName = $($SubscriptionArray[$SelectedSub - 1].Name)
    }
    elseif($($SubscriptionArray[$SelectedSub - 1].SubscriptionName))
    {
        $SubscriptionName = $($SubscriptionArray[$SelectedSub - 1].SubscriptionName)
    }
    write-verbose "You Selected Azure Subscription: $SubscriptionName"
    
    if($($SubscriptionArray[$SelectedSub - 1].SubscriptionID))
    {
        [guid]$SubscriptionID = $($SubscriptionArray[$SelectedSub - 1].SubscriptionID)
    }
    if($($SubscriptionArray[$SelectedSub - 1].ID))
    {
        [guid]$SubscriptionID = $($SubscriptionArray[$SelectedSub - 1].ID)
    }
    $SubscriptionID = $SubscriptionID.Guid
}
Write-Host "Selecting Azure Subscription: $($SubscriptionID) ..." -ForegroundColor Cyan
$Null = Select-AzSubscription -SubscriptionId $SubscriptionID

# Use workspacename and resourcegroup if that is provided as parameters and validate it is a workspace that can be accessed
if(($WorkspaceName) -and ($ResourceGroup))
{
    try {
        Write-Host "You Selected Workspace: " -nonewline -ForegroundColor Cyan
        Write-Host $WorkspaceName -ForegroundColor Yellow
        $Workspace = Get-AzOperationalInsightsWorkspace -Name $WorkspaceName -ResourceGroupName $ResourceGroup
    }
    catch {
        Write-Warning -Message 'No Workspace found'
    }
}
# Build a list of workspaces to choose from. If workspace is in another subscription
# provide the resourceID of that workspace as a parameter
if(!($WorkspaceName))
{
    [array]$Workspaces=@()
    try
    {
        $Workspaces = Add-IndexNumberToArray (Get-AzOperationalInsightsWorkspace) 
        Write-Host "Generating a list of workspaces from Azure Subscription Selected..." -ForegroundColor Cyan

        [int]$SelectedWS = 0
        if ($Workspaces.Count -eq 1)
        {
            $SelectedWS = 1
        }

        # Get WS Resource ID if one isn't provided
        while($SelectedWS -gt $Workspaces.Count -or $SelectedWS -lt 1 -and $Null -ne $Workspaces)
        {
            Write-Host "Please select a workspace from the list below"
            $Workspaces| Select-Object "#", Name, Location, ResourceGroupName, ResourceId | Format-Table
            if($Workspaces.count -ne 0)
            {

                try
                {
                    $SelectedWS = Read-Host "Please enter a selection from 1 to $($Workspaces.count)"
                }
                catch
                {
                    Write-Warning -Message 'Invalid option, please try again.'
                }
            }
        }
    }
    catch
    {
        Write-Warning -Message 'No Workspace found - try specifying workspacename, resourcegroup and subscriptionID parameters'
    }
    If($Workspaces)
    {
        Write-Host "You Selected Workspace: " -nonewline -ForegroundColor Cyan
        Write-Host "$($Workspaces[$SelectedWS - 1].Name)" -ForegroundColor Yellow
        $WorkspaceName = $($Workspaces[$SelectedWS - 1].Name)
        $ResourceGroup = $($Workspaces[$SelectedWS - 1].ResourceGroupName)

    }
    else
    {
        Throw "No OMS workspaces available in selected subscription $SubscriptionID"
    }
}

# Checking for SavedSearchID or presenting all saved searches
If(!($SavedSearchID)-and !($Query))
{
    [int]$SelectedID = 0

    $SavedSearchIDArray = Add-IndexNumberToArray ($(Get-AzOperationalInsightsSavedSearch -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName).Value)

    # Only one saved
   if ($SavedSearchIDArray.Count -eq 1) 
   {
       $SelectedID = 1
   }
   # Get SavedSearchID if one isn't provided
   while($SelectedID -gt $SavedSearchIDArray.Count -or $SelectedID -lt 1)
   {
       Write-host "Please select a Saved Search from the list below."
       $SavedSearchIDArray | Select-Object "#", @{Label = "DisplayName";Expression={$_.Properties.DisplayName}}|Format-Table
       try
       {
           $SelectedID = Read-Host "Please enter a selection from 1 to $($SavedSearchIDArray.count)"
       }
       catch
       {
           Write-Warning -Message 'Invalid option, please try again.'
       }
   }
   
   $SavedSearchID = $($SavedSEarchIDArray[$SelectedID - 1].ID.split("/")[9])
   Write-Host "Using SavedSearchID $($SavedSearchID)" -ForegroundColor Cyan
}

# Get the workspace object to query against
if(!($Workspace))
{
    $Workspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $ResourceGroup -Name $WorkspaceName
}

if(!($Query))
{
    if($savedSearchIDArray)
    {
        $Query = $($SavedSearchIDArray[$SelectedID-1].Properties.query)
    }
    else
    {
        try 
        {
            $SavedSearch = Get-AzOperationalInsightsSavedSearch -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName -SavedSearchId $SavedSearchID
            $Query = $($SavedSearch.Properties.Query) 
        }
        catch 
        {
            Write-Warning -Message 'SavedSearchID potentially Invalid. Please try again or use "query" parameter.'
        }
    }
}

    
Write-host "Now querying for data to export for the last $($Timespan.TotalMinutes) minutes" -ForegroundColor Cyan

$Data = $(Invoke-AzOperationalInsightsQuery -Workspace $Workspace -Query $Query -Timespan ($Timespan)).Results
If($Data -ne "")
{
    If($Console)
    {
        Write-Host "Now Exporting data to console... " -ForegroundColor Yellow
        $Data | Format-Table

    }
    else
    {
        $FileName = "$($ExportDirectory)\$(get-date -format filedatetime).Csv"
        Write-Host "Now Exporting data to " -NoNewline 
        Write-Host $FileName -ForegroundColor Yellow
        $Data | Export-Csv -path $FileName
    }
}
else {
    write-host "Query returned no data!"
}
remove-variable SubscriptionID
Write-host "Complete!"