Skip to content

Update PSGallery Stats #71

Update PSGallery Stats

Update PSGallery Stats #71

name: Update PSGallery Stats
# Runs daily at 06:00 IST (00:30 UTC) — away from the midnight deploy window.
# Also triggerable manually from the Actions tab.
# On commit, deploy.yml auto-triggers (push to main) → site rebuilds with fresh stats.
on:
schedule:
- cron: "30 0 * * *"
workflow_dispatch:
jobs:
update-stats:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch PSGallery stats and update data/psgallery.yaml
run: |
python3 << 'PYEOF'
import urllib.request
import xml.etree.ElementTree as ET
import datetime
import textwrap
MODULES = [
{
"name": "VB.WorkstationReport",
"description": "End-to-end workstation auditing — user profiles, printer mappings, folder redirections, OneDrive backup status, and network interfaces.",
},
{
"name": "VB.NextCloud",
"description": "WebDAV utilities for Nextcloud from PowerShell — single-file upload, batch orchestration with auto folder creation, and directory listing.",
},
{
"name": "VB.ServerInventory",
"description": "34 cmdlets across 10 categories for comprehensive Windows Server auditing — AD health, GPO, security, DHCP/DNS, RDS, and more.",
},
{
"name": "VB.AdminTools",
"description": "See PSGallery for details.",
},
]
ns = {
"atom": "http://www.w3.org/2005/Atom",
"d": "http://schemas.microsoft.com/ado/2007/08/dataservices",
"m": "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata",
}
total_downloads = 0
module_lines = []
for mod in MODULES:
name = mod["name"]
desc = mod["description"]
url = f"https://www.powershellgallery.com/packages/{name}"
api_url = (
f"https://www.powershellgallery.com/api/v2/FindPackagesById()"
f"?id='{name}'&$orderby=Version%20desc&$top=1&$select=Id,Version,DownloadCount"
)
version = ""
downloads = 0
try:
req = urllib.request.Request(api_url, headers={"Accept": "application/atom+xml"})
with urllib.request.urlopen(req, timeout=30) as resp:
xml_data = resp.read().decode("utf-8")
root = ET.fromstring(xml_data)
entry = root.find("atom:entry", ns)
if entry is not None:
props = entry.find("m:properties", ns)
version = props.findtext("d:Version", default="", namespaces=ns).strip()
dl_raw = props.findtext("d:DownloadCount", default="0", namespaces=ns).strip()
downloads = int(dl_raw) if dl_raw.isdigit() else 0
print(f" {name}: v{version}, {downloads} downloads")
except Exception as e:
print(f" WARNING: Failed to fetch {name} — {e}")
total_downloads += downloads
module_lines.append(f"- name: {name}")
module_lines.append(f" url: {url}")
module_lines.append(f' description: "{desc}"')
module_lines.append(f" downloads: {downloads}")
module_lines.append(f' version: "{version}"')
module_lines.append("")
today = datetime.date.today().isoformat()
modules_block = "\n".join(module_lines)
yaml_content = (
"# PSGallery module stats — auto-updated daily by GitHub Actions"
" (.github/workflows/psgallery-stats.yml)\n"
"# Do not edit manually — changes will be overwritten on next scheduled run.\n"
f"# Last updated: {today}\n"
"\n"
"modules:\n"
f"{modules_block}\n"
f"total_downloads: {total_downloads}\n"
f"total_modules: {len(MODULES)}\n"
f'last_updated: "{today}"\n'
)
with open("data/psgallery.yaml", "w", encoding="utf-8") as f:
f.write(yaml_content)
print(f"Done — {len(MODULES)} modules, {total_downloads} total downloads.")
PYEOF
- name: Commit updated stats (triggers site rebuild via deploy.yml)
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add data/psgallery.yaml
git diff --staged --quiet \
&& echo "No changes — stats unchanged." \
|| (git commit -m "chore: update PSGallery stats [$(date -u +%Y-%m-%d)]" && git push)