Public/Application/Get-CardResponse.ps1
|
function Get-CardResponse { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Variable used in template')] [system.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Settings variable used in module')] [system.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseUsingScopeModifierInNewRunspaces', '', Justification = 'Variable used in runspace via parameter')] param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Json, [parameter(Mandatory = $false)] [string]$PromptTitle = $_MvRACSettings.'Get-Response'.PromptTitle, [parameter(Mandatory = $false)] [string]$CardTitle = $_MvRACSettings.'Get-Response'.CardTitle, [parameter(Mandatory = $false)] [string]$LogoUrl = $_MvRACSettings.'Get-Response'.LogoUrl, [parameter(Mandatory = $false)] [string]$LogoHeaderText = $_MvRACSettings.'Get-Response'.LogoHeader, [bool]$ShowVersion = $_MvRACSettings.'Get-Response'.ShowVersion, [parameter(Mandatory = $false)] [int]$PortNumber = $_MvRACSettings.'Get-Response'.PortNumber, [parameter(Mandatory = $false)] [string]$HeaderBackgroundStart = $_MvRACSettings.'Get-Response'.HeaderBackgroundStart, [parameter(Mandatory = $false)] [string]$HeaderBackgroundEnd = $_MvRACSettings.'Get-Response'.HeaderBackgroundEnd, [parameter(Mandatory = $false)] [ValidateSet("Browser", "EdgeApp")] [string]$ViewMethod = $_MvRACSettings.'Get-Response'.ViewMethod, [parameter(Mandatory = $false)] [int]$WindowWidth = 400, [parameter(Mandatory = $false)] [int]$WindowHeight = 600, [switch]$HideHeader, [switch]$ShowTitle, [switch]$ServeOnly, [switch]$AutoSize ) #Serve the card as a web page to capture response process { $html = Get-Content -Path "$PSScriptRoot\Templates\PromptCard.html" -Raw # Find an available port if the default is in use $MaxPortRetries = 10 $CurrentPort = $PortNumber $PortFound = $false for ($i = 0; $i -lt $MaxPortRetries; $i++) { $TestPort = $CurrentPort + $i # Test if port is available try { $TestListener = [System.Net.HttpListener]::new() if ($IsWindows) { $TestListener.Prefixes.Add("http://localhost:$TestPort/") } else { $TestListener.Prefixes.Add("http://+:$TestPort/") } $TestListener.Start() $TestListener.Stop() $TestListener.Close() # Port is available $CurrentPort = $TestPort $PortFound = $true if ($i -gt 0) { Write-Verbose "Port $PortNumber was in use, using port $CurrentPort instead" } break } catch { # Port is in use, try next one Write-Verbose "Port $TestPort is in use, trying next port..." continue } } if (-not $PortFound) { Write-Error "Could not find an available port after $MaxPortRetries attempts starting from $PortNumber" return } # Set the service URL based on OS if ($IsWindows) { $ServiceUrl = "http://localhost:$CurrentPort/" } else { $ServiceUrl = "http://+:$CurrentPort/" } $LogoHeader = $LogoHeaderText if ( $ShowVersion ) { $LogoHeader = "$LogoHeaderText <span class='version'>v$ModuleVersion</span>" } if ( -not $ShowTitle ) { $TitleClass = "hide-title" } else { $TitleClass = "" } # Build the extensions payload using the provided JSON $ExtensionsPayload = Build-ExtensionsPayload -Json $Json -ScriptsPath "$PSScriptRoot\Templates\Extension\Script" -StylesPath "$PSScriptRoot\Templates\Extension\Style" -EncapsulateStyles if ($ExtensionsPayload) { $ExtensionsJs = $ExtensionsPayload.Scripts $ExtensionsCss = $ExtensionsPayload.Styles } else { $ExtensionsJs = '' $ExtensionsCss = '' } # Generate a unique response GUID to track the response of the card $ResponseGuid = [guid]::NewGuid().ToString() #Hide header class if ( $HideHeader ) { $HideHeaderClass = "hide-header" } else { $HideHeaderClass = "" } # Set max width and height for autosize if ( $AutoSize ) { Add-Type -AssemblyName System.Windows.Forms $Screen = [System.Windows.Forms.Screen]::PrimaryScreen $MaxWidth = [math]::Max(1200, $Screen.WorkingArea.Width - 100) $MaxHeight = [math]::Max(900, $Screen.WorkingArea.Height - 100) } else { $MaxWidth = 1200 $MaxHeight = 900 } # Expand any variables in the HTML template (ResponseGuid, ServiceUrl, CardTitle, LogoUrl, etc) $html = $ExecutionContext.InvokeCommand.ExpandString($html) #Start the local web server to serve the card and listen for response $WSSession = New-LocalCardWebserver -Html $html -ServiceUrl $ServiceUrl -ResponseGuid $ResponseGuid #Start the listener $asyncResult = $WSSession.PowerShell.BeginInvoke() #Open browser to the page if (!$ServeOnly) { # Initialize EdgeAppProcess variable for window close detection $EdgeAppProcess = $null switch ($ViewMethod) { "EdgeApp" { try { # Use Edge in app mode for clean WebView2 experience Write-Information "Opening in Edge (WebView2 browser mode)..." # Create a wrapper HTML that resizes window and redirects $wrapperHtml = $ExecutionContext.InvokeCommand.ExpandString((Get-Content -Path "$PSScriptRoot\Templates\EdgeAppLoader.html" -Raw)) $tempFile = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "AdaptiveCard_$(Get-Random).html") [System.IO.File]::WriteAllText($tempFile, $wrapperHtml, [System.Text.Encoding]::UTF8) # Open with Edge app mode and capture the process $ParentEdgeProcess = Start-Process "msedge" -ArgumentList "--app=file:///$($tempFile.Replace('\','/'))" -PassThru # Wait for Edge to create the app window Start-Sleep -Milliseconds 100 #loop to find the correct Edge window $MaxPollTries = 50 do { Start-Sleep -Milliseconds 100 $EdgeAppProcess = Get-Process -Name "msedge" -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowTitle -eq $CardTitle -and $_.HasExited -eq $false -and $_.MainWindowHandle -ne 0 } } while (-not $EdgeAppProcess -and $MaxPollTries--) if ($EdgeAppProcess) { Write-Verbose "Found Edge app process: ID=$($EdgeAppProcess.Id), Title='$($EdgeAppProcess.MainWindowTitle)'" } else { Write-Warning "Could not find Edge app window. Window close detection will not work." } # Clean up temp file after a delay Start-Job -ScriptBlock { param($file) Start-Sleep -Seconds 10 if (Test-Path $file) { Remove-Item $file -Force -ErrorAction SilentlyContinue } } -ArgumentList $tempFile | Out-Null } catch { Write-Warning "Failed to launch Edge: $($_.Exception.Message)" Write-Warning "Falling back to default browser..." Start-Process $ServiceUrl } } "Browser" { #Test for parameters that are not compatible with browser view if ( $AutoSize ) { Write-Warning "AutoSize parameter is not supported in Browser view. Ignoring." } if ( $WindowWidth -ne 400 -or $WindowHeight -ne 600 ) { Write-Warning "Custom window size parameters are not supported in Browser view. Ignoring." } Start-Process $ServiceUrl } default {} } $WaitingPrompt = "{blue}[{white}Waiting for user response{gray}{use Ctrl+C to cancel}{blue}]" #Set The Dot count for animation $DotCount = 0 Write-ColoredHost $WaitingPrompt -NoNewLine [console]::CursorVisible = $false #Test to see if $asyncResult halted abnormally if ($asyncResult.IsCompleted -eq $True ) { Write-Warning "Async operation did not complete as expected." #Grab the log stream from the runspace $logStream = $PowerShell.Streams.Error $logStream | ForEach-Object { Write-Verbose "Error: $_" } } try { while ($asyncResult.IsCompleted -eq $false) { #If crtl+c is pressed, stop listening Start-Sleep -Milliseconds 250 $DotCount = ($DotCount + 1) % 7 $Dots = "►" * $DotCount if ($DotCount -eq 0) { $Dots = " " } $PromptToShow = "{blue}[{white}Waiting for user response{gray}(use Ctrl+C to cancel){blue}] $Dots" #Overwrite the previous line $Host.UI.RawUI.CursorPosition = @{X = 0; Y = $Host.UI.RawUI.CursorPosition.Y } #Hide the cursor while waiting Write-ColoredHost ("`r" + $PromptToShow) -NoNewLine #If the the viewMode is EdgeApp and the window is no longer open, cancel waiting if ( $ViewMethod -eq "EdgeApp") { # Check if the Edge process is still running if ($EdgeAppProcess -and $EdgeAppProcess.HasExited -and $asyncResult.IsCompleted -eq $false) { Write-Verbose "EdgeApp window was closed by user" throw "WindowClosed" } } } Write-ColoredHost "{Green}[V]" #Show the cursor again [console]::CursorVisible = $true $data = $WSSession.PowerShell.EndInvoke($asyncResult) } catch { Write-Error "An error occurred: $_" } finally { if ($null -eq $data) { try { [void](Invoke-WebRequest -Uri $ServiceUrl -Method Post -OperationTimeoutSeconds 1 -ConnectionTimeoutSeconds 1 -Body @{responseGuid = $ResponseGuid }) } catch { [void]$_ } [void]($WSSession.PowerShell.Stop()) } #Kill the Edge app process if still running # if ($EdgeAppProcess) { # # Stop-Process -Id $EdgeAppProcess.Id -Force -ErrorAction SilentlyContinue # } #Force kill the powershell if still running [void]($WSSession.PowerShell.Dispose()) #Close the runspace $WSSession.Runspace.Close() $WSSession.Runspace.Dispose() } if ( ![string]::IsNullOrWhiteSpace($data) ) { return $data | ConvertFrom-Json | Select-Object -ExcludeProperty ResponseGuid } } } } |