Get-AzRMActivityLogs.ps1
function Get-AzRMActivityLogs { <# .SYNOPSIS The Get-AzRMActivityLogs function dumps in JSON files Azure Resource Manager activity logs for a specific time range. .EXAMPLE PS C:\>$appId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" PS C:\>$tenant = "example.onmicrosoft.com" PS C:\>$certificatePath = "./example.pfx" PS C:\>$endDate = Get-Date PS C:\>$startDate = $endDate.AddDays(-90) PS C:\>Get-AzRMActivityLogs -startDate $startDate -endDate $endDate -appId $appId -tenant $tenant -certificatePath $certificatePath Dump all Azure Resource Manager activity logs for the last 90 days. #> param ( [Parameter(Mandatory = $true)] [DateTime]$startDate, [Parameter(Mandatory = $true)] [DateTime]$endDate, [Parameter(Mandatory = $true)] [String]$certificatePath, [Parameter(Mandatory = $true)] [String]$appId, [Parameter(Mandatory = $true)] [String]$tenant, [Parameter(Mandatory = $false)] [String]$logFile = "Get-AzRMActivityLogs.log" ) $currentPath = (Get-Location).path $null, $needPassword, $certificateSecurePassword = Import-Certificate -certificatePath $certificatePath -logFile $logFile Connect-AzApplication -logFile $logFile -certificatePath $certificatePath -certificateSecurePassword $certificateSecurePassword -needPassword $needPassword -tenant $tenant -appId $appId $azureSubscriptionsFolder = $currentPath + "\azure_rm_subscriptions" if ((Test-Path $azureSubscriptionsFolder) -eq $false){ New-Item $azureSubscriptionsFolder -Type Directory | Out-Null } $subscriptionsRaw = Get-AzSubscription -ErrorAction Stop $subscriptionsNameAndId = $subscriptionsRaw | Select-Object Name, Id Write-Host "This application has access to the following subscriptions:" "This application has access to the following subscriptions:" | Write-Log -LogPath $logFile $subscriptionsNameAndId | Out-Host $subscriptionsNameAndId | Write-Log -LogPath $logFile $choice = Read-Host "Do you want to collect Azure Resource Manager activity logs for all [a], specific [s] or no [N] subscription ? [a/s/N]" if ($choice.ToUpper() -eq "S"){ [System.Collections.ArrayList]$wantedSubscriptionsNameAndId = @{} $read = $True Write-Host "Leave Blank and press 'Enter' to Stop" while ($read){ $potentialSubscriptionId = Read-Host "Please enter the subscription IDs, one by one, and press 'Enter'" if ($potentialSubscriptionId){ $selectedInput = $subscriptionsNameAndId | Where-Object {$_.Id -eq $potentialSubscriptionId} if ($null -ne $selectedInput){ $wantedSubscriptionsNameAndId.Add($selectedInput) | Out-Null Write-Host "Added $potentialSubscriptionId" } else { Write-Warning "Invalid subscription ID, please try again" } } else { $read = $False } } } elseif ($choice.ToUpper() -eq "A"){ $wantedSubscriptionsNameAndId = $subscriptionsNameAndId } else { Write-Error "No subscription was selected. Exiting" "No subscription was selected. Exiting" | Write-Log -LogPath $logFile -LogLevel Error exit } $outputFile = $azureSubscriptionsFolder + "\AzRMsubscriptions_" + $tenant + ".json" $subscriptionsRaw | ConvertTo-Json -Depth 99 | Out-File $outputFile -Encoding UTF8 $launchSearch = { param($newStartDate, $newEndDate, $currentPath, $subscriptionId, $appId, $tenant, $certificatePath, [SecureString]$certificateSecurePassword, [Bool]$needPassword) Select-AzSubscription -SubscriptionID $subscriptionId -ErrorAction Stop $dateToProcess = ($newStartDate.ToString("yyyy-MM-dd")) $logFile = $currentPath + "\AzRM_" + $subscriptionId + "_" + $dateToProcess + ".log" $tenant = ($user).split("@")[1] $azureRMActivityFolder = $currentPath + "\azure_rm_activity" if ((Test-Path $azureRMActivityFolder) -eq $false){ New-Item $azureRMActivityFolder -Type Directory } $totalHours = [Math]::Floor((New-TimeSpan -Start $newStartDate -End $newEndDate).TotalHours) if ($totalHours -eq 24){ $totalHours-- } for ($h=0; $h -le $totalHours; $h++){ if ($h -eq 0){ $newStartHour = $newStartDate $newEndHour = $newStartDate.AddMinutes(59 - $newStartDate.Minute).AddSeconds(60 - $newStartDate.Second) } elseif ($h -eq $totalHours){ $newStartHour = $newEndHour $newEndHour = $newEndDate } else { $newStartHour = $newEndHour $newEndHour = $newStartHour.addHours(1) } "Processing Azure Resource Manager activity logs between {0:yyyy-MM-dd} {0:HH:mm:ss} and {1:yyyy-MM-dd} {1:HH:mm:ss}" -f ($newStartHour, $newEndHour) | Write-Log -LogPath $logFile $outputDate = "{0:yyyy-MM-dd}_{0:HH-00-00}" -f ($newStartHour) $dateStart = "{0:s}" -f $newStartHour + "Z" $dateEnd = "{0:s}" -f $newEndHour + "Z" $azureRMActivityEvents = Get-AzureRMActivityLog -dateStart $dateStart -dateEnd $dateEnd -certificatePath $certificatePath -certificateSecurePassword $certificateSecurePassword -needPassword $needPassword -appId $appId -tenant $tenant -logFile $logFile $folderToProcess = $azureRMActivityFolder + "\" + $dateToProcess if ((Test-Path $folderToProcess) -eq $false){ New-Item $folderToProcess -Type Directory } $outputFile = $folderToProcess + "\AzRM_" + $tenant + "_" + $subscriptionId + "_" + $outputDate + ".json" if ($azureRMActivityEvents){ $nbAzureRMActivityEvents = ($azureRMActivityEvents | Measure-Object).Count "Dumping $($nbAzureRMActivityEvents) Azure Resource Manager activity logs events to $($outputFile)" | Write-Log -LogPath $logFile for ($i=0; $i -lt $nbAzureRMActivityEvents; $i++){ # we can't use ConvertTo-Json, cf. https://github.com/Azure/azure-powershell/issues/11353 $azureRMActivityEvents[$i] = [Newtonsoft.Json.JsonConvert]::SerializeObject($azureRMActivityEvents[$i]) } $azureRMActivityEvents | Out-File $outputFile -Encoding UTF8 } else { "No Azure Resource Manager activity logs event to dump to $($outputFile)" | Write-Log -LogPath $logFile -LogLevel "Warning" } } } $totalTimeSpan = (New-TimeSpan -Start $startDate -End $endDate) if (($totalTimeSpan.Hours -eq 0) -and ($totalTimeSpan.Minutes -eq 0) -and ($totalTimeSpan.Seconds -eq 0)){ $totaldays = $totalTimeSpan.days $totalLoops = $totaldays } else { $totaldays = $totalTimeSpan.days + 1 $totalLoops = $totalTimeSpan.days } Get-RSJob | Remove-RSJob -Force foreach ($subscription in $wantedSubscriptionsNameAndId){ Write-Host "Starting processing Azure Resource Manager activity logs for $($subscription.Name) subscription" "Starting processing Azure Resource Manager activity logs for $($subscription.Name) subscription" | Write-Log -LogPath $logFile for ($d=0; $d -le $totalLoops; $d++){ if ($d -eq 0){ $newStartDate = $startDate $newEndDate = Get-Date("{0:yyyy-MM-dd} 00:00:00.000" -f ($newStartDate.AddDays(1))) } elseif ($d -eq $totaldays){ $newEndDate = $endDate $newStartDate = Get-Date("{0:yyyy-MM-dd} 00:00:00.000" -f ($newEndDate)) } else { $newStartDate = $newEndDate $newEndDate = $newEndDate.AddDays(1) } "Lauching job number $($d) with startDate {0:yyyy-MM-dd} {0:HH:mm:ss} and endDate {1:yyyy-MM-dd} {1:HH:mm:ss}" -f ($newStartDate, $newEndDate) | Write-Log -LogPath $logFile $dateToProcess = ($newStartDate.ToString("yyyy-MM-dd")) $subscriptionId = $subscription.Id $jobName = "AzRM_" + $subscriptionId + "_" + $dateToProcess Start-RSJob -Name $jobName -ScriptBlock $launchSearch -FunctionsToImport Write-Log, Connect-AzApplication, Get-AzureRMActivityLog -ArgumentList $newStartDate, $newEndDate, $currentPath, $subscriptionId, $appId, $tenant, $certificatePath, $certificateSecurePassword, $needPassword $maxJobRunning = 3 $jobRunningCount = (Get-RSJob | Where-Object {$_.State -eq "Running"} | Measure-Object).Count while ($jobRunningCount -ge $maxJobRunning){ Start-Sleep -Seconds 1 $jobRunningCount = (Get-RSJob | Where-Object {$_.State -eq "Running"} | Measure-Object).Count } $jobsDone = Get-RSJob | Where-Object {$_.State -eq "Completed"} if ($jobsDone){ foreach ($jobDone in $jobsDone){ "Runspace Job $($jobDone.Name) has finished - dumping log" | Write-Log -LogPath $logFile $logFileName = $jobDone.Name + ".log" Get-Content $logFileName | Out-File $logFile -Encoding UTF8 -Append Remove-Item $logFileName -Confirm:$false -Force $jobDone | Remove-RSJob "Runspace Job $($jobDone.Name) finished - job removed" | Write-Log -LogPath $logFile } } $jobsFailed = Get-RSJob | Where-Object {$_.State -eq "Failed"} if ($jobsFailed){ foreach ($jobFailed in $jobsFailed){ "Runspace Job $($jobFailed.Name) failed with error $($jobFailed.Error)" | Write-Log -LogPath $logFile -LogLevel "Error" "Runspace Job $($jobFailed.Name) failed - dumping log" | Write-Log -LogPath $logFile -LogLevel "Error" $logFileName = $jobFailed.Name + ".log" Get-Content $logFileName | Out-File $logFile -Encoding UTF8 -Append Remove-Item $logFileName -Confirm:$false -Force $jobFailed | Remove-RSJob "Runspace Job $($jobFailed.Name) failed - job removed" | Write-Log -LogPath $logFile -LogLevel "Error" } } } # Waiting for final jobs to complete $jobRunningCount = (Get-RSJob | Where-Object {$_.State -eq "Running"} | Measure-Object).Count while ($jobRunningCount -ge 1){ Start-Sleep -Seconds 1 $jobRunningCount = (Get-RSJob | Where-Object {$_.State -eq "Running"} | Measure-Object).Count } $jobsDone = Get-RSJob | Where-Object {$_.State -eq "Completed"} if ($jobsDone){ foreach ($jobDone in $jobsDone){ "Runspace Job $($jobDone.Name) has finished - dumping log" | Write-Log -LogPath $logFile $logFileName = $jobDone.Name + ".log" Get-Content $logFileName | Out-File $logFile -Encoding UTF8 -Append Remove-Item $logFileName -Confirm:$false -Force $jobDone | Remove-RSJob "Runspace Job $($jobDone.Name) finished - job removed" | Write-Log -LogPath $logFile } } $jobsFailed = Get-RSJob | Where-Object {$_.State -eq "Failed"} if ($jobsFailed){ foreach ($jobFailed in $jobsFailed){ "Runspace Job $($jobFailed.Name) failed with error $($jobFailed.Error)" | Write-Log -LogPath $logFile -LogLevel "Error" "Runspace Job $($jobFailed.Name) failed - dumping log" | Write-Log -LogPath $logFile -LogLevel "Error" $logFileName = $jobFailed.Name + ".log" Get-Content $logFileName | Out-File $logFile -Encoding UTF8 -Append Remove-Item $logFileName -Confirm:$false -Force $jobFailed | Remove-RSJob "Runspace Job $($jobFailed.Name) failed - job removed" | Write-Log -LogPath $logFile -LogLevel "Error" } } } } |