PSI WinGet Source
Private WinGet package source for distributing PSI internal developer tools. Built with .NET 8 Minimal APIs, deployed to Azure App Service.
Quick Start
The PSI WinGet source is deployed to all managed machines via Intune policy. If it’s not on your machine yet:
# Check if PSI source is registered
winget source list
# If not listed, an admin can register it:
winget source add --name PSI --arg https://packages.progressivesurface.com/api --type Microsoft.Rest --accept-source-agreementsInstall a package:
winget install PSI.CSMSearch for packages:
winget search --source PSI PSICheck for updates:
winget upgrade --source PSIAvailable Packages
| Package ID | Name | Description |
|---|---|---|
PSI.CSM | Claude Session Manager | TUI for browsing and managing Claude Code sessions |
Architecture
winget install PSI.CSM
│
│ HTTPS (private endpoint)
│
└──► ps-winget-source (.NET 8 Minimal API)
│
├── /api/information ← WinGet REST endpoints
├── /api/packages
├── /api/packageManifests/{id}
├── /api/manifestSearch
├── /api/download/{id} ← Proxied installer downloads
│
├── /api/admin/packages ← Admin API
│
├── SQLite (package metadata)
└── Azure Blob (psiwingetpkgs → installer binaries)
Components
| Component | Details |
|---|---|
| API | .NET 8 Minimal API (psi-winget-source) |
| App Service | ps-winget-source on asp-erp-migration-tool plan (PS-WEBAPPS) |
| Database | SQLite (EF Core), persistent on /home/winget.db |
| Installer Storage | psiwingetpkgs Azure Blob Storage, container installers (private) |
| Container Registry | psicontainers.azurecr.io |
| Domain | packages.progressivesurface.com (private endpoint: 10.160.140.17) |
| SSL | Wildcard cert *.progressivesurface.com |
| Entra App | PSI WinGet Source (b0b01ac6-8f1f-4b55-af50-c582da3dfd77) |
How Downloads Work
Installer binaries are stored in private Azure Blob Storage. The WinGet client never accesses blob storage directly. Instead:
- WinGet requests the package manifest from
/api/packageManifests/{id} - The manifest contains an installer URL pointing to
/api/download/{installerId} - WinGet downloads from that URL
- The API streams the file from blob storage to the client
This keeps blob storage completely private with no public access or SAS tokens.
Intune Deployment
The PSI WinGet source is deployed to all managed machines via Intune Settings Catalog (not scripts).
Settings Catalog Configuration
Profile: Devices → Configuration → Settings Catalog
| Setting | Value |
|---|---|
| Enable App Installer | Enabled |
| Enable App Installer Additional Sources | Enabled |
Additional Sources entry:
{"Name":"PSI","Arg":"https://packages.progressivesurface.com/api","Data":"","Explicit":false,"Identifier":"PSI","Type":"Microsoft.Rest","TrustLevel":["Trusted"]}This writes to HKLM\Software\Policies\Microsoft\Windows\AppInstaller via MDM/CSP. The source is available to all users on the machine and cannot be removed by users.
Do NOT use EnableAllowedSources unless you intend to restrict which sources users can add. It acts as a whitelist and will block sources not in the list.
CI/CD
Push to main in the psi-winget-source repo triggers automatic deployment:
- GitHub Actions runs on
ps-cicd-runner(self-hosted Linux) az acr build— remote Docker build in Azure Container Registry- App Service is configured to pull the new image from
psicontainers.azurecr.io - App Service restarts with the new container
No Docker required on the CI runner — ACR builds remotely.
Admin API
Manage packages via REST API. Swagger UI available at the root URL (packages.progressivesurface.com).
Create a Package
curl -X POST https://packages.progressivesurface.com/api/admin/packages \
-H "Content-Type: application/json" \
-d '{"Identifier":"PSI.MyTool","Name":"My Tool","Publisher":"Progressive Surface Inc"}'Add a Version
curl -X POST https://packages.progressivesurface.com/api/admin/packages/PSI.MyTool/versions \
-H "Content-Type: application/json" \
-d '{
"Version":"1.0.0",
"ShortDescription":"What this tool does",
"InstallerUrl":"https://psiwingetpkgs.blob.core.windows.net/installers/PSI.MyTool/1.0.0/mytool-1.0.0-win-x64.zip",
"InstallerSha256":"<sha256>",
"Architecture":"x64",
"InstallerType":"zip",
"NestedInstallerType":"portable",
"PortableCommandAlias":"mytool"
}'List All Packages
curl https://packages.progressivesurface.com/api/admin/packagesDelete a Version
curl -X DELETE https://packages.progressivesurface.com/api/admin/packages/PSI.MyTool/versions/1.0.0Publishing a New Package
1. Build the installer
Python tools:
pip install pyinstaller
pyinstaller --onefile --name mytool mytool.py
# Creates dist/mytool.exe.NET tools:
dotnet publish -c Release -r win-x64 --self-contained2. Create a zip and compute SHA256
Compress-Archive -Path dist/mytool.exe -DestinationPath mytool-1.0.0-win-x64.zip
(Get-FileHash mytool-1.0.0-win-x64.zip -Algorithm SHA256).Hash3. Upload to blob storage
az storage blob upload --account-name psiwingetpkgs --container-name installers \
--file mytool-1.0.0-win-x64.zip \
--name "PSI.MyTool/1.0.0/mytool-1.0.0-win-x64.zip" \
--auth-mode key4. Register via admin API
Use the admin API calls above to create the package and add the version with the blob URL and SHA256.
5. Test
winget search --source PSI mytool
winget install PSI.MyTool --source PSIProject Structure
psi-winget-source/
├── src/PSI.WinGet.API/
│ ├── Program.cs # Startup, middleware chain
│ ├── Endpoints/
│ │ ├── WinGetEndpoints.cs # 4 WinGet REST API endpoints + download proxy
│ │ ├── AdminEndpoints.cs # Package management CRUD
│ │ └── HealthEndpoints.cs # /api/health
│ ├── Services/
│ │ ├── PackageService.cs # CRUD + search + manifest generation
│ │ └── BlobStorageService.cs # Azure Blob download proxy
│ ├── Models/ # DB entities + WinGet response DTOs
│ └── Data/
│ └── WinGetDbContext.cs # EF Core + SQLite
├── Dockerfile # Multi-stage .NET 8 build
├── scripts/intune/ # Intune detection/remediation scripts (backup)
└── .github/workflows/deploy.yml
Troubleshooting
| Issue | Solution |
|---|---|
| ”No sources match the given value: PSI” | Source not registered. Check winget source list. Register via Intune policy or manually with winget source add. |
| ”The configured rest source is not supported” | API may be down or returning wrong format. Check curl https://packages.progressivesurface.com/api/information. |
| Download fails (409 Conflict) | Package may already be installed. Run winget uninstall PSI.CSM first. |
| Download fails (other) | Check that the installer blob exists and the download proxy is working: curl https://packages.progressivesurface.com/api/download/1. |
| Package not found after publishing | The SQLite DB resets on container restart. Re-seed via admin API. (Future: persistent volume or external DB.) |
| Source not appearing after Intune push | Run winget source list — check “Group Policy” section in winget --info. May need a device sync in Intune. |
Related Pages
- azure-resources — Azure resource map (App Service, storage, ACR, Entra app)
- deploy-to-azure — PSI web app deployment patterns
- dev-setup — Developer machine setup
- index — Application inventory