AzureAutomationTools.Logging.psm1
Set-StrictMode -Version latest function Export-AzureRmAutomationRunbookLog { param ( # Target ResourceGroupName of the AutomationAccount [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ResourceGroupName, # Automation account to pull the logs from [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $AutomationAccountName, # Storage account name to post the logs to [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $StorageAccountName, # Storage container to post the logs to - best solution is to create a blank container called 'logs' [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $StorageContainerName, # Runbook names to include [Parameter()] [string[]] $IncludeRunbook = '*', # Runbook names to exclude [Parameter()] [string[]] $ExcludeRunbook = $null, # Collect logs completed after this datetime [Parameter()] [datetime] $CollectLogsFrom = [datetime]::MinValue, # Collect logs completed until this datetime [Parameter()] [datetime] $CollectLogsTo = [datetime]::MaxValue ) $ErrorActionPreference = 'Stop' #region Helper functions function TestRunbookInclusion { param ( [Parameter(Mandatory = $true)] [string] $RunbookName ) $Result = $false foreach ($Inclusion in $IncludeRunbook) { if ($RunbookName -like $Inclusion) { $Result = $true break } } foreach ($Exclusion in $ExcludeRunbook) { if ($RunbookName -like $Exclusion) { $Result = $false break } } return $Result } #endregion #region pre-Azure checks if ($CollectLogsTo -lt $CollectLogsFrom) { throw "CollectLogsFrom cannot be greater than CollectLogsTo! CollectLogsFrom: $($CollectLogsFrom.ToString('s')); CollectLogsTo: $($CollectLogsTo.ToString('s'))" } if ($null -eq $IncludeRunbook) { $IncludeRunbook = '*' } #endregion $KeySplat = @{ ResourceGroupName = $ResourceGroupName Name = $StorageAccountName } $StorageAccountKeyObj = (Get-AzureRmStorageAccountKey @KeySplat) if ($StorageAccountKeyObj -is [System.Collections.IList]) { $StorageAccountKey = $StorageAccountKeyObj | Where-Object KeyName -eq 'Key1' | Select-Object -ExpandProperty 'Value' } else { $StorageAccountKey = $StorageAccountKeyObj.Key1 } $ContextSplat = @{ StorageAccountName = $StorageAccountName StorageAccountKey = $StorageAccountKey } $StorageContext = New-AzureStorageContext @ContextSplat $Container = Get-AzureStorageContainer -Name $StorageContainerName -Context $StorageContext Write-Output "Collecting jobs data..." $Jobs = Get-AzureRmAutomationJob -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName | Where-Object { $_.Status -In @('Completed', 'Failed', 'Stopped') -and $_.EndTime.UtcDateTime -ge $CollectLogsFrom -and $_.EndTime.UtcDateTime -le $CollectLogsTo -and (TestRunbookInclusion -RunbookName $_.RunbookName) } | Get-AzureRmAutomationJob | Sort-Object -Property EndTime Write-Output "Collecting jobs data... Done." if ($Jobs.Count -eq 0) { Write-Output "Could not find any jobs." Write-Warning "Could not find any jobs." return } else { Write-Output "Logging $($Jobs.Count) jobs..." } # Check if job exists foreach ($Job in $Jobs) { Write-Verbose "Capturing:@{ Name = $($Job.RunbookName); Start = $($Job.CreationTime.UtcDateTime.ToString('s')) }" $JobName = $Job.RunbookName $CreateDate = $job.CreationTime.UtcDateTime $Year = $CreateDate.Year $Month = [string]::Format('{0:00}', $CreateDate.Month) $Day = [string]::Format('{0:00}', $CreateDate.Day) $JobBlobPath = "$ResourceGroupName/$AutomationAccountName/$JobName/$Year/$Month/$Day/$($Job.JobId).json" try { $CurrentEntry = Get-AzureStorageBlob -Blob $JobBlobPath -Container $StorageContainerName -Context $StorageContext throw "Blob '$($JobBlobPath)' already exists in '$($StorageAccountName)/$($StorageContainerName)'." } catch [Microsoft.WindowsAzure.Commands.Storage.Common.ResourceNotFoundException] { Write-Verbose "No entry found for Blob '$($JobBlobPath)'. Continuing with import." } $LogObjParams = [ordered]@{} foreach ($Param in ($Job | Get-Member | Where-Object MemberType -eq 'Property').Name) { $Value = $Job."$Param" $LogObjParams[$Param] = $Value } $Params = @{ JobId = $Job.JobId ResourceGroupName = $ResourceGroupName AutomationAccountName = $AutomationAccountName } $Streams = [ordered]@{} foreach ($Stream in @('Output', 'Warning', 'Error')) { $StreamData = Get-AzureRmAutomationJobOutput @Params -Stream $Stream $StreamOutput = @() foreach ($Entry in $StreamData) { $RecordParams = @{ Id = $Entry.StreamRecordId JobId = $Job.JobId ResourceGroupName = $ResourceGroupName AutomationAccountName = $AutomationAccountName } $Record = Get-AzureRmAutomationJobOutputRecord @RecordParams $StreamOutput += @{ StreamRecordId = $Record.StreamRecordId Time = $Record.Time Value = $Record.Value } Write-Verbose "Captured record $($Entry.StreamRecordId)." } $Streams[$Stream] = $StreamOutput } $LogObjParams['Streams'] = $Streams $LogPath = [System.IO.Path]::GetTempFileName() $LogObjParams | ConvertTo-Json -Depth 100 | Set-Content -Path $LogPath -Force Set-AzureStorageBlobContent -File $LogPath -Container $StorageContainerName -Blob $JobBlobPath -Context $StorageContext | Out-Null Write-Output "Successfully uploaded log: '$JobBlobPath'" Remove-Item -Path $LogPath -Force } Write-Output "Finished harvesting logs." } |