代码之家  ›  专栏  ›  技术社区  ›  Jan Bannister

如何回滚ClickOnce应用程序?

  •  44
  • Jan Bannister  · 技术社区  · 17 年前

    有没有一种方法(hacky会做)允许用户返回到以前版本的 ClickOnce 网络部署应用程序?

    我查过文档和API,但似乎没有办法。你 可以 有选择地选择是否要更新,但一旦更新,似乎就没有办法返回了。

    9 回复  |  直到 7 年前
        1
  •  18
  •   Jonathan Allen    17 年前

    ClickOnce将使用您发送给他们的任何版本。如果您向他们发送一个旧版本,他们将回滚到那个旧版本。

    早在五月份,我的朋友大卫就写了一篇关于如何在每个用户的基础上实现这一点的文章。我们可以让每个用户使用不同的版本。应用程序甚至告诉数据库用户想要哪个版本,这样他们理论上可以更改他们的版本,然后简单地重新启动应用程序。

    Fine Grained Versioning with ClickOnce

        2
  •  81
  •   Peter Mortensen Pieter Jan Bonestroo    12 年前

    通过更改服务器清单文件,可以在服务器端恢复到旧版本。当客户端重新启动应用程序时,它将看到它的版本与服务器所说的“当前”版本不同,并将下载新版本。此服务器清单文件通常总是指向最新版本,但不必指向最新版本。

    以下是如何更改它(我使用Visual Studio 2008发布的)。其他版本可能具有不同的发布文件夹结构)。

    在publish.htm所在的文件夹中, XML 文档调用 [appName].application . 这是客户端用来比较其当前版本的服务器端清单文件。此文档中包含客户端应运行的“当前”版本以及可以在服务器上找到部署文件的位置。

    在同一位置 publish.htm 也是一个名为“应用程序文件”的文件夹。此文件夹包含以前每次发布的子文件夹。在每个子文件夹中都有另一个XML文档,与我上面提到的同名 [AppName].应用程序 . 复制此文件(从包含要还原到的版本的文件夹)并将其粘贴到与 出版公司 (几级以上)。当客户端应用程序重新启动时,它看起来就像新版本可用一样,下载并运行它。客户端现在将运行以前的版本。

        3
  •  7
  •   Patrick Desjardins    17 年前

    您可以进入“添加/删除应用程序”并选择您的应用程序,然后选择获取最后一次安装。

        4
  •  2
  •   Gulzar Nazim    17 年前

    你可以使用 MAGEUI 回滚到服务器上的以前清单版本。 Check this out .

        5
  •  1
  •   Peter Mortensen Pieter Jan Bonestroo    13 年前

    如果查看部署位置,您将看到每个以前的版本,在一个附加了版本号的单独文件夹中,以及附加了版本号的部署清单。

    您可以将其中任何一个重命名为当前部署,下次更新该应用程序时,它将拉入您回滚到的版本。

        6
  •  1
  •   Vitaliy Ulantikov Peter Miehle    12 年前

    我了解ClickOnce版本检查算法如下:

    1. 如果客户端上安装的版本=部署到服务器的版本-不执行任何操作
    2. 如果客户端版本<服务器版本-升级
    3. 如果客户端版本>服务器版本:
      1. 如果客户端上指定的最小版本=服务器版本-显示错误
      2. 如果在客户端<服务器版本上指定了最低版本-降级
      3. 如果客户端上未指定MinimumVersion-降级
        7
  •  0
  •   Peter Mortensen Pieter Jan Bonestroo    13 年前

    如果你认识出版商,这可以通过反思来实现。 URI 以及部署和应用程序的名称、版本语言公钥令牌和处理器体系结构。

    下面的代码将尝试回滚“coolapp.app” ClickOnce 应用。如果它无法回滚,它将尝试卸载它。

    using System;
    using System.Deployment.Application;
    using System.Reflection;
    
    namespace ClickOnceAppRollback
    {
        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            static void Main()
            {
                string appId = string.Format("{0}#{1}, Version={2}, Culture={3}, PublicKeyToken={4}, processorArchitecture={5}/{6}, Version={7}, Culture={8}, PublicKeyToken={9}, processorArchitecture={10}, type={11}",
                    /*The URI location of the app*/@"http://www.microsoft.com/coolapp.exe.application",
                    /*The application's assemblyIdentity name*/"coolapp.app",
                    /*The application's assemblyIdentity version*/"10.8.62.17109",
                    /*The application's assemblyIdentity language*/"neutral",
                    /*The application's assemblyIdentity public Key Token*/"0000000000000000",
                    /*The application's assemblyIdentity processor architecture*/"msil",
                    /*The deployment's dependentAssembly name*/"coolapp.exe",
                    /*The deployment's dependentAssembly version*/"10.8.62.17109",
                    /*The deployment's dependentAssembly language*/"neutral",
                    /*The deployment's dependentAssembly public Key Token*/"0000000000000000",
                    /*The deployment's dependentAssembly processor architecture*/"msil",
                    /*The deployment's dependentAssembly type*/"win32");
    
                var ctor = typeof(ApplicationDeployment).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(string) }, null);
                var appDeployment = ctor.Invoke(new object[] { appId });
    
                var subState = appDeployment.GetType().GetField("_subState", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(appDeployment);
                var subStore = appDeployment.GetType().GetField("_subStore", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(appDeployment);
                try
                {
                    subStore.GetType().GetMethod("RollbackSubscription").Invoke(subStore, new object[] { subState });
                }
                catch
                {
                    subStore.GetType().GetMethod("UninstallSubscription").Invoke(subStore, new object[] { subState });
                }
            }
        }
    }
    
        8
  •  0
  •   Peter Mortensen Pieter Jan Bonestroo    12 年前

    我只需要在我的实时生产服务器上做一个这样的操作,并且拥有所有这些笔记是很好的。我的解决方案有点不同,我想把它作为一个补丁添加进去。在进行生产部署之前,我总是预先备份整个包含文件夹。我可以将整个文件夹结构复制回原始状态,一切正常。

    此方法的注意事项:

    • 如果应用程序很大,或者应用程序文件文件夹中已经发布了许多版本,那么您的备份将很大。确保您有足够的空间(对我来说,存储空间不是对象)。
    • 权限有一种以这种方式攻击你的坏倾向。确保部署位置是为外部访问托管的,以便在还原之前和之后验证所有权限。
    • 在中回收应用程序池对我很有帮助。 IIS .
        9
  •  0
  •   Jon Roberts    7 年前

    刚刚使用它来回滚在Visual Studio 2017中开发的ClickOnce应用程序。在我的例子中,根文件夹中只有两个文件,一个名为[applicationname].manifest,另一个名为setup.exe。

    [applicationname].manifest包含对当前版本号的数字引用,但每个引用都链接到PublicKeyToken值,因此我不愿意手动编辑它。

    因此,在application files文件夹中,在包含要回滚到的版本的子文件夹下,我找到了另一个[applicationname].manifest,我将其复制到根文件夹(备份了原始文件)。

    就这样。这对我很有效,而且是一个非常简单的解决方案。不过,我没有使用最低要求的版本,所以不能说这是否会影响它。

    推荐文章