#requires -Version 7.2
[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [ValidatePattern('^[0-9a-fA-F-]{36}$')]
    [string]$SubscriptionId,

    [ValidatePattern('^rg-machine-configuration-deep-dive-we$')]
    [string]$ResourceGroupName = 'rg-machine-configuration-deep-dive-we',

    [ValidateSet('Infrastructure', 'Audit', 'Drift', 'Enforce', 'Final', 'Cleanup')]
    [string]$Scenario = 'Final'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$root = Split-Path -Parent $PSScriptRoot
$privateRoot = Join-Path $root '.private'
$contextPath = Join-Path $privateRoot 'runtime-context.json'
$outputPath = Join-Path $root 'sanitized-evidence.md'

function Invoke-Az {
    param([string[]]$Arguments, [switch]$AllowFailure)
    $effectiveArguments = @($Arguments) + '--only-show-errors'
    $previousPythonWarnings = $env:PYTHONWARNINGS
    $env:PYTHONWARNINGS = 'ignore'
    try { $output = & az @effectiveArguments 2>&1 }
    finally { $env:PYTHONWARNINGS = $previousPythonWarnings }
    $code = $LASTEXITCODE
    if ($code -ne 0 -and -not $AllowFailure) { throw ($output -join "`n") }
    [pscustomobject]@{ ExitCode = $code; Text = ($output -join "`n").Trim() }
}

function Protect-Text {
    param([AllowNull()][string]$Text)
    if ($null -eq $Text) { return '' }
    $safe = $Text -replace [regex]::Escape($SubscriptionId), '<subscription-redacted>'
    $safe = $safe -replace '(?i)/subscriptions/[0-9a-f-]{36}', '/subscriptions/<redacted>'
    $safe = $safe -replace '(?i)\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b', '<guid-redacted>'
    $safe = $safe -replace '(?i)https://[a-z0-9]{3,24}\.blob\.core\.windows\.net', 'https://<storage-redacted>.blob.core.windows.net'
    $safe = $safe -replace '(?i)\b\d{1,3}(?:\.\d{1,3}){3}\b', '<ip-redacted>'
    return $safe
}

$active = Invoke-Az @('account', 'show', '--query', '{id:id,state:state}', '-o', 'json')
$activeObject = $active.Text | ConvertFrom-Json
if ($activeObject.id -ne $SubscriptionId -or $activeObject.state -ne 'Enabled') { throw 'The exact active subscription check failed.' }

"# Sanitized Azure Machine Configuration evidence`n`n- Scenario: **$Scenario**`n- Captured UTC: **$([DateTime]::UtcNow.ToString('o'))**`n" | Set-Content -LiteralPath $outputPath -Encoding utf8

function Add-Evidence {
    param([string]$Title, [string[]]$Arguments)
    $result = Invoke-Az -Arguments $Arguments -AllowFailure
    "## $Title`n`n``````text`n$(Protect-Text $result.Text)`n```````n" | Add-Content -LiteralPath $outputPath -Encoding utf8
}

$exists = (Invoke-Az @('group', 'exists', '-n', $ResourceGroupName, '-o', 'tsv')).Text.Trim()
if ($exists -ne 'true') {
    "## Cleanup`n`nThe exact workshop resource group is absent.`n" | Add-Content -LiteralPath $outputPath -Encoding utf8
    Write-Host "Evidence saved: $outputPath"
    return
}

$context = if (Test-Path -LiteralPath $contextPath) { Get-Content -Raw -LiteralPath $contextPath | ConvertFrom-Json } else { $null }
Add-Evidence 'Resource inventory' @('resource', 'list', '-g', $ResourceGroupName, '--query', '[].{Name:name,Type:type,Location:location}', '-o', 'table')
Add-Evidence 'Private VM network state' @('vm', 'list-ip-addresses', '-g', $ResourceGroupName, '--query', '[].virtualMachine.{Name:name,Private:network.privateIpAddresses,Public:network.publicIpAddresses}', '-o', 'json')
Add-Evidence 'VM tags and identities' @('vm', 'list', '-g', $ResourceGroupName, '--query', '[].{Name:name,Ring:tags.ConfigurationRing,Role:tags.ServerRole,IdentityType:identity.type}', '-o', 'table')
Add-Evidence 'NSG custom rules' @('network', 'nsg', 'rule', 'list', '-g', $ResourceGroupName, '--nsg-name', 'nsg-machine-config-workload-we', '--query', '[].{Name:name,Direction:direction,Access:access,Source:sourceAddressPrefix,Port:destinationPortRange}', '-o', 'table')
Add-Evidence 'Storage network posture' @('storage', 'account', 'show', '-g', $ResourceGroupName, '-n', $context.StorageAccountName, '--query', '{PublicNetworkAccess:publicNetworkAccess,DefaultAction:networkRuleSet.defaultAction,AllowSharedKey:allowSharedKeyAccess,AllowBlobPublic:allowBlobPublicAccess,MinimumTls:minimumTlsVersion}', '-o', 'json')
Add-Evidence 'Private Endpoint connection' @('network', 'private-endpoint', 'show', '-g', $ResourceGroupName, '-n', 'pe-machine-config-blob-we', '--query', '{State:provisioningState,Connection:privateLinkServiceConnections[0].privateLinkServiceConnectionState.status,GroupIds:privateLinkServiceConnections[0].groupIds}', '-o', 'json')
Add-Evidence 'Machine Configuration extensions' @('vm', 'extension', 'list', '-g', $ResourceGroupName, '--vm-name', 'vm-mc-ws2022-we', '--query', '[].{Name:name,Version:typeHandlerVersion,State:provisioningState}', '-o', 'table')
Add-Evidence 'Application VM extensions' @('vm', 'extension', 'list', '-g', $ResourceGroupName, '--vm-name', 'vm-mc-ws2025-we', '--query', '[].{Name:name,Version:typeHandlerVersion,State:provisioningState}', '-o', 'table')
Add-Evidence 'Guestconfig CLI baseline assignment' @('guestconfig', 'guest-configuration-assignment', 'list', '-g', $ResourceGroupName, '--vm-name', 'vm-mc-ws2022-we', '--query', "[?starts_with(name, 'WorkshopWindowsBaseline')].{Name:name,Compliance:properties.complianceStatus,Type:properties.guestConfiguration.assignmentType}", '-o', 'table')
Add-Evidence 'Guestconfig CLI application assignment' @('guestconfig', 'guest-configuration-assignment', 'list', '-g', $ResourceGroupName, '--vm-name', 'vm-mc-ws2025-we', '--query', "[?starts_with(name, 'WorkshopWindowsBaseline')].{Name:name,Compliance:properties.complianceStatus,Type:properties.guestConfiguration.assignmentType}", '-o', 'table')
Add-Evidence 'Policy assignments' @('policy', 'assignment', 'list', '--scope', "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName", '--query', '[].{Name:name,DisplayName:displayName,EnforcementMode:enforcementMode}', '-o', 'table')
Add-Evidence 'Policy compliance states' @('policy', 'state', 'list', '--resource-group', $ResourceGroupName, '--query', '[].{Resource:resourceId,Assignment:policyAssignmentName,Reference:policyDefinitionReferenceId,State:complianceState}', '-o', 'json')
Add-Evidence 'Machine Configuration Resource Graph' @('graph', 'query', '-q', "guestconfigurationresources | where id contains '$ResourceGroupName' | project vm=tostring(split(id,'/')[8]), assignment=name, compliance=tostring(properties.complianceStatus), type=tostring(properties.assignmentType), start=tostring(properties.latestAssignmentReport.startTime), end=tostring(properties.latestAssignmentReport.endTime)", '--query', 'data', '-o', 'table')
Add-Evidence 'Policy remediation state' @('rest', '--method', 'get', '--url', "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.PolicyInsights/remediations?api-version=2024-10-01", '--query', 'value[].{Name:name,State:properties.provisioningState,Created:properties.createdOn,Completed:properties.lastUpdatedOn}', '-o', 'table')

if ($Scenario -in @('Drift', 'Enforce', 'Final')) {
    $verificationScript = Get-Content -Raw -LiteralPath (Join-Path $PSScriptRoot 'Set-WorkshopConfigurationDrift.ps1')
    foreach ($item in @(@{ Vm = 'vm-mc-ws2022-we'; Role = 'Baseline' }, @{ Vm = 'vm-mc-ws2025-we'; Role = 'Application' })) {
        $temp = Join-Path $privateRoot "evidence-$($item.Vm).ps1"
        Set-Content -LiteralPath $temp -Value $verificationScript -Encoding utf8
        try {
            Add-Evidence "Guest desired-state proof for $($item.Vm)" @('vm', 'run-command', 'invoke', '-g', $ResourceGroupName, '-n', $item.Vm, '--command-id', 'RunPowerShellScript', '--scripts', "@$temp", '--parameters', "Scenario=$($item.Role)", 'Action=Verify', '--query', 'value[0].message', '-o', 'tsv')
        }
        finally { Remove-Item -LiteralPath $temp -Force -ErrorAction SilentlyContinue }
    }
}

if ($context -and $context.WorkspaceResourceId) {
    $workspace = Invoke-Az -Arguments @('rest', '--method', 'get', '--url', "https://management.azure.com$($context.WorkspaceResourceId)?api-version=2025-07-01", '--query', 'properties.customerId', '-o', 'tsv')
    if ($workspace.ExitCode -eq 0 -and $workspace.Text) {
        $queryPath = Join-Path $privateRoot 'log-analytics-query.json'
        @{
            query = "Event | where TimeGenerated > ago(24h) | where Source == 'AzureWorkshopzMachineConfiguration' | project TimeGenerated, Computer, EventID, RenderedDescription | order by TimeGenerated asc"
        } | ConvertTo-Json -Compress | Set-Content -LiteralPath $queryPath -Encoding utf8
        try {
            Add-Evidence 'Log Analytics workshop events' @('rest', '--method', 'post', '--url', "https://api.loganalytics.io/v1/workspaces/$($workspace.Text)/query", '--resource', 'https://api.loganalytics.io', '--body', "@$queryPath", '--query', 'tables[0].rows', '-o', 'table')
        }
        finally { Remove-Item -LiteralPath $queryPath -Force -ErrorAction SilentlyContinue }
    }
}

Write-Host "Evidence saved: $outputPath"
