feat: support device permissions via package identity (#257741)

* feat: support device permissions via package identity

* chore: update explorer dll checksums

* chore: cleanup appx preparation

* chore: avoid cross-device link error

* chore: remove appx installation gate

* chore: rm duplicate appx installation checks

* chore: extract package full name from Get-AppxPackage

* chore: fix remove-appxpackage command
This commit is contained in:
Robo 2025-07-26 04:57:36 +09:00 committed by GitHub
parent 1bd100374b
commit f5e06c148d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 196 additions and 61 deletions

View File

@ -175,9 +175,9 @@ steps:
displayName: Transpile client and extensions
- ${{ else }}:
- ${{ if and(ne(parameters.VSCODE_CIBUILD, true), eq(parameters.VSCODE_QUALITY, 'insider')) }}:
- powershell: node build/win32/explorer-appx-fetcher .build/win32/appx
displayName: Download Explorer Sparse Package
- ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'exploration')) }}:
- powershell: node build/win32/explorer-dll-fetcher .build/win32/appx
displayName: Download Explorer dll
- powershell: |
. build/azure-pipelines/win32/exec.ps1
@ -190,6 +190,23 @@ steps:
GITHUB_TOKEN: "$(github-distro-mixin-password)"
displayName: Build client
# Note: the appx prepare step has to follow Build client step since build step replaces the template
# strings in the raw manifest file at resources/win32/appx/AppxManifest.xml and places it under
# <build-out-dir>/appx/manifest, we need a separate step to prepare the appx package with the
# final contents. In our case only the manifest file is bundled into the appx package.
- ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'exploration')) }}:
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
# Add Windows SDK to path
$sdk = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64"
$env:PATH = "$sdk;$env:PATH"
$AppxName = if ('$(VSCODE_QUALITY)' -eq 'stable') { 'code' } else { 'code_insider' }
makeappx pack /d "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/manifest" /p "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/${AppxName}_$(VSCODE_ARCH).appx" /nv
# Remove the raw manifest folder
Remove-Item -Path "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/manifest" -Recurse -Force
displayName: Prepare appx package
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"

View File

@ -0,0 +1,4 @@
11b36db4f244693381e52316261ce61678286f6bdfe2614c6352f6fecf3f060d code_explorer_command_arm64.dll
bfab3719038ca46bcd8afb9249a00f851dd08aa3cc8d13d01a917111a2a6d7c2 code_explorer_command_x64.dll
b5cd79c1e91390bdeefaf35cc5c62a6022220832e145781e5609913fac706ad9 code_insider_explorer_command_arm64.dll
f04335cc6fbe8425bd5516e6acbfa05ca706fd7566799a1e22fca1344c25351f code_insider_explorer_command_x64.dll

View File

@ -404,8 +404,21 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
result = es.merge(result, gulp.src('.build/policies/win32/**', { base: '.build/policies/win32' })
.pipe(rename(f => f.dirname = `policies/${f.dirname}`)));
if (quality === 'insider') {
if (quality !== 'exploration') {
result = es.merge(result, gulp.src('.build/win32/appx/**', { base: '.build/win32' }));
const rawVersion = version.replace(/-\w+$/, '').split('.');
const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${rawVersion[2]}`;
result = es.merge(result, gulp.src('resources/win32/appx/AppxManifest.xml', { base: '.' })
.pipe(replace('@@AppxPackageName@@', product.win32AppUserModelId))
.pipe(replace('@@AppxPackageVersion@@', appxVersion))
.pipe(replace('@@AppxPackageDisplayName@@', product.nameLong))
.pipe(replace('@@AppxPackageDescription@@', product.win32NameVersion))
.pipe(replace('@@ApplicationIdShort@@', product.win32RegValueName))
.pipe(replace('@@ApplicationExe@@', product.nameShort + '.exe'))
.pipe(replace('@@FileExplorerContextMenuID@@', quality === 'stable' ? 'OpenWithCode' : 'OpenWithCodeInsiders'))
.pipe(replace('@@FileExplorerContextMenuCLSID@@', product.win32ContextMenu[arch].clsid))
.pipe(replace('@@FileExplorerContextMenuDLL@@', `${quality === 'stable' ? 'code' : 'code_insider'}_explorer_command_${arch}.dll`))
.pipe(rename(f => f.dirname = `appx/manifest`)));
}
} else if (platform === 'linux') {
result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' })

View File

@ -112,10 +112,9 @@ function buildWin32Setup(arch, target) {
Quality: quality
};
if (quality === 'insider') {
definitions['AppxPackage'] = `code_insiders_explorer_${arch}.appx`;
definitions['AppxPackageFullname'] = `Microsoft.${product.win32RegValueName}_1.0.0.0_neutral__8wekyb3d8bbwe`;
definitions['AppxPackageName'] = `Microsoft.${product.win32RegValueName}`;
if (quality !== 'exploration') {
definitions['AppxPackage'] = `${quality === 'stable' ? 'code' : 'code_insider'}_${arch}.appx`;
definitions['AppxPackageName'] = `${product.win32AppUserModelId}`;
}
packageInnoSetup(issPath, { definitions }, cb);

View File

@ -94,8 +94,8 @@ Name: "{app}"; AfterInstall: DisableAppDirInheritance
Source: "*"; Excludes: "\CodeSignSummary*.md,\tools,\tools\*,\appx,\appx\*,\resources\app\product.json"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "tools\*"; DestDir: "{app}\tools"; Flags: ignoreversion
Source: "{#ProductJsonPath}"; DestDir: "{code:GetDestDir}\resources\app"; Flags: ignoreversion
#ifdef AppxPackageFullname
Source: "appx\*"; DestDir: "{app}\appx"; BeforeInstall: RemoveAppxPackage; AfterInstall: AddAppxPackage; Flags: ignoreversion; Check: IsWindows11OrLater and QualityIsInsiders
#ifdef AppxPackageName
Source: "appx\*"; DestDir: "{app}\appx"; BeforeInstall: RemoveAppxPackage; AfterInstall: AddAppxPackage; Flags: ignoreversion; Check: IsWindows11OrLater
#endif
[Icons]
@ -1466,26 +1466,26 @@ begin
Result := False;
end;
#ifdef AppxPackageFullname
#ifdef AppxPackageName
var
Line: String;
AppxPackageFullname: String;
procedure ExecAndGetFirstLineLog(const S: String; const Error, FirstLine: Boolean);
begin
if not Error and (Line = '') and (Trim(S) <> '') then
Line := S;
if not Error and (AppxPackageFullname = '') and (Trim(S) <> '') then
AppxPackageFullname := S;
Log(S);
end;
function AppxPackageInstalled(var ResultCode: Integer): Boolean;
begin
Line := '';
AppxPackageFullname := '';
try
ExecAndLogOutput('powershell.exe', '-Command ' + AddQuotes('Get-AppxPackage -Name ''{#AppxPackageName}'''), '', SW_HIDE, ewWaitUntilTerminated, ResultCode, @ExecAndGetFirstLineLog);
ExecAndLogOutput('powershell.exe', '-Command ' + AddQuotes('Get-AppxPackage -Name ''{#AppxPackageName}'' | Select-Object -ExpandProperty PackageFullName'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode, @ExecAndGetFirstLineLog);
except
Log(GetExceptionMessage);
end;
if (Line <> '') then
if (AppxPackageFullname <> '') then
Result := True
else
Result := False
@ -1495,7 +1495,7 @@ procedure AddAppxPackage();
var
AddAppxPackageResultCode: Integer;
begin
if not AppxPackageInstalled(AddAppxPackageResultCode) and WizardIsTaskSelected('addcontextmenufiles') then begin
if not AppxPackageInstalled(AddAppxPackageResultCode) then begin
ShellExec('', 'powershell.exe', '-Command ' + AddQuotes('Add-AppxPackage -Path ''' + ExpandConstant('{app}\appx\{#AppxPackage}') + ''' -ExternalLocation ''' + ExpandConstant('{app}\appx') + ''''), '', SW_HIDE, ewWaitUntilTerminated, AddAppxPackageResultCode);
end;
end;
@ -1505,7 +1505,7 @@ var
RemoveAppxPackageResultCode: Integer;
begin
if AppxPackageInstalled(RemoveAppxPackageResultCode) then begin
ShellExec('', 'powershell.exe', '-Command ' + AddQuotes('Remove-AppxPackage -Package ''{#AppxPackageFullname}'''), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode);
ShellExec('', 'powershell.exe', '-Command ' + AddQuotes('Remove-AppxPackage -Package ''' + AppxPackageFullname + ''''), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode);
end;
end;
#endif
@ -1517,7 +1517,7 @@ var
begin
if CurStep = ssPostInstall then
begin
#ifdef AppxPackageFullname
#ifdef AppxPackageName
if not WizardIsTaskSelected('addcontextmenufiles') then begin
RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\{#RegValueName}ContextMenu');
end else begin
@ -1606,15 +1606,12 @@ var
Parts: TArrayOfString;
NewPath: string;
i: Integer;
ResultCode: Integer;
begin
if not CurUninstallStep = usUninstall then begin
exit;
end;
#ifdef AppxPackageFullname
if AppxPackageInstalled(ResultCode) then begin
RemoveAppxPackage();
end;
#ifdef AppxPackageName
RemoveAppxPackage();
#endif
if not RegQueryStringValue({#EnvironmentRootKey}, '{#EnvironmentKey}', 'Path', Path)
then begin

View File

@ -7,45 +7,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadExplorerAppx = downloadExplorerAppx;
exports.downloadExplorerDll = downloadExplorerDll;
const fs_1 = __importDefault(require("fs"));
const debug_1 = __importDefault(require("debug"));
const extract_zip_1 = __importDefault(require("extract-zip"));
const path_1 = __importDefault(require("path"));
const get_1 = require("@electron/get");
const root = path_1.default.dirname(path_1.default.dirname(__dirname));
const d = (0, debug_1.default)('explorer-appx-fetcher');
async function downloadExplorerAppx(outDir, quality = 'stable', targetArch = 'x64') {
const fileNamePrefix = quality === 'insider' ? 'code_insiders' : 'code';
const fileName = `${fileNamePrefix}_explorer_${targetArch}.zip`;
if (await fs_1.default.existsSync(path_1.default.resolve(outDir, 'resources.pri'))) {
return;
}
const product_json_1 = __importDefault(require("../../product.json"));
const d = (0, debug_1.default)('explorer-dll-fetcher');
async function downloadExplorerDll(outDir, quality = 'stable', targetArch = 'x64') {
const fileNamePrefix = quality === 'insider' ? 'code_insider' : 'code';
const fileName = `${fileNamePrefix}_explorer_command_${targetArch}.dll`;
if (!await fs_1.default.existsSync(outDir)) {
await fs_1.default.mkdirSync(outDir, { recursive: true });
}
// Read and parse checksums file
const checksumsFilePath = path_1.default.join(path_1.default.dirname(__dirname), 'checksums', 'explorer-dll.txt');
const checksumsContent = fs_1.default.readFileSync(checksumsFilePath, 'utf8');
const checksums = {};
checksumsContent.split('\n').forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const [checksum, filename] = trimmedLine.split(/\s+/);
if (checksum && filename) {
checksums[filename] = checksum;
}
}
});
d(`downloading ${fileName}`);
const artifact = await (0, get_1.downloadArtifact)({
isGeneric: true,
version: '3.0.4',
version: 'v4.0.0-350164',
artifactName: fileName,
unsafelyDisableChecksums: true,
checksums,
mirrorOptions: {
mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/',
customDir: '3.0.4',
customDir: 'v4.0.0-350164',
customFilename: fileName
}
});
d(`unpacking from ${fileName}`);
await (0, extract_zip_1.default)(artifact, { dir: fs_1.default.realpathSync(outDir) });
d(`moving ${artifact} to ${outDir}`);
await fs_1.default.copyFileSync(artifact, path_1.default.join(outDir, fileName));
}
async function main(outputDir) {
const arch = process.env['VSCODE_ARCH'];
if (!outputDir) {
throw new Error('Required build env not set');
}
const product = JSON.parse(fs_1.default.readFileSync(path_1.default.join(root, 'product.json'), 'utf8'));
await downloadExplorerAppx(outputDir, product.quality, arch);
await downloadExplorerDll(outputDir, product_json_1.default.quality, arch);
}
if (require.main === module) {
main(process.argv[2]).catch(err => {
@ -53,4 +61,4 @@ if (require.main === module) {
process.exit(1);
});
}
//# sourceMappingURL=explorer-appx-fetcher.js.map
//# sourceMappingURL=explorer-dll-fetcher.js.map

View File

@ -7,41 +7,50 @@
import fs from 'fs';
import debug from 'debug';
import extract from 'extract-zip';
import path from 'path';
import { downloadArtifact } from '@electron/get';
import product from '../../product.json';
const root = path.dirname(path.dirname(__dirname));
const d = debug('explorer-dll-fetcher');
const d = debug('explorer-appx-fetcher');
export async function downloadExplorerAppx(outDir: string, quality: string = 'stable', targetArch: string = 'x64'): Promise<void> {
const fileNamePrefix = quality === 'insider' ? 'code_insiders' : 'code';
const fileName = `${fileNamePrefix}_explorer_${targetArch}.zip`;
if (await fs.existsSync(path.resolve(outDir, 'resources.pri'))) {
return;
}
export async function downloadExplorerDll(outDir: string, quality: string = 'stable', targetArch: string = 'x64'): Promise<void> {
const fileNamePrefix = quality === 'insider' ? 'code_insider' : 'code';
const fileName = `${fileNamePrefix}_explorer_command_${targetArch}.dll`;
if (!await fs.existsSync(outDir)) {
await fs.mkdirSync(outDir, { recursive: true });
}
// Read and parse checksums file
const checksumsFilePath = path.join(path.dirname(__dirname), 'checksums', 'explorer-dll.txt');
const checksumsContent = fs.readFileSync(checksumsFilePath, 'utf8');
const checksums: Record<string, string> = {};
checksumsContent.split('\n').forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const [checksum, filename] = trimmedLine.split(/\s+/);
if (checksum && filename) {
checksums[filename] = checksum;
}
}
});
d(`downloading ${fileName}`);
const artifact = await downloadArtifact({
isGeneric: true,
version: '3.0.4',
version: 'v4.0.0-350164',
artifactName: fileName,
unsafelyDisableChecksums: true,
checksums,
mirrorOptions: {
mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/',
customDir: '3.0.4',
customDir: 'v4.0.0-350164',
customFilename: fileName
}
});
d(`unpacking from ${fileName}`);
await extract(artifact, { dir: fs.realpathSync(outDir) });
d(`moving ${artifact} to ${outDir}`);
await fs.copyFileSync(artifact, path.join(outDir, fileName));
}
async function main(outputDir?: string): Promise<void> {
@ -51,8 +60,7 @@ async function main(outputDir?: string): Promise<void> {
throw new Error('Required build env not set');
}
const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8'));
await downloadExplorerAppx(outputDir, (product as any).quality, arch);
await downloadExplorerDll(outputDir, (product as any).quality, arch);
}
if (require.main === module) {

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:desktop10="http://schemas.microsoft.com/appx/manifest/desktop/windows10/10"
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
IgnorableNamespaces="uap uap2 uap3 rescap desktop desktop4 desktop5 desktop6 desktop10 uap10 com">
<Identity
Name="@@AppxPackageName@@"
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
Version="@@AppxPackageVersion@@"
ProcessorArchitecture="neutral" />
<Properties>
<DisplayName>@@AppxPackageDisplayName@@</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>resources\app\resources\win32\code_150x150.png</Logo>
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
<desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>
</Properties>
<Resources>
<Resource Language="en-us" />
<Resource Language="es-es" />
<Resource Language="de-de" />
<Resource Language="fr-fr" />
<Resource Language="hu-hu" />
<Resource Language="it-it" />
<Resource Language="ja-jp" />
<Resource Language="ko-kr" />
<Resource Language="pt-br" />
<Resource Language="ru-ru" />
<Resource Language="tr-tr" />
<Resource Language="zh-cn" />
<Resource Language="zh-tw" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources"/>
<DeviceCapability Name="microphone"/>
</Capabilities>
<Applications>
<Application Id="@@ApplicationIdShort@@"
Executable="@@ApplicationExe@@"
uap10:TrustLevel="mediumIL"
uap10:RuntimeBehavior="win32App">
<uap:VisualElements
AppListEntry="none"
DisplayName="@@AppxPackageDisplayName@@"
Description="@@AppxPackageDescription@@"
BackgroundColor="transparent"
Square150x150Logo="resources\app\resources\win32\code_150x150.png"
Square44x44Logo="resources\app\resources\win32\code_70x70.png">
</uap:VisualElements>
<Extensions>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="@@FileExplorerContextMenuID@@" Clsid="@@FileExplorerContextMenuCLSID@@" />
</desktop5:ItemType>
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="@@FileExplorerContextMenuID@@" Clsid="@@FileExplorerContextMenuCLSID@@" />
</desktop5:ItemType>
<desktop5:ItemType Type="*">
<desktop5:Verb Id="@@FileExplorerContextMenuID@@" Clsid="@@FileExplorerContextMenuCLSID@@" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="@@AppxPackageDisplayName@@">
<com:Class Id="@@FileExplorerContextMenuCLSID@@" Path="@@FileExplorerContextMenuDLL@@" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
</Package>