Private/ScriptRecording.ps1
|
# InTUI Script Recording # Records Graph API actions for playback/automation function Start-InTUIRecording { <# .SYNOPSIS Starts recording Graph API actions. #> [CmdletBinding()] param() if ($script:RecordingEnabled) { Write-InTUILog -Level 'WARN' -Message "Recording already in progress" return $false } $script:RecordingEnabled = $true $script:RecordedActions = [System.Collections.Generic.List[hashtable]]::new() $script:RecordingStartTime = [DateTime]::UtcNow Write-InTUILog -Message "Recording started" return $true } function Stop-InTUIRecording { <# .SYNOPSIS Stops recording Graph API actions. .OUTPUTS Returns the recorded actions. #> [CmdletBinding()] param() if (-not $script:RecordingEnabled) { Write-InTUILog -Level 'WARN' -Message "No recording in progress" return $null } $script:RecordingEnabled = $false $script:RecordingEndTime = [DateTime]::UtcNow $actions = $script:RecordedActions $duration = ($script:RecordingEndTime - $script:RecordingStartTime).TotalSeconds Write-InTUILog -Message "Recording stopped" -Context @{ ActionCount = $actions.Count Duration = [math]::Round($duration) } return @{ Actions = $actions StartTime = $script:RecordingStartTime EndTime = $script:RecordingEndTime Duration = $duration } } function Add-InTUIRecordedAction { <# .SYNOPSIS Adds an action to the current recording. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('GET', 'POST', 'PATCH', 'DELETE')] [string]$Method, [Parameter(Mandatory)] [string]$Uri, [Parameter()] [hashtable]$Body, [Parameter()] [switch]$Beta ) if (-not $script:RecordingEnabled) { return } # Only record write operations (POST, PATCH, DELETE) # GET requests are read-only and don't need to be replayed if ($Method -eq 'GET') { return } $action = @{ Timestamp = [DateTime]::UtcNow.ToString('o') Method = $Method Uri = $Uri Beta = [bool]$Beta } if ($Body) { $action['Body'] = $Body } $script:RecordedActions.Add($action) Write-InTUILog -Message "Action recorded" -Context @{ Method = $Method Uri = $Uri ActionNumber = $script:RecordedActions.Count } } function Export-InTUIRecording { <# .SYNOPSIS Exports recorded actions to a PowerShell script. .PARAMETER Recording The recording object from Stop-InTUIRecording. .PARAMETER Path Output file path. Defaults to timestamped file in current directory. .PARAMETER IncludeConnection Include Connect-MgGraph call at the start. #> [CmdletBinding()] param( [Parameter(Mandatory)] $Recording, [Parameter()] [string]$Path, [Parameter()] [switch]$IncludeConnection ) if (-not $Recording -or $Recording.Actions.Count -eq 0) { Write-InTUILog -Level 'WARN' -Message "No actions to export" return $null } if (-not $Path) { $timestamp = [DateTime]::UtcNow.ToString('yyyyMMdd_HHmmss') $Path = Join-Path $script:InTUIConfig.DefaultExportPath "InTUI_Recording_$timestamp.ps1" } $scriptLines = [System.Collections.Generic.List[string]]::new() # Header $scriptLines.Add("# InTUI Recorded Script") $scriptLines.Add("# Generated: $([DateTime]::UtcNow.ToString('yyyy-MM-dd HH:mm:ss')) UTC") $scriptLines.Add("# Recording Duration: $([math]::Round($Recording.Duration)) seconds") $scriptLines.Add("# Actions: $($Recording.Actions.Count)") $scriptLines.Add("") $scriptLines.Add("#Requires -Modules Microsoft.Graph.Authentication") $scriptLines.Add("") if ($IncludeConnection) { $scriptLines.Add("# Connect to Microsoft Graph") $scriptLines.Add("Connect-MgGraph -Scopes @(") $scriptLines.Add(" 'DeviceManagementManagedDevices.ReadWrite.All',") $scriptLines.Add(" 'DeviceManagementApps.ReadWrite.All',") $scriptLines.Add(" 'User.Read.All',") $scriptLines.Add(" 'Group.Read.All'") $scriptLines.Add(") -NoWelcome") $scriptLines.Add("") } $scriptLines.Add("# Recorded Actions") $scriptLines.Add("`$results = @()") $scriptLines.Add("") $actionNum = 0 foreach ($action in $Recording.Actions) { $actionNum++ $scriptLines.Add("# Action $actionNum - $($action.Method) at $($action.Timestamp)") $baseUrl = if ($action.Beta) { 'https://graph.microsoft.com/beta' } else { 'https://graph.microsoft.com/v1.0' } $fullUri = if ($action.Uri -match '^https://') { $action.Uri } else { "$baseUrl/$($action.Uri.TrimStart('/'))" } $invokeParams = "`$params$actionNum = @{`n" $invokeParams += " Uri = '$fullUri'`n" $invokeParams += " Method = '$($action.Method)'`n" $invokeParams += " OutputType = 'PSObject'`n" if ($action.Body) { $bodyJson = $action.Body | ConvertTo-Json -Depth 10 -Compress $invokeParams += " Body = '$bodyJson'`n" $invokeParams += " ContentType = 'application/json'`n" } $invokeParams += "}" $scriptLines.Add($invokeParams) $scriptLines.Add("") $scriptLines.Add("try {") $scriptLines.Add(" `$result$actionNum = Invoke-MgGraphRequest @params$actionNum") $scriptLines.Add(" `$results += @{ Success = `$true; Action = $actionNum; Result = `$result$actionNum }") $scriptLines.Add(" Write-Host `"Action $actionNum completed successfully`" -ForegroundColor Green") $scriptLines.Add("}") $scriptLines.Add("catch {") $scriptLines.Add(" `$results += @{ Success = `$false; Action = $actionNum; Error = `$_.Exception.Message }") $scriptLines.Add(" Write-Host `"Action $actionNum failed: `$(`$_.Exception.Message)`" -ForegroundColor Red") $scriptLines.Add("}") $scriptLines.Add("") } $scriptLines.Add("# Summary") $scriptLines.Add("`$successCount = @(`$results | Where-Object { `$_.Success }).Count") $scriptLines.Add("`$failCount = @(`$results | Where-Object { -not `$_.Success }).Count") $scriptLines.Add("Write-Host `"Completed: `$successCount succeeded, `$failCount failed`"") $scriptLines.Add("") $scriptLines.Add("`$results") try { $scriptContent = $scriptLines -join "`n" Set-Content -Path $Path -Value $scriptContent -Encoding UTF8 Write-InTUILog -Message "Recording exported" -Context @{ Path = $Path ActionCount = $Recording.Actions.Count } return $Path } catch { Write-InTUILog -Level 'ERROR' -Message "Failed to export recording: $($_.Exception.Message)" return $null } } function Get-InTUIRecordingStatus { <# .SYNOPSIS Returns the current recording status. #> [CmdletBinding()] param() if (-not $script:RecordingEnabled) { return @{ IsRecording = $false ActionCount = 0 Duration = 0 } } $duration = ([DateTime]::UtcNow - $script:RecordingStartTime).TotalSeconds return @{ IsRecording = $true ActionCount = $script:RecordedActions.Count Duration = [math]::Round($duration) StartTime = $script:RecordingStartTime } } function Show-InTUIRecordingMenu { <# .SYNOPSIS Shows the recording control menu. #> [CmdletBinding()] param() $status = Get-InTUIRecordingStatus if ($status.IsRecording) { $statusText = "[red]Recording in progress[/] - $($status.ActionCount) actions captured ($($status.Duration)s)" } else { $statusText = "[grey]Not recording[/]" } Clear-Host Show-InTUIHeader Show-InTUIBreadcrumb -Path @('Home', 'Script Recording') Write-InTUIText $statusText Write-InTUIText "" $choices = if ($status.IsRecording) { @( 'Stop Recording and Export', 'Stop Recording (Discard)', '─────────────', 'Back' ) } else { @( 'Start Recording', '─────────────', 'Back' ) } $selection = Show-InTUIMenu -Title "[DarkOrange]Recording Options[/]" -Choices $choices switch ($selection) { 'Start Recording' { if (Start-InTUIRecording) { Show-InTUISuccess "Recording started. Navigate and perform actions to record." } else { Show-InTUIWarning "Failed to start recording." } Read-InTUIKey } 'Stop Recording and Export' { $recording = Stop-InTUIRecording if ($recording -and $recording.Actions.Count -gt 0) { $confirm = Show-InTUIConfirm -Message "[yellow]Include Connect-MgGraph in the script?[/]" $path = Export-InTUIRecording -Recording $recording -IncludeConnection:$confirm if ($path) { Show-InTUISuccess "Recording exported to: $path" } else { Show-InTUIWarning "No actions were recorded." } } else { Show-InTUIWarning "No actions were recorded." } Read-InTUIKey } 'Stop Recording (Discard)' { $null = Stop-InTUIRecording $script:RecordedActions = $null Show-InTUISuccess "Recording discarded." Read-InTUIKey } } } |