Public/Invoke-CAIQ.ps1
Function Invoke-CAIQ { <# .SYNOPSIS This function is used to compare the current conditional access policies with the last modified policy and create a report of the changes. .DESCRIPTION This function is used to compare the current conditional access policies with the last modified policy and create a report of the changes. .PARAMETER StartDate The start date of the audit log. .PARAMETER EndDate The end date of the audit log. .PARAMETER OutputPath The path to save the policies to. .PARAMETER FileName The name of the file to save the report to. .PARAMETER Title The title of the report. .PARAMETER LogPath The path to save the log to. .PARAMETER AuditLogArchive The path to save the audit log archive to. .PARAMETER InvokeHtml If this switch is specified, the report will be saved to the output path. .INPUTS System.String System.Boolean .OUTPUTS System.String .LINK https://github.com/thetolkienblackguy/ConditionalAccessIQ/blob/main/README.md #> [Alias("Invoke-ConditionalAccessIQ")] [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory=$false)] [ValidateScript({ $utc_regex = '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$' if ($_ -notmatch $utc_regex) { throw "StartDate must be in UTC format (yyyy-MM-ddTHH:mm:ssZ)" } return $true })] [string]$StartDate = (Get-Date).AddDays(-1).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), [Parameter(Mandatory=$false)] [ValidateScript({ $utc_regex = '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$' if ($_ -notmatch $utc_regex) { throw "EndDate must be in UTC format (yyyy-MM-ddTHH:mm:ssZ)" } if ([DateTime]::ParseExact($_, "yyyy-MM-ddTHH:mm:ssZ", [System.Globalization.CultureInfo]::InvariantCulture) -lt [DateTime]::ParseExact($StartDate, "yyyy-MM-ddTHH:mm:ssZ", [System.Globalization.CultureInfo]::InvariantCulture)) { throw "EndDate cannot be before StartDate" } return $true })] [string]$EndDate = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), [Parameter(Mandatory=$false)] [ValidateScript({Test-Path -Path $_ -PathType Container})] [string]$OutputPath = "$($PWD)\ConditionalAccessIQ", [Parameter(Mandatory=$false)] [string]$FileName = "Conditional_Access_Intelligence.html", [Parameter(Mandatory=$false)] [string]$Title = "Conditional Access Policy Changes", [Parameter(Mandatory=$false)] [string]$Logfile = "$($outputPath)\Logs\Invoke-ConditionalAccessIQ_$(Get-Date -Format "yyyy-MM-dd_HH-mm-ss").log", [Parameter(Mandatory=$false)] [string]$AuditLogArchive = "$($outputPath)\Logs\CAIQ_Audit_Log_Archive.json", [Parameter(Mandatory=$false)] [bool]$InvokeHtml = $true ) Begin { #region Pre Processing # Setting default parameter values $PSDefaultParameterValues = @{} $PSDefaultParameterValues["Invoke-CAIQLogging:Logfile"] = $logFile $PSDefaultParameterValues["Invoke-CAIQLogging:WriteOutput"] = $true $PSDefaultParameterValues["Sort-Object:Descending"] = $true $PSDefaultParameterValues["ConvertFrom-Json:Depth"] = 10 $PSDefaultParameterValues["ConvertTo-Json:Depth"] = 10 $PSDefaultParameterValues["Out-File:Append"] = $true #endregion #region Splatting $new_index_params = @{} $new_index_params["OutputPath"] = $outputPath $new_index_params["Name"] = $fileName $new_index_params["Title"] = $title #endregion } Process { #region Get modified and new policies $policy_reports = [System.Collections.Generic.List[PSCustomObject]]::new() Try { Invoke-CAIQLogging -Message "Getting conditional access policies" # Get all policies $policies = Get-CAIQConditionalAccessPolicy -All -ErrorAction Stop Invoke-CAIQLogging -Message "Found $(@($policies).Count) conditional access policies" -ForegroundColor Green } Catch { Invoke-CAIQLogging -Message "Failed to get conditional access policies due to the following error: $_" -ForegroundColor Red Exit 1 } Foreach ($policy in $policies) { # Get the policy id $policy_id = $policy.id # Get the policy display name $policy_display_name = $policy.DisplayName # Build the policy path $policy_path = Join-Path -Path $outputPath -ChildPath "Policies\$policy_id" # Initialize the audit logs list $audit_logs = [System.Collections.Generic.List[PSCustomObject]]::new() # Export-CAIQJson parameters $export_json_params = @{} $export_json_params["Policy"] = $policy $export_json_params["Path"] = $policy_path Try { If (!(Test-Path -Path $policy_path)) { # Save the policy to the full path Invoke-CAIQLogging -Message "No previous version detected. Saving backup of policy $($policy_display_name) to $($policy_path)" Export-CAIQJson @export_json_params Invoke-CAIQLogging -Message "Policy $($policy_display_name) has been saved to $($policy_path) successfully" -ForegroundColor Green # Checking if this is a newly created policy Invoke-CAIQLogging -Message "Checking if this is a newly created policy" $add_audit_logs = Get-CAIQDirectoryAuditLog -PolicyId $policy_id -Action "Add" Foreach ($add_audit_log in $add_audit_logs) { $add_audit_log | Add-Member -MemberType NoteProperty -Name "Action" -Value "Add" $audit_logs.Add($add_audit_log) } } # Get the audit logs for the policy Invoke-CAIQLogging -Message "Getting the audit logs for the policy $($policy_display_name)" $update_audit_logs = Get-CAIQDirectoryAuditLog -PolicyId $policy_id Foreach ($update_audit_log in $update_audit_logs) { # Add the action to the audit log $update_audit_log | Add-Member -MemberType NoteProperty -Name "Action" -Value "Update" # Add the audit log to the audit logs list $audit_logs.Add($update_audit_log) } If (!$audit_logs) { Invoke-CAIQLogging -Message "The policy $($policy_display_name) has no modifications to report" -ForegroundColor Cyan } Else { Foreach ($audit_log in $audit_logs) { # Invoke-CAIQAuditLogProcessing parameters $process_log_params = @{} $process_log_params["AuditLog"] = $audit_log $process_log_params["Policy"] = $policy $process_log_params["PolicyPath"] = $policy_path # Process the audit log $html_report_obj = Invoke-CAIQAuditLogProcessing @process_log_params # Export the audit log to the audit Invoke-CAIQLogging -Message "Exporting the audit log to the audit log archive" Try { #$audit_log | Export-Csv -Path $auditLogArchive $audit_log | ConvertTo-Json | Out-File -FilePath $auditLogArchive -Append } Catch { Invoke-CAIQLogging -Message "Could not export the audit log to the audit log archive due to the following error: $_" -ForegroundColor Red } # Add the HTML report object to the policy reports $policy_reports.Add([PSCustomObject]$html_report_obj) } } } Catch { Invoke-CAIQLogging -Message "Could not process policy $($policy_display_name) due to the following error: $_" -ForegroundColor Red } } #endregion #region Identify and process deleted policies # Get the deleted policies Invoke-CAIQLogging -Message "Identifying deleted policies" $deleted_policies = Get-CAIQDeletedPolicies -Path $outputPath -Policies $policies If ($deleted_policies) { Invoke-CAIQLogging -Message "Found $(@($deleted_policies).Count) deleted policies" -ForegroundColor Green # Build the archived policy path, this is the policy path with policies that have been deleted $deleted_policy_path = Join-Path -Path $outputPath -ChildPath "Policies\Deleted" Foreach ($deleted_policy in $deleted_policies) { Invoke-CAIQLogging -Message "Processing deleted policy $($deleted_policy)" # Get the policy id $policy_id = $deleted_policy # Build the policy path $policy_path = Join-Path -Path $outputPath -ChildPath "Policies\$policy_id" # Find deletion audit log $audit_log = Get-CAIQDirectoryAuditLog -PolicyId $policy_id -Action "Delete" If ($audit_log) { # Add the action to the audit log $audit_log | Add-Member -MemberType NoteProperty -Name "Action" -Value "Delete" # Retrieve the policy settings from the audit log $policy = ConvertTo-CAIQConditionalAccessObject -AuditLog $audit_log -ValueType "oldValue" # Invoke-CAIQAuditLogProcessing parameters $process_log_params = @{} $process_log_params["AuditLog"] = $audit_log $process_log_params["Policy"] = $policy $process_log_params["PolicyPath"] = $policy_path # Move-Item parameters $move_item_params = @{} $move_item_params["Path"] = $policy_path $move_item_params["Destination"] = $deleted_policy_path $move_item_params["Force"] = $true # Process the deletion audit log $html_report_obj = Invoke-CAIQAuditLogProcessing @process_log_params # Export the audit log to the audit log archive Invoke-CAIQLogging -Message "Exporting the audit log to the audit log archive" Try { #ConvertTo-CAIQFlatObject -InputObject $audit_log | Export-Csv -Path $auditLogArchive $audit_log | ConvertTo-Json | Set-Content -Path $auditLogArchive } Catch { Invoke-CAIQLogging -Message "Could not export the audit log to the audit log archive due to the following error: $_" -ForegroundColor Red } # Move the deleted policy to the archived folder Invoke-CAIQLogging -Message "Moving the deleted policy's data to $($deleted_policy_path)" Try { Move-Item @move_item_params } Catch { Invoke-CAIQLogging -Message "Could not move the deleted policy's data to $($deleted_policy_path) due to the following error: $_" -ForegroundColor Red } # Add the HTML report object to the policy reports $policy_reports.Add([PSCustomObject]$html_report_obj) } Else { Invoke-CAIQLogging -Message "No deletion audit log found for policy $($deleted_policy)" } } } Else { Invoke-CAIQLogging -Message "No deleted policies found" -ForegroundColor Cyan } #endregion } End { #region Post Processing If ($policy_reports) { Try { # Create the html file Invoke-CAIQLogging -Message "Creating $($fileName) file" $index_html_obj = New-CAIQHtml -PolicyReports $policy_reports @new_index_params Invoke-CAIQLogging -Message "$($fileName) created at: $($index_html_obj.Path)" -ForegroundColor Green If ($invokeHtml) { Invoke-Item $index_html_obj.Path } } Catch { Invoke-CAIQLogging -Message "Could not create the $($fileName) file due to the following error: $_" -ForegroundColor Red Exit 1 } } Else { Invoke-CAIQLogging -Message "No policy changes detected." -ForegroundColor Yellow } #endregion } } |