Context menu ו-file association ב-Windows 11 — IExplorerCommand, MSIX ו-sparse package
· עודכן בתאריך: · Go Komura · Windows, shell extension, context menu, file association, COM, Windows 11, File Explorer, MSIX, פיתוח Windows
היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 20 Aug 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22176262)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). Context menu ו-file association ב-Windows 11 — IExplorerCommand, MSIX ו-sparse package. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22176262 https://comcomponent.com/he/blog/windows-shell-integration-context-menu-file-association/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22176262
- DOI (הגרסה הזו)
- 10.5281/zenodo.22176263
קיבלתי פנייה ש«החלפנו את המחשבים ל-Windows 11, וה-context menu של האפליקציה שבניתם לנו לפני שנים נעלם». בהקשבה מדויקת יותר, הוא לא נעלם. לוחצים לחיצה ימנית על קובץ, בוחרים “הצג אפשרויות נוספות” בתחתית התפריט, והתפריט המוכר מופיע כמו תמיד. כלומר פריטי התפריט של האפליקציה הוסתרו לחיצה אחת פנימה. מהשטח שומעים «לחיצה אחת נוספת» ו«פניות על כך שלא מוצאים את הפריט התרבו».
זו לא תקלה ולא טעות הגדרה; זה שינוי עיצוב של Windows 11. ה-context menu של File Explorer הפך למבנה דו-שכבתי, ישן וחדש, והתנאים להעלות פריט לתפריט החדש שונים לגמרי ממה שהיה.
בינתיים מנגנון file association ו-shell extension מתחתיו עדיין עולם COM ו-Registry ישן. מפתח extension מצביע על ProgID, ה-verb של ה-ProgID מחזיק command line, ו-extension מורכב יותר רץ כ-in-process COM server (DLL) שנטען ל-Explorer — המבנה הזה לא השתנה יותר מעשרים שנה. אם לא מכירים גם את היסוד שלא השתנה וגם את התפריט ש-Windows 11 פיצלה לשניים, אי אפשר לבודד «התפריט לא מופיע», «הוסתר» או «מופיע פעמיים».
המאמר מיועד לאנשי IT בעסקים קטנים ובינוניים ולמפתחי Windows שמטפלים באפליקציות עסקיות. הוא קושר בתמונה אחת את המבנה התלת-שלבי של file association, מגבלות של shell extension קלאסי, איך לכוון ל-context menu החדש של Windows 11, ורישום installer, cleanup ו-troubleshooting.
1. קודם כל, המסקנות
- היסוד של context menu ושל file association הוא מבנה Registry תלת-שלבי: מפתח extension → ProgID → verb. מפתח ה-extension הוא pointer ל-ProgID, ה-ProgID הוא האובייקט עצמו, ו-
shell\<verb>\commandתחתיו מחזיק את ה-command line.1 - HKEY_CLASSES_ROOT (HKCR) אינה hive עצמאית; היא merged view של HKLM\Software\Classes ו-HKCU\Software\Classes. רישום לכל המשתמשים כותבים ל-HKLM, רישום לפי משתמש ל-HKCU, ו-HKCR מתייחסים אליו כקריאה בלבד.2
- Default app (זו שנפתחת ב-double-click) מיועדת להיבחר בידי המשתמש, ותוכנית לא יכולה לגנוב אותה. ה-OS מגן על בחירת המשתמש; מה ש-installer יכול לעשות הוא להירשם כמועמד.3
- Shell extension קלאסי הוא in-process COM DLL שנטען ל-Explorer. crash או delay ב-extension מתפשטים ל-Explorer כולו (ולאפליקציות אחרות שמשתמשות ב-shell); בסביבת 64-bit נדרש DLL של 64-bit; ומימוש ב-managed code אינו נתמך.45
- ב-Windows 11 ה-context menu התפצל לשניים. הפקודות היחידות שמופיעות בתפריט החדש הן אלה שנרשמו עם IExplorerCommand ועם package identity; הרחבות IContextMenu קלאסיות עוברות לתפריט הישן תחת «הצג אפשרויות נוספות» (Shift+F10).67
- המסלול הרשמי להעלות command מותאם לתפריט החדש הוא לרשום native DLL שמממש IExplorerCommand ב-MSIX manifest (desktop4:FileExplorerContextMenus). אפליקציה שאינה יכולה לעבור ל-MSIX יכולה לקבל identity בלבד עם sparse package (MSIX עם external location).78
- אם כל מה שרוצים הוא «לפתוח עם האפליקציה הזו», file association ו-static verb עדיין מספיקים. אין צורך ב-DLL של shell extension, ו-Microsoft עצמה כותבת במפורש «בחרו בשיטה הפשוטה ביותר שעומדת בדרישות (static verb)».9
- אחרי רישום או שינוי מודיעים ב-SHChangeNotify(SHCNE_ASSOCCHANGED); ב-uninstall מוחקים את ה-ProgID אך לא מוחקים את ערך ברירת המחדל של מפתח ה-extension — זה המדריך הרשמי. shell integration כולל גם את עיצוב ה-cleanup.110
במשפט אחד: עולם ה-file association וה-verbs לא השתנה; רק אופן הצגת התפריט התפצל לשניים ב-Windows 11. להלן עולים מהיסוד.
2. איך עובד file association — המבנה התלת-שלבי מפתח extension → ProgID → verb
2.1. קריאת המבנה התלת-שלבי מתוך דוגמה אחת
מה שקורה ב-double-click על קובץ עם extension נתון נקבע בשלוש שכבות של מפתחות Registry.1
HKEY_CLASSES_ROOT
.kmrpt ← (1) מפתח ה-extension
(Default) = KomuraSoft.Report.1 ← pointer שרק נותן שם ל-ProgID
OpenWithProgids
KomuraSoft.Report.1 ← מועמד תחת «פתיחה באמצעות»
KomuraSoft.Report.1 ← (2) ProgID (האובייקט של ה-association)
(Default) = Komura Report document
DefaultIcon
(Default) = "C:\Program Files\KomuraSoft\Report.exe",0
shell ← (3) רשימת verbs
open
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" "%1"
- (1) מפתח ה-extension (
.kmrpt) מצביע רק על שם ProgID כערך ברירת המחדל שלו. כתיבת command כאן ישירות היא טעות. - (2) ה-ProgID (
KomuraSoft.Report.1) הוא האובייקט של ה-association; הוא מחזיק את display name, האייקון ורשימת ה-verbs. - (3) verb הוא פעולה כמו «פתיחה» או «הדפסה», וערך ברירת המחדל של
shell\open\commandהוא ה-command line שמופעל בפועל.
ההפרדה הזו היא הסיבה שאפשר להצביע כמה extensions (למשל .kmrpt ו-.kmrpt-file) לאותו ProgID, או להחליף את ה-ProgID בשדרוג האפליקציה.
flowchart TB
accTitle: המבנה התלת-שלבי של file association
accDescr: מפתח ה-extension הוא pointer שערך ברירת המחדל שלו נותן שם ל-ProgID; ה-ProgID הוא האובייקט שמחזיק display name, אייקון ורשימת verbs; וערך ברירת המחדל של command תחת ה-verb הוא ה-command line שמופעל בפועל
ext["מפתח extension .kmrpt"] -->|מצביע ל-ProgID כברירת מחדל| pid["ProgID KomuraSoft.Report.1"]
pid --> vb["verb (open ואחרים תחת shell)"]
vb --> cmd["ערך ברירת המחדל של command"]
cmd --> exe["Report.exe מופעל"]
pid -.-> attr["מחזיק גם display name ו-DefaultIcon"]
איור 1: מפתח ה-extension הוא pointer, ה-ProgID הוא האובייקט, ו-command של ה-verb הוא ה-command line שמופעל בפועל.
2.2. HKCR היא «merged view» — לאן כותבים משנה את המשמעות
הדוגמה למעלה מוצגת תחת HKEY_CLASSES_ROOT (HKCR), אבל HKCR אינה מיקום אחסון פיזי; היא merged view של HKLM\Software\Classes ו-HKCU\Software\Classes. אם אותו מפתח קיים בשניהם, צד HKCU מנצח.2
flowchart TB
accTitle: HKCR היא merged view
accDescr: HKCR היא Classes של HKLM ושל HKCU מונחות זו על זו; אם אותו מפתח קיים בשניהם HKCU מנצחת; כותבים רישום ל-HKLM או ל-HKCU במפורש ומתייחסים ל-HKCR כקריאה בלבד
hklm["HKLM\\Software\\Classes (כל המשתמשים)"] --> hkcr["HKCR (merged view)"]
hkcu["HKCU\\Software\\Classes (לפי משתמש)"] --> hkcr
hkcu -.-> win["אם אותו מפתח קיים, HKCU מנצחת"]
hkcr -.-> ro["מתייחסים אליה כקריאה בלבד (לאישור)"]
איור 2: HKCR היא איך נראות Classes של HKLM ושל HKCU יחד; תמיד מציינים אחד מהם כיעד כתיבה.
| יעד כתיבה | משמעות | הרשאות נדרשות |
|---|---|---|
HKLM\Software\Classes |
רישום משותף לכל המשתמשים | Administrator |
HKCU\Software\Classes |
רישום למשתמש הזה בלבד | אין |
כתיבה ישירה ל-HKCR |
מנותבת לפי היכן כבר חי המפתח הקיים | תלוי |
בפועל, הפיצול הבטוח הוא תמיד לכתוב רישום ל-HKLM או ל-HKCU במפורש, ולהתייחס ל-HKCR כקריאה בלבד (לאישור). גם היחס ל-WOW64 Registry redirect ראוי לסידור. נתוני association ישירות תחת HKLM\Software\Classes כמו מפתחות extension ו-ProgID משותפים בין תצוגות ה-Registry 32-bit ו-64-bit מאז Windows 7, כך ש-installer 32-bit שכותב אותם אינו בורח לצד Wow6432Node. חלק ממפתחות-המשנה של רישום COM כמו Classes\CLSID, לעומת זאת, מופנים, וכשרושמים shell extension (in-process COM) פיצול הכתיבה 32 / 64-bit חשוב. פרטים ב-“Registry 32-bit/64-bit Redirection and Virtualization Pitfalls — Wow6432Node and the "The Value I Wrote Isn’t There" Problem”.
2.3. רישום מצד האפליקציה — App Paths, Applications, RegisteredApplications
יש גם שלושה סוגי רישום מצד האפליקציה, שמתאימים לצד הקובץ (extension ו-ProgID).11
- App Paths (
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths): רישום שמאפשר ל-ShellExecuteExלהפעיל לפי שם קובץ ההפעלה בלבד. Microsoft ממליצה על כך כי אין צורך לזהם את משתנה הסביבה PATH. - Applications (
HKCR\Applications\<app.exe>): מגדיר את דרך הפתיחה כברירת מחדל כשמוסרים קובץ שרירותי תחת «פתיחה באמצעות», ואת display name של האפליקציה (FriendlyAppName). - RegisteredApplications + Capabilities: מכריז על ה-extensions וסוגי MIME שהאפליקציה יכולה לטפל בהם, והוא הרישום שגורם לה להופיע כמועמדת בדף הגדרות default apps של Windows.
רוב הפניות מסוג «האפליקציה שלנו לא מופיעה ברשימת default apps» הן מקרים שבהם נרשם ה-ProgID והושמט רישום Capabilities הזה.
flowchart TB
accTitle: שלושת סוגי הרישום מצד האפליקציה
accDescr: רישום מצד האפליקציה יש לו שלושה סוגים — App Paths, Applications ו-RegisteredApplications — האחראים בהתאמה להפעלה לפי שם קובץ בלבד, לדרך הפתיחה כברירת מחדל תחת פתיחה באמצעות, ולהופעה בדף default apps
app["רישום מצד האפליקציה"] --> ap["App Paths"]
app --> apps["Applications"]
app --> ra["RegisteredApplications"]
ap --> r1["הפעלה לפי שם קובץ בלבד"]
apps --> r2["ברירת מחדל תחת פתיחה באמצעות"]
ra --> r3["מופיע בדף default apps"]
r3 -.-> cap["נדרשת הכרזת Capabilities"]
איור 3: יש שלושה סוגי רישום מצד האפליקציה, והופעה כמועמדת ל-default apps דורשת רישום Capabilities.
2.4. Default app שייך למשתמש — הגנת UserChoice
כתיבת ProgID כערך ברירת המחדל של מפתח ה-extension אינה הופכת אותו לבדה ל-default app. תוצאת בחירה מפורשת של המשתמש תחת «פתיחה באמצעות» וכדומה נשמרת ב-HKCU\...\Explorer\FileExts\<extension>\UserChoice, ופתרון ה-association מעדיף את הצד ההוא.
והנקודה החשובה היא שWindows אינה תומכת בשינוי תכנותי של default app. הגדרות default app מיועדות להיעשות בידי המשתמש דרך Settings של המערכת; נתוני UserChoice הם obfuscated, ו-filter driver (UCPD.sys) חוסם כתיבה מאפליקציות. בסביבה מנוהלת, Group Policy / MDM היא האמצעי הרשמי.3
זה שכלים כמו SetUserFTA, ש«מחקים את ה-hash וכותבים מחדש», היו בשימוש הוא הצד השני של ההגנה הזו. מה שצריך לשים ב-installer של אפליקציה פנימית אינו גניבת ה-default, אלא השלושה (א) רישום נכון של ה-ProgID וה-verbs, (ב) הוספת עצמכם ל-OpenWithProgIds, ו-(ג) במידת הצורך, הפניה לדף ההגדרות.
flowchart TB
accTitle: פתרון default app והגנת UserChoice
accDescr: תוצאת בחירה מפורשת של המשתמש נשמרת ב-UserChoice ומועדפת בפתרון ה-association; UCPD.sys חוסם כתיבה מחדש מאפליקציות, ולכן מה ש-installer יכול לעשות הוא להירשם כמועמד ולהפנות לדף ההגדרות
uc["UserChoice (בחירת המשתמש)"] -->|מועדף| res["פתרון association"]
ext["ערך ברירת המחדל של מפתח ה-extension"] --> res
wr["כתיבה מחדש מאפליקציה"] -.->|UCPD.sys חוסם| uc
res ~~~ inst["עבודת ה-installer"]
inst --> a1["רישום ה-ProgID וה-verbs"]
inst --> a2["הוספה ל-OpenWithProgIds"]
inst --> a3["הפניה לדף ההגדרות"]
איור 4: פתרון ה-association מעדיף את בחירת המשתמש (UserChoice), וה-OS מגן עליה מכתיבה מחדש של אפליקציות.
3. Verbs שאינם «open» — print, edit, runas ו-custom verbs
Verb אינו רק open. verbs תקניים שה-OS מכיר את משמעותם כוללים גם edit, print, play ו-preview מלבד open, ו-verb תקני מקבל אוטומטית display name שעוקב אחרי locale של ה-OS. ה-verb כברירת מחדל ב-double-click נקבע בסדר: ערך ברירת המחדל של מפתח shell → ה-verb הראשון ב-Registry → open → openwith.12
flowchart TB
accTitle: הסדר שבו נקבע ה-verb כברירת מחדל
accDescr: ה-verb כברירת מחדל ב-double-click הוא הראשון שנמצא בסדר ערך ברירת המחדל של מפתח shell, ה-verb הראשון ב-Registry, open, openwith
s1["ערך ברירת המחדל של מפתח shell"] -->|אם אין| s2["ה-verb הראשון ב-Registry"]
s2 -->|אם אין| s3["open"]
s3 -->|אם אין| s4["openwith"]
איור 5: ה-verb כברירת מחדל ב-double-click הוא הראשון שנמצא בסדר הזה.
כשרוצים להוסיף פעולה משלכם, רושמים custom verb.
KomuraSoft.Report.1
shell
open
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" "%1"
print
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" /print "%1"
verify ← custom verb
(Default) = Verify report (&V) ← display name בתפריט
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" /verify "%1"
שלוש עובדות קטנות שכדאי לדעת.
- רושמים verb בשם runas ומגדירים הפעלה elevated ששקולה ל-«הפעל כמנהל», והוא משמש גם כש-API ממשפחת
ShellExecuteמצייןrunas. - שמים ערך ריק בשם
Extendedעל מפתח ה-verb והוא הופך לextended verb שמוצג רק ב-Shift+לחיצה ימנית. נוח להסתרת פעולה מסוכנת שנדיר להשתמש בה.12 - לחלק מ-associations של אפליקציות ישנות עדיין יש תצורה ששולחת מסמך ל-process קיים עם DDE (מפתח
ddeexec), אבל הפעלת verb דרך DDE היא כבר מורשת Deprecated. אין סיבה לכתוב אותה מחדש.12
תאונה נוספת שקורה הרבה היא ציטוט ב-command line. אם רכיב במחרוזת הפקודה יכול להכיל רווח, חייבים לעטוף אותו במירכאות. זה חל כמובן על נתיב EXE כמו C:\Program Files\..., ו%1 (נתיב הקובץ שנבחר) צריך תמיד להיכתב "%1". אי אפשר להבטיח שנתיב קובץ של משתמש אינו מכיל רווח. My Program.exe בלי מירכאות מתפרש כ-«הפעל את My עם ה-argument Program.exe».13
flowchart TB
accTitle: תאונת הציטוט ב-command line
accDescr: פקודה בלי מירכאות נחתכת ברווח ומתפרשת בטעות כהפעלת My עם ה-argument Program.exe, ולכן נתיב EXE שיכול להכיל רווח ו-%1 שמייצג את נתיב הקובץ שנבחר צריכים תמיד להיות עטופים במירכאות
c1["פקודה בלי מירכאות"] -->|נחתכת ברווח| bad["מתפרשת בטעות כהפעלת EXE אחר"]
c2["פקודה במירכאות"] --> good["מופעלת כמתוכנן"]
c2 -.-> q1["עוטפים את נתיב ה-EXE במירכאות"]
q1 -.-> q2["עוטפים גם את %1 תמיד במירכאות"]
איור 6: פקודה בלי מירכאות נחתכת בטעות ברווח, לכן תמיד עוטפים את נתיב ה-EXE ואת %1 במירכאות.
מנגנון ה-Registry בלבד עד כאן (static verb) אפשר לממש בלי לכתוב אפילו DLL אחד, והוא אינו מסכן את יציבות Explorer. Microsoft עצמה חוזרת ואומרת «לפני שכותבים shell extension, בדקו אם ה-static verb הפשוט ביותר שעומד בדרישות יספיק».9
4. Shell extension קלאסי — DLL שרץ בתוך Explorer
4.1. סוגי shell extension
דרישות ש-static verb אינו יכול למלא — «שנו את התפריט דינמית לפי הבחירה», «החליפו את האייקון או property sheet» — משתמשות ב-shell extension handler. סוגים מייצגים להלן.4
| Handler | ממשק עיקרי | מה הוא יכול לעשות |
|---|---|---|
| Context menu handler | IContextMenu + IShellExtInit | הוספה ושליטה דינמית בפריטי תפריט |
| Icon handler / icon overlay | IExtractIcon / IShellIconOverlayIdentifier | אייקון לכל קובץ ושכבת-על |
| Property sheet handler | IShellPropSheetExt | הוספת לשונית ל-property sheet |
| Thumbnail / infotip | IThumbnailProvider / IQueryInfo | תצוגת thumbnail ותיאור hover |
| Drag-and-drop / copy hook handler | IDropTarget / ICopyHook | התערבות ב-drop או בהעתקה/העברה |
כל אלה ממומשים כמחלקות COM ונרשמים ב-Registry לפי CLSID. רעיון COM עצמו מכוסה ב-“מה זה COM / ActiveX / OCX — הסבר מרוכז על ההבדלים והקשרים”.
4.2. מה פירוש להיות in-process COM server
מהות shell extension קלאסי היא שזו in-process COM server (DLL) שנטען ל-Explorer (או לכל אפליקציה שפתחה common file dialog). כל אזהרה נובעת מכך.4
- אם ה-extension קורס, Explorer נופל איתו. אם הוא נתקע, לחיצה ימנית קופאת כמה שניות. הנזק גם אינו מוגבל ל-Explorer; הוא מגיע לכל אפליקציה שהציגה דיאלוג פתיחת קובץ.
- בניית התפריט מתרחשת ב-UI thread, לכן אסור לעשות עבודה איטית כמו גישה לרשת או I/O של קבצים בזמן הצגת התפריט.
- רושמים את מודל ה-threading כ-
Apartmentככלל.
flowchart TB
accTitle: מבנה הנזק הנלווה של extension in-process
accDescr: DLL של shell extension נטען לא רק ל-Explorer אלא גם ל-process של כל אפליקציה שפתחה file dialog, ולכן crash או hang ב-extension מתפשטים לכל ה-host process
dll["DLL של shell extension"] -->|נטען in-process| exp["Explorer"]
dll -->|נטען in-process| any["כל אפליקציה שפותחת dialog"]
exp --> dmg["crash או hang מתפשטים"]
any --> dmg
dmg -.-> rule["לא עושים עבודה איטית בזמן הצגה"]
איור 7: ה-DLL של ה-extension רץ בתוך ה-host process, ולכן crash או hang מתפשטים ל-host כולו.
חוקרים פנייה כמו «Explorer קופא כשאני פותח תיקייה מסוימת» או «לחיצה ימנית לוקחת חמש שניות» ולא נדיר שהסיבה היא shell extension של צד שלישי ולא האפליקציה הפנימית. שיטות בידוד בפרק 8.
4.3. התאמת bitness — סביבת 64-bit דורשת DLL של 64-bit
DLL in-process חייב להתאים ל-bitness של ה-process שטוען אותו. Explorer ב-Windows 64-bit הוא process 64-bit, לכן DLL של shell extension שנבנה רק כ-32-bit לעולם אינו נטען ולעולם אינו מופיע בתפריט. וגם אין שגיאה, ולכן זו סיבה קבועה ל-«רשמתי אבל זה לא מופיע». שילוב גוף אפליקציה 32-bit עם DLL של shell extension 64-bit הוא תצורה לגיטימית, אבל צריך לשים לב שרישום COM מתפצל לפי bitness (Wow6432Node). הפעלה מ-command של verb היא EXE ב-process נפרד, ולכן אינה כפופה למגבלה הזו (להשאיר אותו EXE 32-bit זה בסדר).
flowchart TB
accTitle: התאמת bitness של DLL של shell extension
accDescr: DLL של shell extension היחיד ש-Explorer 64-bit יכול לטעון הוא בן 64-bit; DLL של 32-bit בלבד אינו מופיע בתפריט ואינו מייצר שגיאה; EXE שמופעל מ-verb command הוא process נפרד ואינו כפוף למגבלה
exp["Explorer 64-bit"] -->|יכול לטעון| d64["DLL של shell extension 64-bit"]
exp -.->|לא יכול לטעון| d32["DLL של 32-bit בלבד"]
d32 -.-> sym["לא מופיע בתפריט, בלי שגיאה"]
exe["EXE שמופעל מ-verb"] -->|process נפרד| ok32["בסדר להשאיר 32-bit"]
איור 8: ה-DLL היחיד שנטען ל-Explorer 64-bit הוא DLL של 64-bit; EXE שמופעל מ-verb אינו כפוף למגבלה הזו.
4.4. מדוע אסור לכתוב זאת ב-managed code
אני מקבל לעיתים קרובות את השאלה «אפשר לכתוב shell extension ב-C#», אבל Microsoft קבעה במפורש שכתיבת shell extension in-process ב-managed code (.NET) אינה מומלצת ומחוץ לתמיכה.5
הסיבה היא טבע ה-extension שנטען ל-process שרירותי. התנגשות גרסאות CLR (במיוחד מתחת ל-.NET Framework 4), בעיית ה-reentrancy של ה-CLR ל-message loop בזמן המתנה על lock, ו-object lifetime לא-דטרמיניסטי מ-garbage collection שמתנגש בחוזה reference counting של COM הם סיבות מבניות לכך שאפליקציית ה-host הופכת ללא יציבה. חלק מהסעיפים הוקלו ב-.NET Framework 4 ואילך וב-.NET מודרני, אבל העמדה הרשמית לא השתנתה.
הקו המעשי פשוט. כותבים extension in-process ב-native C++. אם רוצים managed code, עושים ממנו EXE רגיל שמופעל מ-verb command, או out-of-process extension שרץ ב-process נפרד (preview handler וכדומה).5
flowchart TB
accTitle: שיפוט האם managed code מותר
accDescr: extension in-process שרץ בתוך Explorer נכתב ב-native C++ ככלל; אם רוצים managed code עושים ממנו EXE רגיל שמופעל מ-verb command או out-of-process extension שרץ ב-process נפרד
q1{"רץ in-process?"} -->|כן| cpp["כותבים ב-native C++"]
q1 -->|לא| mg["managed code בסדר"]
cpp -.-> why["התנגשות CLR / reentrancy הופכת את ה-host ללא יציב"]
mg --> e1["EXE שמופעל מ-verb"]
mg --> e2["preview מחוץ ל-process"]
איור 9: extension in-process הוא native C++ ככלל; managed code מוגבל לתצורה שרצה ב-process נפרד.
5. ה-context menu החדש של Windows 11 — התפריט שהתפצל לשניים
5.1. מה קרה
Windows 11 ריעננה את ה-context menu של File Explorer. גזירה, העתקה וכדומה הפכו לשורת אייקונים למעלה; «פתיחה» ו-«פתיחה באמצעות» קובצו יחד למעלה; ופקודות שאפליקציה מוסיפה מקובצות מתחת לפקודות התקניות של ה-shell. כשאפליקציה אחת מוסיפה כמה commands, הן נאספות ל-flyout (תת-תפריט) בשם האפליקציה.6
והנקודה המכרעת היא זו. shell extensions קלאסיים מבוססי IContextMenu לא נמחקו; הם הועברו לצד התפריט הישן שנפתח ב-«הצג אפשרויות נוספות» (Shift+F10) וטוען את תפריט Windows 10 כפי שהוא.6 זהות פניית הפתיחה «התפריט הוסתר» היא הפיצול הזה.
flowchart TB
accTitle: ה-context menu ש-Windows 11 פיצלה לשניים
accDescr: מה שנפתח ראשון בלחיצה ימנית הוא התפריט החדש; הפקודות היחידות שמופיעות שם הן אלה שנרשמו עם IExplorerCommand ו-package identity; הרחבות IContextMenu קלאסיות עוברות לתפריט הישן שנפתח בהצג אפשרויות נוספות
rc["לחיצה ימנית על קובץ"] --> newm["התפריט החדש (Windows 11)"]
newm --> newi["פקודות IExplorerCommand + identity"]
newm -->|הצג אפשרויות נוספות Shift+F10| oldm["התפריט הישן (תפריט Windows 10)"]
oldm --> oldi["הרחבות IContextMenu קלאסיות"]
newi -.-> fly["כמה commands נאספות ל-flyout"]
איור 10: הפקודות היחידות שמופיעות בתפריט החדש הן פקודות IExplorerCommand + identity; הרחבות קלאסיות עוברות לצד התפריט הישן.
5.2. המסלול הרשמי לתפריט החדש — IExplorerCommand + רישום manifest
יש דרך אחת להעלות command מותאם לתפריט החדש. מכינים native DLL שמממש את ממשק IExplorerCommand, ומכריזים על COM server ועל הרחבת context menu ב-manifest של חבילת MSIX.7
<!-- Package manifest (excerpt) -->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="Komura commands">
<com:Class Id="01234567-89AB-CDEF-0123-456789ABCDEF"
Path="KomuraCommand.dll" ThreadingModel="STA" />
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type=".kmrpt">
<desktop5:Verb Id="VerifyReport"
Clsid="01234567-89AB-CDEF-0123-456789ABCDEF" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
Type של ItemType יכול לציין extension מסוים, או * (כל הקבצים), Directory (תיקיות), או Directory\Background (רקע תיקייה). מתאימים את ה-DLL לארכיטקטורת Explorer (64-bit / ARM64).7
IExplorerCommand עצמו הוא ממשק שקיים מאז עידן Windows 7; מממשים כותרת (GetTitle), אייקון (GetIcon), מצב מופעל / מושבת / מוסתר (GetState) והפעלה (Invoke). המתודות נקראות מ-UI thread, לכן גישה למשאבי רשת אסורה, ומתודות בניית תפריט צריכות לחזור במהירות. עבודה כבדה אחרי Invoke.147
flowchart TB
accTitle: מבנה ה-manifest של רישום התפריט החדש
accDescr: הכרזת COM server ב-MSIX manifest ממפה CLSID ל-DLL, והכרזת הרחבת context menu קושרת את היעד והמימוש עם ItemType ו-Verb, כך ש-command מותאם מופיע בתפריט החדש
man["MSIX manifest"] --> com["הכרזת COM server"]
man --> ctx["הכרזת הרחבת תפריט"]
com -->|ממפה CLSID ל-DLL| impl["DLL מימוש IExplorerCommand"]
ctx -->|מציין עם ItemType ו-Verb| impl
impl --> shown["הפקודה מופיעה בתפריט החדש"]
ctx -.-> tgt["היעד הוא extension, כל הקבצים וכדומה"]
איור 11: שתי הכרזות ב-manifest קושרות את DLL המימוש ליעד, והפקודה מופיעה בתפריט החדש.
5.3. האפשרות לאפליקציה לא-ארוזה — קבלת identity בלבד עם sparse package
פתח המילוט כש«האפליקציה שלנו לא יכולה להיות מופצת אלא כ-MSI; MSIX בלתי אפשרי» הוא sparse package (MSIX עם external location). חותמים MSIX קטן שהוא manifest בלבד, בלי גוף אפליקציה, ורושמים אותו בסוף ה-installer הקיים. האפליקציה רוכשת אז package identity, ורישום ה-manifest למעלה (= הופעה בתפריט החדש) הופך לאפשרי. זמין מ-Windows 10 version 2004 ואילך, והחבילה זקוקה לחתימה בתעודה מהימנה במחשב היעד.8
flowchart TB
accTitle: זרימת קבלת identity עם sparse package
accDescr: אחרי שה-installer הקיים מניח את גוף האפליקציה, רישום sparse package של manifest בלבד עם external location נותן לאפליקציה package identity ומאפשר רישום manifest לתפריט החדש
inst["installer קיים"] --> files["הנחת גוף האפליקציה"]
sp["sparse package"] -.-> only["manifest בלבד, בלי גוף"]
files --> reg["רישום עם external location"]
sp --> reg
reg --> id["רכישת package identity"]
id --> ok["רישום לתפריט החדש הופך לאפשרי"]
sp -.-> sign["נדרשת חתימה מהימנה"]
איור 12: רושמים sparse package שאינה מכילה גוף, עם external location, והאפליקציה מקבלת package identity.
היתרון הגדול ביותר הוא שאין צורך להחליף את ה-installer; זו התשובה המציאותית לאפליקציה שכבר יש לה נכס installer MSI/EXE. להשוואה עם מעבר מלא ל-MSIX ראו גם “איך בוחרים שיטת הפצה ליישום Windows - MSI/MSIX/ClickOnce/xcopy/עדכון עצמי”.
5.4. איך verbs של association מופיעים בתפריט החדש
נקודה שקל לטעות בה: ה-associations של פרקים 2 ו-3 (ProgID ו-verb) עדיין חיים בתפריט החדש. ה-verb כברירת מחדל ב-double-click, «פתיחה», ומועמדי «פתיחה באמצעות» נפתרים מה-association ומוצגים בראש התפריט החדש. לכן אם כל מה שרוצים הוא «להיות מסוגלים לפתוח עם האפליקציה הזו», Windows 11 אינו דורש עבודה נוספת. מצד שני, association אינו הרחבת תפריט כלל-תכליתית, ולכן אם רוצים command מותאם שרירותי בשכבה הראשונה של התפריט החדש צריך IExplorerCommand ועוד identity — זה פיצול התפקידים.7
flowchart TB
accTitle: פיצול התפקידים בין associations לתפריט החדש
accDescr: association של ProgID ו-verb עדיין משמש בתפריט החדש לפתרון ה-verb כברירת מחדל, פתיחה ופתיחה באמצעות, ומוצג למעלה; העלאת command מותאם שרירותי לשכבה הראשונה של התפריט החדש דורשת IExplorerCommand ו-identity
assoc["association (ProgID + verb)"] --> sol["פתרון ברירת מחדל / פתיחה"]
sol --> top["ראש התפריט החדש"]
assoc -.-> keep["אין עבודה נוספת ב-Win11"]
cmd["command מותאם"] --> need["IExplorerCommand+identity"]
need --> first["השכבה הראשונה של התפריט החדש"]
איור 13: associations עדיין מטפלים בפתרון משפחת «פתיחה» בתפריט החדש; רק command מותאם דורש IExplorerCommand ועוד identity.
6. טבלת החלטה מעשית — איזו משלוש האפשרויות לבחור
מארגנים את מה שיש עד כאן לבחירה משולשת מעשית.
| מה רוצים להשיג | האמצעי המומלץ | איך זה נראה ב-Windows 11 | העבודה והעלות הנדרשות |
|---|---|---|---|
| (א) להפעיל את האפליקציה הפנימית ב-double-click או «פתיחה» | association + static verb (רישום Registry בלבד) | משולב ב-«פתיחה» וב-«פתיחה באמצעות» בתפריט החדש | רישום Registry של ה-installer בלבד. אין DLL, אין דרישת חתימה נוספת |
| (ב) להעלות command מותאם לקובץ/תיקייה שנבחרו לתפריט החדש | מימוש IExplorerCommand + רישום MSIX manifest. אפליקציה לא-ארוזה מקבלת identity ב-sparse package | השכבה הראשונה של התפריט החדש (כמה commands נאספות ל-flyout בשם האפליקציה) | native C++ DLL + package identity + code signing |
| (ג) להמשיך להשתמש ב-IContextMenu קלאסי קיים | להשאיר כרגע כמו שהוא (לא לבחור לפיתוח חדש) | צד התפריט הישן בלבד, תחת «הצג אפשרויות נוספות» (Shift+F10) | תחזוקת build 64-bit ורישום COM. לתכנן מעבר מאוחר ל-(ב) |
שתי נקודות שיפוט. ראשית, אל תביאו (ב) או (ג) לדרישה ש-(א) ממלאת. ברגע שכותבים shell extension לוקחים אחריות על יציבות Explorer. שנית, (ג) הוא רק «לא שבור»; כחוויית משתמש הוא נשאר צעד גרוע יותר. ככל ש-command בשימוש תכוף יותר בפעילות יומיומית, כך גדול יותר התשואה על מעבר ל-(ב).
flowchart TB
accTitle: איך לבחור בין שלוש האפשרויות
accDescr: אם כל מה שרוצים הוא הפעלה ב-double-click או פתיחה, association ו-static verb מספיקים; להעלאת command מותאם לתפריט החדש משתמשים ב-IExplorerCommand ורישום MSIX manifest; אם אי אפשר לעבור ל-MSIX מעניקים identity ב-sparse package; משאירים IContextMenu קלאסי קיים בצד התפריט הישן כרגע
q1{"האם פתיחה מספיקה?"} -->|כן| pa["association + static verb"]
q1 -->|לא| q2{"מותאם בתפריט החדש?"}
q2 -->|כן| q3{"אפשר לעבור ל-MSIX?"}
q3 -->|כן| pb1["IExplorerCommand+MSIX"]
q3 -->|לא| pb2["identity ב-sparse package"]
q2 -->|לא| pc["להשאיר קלאסי כרגע"]
pc -.-> old["צד התפריט הישן בלבד"]
pa -.-> dllfree["אין DLL, סיכון קטן"]
איור 14: בוחרים בין static verb, IExplorerCommand ועוד identity, והשארת הקלאסי, לפי הדרישה.
7. פריסה ורישום בפועל — installer, sparse package, cleanup
7.1. HKLM או HKCU
מתאימים לצורה של ה-installer. לכל המשתמשים (מונח תחת Program Files, הרשאות Administrator) הוא HKLM\Software\Classes; התקנה לפי משתמש (בלי elevation) היא HKCU\Software\Classes. מערבבים אותם ומייצרים פניות מסוג «א יכול לפתוח אבל ב לא יכול». ל-shell extension שכולל רישום CLSID, Reg-Free COM — שמסיר את הצורך ברישום ה-Registry עצמו — הוא אפשרות תקפה לשימוש COM בתוך האפליקציה, אבל אי אפשר ליישם אותו על shell extension ש-Explorer טוען, ולכן צריך את הרישום הישיר (“מה זה Reg-Free COM - שימוש ב-COM בלי רישום”).
7.2. אחרי שינוי, מודיעים — SHChangeNotify
אחרי שרושמים, משנים או מוחקים association, מודיעים על אירוע SHCNE_ASSOCCHANGED עם SHChangeNotify. מדלגים על זה ו-Explorer עלול לא להבחין בשינוי עד restart.110
// Call once after changing associations, e.g. from an installer custom action
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
7.3. רישום והסרת sparse package
רישום והסרת sparse package הם עבודת ה-installer. רושמים אחרי הנחת הקבצים; מסירים לפני מחיקת הקבצים.8
# At install time: after placing the files, register the install folder as the external location
Add-AppxPackage -Path "C:\Program Files\KomuraSoft\KomuraReport.identity.msix" `
-ExternalLocation "C:\Program Files\KomuraSoft"
# At uninstall time: remove the package registration before deleting the files
Remove-AppxPackage <package full name>
נקודה לשמירה: Add-AppxPackage רושם עבור המשתמש שהריץ אותו. אם קוראים לו מ-custom action של MSI לכל-המחשב, שרצה תחת LocalSystem, זה אינו מעניק identity למשתמש שהתקין, ולכן מגדירים אותו לרוץ תחת impersonation למשתמש. גם אז, הרישום תחת impersonation הוא רק עבור המשתמש שהריץ את ההתקנה הזו. במחשב שמשמש כמה משתמשים, למשתמשים אחרים ולמשתמשים שנוצרים אחר כך אין package identity, והפקודה אינה מופיעה בתפריט החדש. כדי שכל משתמש ישתמש בה, מספקים מנגנון כמו בדיקת רישום החבילה שלכם בהפעלה הראשונה ורישום אם חסר (רישום לפי משתמש), וכוללים הסרה מכל משתמש שיש לו רישום בתוכנית ה-uninstall. השתקפות רישום manifest עשויה גם לדרוש restart של Explorer (או sign-out).7
flowchart TB
accTitle: סדר רישום והסרת sparse package
accDescr: בזמן התקנה רושמים את ה-sparse package אחרי הנחת הקבצים; בזמן uninstall מסירים את הרישום לפני מחיקת הקבצים; שמים לב שהרישום תקף רק למשתמש שהריץ אותו
i1["התקנה"] --> i2["הנחת הקבצים"]
i2 --> i3["רישום ה-sparse package"]
u1["uninstall"] --> u2["הסרת רישום החבילה"]
u2 --> u3["מחיקת הקבצים"]
i3 -.-> pu["הרישום תקף רק למשתמש הרץ"]
איור 15: רושמים אחרי הנחת הקבצים, מסירים לפני מחיקת הקבצים, ושמים לב שהרישום הוא לפי המשתמש הרץ.
7.4. Cleanup ב-uninstall — מה למחוק ומה להשאיר
ל-cleanup ב-uninstall יש קו מדריך רשמי ברור.1
- מוחקים: את כל מפתח ה-ProgID הפנימי, רישום Capabilities/RegisteredApplications, רישום CLSID של ה-shell extension, ה-sparse package (Remove-AppxPackage).
- משאירים: ערך ברירת המחדל של מפתח ה-extension (
.kmrpt). ההמלצה הרשמית היא לא למחוק אותו גם אם הוא עדיין מצביע על ה-ProgID הפנימי. לשפוט אחרי התקנה אם אפליקציה אחרת לקחה את ה-default קשה, ו-Windows פשוט מתעלמת מ-ProgID ערך-ברירת-מחדל שאינו רשום, כך שהשארתו אינה מזיקה באמת. - קוראים ל-SHChangeNotify(SHCNE_ASSOCCHANGED) גם בסוף ה-cleanup.
רוב בעיות «הסרנו התקנה ועדיין מופיעות שאריות בתפריט» הן דליפה בעיצוב ה-cleanup הזה.
flowchart TB
accTitle: עיצוב ה-cleanup ב-uninstall
accDescr: ב-uninstall מוחקים את מפתח ה-ProgID הפנימי, רישום CLSID וה-sparse package; משאירים את ערך ברירת המחדל של מפתח ה-extension כי ProgID לא-רשום מתעלמים ממנו; מודיעים על השינוי ב-SHChangeNotify בסוף ה-cleanup
un["uninstall"] --> del["מוחקים"]
un --> keep["משאירים"]
del --> d1["רישום ProgID ו-CLSID"]
del --> d2["sparse package"]
keep --> k1["ערך ברירת המחדל של מפתח ה-extension"]
k1 -.-> why["מתעלמים מ-ProgID לא-רשום"]
d1 --> fin["מודיעים ב-SHChangeNotify בסוף"]
k1 --> fin
איור 16: מוחקים את הרישום הפנימי, משאירים את ערך ברירת המחדל של מפתח ה-extension, ומודיעים על השינוי בסוף ה-cleanup.
8. Troubleshooting — חסר, כפול, כבד
8.1. זה לא מופיע בתפריט
מבודדים בסדר הזה.
- באיזה תפריט מסתכלים: רישום בסגנון קלאסי מופיע רק בצד התפריט הישן תחת Shift+F10. בודקים קודם את שניהם.
- Bitness: DLL של shell extension של 32-bit בלבד אינו נטען ל-Explorer 64-bit (סעיף 4.3).
- יעד רישום: HKLM/HKCU, ערבוב Wow6432Node. מאשרים את המפתח בפועל עם
reg query. - רישום חבילה: לתפריט החדש מאשרים נוכחות עם
Get-AppxPackage, אמון בתעודת החתימה, ונתיב-ExternalLocation, ואז עושים restart ל-Explorer.7 - הודעה שפוספסה: אם שכחו SHChangeNotify, אפשר לדעת לפי האם restart של Explorer גורם לזה להיכנס לתוקף.
flowchart TB
accTitle: סדר בידוד כשזה לא מופיע בתפריט
accDescr: מתחילים באישור באיזה תפריט מסתכלים, ואז מבודדים bitness של DLL, יעד רישום ה-Registry, רישום חבילה וחתימה, ו-SHChangeNotify שפוספס, בסדר הזה
c1["מאשרים איזה תפריט, ישן או חדש"] --> c2["מאשרים bitness של DLL"]
c2 --> c3["מאשרים יעד רישום HKLM ו-HKCU"]
c3 --> c4["מאשרים רישום חבילה וחתימה"]
c4 --> c5["מזהים הודעה שפוספסה ב-restart"]
איור 17: כש«לא מופיע», מבודדים בסדר התפריט שמסתכלים עליו, bitness, יעד הרישום, רישום החבילה, הודעה שפוספסה.
8.2. זה מופיע פעמיים, או לא נעלם
סיבות טיפוסיות הן דו-קיום של רישום Registry קלאסי ורישום manifest, דליפה ב-cleanup של uninstall (סעיף 7.4), או שאריות ProgID של גרסה ישנה. אם זה מופיע פעמיים רק בתפריט הישן, חושבים על שאריות; אם זה מופיע גם בישן וגם בחדש, חושבים על דו-קיום.
flowchart TB
accTitle: בידוד תצוגה כפולה
accDescr: פעמיים רק בתפריט הישן מצביע על שאריות כמו דליפת cleanup או ProgID ישן; פעמיים גם בישן וגם בחדש מצביע על דו-קיום של רישום Registry קלאסי ורישום manifest
q{"באיזה זה מופיע פעמיים?"} -->|התפריט הישן בלבד| zan["שאריות"]
q -->|ישן וחדש| hei["דו-קיום"]
zan -.-> z1["דליפת cleanup או ProgID ישן שנשאר"]
hei -.-> h1["רישום Registry קלאסי שמתקיים יחד עם הרישום החדש"]
איור 18: פעמיים רק בתפריט הישן מצביע על שאריות; פעמיים גם בישן וגם בחדש מצביע על דו-קיום.
8.3. Explorer כבד או קורס
כשלחיצה ימנית איטית, או שתיקייה מסוימת קורסת, קודם ממפים את ה-shell extensions המותקנים. מפרטים extensions שאינם של Microsoft בכלי כמו ShellExView של NirSoft, משביתים זמנית את החשודות, ומאתרים את ה-DLL בחיפוש בינארי. ב-crash, «Faulting module» ב-Event Viewer הוא גם רמז. אם ה-extension הפנימי היה הסיבה, חושדים ב-I/O סינכרוני או בגישה לרשת בנתיב בניית התפריט (סעיפים 4.2 ו-5.2).
flowchart TB
accTitle: זיהוי ה-DLL האשם כשזה כבד או קורס
accDescr: מפרטים shell extensions שאינם של Microsoft ב-ShellExView, משביתים זמנית את החשודות ומאתרים את ה-DLL בחיפוש בינארי; ב-crash מודול התקלה ב-Event Viewer הוא גם רמז
s1["מיפוי shell extensions"] --> s2["פירוט אלה שאינם של Microsoft"]
s2 --> s3["השבתה זמנית וחיפוש בינארי"]
s3 --> s4["זיהוי ה-DLL האשם"]
crash["ב-crash"] -.-> ev["בדיקת מודול התקלה"]
ev -.-> s4
איור 19: משביתים זמנית extensions שאינם של Microsoft ומאתרים בחיפוש בינארי; ב-crash משתמשים גם ב-Event Viewer.
8.4. Windows Sandbox נוח לאימות
אימות shell integration מבוסס על אישור «התקנה בסביבה נקייה → הפעלה → uninstall → אפס שאריות». הנוח כאן הוא Windows Sandbox (Pro/Enterprise/Education): כל הפעלה מביאה Windows חדש חד-פעמי תוך שניות, כך שאפשר להריץ בדיקות רישום ו-cleanup של ה-installer כמה שרוצים. סוגרים והכול נעלם, ולכן זה מתאים גם לחקירת שאריות Registry.15
9. סיכום
- File association הוא המבנה התלת-שלבי «מפתח extension → ProgID → verb», ו-HKCR היא merged view של Classes של HKLM/HKCU. מציינים את יעד הכתיבה במפורש, ותמיד עוטפים
%1במירכאות. - Default app מיועד להיבחר בידי המשתמש ואי אפשר לשנות אותו מתוכנית. עבודת ה-installer היא להירשם נכון כמועמד.
- Shell extension קלאסי הוא in-process COM DLL שנטען ל-Explorer. crash או delay מתפשטים לכלל; נדרשות 64-bit; managed code אינו נתמך; המימוש ב-native C++ הוא הכלל.
- ב-Windows 11 ה-context menu התפצל לשניים. העלאת command מותאם לתפריט החדש דורשת IExplorerCommand ועוד MSIX manifest; IContextMenu קלאסי עובר לצד «הצג אפשרויות נוספות».
- לאפליקציה שאינה יכולה לעבור ל-MSIX, קבלת identity עם sparse package (MSIX עם external location) היא התשובה המציאותית.
- אם כל מה שרוצים הוא «לפתוח עם האפליקציה הזו», association ו-static verb עדיין מספיקים. להתחיל מהאמצעי הפשוט ביותר הוא גם המדריך הרשמי.
- אחרי רישום, שינוי או מחיקה מודיעים ב-SHChangeNotify; ב-uninstall מוחקים את ה-ProgID ומשאירים את ערך ברירת המחדל של מפתח ה-extension. Windows Sandbox נוח לאימות.
אם החלפת מחשב ל-Windows 11 גרמה לכם להבחין ש«התפריט הוסתר», קודם מאשרים איזה מבין (א), (ב) ו-(ג) בטבלת ההחלטה של פרק 6 זה. אמורים להיות מסוגלים להעריך את היקף העבודה במקום.
מאמרים קשורים
- מה זה COM / ActiveX / OCX — הסבר מרוכז על ההבדלים והקשרים
- מה זה Reg-Free COM - שימוש ב-COM בלי רישום
- Registry 32-bit/64-bit Redirection and Virtualization Pitfalls — Wow6432Node and the “The Value I Wrote Isn’t There” Problem
- איך בוחרים שיטת הפצה ליישום Windows - MSI/MSIX/ClickOnce/xcopy/עדכון עצמי
- DLL and COM Interface Backward Compatibility — A Decision Table for Which Changes Break Callers
- AppCompat ב-Windows: compatibility mode, shims ו-Compatibility Administrator
תחומי ייעוץ קשורים
KomuraSoft LLC מטפלת בתכנון ובמימוש של file association, context menu ו-shell extension לאפליקציות עסקיות; כיוון ל-context menu החדש של Windows 11 (מעבר ל-IExplorerCommand, הכנסת sparse package); סקירת רישום ו-cleanup של installer קיים; וחקירת הסיבה ל-Explorer כבד או קורס. אפשר להתחיל מההחלטה מה לעשות עם «התפריט שהוסתר תחת הצג אפשרויות נוספות».
- Windows Custom Software Development
- שימוש חוזר והעברה של נכסים קיימים
- ייעוץ טכני וסקירת תכנון
- יצירת קשר
קישורי עיון
-
Microsoft Learn, File Types. על מבנה מפתח extension שמצביע על ProgID; OpenWithProgIds; פיצול הרישום בין HKLM/HKCU\Software\Classes; קריאה ל-SHChangeNotify(SHCNE_ASSOCCHANGED) אחרי שינוי association; ומחיקת ה-ProgID ב-uninstall תוך השארת ערך ברירת המחדל של מפתח ה-extension. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, HKEY_CLASSES_ROOT Key. על כך ש-HKEY_CLASSES_ROOT היא merged view של HKLM\Software\Classes ו-HKCU\Software\Classes; הגדרות צד המשתמש קודמות לאלה של צד המחשב; וכללי הניתוב בכתיבה. ↩ ↩2
-
Microsoft Learn, Windows app defaults platform. על כך ששינוי default app מיועד להיעשות רק דרך Settings של המערכת; נתוני הגדרות משתמש הם obfuscated ומוגנים בכתיבה ב-filter driver (UCPD.sys); שינוי מבוסס-Registry אינו נתמך; ושימוש ב-Group Policy / MDM בסביבה מנוהלת. ↩ ↩2
-
Microsoft Learn, Working with Shell Extensions. על סוגי shell extension handler; על כך ש-extension הוא in-process COM DLL שנטען ל-Explorer (ול-processes שמארחים את ה-shell) כך ש-crash או hang מתפשטים ל-Explorer כולו; רישום עם ThreadingModel=Apartment; ובחינת חלופה פשוטה יותר לפני shell extension. ↩ ↩2 ↩3
-
Microsoft Learn, Guidance for Implementing In-Process Extensions. על כך ש-Microsoft אינה ממליצה ואינה תומכת במימוש shell extension in-process ב-managed code; סיבות כוללות התנגשות גרסאות CLR, reentrancy ו-object lifetime לא-דטרמיניסטי; ו-managed code מקובל ל-out-of-process extension (preview handler, או הפעלה מ-shell\verb\command). ↩ ↩2 ↩3
-
Windows Developer Blog, Extending the Context Menu and Share Dialog in Windows 11. על עיצוב ה-context menu החדש של Windows 11; הרחבה דרך IExplorerCommand ועוד identity של אפליקציה; מיקום «פתיחה» ו-«פתיחה באמצעות» למעלה; איסוף כמה commands ל-flyout בשם האפליקציה; וטעינת הרחבות IContextMenu קלאסיות כתפריט Windows 10 תחת «הצג אפשרויות נוספות» (Shift+F10). ↩ ↩2 ↩3
-
Microsoft Learn, Add a File Explorer context menu command to a packaged desktop app. על כך שרישום ב-context menu החדש של Windows 11 נעשה עם מימוש IExplorerCommand ועוד windows.comServer ועוד הכרזת manifest desktop4:FileExplorerContextMenus; ItemType יכול לציין *, Directory או Directory\Background; התאמת ארכיטקטורת DLL; שמירה על מתודות בניית תפריט מהירות; כיסוי אפליקציה לא-ארוזה ב-sparse package; שלפעמים נדרש restart של Explorer כדי שהרישום ייכנס לתוקף; וש-file association אינו הרחבת תפריט כלל-תכליתית. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Grant package identity by packaging with external location. על קבלת package identity ברישום חבילת external location (sparse package) בלי לשנות את ה-installer הקיים; זמינות מ-Windows 10 version 2004 ואילך; ותכונות Windows שדורשות identity (רישום context menu, notifications וכדומה) הופכות לשימושיות. ↩ ↩2 ↩3
-
Microsoft Learn, Choosing a Static or Dynamic Shortcut Menu Method. על בחירת שיטת ה-static verb הפשוטה ביותר שעומדת בדרישות; IContextMenu החזק ביותר אבל גם המורכב ביותר ומסווג לצד הלא-מומלץ; ו-IExplorerCommand/IExplorerCommandState השיטה המומלצת. ↩ ↩2
-
Microsoft Learn, SHChangeNotify function. על איך להרים את אירוע SHCNE_ASSOCCHANGED שמודיע למערכת על שינוי file association, ועל השימוש בו כדי שה-shell תבחין בשינוי. ↩ ↩2
-
Microsoft Learn, Application Registration. על כך שרישום קובץ הפעלה דרך מפתח-המשנה App Paths מומלץ; תפקיד מפתח-המשנה Applications; רישום verbs דרך SystemFileAssociations; ועדיפות ה-ProgID והמידע הקשור כש-default app משתנה. ↩
-
Microsoft Learn, Verbs and File Associations. על כך ש-verb הוא פעולה שמשמשת גם את ShellExecuteEx; רכיבים במחרוזת command שיכולים להכיל רווח צריכים להיות עטופים במירכאות, ו-“%1” תמיד נכתב במירכאות; ורישום הליך ברירת מחדל תחת HKCR\Applications. ↩
-
Microsoft Learn, IExplorerCommand interface. על הרכב המתודות GetTitle, GetIcon, GetState, Invoke, EnumSubCommands וכדומה; המתודות נקראות ב-UI thread ולכן אסור להן לתקשר עם משאבי רשת; וזמינות מ-Windows Vista ואילך. ↩
-
Microsoft Learn, Windows Sandbox. על היכולת להפעיל סביבת Windows מבודדת חד-פעמית תוך שניות, כל השינויים נזרקים כשסוגרים אותה, התאמתה לבדיקות תוכנה ואימות installer, וזמינות ב-Pro/Enterprise/Education. ↩
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
סיום התמיכה ב-printer drivers של Windows — איך אפליקציות עסקיות מתכוננות להדפסת דוחות ותוויות
Microsoft מפסיקה בהדרגה printer drivers מסוג v3/v4. מה Windows protected print mode מסיר, ואיך עושים inventory ומכינים הדפסת דוחות ותוויו...
WinRT הוא COM — IInspectable, .winmd, language projections, ולמה WinUI עדיין יושב על חוזה בינארי
WinRT אינו managed runtime אלא ABI שנבנה על COM ועליו metadata של .winmd ו-language projections. מכסה IUnknown מול IInspectable, הגדרת HW...
מה זה OLE object? — איך embedding ו-linking עובדים, והמלכודות במסמכים עסקיים
OLE object הוא מה שמטמיע טבלת Excel ב-Word. לומדים embedding מול linking, compound files, In-Place Activation, קישורים שבורים, ניפוח ואבטחה.
איך clipboard ו-drag-and-drop עובדים — טיפול נכון ב-OLE data transfer באפליקציות עסקיות
למה הדבקת Excel מתפרקת ולמה paste נכשל אחרי סגירת המקור: clipboard formats, delayed rendering, OLE drag-and-drop, ומדיניות clipboard hist...
הרשת עובדת אבל Windows מציג "אין אינטרנט" — לבודד NCSI, DNS, Proxy ו-VPN ב-Windows
למה Windows מציג "אין אינטרנט" בזמן שהרשת עובדת — נקודת המוצא היא קביעת הקישוריות של NCSI. כאן מפרידים בין DNS, proxy, VPN ו-captive port...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
יישומים עסקיים, חיבור התקנים וכלי תקשורת, מהגדרת הדרישות ועד הפיתוח.
תחזוקה ומודרניזציה של תוכנת Windows
הרחבות, תחזוקה ומודרניזציה הדרגתית של תוכנת Windows קיימת.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- למה ב-Windows 11 פריט ה-context menu של האפליקציה שלנו מופיע רק תחת "הצג אפשרויות נוספות"?
- כי ב-Windows 11 ה-context menu של File Explorer מפוצל לשתי שכבות. בתפריט החדש יכולים להופיע רק commands שמממשים IExplorerCommand ורשומים ב-manifest של חבילת MSIX (כלומר יש להם package identity). shell extension קלאסי שמבוסס על IContextMenu עובר לתפריט הישן שנפתח מ-"הצג אפשרויות נוספות" (Shift+F10). ה-extension עצמו לא שבור, ולכן הוא ממשיך לעבוד כרגע. אם רוצים אותו בתפריט החדש צריך לעבור ל-IExplorerCommand, ולתת identity דרך MSIX או sparse package.
- האם ה-installer יכול לקבוע את האפליקציה שלנו כ-default app לקובץ (זו שנפתחת ב-double-click)?
- לא. בחירת default app מיועדת למשתמש, ו-Windows לא תומכת בשינוי default app מחוץ ל-Settings של המערכת. נתוני UserChoice ששומרים את הבחירה לכל משתמש הם obfuscated, ו-filter driver (UCPD.sys) חוסם כתיבה מאפליקציות. מה ש-installer יכול לעשות הוא לרשום ProgID ו-verbs, להוסיף את עצמו ל-OpenWithProgIds כדי להופיע כמועמד תחת "פתיחה באמצעות", ולהפנות את המשתמש לדף הגדרות default apps. המימוש הנכון אינו לגנוב את ה-default, אלא להיות מוכן להיבחר.
- מותר לכתוב shell extension ב-managed code כמו C#?
- Microsoft כתבה במפורש ש-shell extension in-process (context menu handler, icon handler וכדומה) ב-managed code אינו מומלץ ואינו נתמך. ה-extension נטען ל-Explorer ול-process של כל אפליקציה שפותחת common file dialog, ולכן התנגשות גרסאות CLR, reentrancy ו-object lifetime לא-דטרמיניסטי הופכים את אפליקציית ה-host ללא יציבה. הכלל הוא native C++. EXE רגיל שמופעל מ-verb command, או out-of-process extension כמו preview handler שרץ ב-process נפרד, תקינים ב-managed code.
- מה זה sparse package (MSIX עם external location)?
- חבילת MSIX קטנה בלי קבצי אפליקציה, רק manifest (identity). לאפליקציה שכבר מותקנת עם installer קיים (MSI, Inno Setup וכדומה) רושמים אותה עם Add-AppxPackage -ExternalLocation שמצביע לתיקיית ההתקנה. האפליקציה מקבלת package identity ויכולה להשתמש בתכונות שדורשות identity, כמו רישום ל-context menu החדש של Windows 11 ו-toast notifications. זמין מ-Windows 10 version 2004 ואילך, והחבילה צריכה code signing מהימן במחשב היעד. זו האפשרות המציאותית כשרוצים את התפריט החדש בלי להעביר את כל ה-deployment ל-MSIX.
- מה עושים כשפריט context menu מופיע פעמיים, או לא נעלם?
- קודם מבודדים לפי התפריט: החדש או הישן (הצג אפשרויות נוספות). כפילות טיפוסית היא רישום Registry קלאסי ורישום MSIX manifest שחיים יחד, או ProgID / CLSID של extension שנשארו אחרי uninstall. אחרי שינוי file association חשדו גם ב-SHChangeNotify(SHCNE_ASSOCCHANGED) שפוספס; מיד אחרי רישום חבילה חשדו ב-Explorer שלא עשה restart. אם זה עדיין לא נפתר, משביתים זמנית extensions שאינם של Microsoft ב-ShellExView ומאתרים את ה-DLL בחיפוש בינארי.