Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / Visual Basic 12

UpdaterApp - a Library for Easy Update

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
26 Sep 2019CPOL4 min read 8K   316   20   4
This article explains an easy method to download and update your WinForms application

Introduction

Whenever we develop software, we need automatic updating. This allows the developer to take advantage of automatic resources through an open source library developed 100% in VB.NET and can be used in C # and other languages.
So in this article, I propose a library (.dll), which is based on the AntSoft Systems On Demand (ASUpdater) library (I am licensed to distribute this code because I am the author of it and CEO of the company).

Requirements

  • .NET Framework 4.5 or higher
  • System.Windows.Forms
  • System.Reflection
  • System.Xml
  • System.Security.Cryptography
  • System.ComponentModel
  • System.Drawing

Code of the Library

Module

The module has basic and common library functions, as well as application information such as Path, Title, and Versions.

In this module, we have the variables, which will receive the application data and the functions UrlExist, GotInternet, CreateHashPure, GetMd5Hash and sub MainLib.

  • Public Function UrlExist (ByVal sUrl As String): Check if the URL exists and return a boolean. You must also enter the path with the file to be downloaded.
  • GotInternet(): Checks if computer is connected to the network. The function returns a Boolean response.
  • CreateHashPure(ByVal sString As String, Optional iSize As Integer = 32): Create MD5 Hash.
    • sString. Enter the variable you want to encrypt in MD5.
    • iSize: Enter the size you want to return encryption in MD5, maximum size is 32.

    The function returns a String response.

  • GetMd5Hash(ByVal md5Hash As MD5, ByVal input As String): The function return a String response.
  • MainLib(). Load the information of the Application on Start.

In module mMain, we have the following code:

VB.NET
  1  Imports System.Windows.Forms
  2  Imports System.Net
  3  Imports System.Xml
  4  Imports System.Text
  5  Imports System.Text.RegularExpressions
  6  Imports System.Security.Cryptography
  7  Imports System.ComponentModel
  8  Imports System.Drawing
  9  Module mMain
 10      Public sPath As String 'Application Path 
 11      Public sTitle As String 'Application Title
 12      Public sVerActual As String 'Actual Application Major Version with format 1
 13      Public sVerActualSimp As String 'Actual Application Version with simple format 1.0
 14      'Sets the Update Version to the Application version, 
 15      'but in the future, the version will receive information from the configuration file, 
 16      'which is on the server hosting the new version installation file.
 17      Public sVerUpdate As FileVersionInfo = _
 18             FileVersionInfo.GetVersionInfo(Application.ExecutablePath)
 19      Public sVersion As String
 20      Public PicUpdateImg As System.Drawing.Image
 21      Public PicAboutImg As System.Drawing.Image
 22      Public LibUpdate As New Updater
 23  
 24      Public Sub MainLib()
 25          'Check if Application path is Root Drive e has or not
 26          If Application.StartupPath.EndsWith("\") Then
 27              sPath = Application.StartupPath
 28          Else
 29              sPath = Application.StartupPath & "\"
 30          End If
 31          'Load to PicUpdateImg (Image Object) the resource image file
 32          PicUpdateImg = My.Resources.ASSUpdater
 33          'Load to PicAboutImg (Image Object) the resource image file
 34          PicAboutImg = My.Resources.ASSUpdaterAbout
 35          'Load the necessary info to run this application
 36          With My.Application.Info
 37              sTitle = .ProductName.ToString & " v" & .Version.Major & "." & .Version.Minor
 38              sVerActual = .Version.Major.ToString
 39              sVerActualSimp = String.Format("{0}.{1}", _
 40                               .Version.Major.ToString, .Version.Minor.ToString)
 41              sVersion = String.Format("Versão {0}", My.Application.Info.Version.ToString)
 42          End With
 43      End Sub
 44      ''' <summary>
 45      ''' Check if URL exists and return a boolean 
 46      ''' </summary>
 47      ''' <param name="sUrl">Enter the URL you want to check for. 
 48      ''' The URL must be complete, including the name of the file to download.</param>
 49      ''' <returns>A boolean response</returns>
 50      ''' <remarks></remarks>
 51      Public Function UrlExist(ByVal sUrl As String) As Boolean
 52          Dim fileUrl As Uri = New Uri(sUrl)
 53          Dim req As System.Net.WebRequest
 54          req = System.Net.WebRequest.Create(sUrl)
 55          Dim resp As System.Net.WebResponse
 56          Try
 57              resp = req.GetResponse()
 58              resp.Close()
 59              req = Nothing
 60              Return (True)
 61          Catch ex As Exception
 62              req = Nothing
 63              Return False
 64          End Try
 65      End Function
 66      ''' <summary>
 67      ''' Checks if computer is connected to network
 68      ''' </summary>
 69      ''' <returns>Boolean Response</returns>
 70      ''' <remarks></remarks>
 71      Public Function GotInternet() As Boolean
 72          Try
 73              Return My.Computer.Network.IsAvailable
 74          Catch weberrt As WebException
 75              Return False
 76          Catch except As Exception
 77              Return False
 78          End Try
 79      End Function
 80      ''' <summary>
 81      ''' Create MD5 Hash
 82      ''' </summary>
 83      ''' <param name="sString">Enter the variable you want to encrypt in MD5</param>
 84      ''' <param name="iSize">Enter the size you want to return encryption in MD5, 
 85      ''' maximum size is 32</param>
 86      ''' <returns></returns>
 87      ''' <remarks></remarks>
 88      Public Function CreateHashPure(ByVal sString As String, _
 89                      Optional iSize As Integer = 32) As String
 90          Using md5Hash As MD5 = MD5.Create()
 91              Dim hash As String = GetMd5Hash(md5Hash, sString)
 92              hash = Left(hash, iSize)
 93              Return LCase(hash)
 94          End Using
 95      End Function
 96      ''' <summary>
 97      ''' Function to Create MD5 Hash, with base the set information
 98      ''' </summary>
 99      ''' <param name="md5Hash">A MD5 Hash created</param>
100      ''' <param name="input">Input string</param>
101      ''' <returns>Return string response</returns>
102      ''' <remarks></remarks>
103      Public Function GetMd5Hash(ByVal md5Hash As MD5, ByVal input As String) As String
104  
105          ' Convert the input string to a byte array and compute the hash.
106          Dim data As Byte() = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input))
107  
108          ' Create a new Stringbuilder to collect the bytes
109          ' and create a string.
110          Dim sBuilder As New StringBuilder()
111  
112          ' Loop through each byte of the hashed data 
113          ' and format each one as a hexadecimal string.
114          Dim i As Integer
115          For i = 0 To data.Length - 1
116              sBuilder.Append(data(i).ToString("x2"))
117          Next i
118  
119          ' Return the hexadecimal string.
120          Return sBuilder.ToString()
121  
122      End Function 'GetMd5Hash
123  
124  End Module

Class

The library has a class named Updater.vb. In this module class, many functions are implemented. This is the heart of the Library, with Functions, Subs and Property. The most important function, which the other, maybe is CheckNewVersion(), which checks if there is a new version to download and returns a boolean response. With base in this response, it's possible to know if the application needs the update.

Other functions and property, as well as Subs, are important because they are part of the library, but they complement other Functions or even Subs. Below is the commented and complete code of the Class, so that the developer can better understand it.

VB.NET
Imports System.Windows.Forms
Imports System.Reflection
Imports System.Xml
Imports System.IO

Public Class Updater
    'Define the variables that received values to use by Application
    Private _versionapp As String
    Private _codeapp As String
    Private _nameapp As String
    Private _urlconfig As String
    Private sNewVersion As String, sDescUpdate As String, sDateUpdate As String
    'Create a Check Update Response for decision making
    Public Enum CheckResponse
        res_empty = 0
        res_noupdate = 1
        res_update = 2
        res_erro = 3
    End Enum

    Public Sub New()
        MainLib()
    End Sub
    ''' <summary>
    ''' After check if has a new version to download, the Library showing a message
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub AppUpdate()
        Dim frm As New frmUpdateDesc 'create a new instance of frmUpdateDesc
        'Show the information about 
        frm.wbDescription.DocumentText = My.Resources.HTMLStart & _
            "<h4>A new version is available for download.</h4>" &
            "<p>Version: " & sNewVersion & "<br/>" &
            "Date: " & sDateUpdate & "</p>" &
            "<div>Description: " & sDescUpdate & "</div>" &
            "<p>You want to get the new version now?<br/>" &
            "To start the download, click the Download button.!</p>" & My.Resources.HTMLEnd
        frm.ShowDialog()
    End Sub
    ''' <summary>
    ''' Displays information obtained in the CheckNewVersion function.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub CheckUpdate()
        'check new update
        Dim CheckUpdataRes As CheckResponse = CheckNewVersion()
        If CheckUpdataRes = CheckResponse.res_update Then 'check new update
            AppUpdate() 'show update form
        ElseIf CheckUpdataRes = CheckResponse.res_noupdate Then
            MessageBox.Show("Your software has the latest version and is up to date!", _
            sTitle, MessageBoxButtons.OK, MessageBoxIcon.Information)
        ElseIf CheckUpdataRes = CheckResponse.res_empty Then 'check new update
            Exit Sub
        ElseIf CheckUpdataRes = CheckResponse.res_erro Then 'check new update
            MessageBox.Show("Could not check for update!", sTitle, _
                             MessageBoxButtons.OK, MessageBoxIcon.Error)
        Else
            MessageBox.Show("Error!", sTitle, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Sub
    ''' <summary>
    ''' Check Online Version
    ''' </summary>
    ''' <returns>Return A Enum a CheckResponse Value</returns>
    ''' <remarks></remarks>
    Public Function CheckNewVersion() As CheckResponse
        Dim PathTemp As String
        PathTemp = System.IO.Path.GetTempPath()
        Try
            'Allows you to verify that all required Properties have been set 
            'so that the library can check for a new update version.
            'Check ULRConfig
            If String.IsNullOrWhiteSpace(URLConfig) Then
                MessageBox.Show("You must specify the configuration file path to check _
                for update!", sTitle, MessageBoxButtons.OK, MessageBoxIcon.Information)
                Return CheckResponse.res_empty
            End If
            'Check VersionApp
            If String.IsNullOrWhiteSpace(VersionApp) Then
                MessageBox.Show("Application version must be specified to check _
                for update!", sTitle, MessageBoxButtons.OK, MessageBoxIcon.Information)
                Return CheckResponse.res_empty
            End If
            'Check NameApp
            If String.IsNullOrWhiteSpace(NameApp) Then
                MessageBox.Show("You must specify the application name to check _
                for updates.!", sTitle, MessageBoxButtons.OK, MessageBoxIcon.Information)
                Return CheckResponse.res_empty
            End If
            'Check if computer is connected a Network
            If GotInternet() = False Then
                MessageBox.Show("Unable to check software update. _
                                 No Internet connection verified!", sTitle, _
                                 MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
                Return False
            End If
            'Allows you to check if the given URL is true or if the given file exists 
            'on the server
            If UrlExist(URLConfig) = True Then
                'Checks if Config file exists
                If File.Exists(PathTemp & My.Application.Info.AssemblyName & ".xml") _
                   Then File.Delete(PathTemp & My.Application.Info.AssemblyName & ".xml")
                'Starts configuration file download by saving to computer Temp folder
                My.Computer.Network.DownloadFile(URLConfig, PathTemp & _
                              My.Application.Info.AssemblyName & ".xml")
                'Start reading configuration information
                GoTo InitialDld
            Else

                Return CheckResponse.res_erro
            End If
InitialDld:
            Clipboard.SetText(PathTemp & My.Application.Info.AssemblyName & ".xml")
            'Checks if the configuration file was successfully obtained
            If System.IO.File.Exists(PathTemp & My.Application.Info.AssemblyName & ".xml") Then
                Dim dom As New XmlDocument()
                'Read information from the configuration file to pass to variables, 
                'allowing the library to tell if there is a need to update
                dom.Load(PathTemp & My.Application.Info.AssemblyName & ".xml")
                'Load Version Info
                Dim sVer As String = dom.SelectSingleNode("//Info/Version").InnerText
                'Load Date Info
                Dim sDate As String = dom.SelectSingleNode("//Info/Date").InnerText
                'Load File download (Install file) Info
                Dim sFileDld As String = dom.SelectSingleNode("//Info/Filename").InnerText
                'Load Description Info
                Dim sDescription As String = _
                        dom.SelectSingleNode("//Info//Description").InnerText
                'Compares the current version of the application 
                'to the obtained version through the configuration file. 
                'If the server version is larger then upgrade is enabled, 
                'if false reports no need for upgrade
                If sVerUpdate.FileVersion.ToString < sVer Then
                    sNewVersion = sVer
                    sDescUpdate = sDescription
                    sDateUpdate = sDate
                    LibUpdate.NameApp = NameApp
                    LibUpdate.VersionApp = VersionApp
                    LibUpdate.URLConfig = URLConfig
                    Return CheckResponse.res_update
                Else
                    Return CheckResponse.res_noupdate
                End If
            Else
                Return CheckResponse.res_erro
            End If
        Catch ex As Exception
            MessageBox.Show("Error: " & ex.Message & vbNewLine & vbNewLine & _
                            "StackTrace: " & ex.StackTrace, sTitle, MessageBoxButtons.OK, _
                             MessageBoxIcon.Stop)

            Return CheckResponse.res_erro
        Finally

        End Try
    End Function
    ''' <summary>
    ''' Get Library Assembly Version
    ''' </summary>
    ''' <returns>Return String Value</returns>
    ''' <remarks></remarks>
    Public Function LibVersion() As String
        Dim assy As System.Reflection.Assembly = GetType(Updater).Assembly
        Dim assyName As String = assy.GetName().Name
        'AssemblyTitle
        Dim attr As Attribute = Attribute.GetCustomAttribute( _
              assy, GetType(AssemblyTitleAttribute))
        Dim adAttr As AssemblyTitleAttribute = _
                    CType(attr, AssemblyTitleAttribute)
        Return String.Format("{0}", GetType(Updater).Assembly.GetName().Version)
    End Function
    ''' <summary>
    ''' Get Library Assembly Name (Title) and Version
    ''' </summary>
    ''' <returns>Return String Value</returns>
    ''' <remarks></remarks>
    Public Function LibNameVersion() As String
        Dim assy As System.Reflection.Assembly = GetType(Updater).Assembly
        Dim assyName As String = assy.GetName().Name
        'AssemblyTitle And AssemblyVersion
        Dim attr As Attribute = Attribute.GetCustomAttribute( _
              assy, GetType(AssemblyTitleAttribute))
        Dim adAttr As AssemblyTitleAttribute = _
                    CType(attr, AssemblyTitleAttribute)
        Return String.Format("{0} v{1}", adAttr.Title, _
               GetType(Updater).Assembly.GetName().Version)
    End Function
    ''' <summary>
    ''' Get Library Assembly Description
    ''' </summary>
    ''' <returns>Return String Value</returns>
    ''' <remarks></remarks>
    Public Function LibDescription() As String
        Dim assy As System.Reflection.Assembly = GetType(Updater).Assembly
        Dim assyName As String = assy.GetName().Name
        'AssemblyDescriptionAttribute
        Dim attr As Attribute = Attribute.GetCustomAttribute( _
              assy, GetType(AssemblyDescriptionAttribute))
        Dim adAttr As AssemblyDescriptionAttribute = _
                    CType(attr, AssemblyDescriptionAttribute)
        Return String.Format("{0}", adAttr.Description)
    End Function
    ''' <summary>
    ''' Get Library Assembly Name (Title)
    ''' </summary>
    ''' <returns>Return String Value</returns>
    ''' <remarks></remarks>
    Public Function LibName() As String
        Dim assy As System.Reflection.Assembly = GetType(Updater).Assembly
        Dim assyName As String = assy.GetName().Name
        'AssemblyTitleAttribute
        Dim attr As Attribute = Attribute.GetCustomAttribute( _
              assy, GetType(AssemblyTitleAttribute))
        Dim adAttr As AssemblyTitleAttribute = _
                    CType(attr, AssemblyTitleAttribute)
        Return String.Format("{0}", adAttr.Title)
    End Function
    ''' <summary>
    ''' Get Library Assembly Copyright
    ''' </summary>
    ''' <returns>Return String Value</returns>
    ''' <remarks></remarks>
    Public Function LibCopyright() As String
        Dim assy As System.Reflection.Assembly = GetType(Updater).Assembly
        Dim assyName As String = assy.GetName().Name
        'AssemblyCopyrightAttribute
        Dim attr As Attribute = Attribute.GetCustomAttribute( _
              assy, GetType(AssemblyCopyrightAttribute))
        Dim adAttr As AssemblyCopyrightAttribute = _
                    CType(attr, AssemblyCopyrightAttribute)
        Return String.Format("{0}", adAttr.Copyright)
    End Function
    ''' <summary>
    ''' Set and Get Application Version
    ''' </summary>
    ''' <returns>Return string Value</returns>
    Public Property VersionApp() As String
        Get
            Return _versionapp
        End Get
        Set(value As String)
            _versionapp = value
        End Set
    End Property
    ''' <summary>
    ''' Set and Get Application Name
    ''' </summary>
    ''' <returns>Return string Value</returns>
    Public Property NameApp() As String
        Get
            Return _nameapp
        End Get
        Set(value As String)
            _nameapp = value
        End Set
    End Property
    ''' <summary>
    ''' Set and Get URL of the XML File configuration and information
    ''' </summary>
    ''' <returns>Return string Value</returns>
    Public Property URLConfig() As String
        Get
            Return _urlconfig
        End Get
        Set(value As String)
            _urlconfig = value
        End Set
    End Property
End Class

Configuration XML File Structure

The structure of the XML file is important so that the library and its CheckNewVersion () function can read the information correctly and then pass the library variables, which will process the information and decide whether or not to update the software.

Below, we provide the structure of XML, which may have the name the developer wants, but it is recommended to be the same as AssemblyName, to facilitate the update management process if this library is used in multiple projects.

XML
<?xml version="1.0" encoding="utf-8"?>
<!--Arquivo de Atualização do Software Appsetup v1.0-->
<!--Date in US Format or in Your Region Format-->
<Application>
<Info>
  <Version>1.1.1.0321</Version>
  <Date>27/09/2019</Date>
  <Filename>http://antsoft.com.br/appsetup.exe</Filename>
  <Description>
    System Update Tool Improvement.
  </Description>
  <copyright>Copyright © 2019, AntSoft Systems On Demand</copyright>
</Info>
</Application>

Forms

The three forms in the library project are in the code available for download and will not be discussed in the article, since it is simple to understand because the code is all commented. Just note that in the form, there is a sub named UpdateService(), which is the core of the form, because this Sub is that, after checking the need for update, the library will download the configuration file in XML and after the installation file, which should preferably be exe or msi extension.

The following are the images of the two library forms. The first image is the Update Description Form, the second image is the Update Form, where the installation file will be obtained and then run to install the new version. In the example, the update file is not found because it is a demo.

Image 1

Image 2

In the frmUpdater Form, the Sub UpdateService() has an important point to explain, as it allows a better understanding of the developer:

VB.NET
Public Sub UpdateService()
       'Clear All items on Listview
       lvwUpdate.Items.Clear()
       'Add new item on ListView
       lvwUpdate.Items.Add("Starting the upgrade", 3)
       Try
           'Allows you to add two new Handles to allow you to generate file
           'download progress and completion
           AddHandler wc.DownloadProgressChanged, AddressOf OnDownloadProgressChanged
           AddHandler wc.DownloadFileCompleted, AddressOf OnFileDownloadCompleted
           'Add new item on ListView
           AddTextlvw("Checking for new version online...")
           'Checks if Config file exists
           If File.Exists(PathTemp & My.Application.Info.AssemblyName & ".xml") _
              Then File.Delete(PathTemp & My.Application.Info.AssemblyName & ".xml")
           'Get Config File
           My.Computer.Network.DownloadFile(UpdaterApp.LibUpdate.URLConfig, _
               PathTemp & My.Application.Info.AssemblyName & ".xml")

           If File.Exists(PathTemp & My.Application.Info.AssemblyName & ".xml") Then
               'Add new item on ListView
               AddTextlvw("Valid update found ...", 2)
           Else
               'Add new item on ListView
               AddTextlvw("Update not verified or process failed!", 1)
               'Add new item on ListView
               AddTextlvw("Try again!", 1)
               Exit Sub
               pBar.Visible = False
           End If
           'Add new item on ListView
           AddTextlvw("Starting version checking ...")
           Dim dom As New XmlDocument()
           'Read information from the configuration file to pass to variables,
           'allowing the library to tell if there is a need to update
           dom.Load(PathTemp & My.Application.Info.AssemblyName & ".xml")

           'Load Version Info
           sNewVersion = dom.SelectSingleNode("//Info/Version").InnerText
           'Load Date Info
           Dim sDate As String = dom.SelectSingleNode("//Info/Date").InnerText
           'Load File download (Install file) Info
           Dim sFileDld As String = dom.SelectSingleNode("//Info/Filename").InnerText
           'Get only filename of the URL Path
           Dim sFileTmp As String = System.IO.Path.GetFileName(sFileDld)
           sFileExec = System.IO.Path.GetFileName(sFileDld)
           'Add new item on ListView
           AddTextlvw("Current version: " & sVerUpdate.FileVersion, 5)
           'Add new item on ListView
           AddTextlvw("Downloadable Version: " & sNewVersion, 5)
           If sVerUpdate.FileVersion.ToString < sNewVersion Then
               'Add new item on ListView
               AddTextlvw("Starting the update file download.", 3)
               'Checks if Install File exists, if true Delete
               If System.IO.File.Exists(PathTemp & "\" & sFileTmp) Then _
                   Kill(PathTemp & "\" & sFileTmp)
               If UrlExist(sFileDld) = True Then
                   'Add new item on ListView
                   AddTextlvw("Downloading the installation file...")
                   'Start download of the Install file setup
                   wc.DownloadFileAsync(New Uri(sFileDld), PathTemp & "\" & sFileTmp)
               Else
                   'Add new item on ListView
                   AddTextlvw("Setup file not found!", 1)
                   pBar.Visible = False
                   Exit Sub
               End If
           Else
               'Add new item on ListView
               AddTextlvw("No need to update.", 4)
               pBar.Visible = False
               btnAbout.Enabled = True

           End If
       Catch ex As Exception
           'Add new item on ListView
           MessageBox.Show("Unable to update software. Error: " & vbNewLine & _
                           ex.Message, sTitle, _
                           MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
           AddTextlvw("Error trying to verify update.", 1)
           pBar.Visible = False

       End Try
   End Sub

Call UpdateApp Library

To call the new version, check function and then start the download if there is a new version available. It is very simple and done in a few command lines. You must reference the UpdateApp Library in the project references.

In a form, you need to enter the following command lines:

VB.NET
Public Class Form1
    Dim LibUpdater As New UpdaterApp.Updater
    Private Sub Button1_Click(sender As Object, e As EventArgs) _
            Handles Button1.Click, LinkLabel1.Click
        With LibUpdater
            'Change this URL to the path where your configuration file is hosted in XML format.
            .URLConfig = "http://www.antsoft.com.br/CPTests/UpdateApp/" & _
              My.Application.Info.AssemblyName & ".xml"
            'Application Name
            .NameApp = My.Application.Info.ProductName
            'Application Version in String Format
            .VersionApp = My.Application.Info.Version.ToString
            'Call the CheckUpdate Function
            .CheckUpdate()
        End With
    End Sub
End Class

Points of Interest

With the above code, it is possible to check a simple, yet efficient way to check for new updates of your application and it can make it easy for all your customers to get news and make it easy to maintain your software, thus optimizing time and allowing more time to devote to developing new versions.

History

This code is the release version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO AntSoft Systems On Demand
Brazil Brazil
I am an Agricultural Engineer with a PhD in Agronomy and a Postdoc in Insect Ecology. I am a self-taught developer of scientific and commercial software. I work with system developments for scientific societies, research institutes. I am also an advanced user of R statistics software and finally I develop software for ecological area.
This is a Organisation (No members)


Comments and Discussions

 
GeneralMy vote of 5 Pin
Kurtt1-Mar-20 14:31
Kurtt1-Mar-20 14:31 
excelent job.
GeneralRe: My vote of 5 Pin
William Costa Rodrigues19-Mar-20 10:42
William Costa Rodrigues19-Mar-20 10:42 
QuestionClickONce vs UpdaterApp Pin
kiquenet.com20-Dec-19 21:13
professionalkiquenet.com20-Dec-19 21:13 
AnswerRe: ClickONce vs UpdaterApp Pin
William Costa Rodrigues13-Feb-20 9:52
William Costa Rodrigues13-Feb-20 9:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.