Compare commits

..

4 Commits

Author SHA1 Message Date
PointStar d2c54840ba publish: 1.2.6 — smoke-test the direct VSTOInstaller updater
No code change. Confirms that the v1.2.5 updater (VSTOInstaller.exe /I)
bypasses the Chrome download trap and pops the install dialog directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:11:38 +03:00
PointStar 870426ec08 fix(updater): invoke VSTOInstaller.exe directly so Chrome doesn't hijack the .vsto
v1.2.3 changed the updater to Process.Start the .vsto URL with
UseShellExecute=true. On any browser-as-default-handler (Chrome / Edge)
that downloads the file to disk instead of handing it to VSTOInstaller,
and re-opening the local copy then fails with InvalidDeploymentException
"לפריסה וליישום אין אזורי אבטחה תואמים" — the downloaded file lives in
the Local Machine zone while the manifest declares the Internet zone.

The reliable path is to invoke VSTOInstaller.exe directly with /I and
the manifest URL. The URL is then processed in the Internet zone,
matching what's declared in the manifest. FindVstoInstaller() looks at
the canonical VSTO 4.0 path and falls back to the HKCR registration of
the .vsto extension. If even that fails, we tell the user to close and
reopen Outlook — the VSTO runtime picks up the new manifest on its own
during startup, so the worst case is just an extra restart, not a stuck
install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:07:58 +03:00
PointStar 137929b4ac publish: 1.2.4 — smoke-test the new manifest-fetch updater
No code change. Confirms that the v1.2.3 updater (HTTP GET the manifest
+ Process.Start the .vsto URL) reaches VSTOInstaller cleanly without
the TrustNotGrantedException that the old ApplicationDeployment.Update
path always threw.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:01:17 +03:00
PointStar 95d7599cb1 fix(updater): replace broken ApplicationDeployment API with manifest GET
Log shows TrustNotGrantedException thrown inside CheckForDetailedUpdate
itself (ApplicationTrust.RequestTrust → DetermineTrustCore →
DetermineTrust). The misleading "המשתמש סירב להעניק את ההרשאות"
fires even when the user accepted the trust dialog on install and the
publisher cert is unchanged: ApplicationDeployment.CheckForDetailedUpdate
re-runs the TrustManager evaluation, which can't show a UAC-style
prompt from inside a hosted Outlook process and falls through to a
denial exception.

Bypass the ClickOnce update API entirely:
1. Read CurrentVersion from ApplicationDeployment (this part is fine)
2. HTTP GET the .vsto manifest at UpdateLocation, parse the top-level
   <assemblyIdentity> version
3. Compare versions
4. If newer, Process.Start the .vsto URL with UseShellExecute=true so
   Windows hands it to VSTOInstaller.exe — which is the only thing that
   actually knows how to update a running VSTO add-in correctly

ApplicationDeployment.Update() is also dropped because it has the same
in-process limitation as the trust check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:57:02 +03:00
3 changed files with 143 additions and 11 deletions
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Media;
@@ -178,18 +179,78 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
}
var deployment = System.Deployment.Application.ApplicationDeployment.CurrentDeployment;
var info = await Task.Run(() => deployment.CheckForDetailedUpdate(false)).ConfigureAwait(true);
if (!info.UpdateAvailable)
var currentVersion = deployment.CurrentVersion;
var manifestUri = deployment.UpdateLocation;
// ApplicationDeployment.CheckForDetailedUpdate triggers a
// TrustManager re-evaluation which throws TrustNotGranted
// inside a hosted VSTO process even when the publisher and
// permissions are unchanged. Bypass it by fetching the
// deployment manifest directly and reading the version.
Version? availableVersion = null;
try
{
UpdateStatusBrush = SuccessBrush;
UpdateStatus = "אתה על הגרסה האחרונה (" + deployment.CurrentVersion + ").";
availableVersion = await Task.Run(() => FetchManifestVersion(manifestUri)).ConfigureAwait(true);
}
catch (Exception fetchEx)
{
_logger.Warning(fetchEx, "CheckForUpdate: manifest fetch failed");
UpdateStatusBrush = ErrorBrush;
UpdateStatus = "שגיאה בבדיקת השרת: " + fetchEx.Message;
return;
}
UpdateStatus = $"זמינה גרסה {info.AvailableVersion} — מוריד…";
var result = await Task.Run(() => deployment.Update()).ConfigureAwait(true);
UpdateStatusBrush = SuccessBrush;
UpdateStatus = $"הותקנה גרסה {info.AvailableVersion}. סגור ופתח את Outlook כדי לטעון אותה.";
if (availableVersion == null)
{
UpdateStatusBrush = ErrorBrush;
UpdateStatus = "לא ניתן לקרוא את גרסת השרת מ-" + manifestUri;
return;
}
if (availableVersion <= currentVersion)
{
UpdateStatusBrush = SuccessBrush;
UpdateStatus = "אתה על הגרסה האחרונה (" + currentVersion + ").";
return;
}
// VSTO add-ins cannot be updated programmatically from inside
// a running Outlook — ApplicationDeployment.Update() throws.
// Don't shell-launch the .vsto URL either: Chrome / Edge will
// download it to disk, and the resulting file has a different
// security zone than the deployment manifest, which fails with
// InvalidDeploymentException ("no matching security zones").
// Call VSTOInstaller.exe directly so the URL is processed in
// the Internet zone, matching the manifest.
var installerPath = FindVstoInstaller();
if (installerPath != null)
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(
installerPath, "/I \"" + manifestUri + "\"")
{
UseShellExecute = false
});
UpdateStatusBrush = SuccessBrush;
UpdateStatus = $"זמינה גרסה {availableVersion} — אשר את ההתקנה בחלון שנפתח, ואז סגור ופתח את Outlook.";
}
catch (Exception launchEx)
{
_logger.Warning(launchEx, "CheckForUpdate: VSTOInstaller launch failed");
UpdateStatusBrush = ErrorBrush;
UpdateStatus = $"זמינה גרסה {availableVersion} — סגור ופתח את Outlook כדי להתקין אוטומטית.";
}
}
else
{
// VSTOInstaller not at the canonical path — VSTO runtime
// still picks up new manifests on Outlook startup, so
// ask the user to restart instead of attempting a hack.
UpdateStatusBrush = SuccessBrush;
UpdateStatus = $"זמינה גרסה {availableVersion} — סגור ופתח את Outlook כדי להתקין אוטומטית.";
}
return;
}
catch (System.Deployment.Application.DeploymentDownloadException ex)
{
@@ -215,6 +276,77 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
}
}
/// <summary>
/// Reads the top-level &lt;assemblyIdentity&gt; version from the
/// .vsto deployment manifest at <paramref name="manifestUri"/>.
/// Returns null if the document doesn't look like a deployment
/// manifest. Runs on a worker thread (synchronous HTTP).
/// </summary>
private static Version? FetchManifestVersion(Uri manifestUri)
{
// The .vsto manifest is small (≈6KB). Keep this synchronous —
// no point pulling in HttpClient and its lifecycle just for one
// GET. WebRequest works fine on .NET FW 4.8 and through proxies.
var req = System.Net.WebRequest.Create(manifestUri);
req.Timeout = 10_000;
using (var response = req.GetResponse())
using (var stream = response.GetResponseStream())
{
var doc = System.Xml.Linq.XDocument.Load(stream);
System.Xml.Linq.XNamespace asmv1 = "urn:schemas-microsoft-com:asm.v1";
var identity = doc.Root?
.Elements()
.FirstOrDefault(e => e.Name.LocalName == "assemblyIdentity" && e.Name.Namespace == asmv1);
var versionAttr = identity?.Attribute("version")?.Value;
if (string.IsNullOrWhiteSpace(versionAttr)) return null;
return Version.TryParse(versionAttr, out var v) ? v : null;
}
}
/// <summary>
/// Locates VSTOInstaller.exe on the machine. The path is stable
/// for VSTO 4.0 (Office 2010+) — falls back to a registry lookup
/// if the default install location was customised.
/// </summary>
private static string? FindVstoInstaller()
{
string[] candidates =
{
@"C:\Program Files (x86)\Common Files\Microsoft Shared\VSTO\10.0\VSTOInstaller.exe",
@"C:\Program Files\Common Files\Microsoft Shared\VSTO\10.0\VSTOInstaller.exe"
};
foreach (var p in candidates)
{
if (System.IO.File.Exists(p)) return p;
}
// Registry fallback: HKCR\VstoFile\shell\Open\command stores
// the verb VSTOInstaller registers for the .vsto extension.
try
{
using (var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(@"VstoFile\shell\Open\command"))
{
var cmd = key?.GetValue(null) as string;
if (!string.IsNullOrEmpty(cmd))
{
// Value looks like: "C:\...\VSTOInstaller.exe" "%1"
var firstQuote = cmd!.IndexOf('"');
if (firstQuote == 0)
{
var endQuote = cmd.IndexOf('"', 1);
if (endQuote > 1)
{
var exe = cmd.Substring(1, endQuote - 1);
if (System.IO.File.Exists(exe)) return exe;
}
}
}
}
}
catch { /* registry access denied or schema differs — fall through */ }
return null;
}
private bool CanCheckForUpdate() => !IsCheckingForUpdate;
partial void OnIsCheckingForUpdateChanged(bool value)
+1 -1
View File
@@ -36,7 +36,7 @@
<PublishUrl>C:\Users\Chaim\source\repos\OutlookAddin\Publish\</PublishUrl>
<InstallUrl>https://gitea.dev.marcus-law.co.il/espocrm-extensions/OutlookAddin/raw/branch/main/Publish/</InstallUrl>
<TargetCulture>en</TargetCulture>
<ApplicationVersion>1.2.2.0</ApplicationVersion>
<ApplicationVersion>1.2.6.0</ApplicationVersion>
<AutoIncrementApplicationRevision>false</AutoIncrementApplicationRevision>
<UpdateEnabled>true</UpdateEnabled>
<UpdateInterval>7</UpdateInterval>
+2 -2
View File
@@ -33,6 +33,6 @@ using System.Security;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.2.0")]
[assembly: AssemblyFileVersion("1.2.2.0")]
[assembly: AssemblyVersion("1.2.6.0")]
[assembly: AssemblyFileVersion("1.2.6.0")]