Skip to content

Cut GitHub API dependency and fix silent-failure paths - #2

Merged
TecharyJames merged 1 commit into
BETAfrom
fix/api-rate-limit-and-failure-semantics
Sep 19, 2026
Merged

TecharyJames merged 1 commit into
BETAfrom
fix/api-rate-limit-and-failure-semantics

Conversation

@TecharyJames

@TecharyJames TecharyJames commented Sep 19, 2026 •

Copy link
Copy Markdown
Member

Why

Get-GitHubInstaller makes two unauthenticated api.github.com calls per install, against an allowance of 60 requests/hour per source IP. A customer site behind a single NAT egress gets ~30 installs an hour before every subsequent install fails, and it fails at the resolve step, so no package is produced at all. Measured while testing: resolving three packages took this IP from 59 to 53.

Separately, several failures reported as successes. The worst is Install-TecharyApp returning silently for an unknown Id — a caller driving this from Intune or an RMM cannot distinguish that from a successful install.

Reducing the API dependency

Layer Effect
Manifest cache in ProgramData\TecharyGet\ManifestCache Repeat and retry installs of the same app cost zero API calls
Stale-cache fallback on live failure A rate-limited or offline site keeps installing from last-known-good instead of hard-failing
Optional PAT (-GitHubToken / TECHARYGET_GITHUB_TOKEN) 60/hr → 5,000/hr, and a 403 now names the cause

The scraping logic moves to Private/Resolve-GitHubManifest.ps1 so resolution and download are separable and the cache has a seam to sit on. Behaviour and the returned object are unchanged.

The custom catalogue was never reachable

Get-CustomApp pointed at .../BETA/TecharyGet/Private/CustomApps.json. There is no TecharyGet/ directory in this repo, so that URL 404s on every call and the catalogue has never synced — it has been running off the module-local copy. Verified against live GitHub:

404  .../BETA/TecharyGet/Private/CustomApps.json   <- current
200  .../BETA/Private/CustomApps.json              <- fixed

The download is now staged and parsed before it replaces the cache, so a proxy or captive-portal page returning HTTP 200 with HTML cannot poison the cache for an hour.

Failures that reported as success

  • Install-TecharyApp returned silently on unknown Id → now throws.
  • Write-PackagerLog called EventLog::SourceExists unguarded. That throws SecurityException when the caller cannot read the event-log registry — the normal non-elevated case — turning every log call into a terminating error. Event logging is now best-effort; the file log is likewise non-fatal.
  • Uninstall-TecharyApp left $Arguments unset when an MSI uninstall string had no product code, reaching Start-Process as $null. Both variables are initialised and that case now reports the reason.
  • Uninstall-TecharyApp appended /S /silent /quiet /norestart to QuietUninstallString, which is silent by definition. Contradictory flags make some uninstallers fail or fall back to a UI prompt, which hangs indefinitely under SYSTEM. Those switches are now only appended to a plain UninstallString.

Version selection

Manifest versions were cast to [Version] with failures discarded, dropping real releases like 1.2.3-beta and 20240101, and throwing outright when no folder parsed. Replaced with a zero-padded sortable key over fixed-width components. This also fixes 1.2 sorting above 1.2.3, and prefers a stable release over a prerelease on a tie:

20240101 > 2.0 > 1.10.0 > 1.2.3.4.5 > 1.2.3 > 1.2.3-beta > 1.2 > 0.9

Module loading and manifest

The loader used $MyInvocation.MyCommand.Path, errored on a missing directory, and surfaced a failed dot-source as a later "command not found". It now uses $PSScriptRoot, skips absent directories, and fails immediately naming the file and the reason. FunctionsToExport advertised Show-IntunePackager, which has no implementation — removed. Version 2.4.

Verification

  • All files parse; Test-ModuleManifest passes; module imports and exports 8 commands
  • Live resolution succeeds for 7zip.7zip (26.03), Notepad++.Notepad++ (8.9.8), Google.Chrome (153.0.8010.53)
  • Version ordering asserted across mixed release, date and prerelease formats
  • Unknown-app throw confirmed
  • Catalogue URL fix confirmed against live GitHub (404 → 200, 3 entries parsed)

Related

Defects found in adjacent code during this work are handled separately: #4 (MSIX removal under SYSTEM), #5 (sideloading policy left enabled), #6 (detection precision).

Reduces the unauthenticated api.github.com dependency and fixes a set of
defects that made failures look like successes.

GitHub API rate limiting
Get-GitHubInstaller made two api.github.com calls per install with no
credentials. That allowance is 60 requests/hour per source IP, so a site
behind a single NAT egress gets roughly 30 installs an hour before every
subsequent install fails at the resolve step. Three layers now sit in
front of it:

  - resolved manifests are cached to ProgramData\TecharyGet\ManifestCache,
    so repeat and retry installs of the same app cost no API calls
  - on a live failure the cache is reused even when stale, so a rate
    limited or offline site keeps installing from last known good instead
    of hard failing
  - an optional PAT (-GitHubToken, or TECHARYGET_GITHUB_TOKEN) lifts the
    allowance to 5000/hour, and a rate limit response now says so

The scraping logic moves to Private/Resolve-GitHubManifest.ps1 so
resolution and download are separable and the cache has a seam to sit on.

Custom catalog was never reachable
Get-CustomApp pointed at .../BETA/TecharyGet/Private/CustomApps.json.
There is no TecharyGet directory in this repo, so that URL 404s on every
call and the catalog never synced. Corrected to the real path. The
download is also staged and parsed before it replaces the cache, so a
captive portal or proxy page returning HTTP 200 with HTML can no longer
poison the cache for an hour.

Failures that reported as success
  - Install-TecharyApp returned silently for an unknown Id. A caller
    driving this from Intune or an RMM cannot tell that from a successful
    install, so a mistyped Id was reported as success. It now throws.
  - Write-PackagerLog called EventLog::SourceExists unguarded. That throws
    SecurityException when the caller cannot read the event log registry,
    which is the normal non-elevated case, so every log call became a
    terminating error. Event logging is now best effort and the file log
    is likewise non-fatal.
  - Uninstall-TecharyApp left $Arguments unset when an MSI uninstall
    string contained no product code, reaching Start-Process as $null.
    Both variables are initialised and that case now reports the reason.
  - Uninstall-TecharyApp appended /S /silent /quiet /norestart to
    QuietUninstallString, which is silent by definition. Passing
    contradictory flags made some vendors' uninstallers fail or fall back
    to a UI prompt. The switches are only appended to a plain
    UninstallString now.

Version selection
Manifest versions were cast to [Version] and the failures discarded, which
silently dropped real releases such as 1.2.3-beta and 20240101, and threw
outright when no folder happened to parse. Replaced with a zero padded
sortable key over fixed width components, which also fixes 1.2 sorting
above 1.2.3, and prefers a stable release over a prerelease on a tie.

Module loading and manifest
The loader used $MyInvocation.MyCommand.Path, errored on a missing
directory and reported a failed dot-source as a later "command not found".
It now uses $PSScriptRoot, skips absent directories and fails immediately
naming the file and the reason. FunctionsToExport advertised
Show-IntunePackager, which has no implementation; removed. Version 2.4.

Verified: all files parse, Test-ModuleManifest passes, the module imports
and exports 8 commands, live resolution succeeds for 7zip.7zip,
Notepad++.Notepad++ and Google.Chrome, and version ordering is asserted
across mixed release, date and prerelease formats.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant