Nefarius.Tools.SDCM
1.1.0
Prefix Reserved
dotnet tool install --global Nefarius.Tools.SDCM --version 1.1.0
dotnet new tool-manifest
dotnet tool install --local Nefarius.Tools.SDCM --version 1.1.0
#tool dotnet:?package=Nefarius.Tools.SDCM&version=1.1.0
nuke :add-package Nefarius.Tools.SDCM --version 1.1.0
Surface Dev Center Manager (SDCM)
Surface Dev Center Manager (SDCM) is a .NET tool that automates common Microsoft Hardware Dev Center (Partner Center) tasks around driver and firmware submissions, using the Hardware Dashboard API.
sdcm lets you create Attestation and WHQL products and submissions, upload and download
packages, manage shipping labels to release drivers on Windows Update, and submit packages for
preproduction signing.
This is
nefarius/SDCM, a modernized fork ofmicrosoft/SDCM: .NET 10, dependency injection,System.CommandLine, MSAL instead of the now end-of-life ADAL, and adotnet tooldistribution. Backward compatibility with the original CLI was not a design goal here - see Migrating from sdcm 1.x below if you're coming from the upstream tool.
Installation
Requires the .NET 10 runtime or later.
As a global tool (recommended - puts sdcm on your PATH):
dotnet tool install -g Nefarius.Tools.SDCM
sdcm --help
As a local tool (pinned per-repository via a tool manifest):
dotnet new tool-manifest # if you don't already have one
dotnet tool install Nefarius.Tools.SDCM
dotnet sdcm --help
To update: dotnet tool update -g Nefarius.Tools.SDCM.
Documentation
- Authentication - Partner Center API key (preferred),
sdcm config set, config discovery,--auth/--aad - Submit and wait from CI - non-interactive submit, wait, and download
- Submission states -
commitStatus,statevscurrentStep, downloads
Setting up credentials
The preferred path is a Partner Center API key (the Key on a Microsoft Entra application created
under User management). That key is sdcm's --auth client-secret profile key.
sdcm config set --tenant-id <tenant-guid> --client-id <client-guid> --key
sdcm product list
--key prompts for the Partner Center secret (or reads one line from stdin). Do not put the key on
the command line.
Walkthrough, config layers, and the other auth modes:
docs/authentication.md. sdcm config path shows which file was resolved.
Command reference
sdcm
├─ product
│ ├─ create --input <file>
│ ├─ list [--product-id <id>] (id form deprecated; prefer get)
│ └─ get --product-id <id>
├─ submission
│ ├─ create --product-id --input <file>
│ ├─ list --product-id <id> [--submission-id <id>] (id form deprecated)
│ ├─ get --product-id --submission-id
│ ├─ status --product-id --submission-id
│ ├─ commit --product-id --submission-id (idempotent)
│ ├─ upload --product-id --submission-id --package <path>
│ ├─ download --product-id --submission-id --output-file <path>
│ │ [--overwrite]
│ ├─ wait --product-id --submission-id [--wait-metadata]
│ │ [--poll-interval <sec>] [--wait-timeout <sec>]
│ └─ metadata
│ ├─ download --product-id --submission-id --output-file <path>
│ │ [--overwrite]
│ └─ create --product-id --submission-id
├─ preprod-submission
│ ├─ submit --package <path>
│ ├─ status --package-id <id>
│ ├─ assets --package-id <id> [--asset-id <id>]
│ ├─ download --package-id <id> --asset-id <id> --output-file <path>
│ │ [--overwrite]
│ └─ wait --package-id <id> [--poll-interval <sec>] [--wait-timeout <sec>]
├─ shipping-label
│ ├─ create --product-id --submission-id --input <file> [--partner-id]
│ ├─ list --product-id --submission-id [--shipping-label-id]
│ └─ wait --product-id --submission-id --shipping-label-id
│ [--poll-interval <sec>] [--wait-timeout <sec>]
├─ partner-submission
│ ├─ list --publisher-id --product-id --submission-id
│ └─ translate --publisher-id --product-id --submission-id
├─ audience list
└─ config
├─ path
├─ init [--force]
└─ set [--tenant-id] [--client-id] [--key]
Global options, valid anywhere in the tree: --profile, --auth, --aad, --config, --timeout
(HTTP timeout in seconds, default 300), --output text|json (default text), -v/--verbose
(diagnostic logging on stderr), and --replay <fixtures.json> (or SDCM_REPLAY) for an offline
fake backend. See Submission states.
Run sdcm <command> --help (or sdcm <noun> <verb> --help) for the full option list of any command.
Input file schema
--input takes the bare payload for the type being created - no wrapper object. This deserializes
directly into the underlying library's NewProduct, NewSubmission or NewShippingLabel types.
Creating a product
{
"productName": "ProductName_HLK",
"testHarness": "HLK",
"announcementDate": "2023-01-01T00:00:00",
"firmwareVersion": "0",
"deviceType": "external",
"isTestSign": false,
"isFlightSign": false,
"selectedProductTypes": {
"windows_v100_RS4": "Unclassified"
},
"requestedSignatures": [
"WINDOWS_v100_X64_RS4_FULL"
]
}
For an Attestation submission, set
testHarnesstoAttestation.
Creating a submission
{
"name": "ProductName_HLK_Submission",
"type": "initial"
}
Creating a shipping label
{
"publishingSpecifications": {
"goLiveDate": "2023-01-01T00:00:00.000Z",
"visibleToAccounts": [],
"isAutoInstallDuringOSUpgrade": true,
"isAutoInstallOnApplicableSystems": true,
"manualAcquisition": false,
"isDisclosureRestricted": true,
"publishToWindows10s": false,
"additionalInfoForMsApproval": {
"microsoftContact": "contact@microsoft.com",
"validationsPerformed": "TBD",
"affectedOems": ["Your Company"],
"isRebootRequired": true,
"isCoEngineered": true,
"isForUnreleasedHardware": true,
"hasUiSoftware": false,
"businessJustification": "Driver Update"
}
},
"targeting": {
"hardwareIds": [
{
"bundleId": "0",
"infId": "empty.inf",
"operatingSystemCode": "WINDOWS_v100_RS4_FULL",
"pnpString": "empty pnp"
}
],
"chids": [
{ "chid": "guid", "distributionState": "pendingAdd" }
],
"restrictedToAudiences": [],
"inServicePublishInfo": { "flooring": "19H1", "ceiling": "19H1" }
},
"name": "ProductName_HLK_ShippingLabel",
"destination": "windowsUpdate"
}
A file still using the old {"createType": ..., "createProduct"/"createSubmission"/"createShippingLabel": {...}}
envelope from sdcm 1.x fails fast with an explicit message pointing back to this section, instead of
a confusing null-reference deeper in the call stack.
Basic operations
Create a product:
sdcm product create --input product.json
Get it back by id, or list every product:
sdcm product get --product-id 12345
sdcm product list
Create and inspect a submission:
sdcm submission create --product-id 12345 --input submission.json
sdcm submission get --product-id 12345 --submission-id 67890
sdcm submission status --product-id 12345 --submission-id 67890
Upload the package (must be signed by the Extended Validation Certificate (EV Cert) registered on your Hardware account), commit, and wait for processing:
sdcm submission upload --product-id 12345 --submission-id 67890 --package test.hlkx
sdcm submission commit --product-id 12345 --submission-id 67890
sdcm submission wait --product-id 12345 --submission-id 67890
Download the signed result:
sdcm submission download --product-id 12345 --submission-id 67890 --output-file signed.zip
Get a package signed for preproduction testing instead - no product/submission needed, just the EV-signed package itself:
$packageId = (sdcm preprod-submission submit --package test.cab --output json | ConvertFrom-Json).id
sdcm preprod-submission wait --package-id $packageId
sdcm preprod-submission assets --package-id $packageId
sdcm preprod-submission download --package-id $packageId --asset-id <asset-id-from-assets> --output-file signed.zip
Add --output json to any command to get machine-readable results for scripting, instead of
regexing human-readable text:
$id = (sdcm product create --input product.json --output json | ConvertFrom-Json).id
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | InvalidArguments - parse errors, malformed or missing --input file |
| 2 | AuthenticationFailed - no usable credentials, token acquisition failed |
| 3 | ApiRequestFailed - generic Hardware Dev Center API error |
| 4 | NotFound - the requested entity doesn't exist |
| 5 | InvalidState - the request is invalid for the entity's current state |
| 6 | RateLimited - HTTP 429 from the service |
| 7 | WorkflowFailed - the submission or shipping label failed server-side |
| 8 | IoError - output path missing, or destination already exists |
| 9 | Canceled - Ctrl+C, or a --wait-timeout was exceeded |
| 10 | UnhandledException |
Automation scripts
End-to-end local scripts (sdcm must be on PATH). For CI submit/wait/download, see
docs/ci-signing.md.
Scripts/HLKx.ps1- WHQL-sign a driver from a signed HLKx packageScripts/Attestation.ps1- Attestation-sign a driver packageScripts/ShippingLabel.ps1- create and wait on a shipping labelScripts/Preprod.ps1- get a package signed for preproduction testing
Migrating from sdcm 1.x
| sdcm 1.x | sdcm 2.x (this fork) |
|---|---|
-create <product json> |
product create --input |
-create <submission json> -productid |
submission create --product-id --input |
-create <shippingLabel json> -productid -submissionid |
shipping-label create --product-id --submission-id --input |
-commit |
submission commit |
-list product\|submission\|shippinglabel\|partnersubmission |
product list / submission list / shipping-label list / partner-submission list |
-upload |
submission upload --package |
-download |
submission download --output-file |
-metadata |
submission metadata download --output-file |
-createmetadata |
submission metadata create |
-wait (with/without -shippinglabelid) |
submission wait / shipping-label wait |
-a / -audience |
audience list |
-translate |
partner-submission translate |
-partnerid |
shipping-label create --partner-id |
-server <int> |
--profile <name> |
-creds <mode> |
--auth <mode> |
Also new:
--inputfiles no longer use the{"createType": ..., "createXxx": {...}}envelope - see Input file schema.authconfig.jsonmoved from an ordinal array to namedprofiles, and now lives in a per-user config directory by default rather than next to the executable - see Authentication.ErrorCodes(48 negative values) was replaced by ten positive exit codes.-vused to be dead code; it now actually raises the log level.
Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.0 | 116 | 9/10/2026 |
| 1.0.0-pre004 | 195 | 9/10/2026 |
| 1.0.0-pre003 | 111 | 9/7/2026 |
| 1.0.0-pre002 | 113 | 9/7/2026 |
| 1.0.0-pre001 | 113 | 9/7/2026 |