Skip to content

Update whats-happening-azure-data-studio.md #20

Update whats-happening-azure-data-studio.md

Update whats-happening-azure-data-studio.md #20

name: Allow signoff for files
permissions:
pull-requests: write
statuses: write
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened]
jobs:
build:
name: Run Script
if: github.repository_visibility == 'private'
runs-on: ubuntu-latest
steps:
- name: Script
shell: pwsh
env:
PayloadJson: ${{ toJSON(github) }}
AccessToken: ${{ secrets.GITHUB_TOKEN }}
# For testing set to a little used folder as the only target
BlockedFilesAndFolders: '[
"ssms/f1-help/connect-to-microsoft-azure-storage.md",
"ssms/f1-help/connect-to-any-sql-server-component-from-sql-server-management-studio.md"
]'
run: |
# Print out current date and time
$currentDateTime = Get-Date
$currentDateTimeString = "{0:yyyy-MM-dd HH:mm:ss}" -f $currentDateTime
Write-Output "Run start: $currentDateTimeString"
# Get GitHub data and event
$GitHubData = $env:PayloadJson | ConvertFrom-Json -Depth 50
$GitRequestEvent = $GitHubData.event_name
if ($GitRequestEvent -eq "issue_comment")
{
$PrGitHubLink = $GitHubData.event.issue.html_url
}
$AccessToken = $env:AccessToken
$BlockedFilesAndFolders = $env:BlockedFilesAndFolders | ConvertFrom-Json
$DefaultBranch = $GitHubData.event.repository.default_branch
If ($GitRequestEvent -eq "issue_comment") {$GitHubState = $GitHubData.event.issue.state} ElseIf ($GitRequestEvent -eq "pull_request_target") {$GitHubState = $GitHubData.event.pull_request.state}
$GitHubAction = $GitHubData.event.action
$GitHubSender = $GitHubData.event.sender.login
$GitHubRepoName = $GitHubData.event.repository.name
$CommentUser = $GitHubData.event.comment.user.login
$PullRequestOpener = $GitHubData.event.pull_request.user.login
If ($GitRequestEvent -eq "issue_comment")
{
$PrIssueNumber = $GitHubData.event.issue.number
$PrUrl = $GitHubData.event.issue.pull_request.url
$IssueUrl = $GitHubData.event.issue.url
$CommentsUrl = $GitHubData.event.issue.comments_url
}
ElseIf ($GitRequestEvent -eq "pull_request_target")
{
$PrIssueNumber = $GitHubData.event.pull_request.number
$PrUrl = $GitHubData.event.pull_request.html_url
$IssueUrl = $GitHubData.event.pull_request.issue_url
$CommentsUrl = $GitHubData.event.pull_request.comments_url
}
If ($GitRequestEvent -eq "issue_comment") {$PrUrl = $GitHubData.event.issue.pull_request.url} ElseIf ($GitRequestEvent -eq "pull_request_target") {$PrUrl = $GitHubData.event.pull_request.url}
$UserPermissionUrl = $GitHubData.event.repository.collaborators_url.Replace("{/collaborator}", "/$CommentUser/permission" )
$UserPermissionPROpenerUrl = $GitHubData.event.repository.collaborators_url.Replace("{/collaborator}", "/$PullRequestOpener/permission" )
$RepoLabelUrl = $GitHubData.event.repository.labels_url
$GitHubHeaders = @{}
$GitHubHeaders.Add("Authorization","token $($AccessToken)")
$GitHubHeaders.Add("User-Agent", "OfficeDocs")
$StatusHelpUrl = "https://dev.azure.com/msft-skilling/Content/_wiki/wikis/Database%20Docs/2358/Partner-publishing-workflow-pilot"
$StatusCheckName = "Allow sign-off of PR files"
$Status = @{}
$Status.Add("context", $StatusCheckName)
$Status.Add("target_url", $StatusHelpUrl)
$CatastrophicError = $False
$SignoffApprovalMessage = "Signoff approved by content team."
$SignoffBlockedMessage = "PR contains files under temporary content freeze."
$SignOffString = "#sign-off"
$ApprovedString = "#approve-files"
$SignOffRegex = "\s*$SignOffString\s*"
$ApprovedRegex = "\s*$ApprovedString\s*"
$EscalationEmail = "[Data Docs Team](mailto:[email protected]?subject=[PR%20REVIEW]%20Question%20on%20$GitHubRepoName%20PR%20$PrIssueNumber&body=$PrGitHubLink)"
$ContentLeadsUrl = "[the content lead in your area](https://dev.azure.com/msft-skilling/Content/_dashboards/dashboard/b25043af-cab3-4b2c-8aac-9e8da15a796b)"
$PRContainsBlockedFilesMessage = "This PR contains files that are temporarily blocked from edits due to a conference-related file-specific content freeze. You can attempt to merge this PR after the upcoming conference. If you have any questions, please reach out to $ContentLeadsUrl or email the $EscalationEmail."
$CheckFailed = $False
$ContentApprovedLabelColor = "2A8000"
$ContentApprovedLabelDescription = "Changes are permitted to the files in this PR."
$ContentApprovedLabel = "pr-files-approved"
$FilesBlockedForConfLabelColor = "eeee88"
$FilesBlockedForConfLabelDescription = "Files in PR were blocked from sign off due to upcoming conference."
$FilesBlockedForConfLabel = "files-blocked-for-conference"
Write-Host "Repo: $GitHubRepoName"
Write-Host "Sender: $GitHubSender"
Write-Host "Request event: $GitRequestEvent"
Write-Host "GitHub action: $GitHubAction"
Write-Host "GitHub state: $GitHubState"
Write-Host "Default branch: $DefaultBranch"
Write-Host "PR number: $PrIssueNumber"
Write-Host "Issue URL: $IssueUrl"
Write-Host "PR URL: $PrGitHubLink"
Write-Host "User Permission URL: $UserPermissionUrl"
Write-Host "User Permission URL (PR Opener): $UserPermissionPROpenerUrl"
Write-Host "Commenter user name: $CommentUser"
Write-Host "Pull request opener name: $PullRequestOpener"
# Make the job summary section show up so the job always looks consistent.
echo "" >> $env:GITHUB_STEP_SUMMARY
#####################
#####################
# Initialize-Labels
Function Initialize-Labels
{
Write-Host "Initialize labels needed by the workflow"
Initialize-Label -LabelName $ContentApprovedLabel -LabelColor $ContentApprovedLabelColor -LabelDescription $ContentApprovedLabelDescription
Initialize-Label -LabelName $FilesBlockedForConfLabel -LabelColor $FilesBlockedForConfLabelColor -LabelDescription $FilesBlockedForConfLabelDescription
}
#####################
#####################
# Initialize-Label
Function Initialize-Label {
[CmdletBinding()]
param(
$LabelName,
$LabelColor,
$LabelDescription
)
# Check if label exists on the *REPO*.
$LabelExists = Test-RepoLabel -RepoUri $RepoLabelUrl -Name $LabelName
If (!$LabelExists)
{
# Create label on the *REPO* if it doesn't exist.
New-RepoLabel -RepoUri $RepoLabelUrl -Name $LabelName -Color $LabelColor -Description $LabelDescription
}
}
#####################
#####################
# Test-RepoLabel
Function Test-RepoLabel {
[CmdletBinding()]
param(
$Name,
$RepoUri
)
# Replace placeholder text in the URL retrieved from the GitHub API with the name of the label we're looking for
$LabelUri = $RepoUri.Replace("{/name}","/$Name")
# Check to see if the label we want exists in the repo
Try {
Write-Host "Checking to see if label $Name exists in repo URL $LabelUri."
$LabelResults = Invoke-WebRequest -UseBasicParsing -Uri $LabelUri -Headers $GitHubHeaders -ErrorAction Stop
$LabelFound = $True
} Catch {
# OK if label doesn't exist. Just means we need to create it.
$LabelFound = $False
}
# Return boolean to calling statement
$LabelFound
}
#####################
#####################
# New-RepoLabel
Function New-RepoLabel {
[CmdletBinding()]
param(
$Name,
$Color,
$Description,
$RepoUri
)
# Remove placeholder text from repo URL
$RepoUri = $RepoUri.Replace("{/name}","")
$Result = $Null
# Construct the JSON statement that will be sent to GitHub as the body of the web request. Include the name of the label, its color, and description.
# Convert hash table to JSON
$Body = @{}
$Body.Add("name", $Name)
$Body.Add("color", $Color)
$Body.Add("description", $description)
$Body = $Body | ConvertTo-Json
# Try to submit the request to GitHub API to create the label
Try {
Write-Host "Creating label $Name in repo $RepoUri."
$Result = Invoke-RestMethod -Uri $RepoUri -Headers $GitHubHeaders -Body $Body -Method POST
} Catch {
Write-Error "ERROR: Failed to create new label $Name on repo $RepoUri. Error: $($Error[0].Exception.Message)."
}
}
#####################
#####################
# Test-PrLabel
Function Test-PrLabel {
[CmdletBinding()]
param(
$LabelArray,
$IssueUrl
)
# Replace placeholder text in the URL retrieved from the GitHub API with the name of the label we're looking for
$IssueLabelUrl = "$IssueUrl/labels"
$LabelHashTable = @{}
$LabelResults = $Null
# Get list of labels on issue/PR
Try
{
$LabelResults = Invoke-RestMethod -Uri $IssueLabelUrl -Headers $GitHubHeaders -ErrorAction Stop
}
Catch
{
Write-Error "ERROR: Failed to get list of labels on $IssueLabelUrl. Error: $($Error[0].Exception.Message)."
}
ForEach ($Label in $LabelArray) {
If ($LabelResults -ne $Null) {
If ($LabelResults.name.Contains($Label)) {
$LabelHashTable.Add($Label, $True)
} Else {
$LabelHashTable.Add($Label, $False)
}
} Else {
$LabelHashTable.Add($Label, $False)
}
}
# Return array of labels on Issue/PR
Return $LabelHashTable
}
#####################
#####################
# Set-PrLabel
Function Set-PrLabel {
param(
$IssueUrl,
$LabelName
)
# Check to see if the label exists on the *PR*.
$LabelResultsArray = Test-PrLabel -LabelArray $LabelName -IssueUrl $IssueUrl
# Only add the label if it doesn't already exist on the *PR*
If (!$LabelResultsArray.$LabelName)
{
# Construct label URL based on issue or pull request URL
$IssueLabelUrl = "$IssueUrl/labels"
# Construct JSON statement that will be sent to GitHub as the body of the web request. Includes only the label name. GitHub expects an array even thought it's a single value
# Convert array to JSON
$Body = @()
$Body += $LabelName
$Body = ConvertTo-Json -InputObject $Body
# Try to submit the request to GitHub API to apply they label to the issue or pull request
Try
{
Write-Host "Setting label $LabelName on URL $IssueLabelUrl."
$Result = Invoke-RestMethod -Uri $IssueLabelUrl -Body $Body -Headers $GitHubHeaders -Method POST
}
Catch
{
Write-Error "ERROR: Failed to set label $LabelName on URL $IssueLabelUrl. Error: $($Error[0].Exception.Message)."
}
}
Else
{
Write-Host "Label $LabelName already exists on PR"
}
}
#####################
#####################
Function Remove-Label
{
param(
$IssueUrl,
$LabelName
)
Write-Host "Remove $LabelName"
# Check to see if the approved label exists on the *PR* and remove it
$LabelResultsArray = Test-PrLabel -LabelArray $LabelName -IssueUrl $IssueUrl
if ($LabelResultsArray.$LabelName)
{
Write-Host "Removing "$LabelName" label from PR."
$LabelUrlName = $LabelName.Replace(" ", "%20")
$LabelUrl = "$IssueUrl/labels/$LabelUrlName"
Write-Host "$LabelUrl"
$Result = Invoke-WebRequest -UseBasicParsing -Uri $LabelUrl -Headers $GitHubHeaders -Method Delete -ErrorAction Stop
Write-Host "Successfully removed $LabelName label."
}
}
#####################
#####################
# Test-BlockedFilesAndFolders
Function Test-BlockedFilesAndFolders
{
[CmdletBinding()]
Param(
$PrFileList,
$BlockedFilesAndFoldersList
)
$FoundFilesAndFolders = @()
Write-Host "Files in PR:"
Write-Host "-------------------"
ForEach ($PRFile in $PrFileList)
{
$File = $PRFile.filename
Write-Host "$File"
}
ForEach ($BlockedFile in $BlockedFilesAndFoldersList)
{
Write-Host "Checking for file: $BlockedFile"
$MatchedFile = $PrFileList | Where-Object { $_.filename -like "*$BlockedFile*" }
if ($MatchedFile)
{
$File = $MatchedFile.filename
Write-Host "Found blocked file = $File"
$FoundFilesAndFolders += $File
}
}
Return $FoundFilesAndFolders
}
#####################
#####################
# Set-PrComment
Function Set-PrComment
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
$Message
)
Write-Host "Adding comment in pr..."
$BodyHash = @{}
$BodyHash.body = $Message
$BodyJson = $BodyHash | ConvertTo-Json
Try
{
$Result = Invoke-WebRequest -UseBasicParsing -Uri $CommentsUrl -Body $BodyJson -Headers $GitHubHeaders -Method POST -ErrorAction Stop
$PostCommentSuccess = $True
}
Catch
{
$PostCommentSuccess = $False
Write-Host "ERROR: Failed to submit message to PR conversation. Error: $($error[0].Exception.Message)."
}
Return $PostCommentSuccess
}
#####################
#####################
# Workflow #
#####################
#####################
Try
{
# Get PR data so we can get the base branch of the PR. Doing this here so we don't need to do unnecessary calls if other criteria fail.
$PrData = Invoke-RestMethod -Method GET -Headers $GitHubHeaders -Uri $PrUrl
$TargetBranch = $PrData.base.ref
$StatusUrl = $PrData.statuses_url
$PrHtmlUrl = $PrData.html_url
Write-Host "PR status url: $StatusUrl"
# Only run checks if target is default branch. Otherwise let it pass.
# Check to see if this is a branch that is being monitored:
Write-Host "Target branch: $TargetBranch"
$TargetingMonitoredBranch = ($TargetBranch -eq $DefaultBranch)
Write-Host "Branch is targeted for monitoring: $TargetingMonitoredBranch"
# Check to see if this is an actionable comment
Write-Host "GitHub Request Event: $GitRequestEvent"
Write-Host "GitHub Action: $GitHubAction"
$SignOffFound = $false
$ApprovedFound = $false
$ActionableComment = $false
$PROpenEvent = $false
$EventIsCommentCreated = (($GitRequestEvent -eq "issue_comment") -and (($GitHubAction -eq "created")))
If ($EventIsCommentCreated)
{
Write-Host "Comment added on PR."
# Get the contents of the comment that was added to the PR
$CommentBody = $GitHubData.event.comment.body
Write-Host "Comment:"
Write-Host "--------------------------"
Write-Host "$CommentBody"
Write-Host "--------------------------"
# Check to see if comment includes #sign-off, or #approve-files
$SignOffFound = $CommentBody -match $SignOffRegex
$ApprovedFound = $CommentBody -match $ApprovedRegex
$ActionableComment = ($SignOffFound -or $ApprovedFound)
}
ElseIf (($GitRequestEvent -eq "pull_request_target") -and (($GitHubAction -eq "opened") -or ($GitHubAction -eq "reopened") -or ($GitHubAction -eq "synchronize")))
{
Write-Host "Non-comment GitHub Event"
Write-Host "Pull request action $GitHubAction. Setting sign off check to pending."
$PROpenEvent = $true
$Status.state = "pending"
$Status.description = "Evaluating whether signoff is allowed on files."
}
else
{
Write-Host "GitHub event not a PR comment"
}
if ($PROpenEvent)
{
# This runs when the PR is first opened
# Check to see if any of the files are under a content freeze and blocked from edits
# Get the list of files on the PR.
Write-Host "Checking for blocked files in PR"
$PrFiles = Invoke-RestMethod -Method GET -Headers $GitHubHeaders -Uri "$PrUrl/files"
# Check to see if any files are blocked.
# Return all of the blocked files in the PR
$BlockedFilesAndFoldersList = Test-BlockedFilesAndFolders -PrFileList $PrFiles -BlockedFilesAndFoldersList $BlockedFilesAndFolders
$BlockedFilesFound = $BlockedFilesAndFoldersList.Count -gt 0
if ($BlockedFilesFound)
{
Write-Host "Blocked files found!"
$Status.state = "pending"
$Status.description = $SignoffBlockedMessage
$PRContainsBlockedFilesMessage += "`n"
ForEach ($BlockedFile in $BlockedFilesAndFoldersList)
{
$PRContainsBlockedFilesMessage += "- $BlockedFile`n"
}
Set-PrLabel -IssueUrl $IssueUrl -LabelName $FilesBlockedForConfLabel
Set-PrComment $PRContainsBlockedFilesMessage
}
else
{
$Status.state = "success"
$Status.description = "No blocked files in PR."
}
$ActionableComment = $True
}
else
{
# This was a comment on the PR so look for action tags
Write-Host "Regex result found $SignOffString : $SignOffFound."
Write-Host "Regex result found $ApprovedString : $ApprovedFound."
# Check to see if any of the files are under a content freeze and blocked from edits
# Get the list of files on the PR.
Write-Host "Checking for blocked files in PR"
$PrFiles = Invoke-RestMethod -Method GET -Headers $GitHubHeaders -Uri "$PrUrl/files"
# Check to see if any files are blocked.
# Return all of the blocked files in the PR
$BlockedFilesAndFoldersList = Test-BlockedFilesAndFolders -PrFileList $PrFiles -BlockedFilesAndFoldersList $BlockedFilesAndFolders
$BlockedFilesFound = $BlockedFilesAndFoldersList.Count -gt 0
if ($BlockedFilesFound)
{
Write-Host "Blocked files found!"
}
# Check to see what labels are already on the PR
Initialize-Labels
$LabelApprovedArray = Test-PrLabel -LabelArray $ContentApprovedLabel -IssueUrl $IssueUrl
$ApprovedLabelSet = $LabelApprovedArray.$ContentApprovedLabel
# Workflow processing:
If (-not $TargetingMonitoredBranch)
{
Write-Host "Approving as $TargetBranch branch is not being monitored."
$ActionableComment = $True
$Status.state = "success"
$Status.description = "Target branch not $DefaultBranch, so approving by default."
}
ElseIf (-not $BlockedFilesFound)
{
Write-Host "Approving as no blocked files or folders found."
$ActionableComment = $True
$Status.state = "success"
$Status.description = "No blocked files or folders found. Allowing sign off."
}
ElseIf (-not $ActionableComment)
{
Write-Host "Comment not sign off or approval."
# Check to see if the content team has approved this yet
# If so, always return success. If not, always keep at pending until approved.
if ($ApprovedLabelSet)
{
$ActionableComment = $True
Write-Host "Content team already approved. Returning success."
$Status.state = "success"
$Status.description = $SignoffApprovalMessage
}
else
{
# The comment was not one to take action on
}
}
Else
{
# The branch is targeted, the files are blocked, and the comment contains an action
Initialize-Labels
Write-Host "String $SignOffString or $ApprovedString or found on PR/Issue #$PrIssueNumber."
# Get permission level of user who created the comment. Need to use .role_name instead of .permission because .permission provides only legacy values.
# .role_name provides legacy plus triage, maintain, and custom roles like write-elevated.
$UserPermission = $(Invoke-RestMethod -Method GET -Headers $GitHubHeaders -Uri $UserPermissionUrl).role_name
Write-Host "User $CommentUser permission level: $UserPermission."
# If user has write or above, allow check to pass. If not, add content team review label.
If (($UserPermission -like "write*") -or ($UserPermission -eq "maintain") -or ($UserPermission -eq "admin") -or ($UserPermission -eq "triage"))
{
Write-Host "User $CommentUser has the permission level: $UserPermission. Pass check."
If ($SignOffFound -or $ApprovedFound)
{
# Set the properties on the $Status object to allow the check to pass. $Status will be sent to GitHub at the end of the workflow.
$Status.state = "success"
$Status.description = $SignoffApprovalMessage
Write-Host "Adding $ContentApprovedLabel label to the PR"
Set-PrLabel -IssueUrl $IssueUrl -LabelName $ContentApprovedLabel
}
}
Else
{
# If the content team has already approved, report success.
# Technically, this means that once approved they could sneak other changes in, but maybe that is ok as we would see.
If ($ApprovedLabelSet)
{
Write-Host "Content team already approved. Returning success."
$Status.state = "success"
$Status.description = $SignoffApprovalMessage
}
Else
{
# Check failed. Add label.
Write-Host "One or more files are under content freeze and user $CommentUser has the permission level: $UserPermission. Fail check."
# Even though the check failed, leave the status as pending.
# It is always pending until the content team has approved.
$Status.state = "pending"
$Status.description = $SignoffBlockedMessage
$PRContainsBlockedFilesMessage += "`n"
ForEach ($BlockedFile in $BlockedFilesAndFoldersList)
{
$PRContainsBlockedFilesMessage += "- $BlockedFile`n"
}
Set-PrLabel -IssueUrl $IssueUrl -LabelName $FilesBlockedForConfLabel
Set-PrComment $PRContainsBlockedFilesMessage
$CheckFailed = $True
}
}
}
if ($Status.state -ne "success")
{
# If the state is anything other than success, attempt to remove the ready-to-merge label
# and add the do-not-merge label
Write-Host "Removing ready-to-merge label"
Remove-Label -LabelName "ready-to-merge" -IssueUrl $IssueUrl
Write-Host "Adding the do-not-merge label"
Set-PrLabel -LabelName "do-not-merge" -IssueUrl $IssueUrl
}
}
}
Catch
{
# Capture and display detailed exception information
Write-Host "An error occurred: $($_.Exception.Message)"
Write-Host "Exception type: $($_.Exception.GetType().FullName)"
Write-Host "Line number: $($_.InvocationInfo.ScriptLineNumber)"
Write-Host "Position: $($_.InvocationInfo.OffsetInLine)"
Write-Host "Script name: $($_.InvocationInfo.ScriptName)"
Write-Host "Error line: $($_.InvocationInfo.Line)"
# Read and display the line of code that caused the error
$scriptPath = $($_.InvocationInfo.ScriptName)
if ($scriptPath) {
$lines = Get-Content -Path $scriptPath
$errorLine = $_.InvocationInfo.ScriptLineNumber
Write-Host "Code at error line $errorLine : $($lines[$errorLine - 1])"
}
# For catastrophic failure, make sure to not block PRs
$Status.state = "success"
$Status.description = "Unexpected error in workflow. Allowing sign off."
$CatastrophicError = $True
}
# Get ready to send $Status to GitHub. First need to convert the $Status object to JSON. Then set a couple variables to prepare for an attempt loop
# to deal with transient communication failures that may prevent the workflow from setting the status.
$StatusJson = $Status | ConvertTo-Json
$SuccessfulPost = $False
$RetryCount = 0
Write-Host "Reporting Status:"
Write-Host "$StatusJson"
# Loop to try set the status on the PR. Keep trying until either the status is successfully posted or we run out of attempts.
# In the unlikely event of a water landing or failure of all six attempts, the status will not set and the user will need to
# submit a new sign off request.
if ($ActionableComment)
{
# Only post a status back if an actionable comment was provided.
# Otherwise leave as expected with no result.
# This keeps the previous status unless explicitly changed.
Do
{
Try
{
# Send POST request to GitHub
Invoke-RestMethod -Headers $GitHubHeaders -Uri $StatusUrl -Method POST -Body $StatusJson -ErrorAction Stop
$SuccessfulPost = $True
}
Catch
{
# If the request fails for any reason, retry it after a delay, up to six times.
$RetryCount++
Start-Sleep 1
}
} Until (($SuccessfulPost) -or ($RetryCount -gt 5))
}
# Print out current date and time
$currentDateTime = Get-Date
$currentDateTimeString = "{0:yyyy-MM-dd HH:mm:ss}" -f $currentDateTime
Write-Output "Run end: $currentDateTimeString"
If ($CheckFailed)
{
# Populates the job summary if a user doesn't have permissions to sign off.
echo "# Pull request validation error" >> $env:GITHUB_STEP_SUMMARY
echo "" >> $env:GITHUB_STEP_SUMMARY
echo "The user $CommentUser with permission level $UserPermission tried to sign off PR: $PrHtmlUrl but doesn't have the necessary permissions to do so. Please contact the content team to request sign off." >> $env:GITHUB_STEP_SUMMARY
}
ElseIf ($CatastrophicError)
{
# Makes sure the workflow looks failed so we can investigate the bug
echo "# Pull request validation error" >> $env:GITHUB_STEP_SUMMARY
echo "" >> $env:GITHUB_STEP_SUMMARY
echo "The user $CommentUser with permission level $UserPermission tried to sign off PR: $PrHtmlUrl. There was an unexpected error in the workflow, so sign-off was allowed to prevent blocking PRs. Please contact the developer of the workflow to investigate." >> $env:GITHUB_STEP_SUMMARY
# Force the workflow to fail so the validation failure can be tracked in Actions. In this workflow, this has no effect other than logging the failure.
Throw "User $CommentUser signed off but an unexpected error occurred. Please contact contact team to investigate the workflow. Sign off allowed to prevent blocking PRs."
}