Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

a quick implementation, adding "Open with Code" option to Windows 11 explorer's context menu #139640

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions build/win32/shell-extension-win11/build.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@echo off
:: build the handler dll and sparse package needed for context menu extension

:: Visual Studio 2019 Windows Desktop Development tools are required

set ARCH=X64
:: call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"

:: set ARCH=X86
:: call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars32.bat"

mkdir Release
mkdir Release\sparse-pkg

:: replace placeholders with real values

powershell -Command "(gc handler\menuhandler.cpp) -replace '@@ROOT_KEY@@', '*\\shell\\VSCode' | Out-File -encoding ASCII Release\menuhandler.cpp"

powershell -Command "(gc sparse-pkg\AppxManifest.xml) -replace '@@NAME@@', 'code-oss' -replace '@@APPNAME@@', 'Code - OSS' | Out-File -encoding ASCII Release\sparse-pkg\AppxManifest.xml"

:: dependency preparation

powershell -Command "iwr https://www.nuget.org/api/v2/package/Microsoft.Windows.ImplementationLibrary/1.0.201120.3 -OutFile Release\wil.zip; Expand-Archive -Force -LiteralPath Release\wil.zip Release\WilUnzipped; cp -Force -r Release\WilUnzipped\include\wil Release"

copy ..\..\..\resources\win32\code_150x150.png Release\sparse-pkg\code.png

:: Command lines as called by msbuild.exe in building the SparsePackages/PhotoStoreContextMenu demo

cl.exe /c /Zi /nologo /W3 /WX- /diagnostics:column /sdl /Oi /GL /O2 /Oy- /D WIN32 /D NDEBUG /D _WINDOWS /D _USRDLL /D _WINDLL /D _UNICODE /D UNICODE /Gm- /EHsc /MD /GS /Gy /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /permissive- /Fp"Release\menuhandler.pch" /Fo"Release\\" /Fd"Release\vc142.pdb" /external:W3 /Gd /TP /analyze- /FC /errorReport:queue "Release\menuhandler.cpp"

link.exe /ERRORREPORT:QUEUE /OUT:"Release\menuhandler.dll" /INCREMENTAL:NO /NOLOGO runtimeobject.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib shlwapi.lib /DEF:"handler\Source.def" /MANIFEST /MANIFESTUAC:NO /manifest:embed /PDB:"Release\menuhandler.pdb" /SUBSYSTEM:WINDOWS /OPT:REF /OPT:ICF /LTCG:incremental /LTCGOUT:"Release\menuhandler.iobj" /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"Release\menuhandler.lib" /MACHINE:%ARCH% /DLL "Release\menuhandler.obj"

MakeAppx.exe pack /d "Release\\sparse-pkg\\" /p "Release\code-sparse.appx" /nv

:: Sign the sparse package

MakeCert.exe /n "CN=localhost" /r /h 0 /eku "1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13" /e "12/31/2099" /sv "Release\Key.pvk" "Release\Key.cer"

Pvk2Pfx.exe /pvk "Release\Key.pvk" /spc "Release\Key.cer" /pfx "Release\Key.pfx"

SignTool.exe sign /fd SHA256 /a /f "Release\Key.pfx" "Release\code-sparse.appx"

:: These built files need to be copied to the installation package for setup.ps1 to use:
:: Release\menuhandler.dll, Release\Key.cer, Release\code-sparse.appx
5 changes: 5 additions & 0 deletions build/win32/shell-extension-win11/handler/Source.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LIBRARY
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllGetActivationFactory PRIVATE
162 changes: 162 additions & 0 deletions build/win32/shell-extension-win11/handler/menuhandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// menuhandler.cpp : Defines the entry point for the context menu DLL handler.

// This code is aggressively copied from
// https://github.com/microsoft/AppModelSamples/Samples/SparsePackages/PhotoStoreContextMenu/dllmain.cpp

#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <shlwapi.h>
#include <shobjidl_core.h>
#include <wrl/client.h>
#include <wrl/implements.h>
#include <wrl/module.h>
#include "wil\resource.h"
#include <sstream>
#include <string>
#include <vector>

using namespace Microsoft::WRL;

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

class ExplorerCommandBase : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand, IObjectWithSite> {
public:
virtual const wchar_t* Title() = 0;
virtual const EXPCMDFLAGS Flags() { return ECF_DEFAULT; }
virtual const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) { return ECS_ENABLED; }

// IExplorerCommand
IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name)
{
*name = nullptr;
auto title = wil::make_cotaskmem_string_nothrow(Title());
RETURN_IF_NULL_ALLOC(title);
*name = title.release();
return S_OK;
}
IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon)
{
wchar_t str_data[1028];
DWORD size = sizeof(str_data);
RETURN_IF_FAILED(RegGetValue(
HKEY_CLASSES_ROOT, L"@@ROOT_KEY@@", L"Icon",
RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, NULL, str_data, &size
));
return SHStrDup(str_data, icon);
}
IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip)
{
*infoTip = nullptr;
return E_NOTIMPL;
}
IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName)
{
*guidCommandName = GUID_NULL;
return S_OK;
}
IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* cmdState)
{
*cmdState = State(selection);
return S_OK;
}
IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept
try {
HWND parent = nullptr;
if (m_site) {
ComPtr<IOleWindow> oleWindow;
RETURN_IF_FAILED(m_site.As(&oleWindow));
RETURN_IF_FAILED(oleWindow->GetWindow(&parent));
}

if (selection) {
DWORD count;
RETURN_IF_FAILED(selection->GetCount(&count));

IShellItem* psi;
LPWSTR itemName;

wchar_t cmdline_buf[1028];
DWORD size = sizeof(cmdline_buf);
RETURN_IF_FAILED(RegGetValue(
HKEY_CLASSES_ROOT, L"@@ROOT_KEY@@\\command", L"",
RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, NULL, cmdline_buf, &size
));


for (DWORD i = 0; i < count; ++i) {
selection->GetItemAt(i, &psi);
RETURN_IF_FAILED(psi->GetDisplayName(SIGDN_FILESYSPATH, &itemName));

std::wstring cmdline{ cmdline_buf };
cmdline = cmdline.replace(cmdline.find(L"%1"), 2, itemName);

STARTUPINFO si = {};
PROCESS_INFORMATION pi = {};

CreateProcess(
nullptr, (LPWSTR)cmdline.c_str(),
nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi);
}
}

return S_OK;
}
CATCH_RETURN();

IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* flags)
{
*flags = Flags();
return S_OK;
}
IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands)
{
*enumCommands = nullptr;
return E_NOTIMPL;
}

// IObjectWithSite
IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept
{
m_site = site;
return S_OK;
}
IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept { return m_site.CopyTo(riid, site); }

protected:
ComPtr<IUnknown> m_site;
};

class __declspec(uuid("BF6EE3D9-EC02-4489-AD75-ACDED99BAB44")) ExplorerCommandHandler final : public ExplorerCommandBase {
public:
const wchar_t* Title() override { return L"Open with Code"; }
};

CoCreatableClass(ExplorerCommandHandler)
CoCreatableClassWrlCreatorMapInclude(ExplorerCommandHandler)

STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory)
{
return Module<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory);
}

STDAPI DllCanUnloadNow()
{
return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
}

STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ void** instance)
{
return Module<InProc>::GetModule().GetClassObject(rclsid, riid, instance);
}
7 changes: 7 additions & 0 deletions build/win32/shell-extension-win11/setup.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Setup the "Open with Code" Context Menu Item

$releasePath = (resolve-path Release)

Certutil.exe -user -addStore TrustedPeople ${releasePath}\Key.cer

Add-AppxPackage ${releasePath}\code-sparse.appx -ExternalLocation ${releasePath}
56 changes: 56 additions & 0 deletions build/win32/shell-extension-win11/sparse-pkg/AppxManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?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: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 uap10 com">
<Identity Name="@@NAME@@" ProcessorArchitecture="neutral" Publisher="CN=localhost" Version="1.0.0.0" />
<Properties>
<DisplayName>@@APPNAME@@</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>code.png</Logo>
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19000.0" MaxVersionTested="10.0.19000.0" />
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources"/>
</Capabilities>
<Applications>
<Application Id="Code" Executable="@@NAME@@.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
<uap:VisualElements AppListEntry="none" DisplayName="@@APPNAME@@" Description="@@APPNAME@@" BackgroundColor="transparent" Square150x150Logo="code.png" Square44x44Logo="code.png">
</uap:VisualElements>
<Extensions>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="OpenWithCode" Clsid="BF6EE3D9-EC02-4489-AD75-ACDED99BAB44" />
</desktop5:ItemType>
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="OpenWithCode" Clsid="BF6EE3D9-EC02-4489-AD75-ACDED99BAB44" />
</desktop5:ItemType>
<desktop5:ItemType Type="*">
<desktop5:Verb Id="OpenWithCode" Clsid="BF6EE3D9-EC02-4489-AD75-ACDED99BAB44" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="Context menu verb handler">
<com:Class Id="BF6EE3D9-EC02-4489-AD75-ACDED99BAB44" Path="menuhandler.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
</Package>