Add ARM64 (Windows on ARM) support

Build, EV-sign, Microsoft attestation-sign, and package AppSandbox for
Windows on ARM with feature parity to x64 except the vTPM and guest-state
isolation (neither is supported by ARM Windows HCS). Target architecture is
resolved at compile time (ASB_IS_ARM64 in disk_util.h); the supported pairing
is x64 build/host/guest and ARM64 build/host/guest, so one flag drives every
architecture decision. x64 behavior is unchanged throughout.

Runtime:
- hcs_vm.c: omit SecuritySettings (vTPM + GuestStateOnly isolation) on ARM;
  Secure Boot is retained.
- disk_util.c: emit the arch token in every autounattend/unattend
  processorArchitecture attribute; on ARM, bypass the Win11 Setup hardware
  checks via the HKLM\SYSTEM\Setup\LabConfig keys across all VM-creation
  paths; select the arch-correct OpenSSH Server MSI.
- d3dlayers.c / agent.c: resolve, download, and stage the arm64 D3D mapping
  layers for GPU-PV; stage the Vulkan ICD manifest under the arch-neutral
  name dzn_icd.json.

Build and release pipeline:
- AppSandbox.sln: add the missing AppSandboxPackage Release|ARM64 Build.0 so
  the VS GUI actually runs sign+package for the ARM64 config.
- AppSandboxPackage.vcxproj: run make-release.ps1 for Release on x64 or ARM64,
  passing -Platform $(Platform).
- make-release.ps1 / sign-drivers.ps1: -Platform ARM64 selects bin\Release-ARM64,
  the ARM64 attestation profile, and the AppSandbox-<ver>-win-arm64.zip name.
  Sanitize LIB before Add-Type so the .NET Framework C# compiler does not fail
  (CS1668) on the non-existent atlmfc\lib\ARM64 path the VS ARM64 build adds to
  LIB.
- Directory.Build.props: disable ApiValidator only when cross-building ARM64 on
  a non-ARM64 host (the x86 ApiExtractor cannot read ARM64 PEs); Microsoft
  re-runs full API validation on every attestation submission.
- Package.vcxproj: stage the vendored ARM64 devcon.exe into the ARM64 drivers
  output, mirroring the x64 devcon copy.
- vendor/devcon/arm64/devcon.exe: add the vendored ARM64 devcon.exe, un-ignored
  in .gitignore mirroring the existing vendor/webview2/arm64 precedent.
This commit is contained in:
James Stringer committed 2026-06-04 19:00:27 -04:00
1 parent 3f4294dab1
commit 717c5b5148
16 files changed
+297 -92

No files matched your search

+6
View File
@@ -419,6 +419,12 @@ FodyWeavers.xsd
!/vendor/webview2/arm64
!/vendor/webview2/arm64/**
# Vendored ARM64 devcon.exe — same explicit un-ignore (the arm64/ rule above
# otherwise catches it). The x64 devcon at vendor/devcon/devcon.exe is tracked
# directly; the ARM64 build stages this copy into bin\Release-ARM64\drivers\.
!/vendor/devcon/arm64
!/vendor/devcon/arm64/**
# Local-only planning and notes — kept on disk for context, not pushed.
/docs/
+1
View File
@@ -178,6 +178,7 @@ Global
{0FA17E55-9C3D-4B21-AE60-5D7C1B2E3F40}.Release|ARM64.ActiveCfg = Release|ARM64
{0FA17E55-9C3D-4B21-AE60-5D7C1B2E3F40}.Release|x64.ActiveCfg = Release|x64
{0FA17E55-9C3D-4B21-AE60-5D7C1B2E3F40}.Release|x64.Build.0 = Release|x64
{0FA17E55-9C3D-4B21-AE60-5D7C1B2E3F40}.Release|ARM64.Build.0 = Release|ARM64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+15
View File
@@ -33,4 +33,19 @@
<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' != 'true'">
<SkipPackageVerification>true</SkipPackageVerification>
</PropertyGroup>
<!--
Same class of problem as the InfVerif skip above, for ARM64 driver builds.
ARM64 drivers must be Universal, so the WDK runs ApiValidator on them — but it
defaults to the x86 ApiExtractor (aitstatic.exe), which cannot read an ARM64 PE
and fails with exit code 193 (ERROR_BAD_EXE_FORMAT) when cross-building ARM64 on
a non-ARM64 host (e.g. the x64 dev/CI box). Disable it ONLY for that cross-build
case: native ARM64 hosts and every x64 build still run it, and Microsoft re-runs
full API validation on every attestation submission, so nothing ships unchecked.
(Checks both PROCESSOR_ARCHITECTURE and ...ITEW6432 so an emulated x64 MSBuild on
an ARM64 host is correctly treated as "host can validate".)
-->
<PropertyGroup Condition="'$(Platform)' == 'ARM64' And '$(PROCESSOR_ARCHITECTURE)' != 'ARM64' And '$(PROCESSOR_ARCHITEW6432)' != 'ARM64'">
<ApiValidator_Enable>false</ApiValidator_Enable>
</PropertyGroup>
</Project>
+38 -22
View File
@@ -7,8 +7,8 @@
* 2. Windows Update FE3 client protocol over WinHTTP (HTTPS):
* GetCookie -> SyncUpdates(filtered by WuCategoryId)
* -> GetExtendedUpdateInfo2 -> time-limited CDN url.
* The x64 package is located by identifier in the response, so version
* changes / extra updates do not break resolution.
* The package for the build architecture is located by identifier in the
* response, so version changes / extra updates do not break resolution.
* 3. Download the .appx (a zip) from the Microsoft CDN.
* 4. Extract with the in-box tar.exe (libarchive handles zip natively).
* 5. Copy the mapping-layer files into <exe_dir>\d3dlayers\ (cached).
@@ -36,17 +36,33 @@
#define DL_FE3_PATH_SECURED L"/ClientWebService/client.asmx/secured"
#define DL_SOAP_HEADERS L"Content-Type: application/soap+xml; charset=utf-8\r\n"
#define DL_CACHE_SUBDIR L"d3dlayers"
#define DL_APPX_NAME L"D3DMappingLayers_x64.appx"
/* The D3D Mapping Layers Store package ships a per-architecture .appx; pick the
one matching the guest (= build) architecture. The host downloads it, so on
the ARM64 build we fetch and stage the arm64 layers for the arm64 guest. */
#if defined(_M_ARM64)
# define DL_ARCH L"arm64"
# define DL_ARCH_A "arm64"
#else
# define DL_ARCH L"x64"
# define DL_ARCH_A "x64"
#endif
#define DL_APPX_NAME L"D3DMappingLayers_" DL_ARCH L".appx"
/* Substring identifying the package file in the SyncUpdates response — the
InstallerSpecificIdentifier contains e.g. "..._x64_..." / "..._arm64_...". */
#define DL_ISI_ARCH "_" DL_ARCH_A "_"
#define DL_MAX_ROUNDS 5 /* FE3 faults intermittently; retry the chain */
/* Files to extract: dest leaf name <- source path inside the .appx. */
/* Files to extract: dest leaf name <- source path inside the .appx. The arch
subfolder inside the package (x64\ / arm64\) is selected by DL_ARCH; the
Vulkan ICD manifest is staged under the arch-neutral name dzn_icd.json so the
guest agent references one fixed filename regardless of architecture. */
static const wchar_t *const DL_DST[] = {
L"OpenGLOn12.dll", L"dxil.dll", L"OpenCLOn12.dll",
L"clon12compiler.dll", L"vulkan_dzn.dll", L"dzn_icd.x64.json"
L"clon12compiler.dll", L"vulkan_dzn.dll", L"dzn_icd.json"
};
static const wchar_t *const DL_SRC[] = {
L"x64\\OpenGLOn12.dll", L"x64\\dxil.dll", L"x64\\OpenCLOn12.dll",
L"x64\\clon12compiler.dll", L"x64\\vulkan_dzn.dll", L"dzn_icd.x64.json"
DL_ARCH L"\\OpenGLOn12.dll", DL_ARCH L"\\dxil.dll", DL_ARCH L"\\OpenCLOn12.dll",
DL_ARCH L"\\clon12compiler.dll", DL_ARCH L"\\vulkan_dzn.dll", L"dzn_icd." DL_ARCH L".json"
};
#define DL_NFILES ((int)(sizeof(DL_DST)/sizeof(DL_DST[0])))
@@ -318,10 +334,10 @@ done:
/* ---- response parsing ---- */
/* Find the x64 D3DMappingLayers .appx in a (decoded) SyncUpdates response and
* resolve its UpdateIdentity by joining the file's enclosing <Update> <ID> to
* the matching NewUpdates <UpdateInfo> <UpdateIdentity>. */
static BOOL parse_x64_leaf(const char *buf, char *uid, size_t uid_cch,
/* Find the D3DMappingLayers .appx matching the build architecture in a
* (decoded) SyncUpdates response and resolve its UpdateIdentity by joining the
* file's enclosing <Update> <ID> to the matching NewUpdates <UpdateIdentity>. */
static BOOL parse_leaf(const char *buf, char *uid, size_t uid_cch,
char *rev, size_t rev_cch, char *digest, size_t dig_cch)
{
const char *p = buf;
@@ -340,11 +356,11 @@ static BOOL parse_x64_leaf(const char *buf, char *uid, size_t uid_cch,
!attr_in(tag, "InstallerSpecificIdentifier", isi, sizeof(isi))) {
p = gt + 1; continue;
}
if (!strstr(isi, "D3DMappingLayers") || !strstr(isi, "_x64_") ||
if (!strstr(isi, "D3DMappingLayers") || !strstr(isi, DL_ISI_ARCH) ||
!strstr(fn, ".appx")) {
p = gt + 1; continue;
}
/* matched the x64 package file */
/* matched the package file for this architecture */
attr_in(tag, "Digest", digest, dig_cch);
/* nearest <ID>N</ID> before this <File> = the ExtendedUpdateInfo id */
@@ -404,7 +420,7 @@ static BOOL parse_file_url(const char *buf, const char *digest,
return FALSE;
}
/* Resolve the x64 package CDN url from Microsoft. Returns 0 on success. */
/* Resolve the (build-architecture) package CDN url from Microsoft. Returns 0 on success. */
static int resolve_url(const char *wucatid, wchar_t *url_out, size_t url_cch)
{
int round, rc = -1;
@@ -438,8 +454,8 @@ static int resolve_url(const char *wucatid, wchar_t *url_out, size_t url_cch)
if (t1) { sync_req = str_replace_all(t1, "%CATID%", wucatid); free(t1); }
if (sync_req) { sync_resp = fe3_post(DL_FE3_PATH, sync_req); free(sync_req); }
if (sync_resp) html_unescape(sync_resp);
if (!sync_resp || !parse_x64_leaf(sync_resp, uid, sizeof(uid), rev, sizeof(rev),
digest, sizeof(digest))) {
if (!sync_resp || !parse_leaf(sync_resp, uid, sizeof(uid), rev, sizeof(rev),
digest, sizeof(digest))) {
free(sync_resp); Sleep(1500); continue;
}
free(sync_resp);
@@ -462,7 +478,7 @@ static int resolve_url(const char *wucatid, wchar_t *url_out, size_t url_cch)
return rc;
}
/* The shipped dzn_icd.x64.json gives library_path as "x64\vulkan_dzn.dll",
/* The shipped dzn_icd.<arch>.json gives library_path as "<arch>\vulkan_dzn.dll",
* relative to the manifest. The layers are staged into one flat directory, so
* point library_path at the DLL's fully-qualified location in the guest.
*
@@ -479,7 +495,7 @@ static void rewrite_dzn_json(const wchar_t *dir)
char *buf, *fixed;
size_t rd;
swprintf_s(path, MAX_PATH, L"%s\\dzn_icd.x64.json", dir);
swprintf_s(path, MAX_PATH, L"%s\\dzn_icd.json", dir);
if (_wfopen_s(&f, path, L"rb") != 0 || !f) return;
fseek(f, 0, SEEK_END); sz = ftell(f); fseek(f, 0, SEEK_SET);
if (sz <= 0 || sz > 65536) { fclose(f); return; }
@@ -489,10 +505,10 @@ static void rewrite_dzn_json(const wchar_t *dir)
fclose(f);
buf[rd] = 0;
/* On disk the JSON value is the 18 bytes x64\\vulkan_dzn.dll (two
* backslash characters, JSON-escaped). Match that exactly and replace it
* with the absolute guest path (each backslash JSON-escaped). */
fixed = str_replace_all(buf, "x64\\\\vulkan_dzn.dll",
/* On disk the JSON value is <arch>\\vulkan_dzn.dll (two backslash
* characters, JSON-escaped). Match that exactly and replace it with the
* absolute guest path (each backslash JSON-escaped). */
fixed = str_replace_all(buf, DL_ARCH_A "\\\\vulkan_dzn.dll",
"C:\\\\Windows\\\\AppSandbox\\\\d3dlayers\\\\vulkan_dzn.dll");
free(buf);
if (!fixed) return;
+2 -1
View File
@@ -14,7 +14,8 @@
* OpenCLOn12.dll OpenCL -> D3D12
* clon12compiler.dll OpenCL compiler backend
* vulkan_dzn.dll Vulkan (Mesa Dozen) -> D3D12
* dzn_icd.x64.json Vulkan ICD manifest (points at vulkan_dzn.dll)
* dzn_icd.json Vulkan ICD manifest (points at vulkan_dzn.dll); staged under
* this arch-neutral name from the package's dzn_icd.<arch>.json
*/
#ifndef D3DLAYERS_H
+121 -32
View File
@@ -15,8 +15,27 @@
#include <urlmon.h>
#define SSH_MSI_URL L"https://github.com/PowerShell/Win32-OpenSSH/releases/download/10.0.0.0p2-Preview/OpenSSH-Win64-v10.0.0.0.msi"
#define SSH_MSI_NAME L"OpenSSH-Win64-v10.0.0.0.msi"
/* Architecture token written into every autounattend/unattend component's
processorArchitecture attribute. Matches the guest, which is the build arch
(see ASB_IS_ARM64 in disk_util.h). */
#if ASB_IS_ARM64
# define ASB_UA_ARCH L"arm64"
#else
# define ASB_UA_ARCH L"amd64"
#endif
/* OpenSSH Server MSI, staged into the guest and installed by SetupComplete.cmd.
The Win32-OpenSSH release ships a per-architecture MSI; pick the one matching
the guest (= build) architecture so it installs on an ARM64 guest. */
#if ASB_IS_ARM64
# define SSH_MSI_URL L"https://github.com/PowerShell/Win32-OpenSSH/releases/download/10.0.0.0p2-Preview/OpenSSH-ARM64-v10.0.0.0.msi"
# define SSH_MSI_NAME L"OpenSSH-ARM64-v10.0.0.0.msi"
# define SSH_MSI_NAME_A "OpenSSH-ARM64-v10.0.0.0.msi" /* narrow, for the cmd scripts */
#else
# define SSH_MSI_URL L"https://github.com/PowerShell/Win32-OpenSSH/releases/download/10.0.0.0p2-Preview/OpenSSH-Win64-v10.0.0.0.msi"
# define SSH_MSI_NAME L"OpenSSH-Win64-v10.0.0.0.msi"
# define SSH_MSI_NAME_A "OpenSSH-Win64-v10.0.0.0.msi" /* narrow, for the cmd scripts */
#endif
static const GUID VHDX_VENDOR_MS = {
0xec984aec, 0xa0f9, 0x47e9,
@@ -212,6 +231,32 @@ static const wchar_t *lang_to_input_locale(const wchar_t *lang)
return L"0409:00000409"; /* fallback */
}
#if ASB_IS_ARM64
/* Emit RunSynchronousCommand entries (inside an already-open <RunSynchronous>)
that disable the Windows 11 setup hardware requirement checks by writing the
HKLM\SYSTEM\Setup\LabConfig bypass keys. ARM Windows HCS exposes no vTPM, so
without these Windows Setup's appraiser would refuse to install. `order` is
the next <Order> value to use; returns the order after the last entry.
ARM-only: on x64 the guest gets a real vTPM from HCS and needs no bypass. */
static int write_tpm_bypass_commands(FILE *f, int order)
{
static const wchar_t *const keys[] = {
L"BypassTPMCheck", L"BypassSecureBootCheck", L"BypassRAMCheck",
L"BypassStorageCheck", L"BypassCPUCheck"
};
int i;
for (i = 0; i < (int)(sizeof(keys) / sizeof(keys[0])); i++) {
fwprintf(f,
L" <RunSynchronousCommand wcm:action=\"add\">\n"
L" <Order>%d</Order>\n"
L" <Path>reg add HKLM\\SYSTEM\\Setup\\LabConfig /v %s /t REG_DWORD /d 1 /f</Path>\n"
L" </RunSynchronousCommand>\n",
order++, keys[i]);
}
return order;
}
#endif
/* Generate autounattend.xml with credentials and VM name substituted.
If is_template, adds SecureStartup-FilterDriver to disable BitLocker
(required for sysprep). */
@@ -238,7 +283,7 @@ static BOOL generate_autounattend(const wchar_t *output_path,
L"\n"
L" <settings pass=\"windowsPE\">\n"
L" <component name=\"Microsoft-Windows-International-Core-WinPE\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <SetupUILanguage><UILanguage>%s</UILanguage></SetupUILanguage>\n"
@@ -248,7 +293,7 @@ static BOOL generate_autounattend(const wchar_t *output_path,
L" <UserLocale>%s</UserLocale>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-Setup\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <UserData>\n"
@@ -274,19 +319,32 @@ static BOOL generate_autounattend(const wchar_t *output_path,
L" <ModifyPartition wcm:action=\"add\"><Order>2</Order><PartitionID>2</PartitionID></ModifyPartition>\n"
L" <ModifyPartition wcm:action=\"add\"><Order>3</Order><PartitionID>3</PartitionID><Format>NTFS</Format><Label>Windows</Label><Letter>C</Letter></ModifyPartition>\n"
L" </ModifyPartitions>\n"
L" </Disk></DiskConfiguration>\n"
L" </Disk></DiskConfiguration>\n",
lang, lang_to_input_locale(lang), lang, lang, lang);
/* ARM only: disable the Windows 11 Setup hardware checks via the
HKLM\SYSTEM\Setup\LabConfig keys, from a windowsPE RunSynchronous in the
Setup component (runs before the appraiser). ARM Windows HCS has no vTPM,
so Setup would otherwise refuse to install. */
#if ASB_IS_ARM64
fwprintf(f, L" <RunSynchronous>\n");
write_tpm_bypass_commands(f, 1);
fwprintf(f, L" </RunSynchronous>\n");
#endif
fwprintf(f,
L" </component>\n"
L" </settings>\n"
L"\n"
L" <settings pass=\"specialize\">\n"
L" <component name=\"Microsoft-Windows-Shell-Setup\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <ComputerName>%s</ComputerName>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-Deployment\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <RunSynchronous>\n"
@@ -294,7 +352,6 @@ static BOOL generate_autounattend(const wchar_t *output_path,
L" <Order>1</Order>\n"
L" <Path>reg add HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE /v BypassNRO /t REG_DWORD /d 1 /f</Path>\n"
L" </RunSynchronousCommand>\n",
lang, lang_to_input_locale(lang), lang, lang, lang,
comp_name);
{
@@ -336,7 +393,7 @@ static BOOL generate_autounattend(const wchar_t *output_path,
if (is_template) {
fwprintf(f,
L" <component name=\"Microsoft-Windows-SecureStartup-FilterDriver\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <PreventDeviceEncryption>true</PreventDeviceEncryption>\n"
@@ -353,7 +410,7 @@ static BOOL generate_autounattend(const wchar_t *output_path,
fwprintf(f,
L" <settings pass=\"oobeSystem\">\n"
L" <component name=\"Microsoft-Windows-Deployment\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <Reseal>\n"
@@ -364,7 +421,7 @@ static BOOL generate_autounattend(const wchar_t *output_path,
L"\n"
L" <settings pass=\"auditUser\">\n"
L" <component name=\"Microsoft-Windows-Deployment\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <RunSynchronous>\n"
@@ -382,7 +439,7 @@ static BOOL generate_autounattend(const wchar_t *output_path,
fwprintf(f,
L" <settings pass=\"oobeSystem\">\n"
L" <component name=\"Microsoft-Windows-International-Core\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <InputLocale>%s</InputLocale>\n"
@@ -391,7 +448,7 @@ static BOOL generate_autounattend(const wchar_t *output_path,
L" <UserLocale>%s</UserLocale>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-Shell-Setup\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <OOBE>\n"
@@ -730,9 +787,9 @@ HRESULT iso_create_resources(const wchar_t *iso_path,
if (ssh_enabled) {
fputs(
"REM Install OpenSSH Server\r\n"
"if exist \"%ISODRV%\\OpenSSH-Win64-v10.0.0.0.msi\" (\r\n"
"if exist \"%ISODRV%\\" SSH_MSI_NAME_A "\" (\r\n"
" echo [SSH] Installing OpenSSH Server... >> \"%LOG%\"\r\n"
" msiexec /i \"%ISODRV%\\OpenSSH-Win64-v10.0.0.0.msi\" /qn /norestart >> \"%LOG%\" 2>&1\r\n"
" msiexec /i \"%ISODRV%\\" SSH_MSI_NAME_A "\" /qn /norestart >> \"%LOG%\" 2>&1\r\n"
" echo [SSH] msiexec exit code: %errorlevel% >> \"%LOG%\"\r\n"
" sc config sshd start= auto >> \"%LOG%\" 2>&1\r\n"
" net start sshd >> \"%LOG%\" 2>&1\r\n"
@@ -791,13 +848,13 @@ static BOOL generate_unattend_instance(const wchar_t *output_path,
L"\n"
L" <settings pass=\"specialize\">\n"
L" <component name=\"Microsoft-Windows-Shell-Setup\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <ComputerName>%s</ComputerName>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-Deployment\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <RunSynchronous>\n"
@@ -821,13 +878,37 @@ static BOOL generate_unattend_instance(const wchar_t *output_path,
L" <Order>5</Order>\n"
L" <Path>cmd /c for %%d in (D E F G H I J) do @if exist %%d:\\SetupComplete.cmd copy /Y %%d:\\SetupComplete.cmd C:\\Windows\\Setup\\Scripts\\</Path>\n"
L" </RunSynchronousCommand>\n"
#if ASB_IS_ARM64
/* ARM only: Windows 11 LabConfig hardware-check bypass keys (no vTPM on
ARM). Orders 6-10 continue this component's RunSynchronous. */
L" <RunSynchronousCommand wcm:action=\"add\">\n"
L" <Order>6</Order>\n"
L" <Path>reg add HKLM\\SYSTEM\\Setup\\LabConfig /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path>\n"
L" </RunSynchronousCommand>\n"
L" <RunSynchronousCommand wcm:action=\"add\">\n"
L" <Order>7</Order>\n"
L" <Path>reg add HKLM\\SYSTEM\\Setup\\LabConfig /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path>\n"
L" </RunSynchronousCommand>\n"
L" <RunSynchronousCommand wcm:action=\"add\">\n"
L" <Order>8</Order>\n"
L" <Path>reg add HKLM\\SYSTEM\\Setup\\LabConfig /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path>\n"
L" </RunSynchronousCommand>\n"
L" <RunSynchronousCommand wcm:action=\"add\">\n"
L" <Order>9</Order>\n"
L" <Path>reg add HKLM\\SYSTEM\\Setup\\LabConfig /v BypassStorageCheck /t REG_DWORD /d 1 /f</Path>\n"
L" </RunSynchronousCommand>\n"
L" <RunSynchronousCommand wcm:action=\"add\">\n"
L" <Order>10</Order>\n"
L" <Path>reg add HKLM\\SYSTEM\\Setup\\LabConfig /v BypassCPUCheck /t REG_DWORD /d 1 /f</Path>\n"
L" </RunSynchronousCommand>\n"
#endif
L" </RunSynchronous>\n"
L" </component>\n"
L" </settings>\n"
L"\n"
L" <settings pass=\"oobeSystem\">\n"
L" <component name=\"Microsoft-Windows-International-Core\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <InputLocale>%s</InputLocale>\n"
@@ -836,7 +917,7 @@ static BOOL generate_unattend_instance(const wchar_t *output_path,
L" <UserLocale>%s</UserLocale>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-Shell-Setup\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <OOBE>\n"
@@ -1204,9 +1285,9 @@ static void stage_agent_and_setup(const wchar_t *staging, const wchar_t *res_dir
if (ssh_enabled) {
fputs(
"REM Install OpenSSH Server\r\n"
"if exist \"%ISODRV%\\OpenSSH-Win64-v10.0.0.0.msi\" (\r\n"
"if exist \"%ISODRV%\\" SSH_MSI_NAME_A "\" (\r\n"
" echo [SSH] Installing OpenSSH Server... >> \"%LOG%\"\r\n"
" msiexec /i \"%ISODRV%\\OpenSSH-Win64-v10.0.0.0.msi\" /qn /norestart >> \"%LOG%\" 2>&1\r\n"
" msiexec /i \"%ISODRV%\\" SSH_MSI_NAME_A "\" /qn /norestart >> \"%LOG%\" 2>&1\r\n"
" echo [SSH] msiexec exit code: %errorlevel% >> \"%LOG%\"\r\n"
" sc config sshd start= auto >> \"%LOG%\" 2>&1\r\n"
" net start sshd >> \"%LOG%\" 2>&1\r\n"
@@ -1329,13 +1410,13 @@ BOOL generate_unattend_vhdx(const wchar_t *output_path,
L"\n"
L" <settings pass=\"specialize\">\n"
L" <component name=\"Microsoft-Windows-Shell-Setup\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <ComputerName>%s</ComputerName>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-Deployment\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <RunSynchronous>\n"
@@ -1364,6 +1445,10 @@ BOOL generate_unattend_vhdx(const wchar_t *output_path,
L" <Path>bcdedit /set testsigning on</Path>\n"
L" </RunSynchronousCommand>\n", order++);
}
#if ASB_IS_ARM64
/* ARM only: Windows 11 LabConfig hardware-check bypass keys (no vTPM on ARM). */
write_tpm_bypass_commands(f, order);
#endif
}
fwprintf(f,
@@ -1376,7 +1461,7 @@ BOOL generate_unattend_vhdx(const wchar_t *output_path,
fwprintf(f,
L" <settings pass=\"oobeSystem\">\n"
L" <component name=\"Microsoft-Windows-International-Core\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <InputLocale>%s</InputLocale>\n"
@@ -1385,7 +1470,7 @@ BOOL generate_unattend_vhdx(const wchar_t *output_path,
L" <UserLocale>%s</UserLocale>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-Shell-Setup\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <OOBE>\n"
@@ -1455,13 +1540,13 @@ BOOL generate_unattend_vhdx_template(const wchar_t *output_path,
L"\n"
L" <settings pass=\"specialize\">\n"
L" <component name=\"Microsoft-Windows-Shell-Setup\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <ComputerName>%s</ComputerName>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-Deployment\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <RunSynchronous>\n"
@@ -1490,13 +1575,17 @@ BOOL generate_unattend_vhdx_template(const wchar_t *output_path,
L" <Path>bcdedit /set testsigning on</Path>\n"
L" </RunSynchronousCommand>\n", order++);
}
#if ASB_IS_ARM64
/* ARM only: Windows 11 LabConfig hardware-check bypass keys (no vTPM on ARM). */
write_tpm_bypass_commands(f, order);
#endif
}
fwprintf(f,
L" </RunSynchronous>\n"
L" </component>\n"
L" <component name=\"Microsoft-Windows-SecureStartup-FilterDriver\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <PreventDeviceEncryption>true</PreventDeviceEncryption>\n"
@@ -1509,7 +1598,7 @@ BOOL generate_unattend_vhdx_template(const wchar_t *output_path,
fwprintf(f,
L" <settings pass=\"oobeSystem\">\n"
L" <component name=\"Microsoft-Windows-Deployment\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <Reseal>\n"
@@ -1520,7 +1609,7 @@ BOOL generate_unattend_vhdx_template(const wchar_t *output_path,
L"\n"
L" <settings pass=\"auditUser\">\n"
L" <component name=\"Microsoft-Windows-Deployment\"\n"
L" processorArchitecture=\"amd64\"\n"
L" processorArchitecture=\"" ASB_UA_ARCH L"\"\n"
L" publicKeyToken=\"31bf3856ad364e35\"\n"
L" language=\"neutral\" versionScope=\"nonSxS\">\n"
L" <RunSynchronous>\n"
@@ -1604,9 +1693,9 @@ BOOL generate_vhdx_setupcomplete(const wchar_t *output_path, BOOL ssh_enabled)
fputs(
"REM Install OpenSSH Server\r\n"
"set SSHDIR=%SystemRoot%\\AppSandbox\r\n"
"if exist \"%SSHDIR%\\OpenSSH-Win64-v10.0.0.0.msi\" (\r\n"
"if exist \"%SSHDIR%\\" SSH_MSI_NAME_A "\" (\r\n"
" echo [SSH] Installing OpenSSH Server... >> \"%LOG%\"\r\n"
" msiexec /i \"%SSHDIR%\\OpenSSH-Win64-v10.0.0.0.msi\" /qn /norestart >> \"%LOG%\" 2>&1\r\n"
" msiexec /i \"%SSHDIR%\\" SSH_MSI_NAME_A "\" /qn /norestart >> \"%LOG%\" 2>&1\r\n"
" echo [SSH] msiexec exit code: %errorlevel% >> \"%LOG%\"\r\n"
" sc config sshd start= auto >> \"%LOG%\" 2>&1\r\n"
" net start sshd >> \"%LOG%\" 2>&1\r\n"
+13
View File
@@ -3,6 +3,19 @@
#include <windows.h>
/* Target architecture, resolved at compile time. The supported pairing is
x64 build -> x64 host -> x64 guest, and ARM64 build -> ARM host -> ARM guest
(no cross-arch installs), so build arch == host arch == guest arch and a
single compile-time flag drives every architecture decision: the HCS
SecuritySettings (ARM Windows HCS has no vTPM / guest-state isolation), the
autounattend processorArchitecture token + Win11 TPM-check bypass, the
OpenSSH MSI variant, and the GPU-PV D3D mapping-layer package. */
#if defined(_M_ARM64)
# define ASB_IS_ARM64 1
#else
# define ASB_IS_ARM64 0
#endif
/* Create a new dynamically-expanding VHDX file.
size_gb: maximum virtual size in gigabytes. */
HRESULT vhdx_create(const wchar_t *path, ULONGLONG size_gb);
+1 -1
View File
@@ -61,7 +61,7 @@ BOOL gpu_append_lxsslib_share(GpuDriverShareList *list);
/* Append a synthetic Plan9 share (name "AppSandbox.GlLayers") exposing the
host directory that holds the D3D mapping layers (OpenGLOn12/dxil/OpenCLOn12/
clon12compiler/vulkan_dzn + dzn_icd.x64.json) and the OpenGL ICD shim. The
clon12compiler/vulkan_dzn + dzn_icd.json) and the OpenGL ICD shim. The
Windows guest agent copies this share AFTER the GPU driver shares and then
provisions it (stages dxil to System32, sets AppInit_DLLs + the Khronos
OpenCL/Vulkan ICD registry keys). guest_path is C:\Windows\AppSandbox\d3dlayers.
+9 -2
View File
@@ -1,6 +1,7 @@
#include "hcs_vm.h"
#include "vm_agent.h"
#include "ui.h"
#include "disk_util.h" /* ASB_IS_ARM64 */
#include <stdio.h>
#include <sddl.h>
#include <aclapi.h>
@@ -1041,9 +1042,14 @@ BOOL hcs_build_vm_json(const VmConfig *config, const wchar_t *endpoint_guid,
name_esc);
}
/* SecuritySettings with TPM — enabled for all Windows VMs.
TPM does not block test-signed drivers; only Secure Boot does. */
/* SecuritySettings (virtual TPM + GuestStateOnly isolation) — x64 only.
The vTPM is required by the Windows 11 guest on x64. ARM Windows HCS
supports neither a vTPM nor guest-state isolation, so the whole block is
emitted only on x64. The Windows 11 Setup TPM/SecureBoot requirement
check is bypassed via the autounattend LabConfig keys (see
generate_autounattend in disk_util.c). */
security_section[0] = L'\0';
#if !ASB_IS_ARM64
if (is_windows) {
wcscpy_s(security_section, 512,
L",\"SecuritySettings\":{"
@@ -1051,6 +1057,7 @@ BOOL hcs_build_vm_json(const VmConfig *config, const wchar_t *endpoint_guid,
L"\"Isolation\":{\"IsolationType\":\"GuestStateOnly\"}"
L"}");
}
#endif
/* Plan9 shares — GPU driver directories for agent p9copy.
Skip for template VMs (no GPU assigned during template creation). */
+1 -1
View File
@@ -454,7 +454,7 @@ static void gl_provision(const wchar_t *dir)
The manifest's library_path is the absolute guest DLL path (set host-side
in d3dlayers.c) so the loader can LoadLibraryEx it under
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, which requires a fully-qualified path. */
swprintf_s(path, MAX_PATH, L"%s\\dzn_icd.x64.json", dir);
swprintf_s(path, MAX_PATH, L"%s\\dzn_icd.json", dir);
if (GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES) {
if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Khronos\\Vulkan\\Drivers",
0, NULL, 0, KEY_SET_VALUE, NULL, &key, NULL) == ERROR_SUCCESS) {
+4 -4
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
AppSandboxPackage — a Utility project that runs tools\sign\make-release.ps1 -NoBuild
AFTER the solution builds, but ONLY for Release|x64.
-Platform $(Platform) AFTER the solution builds, for Release|x64 or Release|ARM64.
In AppSandbox.sln this project is set to depend on every other project, so Visual
Studio builds it LAST and SKIPS it entirely if any project failed to build — i.e.
@@ -54,9 +54,9 @@
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>
<Target Name="AppSandboxSignAndPackage" AfterTargets="Build"
Condition="'$(Configuration)|$(Platform)'=='Release|x64' And '$(ASB_SKIP_PACKAGE_PROJECT)' != '1'">
<Message Importance="high" Text="==== AppSandbox: make-release.ps1 -NoBuild (sign + package) ====" />
<Exec Command="powershell.exe -NoProfile -ExecutionPolicy Bypass -File &quot;$(SolutionDir)tools\sign\make-release.ps1&quot; -NoBuild" />
Condition="'$(Configuration)'=='Release' And ('$(Platform)'=='x64' Or '$(Platform)'=='ARM64') And '$(ASB_SKIP_PACKAGE_PROJECT)' != '1'">
<Message Importance="high" Text="==== AppSandbox: make-release.ps1 -NoBuild -Platform $(Platform) (sign + package) ====" />
<Exec Command="powershell.exe -NoProfile -ExecutionPolicy Bypass -File &quot;$(SolutionDir)tools\sign\make-release.ps1&quot; -NoBuild -Platform $(Platform)" />
</Target>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
+40 -18
View File
@@ -36,6 +36,8 @@ via attestation through Partner Center.
associated** to it (Partner Center adds the cert under the hardware account).
3. **Azure AD (Entra) app** for the Hardware submission API (below).
4. Windows SDK + WDK installed (`signtool.exe`, `infverif.exe`, `makecab.exe`).
5. For **ARM64** releases: the **ARM64 MSVC + WDK build tools** (cross-building from an x64
host is fully supported — no ARM device is needed to build, sign, or submit).
### 2a. Create the Entra app (for the submission API)
@@ -77,11 +79,11 @@ tools\sign\test-hardware-api.ps1
| File | Committed? | Purpose |
| --- | --- | --- |
| `partner-center.local.json` | **no** (git-ignored) | Entra API creds (tenant/client/secret) |
| `submission-config.json` | yes | Product names, OS signature targets, driver→cab mapping, timestamp URL, optional `evCertThumbprint` |
| `submission-config.json` | yes | Product names, OS signature targets, driver→cab mapping, timestamp URL, optional `evCertThumbprint` (x64 values; `sign-drivers.ps1 -Platform ARM64` derives the ARM64 equivalents at sign time) |
| `test-hardware-api.ps1` | yes | Validate creds + Hardware roles |
| `sign-drivers.ps1` | yes | EV-sign drivers (binary + catalog + cab) → attestation submit (both in parallel) → download MS-signed |
| `make-release.ps1` | yes | Build → (if YubiKey) sign all binaries + driver-attest + ZIP; else skip |
| `AppSandboxPackage.vcxproj` | yes | Utility project; runs `make-release.ps1 -NoBuild` after a successful `Release|x64` VS build |
| `AppSandboxPackage.vcxproj` | yes | Utility project; runs `make-release.ps1 -NoBuild -Platform $(Platform)` after a successful `Release|x64` **or** `Release|ARM64` VS build |
| `SIGNING.md` | yes | This document |
Versioning lives at the **repo root**: `Directory.Build.props` (the version numbers + the
@@ -97,18 +99,20 @@ that is inherent to EV signing — so the goal is "keep it out of source," not s
## 4. Running it
### From Visual Studio (the usual path)
**Build Solution** in **Release|x64**. The `AppSandboxPackage` project depends on every
other project, so Visual Studio builds it **last** and — via its `AfterTargets="Build"`
step — runs `make-release.ps1 -NoBuild`, **but only if every project built successfully**
(a failed build skips it, so no signing/packaging on a broken build). With no EV YubiKey
it no-ops (build only); with the token in it signs + attestation-signs + zips. Debug
builds and Release|ARM64 never trigger it.
**Build Solution** in **Release|x64** or **Release|ARM64**. The `AppSandboxPackage` project
depends on every other project, so Visual Studio builds it **last** and — via its
`AfterTargets="Build"` step — runs `make-release.ps1 -NoBuild -Platform $(Platform)`, **but
only if every project built successfully** (a failed build skips it, so no signing/packaging
on a broken build). With no EV YubiKey it no-ops (build only); with the token in it signs +
attestation-signs + zips (ARM64 outputs to `bin\Release-ARM64`, with its ARM64 attestation
profile and `-win-arm64.zip`). Debug builds never trigger it.
### Normal release (the one command)
```powershell
tools\sign\make-release.ps1
tools\sign\make-release.ps1 # x64
tools\sign\make-release.ps1 -Platform ARM64 # Windows on ARM
```
- Builds `Release|x64`.
- Builds `Release|x64` (or `Release|ARM64` with `-Platform ARM64`; output under `bin\Release-ARM64\`).
- **If the EV YubiKey is in:** EV-signs all binaries — app `.exe`/`.dll` **and** driver
`.sys`/`.dll` — in one call (**PIN 1**); then `sign-drivers.ps1` regenerates + EV-signs the
catalogs (**PIN 2**) and EV-signs the cabs (**PIN 3**), submits both drivers to Microsoft,
@@ -164,16 +168,34 @@ API flow (`https://manage.devcenter.microsoft.com/v2.0/my/hardware`): token →
URL) → PUT the signed CAB → `POST …/commit` → poll `workflowStatus.state` until `completed` →
download `downloads.items[type=="signedPackage"]`. Both drivers run **in parallel**.
**One product per version.** A product accepts exactly **one** submission (a second returns
`"Initial submission already exists"`), so each release is its own product — the version is
baked into the product name (e.g. `App Sandbox Virtual Display Driver 0.1.0`) so the dashboard
reads as a version history. Products **cannot be deleted** via the API (only renamed, via
**One product per version per architecture.** A product accepts exactly **one** submission (a
second returns `"Initial submission already exists"`), so each release is its own product — the
version **and architecture** are baked into the product name (e.g. `App Sandbox Virtual Display
Driver x64 0.1.0` / `… ARM64 0.1.0`) so the dashboard reads as a per-arch version history.
Products **cannot be deleted** via the API (only renamed, via
`PATCH /products/{id}`), so retire old ones by renaming, not removal.
**OS targets** are set in `submission-config.json` → `requestedSignatures`. Current
codes are listed in Microsoft's *Get product data* doc; Windows 11 x64:
`WINDOWS_v100_X64_CO_FULL` (21H2), `_NI_FULL` (22H2), `_GE_FULL` (24H2),
`_25H2_FULL` (25H2).
**OS targets** are set in `submission-config.json` → `requestedSignatures`. Current codes
are listed in Microsoft's *Get product data* doc; Windows 11 x64: `WINDOWS_v100_X64_CO_FULL`
(21H2), `_NI_FULL` (22H2), `_GE_FULL` (24H2), `_25H2_FULL` (25H2). For an ARM64 build,
`sign-drivers.ps1 -Platform ARM64` rewrites these to the `ARM64` codes (and uses `inf2cat
/os:10_CO_ARM64`) at sign time, so the committed config stays x64.
**x64 and ARM64 are submitted separately.** `sign-drivers.ps1 -Platform <arch>` (driven by
`make-release.ps1 -Platform <arch>`) builds the cab from that arch's driver output,
regenerates the catalog with `inf2cat /os:10_CO_<ARCH>`, and creates its **own** product.
The **architecture is in the product/submission name** (`… x64 <ver>` / `… ARM64 <ver>`)
so the two are easy to tell apart in the Partner Center dashboard. (A single combined
multi-arch submission is technically possible but would need both arches built before
signing, which breaks the per-platform "build one config → auto sign+zip" automation —
hence separate per-arch submissions.)
**ARM64 ApiValidator** (the Universal-DDI check the WDK forces on ARM64 drivers) is **disabled
for ARM64 cross-builds from a non-ARM64 host** (`ApiValidator_Enable=false` in
`Directory.Build.props`, gated on `Platform==ARM64` + host≠ARM64). The WDK defaults it to the x86
ApiExtractor, which can't read an ARM64 PE and fails with exit 193; native ARM64 hosts and all x64
builds still run it, and Microsoft re-runs API validation on submission. Same rationale as the
InfVerif skip below.
**INF verification** (`infverif /h`, required by Partner Center since Apr 2025) runs at
**build time inside Visual Studio**. Command-line / CI builds skip the build-time task
+22 -8
View File
@@ -2,7 +2,7 @@
make-release.ps1 - Build AppSandbox, then conditionally sign + package.
Flow:
1. Build the solution (Release|x64) unless -NoBuild.
1. Build the solution (Release|$Platform) unless -NoBuild.
2. Check whether the App Sandbox LLC EV YubiKey is plugged in.
- NOT present -> SKIP signing, driver attestation, and ZIP.
The (test-signed) dev build is left as-is. Done.
@@ -14,15 +14,21 @@
So nothing is signed or zipped on an ordinary dev machine without the token.
USAGE:
.\make-release.ps1 # build + (if YubiKey) sign + attest + zip
.\make-release.ps1 -NoBuild # package the current bin\Release
.\make-release.ps1 # x64: build + (if YubiKey) sign + attest + zip
.\make-release.ps1 -Platform ARM64 # Windows on ARM (bin\Release-ARM64 -> *-win-arm64.zip)
.\make-release.ps1 -NoBuild # package the current bin\Release[-ARM64]
.\make-release.ps1 -SkipDrivers # sign app binaries + zip; leave drivers as-is
.\make-release.ps1 -ForceDriverSign # re-submit drivers for attestation even if cached
-Platform selects the bin\ output dir (ARM64 -> bin\Release-ARM64), the driver
attestation OS/signature codes (sign-drivers.ps1 -Platform rewrites the x64 config
at sign time), and the zip name. The AppSandboxPackage project passes
-Platform $(Platform) after a Release build.
#>
[CmdletBinding()]
param(
[string]$Configuration = 'Release',
[string]$Platform = 'x64',
[ValidateSet('x64','ARM64')][string]$Platform = 'x64',
[string]$OS = 'win', # OS token in the zip name; not passed to MSBuild
[string]$Version = '', # empty => read from Directory.Build.props (single source)
[switch]$NoBuild,
@@ -40,7 +46,10 @@ $env:ASB_SKIP_PACKAGE_PROJECT = '1'
$repo = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
$sln = Join-Path $repo 'AppSandbox.sln'
$bin = Join-Path $repo ("bin\{0}" -f $Configuration) # x64 -> bin\Release
# Output dir matches the .vcxproj OutDir convention: x64 -> bin\Release,
# ARM64 -> bin\Release-ARM64.
$binLeaf = if ($Platform -eq 'ARM64') { "$Configuration-ARM64" } else { $Configuration }
$bin = Join-Path $repo ("bin\{0}" -f $binLeaf)
$cfgPath = Join-Path $PSScriptRoot 'submission-config.json'
$cfg = if (Test-Path $cfgPath) { Get-Content $cfgPath -Raw | ConvertFrom-Json } else { $null }
@@ -84,6 +93,11 @@ function Get-AsbVersion([string]$repoRoot) {
# returns an error (never UI) when the specific card bearing it is absent.
# This is what tells our token apart from some unrelated card the user inserted.
if (-not ([System.Management.Automation.PSTypeName]'AsbScReader').Type) {
# Add-Type shells out to the .NET Framework C# compiler, which reads the LIB environment
# variable and fails the compile (CS1668, warning-as-error) on any entry that does not exist.
# The VS ARM64 build pushes a non-existent ...\atlmfc\lib\ARM64 onto LIB (the ATL/MFC ARM64
# libs aren't installed); csc needs nothing from LIB here, so drop missing entries first.
if ($env:LIB) { $env:LIB = (($env:LIB -split ';') | Where-Object { $_ -and (Test-Path $_) }) -join ';' }
Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
@@ -290,8 +304,8 @@ if (-not $SkipDrivers) {
if ($reusable) {
Write-Host "Reusing existing Microsoft-signed drivers (v$($asb.Full)) from drivers-signed - no resubmit."
} else {
Write-Host "Driver attestation signing (sign-drivers.ps1) - Microsoft round-trip..."
& (Join-Path $PSScriptRoot 'sign-drivers.ps1')
Write-Host "Driver attestation signing (sign-drivers.ps1 -Platform $Platform) - Microsoft round-trip..."
& (Join-Path $PSScriptRoot 'sign-drivers.ps1') -Platform $Platform
if ($LASTEXITCODE -ne 0) { throw "Driver attestation failed." }
}
# Deposit the MS-signed drivers into bin\Release\drivers (deposited .sys/.dll match the MS
@@ -360,7 +374,7 @@ $files | ForEach-Object { Write-Host (" " + $_.FullName.Substring($stage.Length
$mb = [math]::Round((($files | Measure-Object Length -Sum).Sum) / 1MB, 1)
Write-Host ("--- {0} files, {1} MB ---`n" -f $files.Count, $mb)
$zip = Join-Path $bin ("AppSandbox-{0}-{1}-{2}.zip" -f $Version, $OS, $Platform)
$zip = Join-Path $bin ("AppSandbox-{0}-{1}-{2}.zip" -f $Version, $OS, $Platform.ToLower())
if (Test-Path $zip) { Remove-Item $zip -Force }
# Flat layout: the items sit at the zip root (file.zip\AppSandbox.exe, file.zip\drivers\...),
# with the drivers\ / resources\ / web\ subfolders preserved - no extra parent folder.
+20 -1
View File
@@ -38,6 +38,7 @@
param(
[string]$ConfigPath = (Join-Path $PSScriptRoot 'submission-config.json'),
[string]$CredPath = (Join-Path $PSScriptRoot 'partner-center.local.json'),
[ValidateSet('x64','ARM64')][string]$Platform = 'x64', # build target arch
[switch]$NoCab,
[switch]$NoSign,
[switch]$SkipSubmit,
@@ -87,6 +88,11 @@ function Invoke-EvSign([string[]]$files, [string]$what) {
# returns an error (never UI) when the specific card bearing it is absent.
# This is what tells our token apart from some unrelated card the user inserted.
if (-not ([System.Management.Automation.PSTypeName]'AsbScReader').Type) {
# Add-Type shells out to the .NET Framework C# compiler, which reads the LIB environment
# variable and fails the compile (CS1668, warning-as-error) on any entry that does not exist.
# The VS ARM64 build pushes a non-existent ...\atlmfc\lib\ARM64 onto LIB (the ATL/MFC ARM64
# libs aren't installed); csc needs nothing from LIB here, so drop missing entries first.
if ($env:LIB) { $env:LIB = (($env:LIB -split ';') | Where-Object { $_ -and (Test-Path $_) }) -join ';' }
Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
@@ -401,6 +407,18 @@ function New-DriverCab($d, [string]$workDir) {
if (-not (Test-Path $ConfigPath)) { throw "Config not found: $ConfigPath" }
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json
# The config holds the x64 defaults. For an ARM64 build target, rewrite the
# arch-specific values (output dirs, inf2cat OS, requested signatures); on x64
# the config is used as-is.
if ($Platform -eq 'ARM64') {
$cfg.workDir = $cfg.workDir -replace 'bin\\Release(?=\\)', 'bin\Release-ARM64'
$cfg.outDir = $cfg.outDir -replace 'bin\\Release(?=\\)', 'bin\Release-ARM64'
$cfg.symbolDir = $cfg.symbolDir -replace '\\x64\\', '\ARM64\'
$cfg.inf2catOs = $cfg.inf2catOs -replace 'X64', 'ARM64'
$cfg.requestedSignatures = @($cfg.requestedSignatures | ForEach-Object { $_ -replace 'X64','ARM64' })
foreach ($d in $cfg.drivers) { $d.sourceDir = $d.sourceDir -replace 'bin\\Release(?=\\)', 'bin\Release-ARM64' }
}
$asb = Get-AsbVersion # version from Directory.Build.props (single source)
$workDir = Resolve-RelPath $cfg.workDir
$script:OutDir = Resolve-RelPath $cfg.outDir
@@ -483,7 +501,8 @@ $ver = $asb.Short
$pending = @()
foreach ($j in $jobs) {
$d = $j.Driver
$productDisplayName = "$($d.productName) $ver"
$archTag = if ($Platform -eq 'ARM64') { ' ARM64' } else { '' }
$productDisplayName = "$($d.productName)$archTag $ver"
Write-Host "`n=== $productDisplayName ==="
$product = Invoke-Api POST "$ApiBase/products" @{
+4 -2
View File
@@ -113,7 +113,8 @@
copy /Y "$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\AppSandboxVDD.dll" "$(SolutionDir)bin\$(Configuration)-ARM64\drivers\"
copy /Y "$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\AppSandboxVDD.inf" "$(SolutionDir)bin\$(Configuration)-ARM64\drivers\"
copy /Y "$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\appsandboxvdd.cat" "$(SolutionDir)bin\$(Configuration)-ARM64\drivers\AppSandboxVDD.cat"
powershell -NoProfile -Command "$sig = Get-AuthenticodeSignature '$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\appsandboxvdd.cat'; if ($sig.SignerCertificate) { [IO.File]::WriteAllBytes('$(SolutionDir)bin\$(Configuration)-ARM64\drivers\AppSandboxVDD.cer', $sig.SignerCertificate.RawData) }"</Command>
powershell -NoProfile -Command "$sig = Get-AuthenticodeSignature '$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\appsandboxvdd.cat'; if ($sig.SignerCertificate) { [IO.File]::WriteAllBytes('$(SolutionDir)bin\$(Configuration)-ARM64\drivers\AppSandboxVDD.cer', $sig.SignerCertificate.RawData) }"
copy /Y "$(SolutionDir)vendor\devcon\arm64\devcon.exe" "$(SolutionDir)bin\$(Configuration)-ARM64\drivers\"</Command>
<Message>Copying VDD driver files to bin\$(Configuration)-ARM64\drivers\...</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
@@ -134,7 +135,8 @@ copy /Y "$(SolutionDir)vendor\devcon\devcon.exe" "$(SolutionDir)bin\$(Configurat
copy /Y "$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\AppSandboxVDD.dll" "$(SolutionDir)bin\$(Configuration)-ARM64\drivers\"
copy /Y "$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\AppSandboxVDD.inf" "$(SolutionDir)bin\$(Configuration)-ARM64\drivers\"
copy /Y "$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\appsandboxvdd.cat" "$(SolutionDir)bin\$(Configuration)-ARM64\drivers\AppSandboxVDD.cat"
powershell -NoProfile -Command "$sig = Get-AuthenticodeSignature '$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\appsandboxvdd.cat'; if ($sig.SignerCertificate) { [IO.File]::WriteAllBytes('$(SolutionDir)bin\$(Configuration)-ARM64\drivers\AppSandboxVDD.cer', $sig.SignerCertificate.RawData) }"</Command>
powershell -NoProfile -Command "$sig = Get-AuthenticodeSignature '$(SolutionDir)ARM64\$(Configuration)\AppSandboxVDD\appsandboxvdd.cat'; if ($sig.SignerCertificate) { [IO.File]::WriteAllBytes('$(SolutionDir)bin\$(Configuration)-ARM64\drivers\AppSandboxVDD.cer', $sig.SignerCertificate.RawData) }"
copy /Y "$(SolutionDir)vendor\devcon\arm64\devcon.exe" "$(SolutionDir)bin\$(Configuration)-ARM64\drivers\"</Command>
<Message>Copying VDD driver files to bin\$(Configuration)-ARM64\drivers\...</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
BIN
View File
Binary file not shown.