A test harness for validating Datadog SSI (Single Step Installation) auto-instrumentation on Windows. It provisions multiple named .NET Windows Services — each a lightweight HTTP reverse proxy — giving you a realistic set of live .NET processes to target with Workload Selection rules.
A single compiled binary (WindowsProxyService.exe) is deployed as multiple Windows Service instances, each forwarding traffic to a different upstream URL and listening on its own port. A web dashboard and system tray app are included to make it easy to start/stop services, fire test requests, and inspect responses without touching the terminal.
| Component | Type | Port | Purpose |
|---|---|---|---|
WindowsProxyService |
Windows Service (×3) | 5052–5054 | YARP reverse proxies — one per upstream API |
WindowsSqlService |
Windows Service | 5055 | ASP.NET Core API over SQL Server Express — Products / Orders / Customers schema |
WindowsDashboardService |
Windows Service | 5051 | Bootstrap 5 web UI — status, test requests, start/stop |
WindowsTrayApp |
Desktop App | — | System tray icon — opens dashboard, start/stop shortcuts |
src/
WindowsProxyService/ Core proxy service (one binary, multiple instances)
WindowsSqlService/ SQL Server API service for Datadog DBM demos
WindowsDashboardService/ Web dashboard + REST control API
wwwroot/index.html Bootstrap 5 dark-theme dashboard UI
WindowsTrayApp/ WinForms system tray application
installer/
Product.wxs WiX v4 MSI package definition
Services.wxs Windows service + shortcut registrations
License.rtf Installer license/disclaimer
rules.toml Datadog Workload Selection rules (compile before use)
Download the latest WindowsProxyServices-<version>.msi from the Releases page and run it. The installer:
- Copies all binaries to
C:\Services\WindowsProxyService\(configurable in the GUI) - Registers and auto-starts all proxy services, the SQL service, and the dashboard service
- Creates a Start Menu shortcut for the tray app
- Adds the tray app to the All Users Startup folder so it launches automatically for every user who logs in
- Offers a "Launch tray app" checkbox on the final screen (checked by default) to start it immediately after install
Silent install:
msiexec /i WindowsProxyServices-1.0.0.msi /quiet
# Custom path:
msiexec /i WindowsProxyServices-1.0.0.msi /quiet INSTALLFOLDER="D:\Custom\"
# Suppress tray app launch (recommended for automated/unattended installs):
msiexec /i WindowsProxyServices-1.0.0.msi /quiet WIXUI_EXITDIALOGOPTIONALCHECKBOX=0Uninstall:
msiexec /x WindowsProxyServices-1.0.0.msi /quiet
# or: Apps & Features → Windows Proxy Services → UninstallSuppress the SmartScreen "Unknown Publisher" warning (one-time per server):
Each release includes WindowsProxyServices.cer alongside the MSI. Import it once as Administrator and Windows will recognise the publisher on all future installs:
Import-Certificate -FilePath WindowsProxyServices.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisherSee Local Development below.
- .NET 8 SDK
- Visual Studio 2022 (any edition) or VS Code with the C# extension
- Windows (required — services and tray app are Windows-only)
The proxy service supports starting one or multiple instances in a single process:
# Terminal 1 — dashboard (http://localhost:5051)
dotnet run --project src/WindowsDashboardService
# Terminal 2 — all three proxy instances in one process (OpenMeteo, JsonPlaceholder, DatadogDemo)
dotnet run --project src/WindowsProxyService -- --all
# Terminal 3 — SQL service (http://localhost:5055)
dotnet run --project src/WindowsSqlService
# Optional — tray app (separate window, needs dashboard running)
dotnet run --project src/WindowsTrayAppOr start a subset of services:
dotnet run --project src/WindowsProxyService -- --name OpenMeteo JsonPlaceholderOpen http://localhost:5051 to access the dashboard.
Tip: When running in console mode the proxies log structured JSON to stdout. The dashboard uses HTTP port probing as a fallback when Windows services aren't registered, so status dots still reflect whether each proxy is actually up.
--name argument forms:
| Form | Behaviour |
|---|---|
--name OpenMeteo |
Start one instance |
--name OpenMeteo JsonPlaceholder |
Start two instances in one process |
--name OpenMeteo --name JsonPlaceholder |
Same, flags repeated |
--name * or --all |
Start all HTTP proxy instances (non-proxy entries such as SqlService are skipped) |
Quickest setup — all services in three processes:
- Open
WindowsProxyServices.sln. - Right-click
WindowsProxyService→ Properties → Debug → General → Open debug launch profiles UI. - Set Command line arguments to
--all. - Right-click the Solution → Properties → Common Properties → Startup Project → Multiple Startup Projects.
- Set
WindowsDashboardService,WindowsProxyService, andWindowsSqlServiceto Start. - Press F5 — the dashboard starts on port 5051, all three proxies start on ports 5052–5054, and the SQL service starts on port 5055.
Start a single proxy instance (e.g. for focused debugging):
Set the command line arguments to --name OpenMeteo (or whichever service you want) instead of --all. To run additional instances alongside it, open terminals and dotnet run with the remaining names.
Run the tray app:
Add WindowsTrayApp to the Multiple Startup Projects list, or start it separately from a terminal.
The Windows Dashboard Service runs at http://localhost:5051 and provides:
- Live status cards for each proxy service, auto-refreshing every 5 seconds
- Preset endpoint selector per card — choose from known endpoints for each service, or enter a custom path; select GET or POST (POST surfaces a body textarea)
- Editable path input per card — pre-filled with the first preset; press Enter or click Send to fire the request and see the formatted JSON response inline
- Custom Request card — enter any full URL, choose GET or POST (with optional JSON body), and fire the request directly through the dashboard service
- Start / Stop buttons — controls each Windows service directly (the dashboard runs as LocalSystem so no elevation prompt is needed)
The dashboard is itself a Windows service (WindowsDashboardService) so it starts automatically with Windows after an MSI install.
REST API (used by the dashboard UI and tray app):
| Method | Path | Description |
|---|---|---|
GET |
/api/services |
List all services with status, port, upstream URL |
POST |
/api/services/{name}/test |
Fire a request through the named proxy; body: {"path":"/custom","method":"GET|POST","body":"…"} |
POST |
/api/services/{name}/start |
Start the named Windows service |
POST |
/api/services/{name}/stop |
Stop the named Windows service |
POST |
/api/custom-request |
Fire a GET or POST to any URL; body: {"url":"…","method":"GET|POST","body":"…"} |
Example:
# List all services and their status
Invoke-RestMethod http://localhost:5051/api/services
# Test the DatadogDemo proxy (health check)
Invoke-RestMethod -Method Post http://localhost:5051/api/services/DatadogDemo/test
# Stop the JsonPlaceholder proxy
Invoke-RestMethod -Method Post http://localhost:5051/api/services/JsonPlaceholder/stopWindowsTrayApp.exe sits in the system tray and gives quick access to everything without opening a browser or terminal.
- Double-click the icon → opens the dashboard in your default browser
- Right-click → context menu:
- Open Dashboard — opens http://localhost:5051
- Start All / Stop All — controls all proxy services at once (admin only)
- Per-service Start / Stop sub-menus (status dot
●/○updates when you open the menu) (admin only) - Exit
Start/Stop controls are disabled for non-admin users. Any user can open the dashboard and view service status; only administrators can start or stop services. The tray app delegates start/stop to the dashboard API, so it needs WindowsDashboardService to be running. If the dashboard is unreachable it shows a warning message.
After an MSI install the tray app launches automatically for all users at login via the All Users Startup folder. It can also be launched manually from Start Menu → Windows Proxy Services Tray or directly from C:\Services\WindowsProxyService\WindowsTrayApp.exe.
Defines all proxy instances. Located at src/WindowsProxyService/services.json (copied alongside the exe at publish time).
[
{
"InstanceName": "OpenMeteo",
"ServiceDescription": "Proxies requests to the Open-Meteo weather forecast API (no auth required)",
"Host": "+",
"Port": 5052,
"ProxyUrl": "https://api.open-meteo.com"
},
{
"InstanceName": "JsonPlaceholder",
"ServiceDescription": "Proxies requests to JSONPlaceholder — fake REST API for testing (no auth required)",
"Host": "+",
"Port": 5053,
"ProxyUrl": "https://jsonplaceholder.typicode.com"
},
{
"InstanceName": "DatadogDemo",
"ServiceDescription": "Proxies requests to the Datadog sanity demo app — health checks, slow queries, distributed traces, and error simulation",
"Host": "+",
"Port": 5054,
"ProxyUrl": "https://datadog-sanity-vercel-demo-app.vercel.app"
},
{
"InstanceName": "SqlService",
"ServiceDescription": "Exposes a WpsDemo SQL Server database (Products / Orders / Customers) for Datadog DBM and SQL tracing demos",
"Host": "+",
"Port": 5055,
"ProxyUrl": "sql://localhost\\SQLEXPRESS/WpsDemo",
"ServiceName": "WindowsSqlService"
}
]| Field | Description |
|---|---|
InstanceName |
Matches the --name value. Windows Service name is WindowsProxyService.<InstanceName>. Pass multiple names or --all to start more than one instance per process. |
Host |
+ listens on all interfaces (Kestrel wildcard). |
Port |
Port this instance binds to. |
ProxyUrl |
Upstream base URL. All incoming paths and query strings are forwarded. |
ServiceName |
(optional) Override the Windows Service name used by the dashboard for start/stop. Defaults to WindowsProxyService.<InstanceName>. |
This project exists to test Datadog's host-level SSI auto-instrumentation for .NET on Windows. With SSI enabled, the Datadog tracer automatically attaches to .NET processes on startup. Workload Selection rules let you control exactly which processes are instrumented.
- Install the Datadog Agent with SSI enabled:
$p = Start-Process -Wait -PassThru msiexec -ArgumentList '/qn /i "https://windows-agent.datadoghq.com/datadog-agent-7-latest.amd64.msi" /log C:\Windows\SystemTemp\install-datadog.log APIKEY="<YOUR_API_KEY>" SITE="datadoghq.com" DD_APM_INSTRUMENTATION_ENABLED="host" DD_APM_INSTRUMENTATION_LIBRARIES="dotnet:3"'- Install the rule compiler:
Invoke-WebRequest -Uri "https://github.com/DataDog/dd-policy-engine/releases/download/v0.1.1/dd-rules-converter-win-x64.zip" -OutFile "dd-rules-converter.zip"
Expand-Archive -Path "dd-rules-converter.zip" -DestinationPath "C:\tools"Edit rules.toml in the repo root, then compile it:
C:\tools\dd-rules-converter.exe -rules rules.toml -output "C:\ProgramData\Datadog\managed\rc-orgwide-wls-policy.bin"The tracer loads the compiled policy automatically the next time a .NET process starts — no agent restart needed. Restart the proxy services to pick up new rules:
Get-Service WindowsProxyService.* | Restart-ServiceAll proxy instances share one binary, so process.executable matches all of them at once. Use dotnet.dll or a naming convention for per-instance targeting.
| Selector | Value | Matches |
|---|---|---|
process.executable |
WindowsProxyService.exe |
All proxy instances |
process.executable |
*ProxyService.exe |
All proxy instances (wildcard) |
dotnet.dll |
WindowsProxyService.dll |
All proxy instances (by DLL) |
process.executable |
WindowsSqlService.exe |
SQL service only |
process.executable |
WindowsDashboardService.exe |
Dashboard service only |
[instrument-all-proxy-instances]
description = "Instrument all WindowsProxyService instances"
instrument = true
expression = "process.executable:WindowsProxyService.exe runtime.language:dotnet"
[instrument-by-name-suffix]
description = "Instrument any .NET process whose executable ends with ProxyService.exe"
instrument = true
expression = "process.executable:*ProxyService.exe runtime.language:dotnet"Enable debug logging to inspect rule evaluation:
$env:DD_TRACE_DEBUG = "true"
$env:DD_TRACE_LOG_DIRECTORY = "C:\logs\datadog"Confirm the compiled policy file exists:
C:\ProgramData\Datadog\managed\rc-orgwide-wls-policy.bin
Open http://localhost:5051 — click Test on any card to fire a request through that proxy and see the live response. Status badges refresh automatically every 5 seconds.
Status endpoint (all services):
Invoke-RestMethod http://localhost:5052/api/status # OpenMeteo proxy
Invoke-RestMethod http://localhost:5053/api/status # JsonPlaceholder proxy
Invoke-RestMethod http://localhost:5054/api/status # DatadogDemo proxy
Invoke-RestMethod http://localhost:5055/api/status # SqlService (database + product count)WindowsProxyService.OpenMeteo — port 5052
# Current weather for Dallas, TX
Invoke-RestMethod "http://localhost:5052/v1/forecast?latitude=32.78&longitude=-96.80¤t_weather=true"
# Hourly temperature for the next 3 days
Invoke-RestMethod "http://localhost:5052/v1/forecast?latitude=32.78&longitude=-96.80&hourly=temperature_2m,windspeed_10m&forecast_days=3"WindowsProxyService.JsonPlaceholder — port 5053
Invoke-RestMethod "http://localhost:5053/posts/1"
Invoke-RestMethod "http://localhost:5053/users"
# Simulated POST (returns the created resource)
Invoke-RestMethod "http://localhost:5053/posts" -Method Post `
-ContentType "application/json" `
-Body '{"title":"Test","body":"Hello proxy","userId":1}'WindowsProxyService.DatadogDemo — port 5054
# Health check
Invoke-RestMethod "http://localhost:5054/api/lab/health"
# Slow query simulation (3s delay — useful for latency traces)
Invoke-RestMethod "http://localhost:5054/api/lab/slow-query?delay=3000"
# Distributed trace chain
Invoke-RestMethod "http://localhost:5054/api/lab/chain"
# Handled error
Invoke-RestMethod "http://localhost:5054/api/lab/handled-error"WindowsSqlService — port 5055
# Database status and product count
Invoke-RestMethod "http://localhost:5055/api/status"
# List all products
Invoke-RestMethod "http://localhost:5055/api/products"
# List all orders (joined with customer and product names)
Invoke-RestMethod "http://localhost:5055/api/orders"
# Order summary by status (stored procedure)
Invoke-RestMethod "http://localhost:5055/api/reports/summary"
# Customer order history (stored procedure)
Invoke-RestMethod "http://localhost:5055/api/customers/1/orders"
# Create an order
Invoke-RestMethod "http://localhost:5055/api/orders" -Method Post `
-ContentType "application/json" `
-Body '{"customerId":1,"productId":1,"quantity":2}'The dashboard can optionally load the Datadog Browser SDK to send Real User Monitoring data. RUM is disabled by default — the page only initialises the SDK when applicationId is non-empty in wwwroot/rum-config.json.
1 — Edit rum-config.json directly (dev / post-install)
Edit C:\Services\WindowsProxyService\wwwroot\rum-config.json and fill in the required fields. The dashboard picks up the new values on its next startup (restart WindowsDashboardService).
{
"applicationId": "aea2638b-...",
"clientToken": "pub35af...",
"site": "us3.datadoghq.com",
"service": "windows-proxy-services",
"env": "prod",
"version": "1.2.3",
"sessionSampleRate": 100,
"sessionReplaySampleRate": 20,
"allowedTracingUrls": ["http://localhost"]
}2 — MSI installer dialog (GUI install)
A dedicated dialog page is shown between the install directory and the ready-to-install screen. Enter App ID, Client Token, select your site from the dropdown (all six Datadog regions), and set an environment name. Leave all fields blank to keep RUM disabled.
Values are written to HKLM\SOFTWARE\WindowsProxyServices\RUM. On first start, WindowsDashboardService reads the registry and regenerates rum-config.json automatically.
3 — Silent / automated MSI install
Pass the values on the msiexec command line:
msiexec /i WindowsProxyServices.msi /quiet `
DD_RUM_APP_ID="aea2638b-..." `
DD_RUM_CLIENT_TOKEN="pub35af..." `
DD_RUM_SITE="us3.datadoghq.com" `
DD_RUM_ENV="prod"4 — GitHub Actions / CI silent install
To exercise the RUM code path in the smoke-test job, set the following in your repo/org settings. The release workflow passes them as msiexec properties during the silent install, following the exact same registry path as an interactive GUI install:
| Name | Type | Description |
|---|---|---|
DD_RUM_APP_ID |
Secret | RUM application ID |
DD_RUM_CLIENT_TOKEN |
Secret | RUM client token |
DD_RUM_SITE |
Variable | Datadog site (e.g. us3.datadoghq.com) — defaults to datadoghq.com |
DD_RUM_ENV |
Variable | Environment name — defaults to ci |
If DD_RUM_APP_ID is not set, the installer runs without RUM and the window.DD_RUM check in the Playwright smoke test is skipped rather than failed.
Note: RUM values are never baked into the MSI at build time. The shipped MSI always has RUM disabled by default; values only reach the dashboard at runtime via the registry path populated by the installer.
| Dropdown label | site value |
|---|---|
| US1 | datadoghq.com |
| US3 | us3.datadoghq.com |
| US5 | us5.datadoghq.com |
| EU1 | datadoghq.eu |
| AP1 | ap1.datadoghq.com |
| US1-FED | ddog-gov.com |
The release workflow (.github/workflows/release.yml) runs automatically on a semver tag push and can also be triggered manually from the Actions tab to produce a test MSI without creating a release.
Job progression:
version → build → smoke-test → release (tag push only)
| Job | Runner | What it does |
|---|---|---|
version |
ubuntu | Resolves semver and MSI version strings; exports them as job outputs |
build |
windows | dotnet publish all four projects, generates Files.wxs, builds the MSI, uploads it as a workflow artifact |
smoke-test |
windows | Installs the MSI silently (optionally passing DD_RUM_* secrets as msiexec properties), polls all services until Running, probes each HTTP endpoint, runs a headless Playwright browser test against the dashboard, then uninstalls. Uploads installer logs on failure. |
release |
ubuntu | Downloads the artifact and publishes a GitHub Release with auto-generated release notes |
The Playwright browser test navigates to http://localhost:5051, asserts service cards are visible, validates rum-config.json is valid JSON, and — when DD_RUM_APP_ID is set — asserts window.DD_RUM was initialised. The RUM assertion is silently skipped when credentials are not configured.
The release workflow signs the MSI with a self-signed certificate when two secrets are configured. The MSI is unsigned (but otherwise identical) without them.
| Secret | Description |
|---|---|
SIGNING_CERT_PFX |
Base64-encoded PFX containing the private + public key pair |
SIGNING_CERT_PASSWORD |
Password used when the PFX was exported |
Generate the certificate once on any Windows machine (10-year validity):
$pfxPath = "$env:TEMP\signing.pfx"
$cert = New-SelfSignedCertificate `
-Type CodeSigning `
-Subject "CN=Windows Proxy Services" `
-CertStoreLocation Cert:\CurrentUser\My `
-NotAfter (Get-Date).AddYears(10) `
-HashAlgorithm SHA256
$pwd = ConvertTo-SecureString "your-strong-password" -AsPlainText -Force
Export-PfxCertificate -Cert $cert -FilePath $pfxPath -Password $pwd
# Encode as base64 and paste into the SIGNING_CERT_PFX secret
[Convert]::ToBase64String([IO.File]::ReadAllBytes($pfxPath)) | clip
Remove-Item $pfxPath # never commit the private keyAdd SIGNING_CERT_PFX (base64 output) and SIGNING_CERT_PASSWORD as repo or org secrets. The CI will then sign every MSI and export WindowsProxyServices.cer as a release asset. The same certificate is reused for all releases, so servers only need to import it once.
Create a release:
git tag v1.2.3 && git push --tagsTrigger a dev build (no release):
Go to Actions → Release → Run workflow and optionally enter a version string. Leave it blank to get 0.0.0-dev.<run_number>. The built MSI is available as a workflow artifact for 14 days.
WindowsSqlService is a standalone ASP.NET Core Windows service that connects to a local SQL Server Express instance and exposes a small e-commerce demo database (Products / Orders / Customers). It is designed to be instrumented by the Datadog .NET tracer, enabling you to validate:
- SQL Server integration — host-level metrics visible in the Datadog infrastructure list
- Query-level metrics — individual query performance, execution plans, and wait stats via DBM
- Trace-to-query correlation — APM traces linked directly to the SQL queries that ran during a given request
The MSI installer checks for an existing SQL Server instance and downloads SQL Server Express if none is found. To install it manually:
# Download and run the SQL Server Express installer (shows its own progress window)
$url = "https://go.microsoft.com/fwlink/p/?LinkID=2216019&clcid=0x409&culture=en-us&country=US"
$dest = "$env:TEMP\SQLEXPR_x64.exe"
Invoke-WebRequest -Uri $url -OutFile $dest
Start-Process $dest -ArgumentList "/QS /ACTION=Install /FEATURES=SQLEngine /INSTANCENAME=SQLEXPRESS /SQLSVCACCOUNT=""NT AUTHORITY\SYSTEM"" /SQLSYSADMINACCOUNTS=""BUILTIN\Administrators"" /AGTSVCSTARTUPTYPE=Disabled /BROWSERSVCSTARTUPTYPE=Automatic /IACCEPTSQLSERVERLICENSETERMS" -WaitThe service connects to localhost\SQLEXPRESS with Windows Authentication. The connection string can be overridden via the WPS_SQL_CONNECTION_STRING environment variable.
After installing the Datadog Agent, create a SQL Server check configuration file:
# C:\ProgramData\Datadog\conf.d\sqlserver.d\conf.yaml
init_config:
instances:
- host: localhost,1433
connector: adodbapi
adoprovider: MSOLEDBSQL
username: datadog
password: "<DD_AGENT_PASSWORD>"
dbm: true
tags:
- env:windows-demoFor trace-to-query correlation, the service already sets DD_DBM_PROPAGATION_MODE=full via the registry environment key written by the installer. No additional configuration is needed on the .NET side.
| Table | Columns |
|---|---|
Products |
Id, Name, Description, Price, Category, Stock, CreatedAt |
Customers |
Id, FirstName, LastName, Email, CreatedAt |
Orders |
Id, CustomerId, ProductId, Quantity, UnitPrice, TotalPrice, Status, OrderDate |
Stored procedures: sp_GetOrderSummary (aggregate by status), sp_GetCustomerOrders @CustomerId.
All tables are created idempotently on first startup. Seed data (6 products, 4 customers, 10 orders) is inserted only when tables are empty.
| Service Name | Port | Upstream / Purpose |
|---|---|---|
WindowsProxyService.OpenMeteo |
5052 | https://api.open-meteo.com |
WindowsProxyService.JsonPlaceholder |
5053 | https://jsonplaceholder.typicode.com |
WindowsProxyService.DatadogDemo |
5054 | https://datadog-sanity-vercel-demo-app.vercel.app |
WindowsSqlService |
5055 | localhost\SQLEXPRESS — WpsDemo database API |
WindowsDashboardService |
5051 | (serves the local dashboard UI) |
Each proxy service emits single-line JSON:
{
"Timestamp": "2026-03-05 10:15:00",
"Level": "Information",
"Message": "Proxy 'OpenMeteo' starting on +:5052, forwarding to https://api.open-meteo.com",
"Category": "WindowsProxyService.Program",
"Scopes": [{ "Instance": "OpenMeteo" }]
}

