# Authenticate via `gh auth login -s admin:enterprise` before running this script
# Save this script as c:\\temp\GHWebookAudit.ps1; then navigate to c:\\temp\ and invoke via `.\GHWebookAudit.ps1 -Orgs @("MyExampleOrg", "MyOtherExampleOrg") -InformationAction Continue`
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
[string[]]$Orgs
)
$results = foreach ($org in $Orgs) {
Write-Information "Fetching ALL repositories for org [$org]..."
# gh api --paginate fetches every page and outputs a stream of JSON arrays
# We use -Raw to ensure we capture the full stream before converting
$reposJson = gh api --paginate "orgs/$org/repos?per_page=1000"
# ConvertFrom-Json can handle multiple JSON arrays in the stream
$repos = $reposJson | ConvertFrom-Json
# Sometimes the pagination returns a single array or a list of arrays;
# Ensure we are iterating over the objects themselves.
$repoList = if ($repos.GetType().IsArray -and $repos[0].GetType().IsArray) {
$repos | ForEach-Object { $_ }
} else {
$repos
}
Write-Information "Found $($repoList.Count) repositories. Starting audit..."
foreach ($repoObj in $repoList) {
$repoName = $repoObj.name
$fullRepo = "$org/$repoName"
Write-Information "Checking: $fullRepo"
# Get all hooks for the repository
$hooksJson = gh api "repos/$fullRepo/hooks" 2>$null
if (-not $hooksJson) { continue }
$hooks = $hooksJson | ConvertFrom-Json
foreach ($hook in $hooks) {
$hookId = $hook.id
$hookUrl = $hook.config.url
# Get the most recent delivery... we could pontentially check for activity between the impacted dates, but that would need additional filters/commplexity; for now keeping it relatively simple
# correction: this only goes back 3 days; so doesn't work... https://docs.github.com/en/webhooks/testing-and-troubleshooting-webhooks/viewing-webhook-deliveries
# # $deliveriesJson = gh api "repos/$fullRepo/hooks/$hookId/deliveries" --limit 1 2>$null
# instead use th eexisting hook response's last response status just to say if it has run; though that doesn't say when
# Extract last response details
$lastStatus = $hook.last_response.status
$lastCode = $hook.last_response.code
$hasEverRun = ($lastStatus -eq 'active') # active vs unused
[PSCustomObject]@{
Organization = $org
Repository = $repoName
HookID = $hookId
Active = $hook.active
URL = $hookUrl
HasEverRun = $hasEverRun
LastStatus = $lastStatus
LastHTTPCode = $lastCode
UpdatedAt = $hook.updated_at
}
}
}
}
# Final Output
if ($results.Count) {
$results | Export-Csv -Path "./Full_Webhook_Audit_Report.csv" -NoTypeInformation
Write-Information "Audit complete! $($results.Count) hooks found. Results saved to Full_Webhook_Audit_Report.csv"
} else {
Write-Information "No webhooks found across the organizations."
}