Thomas Reinwart

Einleitung

Früher oder später steht jeder vor der Frage, in welcher Form liefert man seine Software aus und wie informiert man seine Kunden über Updates. Für jeden Projekttyp kann eine andere Deployment Strategie Sinn machen bzw. ist durch den verwendeten Projekttyp ausschließlich möglich.

Da sich eine Windows Applikation in der Realität nicht professionell als single exe oder zip ausliefern lässt, bleibt nur ein Setup Produkt oder die Auslieferung mittels Microsoft ClickOnce über. Eine Information über eine Aktualisierung des Produkts auf einer Webseite alleine ist nicht mehr zeitgemäß, eine aktive Prüfung durch die Applikation muss her.

Feature ClickOnce MSI Windows Installer
Automatic update Yes Yes
Post-installation rollback Yes No
Update from Web Yes No
Does not affect shared components or other applications Yes No
Security permissions granted Grants only permissions necessary for the application (more safe) Grants Full Trust by default (less safe)
Security permissions required Internet or Intranet Zone (Full Trust for CD-ROM installation) Administrator
Application and deployment manifest signing Yes No
Installation-time user interface Single prompt Multipart Wizard
Installation of assemblies on demand Yes No
Installation of shared files No Yes
Installation of drivers No Yes (with custom actions)
Installation to Global Assembly Cache No Yes
Installation for multiple users No Yes
Add application to Start menu Yes Yes
Add application to Startup group No Yes
Add application to Favorites menu No Yes
Register file types Yes Yes
Install time registry access Limited Yes
Binary file patching No Yes
Application installation location ClickOnce application cache Program Files folder

Microsoft schlägt dazu in einem Strategie Dokument zur Auslieferung folgendes vor:

Microsoft ClickOnce

Mit dieser Technologie lassen sich auf einfache Weise Windows Anwendungen verteilen. Die Abhängigkeiten, wie etwa zu einer .net Framework Version, werden automatisch im Setup Wizzard erkannt. Möchte man eine neue Version seiner Anwendung erstellen, zählt ClickOnce die Version automatisch bei jedem Publish hinauf, man ergänzt gegebenenfalls nur seine neue Assembly und zählt die eigene Assembly Version hinauf. Aber über den Update Mechanismus selber muss man sich keine weiteren Gedanken machen. Das erstellte Setup kann auf verschiedenen Ablagen deponiert werden: UNC File Verzeichnis, ftp, http. Für die notwendige Security sorgt CAS (Code Access Security), dabei wird verhindert, dass Systemfunktionen nicht von einem ClickOnce-Programm aus dem Internet aufgerufen werden können. Nach der erstmaligen Installation wird am Client die Information der Installationsquelle gespeichert. Beim Starten der ausgelieferten Anwendung wird gegen die Installationsquelle überprüft, ob eine neuen Version vorliegt. Sollte die Quelle offline sein, lässt sich die Anwendung trotzdem starten. Beachten sollte man, dass beim Verlegen der Installationsquelle am Server die Clients keine Updates mehr erhalten. Das bemerkt man lange nicht, wundert sich aber dann, warum die Anwender keine Updates mehr erhalten.

Was zu den Projekttypen zu ergänzen ist: ClickOnce funktioniert bei Form und WPF Applikationen, hingegen bei Windows Services oder Web Applikationen nicht. Die installierten Pakete werden im User spezifischen Ablageverzeichnis gespeichert, pro User. Jedes ClickOnce-Programm ist vom anderen separiert. Teilen sich mehrere User einen Rechner, ist eine mehrfache Installation für jeden der User notwendig. Außerdem werden alte Versionen default nicht gelöscht, mit jedem Update kommt eine neue am Filesystem hinzu. ClickOnce hat den Vorteil, dass hier keine Admin Rechte zur Installation notwendig sind. Eine ClickOnce Installation kann eine andere installierte ClickOnce nicht beeinflussen oder zerstören, alles ist separat und es gibt keine gemeinsamen Verzeichnisse zwischen ClickOnce Anwendungen.

Mit Windows 8 kam noch eine weitere Hürde bei der ClickOnce Installation dazu. Wurde die Installations http Url nicht mit einem Zertifikat ausgestattet, weist Windows auf eine unsichere Quelle hin, sofern die Applikation außerhalb der eigenen Domain ausgeliefert wurde. Das lässt sich zwar im Installationsdialog von Windows umgehen, jedoch ist dies bei jedem der Updates notwendig. Das schafft wenig Vertrauen in das eigene Produkt. Die Lösung ist hier ein Zertifikat, dass die Option „Code Signing“ enthält, welches man aber z.B. bei GeoTrust käuflich erwerben muss.

Visual Studio Installer

Ab  Visual Studio 2013 liefert Microsoft kein Setup Project Template mehr aus. Stattdessen wird ein Installshield light angeboten. Über das Menü Extension and Updates in Visual Studio kann das Visual Studio Installer Project aber nachinstalliert werden. Anschließend gibt es eine Variante mit Wizzard bei der Zusammenstellung des eigenen Setups und eine ohne Wizzard. Als Ergebnis erhält man ein msi File und ein Setup.exe. Für den Autoupdater benötigen wir nur das msi File.

Autoupdater Produkte

Gleich Vorweg: es gibt keinen zwingenden Zusammenhang zwischen Microsoft Visual Studio Installer und einem der Autoupdater Produkte. Sie können auch ein anderes Setup Projekt wählen. Der Updater prüft die Möglichkeit eines Updates und startet dann bloß das msi File. Das Setup selber muss seine eigene Installation handhaben und mit einem Update der Files zurechtkommen.

Inzwischen gibt es eine Palette von Libraries, nuget Packages und individuelle Lösungen, die sich dem Thema annehmen. Über die Seite nugetmusthaves [1] kann man sich einen Überblick über die beliebtesten Produkte schaffen. Das nuget package von Netsparkle liegt auf GitHub.

Installation NetSparkle.new

Die Installation von NetSparkle.New in Visual Studio erfolgt über nuget [2] :

nuget Package: Install-Package NetSparkle.New

Implementierung des Updaters in der Application

using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using NetSparkle;
namespace AutoUpdateSolution
{
    public partial class FormAutoUpdate : Form
    {
        private Sparkle _sparkle;
        public FormAutoUpdate()
        {
            InitializeComponent();
   _sparkle = new Sparkle(
      "http://obelix/NetSparkle/autoupdatesolution/appcast.xml",
      SystemIcons.Application, SecurityMode.Strict)  
      // choose SecurityMode
            {
                TrustEverySSLConnection = true,
                PrintDiagnosticToConsole =  true,
                UseNotificationToast = true, 
            };
            _sparkle.UpdateDetected += 
              new UpdateDetected(_sparkle_updateDetected);
   _sparkle.PrintDiagnosticToConsole = true;   
              // Output console info
       _sparkle.StartLoop(true);
            //if (_sparkle.IsUpdateLoopRunning)
            //    MessageBox.Show("Loop is running");
            //else
            //    MessageBox.Show("Loop is not running");
        }
        void _sparkle_updateDetected
              (object sender, UpdateDetectedEventArgs e)
        {
            DialogResult res = MessageBox.Show(
              "Update detected, perform unattended", "Update", 
              MessageBoxButtons.YesNoCancel);
  switch (res)
            {
                case DialogResult.Yes:
                    e.NextAction = 
                      NextUpdateAction.PerformUpdateUnattended;
                    break;
                case DialogResult.Cancel:
                    e.NextAction = 
                      NextUpdateAction.ProhibitUpdate;
                    break;
                default:
                    e.NextAction = 
                      NextUpdateAction.ShowStandardUserInterface;
                    break;
            }
        }
   private void Form1_FormClosing
                (object sender, FormClosingEventArgs e)
        {
            _sparkle.StopLoop();
        }
        private void buttonCheckUpdate_ClickAsync
                     (object sender, EventArgs e)
        {
            Task.WaitAll(Task.Run(async () => 
                 await CheckAsync()));
        }
        private async Task CheckAsync()
        {
            SparkleUpdateInfo updateInfo = 
               await _sparkle.CheckForUpdatesAtUserRequest();
            textBoxOuput.Text += updateInfo.Status.ToString();
            textBoxOuput.Text += updateInfo.Updates.ToString();
        }
    }
}

Der Company Name in der AssemblyInfo.cd ist zwingend zu ergänzen:

[assembly: AssemblyCompany("My Company")]

Nach dem Starten im Debugger und der manuellen Update Prüfung erscheint diese Fehlermeldung:

Um den Fehler zu finden ist es notwendig, mehr Informationen von Netsparkle auszugeben. Dazu ergänzt man

_sparkle.PrintDiagnosticToConsole = true;  

Vor (!) dem StartLoop Aufruf. Im Output Fenster von Visual Studio erscheint nun:

netsparkle: Starting update loop…
netsparkle: Reading config…
netsparkle: Downloading and checking appcast
netsparkle: Signature check of appcast failed
netsparkle: No version information in app cast found
netsparkle: Sleeping for an other 1440 minutes, exit event or force update
check event
netsparkle: Downloading and checking appcast
netsparkle: Signature check of appcast failed
netsparkle: No version information in app cast found

Im nächsten Schritt müssen wir uns laut Fehlermeldung um die passenden Signaturen kümmern.

Passende Signatur erstellen

Da NetSparkle Programme aus dem Internet herunterlädt und diese auf dem lokalen Rechner ausführt, sollte man die Update-Pakete absichern. Hierfür wird mit den mitgelieferten Tools zuerst ein Schlüsselpaar generiert.

Dazu erzeugen wir uns NetSparkleDSAHelper.exe, der ebenfalls auf der nuget Projektseite als Sourcecode vorliegt. 

NetSparkle.DSAHelper.exe
NetSparkle DSA Helper
(c) 2011 Dirk Eisenberg under the terms of MIT license
NetSparkle.DSAHelper.exe /genkey_pair 
Generates a public and a private DSA key pair which is stored in the current working directory. The private is stored in the file NetSparkle_DSA.priv 
The public key will be stored in a file named NetSparkle_DSA.pub. Add the public key file as resource to your application. 
NetSparkle.DSAHelper.exe /sign_update {YourPackage.msi} {NetSparkle_DSA.priv} 
Allows to sign an existing update package unattended. YourPackage.msi has to be a valid path to the package binary as self (mostly Windows Installer packages).
The NetSparkle_DSA.priv has to be a path to the generated DAS private key, which has to be used for signing.
NetSparkle.DSAHelper.exe /verify_update {YourPackage.msi} {NetSparkle_DSA.pub}
"{Base64SignatureString}"Storing public key to NetSparkle_DSA.pub

1)

NetSparkle.DSAHelper.exe /genkey_pair
NetSparkle DSA Helper
(c) 2011 Dirk Eisenberg under the terms of MIT license 
Generating key pair with 1024 Bits... 
Storing private key to NetSparkle_DSA.priv 
Storing public key to NetSparkle_DSA.pub

2)

NetSparkle.DSAHelper.exe /sign_update setupproject.msi NetSparkle_DSA.priv ZivBB/nU40jhCWnz2ifOgAuHYotRYPSNXkObV5lvtK9RNM4bBD3+4Q==

Es wird ein öffentlicher und privater Schlüssel erzeugt.

Der öffentliche Schlüssel wird im Projekt hinzugefügt und als Embedded Resource markiert:

Hinzufügen des privaten Schlüssels in die appcast.xml

Am Server im IIS legt man folgende Filestruktur an:

<?xml version="1.0" encoding="utf-8"?>

<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"  xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>AutoUpdateSolution</title>
    <link>http://obelix/NetSparkle/autoupdatesolution/versioninfo.xml</link>
    <description></description>
    <language>de</language>
    <item>
      <title>Version 1.0.1</title>
      <sparkle:releaseNotesLink>http://obelix/NetSparkle/autoupdatesolution/1.0.1/rnotes.md</sparkle:releaseNotesLink>
      <pubDate>Wed, 19 Jul 2017 17:00:00 +0000</pubDate>
      <enclosure
url="http://obelix/NetSparkle/autoupdatesolution/1.0.1/SetupProject.msi"
length="556032"
type="application/octet-stream"
sparkle:version="1.0.1"
sparkle:dsaSignature="ZivBB/nU40jhCWnz2ifOgAuHYotRYPSNXkObV5lvtK9RNM4bBD3+4Q=="
/>
    </item>
  </channel>
</rss>

App.config Proxy Konfiguration

Falls im netsparkl Output eine Fehlermeldung auf ein Netzwerk Problem hinweist, kann es am Proxy liegen. Mit dieser Einstellung in der app.config werden die Settings von default proxy verwendet.

<!--?xml version="1.0" encoding="utf-8" ?-->
<configuration>
    <startup> 
        <supportedruntime version="v4.0" sku=".NETFramework,Version=v4.5.2">
    </supportedruntime></startup>
  <system.net>
    <defaultproxy usedefaultcredentials="true">
    </defaultproxy>
  </system.net>
</configuration>

Signierte Updates erstellen – appcast.xml.dsa

Der Fehler “netsparkle: Signature check of appcast failed” deutet darauf hin, das am Server die appcast.xml.dsa entweder nicht vorhanden oder ungültig ist.

Ob im IIS die Extension „dsa“ auch konfiguriert wurde, testet man, indem man die Url im Browser aufruft:

http://obelix/NetSparkle/autoupdatesolution/appcast.xml.dsa

Hier war der Fehler im IIS, die Extension „dsa“ war nicht definiert.

HTTP Error 404.3 - Not Found

The page you are requesting cannot be served because of the extension configuration. If the page is a script, add a handler. If the file should be downloaded, add a MIME map.

hinzugefügter MIME Type .dsa

appcast.xml.dsa muss dazu mit NetSparkle.DSAHelper.exe erstellt werden.

Default Construktor ist mit 
_sparkle = new Sparkle("http://obelix/NetSparkle/autoupdatesolution/appcast.xml", SystemIcons.Application) der SecurityMode Strict.

Bei ungültiger sparkle:dsaSignature:

Gültige sparkle:dsaSignature:

Unsignierte Updates verwenden

Ist der Aufwand zu hoch, können mit diesem Constructor auch unsignierte Updates verwendet werden.

_sparkle = new Sparkle("http://obelix/NetSparkle/autoupdatesolution/appcast.xml", SystemIcons.Application, SecurityMode.Unsafe)

Release Notes

Die Release Notes werden in einem Markdown File mit der Endung „.md“, „.mkdn“, „.mkd“, „.markdown“ erstellt. Markdown ist eine einfache Auszeichnungssprache, dabei wird der Text in ein gültiges W3C XHTML umgewandelt. Als Editor kann man Visual Studio oder Visual Studio Code verwenden, es gibt diverse weitere Markdown Extensions [3].

Beispiel

# Sample Application Release Notes
## 1.0.1
* Fixed bug where ExecuteAsync sometimes doesn't send data

Die md Extension muss im IIS in den Mime Types hinzugefügt werden:

hinzugefügter MIME Type .md

Update Test

Das MSI Setup wird ohne UAC Dialog ausgeführt.

Fazit zu NetSparkle.new

Die Dokumentation des NetSparkle.new Projekts ist noch verbesserungswürdig.

Durch die verschiedenen Ableger von NetSparkle und deren leicht unterschiedlichen APIs, nicht dokumentieren Fehlermeldungen, blieb mir zeitweise nichts anderes übrig, als den Update Vorgang mit dem Sourcecode während des Update Vorganges zu debuggen. Zu einem appcast.xml Beispiel gelangt man, in dem man jenes vom Server der Demo App verwendet. Das für die Signatur ein weiteres appcast.xml.dsa File benötigt wird, findet man leider nur im Debugger heraus.

Zukunftsaussicht MSIX

Auf Windows Rechner werden Applikationen mit MSI oder Setup.exe installiert, Applikationen aus dem Store über appx. Der Nachfolger von MSI steht bereits in den Startlöchern, MSIX ein neues Containerformat vom Microsoft zur Software Auslieferung, dass alle Formate zusammenfasst. Damit kann man seine Produkte über den Store als auch über Downloads anbieten. MSIX bietet unter anderem eine höhere Sicherheit, auch Updates lassen sich damit einfacher einspielen. Das MSIX Setup Tool liegt bereits im Windows Store. Im April 2019 erscheint Visual Studio 2019, wir werden sehen, was hier für neue Möglichkeiten im Auslieferungsprozess angeboten werden.

Fazit – für welche Deployment Schiene entscheide ich mich nun

Sofern der .net Projekt Typ nicht bereits die Technologie der Auslieferung bestimmt und eine Auswahl möglich ist, haben beide Technologien seine Vor- und Nachteile.

Microsoft ClickOnce ist die schnelle einfache Alternative, bei der allerdings ein Zertifikat notwendig wird, sobald man seine Windows Domäne bei der Auslieferung verlässt. In der eigenen Domäne, bei mehrfach durch Benutzer genutzten Rechner, ist eine Installation für jeden einzelnen User am gleichen Rechner notwendig. Wobei jeder User dies ohne zusätzliche Admin Rechte selber ausführen kann.

Handelt es sich um eine anspruchsvolle Anwendung, bei der auch Treiber installiert oder ähnliche Eingriffe im Windows System notwendig werden, also alles was über den User spezifischen Ordner wie bei ClickOnce hinausgeht, bleibt ohnehin nur mehr ein klassische Windows Setup über. Aber mit NetSparkle.new nun auch mit einer automatischen Update Möglichkeit.

Links & Quellen

[1]   nugetmusthaves
http://nugetmusthaves.com/Tag/AutoUpdate

[2]   NetSparkle.New
https://www.nuget.org/packages/NetSparkle.New/

[3]   Markdown
https://de.wikipedia.org/wiki/Markdown

Autorenbox

Thomas Reinwart verfügt über umfangreiche Berufserfahrung auf dem IT Sektor. In den letzten 25 Jahren war er in den Bereichen Softwareentwicklung, Softwaredesign, Architekt und als Consultant tätig. Technischer Fokus ist derzeit Microsoft .net und SQL Server, wo er alle aktuellen Microsoft Zertifizierungen hat.

Email: office@reinwart.com

Zur Werkzeugleiste springen