התקנה עדכון וחזרה לאחור¶
העתקנו את הexe למחשב נקי והוא לא נפתח. במחשב המפתח היו runtime, DLLים ונתיבי חיפוש שלא נכללו בתוצר. כדי להבין אם המוצר באמת מוכן, צריך להפריד בין מה שנמצא בתיקיית הקוד לבין מה שנדרש למשתמש בזמן הריצה.
התקנה היא גם מעבר מצב. היא מוסיפה קבצים ורישומים, בוחרת זהות ונתיבים, ולעיתים משנה נתונים קיימים. שדרוג תקין צריך להתחשב בתהליך שעדיין רץ ובגרסה הקודמת שייתכן שנרצה להחזיר.
בוחרים מודל הפצה לפי המוצר¶
הפורמט MSI משתמש במודל של Windows Installer, עם רכיבים, features, פעולות התקנה ותחזוקה. תכנון רכיבים יציבים וkey paths חשוב לrepair ולupgrade. הפעולה מסוג custom action יכולה לבצע פעולה שהמנוע אינו יודע להחזיר לאחור בלי קוד מתאים, ולכן אין להניח שכל side effect נכלל אוטומטית בrollback.
הפורמט MSIX נותן זהות package, תיאור הצהרתי, חתימה וניהול פריסה. הוא מתאים למוצרים רבים, אך צריך לבדוק את התאמת השירותים, ההרחבות, הדרייברים ומודל האחסון של המוצר ליכולות ולמגבלות של גרסאות היעד. סוג הקובץ לבדו אינו החלטת ארכיטקטורה.
במעבדה נארוז את החלון הקטן משיעור 10.1 כMSIX. השירות והרכיבים המורכבים ייכנסו לתכנון ההפצה של פרויקט הגמר, אחרי שנדע לתאר את התלויות שלהם במפורש.
מכינים תיקייה שיש לה משמעות¶
נפתח תיקיית מעבדה חדשה ונעתיק אליה את window.cpp. הפקודות כאן נרצות בDeveloper PowerShell עם MSVC וWindows SDK, בתוך VM שמיועדת לניסוי התקנה. נבנה עם runtime סטטי כדי שהיישום הקטן לא יהיה תלוי בהתקנת Visual C++ Redistributable לצורך הניסוי הזה.
New-Item -ItemType Directory -Path .\Package\Assets -Force | Out-Null
cl /std:c++20 /EHsc /O2 /MT /W4 window.cpp /Fe:Package\CourseWindow.exe user32.lib gdi32.lib
if ($LASTEXITCODE -ne 0) { throw 'Native build failed' }
הבחירה ב/MT אינה המלצה לשנות כל DLL במוצר. כאשר זיכרון או state של CRT חוצים גבול DLL, צריך חוזה שמונע ערבוב allocators ומצבי runtime. במוצר עם runtime דינמי נתעד ונפרוס את התלות המתאימה במקום להסתמך על מה שהIDE התקין אצלנו.
ניצור שלושה assets פשוטים כדי שהחבילה תהיה עצמאית. אלה סמלי מעבדה גאומטריים, ולא תלות בקובץ שקיים רק במחשב המפתח.
Add-Type -AssemblyName System.Drawing
$sizes = @{ StoreLogo = 50; Square44 = 44; Square150 = 150 }
foreach ($name in $sizes.Keys) {
$size = [int]$sizes[$name]
$bitmap = [System.Drawing.Bitmap]::new($size, $size)
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
try {
$graphics.Clear([System.Drawing.Color]::FromArgb(24, 45, 70))
$margin = [int]($size / 4)
$graphics.FillRectangle([System.Drawing.Brushes]::White,
$margin, $margin, $size - 2 * $margin, $size - 2 * $margin)
$path = Join-Path $PWD ("Package/Assets/" + $name + '.png')
$bitmap.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
} finally {
$graphics.Dispose()
$bitmap.Dispose()
}
}
מגדירים זהות וחוזה הפעלה¶
נשמור את הקובץ הבא כPackage\AppxManifest.xml. השם והpublisher הם זהות מעבדה חדשה. הערך של Publisher חייב להתאים בדיוק לsubject של תעודת החתימה שניצור בהמשך. גרסת המערכת המרבית שנבדקה צריכה להתעדכן לפי בדיקות הrelease בפועל.
<?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:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap uap10 rescap">
<Identity Name="AmitTech.CourseWindow" Publisher="CN=AmitTech Lab"
Version="1.0.0.0" ProcessorArchitecture="x64" />
<Properties>
<DisplayName>חלון המעבדה</DisplayName>
<PublisherDisplayName>עמית מעבדה</PublisherDisplayName>
<Description>תרגול אריזה ועדכון של חלון מקומי</Description>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Resources><Resource Language="he-IL" /></Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop"
MinVersion="10.0.19041.0" MaxVersionTested="10.0.26200.0" />
</Dependencies>
<Capabilities><rescap:Capability Name="runFullTrust" /></Capabilities>
<Applications>
<Application Id="CourseWindow" Executable="CourseWindow.exe"
uap10:RuntimeBehavior="packagedClassicApp" uap10:TrustLevel="mediumIL">
<uap:VisualElements DisplayName="חלון המעבדה"
Description="תרגול אריזה ועדכון של חלון מקומי"
Square150x150Logo="Assets\Square150.png"
Square44x44Logo="Assets\Square44.png" BackgroundColor="transparent" />
</Application>
</Applications>
</Package>
ההצהרה על runFullTrust מתאימה למודל desktop שבחרנו; היא אינה בקשה להרצת administrator ואינה הופכת את התוכנית לבטוחה. הפרטים של רכיבי חבילת desktop חשובים כאן יותר מהעתקה עיוורת של manifest של יישום אחר.
ניצור את החבילה עם כלי הSDK. אם validation נכשל, נתקן את manifest או את הassets ולא נבטל את הבדיקה כדי לקבל קובץ.
makeappx pack /d .\Package /p .\CourseWindow-1.0.0.0.msix
if ($LASTEXITCODE -ne 0) { throw 'Packaging failed' }
החתימה קושרת תוכן לזהות¶
ניצור תעודת בדיקה בstore האישי של משתמש המעבדה. המפתח הפרטי נשאר שם ואינו נכנס לתיקיית Package. נייצא רק את התעודה הציבורית לצורך הגדרת אמון בVM.
$courseCertificate = New-SelfSignedCertificate -Type Custom `
-Subject 'CN=AmitTech Lab' -KeyUsage DigitalSignature `
-CertStoreLocation 'Cert:\CurrentUser\My' `
-NotAfter (Get-Date).AddDays(30) `
-TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3', '2.5.29.19={text}')
Export-Certificate -Cert $courseCertificate -FilePath .\CourseLab.cer | Out-Null
$courseCertificate.Thumbprint | Set-Content .\CourseLab-thumbprint.txt
signtool sign /fd SHA256 /sha1 $courseCertificate.Thumbprint .\CourseWindow-1.0.0.0.msix
if ($LASTEXITCODE -ne 0) { throw 'Signing failed' }
הthumbprint בוחר תעודה מסוימת. האלגוריתם /fd SHA256 מתאר את hash החתימה של החבילה, ואינו נקבע לפי השם SHA1 של selector התעודה. בשחרור אמיתי נגדיר שירות חתימה או תעודה מתאימה וtimestamp כחלק מתהליך הפריסה; תעודת המעבדה הקצרה אינה זהות להפצה רחבה.
כדי להתקין את חבילת הבדיקה, נגדיר אמון בתעודה הציבורית בTrustedPeople של VM המעבדה. הפעולה הבאה דורשת PowerShell מוגבה באותה VM ומשנה את אמון המכונה, ולכן היא חלק מהקמת המעבדה ומתועדת להסרה. אין צורך לייצא או לשתף PFX עם המפתח הפרטי.
Import-Certificate -FilePath .\CourseLab.cer `
-CertStoreLocation 'Cert:\LocalMachine\TrustedPeople' | Out-Null
לאחר הגדרת האמון נבדוק חתימה ונפרוס עבור המשתמש שמבצע את הבדיקה.
signtool verify /pa /v .\CourseWindow-1.0.0.0.msix
if ($LASTEXITCODE -ne 0) { throw 'Signature verification failed' }
Add-AppxPackage -Path .\CourseWindow-1.0.0.0.msix
Get-AppxPackage -Name AmitTech.CourseWindow | Select-Object Name, Version, Architecture
התהליך נשען על יצירת תעודת חבילה ועל חתימה עם SignTool. חתימה תקינה מוכיחה קשר בין זהות לתוכן שלא השתנה מאז החתימה; היא אינה הוכחה שאין בקוד באגים.
מעדכנים בלי לדרוס את הנתונים¶
נשנה פרט נראה בחלון, נבנה מחדש ונעלה את גרסת manifest ל1.0.1.0. השם והpublisher נשארים זהים כי מדובר באותו מוצר. ניצור חבילה חדשה בשם חדש, נחתום באותה זהות ונבדוק שדרוג מהגרסה הקודמת על מכונה נקייה.
קוד התוכנית והנתונים של המשתמש אינם אותו משאב. לא נשמור config משתנה ליד exe בתוך תיקיית התקנה מוגנת ונצפה שהinstaller ישמר אותו בכל שדרוג. נגדיר אזור נתונים, schema וגרסת פורמט, ונבחן כיצד מודל הpackage וההרשאות משפיעים על הpath שבחרנו.
החלפת binaries בחזרה לגרסה ישנה אינה בהכרח rollback של נתונים. אם גרסה חדשה כתבה schema שהישנה אינה יודעת לקרוא, נחזור לקוד ישן מול נתונים חדשים וניכשל שוב. נתכנן migration שניתן לעצור לפני commit, backup מתאים או תאימות קריאה לאחור, ונכתוב במפורש איזה מעבר אינו הפיך.
מסיימים את ניסוי ההתקנה¶
בסיום נסיר רק את package המעבדה. להסרת האמון נשתמש בthumbprint ששמרנו, ולא בחיפוש רחב לפי subject שיכול להתאים לתעודות אחרות. את הסרת האמון מבצעים עם ההרשאות המתאימות בVM.
Get-AppxPackage -Name AmitTech.CourseWindow | Remove-AppxPackage
$courseThumbprint = (Get-Content .\CourseLab-thumbprint.txt -Raw).Trim()
if ($courseThumbprint -notmatch '^[0-9A-Fa-f]{40}$') { throw 'Invalid lab thumbprint' }
Remove-Item -LiteralPath ('Cert:\LocalMachine\TrustedPeople\' + $courseThumbprint)
Remove-Item -LiteralPath ('Cert:\CurrentUser\My\' + $courseThumbprint)
המחיקה האחרונה מתייחסת למפתח הבדיקה של המשתמש שיצר אותו. אם משתמש ההקמה והמשתמש המוגבה שונים, נבצע כל שלב בstore של הבעלים המתאים. בדיקת הסרה מלאה כוללת גם בירור אילו נתוני משתמש נשמרו בכוונה ואילו קובצי מעבדה נשארו מחוץ לחבילה.